[WV2] Move to a new WardrobeController

This commit is contained in:
Emi Matchu 2025-11-11 17:21:03 -08:00
parent 811bb3e036
commit 78931ddb47
11 changed files with 277 additions and 268 deletions

View file

@ -77,53 +77,6 @@ class OutfitsController < ApplicationController
@campaign = Fundraising::Campaign.current rescue nil @campaign = Fundraising::Campaign.current rescue nil
end end
def new_v2
# Get selected species and color from params, or default to Blue Acara
@selected_species = params[:species] ? Species.find_by_id(params[:species]) : Species.find_by_name("Acara")
@selected_color = params[:color] ? Color.find_by_id(params[:color]) : Color.find_by_name("Blue")
# Load valid colors for the selected species (colors that have existing pet types)
@species = Species.alphabetical
@colors = @selected_species.compatible_colors
# Find the best pet type for this species+color combo
# If the exact combo doesn't exist, this will fall back to a simple color
@pet_type = PetType.for_species_and_color(
species_id: @selected_species.id,
color_id: @selected_color.id
)
# Use the pet type's actual color as the selected color
# (might differ from requested color if we fell back to a simple color)
@selected_color = @pet_type&.color
# Load items from the objects[] parameter
item_ids = params[:objects] || []
items = Item.where(id: item_ids)
# Build the outfit
@outfit = Outfit.new(
pet_state: @pet_type&.canonical_pet_state,
worn_items: items,
)
# Preload the manifests for all visible layers, so they load efficiently
# in parallel rather than sequentially when rendering
SwfAsset.preload_manifests(@outfit.visible_layers)
# Handle search mode
@search_mode = params[:q].present?
if @search_mode
search_filters = build_search_filters(params[:q], @outfit)
query_params = ActionController::Parameters.new(
search_filters.each_with_index.map { |filter, i| [i.to_s, filter] }.to_h
)
@query = Item::Search::Query.from_params(query_params, current_user)
@search_results = @query.results.paginate(page: params.dig(:q, :page), per_page: 30)
end
render layout: false
end
def show def show
@outfit = Outfit.find(params[:id]) @outfit = Outfit.find(params[:id])
@ -176,51 +129,5 @@ class OutfitsController < ApplicationController
:status => :bad_request :status => :bad_request
end end
def build_search_filters(query_params, outfit)
filters = []
# Add name filter if present
if query_params[:name].present?
filters << { key: "name", value: query_params[:name] }
end
# Add item kind filter if present
if query_params[:item_kind].present?
case query_params[:item_kind]
when "nc"
filters << { key: "is_nc", value: "true" }
when "np"
filters << { key: "is_np", value: "true" }
when "pb"
filters << { key: "is_pb", value: "true" }
end
end
# Add zone filter if present
if query_params[:zone].present?
filters << { key: "occupied_zone_set_name", value: query_params[:zone] }
end
# Always add auto-filter for items that fit the current pet
pet_type = outfit.pet_type
if pet_type
fit_filter = {
key: "fits",
value: {
species_id: pet_type.species_id.to_s,
color_id: pet_type.color_id.to_s
}
}
# Include alt_style_id if present
if outfit.alt_style_id.present?
fit_filter[:value][:alt_style_id] = outfit.alt_style_id.to_s
end
filters << fit_filter
end
filters
end
end end

View file

@ -0,0 +1,98 @@
class WardrobeController < ApplicationController
def show
# Get selected species and color from params, or default to Blue Acara
@selected_species = params[:species] ? Species.find_by_id(params[:species]) : Species.find_by_name("Acara")
@selected_color = params[:color] ? Color.find_by_id(params[:color]) : Color.find_by_name("Blue")
# Load valid colors for the selected species (colors that have existing pet types)
@species = Species.alphabetical
@colors = @selected_species.compatible_colors
# Find the best pet type for this species+color combo
# If the exact combo doesn't exist, this will fall back to a simple color
@pet_type = PetType.for_species_and_color(
species_id: @selected_species.id,
color_id: @selected_color.id
)
# Use the pet type's actual color as the selected color
# (might differ from requested color if we fell back to a simple color)
@selected_color = @pet_type&.color
# Load items from the objects[] parameter
item_ids = params[:objects] || []
items = Item.where(id: item_ids)
# Build the outfit
@outfit = Outfit.new(
pet_state: @pet_type&.canonical_pet_state,
worn_items: items,
)
# Preload the manifests for all visible layers, so they load efficiently
# in parallel rather than sequentially when rendering
SwfAsset.preload_manifests(@outfit.visible_layers)
# Handle search mode
@search_mode = params[:q].present?
if @search_mode
search_filters = build_search_filters(params[:q], @outfit)
query_params = ActionController::Parameters.new(
search_filters.each_with_index.map { |filter, i| [i.to_s, filter] }.to_h
)
@query = Item::Search::Query.from_params(query_params, current_user)
@search_results = @query.results.paginate(page: params.dig(:q, :page), per_page: 30)
end
render layout: false
end
private
def build_search_filters(query_params, outfit)
filters = []
# Add name filter if present
if query_params[:name].present?
filters << { key: "name", value: query_params[:name] }
end
# Add item kind filter if present
if query_params[:item_kind].present?
case query_params[:item_kind]
when "nc"
filters << { key: "is_nc", value: "true" }
when "np"
filters << { key: "is_np", value: "true" }
when "pb"
filters << { key: "is_pb", value: "true" }
end
end
# Add zone filter if present
if query_params[:zone].present?
filters << { key: "occupied_zone_set_name", value: query_params[:zone] }
end
# Always add auto-filter for items that fit the current pet
pet_type = outfit.pet_type
if pet_type
fit_filter = {
key: "fits",
value: {
species_id: pet_type.species_id.to_s,
color_id: pet_type.color_id.to_s
}
}
# Include alt_style_id if present
if outfit.alt_style_id.present?
fit_filter[:value][:alt_style_id] = outfit.alt_style_id.to_s
end
filters << fit_filter
end
filters
end
end

View file

@ -65,30 +65,6 @@ module OutfitsHelper
text_field_tag 'name', nil, options text_field_tag 'name', nil, options
end end
# Generate hidden fields to preserve outfit state in URL params.
# Use the `except` parameter to skip certain fields, e.g. to override
# them with specific values, like in the species/color picker.
def outfit_state_params(outfit = @outfit, except: [])
fields = []
fields << hidden_field_tag(:species, @outfit.species_id) unless except.include?(:species)
fields << hidden_field_tag(:color, @outfit.color_id) unless except.include?(:color)
unless except.include?(:worn_items)
outfit.worn_items.each do |item|
fields << hidden_field_tag('objects[]', item.id)
end
end
unless except.include?(:q)
(params[:q] || {}).each do |key, value|
fields << hidden_field_tag("q[#{key}]", value) if value.present?
end
end
safe_join fields
end
def outfit_viewer(...) def outfit_viewer(...)
render partial: "outfit_viewer", render partial: "outfit_viewer",
locals: parse_outfit_viewer_options(...) locals: parse_outfit_viewer_options(...)
@ -99,133 +75,8 @@ module OutfitsHelper
locals: parse_outfit_viewer_options(...) locals: parse_outfit_viewer_options(...)
end end
# Group outfit items by zone, applying smart multi-zone simplification.
# Returns an array of hashes: {zone:, items:}
# This matches the logic from wardrobe-2020's getZonesAndItems function.
def outfit_items_by_zone(outfit)
return [] if outfit.pet_type.nil?
# Get item appearances for this outfit
item_appearances = Item.appearances_for(
outfit.worn_items,
outfit.pet_type,
swf_asset_includes: [:zone]
)
# Separate incompatible items (no layers for this pet)
compatible_items = []
incompatible_items = []
outfit.worn_items.each do |item|
appearance = item_appearances[item.id]
if appearance&.present?
compatible_items << {item: item, appearance: appearance}
else
incompatible_items << item
end
end
# Group items by zone - multi-zone items appear in each zone
items_by_zone = Hash.new { |h, k| h[k] = [] }
zones_by_id = {}
compatible_items.each do |item_with_appearance|
item = item_with_appearance[:item]
appearance = item_with_appearance[:appearance]
# Get unique zones for this item (an item may have multiple assets per zone)
appearance.swf_assets.map(&:zone).uniq.each do |zone|
zones_by_id[zone.id] = zone
items_by_zone[zone.id] << item
end
end
# Create zone groups with sorted items
zones_and_items = items_by_zone.map do |zone_id, items|
{
zone_id: zone_id,
zone_label: zones_by_id[zone_id].label,
items: items.sort_by { |item| item.name.downcase }
}
end
# Sort zone groups alphabetically by label, then by ID for tiebreaking
zones_and_items.sort_by! do |group|
[group[:zone_label].downcase, group[:zone_id]]
end
# Apply multi-zone simplification: remove redundant single-item groups
zones_and_items = simplify_multi_zone_groups(zones_and_items)
# Add zone ID disambiguation for duplicate labels
zones_and_items = disambiguate_zone_labels(zones_and_items)
# Add incompatible items section if any
if incompatible_items.any?
zones_and_items << {
zone_id: nil,
zone_label: "Incompatible",
items: incompatible_items.sort_by { |item| item.name.downcase }
}
end
zones_and_items
end
private private
# Simplify zone groups by removing redundant single-item groups.
# Keep groups with multiple items (conflicts). For single-item groups,
# only keep them if the item doesn't appear in a multi-item group.
def simplify_multi_zone_groups(zones_and_items)
# Find groups with conflicts (multiple items)
groups_with_conflicts = zones_and_items.select { |g| g[:items].length > 1 }
# Track which items appear in conflict groups
items_with_conflicts = Set.new(
groups_with_conflicts.flat_map { |g| g[:items].map(&:id) }
)
# Track which items we've already shown
items_we_have_seen = Set.new
# Filter groups
zones_and_items.select do |group|
# Always keep groups with multiple items
if group[:items].length > 1
group[:items].each { |item| items_we_have_seen.add(item.id) }
true
else
# For single-item groups, only keep if:
# - Item hasn't been seen yet AND
# - Item won't appear in a conflict group
item = group[:items].first
item_id = item.id
if items_we_have_seen.include?(item_id) || items_with_conflicts.include?(item_id)
false
else
items_we_have_seen.add(item_id)
true
end
end
end
end
# Add zone IDs to labels when there are duplicates
def disambiguate_zone_labels(zones_and_items)
label_counts = zones_and_items.group_by { |g| g[:zone_label] }
.transform_values(&:count)
zones_and_items.each do |group|
if label_counts[group[:zone_label]] > 1
group[:zone_label] = "#{group[:zone_label]} (##{group[:zone_id]})"
end
end
zones_and_items
end
def parse_outfit_viewer_options( def parse_outfit_viewer_options(
outfit=nil, pet_state: nil, preferred_image_format: :png, **html_options outfit=nil, pet_state: nil, preferred_image_format: :png, **html_options
) )

View file

@ -0,0 +1,152 @@
module WardrobeHelper
# Generate hidden fields to preserve outfit state in URL params.
# Use the `except` parameter to skip certain fields, e.g. to override
# them with specific values, like in the species/color picker.
def outfit_state_params(outfit = @outfit, except: [])
fields = []
fields << hidden_field_tag(:species, @outfit.species_id) unless except.include?(:species)
fields << hidden_field_tag(:color, @outfit.color_id) unless except.include?(:color)
unless except.include?(:worn_items)
outfit.worn_items.each do |item|
fields << hidden_field_tag('objects[]', item.id)
end
end
unless except.include?(:q)
(params[:q] || {}).each do |key, value|
fields << hidden_field_tag("q[#{key}]", value) if value.present?
end
end
safe_join fields
end
# Group outfit items by zone, applying smart multi-zone simplification.
# Returns an array of hashes: {zone:, items:}
# This matches the logic from wardrobe-2020's getZonesAndItems function.
def outfit_items_by_zone(outfit)
return [] if outfit.pet_type.nil?
# Get item appearances for this outfit
item_appearances = Item.appearances_for(
outfit.worn_items,
outfit.pet_type,
swf_asset_includes: [:zone]
)
# Separate incompatible items (no layers for this pet)
compatible_items = []
incompatible_items = []
outfit.worn_items.each do |item|
appearance = item_appearances[item.id]
if appearance&.present?
compatible_items << {item: item, appearance: appearance}
else
incompatible_items << item
end
end
# Group items by zone - multi-zone items appear in each zone
items_by_zone = Hash.new { |h, k| h[k] = [] }
zones_by_id = {}
compatible_items.each do |item_with_appearance|
item = item_with_appearance[:item]
appearance = item_with_appearance[:appearance]
# Get unique zones for this item (an item may have multiple assets per zone)
appearance.swf_assets.map(&:zone).uniq.each do |zone|
zones_by_id[zone.id] = zone
items_by_zone[zone.id] << item
end
end
# Create zone groups with sorted items
zones_and_items = items_by_zone.map do |zone_id, items|
{
zone_id: zone_id,
zone_label: zones_by_id[zone_id].label,
items: items.sort_by { |item| item.name.downcase }
}
end
# Sort zone groups alphabetically by label, then by ID for tiebreaking
zones_and_items.sort_by! do |group|
[group[:zone_label].downcase, group[:zone_id]]
end
# Apply multi-zone simplification: remove redundant single-item groups
zones_and_items = simplify_multi_zone_groups(zones_and_items)
# Add zone ID disambiguation for duplicate labels
zones_and_items = disambiguate_zone_labels(zones_and_items)
# Add incompatible items section if any
if incompatible_items.any?
zones_and_items << {
zone_id: nil,
zone_label: "Incompatible",
items: incompatible_items.sort_by { |item| item.name.downcase }
}
end
zones_and_items
end
private
# Simplify zone groups by removing redundant single-item groups.
# Keep groups with multiple items (conflicts). For single-item groups,
# only keep them if the item doesn't appear in a multi-item group.
def simplify_multi_zone_groups(zones_and_items)
# Find groups with conflicts (multiple items)
groups_with_conflicts = zones_and_items.select { |g| g[:items].length > 1 }
# Track which items appear in conflict groups
items_with_conflicts = Set.new(
groups_with_conflicts.flat_map { |g| g[:items].map(&:id) }
)
# Track which items we've already shown
items_we_have_seen = Set.new
# Filter groups
zones_and_items.select do |group|
# Always keep groups with multiple items
if group[:items].length > 1
group[:items].each { |item| items_we_have_seen.add(item.id) }
true
else
# For single-item groups, only keep if:
# - Item hasn't been seen yet AND
# - Item won't appear in a conflict group
item = group[:items].first
item_id = item.id
if items_we_have_seen.include?(item_id) || items_with_conflicts.include?(item_id)
false
else
items_we_have_seen.add(item_id)
true
end
end
end
end
# Add zone IDs to labels when there are duplicates
def disambiguate_zone_labels(zones_and_items)
label_counts = zones_and_items.group_by { |g| g[:zone_label] }
.transform_values(&:count)
zones_and_items.each do |group|
if label_counts[group[:zone_label]] > 1
group[:zone_label] = "#{group[:zone_label]} (##{group[:zone_id]})"
end
end
zones_and_items
end
end

View file

@ -9,12 +9,12 @@
%link{href: image_path('favicon.png'), rel: 'icon'} %link{href: image_path('favicon.png'), rel: 'icon'}
= stylesheet_link_tag "application/hanger-spinner" = stylesheet_link_tag "application/hanger-spinner"
= stylesheet_link_tag "application/outfit-viewer" = stylesheet_link_tag "application/outfit-viewer"
= page_stylesheet_link_tag "outfits/new_v2" = page_stylesheet_link_tag "wardrobe/show"
= javascript_include_tag "application", async: true = javascript_include_tag "application", async: true
= javascript_include_tag "idiomorph", async: true = javascript_include_tag "idiomorph", async: true
= javascript_include_tag "outfit-viewer", async: true = javascript_include_tag "outfit-viewer", async: true
= javascript_include_tag "species-color-picker", async: true = javascript_include_tag "species-color-picker", async: true
= javascript_include_tag "outfits/new_v2", async: true = javascript_include_tag "wardrobe/show", async: true
= csrf_meta_tags = csrf_meta_tags
%meta{name: 'outfit-viewer-morph-mode', value: 'full-page'} %meta{name: 'outfit-viewer-morph-mode', value: 'full-page'}
%body.wardrobe-v2 %body.wardrobe-v2

View file

@ -10,8 +10,8 @@ OpenneoImpressItems::Application.routes.draw do
# TODO: It's a bit silly that outfits/new points to outfits#edit. # TODO: It's a bit silly that outfits/new points to outfits#edit.
# Should we refactor the controller/view structure here? # Should we refactor the controller/view structure here?
get '/outfits/new', to: 'outfits#edit', as: :wardrobe get '/outfits/new', to: 'outfits#edit', as: :wardrobe
get '/outfits/new/v2', to: 'outfits#new_v2', as: :wardrobe_v2
get '/wardrobe' => redirect('/outfits/new') get '/wardrobe' => redirect('/outfits/new')
get '/wardrobe/v2', to: 'wardrobe#show', as: :wardrobe_v2
get '/start/:color_name/:species_name' => 'outfits#start' get '/start/:color_name/:species_name' => 'outfits#start'
# The outfits users have created! # The outfits users have created!

View file

@ -13,34 +13,34 @@ Replace the complex React outfit editor (`app/javascript/wardrobe-2020/`) with a
## Current Status ## Current Status
**Wardrobe V2 is in early prototype/proof-of-concept stage.** It's accessible at `/outfits/new/v2` but is not yet linked from the main UI. **Wardrobe V2 is in early prototype/proof-of-concept stage.** It's accessible at `/wardrobe/v2` but is not yet linked from the main UI.
### What's Implemented ### What's Implemented
#### Core Infrastructure #### Core Infrastructure
**Route & Controller** ([outfits_controller.rb:80-115](app/controllers/outfits_controller.rb#L80-L115)) **Route & Controller** ([wardrobe_controller.rb](app/controllers/wardrobe_controller.rb))
- `GET /outfits/new/v2` - Main wardrobe endpoint - `GET /wardrobe/v2` - Main wardrobe endpoint
- Takes URL params: `species`, `color`, `objects[]` (item IDs) - Takes URL params: `species`, `color`, `objects[]` (item IDs)
- Returns full HTML page (no layout, designed to work standalone) - Returns full HTML page (no layout, designed to work standalone)
- Defaults to Blue Acara if no pet specified - Defaults to Blue Acara if no pet specified
**View Layer** ([new_v2.html.haml](app/views/outfits/new_v2.html.haml)) **View Layer** ([show.html.haml](app/views/wardrobe/show.html.haml))
- Full-page layout with preview (left) and controls (right) - Full-page layout with preview (left) and controls (right)
- Responsive: stacks vertically on mobile (< 800px) - Responsive: stacks vertically on mobile (< 800px)
- Uses existing `outfit_viewer` partial for rendering - Uses existing `outfit_viewer` partial for rendering
- Custom CSS in [outfits/new_v2.css](app/assets/stylesheets/outfits/new_v2.css) - Custom CSS in [wardrobe/show.css](app/assets/stylesheets/wardrobe/show.css)
- Minimal JavaScript in [outfits/new_v2.js](app/assets/javascripts/outfits/new_v2.js) - Minimal JavaScript in [wardrobe/show.js](app/assets/javascripts/wardrobe/show.js)
**Pet Selection** ([new_v2.html.haml:31-42](app/views/outfits/new_v2.html.haml#L31-L42)) **Pet Selection** ([show.html.haml:31-42](app/views/wardrobe/show.html.haml#L31-L42))
- Species/color picker using `<species-color-picker>` web component - Species/color picker using `<species-color-picker>` web component
- Floats over preview area (bottom), reveals on hover/focus - Floats over preview area (bottom), reveals on hover/focus
- Progressive enhancement: submit button appears if JS slow/disabled - Progressive enhancement: submit button appears if JS slow/disabled
- Auto-submits form on change when JS loaded - Auto-submits form on change when JS loaded
- Filters colors to only those compatible with selected species - Filters colors to only those compatible with selected species
- Advanced fallback: if species+color combo doesn't exist, falls back to simple color ([outfits_controller.rb:89-98](app/controllers/outfits_controller.rb#L89-L98)) - Advanced fallback: if species+color combo doesn't exist, falls back to simple color ([wardrobe_controller.rb:13-16](app/controllers/wardrobe_controller.rb#L13-L16))
**Item Display** ([new_v2.html.haml:47-64](app/views/outfits/new_v2.html.haml#L47-L64)) **Item Display** ([show.html.haml:47-64](app/views/wardrobe/show.html.haml#L47-L64))
- Groups worn items by zone (Hat, Jacket, Wings, etc.) - Groups worn items by zone (Hat, Jacket, Wings, etc.)
- Smart multi-zone simplification: hides redundant single-item zones - Smart multi-zone simplification: hides redundant single-item zones
- Shows items that occupy multiple zones in conflict zones only - Shows items that occupy multiple zones in conflict zones only
@ -49,7 +49,7 @@ Replace the complex React outfit editor (`app/javascript/wardrobe-2020/`) with a
- Displays item thumbnails, names, and badges (NC/NP, first seen date) - Displays item thumbnails, names, and badges (NC/NP, first seen date)
- Alphabetical sorting within zones - Alphabetical sorting within zones
**Item Search** ([new_v2.html.haml:47-50](app/views/outfits/new_v2.html.haml#L47-L50)) **Item Search** ([show.html.haml:47-50](app/views/wardrobe/show.html.haml#L47-L50))
- Search form at top of controls section - Search form at top of controls section
- Auto-filters to items that fit current pet (species + color + alt_style) - Auto-filters to items that fit current pet (species + color + alt_style)
- Uses `Item::Search::Query.from_params` for structured search - Uses `Item::Search::Query.from_params` for structured search
@ -58,20 +58,20 @@ Replace the complex React outfit editor (`app/javascript/wardrobe-2020/`) with a
- All search state scoped under `q[...]` params (name, page, etc.) - All search state scoped under `q[...]` params (name, page, etc.)
- "Back to outfit" button to exit search - "Back to outfit" button to exit search
**Item Addition** ([_search_results.html.haml](app/views/outfits/_search_results.html.haml)) **Item Addition** ([_search_results.html.haml](app/views/wardrobe/_search_results.html.haml))
- Add button () on each search result item - Add button () on each search result item
- Adds item to outfit via GET request with updated `objects[]` params - Adds item to outfit via GET request with updated `objects[]` params
- Button hidden by default, appears on hover/focus - Button hidden by default, appears on hover/focus
- Preserves search state when adding items - Preserves search state when adding items
- Uses `outfit.with_item(item)` helper to generate new state - Uses `outfit.with_item(item)` helper to generate new state
**Item Removal** ([new_v2.html.haml:70-72](app/views/outfits/new_v2.html.haml#L70-L72)) **Item Removal** ([show.html.haml:70-72](app/views/wardrobe/show.html.haml#L70-L72))
- Remove button (❌) on each worn item - Remove button (❌) on each worn item
- Removes item from outfit via GET request with updated `objects[]` params - Removes item from outfit via GET request with updated `objects[]` params
- Button hidden by default, appears on hover/focus - Button hidden by default, appears on hover/focus
- Uses `outfit.without_item(item)` helper to generate new state - Uses `outfit.without_item(item)` helper to generate new state
**State Management** ([outfits_helper.rb:68-90](app/helpers/outfits_helper.rb#L68-L90)) **State Management** ([wardrobe_helper.rb:68-90](app/helpers/wardrobe_helper.rb#L68-L90))
- All state lives in URL params (no client-side state) - All state lives in URL params (no client-side state)
- `outfit_state_params` helper generates hidden fields for outfit state - `outfit_state_params` helper generates hidden fields for outfit state
- Preserves: species, color, worn items (`objects[]`), search query (`q[...]`) - Preserves: species, color, worn items (`objects[]`), search query (`q[...]`)
@ -80,11 +80,11 @@ Replace the complex React outfit editor (`app/javascript/wardrobe-2020/`) with a
#### Supporting Helpers #### Supporting Helpers
**`outfit_items_by_zone`** ([outfits_helper.rb:96-167](app/helpers/outfits_helper.rb#L96-L167)) **`outfit_items_by_zone`** ([wardrobe_helper.rb:96-167](app/helpers/wardrobe_helper.rb#L96-L167))
- Core grouping logic for items by zone - Core grouping logic for items by zone
- Matches wardrobe-2020's `getZonesAndItems` behavior - Matches wardrobe-2020's `getZonesAndItems` behavior
- Returns array of `{zone_id:, zone_label:, items:}` hashes - Returns array of `{zone_id:, zone_label:, items:}` hashes
- Extensively tested in [outfits_helper_spec.rb](spec/helpers/outfits_helper_spec.rb) - Extensively tested in [wardrobe_helper_spec.rb](spec/helpers/wardrobe_helper_spec.rb)
**`outfit_viewer`** ([outfits_helper.rb:86-89](app/helpers/outfits_helper.rb#L86-L89)) **`outfit_viewer`** ([outfits_helper.rb:86-89](app/helpers/outfits_helper.rb#L86-L89))
- Renders `<outfit-viewer>` web component - Renders `<outfit-viewer>` web component
@ -969,12 +969,13 @@ This section documents ALL features in the React-based Wardrobe 2020 for referen
- [Customization Architecture](./customization-architecture.md) - Data model deep dive - [Customization Architecture](./customization-architecture.md) - Data model deep dive
**Wardrobe V2 Files:** **Wardrobe V2 Files:**
- Controller: [app/controllers/outfits_controller.rb](../app/controllers/outfits_controller.rb) - Controller: [app/controllers/wardrobe_controller.rb](../app/controllers/wardrobe_controller.rb)
- View: [app/views/outfits/new_v2.html.haml](../app/views/outfits/new_v2.html.haml) - View: [app/views/wardrobe/show.html.haml](../app/views/wardrobe/show.html.haml)
- Helpers: [app/helpers/outfits_helper.rb](../app/helpers/outfits_helper.rb) - Search results partial: [app/views/wardrobe/_search_results.html.haml](../app/views/wardrobe/_search_results.html.haml)
- Tests: [spec/helpers/outfits_helper_spec.rb](../spec/helpers/outfits_helper_spec.rb) - Helpers: [app/helpers/wardrobe_helper.rb](../app/helpers/wardrobe_helper.rb)
- Styles: [app/assets/stylesheets/outfits/new_v2.css](../app/assets/stylesheets/outfits/new_v2.css) - Tests: [spec/helpers/wardrobe_helper_spec.rb](../spec/helpers/wardrobe_helper_spec.rb)
- JavaScript: [app/assets/javascripts/outfits/new_v2.js](../app/assets/javascripts/outfits/new_v2.js) - Styles: [app/assets/stylesheets/wardrobe/show.css](../app/assets/stylesheets/wardrobe/show.css)
- JavaScript: [app/assets/javascripts/wardrobe/show.js](../app/assets/javascripts/wardrobe/show.js)
- Web Components: - Web Components:
- [app/assets/javascripts/species-color-picker.js](../app/assets/javascripts/species-color-picker.js) - [app/assets/javascripts/species-color-picker.js](../app/assets/javascripts/species-color-picker.js)
- [app/assets/javascripts/outfit-viewer.js](../app/assets/javascripts/outfit-viewer.js) - [app/assets/javascripts/outfit-viewer.js](../app/assets/javascripts/outfit-viewer.js)

View file

@ -1,6 +1,6 @@
require_relative '../rails_helper' require_relative '../rails_helper'
RSpec.describe OutfitsHelper, type: :helper do RSpec.describe WardrobeHelper, type: :helper do
fixtures :zones, :colors, :species, :pet_types fixtures :zones, :colors, :species, :pet_types
# Use the Blue Acara's body_id throughout tests # Use the Blue Acara's body_id throughout tests