/** * @jest-environment jsdom */ import { Application } from "stimulus"; import bulk_form_controller from "../../../app/webpacker/controllers/bulk_form_controller"; describe("BulkFormController", () => { beforeAll(() => { const application = Application.start(); application.register("bulk-form", bulk_form_controller); }); describe("Modifying input values", () => { // Mock I18n. TODO: moved to a shared helper beforeAll(() => { const mockedT = jest.fn(); mockedT.mockImplementation((string, opts) => string + ", " + JSON.stringify(opts)); global.I18n = { t: mockedT, }; }); // (jest still doesn't have aroundEach https://github.com/jestjs/jest/issues/4543 ) afterAll(() => { delete global.I18n; }); beforeEach(() => { document.body.innerHTML = `
`; }); describe("marking changed fields", () => { it("input: onInput", () => { input1a.value = "updated1a"; input1a.dispatchEvent(new Event("input")); // Expect only first field to show changed expect(input1a.classList).toContain("changed"); expect(input1b.classList).not.toContain("changed"); expect(input2.classList).not.toContain("changed"); // Change back to original value input1a.value = "initial1a"; input1a.dispatchEvent(new Event("input")); expect(input1a.classList).not.toContain("changed"); }); it("select: onInput", () => { // Select a different option (it's the only way in Jest..) select1.options[0].selected = true; select1.options[1].selected = false; select1.dispatchEvent(new Event("input")); // Expect select to show changed expect(input1a.classList).not.toContain("changed"); expect(input1b.classList).not.toContain("changed"); expect(select1.classList).toContain("changed"); // Change back to original value select1.options[0].selected = false; select1.options[1].selected = true; select1.dispatchEvent(new Event("input")); expect(select1.classList).not.toContain("changed"); }); it("multiple fields", () => { input1a.value = "updated1a"; input1a.dispatchEvent(new Event("input")); input2.value = "updated2"; input2.dispatchEvent(new Event("input")); // Expect only first field to show changed expect(input1a.classList).toContain("changed"); expect(input1b.classList).not.toContain("changed"); expect(input2.classList).toContain("changed"); // Change only one back to original value input1a.value = "initial1a"; input1a.dispatchEvent(new Event("input")); expect(input1a.classList).not.toContain("changed"); expect(input1b.classList).not.toContain("changed"); expect(input2.classList).toContain("changed"); }); describe("select not include_blank", () => { beforeEach(() => { document.body.innerHTML = ` `; }); it("shows as changed", () => { // Expect select to show changed (select-one always has something selected) expect(select1.classList).toContain("changed"); // Change selection select1.options[0].selected = false; select1.options[1].selected = true; select1.dispatchEvent(new Event("input")); expect(select1.classList).toContain("changed"); }); }); describe("select-one with include_blank", () => { beforeEach(() => { document.body.innerHTML = ` `; }); it("does not show as changed", () => { expect(select1.classList).not.toContain("changed"); // Change selection select1.options[0].selected = false; select1.options[1].selected = true; select1.dispatchEvent(new Event("input")); expect(select1.classList).toContain("changed"); }); }); }); describe("activating sections, and showing a summary", () => { // This scenario should probably be broken up into smaller units. it("counts changed records ", () => { // Record 1: First field changed input1a.value = "updated1a"; input1a.dispatchEvent(new Event("input")); // Actions and changed summary are shown, with other sections disabled expect(actions.classList).not.toContain("hidden"); expect(changed_summary.textContent).toBe('changed_summary, {"count":1}'); expect(disable1.classList).toContain("disabled-section"); expect(disable1_element.disabled).toBe(true); expect(disable2.classList).toContain("disabled-section"); expect(disable2_element.disabled).toBe(true); // Record 1: Second field changed input1b.value = "updated1b"; input1b.dispatchEvent(new Event("input")); // Expect to show same summary translation expect(actions.classList).not.toContain("hidden"); expect(changed_summary.textContent).toBe('changed_summary, {"count":1}'); // Record 2: has been changed input2.value = "updated2"; input2.dispatchEvent(new Event("input")); // Expect summary to count both records expect(actions.classList).not.toContain("hidden"); expect(changed_summary.textContent).toBe('changed_summary, {"count":2}'); // Record 1: Change first field back to original value input1a.value = "initial1a"; input1a.dispatchEvent(new Event("input")); // Both records are still changed. expect(input1a.classList).not.toContain("changed"); expect(input1b.classList).toContain("changed"); expect(input2.classList).toContain("changed"); expect(actions.classList).not.toContain("hidden"); expect(changed_summary.textContent).toBe('changed_summary, {"count":2}'); // Record 1: Change second field back to original value input1b.value = "initial1b"; input1b.dispatchEvent(new Event("input")); // Both fields for record 1 show unchanged, but second record is still changed expect(actions.classList).not.toContain("hidden"); expect(changed_summary.textContent).toBe('changed_summary, {"count":1}'); expect(disable1.classList).toContain("disabled-section"); expect(disable1_element.disabled).toBe(true); expect(disable2.classList).toContain("disabled-section"); expect(disable2_element.disabled).toBe(true); // Record 2: Change back to original value input2.value = "initial2"; input2.dispatchEvent(new Event("input")); // Actions are hidden and other sections are now re-enabled expect(actions.classList).toContain("hidden"); expect(changed_summary.textContent).toBe('changed_summary, {"count":0}'); expect(disable1.classList).not.toContain("disabled-section"); expect(disable1_element.disabled).toBe(false); expect(disable2.classList).not.toContain("disabled-section"); expect(disable2_element.disabled).toBe(false); }); }); }); describe("When there are errors", () => { beforeEach(() => { document.body.innerHTML = ` `; const actions = document.getElementById("actions"); const changed_summary = document.getElementById("changed_summary"); const input1a = document.getElementById("input1a"); }); it("form actions section remains visible", () => { // Expect actions to remain visible expect(actions.classList).not.toContain("hidden"); // Record 1: First field changed input1a.value = "updated1a"; input1a.dispatchEvent(new Event("input")); // Expect actions to remain visible expect(actions.classList).not.toContain("hidden"); // Change back to original value input1a.value = "initial1a"; input1a.dispatchEvent(new Event("input")); // Expect actions to remain visible expect(actions.classList).not.toContain("hidden"); }); }); 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", () => { // const disable1 = document.getElementById("disable1"); // const disable2 = document.getElementById("disable2"); // const controller = document.querySelector('[data-controller="bulk-form"]'); // const form = document.querySelector('[data-controller="bulk-form"]'); // // Form is changed and other sections are disabled // input1a.value = 'updated1a'; // input1a.dispatchEvent(new Event("change")); // expect(disable1.classList).toContain('disabled-section'); // expect(disable2.classList).toContain('disabled-section'); // // form.submit(); //not implemented // document.body.removeChild(controller); //doesn't trigger disconnect // controller.innerHTML = ''; //doesn't trigger disconnect // expect(disable1.classList).not.toContain('disabled-section'); // expect(disable2.classList).not.toContain('disabled-section'); // //TODO: expect window to have no beforeunload event listener // }); // }); });