diff --git a/app/views/admin/products_v3/_table.html.haml b/app/views/admin/products_v3/_table.html.haml index 9ffbc22f30..cf581109fd 100644 --- a/app/views/admin/products_v3/_table.html.haml +++ b/app/views/admin/products_v3/_table.html.haml @@ -46,7 +46,8 @@ %th.align-right= t('admin.products_page.columns.actions') - products.each_with_index do |product, product_index| = form.fields_for("products", product, index: product_index) do |product_form| - %tbody.relaxed{ 'data-record-id': product_form.object.id, 'data-controller': "nested-form" } + %tbody.relaxed{ data: { 'record-id': product_form.object.id, controller: "nested-form", + action: 'nested-form:add->bulk-form#registerElements' } } %tr = render partial: 'product_row', locals: { product:, f: product_form } diff --git a/app/webpacker/controllers/bulk_form_controller.js b/app/webpacker/controllers/bulk_form_controller.js index 189aa9ca05..98a6063a0e 100644 --- a/app/webpacker/controllers/bulk_form_controller.js +++ b/app/webpacker/controllers/bulk_form_controller.js @@ -24,6 +24,15 @@ export default class BulkFormController extends Controller { window.removeEventListener("beforeunload", this.preventLeavingBulkForm); } + // Register any new elements (may be called by another controller after dynamically adding fields) + registerElements() { + const registeredElements = Object.values(this.recordElements).flat(); + // Select only elements that haven't been registered yet + const newElements = Array.from(this.form.elements).filter(n => !registeredElements.includes(n)); + + this.#registerElements(newElements); + } + toggleChanged(e) { const element = e.target; element.classList.toggle("changed", this.#isChanged(element)); diff --git a/package.json b/package.json index 771823a69e..f5bd1b0f56 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "shortcut-buttons-flatpickr": "^0.4.0", "stimulus": "^3.2.2", "stimulus-flatpickr": "^1.4.0", - "stimulus-rails-nested-form": "https://github.com/dacook/stimulus-rails-nested-form.git#dist", + "stimulus-rails-nested-form": "https://github.com/openfoodfoundation/stimulus-rails-nested-form.git#dist", "stimulus_reflex": "3.5.0-rc3", "tom-select": "^2.3.1", "trix": "^2.0.10", diff --git a/spec/javascripts/stimulus/bulk_form_controller_test.js b/spec/javascripts/stimulus/bulk_form_controller_test.js index b0a3386ce9..7571191708 100644 --- a/spec/javascripts/stimulus/bulk_form_controller_test.js +++ b/spec/javascripts/stimulus/bulk_form_controller_test.js @@ -181,6 +181,48 @@ describe("BulkFormController", () => { }); }); + describe("Adding new fields", () => { + beforeEach(() => { + document.body.innerHTML = ` +
+ `; + }); + + describe("registerElements", () => { + beforeEach(() => { + // Add new field after controller has initialised + input1a.insertAdjacentHTML("afterend", template.innerHTML); + + // Trigger bulk-form#registerElements + form.dispatchEvent(new Event("custom-event")); + }); + + it("onInput", () => { + input1b.value = 'updated1b'; + input1b.dispatchEvent(new Event("input")); + // Expect only updated field to show changed + expect(input1b.classList).toContain('changed'); + expect(input2.classList).not.toContain('changed'); + + // Change back to original value + input1b.value = 'initial1b'; + input1b.dispatchEvent(new Event("input")); + expect(input1b.classList).not.toContain('changed'); + }); + }) + }); + // unable to test disconnect at this stage // describe("disconnect()", () => { // it("resets other elements", () => { diff --git a/spec/system/admin/products_v3/products_spec.rb b/spec/system/admin/products_v3/products_spec.rb index c170187e3a..cb2d746343 100644 --- a/spec/system/admin/products_v3/products_spec.rb +++ b/spec/system/admin/products_v3/products_spec.rb @@ -357,7 +357,6 @@ describe 'As an admin, I can manage products', feature: :admin_style_v3 do fill_in "On Hand", with: "3" expect { - pending "changes are not detected" click_button "Save changes" expect(page).to have_content "Changes saved" @@ -432,7 +431,6 @@ describe 'As an admin, I can manage products', feature: :admin_style_v3 do it "saves changes after fixing errors" do expect { - pending "changes are not detected" click_button "Save changes" variant_a1.reload @@ -459,6 +457,125 @@ describe 'As an admin, I can manage products', feature: :admin_style_v3 do end end end + + describe "adding variants" do + it "creates a new variant" do + click_on "New variant" + + # find empty row for Apples + new_variant_row = find_field("Name", placeholder: "Apples", with: "").ancestor("tr") + expect(new_variant_row).to be_present + + within new_variant_row do + fill_in "Name", with: "Large box" + fill_in "SKU", with: "APL-02" + fill_in "Unit", with: 1000 + fill_in "Price", with: 10.25 + click_on "On Hand" # activate popout + end + fill_in "On Hand", with: "3" + + expect { + click_button "Save changes" + + expect(page).to have_content "Changes saved" + product_a.reload + }.to change { product_a.variants.count }.by(1) + + new_variant = product_a.variants.last + expect(new_variant.display_name).to eq "Large box" + expect(new_variant.sku).to eq "APL-02" + expect(new_variant.price).to eq 10.25 + expect(new_variant.unit_value).to eq 1000 + expect(new_variant.on_hand).to eq 3 + + within row_containing_name("Large box") do + expect(page).to have_field "Name", with: "Large box" + expect(page).to have_field "SKU", with: "APL-02" + expect(page).to have_field "Price", with: "10.25" + expect(page).to have_content "1kg" + expect(page).to have_css "button[aria-label='On Hand']", text: "3" + end + end + + context "with invalid data" do + before do + click_on "New variant" + + # find empty row for Apples + new_variant_row = find_field("Name", placeholder: "Apples", with: "").ancestor("tr") + expect(new_variant_row).to be_present + + within new_variant_row do + fill_in "Name", with: "N" * 256 # too long + fill_in "SKU", with: "n" * 256 + fill_in "Unit", with: "" # can't be blank + fill_in "Price", with: "10.25" # valid + end + end + + it "shows errors for both existing and new variant fields" do + # Update existing variant with invalid data too + within row_containing_name("Medium box") do + fill_in "Name", with: "M" * 256 + fill_in "SKU", with: "m" * 256 + fill_in "Price", with: "10.25" + end + + expect { + click_button "Save changes" + + expect(page).to have_content "1 product could not be saved" + expect(page).to have_content "Please review the errors and try again" + variant_a1.reload + }.to_not change { variant_a1.display_name } + + # New variant + within row_containing_name("N" * 256) do + expect(page).to have_field "Name", with: "N" * 256 + expect(page).to have_field "SKU", with: "n" * 256 + expect(page).to have_content "is too long" + expect(page).to have_field "Unit", with: "" + expect(page).to have_content "can't be blank" + expect(page).to have_field "Price", with: "10.25" # other updated value is retained + end + + # Existing variant + within row_containing_name("M" * 256) do + expect(page).to have_field "Name", with: "M" * 256 + expect(page).to have_field "SKU", with: "m" * 256 + expect(page).to have_content "is too long" + end + end + + it "saves changes after fixing errors" do + expect { + click_button "Save changes" + + variant_a1.reload + }.to_not change { variant_a1.display_name } + + within row_containing_name("N" * 256) do + fill_in "Name", with: "Nice box" + fill_in "SKU", with: "APL-02" + fill_in "Unit", with: "200" + end + + expect { + click_button "Save changes" + + expect(page).to have_content "Changes saved" + product_a.reload + }.to change { product_a.variants.count }.by(1) + + new_variant = product_a.variants.last + expect(new_variant.display_name).to eq "Nice box" + expect(new_variant.sku).to eq "APL-02" + expect(new_variant.price).to eq 10.25 + expect(new_variant.unit_value).to eq 200 + end + end + end end describe "edit image" do diff --git a/yarn.lock b/yarn.lock index 7f7d05d9f5..433742f9b6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8350,9 +8350,9 @@ stimulus-flatpickr@^1.4.0: resolved "https://registry.yarnpkg.com/stimulus-flatpickr/-/stimulus-flatpickr-1.4.0.tgz#a41071a3e69cfc50b7eaaacf356fc0ab1ab0543c" integrity sha512-rcC/c9+E+f5W2kOjaaLShtf3i+p95ACqt+oGzSAgeuZh2YeIN8gW4EWO7h0STBLzSVPl6BjIfPWP7upMPavIVQ== -"stimulus-rails-nested-form@https://github.com/dacook/stimulus-rails-nested-form.git#dist": +"stimulus-rails-nested-form@https://github.com/openfoodfoundation/stimulus-rails-nested-form.git#dist": version "4.1.0" - resolved "https://github.com/dacook/stimulus-rails-nested-form.git#d3b82ea638a7156f1122736cf739ab1821a1817e" + resolved "https://github.com/openfoodfoundation/stimulus-rails-nested-form.git#d3b82ea638a7156f1122736cf739ab1821a1817e" stimulus@^3.2.2: version "3.2.2"