diff --git a/app/assets/javascripts/outfits/new_v2.js b/app/assets/javascripts/wardrobe/show.js similarity index 100% rename from app/assets/javascripts/outfits/new_v2.js rename to app/assets/javascripts/wardrobe/show.js diff --git a/app/assets/stylesheets/outfits/new_v2.css b/app/assets/stylesheets/wardrobe/show.css similarity index 99% rename from app/assets/stylesheets/outfits/new_v2.css rename to app/assets/stylesheets/wardrobe/show.css index 2ece10a7..35d55795 100644 --- a/app/assets/stylesheets/outfits/new_v2.css +++ b/app/assets/stylesheets/wardrobe/show.css @@ -542,4 +542,4 @@ body.wardrobe-v2 { to { opacity: 1; } -} \ No newline at end of file +} diff --git a/app/controllers/outfits_controller.rb b/app/controllers/outfits_controller.rb index e91fc156..73be29fd 100644 --- a/app/controllers/outfits_controller.rb +++ b/app/controllers/outfits_controller.rb @@ -77,53 +77,6 @@ class OutfitsController < ApplicationController @campaign = Fundraising::Campaign.current rescue nil 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 @outfit = Outfit.find(params[:id]) @@ -176,51 +129,5 @@ class OutfitsController < ApplicationController :status => :bad_request 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 diff --git a/app/controllers/wardrobe_controller.rb b/app/controllers/wardrobe_controller.rb new file mode 100644 index 00000000..6529d913 --- /dev/null +++ b/app/controllers/wardrobe_controller.rb @@ -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 diff --git a/app/helpers/outfits_helper.rb b/app/helpers/outfits_helper.rb index 460eb5bb..a31d8587 100644 --- a/app/helpers/outfits_helper.rb +++ b/app/helpers/outfits_helper.rb @@ -65,30 +65,6 @@ module OutfitsHelper text_field_tag 'name', nil, options 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(...) render partial: "outfit_viewer", locals: parse_outfit_viewer_options(...) @@ -99,133 +75,8 @@ module OutfitsHelper locals: parse_outfit_viewer_options(...) 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 - def parse_outfit_viewer_options( outfit=nil, pet_state: nil, preferred_image_format: :png, **html_options ) diff --git a/app/helpers/wardrobe_helper.rb b/app/helpers/wardrobe_helper.rb new file mode 100644 index 00000000..1e46416a --- /dev/null +++ b/app/helpers/wardrobe_helper.rb @@ -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 diff --git a/app/views/outfits/_search_results.html.haml b/app/views/wardrobe/_search_results.html.haml similarity index 100% rename from app/views/outfits/_search_results.html.haml rename to app/views/wardrobe/_search_results.html.haml diff --git a/app/views/outfits/new_v2.html.haml b/app/views/wardrobe/show.html.haml similarity index 96% rename from app/views/outfits/new_v2.html.haml rename to app/views/wardrobe/show.html.haml index 93e97be2..3ffbbd51 100644 --- a/app/views/outfits/new_v2.html.haml +++ b/app/views/wardrobe/show.html.haml @@ -9,12 +9,12 @@ %link{href: image_path('favicon.png'), rel: 'icon'} = stylesheet_link_tag "application/hanger-spinner" = 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 "idiomorph", async: true = javascript_include_tag "outfit-viewer", 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 %meta{name: 'outfit-viewer-morph-mode', value: 'full-page'} %body.wardrobe-v2 diff --git a/config/routes.rb b/config/routes.rb index 0c951f2a..f8e03bfa 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -10,8 +10,8 @@ OpenneoImpressItems::Application.routes.draw do # TODO: It's a bit silly that outfits/new points to outfits#edit. # Should we refactor the controller/view structure here? 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/v2', to: 'wardrobe#show', as: :wardrobe_v2 get '/start/:color_name/:species_name' => 'outfits#start' # The outfits users have created! diff --git a/docs/wardrobe-v2-migration.md b/docs/wardrobe-v2-migration.md index 3492001c..32c46668 100644 --- a/docs/wardrobe-v2-migration.md +++ b/docs/wardrobe-v2-migration.md @@ -13,34 +13,34 @@ Replace the complex React outfit editor (`app/javascript/wardrobe-2020/`) with a ## 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 #### Core Infrastructure -**Route & Controller** ([outfits_controller.rb:80-115](app/controllers/outfits_controller.rb#L80-L115)) -- `GET /outfits/new/v2` - Main wardrobe endpoint +**Route & Controller** ([wardrobe_controller.rb](app/controllers/wardrobe_controller.rb)) +- `GET /wardrobe/v2` - Main wardrobe endpoint - Takes URL params: `species`, `color`, `objects[]` (item IDs) - Returns full HTML page (no layout, designed to work standalone) - 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) - Responsive: stacks vertically on mobile (< 800px) - Uses existing `outfit_viewer` partial for rendering -- Custom CSS in [outfits/new_v2.css](app/assets/stylesheets/outfits/new_v2.css) -- Minimal JavaScript in [outfits/new_v2.js](app/assets/javascripts/outfits/new_v2.js) +- Custom CSS in [wardrobe/show.css](app/assets/stylesheets/wardrobe/show.css) +- 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 `` web component - Floats over preview area (bottom), reveals on hover/focus - Progressive enhancement: submit button appears if JS slow/disabled - Auto-submits form on change when JS loaded - 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.) - Smart multi-zone simplification: hides redundant single-item zones - 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) - 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 - Auto-filters to items that fit current pet (species + color + alt_style) - 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.) - "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 - Adds item to outfit via GET request with updated `objects[]` params - Button hidden by default, appears on hover/focus - Preserves search state when adding items - 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 - Removes item from outfit via GET request with updated `objects[]` params - Button hidden by default, appears on hover/focus - 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) - `outfit_state_params` helper generates hidden fields for outfit state - 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 -**`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 - Matches wardrobe-2020's `getZonesAndItems` behavior - 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)) - Renders `` 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 **Wardrobe V2 Files:** -- Controller: [app/controllers/outfits_controller.rb](../app/controllers/outfits_controller.rb) -- View: [app/views/outfits/new_v2.html.haml](../app/views/outfits/new_v2.html.haml) -- Helpers: [app/helpers/outfits_helper.rb](../app/helpers/outfits_helper.rb) -- Tests: [spec/helpers/outfits_helper_spec.rb](../spec/helpers/outfits_helper_spec.rb) -- Styles: [app/assets/stylesheets/outfits/new_v2.css](../app/assets/stylesheets/outfits/new_v2.css) -- JavaScript: [app/assets/javascripts/outfits/new_v2.js](../app/assets/javascripts/outfits/new_v2.js) +- Controller: [app/controllers/wardrobe_controller.rb](../app/controllers/wardrobe_controller.rb) +- View: [app/views/wardrobe/show.html.haml](../app/views/wardrobe/show.html.haml) +- Search results partial: [app/views/wardrobe/_search_results.html.haml](../app/views/wardrobe/_search_results.html.haml) +- Helpers: [app/helpers/wardrobe_helper.rb](../app/helpers/wardrobe_helper.rb) +- Tests: [spec/helpers/wardrobe_helper_spec.rb](../spec/helpers/wardrobe_helper_spec.rb) +- 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: - [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) diff --git a/spec/helpers/outfits_helper_spec.rb b/spec/helpers/wardrobe_helper_spec.rb similarity index 99% rename from spec/helpers/outfits_helper_spec.rb rename to spec/helpers/wardrobe_helper_spec.rb index f7faa2fa..af7f0997 100644 --- a/spec/helpers/outfits_helper_spec.rb +++ b/spec/helpers/wardrobe_helper_spec.rb @@ -1,6 +1,6 @@ require_relative '../rails_helper' -RSpec.describe OutfitsHelper, type: :helper do +RSpec.describe WardrobeHelper, type: :helper do fixtures :zones, :colors, :species, :pet_types # Use the Blue Acara's body_id throughout tests