From 01325f4ea4273dbabf42d233db270e0fc57b3ec9 Mon Sep 17 00:00:00 2001 From: Madeline Collier Date: Thu, 5 Dec 2024 13:27:37 +0100 Subject: [PATCH] Add product properties create/edit flow to admin This builds off of the excellent work started in https://github.com/solidusio/solidus/pull/5885 and in https://github.com/solidusio/solidus/pull/5926 to build out the create/edit/update flow for product properties in the new admin. I've added a request spec that was missing and tweaked a few other things. --- .../properties/edit/component.html.erb | 17 +++ .../properties/edit/component.rb | 12 ++ .../properties/edit/component.yml | 4 + .../properties/index/component.rb | 15 +- .../properties/new/component.html.erb | 18 +++ .../solidus_admin/properties/new/component.rb | 12 ++ .../properties/new/component.yml | 4 + .../solidus_admin/properties_controller.rb | 100 +++++++++++-- admin/config/locales/properties.en.yml | 4 + admin/config/routes.rb | 2 +- admin/spec/features/properties_spec.rb | 85 +++++++++++ .../requests/solidus_admin/properties_spec.rb | 133 ++++++++++++++++++ 12 files changed, 395 insertions(+), 11 deletions(-) create mode 100644 admin/app/components/solidus_admin/properties/edit/component.html.erb create mode 100644 admin/app/components/solidus_admin/properties/edit/component.rb create mode 100644 admin/app/components/solidus_admin/properties/edit/component.yml create mode 100644 admin/app/components/solidus_admin/properties/new/component.html.erb create mode 100644 admin/app/components/solidus_admin/properties/new/component.rb create mode 100644 admin/app/components/solidus_admin/properties/new/component.yml create mode 100644 admin/spec/requests/solidus_admin/properties_spec.rb diff --git a/admin/app/components/solidus_admin/properties/edit/component.html.erb b/admin/app/components/solidus_admin/properties/edit/component.html.erb new file mode 100644 index 00000000000..e116b899486 --- /dev/null +++ b/admin/app/components/solidus_admin/properties/edit/component.html.erb @@ -0,0 +1,17 @@ +<%= turbo_frame_tag :edit_property_modal do %> + <%= render component("ui/modal").new(title: t(".title")) do |modal| %> + <%= form_for @property, url: solidus_admin.property_path(@property), html: { id: form_id } do |f| %> +
+ <%= render component("ui/forms/field").text_field(f, :name, class: "required") %> + <%= render component("ui/forms/field").text_field(f, :presentation, class: "required") %> +
+ <% modal.with_actions do %> +
+ <%= render component("ui/button").new(scheme: :secondary, text: t('.cancel')) %> +
+ <%= render component("ui/button").new(form: form_id, type: :submit, text: t('.submit')) %> + <% end %> + <% end %> + <% end %> +<% end %> +<%= render component("properties/index").new(page: @page) %> diff --git a/admin/app/components/solidus_admin/properties/edit/component.rb b/admin/app/components/solidus_admin/properties/edit/component.rb new file mode 100644 index 00000000000..b2ed34d9114 --- /dev/null +++ b/admin/app/components/solidus_admin/properties/edit/component.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +class SolidusAdmin::Properties::Edit::Component < SolidusAdmin::BaseComponent + def initialize(page:, property:) + @page = page + @property = property + end + + def form_id + dom_id(@property, "#{stimulus_id}_edit_property_form") + end +end diff --git a/admin/app/components/solidus_admin/properties/edit/component.yml b/admin/app/components/solidus_admin/properties/edit/component.yml new file mode 100644 index 00000000000..bed9d213c8c --- /dev/null +++ b/admin/app/components/solidus_admin/properties/edit/component.yml @@ -0,0 +1,4 @@ +en: + title: "Edit Property" + cancel: "Cancel" + submit: "Update Property" diff --git a/admin/app/components/solidus_admin/properties/index/component.rb b/admin/app/components/solidus_admin/properties/index/component.rb index e8289365922..61aca2318c4 100644 --- a/admin/app/components/solidus_admin/properties/index/component.rb +++ b/admin/app/components/solidus_admin/properties/index/component.rb @@ -5,6 +5,10 @@ def model_class Spree::Property end + def title + t('solidus_admin.properties.title') + end + def search_key :name_cont end @@ -14,14 +18,21 @@ def search_url end def row_url(property) - spree.admin_property_path(property) + solidus_admin.edit_property_path(property, _turbo_frame: :edit_property_modal) + end + + def turbo_frames + %w[ + new_property_modal + edit_property_modal + ] end def page_actions render component("ui/button").new( tag: :a, text: t('.add'), - href: spree.new_admin_property_path, + href: solidus_admin.new_property_path, data: { turbo_frame: :new_property_modal }, icon: "add-line", ) end diff --git a/admin/app/components/solidus_admin/properties/new/component.html.erb b/admin/app/components/solidus_admin/properties/new/component.html.erb new file mode 100644 index 00000000000..d306cebcf4c --- /dev/null +++ b/admin/app/components/solidus_admin/properties/new/component.html.erb @@ -0,0 +1,18 @@ +<%= turbo_frame_tag :new_property_modal do %> + <%= render component("ui/modal").new(title: t(".title")) do |modal| %> + <%= form_for @property, url: solidus_admin.properties_path, html: { id: form_id } do |f| %> +
+ <%= render component("ui/forms/field").text_field(f, :name, class: "required") %> + <%= render component("ui/forms/field").text_field(f, :presentation, class: "required") %> +
+ <% modal.with_actions do %> +
+ <%= render component("ui/button").new(scheme: :secondary, text: t('.cancel')) %> +
+ <%= render component("ui/button").new(form: form_id, type: :submit, text: t('.submit')) %> + <% end %> + <% end %> + <% end %> +<% end %> + +<%= render component("properties/index").new(page: @page) %> diff --git a/admin/app/components/solidus_admin/properties/new/component.rb b/admin/app/components/solidus_admin/properties/new/component.rb new file mode 100644 index 00000000000..55e8806c68f --- /dev/null +++ b/admin/app/components/solidus_admin/properties/new/component.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +class SolidusAdmin::Properties::New::Component < SolidusAdmin::BaseComponent + def initialize(page:, property:) + @page = page + @property = property + end + + def form_id + dom_id(@property, "#{stimulus_id}_new_property_form") + end +end diff --git a/admin/app/components/solidus_admin/properties/new/component.yml b/admin/app/components/solidus_admin/properties/new/component.yml new file mode 100644 index 00000000000..e91d4784c52 --- /dev/null +++ b/admin/app/components/solidus_admin/properties/new/component.yml @@ -0,0 +1,4 @@ +en: + title: "New Property" + cancel: "Cancel" + submit: "Add Property" diff --git a/admin/app/controllers/solidus_admin/properties_controller.rb b/admin/app/controllers/solidus_admin/properties_controller.rb index 666451459d4..53db315d8d6 100644 --- a/admin/app/controllers/solidus_admin/properties_controller.rb +++ b/admin/app/controllers/solidus_admin/properties_controller.rb @@ -4,21 +4,86 @@ module SolidusAdmin class PropertiesController < SolidusAdmin::BaseController include SolidusAdmin::ControllerHelpers::Search - def index - properties = apply_search_to( - Spree::Property.order(created_at: :desc, id: :desc), - param: :q, - ) + before_action :set_property, only: %i[edit update] - set_page_and_extract_portion_from( - properties, - ) + def index + set_index_page respond_to do |format| format.html { render component('properties/index').new(page: @page) } end end + def new + @property = Spree::Property.new + + set_index_page + + respond_to do |format| + format.html { render component('properties/new').new(page: @page, property: @property) } + end + end + + def create + @property = Spree::Property.new(property_params) + + if @property.save + respond_to do |format| + flash[:notice] = t('.success') + + format.html do + redirect_to solidus_admin.properties_path, status: :see_other + end + + format.turbo_stream do + render turbo_stream: '' + end + end + else + set_index_page + + respond_to do |format| + format.html do + page_component = component('properties/new').new(page: @page, property: @property) + render page_component, status: :unprocessable_entity + end + end + end + end + + def edit + set_index_page + + respond_to do |format| + format.html { render component('properties/edit').new(page: @page, property: @property) } + end + end + + def update + if @property.update(property_params) + respond_to do |format| + flash[:notice] = t('.success') + + format.html do + redirect_to solidus_admin.properties_path, status: :see_other + end + + format.turbo_stream do + render turbo_stream: '' + end + end + else + set_index_page + + respond_to do |format| + format.html do + page_component = component('properties/edit').new(page: @page, property: @property) + render page_component, status: :unprocessable_entity + end + end + end + end + def destroy @properties = Spree::Property.where(id: params[:id]) @@ -29,5 +94,24 @@ def destroy flash[:notice] = t('.success') redirect_to properties_path, status: :see_other end + + private + + def set_property + @property = Spree::Property.find(params[:id]) + end + + def property_params + params.require(:property).permit(:name, :presentation) + end + + def set_index_page + properties = apply_search_to( + Spree::Property.unscoped.order(id: :desc), + param: :q, + ) + + set_page_and_extract_portion_from(properties) + end end end diff --git a/admin/config/locales/properties.en.yml b/admin/config/locales/properties.en.yml index 5bc4aec5cbb..95ef30fcae4 100644 --- a/admin/config/locales/properties.en.yml +++ b/admin/config/locales/properties.en.yml @@ -2,5 +2,9 @@ en: solidus_admin: properties: title: "Properties" + create: + success: "Property was successfully created." + update: + success: "Property was successfully updated." destroy: success: "Properties were successfully removed." diff --git a/admin/config/routes.rb b/admin/config/routes.rb index c42101b7720..70ef4c7e50b 100644 --- a/admin/config/routes.rb +++ b/admin/config/routes.rb @@ -56,7 +56,7 @@ end admin_resources :promotions, only: [:index, :destroy] - admin_resources :properties, only: [:index, :destroy] + admin_resources :properties, except: [:show] admin_resources :option_types, only: [:index, :destroy], sortable: true admin_resources :taxonomies, only: [:index, :destroy], sortable: true admin_resources :promotion_categories, only: [:index, :destroy] diff --git a/admin/spec/features/properties_spec.rb b/admin/spec/features/properties_spec.rb index 7c2025e5bfe..d3eb375613a 100644 --- a/admin/spec/features/properties_spec.rb +++ b/admin/spec/features/properties_spec.rb @@ -21,4 +21,89 @@ expect(page).not_to have_content("Type prop") expect(Spree::Property.count).to eq(1) end + + context "creating a new property" do + it "creates a new product property" do + visit "/admin/properties" + click_on "Add new" + + fill_in "Name", with: "Color" + fill_in "Presentation", with: "Cool Color" + click_on "Add Property" + + expect(page).to have_content("Property was successfully created.") + expect(page).to have_content("Color") + expect(page).to have_content("Cool Color") + expect(Spree::Property.count).to eq(1) + end + + it "shows validation errors" do + visit "/admin/properties" + click_on "Add new" + + fill_in "Name", with: "" + click_on "Add Property" + + expect(page).to have_content("can't be blank") + expect(Spree::Property.count).to eq(0) + end + end + + context "editing an existing property" do + let!(:property) { create(:property, name: "Color", presentation: "Cool Color") } + + it "updates the property" do + visit "/admin/properties" + find_row("Color").click + + fill_in "Name", with: "Size" + fill_in "Presentation", with: "Cool Size" + click_on "Update Property" + + expect(page).to have_content("Property was successfully updated.") + expect(page).to have_content("Size") + expect(page).to have_content("Cool Size") + expect(Spree::Property.count).to eq(1) + end + + it "shows validation errors" do + visit "/admin/properties" + find_row("Color").click + + fill_in "Name", with: "" + click_on "Update Property" + + expect(page).to have_content("can't be blank") + expect(Spree::Property.count).to eq(1) + end + end + + context "editing an existing property" do + let!(:property) { create(:property, name: "Color", presentation: "Cool Color") } + + it "updates the property" do + visit "/admin/properties" + find_row("Color").click + + fill_in "Name", with: "Size" + fill_in "Presentation", with: "Cool Size" + click_on "Update Property" + + expect(page).to have_content("Property was successfully updated.") + expect(page).to have_content("Size") + expect(page).to have_content("Cool Size") + expect(Spree::Property.count).to eq(1) + end + + it "shows validation errors" do + visit "/admin/properties" + find_row("Color").click + + fill_in "Name", with: "" + click_on "Update Property" + + expect(page).to have_content("can't be blank") + expect(Spree::Property.count).to eq(1) + end + end end diff --git a/admin/spec/requests/solidus_admin/properties_spec.rb b/admin/spec/requests/solidus_admin/properties_spec.rb new file mode 100644 index 00000000000..fa781e8153a --- /dev/null +++ b/admin/spec/requests/solidus_admin/properties_spec.rb @@ -0,0 +1,133 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe "SolidusAdmin::PropertiesController", type: :request do + let(:admin_user) { create(:admin_user) } + let(:property) { create(:property) } + + before do + allow_any_instance_of(SolidusAdmin::BaseController).to receive(:spree_current_user).and_return(admin_user) + end + + describe "GET /index" do + it "renders the index template with a 200 OK status" do + get solidus_admin.properties_path + expect(response).to have_http_status(:ok) + end + end + + describe "GET /new" do + it "renders the new template with a 200 OK status" do + get solidus_admin.new_property_path + expect(response).to have_http_status(:ok) + end + end + + describe "POST /create" do + context "with valid parameters" do + let(:valid_attributes) { { name: "Material", presentation: "Material Type" } } + + it "creates a new Property" do + expect { + post solidus_admin.properties_path, params: { property: valid_attributes } + }.to change(Spree::Property, :count).by(1) + end + + it "redirects to the index page with a 303 See Other status" do + post solidus_admin.properties_path, params: { property: valid_attributes } + expect(response).to redirect_to(solidus_admin.properties_path) + expect(response).to have_http_status(:see_other) + end + + it "displays a success flash message" do + post solidus_admin.properties_path, params: { property: valid_attributes } + follow_redirect! + expect(response.body).to include("Property was successfully created.") + end + end + + context "with invalid parameters" do + let(:invalid_attributes) { { name: "", presentation: "" } } + + it "does not create a new Property" do + expect { + post solidus_admin.properties_path, params: { property: invalid_attributes } + }.not_to change(Spree::Property, :count) + end + + it "renders the new template with unprocessable_entity status" do + post solidus_admin.properties_path, params: { property: invalid_attributes } + expect(response).to have_http_status(:unprocessable_entity) + end + end + end + + describe "GET /edit" do + it "renders the edit template with a 200 OK status" do + get solidus_admin.edit_property_path(property) + expect(response).to have_http_status(:ok) + end + end + + describe "PATCH /update" do + context "with valid parameters" do + let(:valid_attributes) { { name: "Updated Name", presentation: "Updated Presentation" } } + + it "updates the property" do + patch solidus_admin.property_path(property), params: { property: valid_attributes } + property.reload + expect(property.name).to eq("Updated Name") + expect(property.presentation).to eq("Updated Presentation") + end + + it "redirects to the index page with a 303 See Other status" do + patch solidus_admin.property_path(property), params: { property: valid_attributes } + expect(response).to redirect_to(solidus_admin.properties_path) + expect(response).to have_http_status(:see_other) + end + + it "displays a success flash message" do + patch solidus_admin.property_path(property), params: { property: valid_attributes } + follow_redirect! + expect(response.body).to include("Property was successfully updated.") + end + end + + context "with invalid parameters" do + let(:invalid_attributes) { { name: "", presentation: "Updated Presentation" } } + + it "does not update the property" do + original_name = property.name + patch solidus_admin.property_path(property), params: { property: invalid_attributes } + property.reload + expect(property.name).to eq(original_name) + end + + it "renders the edit template with unprocessable_entity status" do + patch solidus_admin.property_path(property), params: { property: invalid_attributes } + expect(response).to have_http_status(:unprocessable_entity) + end + end + end + + describe "DELETE /destroy" do + it "deletes the property and redirects to the index page with a 303 See Other status" do + # Ensure the property exists before attempting to delete it. + property + + expect { + delete solidus_admin.property_path(property) + }.to change(Spree::Property, :count).by(-1) + + expect(response).to redirect_to(solidus_admin.properties_path) + expect(response).to have_http_status(:see_other) + end + + it "displays a success flash message after deletion" do + delete solidus_admin.property_path(property) + follow_redirect! + expect(response.body).to include("Properties were successfully removed.") + end + end +end