diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 000000000..548cc94c4 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,5 @@ +{ + "tabWidth": 4, + "bracketSameLine": true, + "printWidth": 100 +} \ No newline at end of file diff --git a/src/App.vue b/src/App.vue index 71b56feaf..9ee03e8dd 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,16 +1,29 @@ + + diff --git a/src/common-components/base-input-button/base-input-button.spec.js b/src/common-components/base-input-button/base-input-button.spec.js new file mode 100644 index 000000000..646e271a2 --- /dev/null +++ b/src/common-components/base-input-button/base-input-button.spec.js @@ -0,0 +1,1263 @@ +import { shallowMount, mount } from "@vue/test-utils"; +import baseInputButton from "./base-input-button"; + +describe("baseInputButton.vue", () => { + describe("general", () => { + describe("checkbox", () => { + test("isMultiSelect => baseInputButton is a checkbox", () => { + // Arrange/Act + const { wrapper } = setupMocks({ + mockData: { + propsData: { + isMultiSelect: true, + }, + }, + }); + + // Assert + const inputElement = wrapper.find("input"); + expect(inputElement.attributes().type).toEqual("checkbox"); + }); + }); + + describe("radio", () => { + test("!isMultiSelect => baseInputButton is a radio button", () => { + // Arrange/Act + const { wrapper } = setupMocks({ + mockData: { + propsData: { + isMultiSelect: false, + }, + }, + }); + + // Assert + const inputElement = wrapper.find("input"); + expect(inputElement.attributes().type).toEqual("radio"); + }); + }); + + // tests in here should be test.each + describe("shared", () => { + const isMultiSelectOptions = [true, false]; + test.each(isMultiSelectOptions)("groupName", (isMultiSelect) => { + const { wrapper } = setupMocks({ + mockData: { + propsData: { + groupName: "boogly", + isMultiSelect: isMultiSelect, + }, + }, + }); + + // Assert + const inputElement = wrapper.find("input"); + const inputElementAttributes = inputElement.attributes(); + expect(inputElementAttributes.name).toEqual("boogly"); + }); + }); + }); + + describe("mouse clicks", () => { + describe("checkbox", () => { + describe("clicked once", () => { + test("correct event and value are emitted", async () => { + // Arrange + const { wrapper } = setupMocks({ + mockData: { + propsData: { + isMultiSelect: true, + value: "X", + }, + }, + }); + + // Act + await wrapper.trigger("click"); + + // Assert + expect(wrapper.emitted()["update:modelValue"][0][0]).toEqual(["X"]); + }); + + test("input is checked", async () => { + // Arrange + const { wrapper } = setupMocks({ + mockData: { + propsData: { + isMultiSelect: true, + value: "X", + }, + }, + }); + const input = wrapper.find("input"); + + // Act + await wrapper.trigger("click"); + + // Assert + expect(input.element.checked).toBe(true); + }); + }); + + describe("clicked twice", () => { + test("input is unchecked", async () => { + // Arrange + const { wrapper } = setupMocks({ + mockData: { + propsData: { + isMultiSelect: true, + value: "X", + }, + }, + }); + const input = wrapper.find("input"); + + // Act + await wrapper.trigger("click"); + await wrapper.trigger("click"); + + // Assert + expect(input.element.checked).toBe(false); + }); + }); + + describe("is initially checked, click once", () => { + test("input is unchecked", async () => { + // Arrange + const { wrapper } = setupMocks({ + mockData: { + propsData: { + isMultiSelect: true, + value: "X", + modelValue: ["X"], + }, + }, + }); + const input = wrapper.find("input"); + + // Act + await wrapper.trigger("click"); + + // Assert + expect(input.element.checked).toBe(false); + }); + + test("input is unchecked", async () => { + // Arrange + const { wrapper } = setupMocks({ + mockData: { + propsData: { + isMultiSelect: true, + value: "X", + modelValue: ["X"], + }, + }, + }); + const input = wrapper.find("input"); + + // Act + await wrapper.trigger("click"); + + // Assert + expect(input.element.checked).toBe(false); + }); + }); + }); + + describe("radio", () => { + test("clicked => correct event and value are emitted", async () => { + // Arrange + const { wrapper } = setupMocks({ + mockData: { + propsData: { + isMultiSelect: false, + value: "X", + }, + }, + }); + + // Act + await wrapper.trigger("mousedown.left"); + await wrapper.trigger("click"); + + // Assert + expect(wrapper.emitted()["update:modelValue"][0][0]).toEqual("X"); + }); + }); + }); + + describe("events", () => { + describe("checkbox", () => { + test("change event fired from checkbox => update:modelValue is emitted with correct value", async () => { + // Arrange + const { wrapper } = setupMocks({ + mockData: { + propsData: { + isMultiSelect: true, + value: "Hi", + }, + }, + }); + + const input = wrapper.find("input"); + + // Act + await input.trigger("change"); + + // Assert + expect(wrapper.emitted()["update:modelValue"][0][0]).toEqual(["Hi"]); + }); + + test("focus and click space on a checkbox => update:modelValue is not emitted", async () => { + // Arrange + const { wrapper } = setupMocks({ + mockData: { + propsData: { + isMultiSelect: true, + value: "Hi", + }, + }, + }); + + const input = wrapper.find("input"); + + // Act + await input.trigger("keypress", { key: "space" }); + + // Assert + expect(wrapper.emitted()).not.toHaveProperty("update:modelValue"); + }); + + test("focus and click enter on a checkbox => update:modelValue is emitted with correct value", async () => { + // Arrange + const { wrapper } = setupMocks({ + mockData: { + propsData: { + isMultiSelect: true, + value: "Hi", + }, + }, + }); + + const input = wrapper.find("input"); + + // Act + await input.trigger("keypress", { key: "enter" }); + + // Assert + expect(wrapper.emitted()["update:modelValue"][0][0]).toEqual(["Hi"]); + }); + }); + + describe("radio", () => { + test("focus and click space on a radio button => update:modelValue is emitted with correct value", async () => { + // Arrange + const { wrapper } = setupMocks({ + mockData: { + propsData: { + isMultiSelect: false, + value: "Hi", + }, + }, + }); + + const input = wrapper.find("input"); + + // Act + await input.trigger("keypress", { key: "space" }); + + // Assert + expect(wrapper.emitted()).toHaveProperty("update:modelValue"); + expect(wrapper.emitted()["update:modelValue"][0][0]).toEqual("Hi"); + }); + + test("focus and click enter on a radio button => update:modelValue is emitted with correct value", async () => { + // Arrange + const { wrapper } = setupMocks({ + mockData: { + propsData: { + isMultiSelect: false, + value: "Hi", + }, + }, + }); + + const input = wrapper.find("input"); + + // Act + await input.trigger("keypress", { key: "enter" }); + + // Assert + expect(wrapper.emitted()).toHaveProperty("update:modelValue"); + expect(wrapper.emitted()["update:modelValue"][0][0]).toEqual("Hi"); + }); + }); + }); + + describe("methods", () => { + describe("handleEventAction", () => { + describe("isMultiSelect", () => { + test("eventType === eventTypes.CLICK => do nothing", () => { + // Arrange + const { wrapper } = setupMocks({ + mockData: { + propsData: { + isMultiSelect: true, + }, + }, + }); + wrapper.vm.handleClick = jest.fn(); + wrapper.vm.handlePushClickEventToGACheck = jest.fn(); + wrapper.vm.handleSelectionChange = jest.fn(); + + // Act + wrapper.vm.handleEventAction("click", { myEvent: "test" }); + + // Assert + expect(wrapper.vm.handleClick).not.toHaveBeenCalled(); + expect(wrapper.vm.handleSelectionChange).not.toHaveBeenCalled(); + expect(wrapper.vm.handlePushClickEventToGACheck).not.toHaveBeenCalled(); + }); + + test("eventType === eventTypes.ENTER => call handleClick and handlePushClickEventToGACheck", () => { + // Arrange + const { wrapper } = setupMocks({ + mockData: { + propsData: { + isMultiSelect: true, + }, + }, + }); + wrapper.vm.handleClick = jest.fn(); + wrapper.vm.handlePushClickEventToGACheck = jest.fn(); + wrapper.vm.handleSelectionChange = jest.fn(); + + // Act + wrapper.vm.handleEventAction("enter", { myEvent: "test" }); + + // Assert + expect(wrapper.vm.handleClick).toHaveBeenCalledWith({ + myEvent: "test", + }); + expect(wrapper.vm.handlePushClickEventToGACheck).toHaveBeenCalledWith("click"); + expect(wrapper.vm.handleSelectionChange).not.toHaveBeenCalled(); + }); + + test("eventType === eventTypes.CHANGE => call handleClick and handlePushClickEventToGACheck", () => { + // Arrange + const { wrapper } = setupMocks({ + mockData: { + propsData: { + isMultiSelect: true, + }, + }, + }); + wrapper.vm.handleClick = jest.fn(); + wrapper.vm.handlePushClickEventToGACheck = jest.fn(); + wrapper.vm.handleSelectionChange = jest.fn(); + + // Act + wrapper.vm.handleEventAction("change", { myEvent: "test" }); + + // Assert + expect(wrapper.vm.handleClick).toHaveBeenCalledWith({ + myEvent: "test", + }); + expect(wrapper.vm.handlePushClickEventToGACheck).toHaveBeenCalledWith("click"); + expect(wrapper.vm.handleSelectionChange).not.toHaveBeenCalled(); + }); + + test("eventType === eventTypes.MOUNT => do nothing", () => { + // Arrange + const { wrapper } = setupMocks({ + mockData: { + propsData: { + isMultiSelect: true, + }, + }, + }); + wrapper.vm.handleClick = jest.fn(); + wrapper.vm.handlePushClickEventToGACheck = jest.fn(); + wrapper.vm.handleSelectionChange = jest.fn(); + + // Act + wrapper.vm.handleEventAction("mount", { myEvent: "test" }); + + // Assert + expect(wrapper.vm.handleClick).not.toHaveBeenCalled(); + expect(wrapper.vm.handleSelectionChange).not.toHaveBeenCalled(); + expect(wrapper.vm.handlePushClickEventToGACheck).not.toHaveBeenCalled(); + }); + + test("eventType === eventTypes.SPACE => do nothing", () => { + // Arrange + const { wrapper } = setupMocks({ + mockData: { + propsData: { + isMultiSelect: true, + }, + }, + }); + wrapper.vm.handleClick = jest.fn(); + wrapper.vm.handlePushClickEventToGACheck = jest.fn(); + wrapper.vm.handleSelectionChange = jest.fn(); + + // Act + wrapper.vm.handleEventAction("space", { myEvent: "test" }); + + // Assert + expect(wrapper.vm.handleClick).not.toHaveBeenCalled(); + expect(wrapper.vm.handleSelectionChange).not.toHaveBeenCalled(); + expect(wrapper.vm.handlePushClickEventToGACheck).not.toHaveBeenCalled(); + }); + }); + + describe("!isMultiSelect", () => { + test("eventType === eventTypes.CLICK => call correct methods", () => { + // Arrange + const { wrapper } = setupMocks({ + mockData: { + propsData: { + isMultiSelect: false, + }, + }, + }); + wrapper.vm.handleClick = jest.fn(); + wrapper.vm.handlePushClickEventToGACheck = jest.fn(); + wrapper.vm.handleSelectionChange = jest.fn(); + + // Act + wrapper.vm.handleEventAction("click", { myEvent: "test" }); + + // Assert + expect(wrapper.vm.handleClick).toHaveBeenCalledWith({ + myEvent: "test", + }); + expect(wrapper.vm.handlePushClickEventToGACheck).toHaveBeenCalledWith("click"); + expect(wrapper.vm.handleSelectionChange).not.toHaveBeenCalled(); + }); + + test("eventType === eventTypes.ENTER => call correct methods", () => { + // Arrange + const { wrapper } = setupMocks({ + mockData: { + propsData: { + isMultiSelect: false, + }, + }, + }); + wrapper.vm.handleClick = jest.fn(); + wrapper.vm.handlePushClickEventToGACheck = jest.fn(); + wrapper.vm.handleSelectionChange = jest.fn(); + + // Act + wrapper.vm.handleEventAction("enter", { myEvent: "test" }); + + // Assert + expect(wrapper.vm.handleClick).toHaveBeenCalledWith({ + myEvent: "test", + }); + expect(wrapper.vm.handlePushClickEventToGACheck).toHaveBeenCalledWith("click"); + expect(wrapper.vm.handleSelectionChange).not.toHaveBeenCalled(); + }); + + test("eventType === eventTypes.SPACE => call correct methods", () => { + // Arrange + const { wrapper } = setupMocks({ + mockData: { + propsData: { + isMultiSelect: false, + }, + }, + }); + wrapper.vm.handleClick = jest.fn(); + wrapper.vm.handlePushClickEventToGACheck = jest.fn(); + wrapper.vm.handleSelectionChange = jest.fn(); + + // Act + wrapper.vm.handleEventAction("space", { myEvent: "test" }); + + // Assert + expect(wrapper.vm.handleClick).toHaveBeenCalledWith({ + myEvent: "test", + }); + expect(wrapper.vm.handlePushClickEventToGACheck).toHaveBeenCalledWith("click"); + expect(wrapper.vm.handleSelectionChange).not.toHaveBeenCalled(); + }); + + test("eventType === eventTypes.CHANGE && !selectingInitiatesLoad => call correct methods", () => { + // Arrange + const { wrapper } = setupMocks({ + mockData: { + propsData: { + isMultiSelect: false, + selectingInitiatesLoad: false, + }, + }, + }); + wrapper.vm.handleClick = jest.fn(); + wrapper.vm.handlePushClickEventToGACheck = jest.fn(); + wrapper.vm.handleSelectionChange = jest.fn(); + + // Act + wrapper.vm.handleEventAction("change", { myEvent: "test" }); + + // Assert + expect(wrapper.vm.handleSelectionChange).not.toHaveBeenCalled(); + expect(wrapper.vm.handleClick).toHaveBeenCalledWith({ + myEvent: "test", + }); + expect(wrapper.vm.handlePushClickEventToGACheck).not.toHaveBeenCalled(); + }); + + test("eventType === eventTypes.CHANGE && selectingInitiatesLoad => call correct methods", () => { + // Arrange + const { wrapper } = setupMocks({ + mockData: { + propsData: { + isMultiSelect: false, + selectingInitiatesLoad: true, + }, + }, + }); + wrapper.vm.handleClick = jest.fn(); + wrapper.vm.handlePushClickEventToGACheck = jest.fn(); + wrapper.vm.handleSelectionChange = jest.fn(); + + // Act + wrapper.vm.handleEventAction("change", { myEvent: "test" }); + + // Assert + expect(wrapper.vm.handleSelectionChange).toHaveBeenCalledWith({ + myEvent: "test", + }); + expect(wrapper.vm.handleClick).not.toHaveBeenCalled(); + expect(wrapper.vm.handlePushClickEventToGACheck).not.toHaveBeenCalled(); + }); + }); + }); + + describe("handleSelectionChange", () => { + const isCheckbox = [ + [true, ["Hi"]], + [false, "Hi"], + ]; + test.each(isCheckbox)( + "handleChange is called with valueToEmit", + (isMultiSelect, resultingValueToEmit) => { + // Arrange + const { wrapper } = setupMocks({ + mockData: { + propsData: { + isMultiSelect: isMultiSelect, + value: "Hi", + }, + }, + }); + + wrapper.vm.handleChange = jest.fn(); + + // Act + wrapper.vm.handleSelectionChange({ myEvent: "TEST" }); + + // Assert + expect(wrapper.vm.handleChange).toHaveBeenCalledWith(resultingValueToEmit); + } + ); + + describe("checkbox", () => { + test("modelValue is null => valueToEmit is correct value", () => { + // Arrange + const { wrapper } = setupMocks({ + mockData: { + propsData: { + isMultiSelect: true, + value: "Bello", + modelValue: null, + }, + }, + }); + + wrapper.vm.handleChange = jest.fn(); + + // Act + wrapper.vm.handleSelectionChange({ myEvent: "TEST" }); + + // Assert + expect(wrapper.vm.valueToEmit).toEqual(["Bello"]); + expect(wrapper.vm.handleChange).toHaveBeenCalledWith(["Bello"]); + }); + + test("modelValue is undefined => valueToEmit is correct value", () => { + // Arrange + const { wrapper } = setupMocks({ + mockData: { + propsData: { + isMultiSelect: true, + value: "Bello", + modelValue: undefined, + }, + }, + }); + + wrapper.vm.handleChange = jest.fn(); + + // Act + wrapper.vm.handleSelectionChange({ myEvent: "TEST" }); + + // Assert + expect(wrapper.vm.valueToEmit).toEqual(["Bello"]); + expect(wrapper.vm.handleChange).toHaveBeenCalledWith(["Bello"]); + }); + + test("modelValue is empty => valueToEmit is correct value", () => { + // Arrange + const { wrapper } = setupMocks({ + mockData: { + propsData: { + isMultiSelect: true, + value: "Bello", + modelValue: [], + }, + }, + }); + + wrapper.vm.handleChange = jest.fn(); + + // Act + wrapper.vm.handleSelectionChange({ myEvent: "TEST" }); + + // Assert + expect(wrapper.vm.valueToEmit).toEqual(["Bello"]); + expect(wrapper.vm.handleChange).toHaveBeenCalledWith(["Bello"]); + }); + + test("modelValue is not empty and does not contain this button's value => valueToEmit is correct value", () => { + // Arrange + const { wrapper } = setupMocks({ + mockData: { + propsData: { + isMultiSelect: true, + value: "Bello", + modelValue: ["Hello"], + }, + }, + }); + + wrapper.vm.handleChange = jest.fn(); + + // Act + wrapper.vm.handleSelectionChange({ myEvent: "TEST" }); + + // Assert + expect(wrapper.vm.valueToEmit).toEqual(["Hello", "Bello"]); + expect(wrapper.vm.handleChange).toHaveBeenCalledWith(["Hello", "Bello"]); + }); + + // testing when value is at start/middle/end of modelValue + const modelValues = [ + [["Bello", "Hello", "Mello"]], + [["Hello", "Bello", "Mello"]], + [["Hello", "Mello", "Bello"]], + ]; + test.each(modelValues)( + "modelValue is not empty and does contain this button's value => valueToEmit is correct value", + (modelValue) => { + // Arrange + const { wrapper } = setupMocks({ + mockData: { + propsData: { + isMultiSelect: true, + value: "Bello", + modelValue: modelValue, + }, + }, + }); + + wrapper.vm.handleChange = jest.fn(); + + // Act + wrapper.vm.handleSelectionChange({ myEvent: "TEST" }); + + // Assert + expect(wrapper.vm.valueToEmit).toEqual(["Hello", "Mello"]); + expect(wrapper.vm.handleChange).toHaveBeenCalledWith(["Hello", "Mello"]); + } + ); + }); + + describe("radio", () => { + const modelValues = [null, undefined, "Bello", "Hello"]; + test.each(modelValues)( + "regardless of modelValue, set valueToEmit to correct value", + (modelValue) => { + // Arrange + const { wrapper } = setupMocks({ + mockData: { + propsData: { + isMultiSelect: false, + value: "Bello", + modelValue: modelValue, + }, + }, + }); + + wrapper.vm.handleChange = jest.fn(); + + // Act + wrapper.vm.handleSelectionChange({ myEvent: "TEST" }); + + // Assert + expect(wrapper.vm.valueToEmit).toEqual("Bello"); + expect(wrapper.vm.handleChange).toHaveBeenCalledWith("Bello"); + } + ); + }); + }); + + describe("handleClick", () => { + const isCheckbox = [[true], [false]]; + test.each(isCheckbox)("handleSelectionChange is also called", async (isMultiSelect) => { + // Arrange + const { wrapper } = setupMocks({ + mockData: { + propsData: { + isMultiSelect: isMultiSelect, + value: "Bello", + }, + }, + }); + + wrapper.vm.handleSelectionChange = jest.fn(); + await wrapper.setData({ valueToEmit: "HELLO WORLD" }); + + // Act + wrapper.vm.handleClick({ myEvent: "Test" }); + + // Assert + expect(wrapper.vm.handleSelectionChange).toHaveBeenCalledWith({ myEvent: "Test" }); + }); + + test.each(isCheckbox)( + "update:modelValue is emitted with valueToEmit", + async (isMultiSelect) => { + // Arrange + const { wrapper } = setupMocks({ + mockData: { + propsData: { + isMultiSelect: isMultiSelect, + value: "Bello", + }, + }, + }); + + wrapper.vm.handleSelectionChange = jest.fn(); + await wrapper.setData({ valueToEmit: "HELLO WORLD" }); + + // Act + wrapper.vm.handleClick({ myEvent: "Test" }); + + // Assert + expect(wrapper.emitted()["update:modelValue"][0][0]).toEqual("HELLO WORLD"); + } + ); + }); + + describe("handlePushClickEventToGACheck", () => { + test("called from click or click-like event => call pushClickEventToGA", () => { + // Arrange + const { wrapper } = setupMocks({}); + wrapper.vm.pushEventToGA = jest.fn(); + + // Act + wrapper.vm.handlePushClickEventToGACheck("click"); + + // Assert + expect(wrapper.vm.pushEventToGA).toHaveBeenCalledTimes(1); + }); + + describe("called from keyboard navigation/button-question-focus-helper", () => { + test("valueToEmit is not null, is a checked radio without selectingInitiatesLoad, and the last value pushed to GA was not this input button's value => pushClickEventToGA is called", async () => { + // Arrange + const { wrapper } = setupMocks({ + mockData: { + propsData: { + isMultiSelect: false, + modelValue: "value2", + value: "value2", + selectingInitiatesLoad: false, + }, + }, + }); + wrapper.vm.pushEventToGA = jest.fn(); + await wrapper.setData({ + valueToEmit: "value2", + lastValuePushedToGa: "value1", + }); + + // Act + wrapper.vm.handlePushClickEventToGACheck(); + + // Assert + expect(wrapper.vm.isChecked).toBe(true); + expect(wrapper.find("input").attributes().type).toBe("radio") + expect(wrapper.vm.pushEventToGA).toHaveBeenCalled(); + }); + + test("valueToEmit is not null, is a checked radio with selectingInitiatesLoad, and the last value pushed to GA was not this input button's value => pushClickEventToGA is called", async () => { + // Arrange + const { wrapper } = setupMocks({ + mockData: { + propsData: { + isMultiSelect: false, + modelValue: "value2", + value: "value2", + selectingInitiatesLoad: true, + }, + }, + }); + wrapper.vm.pushEventToGA = jest.fn(); + await wrapper.setData({ + valueToEmit: "value2", + lastValuePushedToGa: "value1", + }); + + // Act + wrapper.vm.handlePushClickEventToGACheck(); + + // Assert + expect(wrapper.vm.isChecked).toBe(true); + expect(wrapper.find("input").attributes().type).toBe("radio") + expect(wrapper.vm.pushEventToGA).not.toHaveBeenCalled(); + }); + + test("valueToEmit is not null, is a checkbox that's checked, and the last value pushed to GA was not this input button's value => pushClickEventToGA is not called", async () => { + // Arrange + const { wrapper } = setupMocks({ + mockData: { + propsData: { + isMultiSelect: true, + modelValue: ["value2"], + value: "value2", + selectingInitiatesLoad: false, + }, + }, + }); + wrapper.vm.pushEventToGA = jest.fn(); + await wrapper.setData({ + valueToEmit: "value2", + lastValuePushedToGa: "value1", + }); + + // Act + wrapper.vm.handlePushClickEventToGACheck(); + + // Assert + expect(wrapper.vm.isChecked).toBe(true); + expect(wrapper.find("input").attributes().type).toBe("checkbox") + expect(wrapper.vm.pushEventToGA).not.toHaveBeenCalled(); + }); + + test("valueToEmit is null => pushClickEventToGA not called", async () => { + // Arrange + const { wrapper } = setupMocks({ + mockData: { + propsData: { + isMultiSelect: false, + modelValue: "value2", + value: "value2", + selectingInitiatesLoad: false, + }, + }, + }); + wrapper.vm.pushEventToGA = jest.fn(); + await wrapper.setData({ + valueToEmit: null, + lastValuePushedToGa: "value1", + }); + + // Act + wrapper.vm.handlePushClickEventToGACheck(); + + // Assert + expect(wrapper.vm.isChecked).toBe(true); + expect(wrapper.find("input").attributes().type).toBe("radio") + expect(wrapper.vm.pushEventToGA).not.toHaveBeenCalled(); + }); + + test("valueToEmit is not null, is an unchecked checkbox, and the last value pushed to GA was not this input button's value => pushClickEventToGA is not called", async () => { + // Arrange + const { wrapper } = setupMocks({ + mockData: { + propsData: { + isMultiSelect: true, + modelValue: ["value1"], + value: "value2", + selectingInitiatesLoad: false, + }, + }, + }); + wrapper.vm.pushEventToGA = jest.fn(); + await wrapper.setData({ + valueToEmit: "value2", + lastValuePushedToGa: "value1", + }); + + // Act + wrapper.vm.handlePushClickEventToGACheck(); + + // Assert + expect(wrapper.vm.isChecked).toBe(false); + expect(wrapper.find("input").attributes().type).toBe("checkbox") + expect(wrapper.vm.pushEventToGA).not.toHaveBeenCalled(); + }); + + test("is an unchecked radio => pushClickEventToGA not called", async () => { + // Arrange + const { wrapper } = setupMocks({ + mockData: { + propsData: { + isMultiSelect: false, + modelValue: "value1", + value: "value2", + selectingInitiatesLoad: false, + }, + }, + }); + wrapper.vm.pushEventToGA = jest.fn(); + await wrapper.setData({ + valueToEmit: "value2", + lastValuePushedToGa: "value1", + }); + + // Act + wrapper.vm.handlePushClickEventToGACheck(); + + // Assert + expect(wrapper.vm.isChecked).toBe(false); + expect(wrapper.find("input").attributes().type).toBe("radio") + expect(wrapper.vm.pushEventToGA).not.toHaveBeenCalled(); + }); + + test("lastValuePushedToGa is the same as this input button's value => pushClickEventToGA not called", async () => { + // Arrange + const { wrapper } = setupMocks({ + mockData: { + propsData: { + isMultiSelect: false, + modelValue: "value2", + value: "value2", + selectingInitiatesLoad: false, + }, + }, + }); + wrapper.vm.pushEventToGA = jest.fn(); + await wrapper.setData({ + valueToEmit: "value2", + lastValuePushedToGa: "value2", + }); + + // Act + wrapper.vm.handlePushClickEventToGACheck(); + + // Assert + expect(wrapper.vm.isChecked).toBe(true); + expect(wrapper.find("input").attributes().type).toBe("radio") + expect(wrapper.vm.pushEventToGA).not.toHaveBeenCalled(); + }); + }); + }); + + describe("pushClickEventToGA", () => { + test("pushEventToGA is called correctly", () => { + // Arrange + const { wrapper } = setupMocks({ + mockData: { + propsData: { + value: "Bello", + setLastValuePushedToGa: jest.fn(), + }, + }, + }); + + wrapper.setData({ + GaActions: { + CLICKED: "Clicked", + }, + }); + + // Act + wrapper.vm.pushClickEventToGA(); + + // Assert + expect(wrapper.vm.pushEventToGA).toHaveBeenCalledWith( + "myPage", + "Clicked", + "Bello", + true, + undefined + ); + expect(wrapper.vm.setLastValuePushedToGa).toHaveBeenCalledWith("Bello"); + }); + + test("pass in a value => setLastValuePushedToGa is called with value", () => { + // Arrange + const { wrapper } = setupMocks({ + mockData: { + propsData: { + setLastValuePushedToGa: jest.fn() + } + } + }); + + // Act + wrapper.vm.pushClickEventToGA("hi there"); + + // Assert + expect(wrapper.vm.setLastValuePushedToGa).toHaveBeenCalledWith("hi there") + }); + + test("don't pass in a value => setLastValuePushedToGa is called with input button's value", () => { + // Arrange + const { wrapper } = setupMocks({ + mockData: { + propsData: { + value: "hello there", + setLastValuePushedToGa: jest.fn() + } + } + }); + + // Act + wrapper.vm.pushClickEventToGA(); + + // Assert + expect(wrapper.vm.setLastValuePushedToGa).toHaveBeenCalledWith("hello there") + }); + }); + }); + + describe("computed", () => { + describe("isChecked", () => { + describe("checkbox", () => { + const falsyModelValues = [[], null, undefined]; + test.each(falsyModelValues)( + "modelValue is falsy/empty => checkbox isn't checked", + (modelValue) => { + // Arrange/Act + const { wrapper } = setupMocks({ + mockData: { + propsData: { + isMultiSelect: true, + modelValue, + }, + }, + }); + + // Assert + const inputElement = wrapper.find("input"); + expect(wrapper.vm.isChecked).toEqual(false); + expect(inputElement.element.checked).toBe(false); + } + ); + + test("modelValue doesn't contain this button's value => checkbox isn't checked", async () => { + // Arrange/Act + const { wrapper } = setupMocks({ + mockData: { + propsData: { + isMultiSelect: true, + modelValue: ["Aaa", "Bbb", "Ccc"], + value: "Ddd", + }, + }, + }); + + await wrapper.vm.$nextTick(); + + // Assert + const inputElement = wrapper.find("input"); + expect(wrapper.vm.isChecked).toEqual(false); + expect(inputElement.element.checked).toBe(false); + }); + + test("modelValue contains this button's value => checkbox is checked", () => { + // Arrange/Act + const { wrapper } = setupMocks({ + mockData: { + propsData: { + isMultiSelect: true, + modelValue: ["Aaa", "Bbb", "Ddd", "Ccc"], + value: "Ddd", + }, + }, + }); + + // Assert + const inputElement = wrapper.find("input"); + expect(wrapper.vm.isChecked).toEqual(true); + expect(inputElement.element.checked).toBe(true); + }); + }); + + describe("radio", () => { + const falsyModelValues = ["", null, undefined, []]; + test.each(falsyModelValues)( + "modelValue is falsy/empty => radio button isn't checked", + (modelValue) => { + // Arrange/Act + const { wrapper } = setupMocks({ + mockData: { + propsData: { + isMultiSelect: false, + modelValue, + value: "Aaa", + }, + }, + }); + + // Assert + const inputElement = wrapper.find("input"); + expect(wrapper.vm.isChecked).toEqual(false); + expect(inputElement.element.checked).toBe(false); + } + ); + + test("modelValue equals this button's value => radio button is checked", () => { + // Arrange/Act + const { wrapper } = setupMocks({ + mockData: { + propsData: { + isMultiSelect: false, + modelValue: "Ddd", + value: "Ddd", + }, + }, + }); + + // Assert + const inputElement = wrapper.find("input"); + expect(wrapper.vm.isChecked).toEqual(true); + expect(inputElement.element.checked).toBe(true); + }); + + test("modelValue doesn't equal this button's value => radio button isn't checked", () => { + // Arrange/Act + const { wrapper } = setupMocks({ + mockData: { + propsData: { + isMultiSelect: false, + modelValue: "Aaa", + value: "Ddd", + }, + }, + }); + + // Assert + const inputElement = wrapper.find("input"); + expect(wrapper.vm.isChecked).toEqual(false); + expect(inputElement.element.checked).toBe(false); + }); + }); + }); + + describe("buttonId", () => { + test("groupName and value combo yield correct id for input button with string value", () => { + // Arrange/Act + const { wrapper } = setupMocks({ + mockData: { + propsData: { + groupName: "my test-name", + value: "Aaa-BBB CcC", + }, + }, + }); + + // Assert + const inputElement = wrapper.find("input"); + expect(wrapper.vm.buttonId).toBe("my-test-name-Aaa-BBB-CcC"); + expect(inputElement.attributes().id).toBe("my-test-name-Aaa-BBB-CcC"); + }); + + test("groupName and value combo yield correct id for input button with number value", () => { + // Arrange/Act + const { wrapper } = setupMocks({ + mockData: { + propsData: { + groupName: "my test-name", + value: 2, + }, + }, + }); + + // Assert + const inputElement = wrapper.find("input"); + expect(wrapper.vm.buttonId).toBe("my-test-name-2"); + expect(inputElement.attributes().id).toBe("my-test-name-2"); + }); + }); + + describe("inputType", () => { + test("isMultiSelect is true => inputType is 'checkbox'", () => { + // Arrange/Act + const { wrapper } = setupMocks({ + mockData: { + propsData: { + isMultiSelect: true, + }, + }, + }); + + // Assert + const inputElement = wrapper.find("input"); + expect(wrapper.vm.inputType).toEqual("checkbox"); + expect(inputElement.attributes().type).toBe("checkbox"); + }); + + test("isMultiSelect is false => inputType is 'radio'", () => { + // Arrange/Act + const { wrapper } = setupMocks({ + mockData: { + propsData: { + isMultiSelect: false, + }, + }, + }); + + // Assert + const inputElement = wrapper.find("input"); + expect(wrapper.vm.inputType).toEqual("radio"); + expect(inputElement.attributes().type).toBe("radio"); + }); + }); + }); +}); + +function setupMocks({ mockData = {}, shouldShallowMount = true }) { + const mountMockData = { + ...mockData, + propsData: { + // to get rid of some annoying warnings + groupName: "groupName", + modelValue: mockData.propsData?.isMultiSelect ? [] : "", + value: "5", + setLastValuePushedToGa: () => {}, + // should override the above if they exist + ...mockData.propsData, + }, + }; + + const wrapper = shouldShallowMount + ? shallowMount(baseInputButton, mountMockData) + : mount(baseInputButton, mountMockData); + + wrapper.vm.pushEventToGA = jest.fn(); + wrapper.vm.$route = { + query: { + fmgPage: "myPage", + }, + }; + wrapper.vm.GaActions = {}; + + return { wrapper }; +} diff --git a/src/common-components/base-input-button/base-input-button.vue b/src/common-components/base-input-button/base-input-button.vue new file mode 100644 index 000000000..c01a9a94c --- /dev/null +++ b/src/common-components/base-input-button/base-input-button.vue @@ -0,0 +1,201 @@ + + + + + diff --git a/src/common-components/base-input-button/button-functionality-props.js b/src/common-components/base-input-button/button-functionality-props.js new file mode 100644 index 000000000..b9a63c925 --- /dev/null +++ b/src/common-components/base-input-button/button-functionality-props.js @@ -0,0 +1,30 @@ +export const inputButtonProps = { + value: { + type: [String, Number], + required: true, + }, + modelValue: { + type: [Array, String, Number], + required: true, + }, + isMultiSelect: Boolean, + groupName: { + type: String, + required: true, + }, + validationRules: { + type: String, + default: "", + }, + valueToLogType: String, + isRequired: { + type: Boolean, + default: true, + }, + lastValuePushedToGa: [String, Number], + setLastValuePushedToGa: Function, + selectingInitiatesLoad: { + type: Boolean, + default: false, + }, +} \ No newline at end of file diff --git a/src/common-components/button-question/button-question.spec.js b/src/common-components/button-question/button-question.spec.js index 3002920e8..22e50b215 100644 --- a/src/common-components/button-question/button-question.spec.js +++ b/src/common-components/button-question/button-question.spec.js @@ -2,189 +2,1128 @@ import { shallowMount } from "@vue/test-utils"; import buttonQuestion from "@/common-components/button-question/button-question"; import { getMountOptions } from "@/helpers/unit-test-helper.js"; -jest.mock("@/store", () => { return {}; }, { virtual: true }); +jest.mock( + "@/store", + () => { + return {}; + }, + { virtual: true } +); describe("buttonQuestion.vue", () => { - it("Should show overflow classes on fieldset if isOverflowScrollable is true", () => { - // Act - const wrapper = shallowMount(buttonQuestion, { - propsData: { - isOverflowScrollable: true, - groupName: "group-name" - } + it("Should show overflow classes on fieldset if isOverflowScrollable is true", () => { + // Act + const wrapper = shallowMount(buttonQuestion, { + propsData: { + isOverflowScrollable: true, + groupName: "group-name", + }, + }); + + // Assert + const fieldSet = wrapper.find("fieldset"); + expect(fieldSet.classes()).toContain("overflow-scroll"); + }); +}); + +describe("buttonQuestion.vue", () => { + it("Fieldset classes should contain row if button type is listCard", () => { + // Act + const wrapper = shallowMount(buttonQuestion, { + propsData: { + buttonType: "listCard", + groupName: "group-name", + }, + }); + // Assert + const Div = wrapper.find("fieldset div"); + expect(Div.classes()).toContain("row"); + }); +}); + +describe("buttonQuestion.vue", () => { + it("Fieldset classes should contain d-flex if button type is listButtonHorizontal", () => { + // Act + const wrapper = shallowMount(buttonQuestion, { + propsData: { + buttonType: "listButtonHorizontal", + groupName: "group-name", + }, + }); + // Assert + const Div = wrapper.find("fieldset div"); + expect(Div.classes()).toContain("d-flex"); + }); +}); + +describe("buttonQuestion.vue", () => { + it("Fieldset classes should contain ui-radio if button type is radio", () => { + // Act + const wrapper = shallowMount(buttonQuestion, { + propsData: { + buttonType: "radio", + groupName: "group-name", + }, + }); + // Assert + const Div = wrapper.find("fieldset div"); + expect(Div.classes()).toContain("ui-radio"); + }); +}); + +describe("buttonQuestion.vue", () => { + describe("selectedValues", () => { + test("is radio => should emit captured value", async () => { + // Arrange + const wrapper = shallowMount( + buttonQuestion, + setupMocks({ propsData: { groupName: "group-name" } }) + ); + await wrapper.setProps({ + answers: ["2022", "2021", "2020"], + isMultiSelect: false, + modelValue: "", + }); + + // Act + wrapper.vm.selectedValues = "2021"; + + // Assert + expect(wrapper.emitted()["update:modelValue"][0][0]).toEqual("2021"); + }); + + test("is checkbox => should emit captured value", async () => { + const wrapper = shallowMount( + buttonQuestion, + setupMocks({ propsData: { groupName: "group-name" } }) + ); + await wrapper.setProps({ + answers: ["2022", "2021", "2020"], + isMultiSelect: false, + modelValue: "", + }); + + // Act + wrapper.vm.selectedValues = ["2022", "2021", "2020", "2019", "2020"]; + + // Assert + expect(wrapper.emitted()["update:modelValue"][0][0]).toEqual([ + "2022", + "2021", + "2020", + "2019", + "2020", + ]); + }); }); - // Assert - const fieldSet = wrapper.find('fieldset'); - expect(fieldSet.classes()).toContain("overflow-scroll"); - }); -}); + describe("buttonsInfo", () => { + describe("buttonLabel", () => { + test("answers have buttonLabel properties => buttonsInfo buttonsLabel properties are correct", () => { + // Arrange + const wrapper = shallowMount( + buttonQuestion, + setupMocks({ + propsData: { + answers: [ + { + buttonLabel: "Label 1", + }, + { + buttonLabel: "Label 2", + }, + ], + }, + }) + ); -describe("buttonQuestion.vue", () => { - it("Fieldset classes should contain row if button type is listCard", () => { - // Act - const wrapper = shallowMount(buttonQuestion, { - propsData: { - buttonType: "listCard", - groupName: "group-name" - } + // Act + const buttonsInfo = wrapper.vm.buttonsInfo; + + // Assert + expect(buttonsInfo[0].buttonLabel).toEqual("Label 1"); + expect(buttonsInfo[1].buttonLabel).toEqual("Label 2"); + }); + + test("answers have Text properties, no buttonLabel properties => buttonsInfo buttonsLabel properties are correct", () => { + // Arrange + const wrapper = shallowMount( + buttonQuestion, + setupMocks({ + propsData: { + answers: [ + { + Text: "Label 1", + }, + { + Text: "Label 2", + }, + ], + }, + }) + ); + + // Act + const buttonsInfo = wrapper.vm.buttonsInfo; + + // Assert + expect(buttonsInfo[0].buttonLabel).toEqual("Label 1"); + expect(buttonsInfo[1].buttonLabel).toEqual("Label 2"); + }); + + test("answers have buttonLabel and Text properties => buttonsInfo buttonsLabel properties are correct", () => { + // Arrange + const wrapper = shallowMount( + buttonQuestion, + setupMocks({ + propsData: { + answers: [ + { + buttonLabel: "buttonLabel 1", + Text: "Text 1", + }, + { + buttonLabel: "buttonLabel 2", + Text: "Text 2", + }, + ], + }, + }) + ); + + // Act + const buttonsInfo = wrapper.vm.buttonsInfo; + + // Assert + expect(buttonsInfo[0].buttonLabel).toEqual("buttonLabel 1"); + expect(buttonsInfo[1].buttonLabel).toEqual("buttonLabel 2"); + }); + + test("answers is an array of strings => buttonLabel is answer values", () => { + // Arrange + const wrapper = shallowMount( + buttonQuestion, + setupMocks({ + propsData: { + answers: ["answer 1", "answer 2"], + }, + }) + ); + + // Act + const buttonsInfo = wrapper.vm.buttonsInfo; + + // Assert + expect(buttonsInfo[0].buttonLabel).toEqual("answer 1"); + expect(buttonsInfo[1].buttonLabel).toEqual("answer 2"); + }); + }); + + describe("altText", () => { + test("answers have altText properties => buttonsInfo altText properties are correct", () => { + // Arrange + const wrapper = shallowMount( + buttonQuestion, + setupMocks({ + propsData: { + answers: [ + { + altText: "altText 1", + }, + { + altText: "altText 2", + }, + ], + }, + }) + ); + + // Act + const buttonsInfo = wrapper.vm.buttonsInfo; + + // Assert + expect(buttonsInfo[0].altText).toEqual("altText 1"); + expect(buttonsInfo[1].altText).toEqual("altText 2"); + }); + + test("answers have Name properties, no buttonLabel properties => buttonsInfo altText properties are correct", () => { + // Arrange + const wrapper = shallowMount( + buttonQuestion, + setupMocks({ + propsData: { + answers: [ + { + Name: "Name 1", + }, + { + Name: "Name 2", + }, + ], + }, + }) + ); + + // Act + const buttonsInfo = wrapper.vm.buttonsInfo; + + // Assert + expect(buttonsInfo[0].altText).toEqual("Name 1"); + expect(buttonsInfo[1].altText).toEqual("Name 2"); + }); + + test("answers have altText and Name properties => buttonsInfo altText properties are correct", () => { + // Arrange + const wrapper = shallowMount( + buttonQuestion, + setupMocks({ + propsData: { + answers: [ + { + altText: "altText 1", + Name: "Name 1", + }, + { + altText: "altText 2", + Name: "Name 2", + }, + ], + }, + }) + ); + + // Act + const buttonsInfo = wrapper.vm.buttonsInfo; + + // Assert + expect(buttonsInfo[0].altText).toEqual("altText 1"); + expect(buttonsInfo[1].altText).toEqual("altText 2"); + }); + + test("answers is an array of strings => altText is answer values", () => { + // Arrange + const wrapper = shallowMount( + buttonQuestion, + setupMocks({ + propsData: { + answers: ["answer 1", "answer 2"], + }, + }) + ); + + // Act + const buttonsInfo = wrapper.vm.buttonsInfo; + + // Assert + expect(buttonsInfo[0].altText).toEqual("answer 1"); + expect(buttonsInfo[1].altText).toEqual("answer 2"); + }); + }); + + describe("buttonLabelSubCopy", () => { + test("answers have buttonLabelSubCopy properties => buttonsInfo buttonLabelSubCopy properties are correct", () => { + // Arrange + const wrapper = shallowMount( + buttonQuestion, + setupMocks({ + propsData: { + answers: [ + { + buttonLabelSubCopy: "buttonLabelSubCopy 1", + }, + { + buttonLabelSubCopy: "buttonLabelSubCopy 2", + }, + ], + }, + }) + ); + + // Act + const buttonsInfo = wrapper.vm.buttonsInfo; + + // Assert + expect(buttonsInfo[0].buttonLabelSubCopy).toEqual("buttonLabelSubCopy 1"); + expect(buttonsInfo[1].buttonLabelSubCopy).toEqual("buttonLabelSubCopy 2"); + }); + + test("answers have SubText properties, no buttonLabelSubCopy properties => buttonsInfo buttonLabelSubCopy properties are correct", () => { + // Arrange + const wrapper = shallowMount( + buttonQuestion, + setupMocks({ + propsData: { + answers: [ + { + SubText: "SubText 1", + }, + { + SubText: "SubText 2", + }, + ], + }, + }) + ); + + // Act + const buttonsInfo = wrapper.vm.buttonsInfo; + + // Assert + expect(buttonsInfo[0].buttonLabelSubCopy).toEqual("SubText 1"); + expect(buttonsInfo[1].buttonLabelSubCopy).toEqual("SubText 2"); + }); + + test("answers have buttonLabelSubCopy and SubText properties => buttonsInfo buttonLabelSubCopy properties are correct", () => { + // Arrange + const wrapper = shallowMount( + buttonQuestion, + setupMocks({ + propsData: { + answers: [ + { + buttonLabelSubCopy: "buttonLabelSubCopy 1", + SubText: "buttonLabelSubCopy 1", + }, + { + buttonLabelSubCopy: "buttonLabelSubCopy 2", + SubText: "buttonLabelSubCopy 2", + }, + ], + }, + }) + ); + + // Act + const buttonsInfo = wrapper.vm.buttonsInfo; + + // Assert + expect(buttonsInfo[0].buttonLabelSubCopy).toEqual("buttonLabelSubCopy 1"); + expect(buttonsInfo[1].buttonLabelSubCopy).toEqual("buttonLabelSubCopy 2"); + }); + + test("answers is an array of strings => there are no buttonLabelSubCopy properties", () => { + // Arrange + const wrapper = shallowMount( + buttonQuestion, + setupMocks({ + propsData: { + answers: ["answer 1", "answer 2"], + }, + }) + ); + + // Act + const buttonsInfo = wrapper.vm.buttonsInfo; + + // Assert + expect(buttonsInfo[0].buttonLabelSubCopy).toBe(undefined); + expect(buttonsInfo[1].buttonLabelSubCopy).toBe(undefined); + }); + }); + + describe("buttonImage", () => { + test("answers have buttonImage properties => buttonsInfo buttonImage properties are correct", () => { + // Arrange + const wrapper = shallowMount( + buttonQuestion, + setupMocks({ + propsData: { + answers: [ + { + buttonImage: "buttonImage 1", + }, + { + buttonImage: "buttonImage 2", + }, + ], + }, + }) + ); + + // Act + const buttonsInfo = wrapper.vm.buttonsInfo; + + // Assert + expect(buttonsInfo[0].buttonImage).toEqual("buttonImage 1"); + expect(buttonsInfo[1].buttonImage).toEqual("buttonImage 2"); + }); + + test("answers have AnswerImageUrl properties, no buttonImage properties => buttonsInfo buttonImage properties are correct", () => { + // Arrange + const wrapper = shallowMount( + buttonQuestion, + setupMocks({ + propsData: { + answers: [ + { + AnswerImageUrl: "AnswerImageUrl 1", + }, + { + AnswerImageUrl: "AnswerImageUrl 2", + }, + ], + }, + }) + ); + + // Act + const buttonsInfo = wrapper.vm.buttonsInfo; + + // Assert + expect(buttonsInfo[0].buttonImage).toEqual("AnswerImageUrl 1"); + expect(buttonsInfo[1].buttonImage).toEqual("AnswerImageUrl 2"); + }); + + test("answers have buttonImage and AnswerImageUrl properties => buttonsInfo buttonImage properties are correct", () => { + // Arrange + const wrapper = shallowMount( + buttonQuestion, + setupMocks({ + propsData: { + answers: [ + { + buttonImage: "buttonImage 1", + AnswerImageUrl: "AnswerImageUrl 1", + }, + { + buttonImage: "buttonImage 2", + AnswerImageUrl: "AnswerImageUrl 2", + }, + ], + }, + }) + ); + + // Act + const buttonsInfo = wrapper.vm.buttonsInfo; + + // Assert + expect(buttonsInfo[0].buttonImage).toEqual("buttonImage 1"); + expect(buttonsInfo[1].buttonImage).toEqual("buttonImage 2"); + }); + + test("answers is an array of strings => there are no buttonImage properties", () => { + // Arrange + const wrapper = shallowMount( + buttonQuestion, + setupMocks({ + propsData: { + answers: ["answer 1", "answer 2"], + }, + }) + ); + + // Act + const buttonsInfo = wrapper.vm.buttonsInfo; + + // Assert + expect(buttonsInfo[0].buttonImage).toBe(undefined); + expect(buttonsInfo[1].buttonImage).toBe(undefined); + }); + }); + + describe("buttonImageId", () => { + test("answers have buttonImageId properties => buttonsInfo buttonImageId properties are correct", () => { + // Arrange + const wrapper = shallowMount( + buttonQuestion, + setupMocks({ + propsData: { + answers: [ + { + buttonImageId: "buttonImageId 1", + }, + { + buttonImageId: "buttonImageId 2", + }, + ], + }, + }) + ); + + // Act + const buttonsInfo = wrapper.vm.buttonsInfo; + + // Assert + expect(buttonsInfo[0].buttonImageId).toEqual("buttonImageId 1"); + expect(buttonsInfo[1].buttonImageId).toEqual("buttonImageId 2"); + }); + + test("answers have ImageId properties, no buttonImageId properties => buttonsInfo buttonImage properties are correct", () => { + // Arrange + const wrapper = shallowMount( + buttonQuestion, + setupMocks({ + propsData: { + answers: [ + { + ImageId: "ImageId 1", + }, + { + ImageId: "ImageId 2", + }, + ], + }, + }) + ); + + // Act + const buttonsInfo = wrapper.vm.buttonsInfo; + + // Assert + expect(buttonsInfo[0].buttonImageId).toEqual("ImageId 1"); + expect(buttonsInfo[1].buttonImageId).toEqual("ImageId 2"); + }); + + test("answers have buttonImageId and ImageId properties => buttonsInfo buttonImageId properties are correct", () => { + // Arrange + const wrapper = shallowMount( + buttonQuestion, + setupMocks({ + propsData: { + answers: [ + { + buttonImageId: "buttonImageId 1", + ImageId: "ImageId 1", + }, + { + buttonImageId: "buttonImageId 2", + ImageId: "ImageId 2", + }, + ], + }, + }) + ); + + // Act + const buttonsInfo = wrapper.vm.buttonsInfo; + + // Assert + expect(buttonsInfo[0].buttonImageId).toEqual("buttonImageId 1"); + expect(buttonsInfo[1].buttonImageId).toEqual("buttonImageId 2"); + }); + + test("answers is an array of strings => there are no buttonImageId properties", () => { + // Arrange + const wrapper = shallowMount( + buttonQuestion, + setupMocks({ + propsData: { + answers: ["answer 1", "answer 2"], + }, + }) + ); + + // Act + const buttonsInfo = wrapper.vm.buttonsInfo; + + // Assert + expect(buttonsInfo[0].buttonImageId).toBe(undefined); + expect(buttonsInfo[1].buttonImageId).toBe(undefined); + }); + }); + + describe("groupName", () => { + const answers = [[["answer 1", "answer 2"]], [[{ value: 1 }, { value: 2 }]]]; + test.each(answers)( + "answers have groupName properties with spaces => buttonsInfo groupName properties are correct", + (answerGroup) => { + // Arrange + const wrapper = shallowMount( + buttonQuestion, + setupMocks({ + propsData: { + answers: answerGroup, + groupName: "this is my group name", + }, + }) + ); + + // Act + const buttonsInfo = wrapper.vm.buttonsInfo; + + // Assert + expect(buttonsInfo[0].groupName).toEqual("this-is-my-group-name"); + expect(buttonsInfo[1].groupName).toEqual("this-is-my-group-name"); + } + ); + + test.each(answers)( + "answers have groupName properties with no spaces => buttonsInfo groupName properties are correct", + (answerGroup) => { + // Arrange + const wrapper = shallowMount( + buttonQuestion, + setupMocks({ + propsData: { + answers: answerGroup, + groupName: "this-is-my-group-name", + }, + }) + ); + + // Act + const buttonsInfo = wrapper.vm.buttonsInfo; + + // Assert + expect(buttonsInfo[0].groupName).toEqual("this-is-my-group-name"); + expect(buttonsInfo[1].groupName).toEqual("this-is-my-group-name"); + } + ); + }); + + describe("value", () => { + describe("useTextForValue is true", () => { + test("answers have value properties => buttonsInfo value properties are correct", () => { + // Arrange + const wrapper = shallowMount( + buttonQuestion, + setupMocks({ + propsData: { + useTextForValue: true, + answers: [ + { + value: "value 1", + }, + { + value: "value 2", + }, + ], + }, + }) + ); + + // Act + const buttonsInfo = wrapper.vm.buttonsInfo; + + // Assert + expect(buttonsInfo[0].value).toEqual("value 1"); + expect(buttonsInfo[1].value).toEqual("value 2"); + }); + + test("answers have Text properties => buttonsInfo value properties are correct", () => { + // Arrange + const wrapper = shallowMount( + buttonQuestion, + setupMocks({ + propsData: { + useTextForValue: true, + answers: [ + { + Text: "Text 1", + }, + { + Text: "Text 2", + }, + ], + }, + }) + ); + + // Act + const buttonsInfo = wrapper.vm.buttonsInfo; + + // Assert + expect(buttonsInfo[0].value).toEqual("Text 1"); + expect(buttonsInfo[1].value).toEqual("Text 2"); + }); + + test("answers have Name properties => buttonsInfo value properties are correct", () => { + // Arrange + const wrapper = shallowMount( + buttonQuestion, + setupMocks({ + propsData: { + useTextForValue: true, + answers: [ + { + Name: "Name 1", + }, + { + Name: "Name 2", + }, + ], + }, + }) + ); + + // Act + const buttonsInfo = wrapper.vm.buttonsInfo; + + // Assert + expect(buttonsInfo[0].value).toEqual("Name 1"); + expect(buttonsInfo[1].value).toEqual("Name 2"); + }); + + test("answers have value and Text properties, no Name properties => buttonsInfo value properties are correct", () => { + // Arrange + const wrapper = shallowMount( + buttonQuestion, + setupMocks({ + propsData: { + useTextForValue: true, + answers: [ + { + value: "value 1", + Text: "Text 1", + }, + { + value: "value 2", + Text: "Text 2", + }, + ], + }, + }) + ); + + // Act + const buttonsInfo = wrapper.vm.buttonsInfo; + + // Assert + expect(buttonsInfo[0].value).toEqual("value 1"); + expect(buttonsInfo[1].value).toEqual("value 2"); + }); + + test("answers have value and Name properties, no Text properties => buttonsInfo value properties are correct", () => { + // Arrange + const wrapper = shallowMount( + buttonQuestion, + setupMocks({ + propsData: { + useTextForValue: true, + answers: [ + { + value: "value 1", + Name: "Name 1", + }, + { + value: "value 2", + Name: "Name 2", + }, + ], + }, + }) + ); + + // Act + const buttonsInfo = wrapper.vm.buttonsInfo; + + // Assert + expect(buttonsInfo[0].value).toEqual("value 1"); + expect(buttonsInfo[1].value).toEqual("value 2"); + }); + + test("answers have Text and Name properties, no value properties => buttonsInfo value properties are correct", () => { + // Arrange + const wrapper = shallowMount( + buttonQuestion, + setupMocks({ + propsData: { + useTextForValue: true, + answers: [ + { + Text: "Text 1", + Name: "Name 1", + }, + { + Text: "Text 2", + Name: "Name 2", + }, + ], + }, + }) + ); + + // Act + const buttonsInfo = wrapper.vm.buttonsInfo; + + // Assert + expect(buttonsInfo[0].value).toEqual("Text 1"); + expect(buttonsInfo[1].value).toEqual("Text 2"); + }); + + test("answers have value, Text, and Name properties => buttonsInfo value properties are correct", () => { + // Arrange + const wrapper = shallowMount( + buttonQuestion, + setupMocks({ + propsData: { + useTextForValue: true, + answers: [ + { + value: "value 1", + Text: "Text 1", + Name: "Name 1", + }, + { + value: "value 2", + Text: "Text 2", + Name: "Name 2", + }, + ], + }, + }) + ); + + // Act + const buttonsInfo = wrapper.vm.buttonsInfo; + + // Assert + expect(buttonsInfo[0].value).toEqual("value 1"); + expect(buttonsInfo[1].value).toEqual("value 2"); + }); + + test("answers is an array of strings => buttonInfo value property values are values from array", () => { + // Arrange + const wrapper = shallowMount( + buttonQuestion, + setupMocks({ + propsData: { + useTextForValue: true, + answers: ["answer 1", "answer 2"], + }, + }) + ); + + // Act + const buttonsInfo = wrapper.vm.buttonsInfo; + + // Assert + expect(buttonsInfo[0].value).toEqual("answer 1"); + expect(buttonsInfo[1].value).toEqual("answer 2"); + }); + }); + + describe("useTextForValue is false", () => { + test("answers have value properties => buttonsInfo value properties are correct", () => { + // Arrange + const wrapper = shallowMount( + buttonQuestion, + setupMocks({ + propsData: { + useTextForValue: false, + answers: [ + { + value: "value 1", + }, + { + value: "value 2", + }, + ], + }, + }) + ); + + // Act + const buttonsInfo = wrapper.vm.buttonsInfo; + + // Assert + expect(buttonsInfo[0].value).toEqual("value 1"); + expect(buttonsInfo[1].value).toEqual("value 2"); + }); + + test("answers have Text properties => buttonsInfo value properties are correct", () => { + // Arrange + const wrapper = shallowMount( + buttonQuestion, + setupMocks({ + propsData: { + useTextForValue: false, + answers: [ + { + Text: "Text 1", + }, + { + Text: "Text 2", + }, + ], + }, + }) + ); + + // Act + const buttonsInfo = wrapper.vm.buttonsInfo; + + // Assert + expect(buttonsInfo[0].value).toBe(null); + expect(buttonsInfo[1].value).toBe(null); + }); + + test("answers have Name properties => buttonsInfo value properties are correct", () => { + // Arrange + const wrapper = shallowMount( + buttonQuestion, + setupMocks({ + propsData: { + useTextForValue: false, + answers: [ + { + Name: "Name 1", + }, + { + Name: "Name 2", + }, + ], + }, + }) + ); + + // Act + const buttonsInfo = wrapper.vm.buttonsInfo; + + // Assert + expect(buttonsInfo[0].value).toEqual("Name 1"); + expect(buttonsInfo[1].value).toEqual("Name 2"); + }); + + test("answers have value and Text properties, no Name properties => buttonsInfo value properties are correct", () => { + // Arrange + const wrapper = shallowMount( + buttonQuestion, + setupMocks({ + propsData: { + useTextForValue: false, + answers: [ + { + value: "value 1", + Text: "Text 1", + }, + { + value: "value 2", + Text: "Text 2", + }, + ], + }, + }) + ); + + // Act + const buttonsInfo = wrapper.vm.buttonsInfo; + + // Assert + expect(buttonsInfo[0].value).toEqual("value 1"); + expect(buttonsInfo[1].value).toEqual("value 2"); + }); + + test("answers have value and Name properties, no Text properties => buttonsInfo value properties are correct", () => { + // Arrange + const wrapper = shallowMount( + buttonQuestion, + setupMocks({ + propsData: { + useTextForValue: false, + answers: [ + { + value: "value 1", + Name: "Name 1", + }, + { + value: "value 2", + Name: "Name 2", + }, + ], + }, + }) + ); + + // Act + const buttonsInfo = wrapper.vm.buttonsInfo; + + // Assert + expect(buttonsInfo[0].value).toEqual("value 1"); + expect(buttonsInfo[1].value).toEqual("value 2"); + }); + + test("answers have Text and Name properties, no value properties => buttonsInfo value properties are correct", () => { + // Arrange + const wrapper = shallowMount( + buttonQuestion, + setupMocks({ + propsData: { + useTextForValue: false, + answers: [ + { + Text: "Text 1", + Name: "Name 1", + }, + { + Text: "Text 2", + Name: "Name 2", + }, + ], + }, + }) + ); + + // Act + const buttonsInfo = wrapper.vm.buttonsInfo; + + // Assert + expect(buttonsInfo[0].value).toEqual("Name 1"); + expect(buttonsInfo[1].value).toEqual("Name 2"); + }); + + test("answers have value, Text, and Name properties => buttonsInfo value properties are correct", () => { + // Arrange + const wrapper = shallowMount( + buttonQuestion, + setupMocks({ + propsData: { + useTextForValue: false, + answers: [ + { + value: "value 1", + Text: "Text 1", + Name: "Name 1", + }, + { + value: "value 2", + Text: "Text 2", + Name: "Name 2", + }, + ], + }, + }) + ); + + // Act + const buttonsInfo = wrapper.vm.buttonsInfo; + + // Assert + expect(buttonsInfo[0].value).toEqual("value 1"); + expect(buttonsInfo[1].value).toEqual("value 2"); + }); + + test("answers is an array of strings => buttonInfo value property values are values from array", () => { + // Arrange + const wrapper = shallowMount( + buttonQuestion, + setupMocks({ + propsData: { + useTextForValue: false, + answers: ["answer 1", "answer 2"], + }, + }) + ); + + // Act + const buttonsInfo = wrapper.vm.buttonsInfo; + + // Assert + expect(buttonsInfo[0].value).toEqual("answer 1"); + expect(buttonsInfo[1].value).toEqual("answer 2"); + }); + }); + }); }); - // Assert - const Div = wrapper.find('fieldset div'); - expect(Div.classes()).toContain("row"); - }); -}); - -describe("buttonQuestion.vue", () => { - it("Fieldset classes should contain d-flex if button type is listButtonHorizontal", () => { - // Act - const wrapper = shallowMount(buttonQuestion, { - propsData: { - buttonType: "listButtonHorizontal", - groupName: "group-name" - } - }); - // Assert - const Div = wrapper.find('fieldset div'); - expect(Div.classes()).toContain("d-flex"); - }); -}); - -describe("buttonQuestion.vue", () => { - it("Fieldset classes should contain ui-radio if button type is radio", () => { - // Act - const wrapper = shallowMount(buttonQuestion, { - propsData: { - buttonType: "radio", - groupName: "group-name" - } - }); - // Assert - const Div = wrapper.find('fieldset div'); - expect(Div.classes()).toContain("ui-radio"); - }); -}); - - -// testing a computed property -describe("buttonQuestion.vue", () => { - it("getColLength should return '12' if prop isWide is set to true", () => { - // Act - const localThis = { isWide: true } - - expect(buttonQuestion.computed.getColLength.call(localThis)).toBe("12"); - }); -}); - -describe("buttonQuestion.vue", () => { - it("getColLength should return '' if prop isWide is set to false", () => { - // Act - const localThis = { - isWide: false, - answers: ['a', 'b'], - groupName: "group-name" - } - - expect(buttonQuestion.computed.getColLength.call(localThis)).toBe(""); - }); -}); - -describe("buttonQuestion.vue", () => { - it("Should return answer.Text if prop useTextForValue is true", async () => { - // Act - const localThis = { useTextForValue: true }; - const answer = { 'Name': 'testName', 'Text': 'testText' }; - - // Assert - expect(buttonQuestion.methods.getValue.call(localThis, answer)).toBe('testText'); - }); -}); - -describe("buttonQuestion.vue", () => { - it("Should return answer.Name if prop useTextForValue is false and answer.Name exists", async () => { - // Act - const localThis = { useTextForValue: false }; - const answer = { 'Name': 'testName', 'Text': 'testText' }; - - // Assert - expect(buttonQuestion.methods.getValue.call(localThis, answer)).toBe('testName'); - }); -}); - -describe("buttonQuestion.vue", () => { - it("Should trigger event modelValue change to new value on when radio button selected", async () => { - // Act - const wrapper = shallowMount(buttonQuestion, setupMocks({propsData: {groupName: "group-name"}})); - await wrapper.setProps({ - answers: ["2022", "2021", "2020"], - isMultiSelect: false, - modelValue: [] - }); - const val = { checkValue: true, value: "2021", } - wrapper.vm.handleCheckedChanged(val); - // Assert - expect(wrapper.emitted()["update:modelValue"][0]).toEqual([["2021"]]); - }); -}); - -describe("buttonQuestion.vue", () => { - it("Should add values to array on checkbox click", () => { - // Act - const wrapper = shallowMount(buttonQuestion, setupMocks({ - propsData: { - modelValue: ["2022", "2021", "2020"], - isMultiSelect: true, - groupName: "group-name" - } - })); - const val = { checkValue: true, value: "2019", } - wrapper.vm.handleCheckedChanged(val); - // Assert - expect(wrapper.emitted()["update:modelValue"][0]).toEqual([["2022", "2021", "2020", "2019"]]); - }); -}); - -describe("buttonQuestion.vue", () => { - it("Should add a value to this.selectedValues if prop isMultiSelect is true, checkValue is true and this.selectedValues already exists", () => { - // Act - const wrapper = shallowMount(buttonQuestion, setupMocks({ - propsData: { - isMultiSelect: true, - modelValue: ['a', 'b'], - groupName: "group-name" - } - })); - const val = { checkValue: true, value: "2021", } - wrapper.vm.handleCheckedChanged(val); - - // Assert - expect(wrapper.vm.selectedValues).toEqual(["a", "b", "2021"]); - }); -}); - -describe("buttonQuestion.vue", () => { - it("Should remove a value to this.selectedValues if prop isMultiSelect is true, checkValue is false and this.selectedValues already exists", () => { - // Act - const wrapper = shallowMount(buttonQuestion, setupMocks({ - propsData: { - isMultiSelect: true, - modelValue: ['a', 'b'], - groupName: "group-name" - } - })); - - const val = { checkValue: false, value: "a", } - wrapper.vm.handleCheckedChanged(val); - - // Assert - expect(wrapper.vm.selectedValues).toEqual(["b"]); - }); }); function setupMocks(mountOptionsMockData = {}) { - const defaultMountOptions = { route: { query: { fmgPage: 'page-name' } } }; - const baseMountOptions = getMountOptions(Object.assign(defaultMountOptions, mountOptionsMockData)); - const allMountOptions = Object.assign(defaultMountOptions, baseMountOptions); + const defaultMountOptions = { route: { query: { fmgPage: "page-name" } } }; + const baseMountOptions = getMountOptions( + Object.assign(defaultMountOptions, mountOptionsMockData) + ); + const allMountOptions = Object.assign(defaultMountOptions, baseMountOptions); - return allMountOptions; + return allMountOptions; } diff --git a/src/common-components/button-question/button-question.vue b/src/common-components/button-question/button-question.vue index 6cd06c7c5..9778cef8f 100644 --- a/src/common-components/button-question/button-question.vue +++ b/src/common-components/button-question/button-question.vue @@ -1,253 +1,249 @@ diff --git a/src/common-components/question-chain/question-chain.vue b/src/common-components/question-chain/question-chain.vue index 4eaa0b69e..40d8be911 100644 --- a/src/common-components/question-chain/question-chain.vue +++ b/src/common-components/question-chain/question-chain.vue @@ -8,8 +8,8 @@ :questionText="q.questionText" :answers="q.answers" :groupName="`question-${glassIndex}-${q.questionSequence}`" - v-model="q.answerSelected" - @isCheckedChanged="handleAnswer" + :modelValue="q.answerSelected" + @update:modelValue="handleAnswer(q, $event)" isRequired :validationRules="validationRules" /> @@ -45,13 +45,13 @@ export default { questionSequence: q.questionSequence, answers: q.answers.map((a) => { return { - Text: a.answerText, + buttonLabel: a.answerText, // Name will either be nextQuestionSequence or answerResult // Name will be used by list-button as the input value. // It must be a single string or number, so concatenating together a string with // 4 pieces of data separated by pipe characters: // question number|type of answer|answer value|answer text - Name: a.nextQuestionSequence ? + value: a.nextQuestionSequence ? q.questionSequence + "|nextQuestion|" + a.nextQuestionSequence + "|" + a.answerText : q.questionSequence + "|answer|" + a.answerResult + "|" + a.answerText, nextQuestionSequence: a.nextQuestionSequence, @@ -77,7 +77,8 @@ export default { } }, methods: { - handleAnswer(returnedAnswer) { + handleAnswer(question, returnedAnswer) { + question.answerSelected = returnedAnswer; /* returnedAnswer example format: { @@ -86,7 +87,7 @@ export default { "buttonId": "Driver-Front-1-1|answer|DD11132|Yes" } */ - const isQuestionChainComplete = this.getQuestionChainAnswerIfComplete(returnedAnswer.value); + const isQuestionChainComplete = this.getQuestionChainAnswerIfComplete(returnedAnswer); if (isQuestionChainComplete) { this.$emit("update:modelValue", isQuestionChainComplete); diff --git a/src/constants/store-actions.js b/src/constants/store-actions.js index 8f5891a3d..8c498feda 100644 --- a/src/constants/store-actions.js +++ b/src/constants/store-actions.js @@ -18,11 +18,11 @@ const storeActions = { LOOKUP_VEHICLE_BY_VIN: "lookupVehicleByVin", LOOKUP_VIN_BY_PLATE: "lookupVinByPlate", LOOKUP_VIN_BY_ADDRESS: "lookupVinByAddress", - GET_PARTS_OR_QUESTIONS: "getPartsOrQuestions", GET_PARTS: "getParts", GET_CAPABILITY_QUESTIONS: "getCapabilityQuestions", - GET_PART_FROM_CAPABILITY_QUESTION_ANSWER: "getPartFromCapabilityQuestionAnswer", + GET_PART_FROM_CAPABILITY_QUESTION_ANSWER: + "getPartFromCapabilityQuestionAnswer", GET_MOLDING_QUESTIONS: "getMoldingQuestions", SAVE_SESSION: "saveSession", LOAD_SESSION: "loadSession", @@ -45,22 +45,23 @@ const storeActions = { RESET_STATE: "resetState", // SAVE COMPONENT STATE - SAVE_VEHICLE_YEAR: "saveVehicleYear", - SAVE_VEHICLE_MAKE:"saveVehicleMake", - SAVE_VEHICLE_MODEL:"saveVehicleModel", + SAVE_VEHICLE_YEAR: "saveVehicleYear", + SAVE_VEHICLE_MAKE: "saveVehicleMake", + SAVE_VEHICLE_MODEL: "saveVehicleModel", SAVE_VEHICLE_STYLE: "saveVehicleStyle", - SAVE_VEHICLE_DAMAGE: "saveVehicleDamage", - SAVE_VIN_LOOKUP: "saveVinLookup", - SAVE_SERVICE_LOCATION: "saveServiceLocation", - SAVE_EMAIL: "saveEmail", - SAVE_REGISTRATION_LICENSE_PLATE_LOOKUP: "saveRegistrationLicensePlateLookup", - SAVE_VIN: "saveVin", + SAVE_VEHICLE_DAMAGE: "saveVehicleDamage", + SAVE_VIN_LOOKUP: "saveVinLookup", + SAVE_SERVICE_LOCATION: "saveServiceLocation", + SAVE_EMAIL: "saveEmail", + SAVE_REGISTRATION_LICENSE_PLATE_LOOKUP: "saveRegistrationLicensePlateLookup", + SAVE_VIN: "saveVin", SAVE_REGISTRATION_ADDRESS_LOOKUP: "saveRegistrationAddressLookup", SAVE_GLASS_PARTS: "saveGlassParts", SAVE_PART_QUESTION_ANSWERS: "savePartQuestionAnswers", - RESET_MOLDING_AND_CAPABILITY_QUESTIONS_IF_NEEDED: "resetMoldingAndCapabilityQuestionAnswersIfNeeded", + RESET_MOLDING_AND_CAPABILITY_QUESTIONS_IF_NEEDED: + "resetMoldingAndCapabilityQuestionAnswersIfNeeded", SAVE_MOLDING_QUESTION_ANSWERS: "saveMoldingQuestionAnswers", - SAVE_CAPABILITY_QUESTION_ANSWERS: "saveCapabilityQuestionAnswers" + SAVE_CAPABILITY_QUESTION_ANSWERS: "saveCapabilityQuestionAnswers", }; export { storeActions }; diff --git a/src/constants/store-mutations.js b/src/constants/store-mutations.js index 0a8990b41..d3de68853 100644 --- a/src/constants/store-mutations.js +++ b/src/constants/store-mutations.js @@ -1,5 +1,4 @@ const storeMutations = { - // VEHICLE MUTATIONS UPDATE_YEAR: "updateYear", UPDATE_MAKE: "updateMake", @@ -22,7 +21,7 @@ const storeMutations = { UPDATE_GLASS_PARTS: "updateGlassParts", UPDATE_OTHER_PARTS: "updateOtherParts", - UPDATE_REGISTRATION_LICENSE_PLATE : "updateRegistrationLicensePlate", + UPDATE_REGISTRATION_LICENSE_PLATE: "updateRegistrationLicensePlate", UPDATE_REGISTRATION_ADDRESS: "updateRegistrationAddress", UPDATE_REGISTRATION_CITY: "updateRegistrationCity", UPDATE_REGISTRATION_STATE: "updateRegistrationState", @@ -69,4 +68,4 @@ const storeMutations = { UPDATE_TRIGGERED_SITE_ENTRY: "updateTriggeredSiteEntry", }; -export { storeMutations }; +export { storeMutations }; \ No newline at end of file diff --git a/src/helpers/button-question-focus-helper.js b/src/helpers/button-question-focus-helper.js new file mode 100644 index 000000000..f2aac0ba5 --- /dev/null +++ b/src/helpers/button-question-focus-helper.js @@ -0,0 +1,41 @@ +/** + * Helper for GA click event. When the user mouse clicks on a `base-input-button`, we + * push the click event. When the user tabs through a list of radio buttons via a + * keyboard, we only want to push the GA click event if the selection was deliberate + * (space/enter key) or if there is a selection and the user tabs off of the radio group. + */ + +let lastFocusedInputGroupName = ""; +let onButtonQuestionLostFocusCallback = null; + +const handleAnyComponentFocus = (e) => { + const targetType = e.target.type; + if (targetType !== "radio" && targetType !== "checkbox") { + invokeButtonQuestionLostFocusCallback(); + } +}; + +const handleButtonComponentFocus = (e) => { + if (e && lastFocusedInputGroupName !== e.groupName) { + invokeButtonQuestionLostFocusCallback(); + } +}; + +const handleInputComponentBlur = (e) => { + if (e) { + lastFocusedInputGroupName = e.groupName; + onButtonQuestionLostFocusCallback = e.onButtonQuestionLostFocusCallback; + } +}; + +const invokeButtonQuestionLostFocusCallback = () => { + if (onButtonQuestionLostFocusCallback) { + onButtonQuestionLostFocusCallback(); + } +}; + +export { + handleAnyComponentFocus, + handleButtonComponentFocus, + handleInputComponentBlur, +}; diff --git a/src/helpers/button-question-focus-helper.spec.js b/src/helpers/button-question-focus-helper.spec.js new file mode 100644 index 000000000..0d6483d20 --- /dev/null +++ b/src/helpers/button-question-focus-helper.spec.js @@ -0,0 +1,251 @@ +import { + handleAnyComponentFocus, + handleButtonComponentFocus, + handleInputComponentBlur, +} from "@/helpers/button-question-focus-helper"; + +describe("buttonQuestionFocusHelper", () => { + let onButtonQuestionLostFocusCallbackOne = jest.fn(); + let onButtonQuestionLostFocusCallbackTwo = jest.fn(); + + let focusOnInputInGroupOne; + let blurFromInputInGroupOne; + let focusOnInputInGroupTwo; + let blurFromInputInGroupTwo; + + let focusOnNonRadioCheckboxElement; + + beforeEach(() => { + onButtonQuestionLostFocusCallbackOne = jest.fn(); + onButtonQuestionLostFocusCallbackTwo = jest.fn(); + + // Sanity check + expect(onButtonQuestionLostFocusCallbackOne).not.toHaveBeenCalled(); + expect(onButtonQuestionLostFocusCallbackTwo).not.toHaveBeenCalled(); + + focusOnInputInGroupOne = () => { + handleButtonComponentFocus({ + groupName: "group1", + }); + }; + + blurFromInputInGroupOne = () => { + handleInputComponentBlur({ + groupName: "group1", + onButtonQuestionLostFocusCallback: onButtonQuestionLostFocusCallbackOne, + }); + }; + + focusOnInputInGroupTwo = () => { + handleButtonComponentFocus({ + groupName: "group2", + }); + }; + + blurFromInputInGroupTwo = () => { + handleInputComponentBlur({ + groupName: "group2", + onButtonQuestionLostFocusCallback: onButtonQuestionLostFocusCallbackTwo, + }); + }; + + focusOnNonRadioCheckboxElement = () => { + handleAnyComponentFocus({ + target: { + type: "nonRadioCheckbox", + }, + }); + }; + }); + + test("focus on input => no callbacks were called", () => { + // focus on input in group 1 + focusOnInputInGroupOne(); + + // Assert + expect(onButtonQuestionLostFocusCallbackOne).not.toHaveBeenCalled(); + expect(onButtonQuestionLostFocusCallbackTwo).not.toHaveBeenCalled(); + }); + + test("focus on input, then focus on input in same group => no callbacks are called", () => { + // focus on input in group 1 + focusOnInputInGroupOne(); + + // Assert + expect(onButtonQuestionLostFocusCallbackOne).not.toHaveBeenCalled(); + expect(onButtonQuestionLostFocusCallbackTwo).not.toHaveBeenCalled(); + + // focus on input in group 1 + blurFromInputInGroupOne(); + focusOnInputInGroupOne(); + + // Assert + expect(onButtonQuestionLostFocusCallbackOne).not.toHaveBeenCalled(); + expect(onButtonQuestionLostFocusCallbackTwo).not.toHaveBeenCalled(); + }); + + test("focus on non-radio/checkbox, focus on input => no callbacks are called", () => { + // focus on non-radio/checkbox + focusOnNonRadioCheckboxElement(); + + // Assert + expect(onButtonQuestionLostFocusCallbackOne).not.toHaveBeenCalled(); + expect(onButtonQuestionLostFocusCallbackTwo).not.toHaveBeenCalled(); + + // focus on input + // focus on input in group 1 + focusOnInputInGroupOne(); + + // Assert + expect(onButtonQuestionLostFocusCallbackOne).not.toHaveBeenCalled(); + expect(onButtonQuestionLostFocusCallbackTwo).not.toHaveBeenCalled(); + }); + + test("focus on input in group 1, then focus on input in different group => callback for group 1 is called", () => { + // focus on input in group 1 + focusOnInputInGroupOne(); + + // Assert + expect(onButtonQuestionLostFocusCallbackOne).not.toHaveBeenCalled(); + expect(onButtonQuestionLostFocusCallbackTwo).not.toHaveBeenCalled(); + + // focus on input in different group + blurFromInputInGroupOne(); + focusOnInputInGroupTwo(); + + // Assert + expect(onButtonQuestionLostFocusCallbackOne).toHaveBeenCalledTimes(1); + expect(onButtonQuestionLostFocusCallbackTwo).not.toHaveBeenCalled(); + }); + + test("focus on input in group 1, focus on input in same group, then focus on input in different group => callback for group 1 is called", () => { + // focus on input in group 1 + focusOnInputInGroupOne(); + + // Assert + expect(onButtonQuestionLostFocusCallbackOne).not.toHaveBeenCalled(); + expect(onButtonQuestionLostFocusCallbackTwo).not.toHaveBeenCalled(); + + // focus on input in same group + blurFromInputInGroupOne(); + focusOnInputInGroupOne(); + + // Assert + expect(onButtonQuestionLostFocusCallbackOne).not.toHaveBeenCalled(); + expect(onButtonQuestionLostFocusCallbackTwo).not.toHaveBeenCalled(); + + // focus on input in different group + blurFromInputInGroupOne(); + focusOnInputInGroupTwo(); + + // Assert + expect(onButtonQuestionLostFocusCallbackOne).toHaveBeenCalledTimes(1); + expect(onButtonQuestionLostFocusCallbackTwo).not.toHaveBeenCalled(); + }); + + test("focus on input in group 1, focus on non-radio/checkbox element => callback from group 1 is called", () => { + // focus on input in group 1 + focusOnInputInGroupOne(); + + // Assert + expect(onButtonQuestionLostFocusCallbackOne).not.toHaveBeenCalled(); + expect(onButtonQuestionLostFocusCallbackTwo).not.toHaveBeenCalled(); + + // focus on input in group 2 + blurFromInputInGroupOne(); + focusOnNonRadioCheckboxElement(); + + // Assert + expect(onButtonQuestionLostFocusCallbackOne).toHaveBeenCalledTimes(1); + expect(onButtonQuestionLostFocusCallbackTwo).not.toHaveBeenCalled(); + }); + + test("focus on input in group 1, focus on input in group 2, focus on non-radio/checkbox element => both callbacks are called", () => { + // focus on input in group 1 + focusOnInputInGroupOne(); + + // Assert + expect(onButtonQuestionLostFocusCallbackOne).not.toHaveBeenCalled(); + expect(onButtonQuestionLostFocusCallbackTwo).not.toHaveBeenCalled(); + + // focus on input in group 2 + blurFromInputInGroupOne(); + focusOnInputInGroupTwo(); + + // Assert + expect(onButtonQuestionLostFocusCallbackOne).toHaveBeenCalledTimes(1); + expect(onButtonQuestionLostFocusCallbackTwo).toHaveBeenCalledTimes(0); + + // focus on non-radio/checkbox + blurFromInputInGroupTwo(); + focusOnNonRadioCheckboxElement(); + + // Assert + expect(onButtonQuestionLostFocusCallbackOne).toHaveBeenCalledTimes(1); + expect(onButtonQuestionLostFocusCallbackTwo).toHaveBeenCalledTimes(1); + }); + + test("focus on input in group 1, focus on input in group 2, focus on input in group 1 => both callbacks are called", () => { + // focus on input in group 1 + focusOnInputInGroupOne(); + + // Assert + expect(onButtonQuestionLostFocusCallbackOne).not.toHaveBeenCalled(); + expect(onButtonQuestionLostFocusCallbackTwo).not.toHaveBeenCalled(); + + // focus on input in group 2 + blurFromInputInGroupOne(); + focusOnInputInGroupTwo(); + + // Assert + expect(onButtonQuestionLostFocusCallbackOne).toHaveBeenCalledTimes(1); + expect(onButtonQuestionLostFocusCallbackTwo).toHaveBeenCalledTimes(0); + + // focus on input in group 1 + blurFromInputInGroupTwo(); + focusOnInputInGroupOne(); + + // Assert + expect(onButtonQuestionLostFocusCallbackOne).toHaveBeenCalledTimes(1); + expect(onButtonQuestionLostFocusCallbackTwo).toHaveBeenCalledTimes(1); + }); + + test("go back and forth a lot => correct callbacks are called at the right time", () => { + // Arrange/Act + // focus on input in group 1 + focusOnInputInGroupOne(); + + // Assert + expect(onButtonQuestionLostFocusCallbackOne).not.toHaveBeenCalled(); + expect(onButtonQuestionLostFocusCallbackTwo).not.toHaveBeenCalled(); + + // focus on input in group 2 + blurFromInputInGroupOne(); + focusOnInputInGroupTwo(); + + // Assert + expect(onButtonQuestionLostFocusCallbackOne).toHaveBeenCalledTimes(1); + expect(onButtonQuestionLostFocusCallbackTwo).not.toHaveBeenCalled(); + + // focus on input in group 1 + blurFromInputInGroupTwo(); + focusOnInputInGroupOne(); + + // Assert + expect(onButtonQuestionLostFocusCallbackOne).toHaveBeenCalledTimes(1); + expect(onButtonQuestionLostFocusCallbackTwo).toHaveBeenCalledTimes(1); + + // focus on non-radio/checkbox element + blurFromInputInGroupOne(); + focusOnNonRadioCheckboxElement(); + + // Assert + expect(onButtonQuestionLostFocusCallbackOne).toHaveBeenCalledTimes(2); + expect(onButtonQuestionLostFocusCallbackTwo).toHaveBeenCalledTimes(1); + + // focus on input in group 1 + focusOnInputInGroupOne(); + expect(onButtonQuestionLostFocusCallbackOne).toHaveBeenCalledTimes(2); + expect(onButtonQuestionLostFocusCallbackTwo).toHaveBeenCalledTimes(1); + }); +}); diff --git a/src/helpers/heritage-integration/order-helper.js b/src/helpers/heritage-integration/order-helper.js index 0a8bc0100..733402cdd 100644 --- a/src/helpers/heritage-integration/order-helper.js +++ b/src/helpers/heritage-integration/order-helper.js @@ -25,7 +25,6 @@ export async function loadSessionIfPresent() { return null; } - // Load referral if there is a cookie, and it doesn't indicate it needs a state reset. return (await loadSession(funnelCookie.ReferralNumber, funnelCookie.ReferralDate, funnelCookie.ReferralCorrelationId, funnelCookie.ReferralParentAccountNumber)).data; } diff --git a/src/layouts/address-vehicles/address-vehicles-question/address-vehicles-question.spec.js b/src/layouts/address-vehicles/address-vehicles-question/address-vehicles-question.spec.js index 70f056e3b..16451b219 100644 --- a/src/layouts/address-vehicles/address-vehicles-question/address-vehicles-question.spec.js +++ b/src/layouts/address-vehicles/address-vehicles-question/address-vehicles-question.spec.js @@ -34,14 +34,14 @@ describe("addressVehiclesQuestion.vue", () => { const wrapper = shallowMount(addressVehiclesQuestion, { mixins: [mockMixin], propsData: { - vehicles: ["1", "2"], - modelValue: ["1", "2"], + vehicles: ["1", "2", "newValue"], + modelValue: "2", } }); // Act const localThis = { $emit: jest.fn() } - addressVehiclesQuestion.computed.selectedVehicleVinAsArray.set.call(localThis, ['newValue']); + addressVehiclesQuestion.computed.selectedVehicleVin.set.call(localThis, 'newValue'); // Assert expect(localThis.$emit).toBeCalledWith("update:modelValue", "newValue"); diff --git a/src/layouts/address-vehicles/address-vehicles-question/address-vehicles-question.vue b/src/layouts/address-vehicles/address-vehicles-question/address-vehicles-question.vue index 4bdaa8887..ade10ed99 100644 --- a/src/layouts/address-vehicles/address-vehicles-question/address-vehicles-question.vue +++ b/src/layouts/address-vehicles/address-vehicles-question/address-vehicles-question.vue @@ -5,7 +5,7 @@ groupName="ChooseAddressVehicle" :questionText="questionText" :answers="vehicles" - v-model="selectedVehicleVinAsArray" + v-model="selectedVehicleVin" isRequired :validation-rules="validationRules" :valueToLogType="ValueToLogTypes.LAST_5" @@ -63,18 +63,16 @@ export default { questionText() { return this.getCmsContent("VehicleConfirmationQuestion", "QuestionText"); }, - selectedVehicleVinAsArray: { + selectedVehicleVin: { get: function() { - const modelValueAsArray = this.modelValue ? [this.modelValue] : []; - return modelValueAsArray; + return this.modelValue; }, set: function(newValue) { - const newValueAsScalar = newValue && newValue.length > 0 ? newValue[newValue.length-1] : null; - this.$emit("update:modelValue", newValueAsScalar); + this.$emit("update:modelValue", newValue); } }, selectedVehicle() { // this computed is only needed for the computed differentVehicleAlertBody text above - return this.vehicles.find( ({ vin }) => vin === this.selectedVehicleVinAsArray[this.selectedVehicleVinAsArray.length-1] ); + return this.vehicles.find( ({ vin }) => vin === this.selectedVehicleVin[this.selectedVehicleVin.length-1] ); }, }, components: { diff --git a/src/layouts/address-vehicles/address-vehicles.vue b/src/layouts/address-vehicles/address-vehicles.vue index 081b83fe9..a0edf876f 100644 --- a/src/layouts/address-vehicles/address-vehicles.vue +++ b/src/layouts/address-vehicles/address-vehicles.vue @@ -195,7 +195,7 @@ export default { selectedVehicleVin: { handler() { // does this vehicle match the previously selected carId? - this.isCarIdDifferent = this.selectedVehicle.vehicle.carId !== store.getters.vehicle.carId; + this.isCarIdDifferent = this.selectedVehicle?.vehicle.carId !== store.getters.vehicle.carId; if (this.isCarIdDifferent) { this.$refs.funnelFooter.updateButtonText(`Continue with ${this.selectedVehicle.vehicle.year} ${this.selectedVehicle.vehicle.make} ${this.selectedVehicle.vehicle.model}`); } else { diff --git a/src/layouts/estimate/estimate.spec.js b/src/layouts/estimate/estimate.spec.js index 5bfb2e517..379ab76f7 100644 --- a/src/layouts/estimate/estimate.spec.js +++ b/src/layouts/estimate/estimate.spec.js @@ -168,7 +168,6 @@ describe("estimate.vue", () => { store.commit(storeMutations.UPDATE_IS_REPAIR, null); // Act - console.log(store.getters.damage) let arePagePrerequisitesValid = wrapper.vm.arePagePrerequisitesValid(); // Assert diff --git a/src/layouts/estimate/estimate.vue b/src/layouts/estimate/estimate.vue index faf2c8ed6..d468fff91 100644 --- a/src/layouts/estimate/estimate.vue +++ b/src/layouts/estimate/estimate.vue @@ -27,7 +27,7 @@ validationRules="option-required" /> -
+
@@ -131,7 +130,7 @@ export default { name: "estimate", data() { return { - selectedVinLookupMethod: "", + selectedVinLookupMethod: null, serviceZipCode: this.getZipFromStore(), emailAddress: this.getEmailFromStore(), displayInvalidZipAlert: false, diff --git a/src/layouts/vehicle-damage/replace-options-question/replace-options-question.spec.js b/src/layouts/vehicle-damage/replace-options-question/replace-options-question.spec.js index 8bdace584..aa6ecb06d 100644 --- a/src/layouts/vehicle-damage/replace-options-question/replace-options-question.spec.js +++ b/src/layouts/vehicle-damage/replace-options-question/replace-options-question.spec.js @@ -36,7 +36,7 @@ describe("replace-options-question.vue", () => { }); describe("replace-options-question.vue", () => { - test("when updateSelectedValues method is called with a single answerToDisplay it will call to update this.selectedValues", async () => { + test("when updateSelectedValues method is called with a single answerToDisplay it will call to update this.selectedReplaceOptions", async () => { //Arrange const { wrapper, cmsContent, replaceOptions diff --git a/src/layouts/vehicle-damage/replace-options-question/replace-options-question.vue b/src/layouts/vehicle-damage/replace-options-question/replace-options-question.vue index bb164f1bc..503c30cb8 100644 --- a/src/layouts/vehicle-damage/replace-options-question/replace-options-question.vue +++ b/src/layouts/vehicle-damage/replace-options-question/replace-options-question.vue @@ -11,7 +11,7 @@ v-model="selectedValues" :validationRules="validationRules" :suppressError="suppressError" - :isRequired=isRequired + :isRequired="isRequired" />
@@ -25,14 +25,14 @@ export default ({ name: "replaceOptionsQuestion", data(){ return { - replaceOptions: [], + replaceOptions: this.isMultiSelect ? [] : "", } }, props: { isAvailable: Boolean, filterByVehicleCategory: Boolean, groupName: String, - modelValue: Array, + modelValue: [Array, String, Number], isMultiSelect: Boolean, validationRules: String, suppressError: Boolean, @@ -46,7 +46,7 @@ export default ({ updateSelectedValues() { // UPDATE SELECTEDVALUES IF ONLY ONE ANSWER if(Array.isArray(this.answersToDisplay) && this.answersToDisplay.length === 1) { - this.selectedValues = [this.answersToDisplay[0].Name]; + this.selectedValues = this.isMultiSelect ? [this.answersToDisplay[0].Name] : this.answersToDisplay[0].Name; } }, }, @@ -91,7 +91,7 @@ export default ({ }, shouldDisplayReplaceOptionsQuestion(shouldDisplayReplaceOptionsQuestion) { if (!shouldDisplayReplaceOptionsQuestion) { - this.selectedValues = []; + this.selectedValues = this.isMultiSelect ? [] : ""; } } }, diff --git a/src/layouts/vehicle-damage/side-door-options/side-door-options.vue b/src/layouts/vehicle-damage/side-door-options/side-door-options.vue index c1c9ba3b3..99f644d6f 100644 --- a/src/layouts/vehicle-damage/side-door-options/side-door-options.vue +++ b/src/layouts/vehicle-damage/side-door-options/side-door-options.vue @@ -57,7 +57,7 @@ export default ({ name: "sideDoorOptions", props: { groupName: String, - modelValue: Array, + modelValue: Object, selectedDamageLocations: Array, cmsWidgetName: String, }, diff --git a/src/layouts/vehicle-damage/vehicle-damage.spec.js b/src/layouts/vehicle-damage/vehicle-damage.spec.js index 7791dff37..4cf6b87c0 100644 --- a/src/layouts/vehicle-damage/vehicle-damage.spec.js +++ b/src/layouts/vehicle-damage/vehicle-damage.spec.js @@ -129,20 +129,20 @@ describe("vehicle-damage.vue", () => { }, }); - wrapper.vm.selectedDamageLocations = ["Windshield", "SideDoor", "RearWindow"]; - wrapper.vm.selectedWindshieldOptions = { - selectedWindshieldChipCount: null, - selectedWindshieldReplaceOptions: ["Single"], - selectedWindshieldDamageType: "Replace" - }; - - wrapper.vm.sideDoorOptionsData = { - selectedDoorSides: ["DriverSide", "PassengerSide"], - selectedDriverSideReplaceOptions: ["Back"], - selectedPassengerSideReplaceOptions: ["Quarter"] - }; - - wrapper.vm.selectedRearReplaceOptions = ["Stationary"]; + wrapper.setData({ + selectedDamageLocations: ["Windshield", "SideDoor", "RearWindow"], + selectedWindshieldOptions: { + selectedWindshieldChipCount: null, + selectedWindshieldReplaceOptions: ["Single"], + selectedWindshieldDamageType: "Replace" + }, + sideDoorOptionsData: { + selectedDoorSides: ["DriverSide", "PassengerSide"], + selectedDriverSideReplaceOptions: ["Back"], + selectedPassengerSideReplaceOptions: ["Quarter"] + }, + selectedRearReplaceOptions: "Stationary", + }) const expectedGlassToReplace = [{ glassLocation: "Windshield", glassName: "Single" }, { glassLocation: "Driver", glassName: "Back" }, { glassLocation: "Passenger", glassName: "Quarter" }, { glassLocation: "Rear", glassName: "Stationary" }]; @@ -519,19 +519,19 @@ describe("vehicle-damage.vue", () => { const storeWindshieldOptions = [[1, false, "Windshield", "Single", { selectedWindshieldDamageType: damageLocationsSelected.REPLACE, - selectedWindshieldChipCount: [], selectedWindshieldReplaceOptions: [damageLocationsSelected.SINGLE] + selectedWindshieldChipCount: null, selectedWindshieldReplaceOptions: [damageLocationsSelected.SINGLE] }], [2, false, "Windshield", "Driver", { selectedWindshieldDamageType: damageLocationsSelected.REPLACE, - selectedWindshieldChipCount: [], selectedWindshieldReplaceOptions: [damageLocationsSelected.DRIVER] + selectedWindshieldChipCount: null, selectedWindshieldReplaceOptions: [damageLocationsSelected.DRIVER] }], [3, false, "Windshield", "Passenger", { selectedWindshieldDamageType: damageLocationsSelected.REPLACE, - selectedWindshieldChipCount: [], selectedWindshieldReplaceOptions: [damageLocationsSelected.PASSENGER] + selectedWindshieldChipCount: null, selectedWindshieldReplaceOptions: [damageLocationsSelected.PASSENGER] }], [4, true, "", "", { selectedWindshieldDamageType: damageLocationsSelected.REPAIR, - selectedWindshieldChipCount: [2], selectedWindshieldReplaceOptions: [] + selectedWindshieldChipCount: 2, selectedWindshieldReplaceOptions: [] }] ]; test.each(storeWindshieldOptions)("getWindshieldOptionsFromStore test #%s", async (testNum, isRepair, damageLocation, damageName, expectedWindshieldOptions) => { @@ -618,8 +618,8 @@ describe("vehicle-damage.vue", () => { expect(glassSelections).toEqual(expectedGlass); }); - const rearReplaceOptions = [["Rear", "Stationary", [damageLocationsSelected.STATIONARY]], - ["Rear", "Slider", [damageLocationsSelected.SLIDER]] + const rearReplaceOptions = [["Rear", "Stationary", damageLocationsSelected.STATIONARY], + ["Rear", "Slider", damageLocationsSelected.SLIDER] ]; test.each(rearReplaceOptions)("getRearReplaceOptionsFromStore for %s-%s returns expected %s", async (damageLocation, damageName, expectedGlass) => { diff --git a/src/layouts/vehicle-damage/vehicle-damage.vue b/src/layouts/vehicle-damage/vehicle-damage.vue index 2b9f08db3..9c78bc1b5 100644 --- a/src/layouts/vehicle-damage/vehicle-damage.vue +++ b/src/layouts/vehicle-damage/vehicle-damage.vue @@ -196,11 +196,15 @@ export default { }, getWindshieldOptionsFromStore() { - var windShieldOptions = { selectedWindshieldDamageType: "", selectedWindshieldChipCount: [], selectedWindshieldReplaceOptions: []}; + var windShieldOptions = { selectedWindshieldDamageType: "", selectedWindshieldChipCount: null, selectedWindshieldReplaceOptions: []}; if (store.getters.damage.isRepair === undefined) return windshieldOptions; - if (!store.getters.damage.isRepair) { + if (store.getters.damage.isRepair) { + windShieldOptions.selectedWindshieldDamageType = damageLocationsSelected.REPAIR; + windShieldOptions.selectedWindshieldChipCount = store.getters.damage.numberOfChips; + } + else { if (store.getters.damage.glassToReplace?.some(glass => { return glass.glassLocation === damageLocationsSelected.WINDSHIELD && glass.glassName === damageLocationsSelected.SINGLE })) { windShieldOptions.selectedWindshieldDamageType = damageLocationsSelected.REPLACE; @@ -220,13 +224,7 @@ export default { } } - if (store.getters.damage.isRepair) { - windShieldOptions.selectedWindshieldDamageType = damageLocationsSelected.REPAIR; - windShieldOptions.selectedWindshieldChipCount.push(store.getters.damage.numberOfChips); - } - return windShieldOptions; - }, getDoorSidesFromStore() { @@ -265,15 +263,9 @@ export default { return passengerSideReplaceOptions; }, - - getRearReplaceOptionsFromStore(){ - var rearReplaceOptions = []; - - store.getters.damage.glassToReplace?.forEach(glass => { - if (glass.glassLocation === damageLocationsSelected.REAR){ - rearReplaceOptions.push(glass.glassName); - } - }); + + getRearReplaceOptionsFromStore() { + var rearReplaceOptions = store.getters.damage.glassToReplace?.filter(glass => glass.glassLocation === damageLocationsSelected.REAR)[0]?.glassName; return rearReplaceOptions; }, @@ -290,7 +282,6 @@ export default { }, navigateForward(){ - // If vin already exists, navigate directly to vin-lookup if(store.getters.vehicle.vin) { this.$router.navigateWithSaving(this.navigationScenarios.CLICKED_FORWARD_WITH_VIN, this.$route); @@ -321,9 +312,7 @@ export default { } if (this.isRearWindowDamageLocation) { - this.selectedRearReplaceOptions.forEach(rearItem => { - selectedGlassToReplace.push({ glassLocation: damageLocationsSelected.REAR, glassName: rearItem}); - }) + selectedGlassToReplace.push({ glassLocation: damageLocationsSelected.REAR, glassName: this.selectedRearReplaceOptions}); } return selectedGlassToReplace; @@ -373,15 +362,15 @@ export default { hasSplitSingleConflict() { if (!this.selectedDamageLocations?.includes("Windshield") || this.selectedWindshieldOptions.selectedWindshieldDamageType === damageLocationsSelected.REPAIR || !this.selectedWindshieldOptions.selectedWindshieldReplaceOptions) return false; - return this.selectedWindshieldOptions.selectedWindshieldReplaceOptions.some(selectedSingleWindshield => + return this.selectedWindshieldOptions.selectedWindshieldReplaceOptions?.some(selectedSingleWindshield => { return selectedSingleWindshield.toUpperCase() === damageLocationsSelected.SINGLE.toUpperCase(); }) && - (this.selectedWindshieldOptions.selectedWindshieldReplaceOptions.some(selectedDriverWindshield => + (this.selectedWindshieldOptions.selectedWindshieldReplaceOptions?.some(selectedDriverWindshield => { return selectedDriverWindshield.toUpperCase() === damageLocationsSelected.DRIVER.toUpperCase(); }) || - this.selectedWindshieldOptions.selectedWindshieldReplaceOptions.some(selectedPassengerWindshield => + this.selectedWindshieldOptions.selectedWindshieldReplaceOptions?.some(selectedPassengerWindshield => { return selectedPassengerWindshield.toUpperCase() === damageLocationsSelected.PASSENGER.toUpperCase(); }) diff --git a/src/layouts/vehicle-damage/windshield-options/windshield-chip-count-question/windshield-chip-count-question.spec.js b/src/layouts/vehicle-damage/windshield-options/windshield-chip-count-question/windshield-chip-count-question.spec.js index c05d97d2f..902e0808a 100644 --- a/src/layouts/vehicle-damage/windshield-options/windshield-chip-count-question/windshield-chip-count-question.spec.js +++ b/src/layouts/vehicle-damage/windshield-options/windshield-chip-count-question/windshield-chip-count-question.spec.js @@ -1,78 +1,81 @@ import { shallowMount } from "@vue/test-utils"; import windshieldChipCountQuestion from "@/layouts/vehicle-damage/windshield-options/windshield-chip-count-question/windshield-chip-count-question"; import { getMountOptions } from "@/helpers/unit-test-helper.js"; -import { nextTick } from "vue"; import store from "@/store"; -jest.mock("@/store", () => { return {}; }, {virtual: true}); +jest.mock( + "@/store", + () => { + return {}; + }, + { virtual: true } +); describe("windshield-chip-count-question.vue", () => { test("Selected chip count is emitted upon selection.", async () => { - //Arrange - const { wrapper } = setupMocks({modelValueProp: ["One"]}); - - //Act - wrapper.setValue({ modelValue: ["Two"] }); - await wrapper.vm.$nextTick(); - - //Assert - expect(wrapper.vm.selectedChipCountValues).toEqual(["One"]); - expect(wrapper.emitted()["update:modelValue"][0]).toEqual([{ modelValue: ["Two"] }]); + //Arrange + const { wrapper } = setupMocks({ modelValueProp: 1 }); + + //Act + wrapper.vm.selectedValue = "2"; + + //Assert + expect(wrapper.emitted()["update:modelValue"][0]).toEqual([2]); }); - }); - describe("Windshield-chip-count-question.vue", () => { - test("Should display question and answers from api.", async () => { - //Arrange - const { wrapper } = setupMocks({modelValueProp: ["One"]}); + test("selectedValue matches modelValue", () => { + // Arrange/Act + const { wrapper } = setupMocks({ modelValueProp: 2 }); - //Act - wrapper.setProps({isAvailable: true}); - wrapper.vm.updateSelectedValues = jest.fn(); - await wrapper.vm.$nextTick(); + // Assert + expect(wrapper.vm.selectedValue).toBe(2); + }); +}); - //Assert - expect(wrapper.vm.updateSelectedValues).toBeCalled(); - }); - }); - - function setupMocks({ +function setupMocks({ modelValueProp = ["Two"], groupName = "WindshieldChipCountQuestion", cmsQuestionText = "How many chips are we repairing?", - cmsAnswers = [{Name: "One"}, {Name: "Two"}, {Name: "Three"}], + cmsAnswers = [{ Name: "One" }, { Name: "Two" }, { Name: "Three" }], dataFromStoreApi = [], - }) { - +}) { //Mock store store.dispatch = jest.fn(() => dataFromStoreApi); - store.getters = { vehicle: {year: 2019, make: 'honda', model: 'civc', style: '2 Door', category: 'CAR'} }; + store.getters = { + vehicle: { + year: 2019, + make: "honda", + model: "civc", + style: "2 Door", + category: "CAR", + }, + }; const mountOptions = getMountOptions({ - store: { - dispatch: store.dispatch, - getters: store.getters, - }, + store: { + dispatch: store.dispatch, + getters: store.getters, + }, }); - + //Mock props const mockMixin = { - methods: { - getCmsContent: jest.fn() - } - } + methods: { + getCmsContent: jest.fn(), + }, + }; mountOptions.propsData = { - modelValue: modelValueProp + modelValue: modelValueProp, }; mountOptions.mixins = [mockMixin]; - + const wrapper = shallowMount(windshieldChipCountQuestion, mountOptions); - + //Mock CMS content const cmsContent = { - groupName: groupName, - QuestionText: cmsQuestionText, - Answers: cmsAnswers, + groupName: groupName, + QuestionText: cmsQuestionText, + Answers: cmsAnswers, }; const damageOptions = dataFromStoreApi; return { wrapper, cmsContent, damageOptions }; - } \ No newline at end of file +} diff --git a/src/layouts/vehicle-damage/windshield-options/windshield-chip-count-question/windshield-chip-count-question.vue b/src/layouts/vehicle-damage/windshield-options/windshield-chip-count-question/windshield-chip-count-question.vue index 31a681857..fc26c1d8c 100644 --- a/src/layouts/vehicle-damage/windshield-options/windshield-chip-count-question/windshield-chip-count-question.vue +++ b/src/layouts/vehicle-damage/windshield-options/windshield-chip-count-question/windshield-chip-count-question.vue @@ -7,7 +7,7 @@ :groupName="groupName" buttonType="listButtonHorizontal" useTextForValue - v-model="selectedChipCountValues" + v-model="selectedValue" :validationRules="validationRules" isRequired /> @@ -21,20 +21,12 @@ import buttonQuestion from "@/common-components/button-question/button-question" export default ({ name: "windshieldOptions", props: { - modelValue: Array, + modelValue: [String, Number], groupName: String, isAvailable: Boolean, validationRules: String, cmsWidgetName: String, }, - methods: { - updateSelectedValues() { - // UPDATE SELECTEDVALUES IF ONLY ONE ANSWER - if(Array.isArray(this.answersToDisplay) && this.answersToDisplay.length === 1 && this.selectedValues) { - this.selectedValues = [this.answersToDisplay[0].Name]; - } - }, - }, computed: { questionText(){ return this.getCmsContent(this.cmsWidgetName, 'QuestionText'); @@ -42,7 +34,7 @@ export default ({ answersFromCms(){ return this.getCmsContent(this.cmsWidgetName, 'Answers'); }, - selectedChipCountValues: { + selectedValue: { get: function() { return this.modelValue; }, @@ -52,12 +44,6 @@ export default ({ } }, }, - watch: { - isAvailable(val) { - // CHECK TO UPDATE SELECTED VALUES WHEN ISAVAILABLE IS TRUE - val && this.updateSelectedValues(); - } - }, components: { buttonQuestion, } diff --git a/src/layouts/vehicle-damage/windshield-options/windshield-damage-type-question/windshield-damage-type-question.spec.js b/src/layouts/vehicle-damage/windshield-options/windshield-damage-type-question/windshield-damage-type-question.spec.js index da19f5308..4b247d705 100644 --- a/src/layouts/vehicle-damage/windshield-options/windshield-damage-type-question/windshield-damage-type-question.spec.js +++ b/src/layouts/vehicle-damage/windshield-options/windshield-damage-type-question/windshield-damage-type-question.spec.js @@ -6,22 +6,22 @@ import store from "@/store"; jest.mock("@/store", () => { return {}; }, {virtual: true}); describe("windshield-damage-type-question.vue", () => { - test("Selected chip count is emitted upon selection.", async () => { + test("Selected windshield damage is emitted upon selection.", async () => { //Arrange - const { wrapper } = setupMocks({modelValueProp: ["Repair"]}); + const { wrapper } = setupMocks({modelValueProp: "Repair"}); //Act - wrapper.setValue({ modelValue: ["Replace"] }); + wrapper.setValue({ modelValue: "Replace" }); await wrapper.vm.$nextTick(); //Assert - expect(wrapper.vm.selectedValues).toEqual(["Repair"]); - expect(wrapper.emitted()["update:modelValue"][0]).toEqual([{ modelValue: ["Replace"] }]); + expect(wrapper.vm.selectedValues).toEqual("Repair"); + expect(wrapper.emitted()["update:modelValue"][0]).toEqual([{ modelValue: "Replace" }]); }); }); function setupMocks({ - modelValueProp = ["Two"], + modelValueProp = "", groupName = "WindshieldDamageTypeQuestion", cmsQuestionText = "What's your windshield damage?", cmsAnswers = [{Name: "Repair"}, {Name: "Replace"}], diff --git a/src/layouts/vehicle-damage/windshield-options/windshield-options.vue b/src/layouts/vehicle-damage/windshield-options/windshield-options.vue index e3524e3f0..8081bb2b2 100644 --- a/src/layouts/vehicle-damage/windshield-options/windshield-options.vue +++ b/src/layouts/vehicle-damage/windshield-options/windshield-options.vue @@ -57,8 +57,8 @@ defineRule("windshield-replace-options-required", required(errorMessages.WINSHIE defineRule("check-for-repair-and-replace", (selectedWindshieldDamageType, selectedDamageLocations) => { return selectedWindshieldDamageType.toString() != damageLocationsSelected.REPAIR || - !selectedDamageLocations.includes(damageLocationsSelected.WINDSHIELD) || - selectedDamageLocations.length === 1; + (!selectedDamageLocations.includes(damageLocationsSelected.WINDSHIELD) && !selectedDamageLocations[0]?.includes(damageLocationsSelected.WINDSHIELD)) || + (selectedDamageLocations[0].length === 1); }); defineRule("repair-only", (value) => { return value.toString() === damageLocationsSelected.REPAIR; @@ -83,7 +83,7 @@ export default ({ }, props: { - modelValue: String, + modelValue: Object, selectedDamageLocations: Array, hasRepairReplaceConflict: Boolean, hasSplitSingleConflict: Boolean, @@ -114,7 +114,7 @@ export default ({ }, selectedWindshieldDamageTypeValue: { get: function() { - return this.selectedValues.selectedWindshieldDamageType; + return this.selectedDamageLocations.includes(damageLocationsSelected.WINDSHIELD) ? this.selectedValues.selectedWindshieldDamageType : null; }, set: function(newValue) { this.selectedValues = this.getWindshieldOptions(newValue, null, null); diff --git a/src/layouts/vehicle-make/make-question/make-question.vue b/src/layouts/vehicle-make/make-question/make-question.vue index d0046fe30..28246814f 100644 --- a/src/layouts/vehicle-make/make-question/make-question.vue +++ b/src/layouts/vehicle-make/make-question/make-question.vue @@ -23,7 +23,7 @@ export default { name: "make-question", data() { return { - makes: Array, + makes: [], }; }, props: { diff --git a/src/layouts/vehicle-make/vehicle-make.spec.js b/src/layouts/vehicle-make/vehicle-make.spec.js index c5ec3cba9..c533516ce 100644 --- a/src/layouts/vehicle-make/vehicle-make.spec.js +++ b/src/layouts/vehicle-make/vehicle-make.spec.js @@ -5,150 +5,224 @@ import { settleAllPromises } from "@/helpers/layout-helper.js"; import { nextTick } from "vue"; import { fetchCmsContentForPage } from "@/helpers/cms-content-helper"; import baseMixin from "@/mixins/base-mixin.js"; +import store from "@/store"; // Components import vehicleMake from "@/layouts/vehicle-make/vehicle-make.vue"; import makeQuestion from "@/layouts/vehicle-make/make-question/make-question"; jest.mock("@/store", () => ({ - commit: jest.fn(), - dispatch: jest.fn(), - getters: { - vehicle: { - year: 2019, - }, - }, + commit: jest.fn(), + dispatch: jest.fn(), + // getters: jest.fn().mockImplementation(() => ({ + // vehicle: { + // year: 2019, + // }, + // })), })); // Mock fetchCmsContentForPage jest.mock("@/helpers/cms-content-helper", () => ({ - fetchCmsContentForPage: jest.fn(), + fetchCmsContentForPage: jest.fn(), })); // Mock our module for promises. jest.mock("@/helpers/layout-helper.js", () => ({ - settleAllPromises: jest.fn(), + settleAllPromises: jest.fn(), })); - describe("vehicle-make.vue", () => { - test("Make question component is initized with api data", async (done) => { - //Arrange - const makeQuestionInitialData = ["honda", "ford", "dodge"]; - const { wrapper, apiPromise } = setupMocks({ - makeQuestionInitialData: makeQuestionInitialData, - }); + test("Make question component is initized with api data", async (done) => { + //Arrange + const makeQuestionInitialData = ["honda", "ford", "dodge"]; + const { wrapper, apiPromise } = setupMocks({ + makeQuestionInitialData: makeQuestionInitialData, + }); - //Act - vehicleMake.beforeRouteEnter.call( - wrapper.vm, - { query: { fmgPage: "vehicle-make" } }, - undefined, - (c) => c(wrapper.vm) - ); + //Act + vehicleMake.beforeRouteEnter.call( + wrapper.vm, + { query: { fmgPage: "vehicle-make" } }, + undefined, + (c) => c(wrapper.vm) + ); - //Assert - apiPromise.finally(() => { - expect(makeQuestion.methods.initializeComponent).toHaveBeenCalledWith( - makeQuestionInitialData - ); - done(); + //Assert + apiPromise.finally(() => { + expect(makeQuestion.methods.initializeComponent).toHaveBeenCalledWith( + makeQuestionInitialData + ); + done(); + }); }); - }); }); describe("vehicle-make.vue", () => { - test("BackButtonAction triggers a router.navigateWithoutSaving change", async (done) => { - //Arrange - const { wrapper, apiPromise } = setupMocks({ - pageHeaderWidgetHeaderText: "Select a make to get started", - mountOptionsMockData: { - router: { - navigate: jest.fn(), - navigateWithoutSaving: jest.fn(), - }, - }, - }); + test("BackButtonAction triggers a router.navigateWithoutSaving change", async (done) => { + //Arrange + const { wrapper, apiPromise } = setupMocks({ + pageHeaderWidgetHeaderText: "Select a make to get started", + mountOptionsMockData: { + router: { + navigate: jest.fn(), + navigateWithoutSaving: jest.fn(), + }, + }, + }); - //Act - vehicleMake.beforeRouteEnter.call( - wrapper.vm, - { query: { fmgPage: "vehicle-make" } }, - undefined, - (c) => c(wrapper.vm) - ); - wrapper.vm.backButtonAction(); - await nextTick(); + //Act + vehicleMake.beforeRouteEnter.call( + wrapper.vm, + { query: { fmgPage: "vehicle-make" } }, + undefined, + (c) => c(wrapper.vm) + ); + wrapper.vm.backButtonAction(); + await nextTick(); - //Assert - apiPromise.finally(() => { - expect(wrapper.vm.$router.navigateWithoutSaving).toHaveBeenCalled(); - done(); + //Assert + apiPromise.finally(() => { + expect(wrapper.vm.$router.navigateWithoutSaving).toHaveBeenCalled(); + done(); + }); }); - }); }); describe("vehicle-make.vue", () => { - test("Year set, arePagePrerequisitesValid should be true ", async () => { - //Arrange - const { wrapper } = setupMocks({}); + describe("arePagePrerequisitesValue", () => { + test("Year set, arePagePrerequisitesValid should be true", async () => { + //Arrange + const { wrapper } = setupMocks({ + vehicleData: { + year: 2019 + } + }); - //Act - vehicleMake.beforeRouteEnter.call( - wrapper.vm, - { query: { fmgPage: "vehicle-make" } }, - undefined, - (c) => c(wrapper.vm) - ); + //Act + vehicleMake.beforeRouteEnter.call( + wrapper.vm, + { query: { fmgPage: "vehicle-make" } }, + undefined, + (c) => c(wrapper.vm) + ); - let arePagePrerequisitesValid = wrapper.vm.arePagePrerequisitesValid(); + let arePagePrerequisitesValid = wrapper.vm.arePagePrerequisitesValid(); - //Assert - expect(arePagePrerequisitesValid).toBe(true); - }); + //Assert + expect(arePagePrerequisitesValid).toBe(true); + }); + + test("Year not set, arePagePrerequisitesValid should be false", async () => { + //Arrange + store.getters.vehicle.year = jest.fn().mockReturnValueOnce(undefined); + const { wrapper } = setupMocks({}); + + //Act + let arePagePrerequisitesValid = wrapper.vm.arePagePrerequisitesValid(); + + //Assert + expect(arePagePrerequisitesValid).toBe(false); + }); + }); + + test("selectedMake changes => save make in store", async () => { + //Arrange + const { wrapper } = setupMocks({ + mountOptionsMockData: { + router: { + navigate: jest.fn(), + navigateWithSaving: jest.fn(), + }, + } + }); + + // Act + await wrapper.setData({ + selectedMake: "Make", + }); + await wrapper.vm.$nextTick(); + + // Assert + expect(wrapper.vm.dispatchStoreAction).toHaveBeenCalledTimes(1); + expect(wrapper.vm.dispatchStoreAction).toHaveBeenCalledWith( + "saveVehicleMake", + "Make", + false + ); + }); + + test("selectedMake changes => navigate with saving", async () => { + //Arrange + const { wrapper } = setupMocks({ + mountOptionsMockData: { + router: { + navigate: jest.fn(), + navigateWithSaving: jest.fn(), + }, + route: { + fmgPage: "test", + }, + }, + }); + + // Act + await wrapper.setData({ + selectedMake: "Make", + }); + + // Assert + expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledTimes(1); + expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledWith( + "SELECTED_MAKE", + expect.anything() + ); + }); }); - function setupMocks({ - vehicleMakeQuestionCmsContent = {}, - makeQuestionInitialData = {}, - pageHeaderWidgetHeaderText = {}, - mountOptionsMockData = {}, + vehicleMakeQuestionCmsContent = {}, + makeQuestionInitialData = {}, + pageHeaderWidgetHeaderText = {}, + mountOptionsMockData = {}, + vehicleData = {} }) { - //Mock api responses - const apiResponses = { - cmsContent: { - FunnelSubHeaderWidget: pageHeaderWidgetHeaderText, - VehicleMakeQuestion: vehicleMakeQuestionCmsContent, - VehicleBannerWidget: { - GenericVehicleImage: - "https://digitalconsumercms-dev.safelite.com/images/default-source/default-album/blurred-image.jpg?sfvrsn=a6ce3034_3", - }, - FunnelHeaderWidget: { - LogoImage: - "https://digitalconsumercms-dev.safelite.com/images/default-source/default-album/safelite-logo.svg?sfvrsn=45e7ed06_3", - }, - }, - makeQuestionInitialData: makeQuestionInitialData, - }; + //Mock api responses + const apiResponses = { + cmsContent: { + FunnelSubHeaderWidget: pageHeaderWidgetHeaderText, + VehicleMakeQuestion: vehicleMakeQuestionCmsContent, + VehicleBannerWidget: { + GenericVehicleImage: + "https://digitalconsumercms-dev.safelite.com/images/default-source/default-album/blurred-image.jpg?sfvrsn=a6ce3034_3", + }, + FunnelHeaderWidget: { + LogoImage: + "https://digitalconsumercms-dev.safelite.com/images/default-source/default-album/safelite-logo.svg?sfvrsn=45e7ed06_3", + }, + }, + makeQuestionInitialData: makeQuestionInitialData, + }; - const apiPromise = Promise.resolve(apiResponses); + const apiPromise = Promise.resolve(apiResponses); - fetchCmsContentForPage.mockImplementation(() => Promise.resolve()); - settleAllPromises.mockImplementation(() => apiPromise); + store.getters = { + vehicle: vehicleData + } - //Mock make question methods - makeQuestion.methods = { - loadInitialData: jest.fn(), - initializeComponent: jest.fn(), - }; + fetchCmsContentForPage.mockImplementation(() => Promise.resolve()); + settleAllPromises.mockImplementation(() => apiPromise); - const mountOptions = getMountOptions(mountOptionsMockData); - const wrapper = shallowMount(vehicleMake, mountOptions); - const makeQuestionWrapper = wrapper.findComponent({ name: "makeQuestion" }); - makeQuestionWrapper.vm.initializeComponent = - makeQuestion.methods.initializeComponent; + //Mock make question methods + makeQuestion.methods = { + loadInitialData: jest.fn(), + initializeComponent: jest.fn(), + }; + + const mountOptions = getMountOptions(mountOptionsMockData); + const wrapper = shallowMount(vehicleMake, mountOptions); + const makeQuestionWrapper = wrapper.findComponent({ name: "makeQuestion" }); + makeQuestionWrapper.vm.initializeComponent = makeQuestion.methods.initializeComponent; wrapper.vm.setCmsContent = baseMixin.methods.setCmsContent; - return { wrapper, apiPromise }; + return { wrapper, apiPromise }; } diff --git a/src/layouts/vehicle-make/vehicle-make.vue b/src/layouts/vehicle-make/vehicle-make.vue index f5a42eefe..2bd49498f 100644 --- a/src/layouts/vehicle-make/vehicle-make.vue +++ b/src/layouts/vehicle-make/vehicle-make.vue @@ -3,14 +3,21 @@
- +
- +
@@ -26,7 +33,6 @@ import funnelSubHeader from "@/common-components/funnel-sub-header/funnel-sub-he // Supporting files import { fetchCmsContentForPage } from "@/helpers/cms-content-helper"; import { settleAllPromises } from "@/helpers/layout-helper"; -import { storeMutations } from "@/constants/store-mutations"; import { storeActions } from "@/constants/store-actions"; import store from "@/store"; @@ -75,7 +81,7 @@ export default { ); }, arePagePrerequisitesValid() { - if (store.getters.vehicle.year){ + if (store.getters.vehicle.year) { return true; } return false; @@ -84,7 +90,7 @@ export default { watch: { selectedMake(make) { - this.dispatchStoreAction(storeActions.SAVE_VEHICLE_MAKE, make, false); + this.dispatchStoreAction(storeActions.SAVE_VEHICLE_MAKE, make, false); this.$router.navigateWithSaving( this.navigationScenarios.SELECTED_MAKE, this.$route diff --git a/src/layouts/vehicle-model/model-question/model-question.vue b/src/layouts/vehicle-model/model-question/model-question.vue index 096f4d70c..bc20bdc6a 100644 --- a/src/layouts/vehicle-model/model-question/model-question.vue +++ b/src/layouts/vehicle-model/model-question/model-question.vue @@ -23,7 +23,7 @@ export default { name: "model-question", data() { return { - models: Array, + models: [], }; }, props: { diff --git a/src/layouts/vehicle-parts/glass-part-question/glass-part-question.spec.js b/src/layouts/vehicle-parts/glass-part-question/glass-part-question.spec.js index 52471bf34..6f8c063fb 100644 --- a/src/layouts/vehicle-parts/glass-part-question/glass-part-question.spec.js +++ b/src/layouts/vehicle-parts/glass-part-question/glass-part-question.spec.js @@ -19,7 +19,6 @@ const featureListData = { } describe("glass-part-question.vue", () => { - test("Part data passed in, should map data for ButtonQuestion (radio type)", async () => { //Arrange diff --git a/src/layouts/vehicle-parts/glass-part-question/glass-part-question.vue b/src/layouts/vehicle-parts/glass-part-question/glass-part-question.vue index 10233d4d8..8ce5cbd5f 100644 --- a/src/layouts/vehicle-parts/glass-part-question/glass-part-question.vue +++ b/src/layouts/vehicle-parts/glass-part-question/glass-part-question.vue @@ -13,9 +13,7 @@ altText="" isRequired :groupName="`${glassLocation}-${glassName}`" - @isCheckedChanged="ResetTintAndPartSelections" - :validationRules="tintValidationRules" - > + :validationRules="tintValidationRules">
+ />
@@ -64,7 +62,10 @@ export default { glassLocation: String, colorAnswers: Array, modelValue: Object, - alreadyPopulatedPartsData: Array + alreadyPopulatedPartsData: { + type: Array, + default: () => [], + }, }, mounted() { this.LoadPreselectedValues(); @@ -75,12 +76,21 @@ export default { computed: { tintValidationRules() { const validationRuleName = `${this.glassLocation}-${this.glassName}-tint-required`; - defineRule(validationRuleName, required(errorMessages.OPTION_REQUIRED)); + + defineRule( + validationRuleName, + required(errorMessages.OPTION_REQUIRED) + ); + return validationRuleName; }, partValidationRules() { const validationRuleName = `${this.glassLocation}-${this.glassName}-part-required`; - defineRule(validationRuleName, required(errorMessages.OPTION_REQUIRED)); + + defineRule( + validationRuleName, + required(errorMessages.OPTION_REQUIRED) + ); return validationRuleName; }, colorQuestionText() { @@ -95,9 +105,9 @@ export default { Object.keys(this.featureListData).forEach((tintOption) => { tintOptions.push({ - Name: tintOption, - Text: tintOption, - AnswerImageUrl: require(`@/assets/img/tints/${this.getTintSourceImage( + value: tintOption, + buttonLabel: tintOption, + buttonImage: require(`@/assets/img/tints/${this.getTintSourceImage( this.glassLocation, tintOption )}`), @@ -112,16 +122,29 @@ export default { return this.modelValue?.partNumber; }, set(newValue) { - this.$emit("update:modelValue", this.partsForSelectedTint.filter(part => part.partNumber == newValue)[0]); + this.$emit( + "update:modelValue", + this.partsForSelectedTint.filter( + (part) => part.partNumber == newValue + )[0] + ); }, }, partsForSelectedTint() { - const matchingGlass = this.PartDataFromApi.partsOrQuestions?.filter(dataForGlassLocationAndName => - dataForGlassLocationAndName.glassName == this.glassName && - dataForGlassLocationAndName.glassLocation == this.glassLocation); - const matchingGlassParts = matchingGlass?.length == 1 ? matchingGlass[0].parts : []; - return matchingGlassParts.filter(part => part.color == this.selectedTint) ?? []; + const matchingGlass = this.PartDataFromApi.partsOrQuestions?.filter( + (dataForGlassLocationAndName) => + dataForGlassLocationAndName.glassName == this.glassName && + dataForGlassLocationAndName.glassLocation == + this.glassLocation + ); + const matchingGlassParts = + matchingGlass?.length == 1 ? matchingGlass[0].parts : []; + return ( + matchingGlassParts.filter( + (part) => part.color == this.selectedTint + ) ?? [] + ); }, // Creates a map of the feature list data in the correct Name/Value @@ -153,7 +176,9 @@ export default { }, PartDataFromApi() { - return this.$store.getters.pageData(this.$route.query.fmgPage) ?? {}; + return ( + this.$store.getters.pageData(this.$route.query.fmgPage) ?? {} + ); }, }, methods: { @@ -190,7 +215,8 @@ export default { // Check if only a single part is present for the tint and set the v-model if it is. AutoSelectIfSinglePart() { if (this.partsForSelectedTint?.length == 1) { - this.selectedPartNumber = this.partsForSelectedTint[0].partNumber; + this.selectedPartNumber = + this.partsForSelectedTint[0].partNumber; } }, @@ -199,7 +225,7 @@ export default { this.$nextTick(() => { if (this.modelValue !== undefined) { // Populate button-question model-value if parts data already exists in VueX - this.selectedTint = this.alreadyPopulatedPartsData?.filter(part => part.partNumber === this.selectedPartNumber)[0]?.color; + this.selectedTint = this.modelValue?.color } }); }, @@ -207,8 +233,8 @@ export default { watch: { selectedTint() { this.AutoSelectIfSinglePart(); - } - } + }, + }, }; diff --git a/src/layouts/vehicle-parts/vehicle-parts.spec.js b/src/layouts/vehicle-parts/vehicle-parts.spec.js index 2fce952c2..df45f7867 100644 --- a/src/layouts/vehicle-parts/vehicle-parts.spec.js +++ b/src/layouts/vehicle-parts/vehicle-parts.spec.js @@ -183,7 +183,16 @@ describe("vehicle-parts.vue", () => { await nextTick(); //Assert - expect(wrapper.vm.selectedGlassParts).toEqual({ "Rear-Stationary": { "Rear": ['DB12209YPYNOEM'] } }); + expect(wrapper.vm.selectedGlassParts).toEqual({ + "Rear-Stationary": { + partNumber: "DB12209YPYNOEM", + description: "heated glass, solar, 1 hole", + color: "Gray Tint Privacy", + requiresRecalibration: false, + requiresCapabilityQuestions: false, + childParts: null + } + }); }); test("User had part questions > BackButtonAction triggers a router.navigateWithoutSaving change with correct scenario", async () => { @@ -509,6 +518,8 @@ function setupMocks({ pageHeaderWidgetHeaderText = {}, mountOptionsMockData = {} wrapper.vm.setCmsContent = baseMixin.methods.setCmsContent; wrapper.vm.$refs.funnelFooter.removeLoader = jest.fn(); wrapper.vm.$refs.loadingModal.showModal = jest.fn(); + // wrapper.vm.$refs.onSubmit = jest.fn(); + // wrapper.vm.$refs.onInvalidSubmit = jest.fn(); return { wrapper, apiPromise }; } diff --git a/src/layouts/vehicle-parts/vehicle-parts.vue b/src/layouts/vehicle-parts/vehicle-parts.vue index 6116e5391..7351f58ab 100644 --- a/src/layouts/vehicle-parts/vehicle-parts.vue +++ b/src/layouts/vehicle-parts/vehicle-parts.vue @@ -1,51 +1,44 @@ @@ -57,17 +50,14 @@ import vehicleBanner from "@/common-components/vehicle-banner/vehicle-banner"; import funnelSubHeader from "@/common-components/funnel-sub-header/funnel-sub-header"; import funnelFooter from "@/common-components/funnel-footer/funnel-footer"; import alert from "@/ux-components/alert/alert"; -import loadingModal from '@/common-components/loading-modal/loading-modal.vue'; +import loadingModal from "@/common-components/loading-modal/loading-modal.vue"; // Supporting Files import { fetchCmsContentForPage } from "@/helpers/cms-content-helper"; import { settleAllPromises } from "@/helpers/layout-helper"; import { fmgPageValues } from "@/router/router-constants/fmgPage-values"; import { Form } from "vee-validate"; import store from "@/store"; -import { storeMutations } from "@/constants/store-mutations.js"; import vehicleQuestionsMixin from "@/mixins/vehicle-questions-mixin"; -import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper"; -import { assertParenthesizedExpression } from "@babel/types"; export default { name: "vehicle-parts", @@ -77,25 +67,25 @@ export default { const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage); // Settle promises and get results const promiseResultMap = [ - { - resultKey: "cmsContent", - promise: cmsContentPromise, - }, + { + resultKey: "cmsContent", + promise: cmsContentPromise, + }, ]; const resultMap = await settleAllPromises(promiseResultMap); // Call the "next" function to complete the transition to this page. next((vm) => { - vm.setCmsContent(resultMap.cmsContent); + vm.setCmsContent(resultMap.cmsContent); - // Glass Part Question dynamic component - Object.keys(vm.$refs) - .filter((r) => r.includes(vm.RefPrefix) && vm.$refs[r][0] !== undefined) - .forEach((c) => - vm.$refs[c][0].initializeComponent({ - ColorQuestionWidget: resultMap.cmsContent.ColorQuestionWidget, - FeatureQuestionWidget: resultMap.cmsContent.FeatureQuestionWidget, - }) - ); + // Glass Part Question dynamic component + Object.keys(vm.$refs) + .filter((r) => r.includes(vm.RefPrefix) && vm.$refs[r][0] !== undefined) + .forEach((c) => + vm.$refs[c][0].initializeComponent({ + ColorQuestionWidget: resultMap.cmsContent.ColorQuestionWidget, + FeatureQuestionWidget: resultMap.cmsContent.FeatureQuestionWidget, + }) + ); }); }, data() { @@ -111,11 +101,13 @@ export default { this.selectedGlassPartNumbers.length !== this.PartsFromApi.partsOrQuestions.length ); }, - selectedGlassPartNumbers () { + selectedGlassPartNumbers() { // Compile all selected parts from the page. const numberArray = []; for (let glassPart of Object.values(this.selectedGlassParts)) { - if (glassPart?.partNumber) { numberArray.push(glassPart.partNumber) } + if (glassPart?.partNumber) { + numberArray.push(glassPart.partNumber); + } } return numberArray; }, @@ -132,7 +124,8 @@ export default { ColorAnswerText: p.color, FeatureAnswers: [ { - FeatureAnswerText: p.description === "" ? p.color : p.description, + FeatureAnswerText: + p.description === "" ? p.color : p.description, PartNumber: p.partNumber, }, ], @@ -156,16 +149,17 @@ export default { methods: { arePagePrerequisitesValid() { // Check if isRepair is populated and if the pageData we need is here (Parts data) - return store.getters.damage.isRepair != null && store.getters.pageData(fmgPageValues.VEHICLE_PARTS) && - Object.keys(store.getters.pageData(fmgPageValues.VEHICLE_PARTS)).length !== 0; + return ( + store.getters.damage.isRepair != null && + store.getters.pageData(fmgPageValues.VEHICLE_PARTS) && + Object.keys(store.getters.pageData(fmgPageValues.VEHICLE_PARTS)).length !== 0 + ); }, async forwardButtonAction() { const matchedParts = []; // Match them to the parts from the API. - for (let [key, value] of Object.entries( - this.PartsFromApi.partsOrQuestions - )) { + for (let [key, value] of Object.entries(this.PartsFromApi.partsOrQuestions)) { for (let [partKey, partValue] of Object.entries(value.parts)) { const currentPart = this.PartsFromApi.partsOrQuestions[key].parts[partKey]; @@ -188,7 +182,11 @@ export default { throw new Error("Could not match any parts to the selected parts"); } - await this.dispatchStoreAction(this.storeActions.RESET_MOLDING_AND_CAPABILITY_QUESTIONS_IF_NEEDED, matchedParts, false); + await this.dispatchStoreAction( + this.storeActions.RESET_MOLDING_AND_CAPABILITY_QUESTIONS_IF_NEEDED, + matchedParts, + false + ); // Navigate to the next page this.navigateForward(matchedParts); @@ -196,20 +194,18 @@ export default { LoadInitialPartsData() { const partsData = this.PartsFromApi; - const alreadyPopulatedPartsData = + this.alreadyPopulatedPartsData = this.$store.getters.lineItems.glassParts === null - ? [] - : this.$store.getters.lineItems.glassParts; + ? [] + : this.$store.getters.lineItems.glassParts; partsData.partsOrQuestions.map((g) => { // If the part is already populated, use the value from the store and populate the v-model. - Object.keys(alreadyPopulatedPartsData).forEach((key) => { - const partNumber = alreadyPopulatedPartsData[key].partNumber; + Object.keys(this.alreadyPopulatedPartsData).forEach((key) => { + const partNumber = this.alreadyPopulatedPartsData[key].partNumber; g.parts.forEach((p) => { if (p.partNumber === partNumber) { - this.selectedGlassParts[g.glassLocation + "-" + g.glassName] = { - [g.glassLocation]: [partNumber], - }; + this.selectedGlassParts[g.glassLocation + "-" + g.glassName] = p; } }); }); diff --git a/src/layouts/vehicle-style/style-question/style-question.vue b/src/layouts/vehicle-style/style-question/style-question.vue index 4e6e90798..806f7ac41 100644 --- a/src/layouts/vehicle-style/style-question/style-question.vue +++ b/src/layouts/vehicle-style/style-question/style-question.vue @@ -23,7 +23,7 @@ export default { name: "style-question", data() { return { - styles: Array, + styles: [], }; }, props: { diff --git a/src/layouts/vehicle-year/year-question/year-question.vue b/src/layouts/vehicle-year/year-question/year-question.vue index 8889a0fdc..6851f282c 100644 --- a/src/layouts/vehicle-year/year-question/year-question.vue +++ b/src/layouts/vehicle-year/year-question/year-question.vue @@ -21,7 +21,7 @@ export default { name: "year-question", data() { return { - years: Array, + years: [], }; }, props: { diff --git a/src/mixins/analytics-mixin.js b/src/mixins/analytics-mixin.js index 6eb18e462..e773c6a01 100644 --- a/src/mixins/analytics-mixin.js +++ b/src/mixins/analytics-mixin.js @@ -48,6 +48,7 @@ export default { pushEventToGA(category, action, label, pushToLogApp = false, valueToLogType = null) { const currentPageName = getPageNameByQueryString(); const labelToLog = getValueToLog(label, valueToLogType); + const eventToBePushed = { 'event': GaEvents.GENERIC_EVENT, 'category': category, @@ -140,7 +141,7 @@ export default { noSession() { return getSessionKeyValue() === 0 || getSessionIdValue() === '00000000-0000-0000-0000-000000000000'; - } + }, }, computed: { analyticsPageEvents() { diff --git a/src/mixins/base-mixin.js b/src/mixins/base-mixin.js index f175c61b8..6189c0b00 100644 --- a/src/mixins/base-mixin.js +++ b/src/mixins/base-mixin.js @@ -6,12 +6,11 @@ import { vehicleCategories } from "@/constants/vehicle-categories.js"; import { routerParams } from "@/router/router-constants/router-params"; import { queryStrings } from "@/constants/query-strings"; import { dynamicStrings } from "@/constants/dynamic-strings"; -import { settleAllPromises } from "@/helpers/layout-helper"; export default { data() { return { - cmsContentByWidget: {} + cmsContentByWidget: {}, }; }, methods: { @@ -19,7 +18,9 @@ export default { this.$root.cmsContentByWidget = cmsContent; }, getCmsContent(widgetName, fieldName) { - return this.$root.cmsContentByWidget?.[widgetName]?.[fieldName] ? this.$root.cmsContentByWidget[widgetName][fieldName] : ''; + return this.$root.cmsContentByWidget?.[widgetName]?.[fieldName] + ? this.$root.cmsContentByWidget[widgetName][fieldName] + : ""; }, dispatchStoreAction(type, payload, encodePayload = true) { // Encode the payload if required @@ -32,7 +33,7 @@ export default { savePageDataToStore(page, data) { store.commit(storeMutations.UPDATE_PAGE_DATA, { page: page, data: data }); }, - onSubmit() { }, // DO NOT REMOVE; needed to prevent default form submit behavior + onSubmit() {}, // DO NOT REMOVE; needed to prevent default form submit behavior. Cannot use .prevent modifier for vee-validate Form onInvalidSubmit({ values, errors, results }) { // identify the first error field and put focus on it // get error names array @@ -49,14 +50,17 @@ export default { return footerInfoBox ? footerInfoBox.offsetHeight : 0; }, async getZipCodeData(zipCode) { - const serviceZipValidationResponse = await this.dispatchStoreAction(storeActions.VALIDATE_ZIP, { zip: zipCode }); - - return { - isValid: serviceZipValidationResponse.data.isValid, + const serviceZipValidationResponse = await this.dispatchStoreAction( + storeActions.VALIDATE_ZIP, + { zip: zipCode } + ); + + return { + isValid: serviceZipValidationResponse.data.isValid, isServiceable: serviceZipValidationResponse.data.isServiceable, - state: serviceZipValidationResponse.data.state + state: serviceZipValidationResponse.data.state, }; - } + }, }, computed: { storeActions() { @@ -74,13 +78,13 @@ export default { routerParams() { return routerParams; }, - queryStrings(){ + queryStrings() { return queryStrings; }, - dynamicStrings(){ + dynamicStrings() { return dynamicStrings; }, - cssClassNameForCmsWidget(){ + cssClassNameForCmsWidget() { return "widget-name-" + this.cmsWidgetName; }, }, diff --git a/src/mixins/input-button-wrapper-mixin.js b/src/mixins/input-button-wrapper-mixin.js new file mode 100644 index 000000000..1878de18b --- /dev/null +++ b/src/mixins/input-button-wrapper-mixin.js @@ -0,0 +1,36 @@ +import { inputButtonProps } from "@/common-components/base-input-button/button-functionality-props"; + +export default { + model: { + prop: "modelValue", + event: "change", + }, + props: { + ...inputButtonProps, + buttonLabel: [Number, String], + buttonLabelSubCopy: String, + buttonImage: String, + buttonImageId: String, + altText: { + type: String, + default: "", + }, + textPosition: String, + screenReaderOnlyText: String, + isWide: Boolean, + }, + computed: { + selectedValue: { + get() { + return this.modelValue; + }, + set(e) { + if (this.preHandleAnswerChange) { + this.preHandleAnswerChange(e); + } + + this.$emit("update:modelValue", e); + }, + }, + }, +}; \ No newline at end of file diff --git a/src/mixins/input-button-wrapper-mixin.spec.js b/src/mixins/input-button-wrapper-mixin.spec.js new file mode 100644 index 000000000..c0028ddab --- /dev/null +++ b/src/mixins/input-button-wrapper-mixin.spec.js @@ -0,0 +1,317 @@ +import { mount } from "@vue/test-utils"; +import baseInputButton from "@/common-components/base-input-button/base-input-button"; +import inputButtonWrapperMixin from "@/mixins/input-button-wrapper-mixin"; +import listButtonHorizontal from "@/ux-components/list-button-horizontal/list-button-horizontal"; +import listButton from "@/ux-components/list-button/list-button"; +import listCard from "@/ux-components/list-card/list-card"; +import radio from "@/ux-components/radio/radio"; + +describe("input-button-wrapper-mixin", () => { + describe("mouse clicks", () => { + describe("checkbox", () => { + test("click on both => both are selected", async () => { + // Arrange + const { wrapper } = setupBaseInputButtonWrapper({ + mockData: { + isMultiSelect: true, + }, + }); + + // Act + const buttonWrappers = wrapper.findAllComponents({ + name: "baseInputButtonWrapper", + }); + const inputButtonOne = buttonWrappers.at(0); + const inputButtonTwo = buttonWrappers.at(1); + expect(inputButtonOne.vm.isMultiSelect).toBe(true); + expect(inputButtonTwo.vm.isMultiSelect).toBe(true); + expect(wrapper.vm.value).toEqual([]); + + await inputButtonOne.find("input").trigger("click"); + await inputButtonTwo.find("input").trigger("click"); + + // Assert + expect(wrapper.vm.value).toEqual(["value1", "value2"]); + }); + + test("click input 1, 2, 1 => only input 2 selected", async () => { + // Arrange + const { wrapper } = setupBaseInputButtonWrapper({ + mockData: { + isMultiSelect: true, + }, + }); + + // Act + const buttonWrappers = wrapper.findAllComponents({ + name: "baseInputButtonWrapper", + }); + const inputButtonOne = buttonWrappers.at(0); + const inputButtonTwo = buttonWrappers.at(1); + expect(inputButtonOne.vm.isMultiSelect).toBe(true); + expect(inputButtonTwo.vm.isMultiSelect).toBe(true); + expect(wrapper.vm.value).toEqual([]); + + await inputButtonOne.find("input").trigger("click"); + await inputButtonTwo.find("input").trigger("click"); + await inputButtonOne.find("input").trigger("click"); + + // Assert + expect(wrapper.vm.value).toEqual(["value2"]); + }); + }); + + describe("radio", () => { + test("click on both => last clicked is selected", async () => { + // Arrange + const { wrapper } = setupBaseInputButtonWrapper({ + mockData: { + isMultiSelect: false, + }, + }); + + // Act + const buttonWrappers = wrapper.findAllComponents({ + name: "baseInputButtonWrapper", + }); + const inputButtonOne = buttonWrappers.at(0); + const inputButtonTwo = buttonWrappers.at(1); + expect(inputButtonOne.vm.isMultiSelect).toBe(false); + expect(inputButtonTwo.vm.isMultiSelect).toBe(false); + expect(wrapper.vm.value).toEqual(""); + + await inputButtonOne.find("input").trigger("click"); + await inputButtonTwo.find("input").trigger("click"); + + // Assert + expect(wrapper.vm.value).toEqual("value2"); + }); + + test("click input 1, 2, 1 => input 1 is selected", async () => { + // Arrange + const { wrapper } = setupBaseInputButtonWrapper({ + mockData: { + isMultiSelect: false, + }, + }); + + // Act + const buttonWrappers = wrapper.findAllComponents({ + name: "baseInputButtonWrapper", + }); + const inputButtonOne = buttonWrappers.at(0); + const inputButtonTwo = buttonWrappers.at(1); + expect(inputButtonOne.vm.isMultiSelect).toBe(false); + expect(inputButtonTwo.vm.isMultiSelect).toBe(false); + expect(wrapper.vm.value).toEqual(""); + + await inputButtonOne.find("input").trigger("click"); + await inputButtonTwo.find("input").trigger("click"); + await inputButtonOne.find("input").trigger("click"); + + // Assert + expect(wrapper.vm.value).toEqual("value1"); + }); + }); + }); + + describe("initial values", () => { + describe("checkbox", () => { + const defaultCheckedCases = [ + [["value2"], false, true], + [["value1", "value2"], true, true], + [["value1"], true, false], + [[], false, false], + ]; + test.each(defaultCheckedCases)( + "initial value is %s => correct input buttons are selected", + async (modelValue, isInputButtonOneChecked, isInputButtonTwoChecked) => { + // Arrange + const { wrapper } = setupBaseInputButtonWrapper({ + mockData: { + isMultiSelect: true, + initialValue: modelValue, + }, + }); + + // Act + const buttonWrappers = wrapper.findAllComponents({ + name: "baseInputButtonWrapper", + }); + const inputs = wrapper.findAll("input"); + const inputButtonOne = buttonWrappers.at(0); + const inputButtonTwo = buttonWrappers.at(1); + + // Assert + expect(inputButtonOne.vm.isMultiSelect).toBe(true); + expect(inputButtonTwo.vm.isMultiSelect).toBe(true); + expect(inputs[0].element.checked).toBe(isInputButtonOneChecked); + expect(inputs[1].element.checked).toBe(isInputButtonTwoChecked); + expect(wrapper.vm.value).toEqual(modelValue); + } + ); + }); + + describe("radio", () => { + const defaultCheckedCases = [ + ["", false, false], + ["value1", true, false], + ["value2", false, true], + [[], false, false], + ]; + test.each(defaultCheckedCases)( + "initial value is %s => correct input buttons are selected", + async (modelValue, isInputButtonOneChecked, isInputButtonTwoChecked) => { + // Arrange + const { wrapper } = setupBaseInputButtonWrapper({ + mockData: { + isMultiSelect: false, + initialValue: modelValue, + }, + }); + + // Act + const buttonWrappers = wrapper.findAllComponents({ + name: "baseInputButtonWrapper", + }); + const inputs = wrapper.findAll("input"); + const inputButtonOne = buttonWrappers.at(0); + const inputButtonTwo = buttonWrappers.at(1); + + // Assert + expect(inputButtonOne.vm.isMultiSelect).toBe(false); + expect(inputButtonTwo.vm.isMultiSelect).toBe(false); + expect(inputs[0].element.checked).toBe(isInputButtonOneChecked); + expect(inputs[1].element.checked).toBe(isInputButtonTwoChecked); + expect(wrapper.vm.value).toEqual(modelValue); + } + ); + }); + }); + + // shared checks between components that use input-button-wrapper-mixin + describe("shared checks", () => { + const inputButtonComponents = [listButtonHorizontal, listButton, listCard, radio]; + test.each(inputButtonComponents.map((x) => [x.name, x]))( + "%s - should return input type checkbox if isMultiSelect is true", + async (name, inputButtonComponent) => { + // Act + const { wrapper } = setupMocksForComponentsUsingInputButtonWrapperMixin({ + component: inputButtonComponent, + mockData: { + propsData: { + isMultiSelect: true, + }, + }, + }); + + // Assert + const input = wrapper.find("input"); + expect(input.attributes().type).toEqual("checkbox"); + } + ); + + test.each(inputButtonComponents.map((x) => [x.name, x]))( + "%s - return input type radio if isMultiSelect is false or not specified", + async (name, inputButtonComponent) => { + // Act + const { wrapper } = setupMocksForComponentsUsingInputButtonWrapperMixin({ + component: inputButtonComponent, + mockData: { + propsData: { + isMultiSelect: false, + }, + }, + }); + + // Assert + const input = wrapper.find("input"); + expect(input.attributes().type).toEqual("radio"); + } + ); + + const selectedValues = ["something", ["test"]]; + inputButtonComponents.forEach((inputButtonComponent) => { + test.each(selectedValues)( + `${inputButtonComponent.name} with selectedValue %s - should emit button value on click`, + async (selectedValue) => { + // Act + const { wrapper } = setupMocksForComponentsUsingInputButtonWrapperMixin({ + component: inputButtonComponent, + mockData: { + propsData: { + isRadioHorizontal: true, + buttonLabel: "Windshield", + value: "List Card Checkbox", + groupID: "radio-demo-1", + groupName: "radio 1", + buttonImage: "windshield-damage.svg", + isRequired: true, + isWide: false, + modelValue: ["List Card Checkbox"], + buttonID: "list-card-id", + }, + }, + }); + + // Act + wrapper.vm.selectedValue = selectedValue; + await wrapper.vm.$nextTick(); + + // Assert + expect(wrapper.emitted()["update:modelValue"][0][0]).toEqual(selectedValue); + } + ); + }); + }); +}); + +function setupMocksForComponentsUsingInputButtonWrapperMixin({ mockData, component }) { + const wrapper = mount(component, { + ...mockData, + propsData: { + ...mockData.propsData, + groupName: "my-group", + modelValue: mockData.propsData?.isMultiSelect ? ["5"] : "5", + value: "4", + }, + mixins: [inputButtonWrapperMixin], + }); + + return { wrapper }; +} + +function setupBaseInputButtonWrapper({ mockData = {} }) { + baseInputButton.methods.pushClickEventToGA = jest.fn(); + + const baseInputButtonWrapper = { + name: "baseInputButtonWrapper", + components: { baseInputButton }, + template: '
', + mixins: [inputButtonWrapperMixin], + }; + + let parentComponentTemplate = `
`; + parentComponentTemplate += ``; + parentComponentTemplate += ``; + parentComponentTemplate += `
`; + const wrapper = mount( + { + data() { + return { + value: mockData.initialValue ?? (mockData.isMultiSelect ? [] : ""), + $route: { + query: { + fmgPage: "myPage", + }, + }, + }; + }, + template: parentComponentTemplate, + components: { baseInputButtonWrapper }, + }, + {} + ); + + return { wrapper }; +} diff --git a/src/mixins/vehicle-questions-mixin.js b/src/mixins/vehicle-questions-mixin.js index 9884117d7..b4fc5f893 100644 --- a/src/mixins/vehicle-questions-mixin.js +++ b/src/mixins/vehicle-questions-mixin.js @@ -177,7 +177,6 @@ export default { // if single parts only const collectedGlassParts = this.reducedGlassPartsArray(partsOrQuestions); // save to store lineItems.glassParts - // TODO KO self.$store.commit(storeMutations.UPDATE_GLASS_PARTS, collectedGlassParts); self.$refs.loadingModal.showModal(); diff --git a/src/router/index.js b/src/router/index.js index c9e0a4a52..a11dae75c 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -238,7 +238,7 @@ function navigateToUrl(url, optionalQuery = {}) { for (const queryKey in optionalQuery) { externalUrl.searchParams.append(queryKey, optionalQuery[queryKey]); } - + window.location.assign(externalUrl); } diff --git a/src/store/index.js b/src/store/index.js index 6dcfd0162..c4ee9e282 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -1,4 +1,4 @@ -import { createStore } from "vuex"; +import { createStore, Store } from "vuex"; import { endpoints } from "@/constants/endpoints.js"; import { storeMutations } from "@/constants/store-mutations"; import { getDateForSavedSessionTimeout } from "@/helpers/heritage-integration/session-helper"; @@ -50,22 +50,22 @@ const getDefaultState = () => { glassToReplace: null, partQuestionAnswers: null, moldingQuestionAnswers: null, - capabilityQuestionAnswers: null + capabilityQuestionAnswers: null, }, lineItems: { - glassParts: null + glassParts: null, }, payment: { isInsurance: null, insuranceCoverage: { - isVerified: null - } + isVerified: null, + }, }, referralNumber: null, referralDate: null, referralCorrelationId: null, accountNumber: 0, - eon: null + eon: null, }, applicationUser: { eventBus: [], @@ -76,9 +76,15 @@ const getDefaultState = () => { crmCustomerId: null, lastPageVisited: null, experiments: [], - triggeredSiteEntry: false + triggeredSiteEntry: false, }, - } + // gaClickInformation: { + // currentlySelectedValues: {}, + // firedGaClickEventValues: {}, + // lastFocusedInputGroup: "", + // wasLastFocusedInputMultiselect: undefined, + // }, + }; }; export const state = getDefaultState(); @@ -195,7 +201,6 @@ export const mutations = { state.order.customer.emailAddress = customerEmailAddress; }, updateVehicle(state, vehicleInfo) { - state.order.vehicle.year = vehicleInfo.year; state.order.vehicle.make = vehicleInfo.make; state.order.vehicle.model = vehicleInfo.model; @@ -209,7 +214,8 @@ export const mutations = { state.order.vehicle.imageColor = vehicleInfo.imageVifColor; }, updateRegistration(state, registrationInfo) { - state.order.vehicle.registration.licensePlate = registrationInfo?.licensePlate; + state.order.vehicle.registration.licensePlate = + registrationInfo?.licensePlate; state.order.vehicle.registration.address = registrationInfo?.address; state.order.vehicle.registration.city = registrationInfo?.city; state.order.vehicle.registration.state = registrationInfo?.state; @@ -235,7 +241,7 @@ export const mutations = { state.applicationUser.crmCustomerId = crmCustomerId; }, updateLastPageVisited(state, lastPageVisited) { - state.applicationUser.lastPageVisited = lastPageVisited + state.applicationUser.lastPageVisited = lastPageVisited; }, // EVENT BUS MUTATIONS addEventToBus(state, event) { @@ -244,8 +250,7 @@ export const mutations = { removeEventFromBus(state, eventData) { const matchedEvent = state.applicationUser.eventBus.find( ({ category, subCategory }) => - category === eventData.category && - subCategory === eventData.subCategory + category === eventData.category && subCategory === eventData.subCategory ); const itemIndex = state.applicationUser.eventBus.indexOf(matchedEvent); @@ -331,7 +336,7 @@ export const mutations = { state: orderInformation.vehicle.registration.state, zipCode: orderInformation.vehicle.registration.zipCode, licensePlate: orderInformation.vehicle.registration.licensePlateNumber, - } + }, }); state.order.damage.glassToReplace = orderInformation.damage.glassToReplace; @@ -340,13 +345,18 @@ export const mutations = { state.order.lineItems.glassParts = orderInformation.parts; state.order.accountNumber = orderInformation.accountNumber; - state.order.serviceLocation.address = orderInformation.serviceLocation.streetAddress, - state.order.serviceLocation.city = orderInformation.serviceLocation.city, - state.order.serviceLocation.state = orderInformation.serviceLocation.state, - state.order.serviceLocation.zipCode = orderInformation.serviceLocation.zipCode; + (state.order.serviceLocation.address = + orderInformation.serviceLocation.streetAddress), + (state.order.serviceLocation.city = + orderInformation.serviceLocation.city), + (state.order.serviceLocation.state = + orderInformation.serviceLocation.state), + (state.order.serviceLocation.zipCode = + orderInformation.serviceLocation.zipCode); state.order.payment.isInsurance = orderInformation.IsInsuranceOrder; - state.order.payment.insuranceCoverage.isVerified = orderInformation?.insuranceInfo.coverageVerified; + state.order.payment.insuranceCoverage.isVerified = + orderInformation?.insuranceInfo.coverageVerified; state.order.customer.emailAddress = orderInformation.customer.emailAddress; state.applicationUser.experiments = orderInformation.experiments; @@ -356,8 +366,23 @@ export const mutations = { }, updateTriggeredSiteEntry(state, wasSiteEntryTriggered) { state.applicationUser.triggeredSiteEntry = wasSiteEntryTriggered; - } -} + }, + // START GA click event mutations + // updateCurrentlySelectedValues(state, groupName, value) { + // state.gaClickInformation.currentlySelectedValues[groupName] = value; + // }, + // updateFiredGaClickEventValues(state, groupName, value) { + // state.gaClickInformation.firedGaClickEventValues[groupName] = value; + // }, + // updateLastFocusedInputGroup(state, groupName) { + // state.gaClickInformation.lastFocusedInputGroup = groupName; + // }, + // updateWasLastFocusedInputMultiselect(state, wasLastFocusedInputMultiselect) { + // state.gaClickInformation.wasLastFocusedInputMultiselect = + // wasLastFocusedInputMultiselect; + // }, + // END GA click event mutations +}; // Export Getters export const getters = { @@ -373,7 +398,9 @@ export const getters = { eventBus: (state) => state.applicationUser.eventBus, damage: (state) => state.order.damage, lineItems: (state) => state.order.lineItems, - pageData: (state) => (page) => { return state.applicationUser.pageData[page]; }, + pageData: (state) => (page) => { + return state.applicationUser.pageData[page]; + }, applicationUser: (state) => state.applicationUser, order: (state) => state.order, payment: (state) => state.order.payment, @@ -390,7 +417,8 @@ export const getters = { funnelServiceState: state.order.serviceLocation.state, funnelServiceZipCode: state.order.serviceLocation.zipCode, funnelParentAccountNumber: state.order.accountNumber, - funnelIsCoverageVerified: state.order.payment.insuranceCoverage.isVerified, + funnelIsCoverageVerified: + state.order.payment.insuranceCoverage.isVerified, funnelHasRecalibrationPart: getHasRecalibrationPart(state), funnelSelectedMultiGlass: state.order.damage.glassToReplace?.length > 1, funnelSelectedWindshieldGlass: getNonFalseValuesOfPropertyInArrayOfObjects(state.order.damage.glassToReplace, "glassLocation").includes(damageLocationsSelected.WINDSHIELD), @@ -403,8 +431,12 @@ export const getters = { funnelOrderPartTypes: [...getNonFalseValuesOfPropertyInArrayOfObjects(state.order.lineItems.glassParts, "recalibrationType"), ...getNonFalseValuesOfPropertyInArrayOfObjects(state.order.lineItems.otherParts, "recalibrationType")], } }, - experimentSettings: (state) => state.applicationUser.experiments.map(x => x.settings).reduce((r, c) => Object.assign(r, c), {}) ?? {} -} + experimentSettings: (state) => + state.applicationUser.experiments + .map((x) => x.settings) + .reduce((r, c) => Object.assign(r, c), {}) ?? {}, + // gaClickInformation: (state) => state.gaClickInformation, +}; function getNonFalseValuesOfPropertyInArrayOfObjects(array, propertyName) { return (array ?? []).map(x => x[propertyName]).filter(x => x); @@ -412,7 +444,6 @@ function getNonFalseValuesOfPropertyInArrayOfObjects(array, propertyName) { // Export Actions export const actions = { - // Vehicle API Actions getVehicleYears(context) { return globalMethods.callHttpClient({ @@ -443,11 +474,14 @@ export const actions = { endpoint: endpoints.LookupVinByPlate.url, payload: { licensePlate: licensePlate, - licenseState: licenseState + licenseState: licenseState, }, }); }, - lookupVinByAddress(context, { licenseLastName, licenseStreetAddress, licenseZip, licenseState }) { + lookupVinByAddress( + context, + { licenseLastName, licenseStreetAddress, licenseZip, licenseState } + ) { return globalMethods.callHttpClient({ method: endpoints.LookupVinByAddress.method, endpoint: endpoints.LookupVinByAddress.url, @@ -455,7 +489,7 @@ export const actions = { licenseLastName: licenseLastName, licenseStreetAddress: licenseStreetAddress, licenseZip: licenseZip, - licenseState: licenseState + licenseState: licenseState, }, }); }, @@ -489,10 +523,22 @@ export const actions = { }) .then((response) => { context.commit(storeMutations.UPDATE_CAR_ID, response.data.carId); - context.commit(storeMutations.UPDATE_VEHICLE_CATEGORY, response.data.category); - context.commit(storeMutations.UPDATE_VEHICLE_IMAGE_URL, response.data.imageUrl); - context.commit(storeMutations.UPDATE_VEHICLE_IMAGE_VIF_NUMBER, response.data.imageVifNumber); - context.commit(storeMutations.UPDATE_VEHICLE_IMAGE_COLOR, response.data.imageVifColor); + context.commit( + storeMutations.UPDATE_VEHICLE_CATEGORY, + response.data.category + ); + context.commit( + storeMutations.UPDATE_VEHICLE_IMAGE_URL, + response.data.imageUrl + ); + context.commit( + storeMutations.UPDATE_VEHICLE_IMAGE_VIF_NUMBER, + response.data.imageVifNumber + ); + context.commit( + storeMutations.UPDATE_VEHICLE_IMAGE_COLOR, + response.data.imageVifColor + ); return response; }); }, @@ -506,8 +552,8 @@ export const actions = { validateZip(context, { zip }) { return globalMethods.callHttpClient({ methods: endpoints.ValidateZip.method, - endpoint: `${endpoints.ValidateZip.url}/${zip}` - }) + endpoint: `${endpoints.ValidateZip.url}/${zip}`, + }); }, // Dependency Actions @@ -522,7 +568,7 @@ export const actions = { }, resetRegistrationAndDependencies(context) { context.commit(storeMutations.RESET_REGISTRATION_STATE); - context.commit(storeMutations.RESET_GLASS_PARTS_STATE) + context.commit(storeMutations.RESET_GLASS_PARTS_STATE); }, resetPartsAndDependencies(context) { context.commit(storeMutations.RESET_GLASS_PARTS_STATE); @@ -578,8 +624,8 @@ export const actions = { assignmentId: experiment.assignmentId, sessionKey: sessionKey, pageName: pageName, - } - } + }, + }, }); }, @@ -587,13 +633,28 @@ export const actions = { updateStoreWithSaveSessionResponse(context, { referralNumber, referralDate, referralCorrelationId, eon, accountNumber, savedSessionId, crmCustomerId }) { context.commit(storeMutations.UPDATE_REFERRAL_NUMBER, referralNumber); context.commit(storeMutations.UPDATE_REFERRAL_DATE, referralDate); - context.commit(storeMutations.UPDATE_REFERRAL_CORRELATION_ID, referralCorrelationId); + context.commit( + storeMutations.UPDATE_REFERRAL_CORRELATION_ID, + referralCorrelationId + ); context.commit(storeMutations.UPDATE_EON, eon); context.commit(storeMutations.UPDATE_PARENT_ACCT_NUMBER, accountNumber); context.commit(storeMutations.UPDATE_SAVED_SESSION_ID, savedSessionId); context.commit(storeMutations.UPDATE_CRM_CUSTOMER_ID, crmCustomerId); }, - logPageView(context, { userId, sessionKey, pageName, sessionId, action, event, shouldUseSessionId, experimentsForUser }) { + logPageView( + context, + { + userId, + sessionKey, + pageName, + sessionId, + action, + event, + shouldUseSessionId, + experimentsForUser, + } + ) { var payload = { userId: userId, sessionKey: sessionKey, @@ -603,17 +664,31 @@ export const actions = { action: action, event: event, shouldUseSessionId: shouldUseSessionId, - experimentsForUser: experimentsForUser + experimentsForUser: experimentsForUser, }; return globalMethods.callHttpClient({ method: endpoints.LogPageView.method, endpoint: endpoints.LogPageView.url, payload: payload, - logApiCall: false + logApiCall: false, }); }, - logCustomEvent(context, { userId, sessionKey, pageName, sessionId, category, action, label, value, shouldUseSessionId, experimentsForUser }) { + logCustomEvent( + context, + { + userId, + sessionKey, + pageName, + sessionId, + category, + action, + label, + value, + shouldUseSessionId, + experimentsForUser, + } + ) { var payload = { userId: userId, sessionKey: sessionKey, @@ -625,14 +700,14 @@ export const actions = { label: label, value: value, shouldUseSessionId: shouldUseSessionId, - experimentsForUser: experimentsForUser + experimentsForUser: experimentsForUser, }; return globalMethods.callHttpClient({ method: endpoints.LogCustomEvent.method, endpoint: endpoints.LogCustomEvent.url, payload: payload, - logApiCall: false + logApiCall: false, }); }, initializeSession(context, { userId, sessionId, userAgent, referrer }) { @@ -644,22 +719,28 @@ export const actions = { userAgent: userAgent, operatorId: "WEB", userName: "SafeliteConceptFunnel", - referrer: referrer + referrer: referrer, }; return globalMethods.callHttpClient({ method: endpoints.InitializeSession.method, endpoint: endpoints.InitializeSession.url, payload: payload, - logApiCall: false + logApiCall: false, }); }, // Misc Actions - setReferralInformation(context, { referralNumber, referralDate, referralCorrelationId, eon }) { + setReferralInformation( + context, + { referralNumber, referralDate, referralCorrelationId, eon } + ) { context.commit(storeMutations.UPDATE_REFERRAL_NUMBER, referralNumber); context.commit(storeMutations.UPDATE_REFERRAL_DATE, referralDate); - context.commit(storeMutations.UPDATE_REFERRAL_CORRELATION_ID, referralCorrelationId); + context.commit( + storeMutations.UPDATE_REFERRAL_CORRELATION_ID, + referralCorrelationId + ); context.commit(storeMutations.UPDATE_EON, eon); }, @@ -667,11 +748,14 @@ export const actions = { return globalMethods.callHttpClient({ method: endpoints.GetExperimentsByUser.method, endpoint: `${endpoints.GetExperimentsByUser.url}/${userId}`, - payload: {} + payload: {}, }); }, - async runExperimentsForTrigger(context, { userId, triggerEvent, triggerValue }) { + async runExperimentsForTrigger( + context, + { userId, triggerEvent, triggerValue } + ) { if (triggerEvent == experimentTriggers.SITE_ENTRY) { context.commit(storeMutations.UPDATE_TRIGGERED_SITE_ENTRY, true); } @@ -681,7 +765,7 @@ export const actions = { userId: userId, triggerEvent: triggerEvent, triggerValue: triggerValue, - experimentOrder: context.getters.experimentOrder + experimentOrder: context.getters.experimentOrder, }; const response = await globalMethods.callHttpClient({ @@ -690,7 +774,10 @@ export const actions = { payload: payload, }); - context.commit(storeMutations.UPDATE_EXPERIMENTS, response.data.experiments); + context.commit( + storeMutations.UPDATE_EXPERIMENTS, + response.data.experiments + ); }, getEvoxImage(context, { relativeUrl }) { @@ -722,7 +809,7 @@ export const actions = { carId: carId, glassPieces: glassArrayForPayload, zip: zipCode, - vin: vin + vin: vin, }, }); @@ -756,7 +843,7 @@ export const actions = { glassPieces: glassArrayForPayload, answerResults: resultsArrayForPayload, zip: zipCode, - vin: vin + vin: vin, }, }); @@ -770,24 +857,31 @@ export const actions = { return globalMethods.callHttpClient({ method: endpoints.GetCapabilityQuestions.method, endpoint: `${endpoints.GetCapabilityQuestions.url}/${carId}/${partNumber}`, - }) + }); }, getPartFromCapabilityQuestionAnswer(context, glassLocation) { - const pageData = context.getters.pageData(fmgPageValues.CAPABILITY_QUESTIONS); + const pageData = context.getters.pageData( + fmgPageValues.CAPABILITY_QUESTIONS + ); - const part = pageData.partsOrQuestions.find(x => x.glassLocation === glassLocation).parts[0]; - const capabilityQuestionAnswers = context.getters.damage.capabilityQuestionAnswers; - const capabilityQuestionAnswersForPart = capabilityQuestionAnswers.find(x => x.glassLocation === glassLocation); + const part = pageData.partsOrQuestions.find( + (x) => x.glassLocation === glassLocation + ).parts[0]; + const capabilityQuestionAnswers = + context.getters.damage.capabilityQuestionAnswers; + const capabilityQuestionAnswersForPart = capabilityQuestionAnswers.find( + (x) => x.glassLocation === glassLocation + ); return globalMethods.callHttpClient({ method: endpoints.GetPartFromCapabilityAnswer.method, endpoint: endpoints.GetPartFromCapabilityAnswer.url, payload: { part, - capabilityAnswerResults: capabilityQuestionAnswersForPart - } - }) + capabilityAnswerResults: capabilityQuestionAnswersForPart, + }, + }); }, // Session API Actions @@ -825,21 +919,21 @@ export const actions = { damage: { numberOfChips: damage.numberOfChips, glassToReplace: newGlassToReplace, - isRepair: damage.isRepair + isRepair: damage.isRepair, }, customer: { emailAddress: order.customer.emailAddress, }, lineItems: { - glassParts: lineItems.glassParts + glassParts: lineItems.glassParts, }, serviceLocation: { streetAddress: order.serviceLocation.address, city: order.serviceLocation.city, state: order.serviceLocation.state, - zipCode: order.serviceLocation.zipCode + zipCode: order.serviceLocation.zipCode, }, - referralNumber: order.referralNumber?.toString(), // TODO It'd be nice to save these as strings in the first place + referralNumber: order.referralNumber?.toString(), // TODO It'd be nice to save these as strings in the first place referralDate: order.referralDate, accountNumber: order.accountNumber?.toString(), existingPromoCode: null, @@ -884,8 +978,7 @@ export const actions = { // Vehicle saveVehicleYear(context, year) { - - //Reset dependent state when changing + //Reset dependent state when changing if (context.state.order.vehicle.year !== year) { context.commit(storeMutations.UPDATE_MAKE, null); context.commit(storeMutations.UPDATE_MODEL, null); @@ -906,7 +999,6 @@ export const actions = { } }, saveVehicleMake(context, make) { - //Reset dependent state when changing if (context.state.order.vehicle.make !== make) { context.commit(storeMutations.UPDATE_MODEL, null); @@ -927,8 +1019,7 @@ export const actions = { } }, saveVehicleModel(context, model) { - - //Reset dependent state when changing + //Reset dependent state when changing if (context.state.order.vehicle.model !== model) { context.commit(storeMutations.UPDATE_STYLE, null); context.commit(storeMutations.UPDATE_CAR_ID, null); @@ -947,7 +1038,7 @@ export const actions = { } }, saveVehicleStyle(context, style) { - //Reset dependent state when changing + //Reset dependent state when changing if (context.state.order.vehicle.style !== style) { context.commit(storeMutations.UPDATE_CAR_ID, null); context.commit(storeMutations.UPDATE_VEHICLE_CATEGORY, null); @@ -964,20 +1055,32 @@ export const actions = { context.commit(storeMutations.UPDATE_STYLE, style); } }, - saveVehicleDamage(context, { isWindshieldRepair, selectedGlassToReplace, selectedWindshieldChipCount }) { - + saveVehicleDamage( + context, + { isWindshieldRepair, selectedGlassToReplace, selectedWindshieldChipCount } + ) { const selectedGlassPassedInSorted = selectedGlassToReplace.slice().sort(); - const isGlassToReplaceTheSame = (context.state.order.damage.glassToReplace?.length === selectedGlassToReplace.length) - && context.state.order.damage.glassToReplace + const isGlassToReplaceTheSame = + context.state.order.damage.glassToReplace?.length === + selectedGlassToReplace.length && + context.state.order.damage.glassToReplace .slice() .sort() - .every((obj, index) => obj.glassLocation === selectedGlassPassedInSorted[index].glassLocation && obj.glassName === selectedGlassPassedInSorted[index].glassName); - const isWindshieldRepairTheSame = isWindshieldRepair === context.state.order.damage.isRepair; - const isChipCountTheSame = Array.isArray(selectedWindshieldChipCount) //TODO: fix the underlying components so this is never an array - ? selectedWindshieldChipCount[0] === context.state.order.damage.numberOfChips - : selectedWindshieldChipCount === context.state.order.damage.numberOfChips; + .every( + (obj, index) => + obj.glassLocation === + selectedGlassPassedInSorted[index].glassLocation && + obj.glassName === selectedGlassPassedInSorted[index].glassName + ); + const isWindshieldRepairTheSame = + isWindshieldRepair === context.state.order.damage.isRepair; - const isDamageChanging = !isGlassToReplaceTheSame || !isWindshieldRepairTheSame || (isWindshieldRepair && !isChipCountTheSame); + const isChipCountTheSame = selectedWindshieldChipCount === context.state.order.damage.numberOfChips; + + const isDamageChanging = + !isGlassToReplaceTheSame || + !isWindshieldRepairTheSame || + (isWindshieldRepair && !isChipCountTheSame); if (isDamageChanging) { //Reset dependent state when changing @@ -985,14 +1088,23 @@ export const actions = { // Save new values context.commit(storeMutations.UPDATE_IS_REPAIR, isWindshieldRepair); - context.commit(storeMutations.UPDATE_NUMBER_OF_CHIPS, isWindshieldRepair ? parseInt(selectedWindshieldChipCount) : null); - context.commit(storeMutations.UPDATE_GLASS_TO_REPLACE, selectedGlassToReplace); + context.commit( + storeMutations.UPDATE_NUMBER_OF_CHIPS, + isWindshieldRepair ? parseInt(selectedWindshieldChipCount) : null + ); + context.commit( + storeMutations.UPDATE_GLASS_TO_REPLACE, + selectedGlassToReplace + ); } }, // Vin lookup - saveVinLookup(context, { isSelectedGlassAvailableForVehicle, vehicleInfo, registrationInfo }) { - //Reset dependent state when changing + saveVinLookup( + context, + { isSelectedGlassAvailableForVehicle, vehicleInfo, registrationInfo } + ) { + //Reset dependent state when changing if (vehicleInfo.vin !== context.state.order.vehicle.vin) { context.dispatch(storeActions.RESET_REGISTRATION_STATE_AND_DEPENDENCIES); context.dispatch(storeActions.RESET_PARTS_STATE_AND_DEPENDENCIES); @@ -1006,10 +1118,15 @@ export const actions = { context.commit(storeMutations.UPDATE_REGISTRATION, registrationInfo); } }, - saveRegistrationLicensePlateLookup(context, { isSelectedGlassAvailableForVehicle, vehicleInfo, registrationInfo }) { - //Reset dependent state when changing - if (registrationInfo?.licensePlate !== context.state.order.vehicle.registration?.licensePlate) { - + saveRegistrationLicensePlateLookup( + context, + { isSelectedGlassAvailableForVehicle, vehicleInfo, registrationInfo } + ) { + //Reset dependent state when changing + if ( + registrationInfo?.licensePlate !== + context.state.order.vehicle.registration?.licensePlate + ) { context.dispatch(storeActions.RESET_REGISTRATION_STATE_AND_DEPENDENCIES); if (!isSelectedGlassAvailableForVehicle) { @@ -1022,10 +1139,25 @@ export const actions = { context.commit(storeMutations.UPDATE_REGISTRATION, registrationInfo); } }, - saveRegistrationAddressLookup(context, { isSelectedGlassAvailableForVehicle, vehicleInfo, registrationInfo }) { - //Reset dependent state when changing - if (registrationInfo?.address !== context.state.order.vehicle.registration?.address || registrationInfo?.city !== context.state.order.vehicle.registration?.city || registrationInfo?.state !== context.state.order.vehicle.registration?.state || registrationInfo?.zipCode !== context.state.order.vehicle.registration?.zipCode || registrationInfo?.firstName !== context.state.order.vehicle.registration?.firstName || registrationInfo?.lastName !== context.state.order.vehicle.registration?.lastName) { - + saveRegistrationAddressLookup( + context, + { isSelectedGlassAvailableForVehicle, vehicleInfo, registrationInfo } + ) { + //Reset dependent state when changing + if ( + registrationInfo?.address !== + context.state.order.vehicle.registration?.address || + registrationInfo?.city !== + context.state.order.vehicle.registration?.city || + registrationInfo?.state !== + context.state.order.vehicle.registration?.state || + registrationInfo?.zipCode !== + context.state.order.vehicle.registration?.zipCode || + registrationInfo?.firstName !== + context.state.order.vehicle.registration?.firstName || + registrationInfo?.lastName !== + context.state.order.vehicle.registration?.lastName + ) { context.dispatch(storeActions.RESET_REGISTRATION_STATE_AND_DEPENDENCIES); if (!isSelectedGlassAvailableForVehicle) { @@ -1040,72 +1172,141 @@ export const actions = { }, savePartQuestionAnswers(context, partQuestionAnswersArray) { // if part question answers have changed, reset subsequent question answers - const sortedPreviousResultsArray = sortArrayOfObjectsByPropertyValue(context.getters.damage.partQuestionAnswers, "result"); - const sortedPartQuestionAnswersArray = sortArrayOfObjectsByPropertyValue(partQuestionAnswersArray, "result") - const havePartQuestionAnswersChanged = sortedPreviousResultsArray?.length !== sortedPartQuestionAnswersArray.length || - !sortedPreviousResultsArray?.every((x, i) => x.result === sortedPartQuestionAnswersArray[i].result); + const sortedPreviousResultsArray = sortArrayOfObjectsByPropertyValue( + context.getters.damage.partQuestionAnswers, + "result" + ); + const sortedPartQuestionAnswersArray = sortArrayOfObjectsByPropertyValue( + partQuestionAnswersArray, + "result" + ); + const havePartQuestionAnswersChanged = + sortedPreviousResultsArray?.length !== + sortedPartQuestionAnswersArray.length || + !sortedPreviousResultsArray?.every( + (x, i) => x.result === sortedPartQuestionAnswersArray[i].result + ); if (havePartQuestionAnswersChanged) { context.commit(storeMutations.UPDATE_GLASS_PARTS, null); context.commit(storeMutations.UPDATE_MOLDING_QUESTION_ANSWERS, null); context.commit(storeMutations.UPDATE_CAPABILITY_QUESTION_ANSWERS, null); - context.commit(storeMutations.UPDATE_PAGE_DATA, { page: fmgPageValues.VEHICLE_PARTS, data: null }); - context.commit(storeMutations.UPDATE_PAGE_DATA, { page: fmgPageValues.MOLDING_QUESTIONS, data: null }); - context.commit(storeMutations.UPDATE_PAGE_DATA, { page: fmgPageValues.CAPABILITY_QUESTIONS, data: null }); + context.commit(storeMutations.UPDATE_PAGE_DATA, { + page: fmgPageValues.VEHICLE_PARTS, + data: null, + }); + context.commit(storeMutations.UPDATE_PAGE_DATA, { + page: fmgPageValues.MOLDING_QUESTIONS, + data: null, + }); + context.commit(storeMutations.UPDATE_PAGE_DATA, { + page: fmgPageValues.CAPABILITY_QUESTIONS, + data: null, + }); } //Save new values - context.commit(storeMutations.UPDATE_PART_QUESTION_ANSWERS, partQuestionAnswersArray); + context.commit( + storeMutations.UPDATE_PART_QUESTION_ANSWERS, + partQuestionAnswersArray + ); }, resetMoldingAndCapabilityQuestionAnswersIfNeeded(context, matchedParts) { - const partsOrQuestionsDataToCompareWith = context.getters.pageData(fmgPageValues.MOLDING_QUESTIONS)?.partsOrQuestions ?? context.getters.pageData(fmgPageValues.CAPABILITY_QUESTIONS)?.partsOrQuestions ?? []; + const partsOrQuestionsDataToCompareWith = + context.getters.pageData(fmgPageValues.MOLDING_QUESTIONS) + ?.partsOrQuestions ?? + context.getters.pageData(fmgPageValues.CAPABILITY_QUESTIONS) + ?.partsOrQuestions ?? + []; function getAllPartNumbers(partsOrQuestions) { return partsOrQuestions[0]?.parts - ? [...partsOrQuestions].map(glass => glass.parts).flat().map(part => part.partNumber).filter(partNumber => !partNumber.toUpperCase().includes("FEE")).sort().join(",") - : [] + ? [...partsOrQuestions] + .map((glass) => glass.parts) + .flat() + .map((part) => part.partNumber) + .filter((partNumber) => !partNumber.toUpperCase().includes("FEE")) + .sort() + .join(",") + : []; } - const previouslySelectedPartNumbers = getAllPartNumbers(partsOrQuestionsDataToCompareWith); + const previouslySelectedPartNumbers = getAllPartNumbers( + partsOrQuestionsDataToCompareWith + ); const currentlySelectedPartNumbers = getAllPartNumbers(matchedParts); - const haveSelectedVehiclePartsChanged = previouslySelectedPartNumbers !== currentlySelectedPartNumbers; + const haveSelectedVehiclePartsChanged = + previouslySelectedPartNumbers !== currentlySelectedPartNumbers; if (haveSelectedVehiclePartsChanged) { context.commit(storeMutations.UPDATE_GLASS_PARTS, null); context.commit(storeMutations.UPDATE_MOLDING_QUESTION_ANSWERS, null); context.commit(storeMutations.UPDATE_CAPABILITY_QUESTION_ANSWERS, null); - context.commit(storeMutations.UPDATE_PAGE_DATA, { page: fmgPageValues.MOLDING_QUESTIONS, data: null }); - context.commit(storeMutations.UPDATE_PAGE_DATA, { page: fmgPageValues.CAPABILITY_QUESTIONS, data: null }); + context.commit(storeMutations.UPDATE_PAGE_DATA, { + page: fmgPageValues.MOLDING_QUESTIONS, + data: null, + }); + context.commit(storeMutations.UPDATE_PAGE_DATA, { + page: fmgPageValues.CAPABILITY_QUESTIONS, + data: null, + }); } }, saveMoldingQuestionAnswers(context, moldingQuestionAnswers) { - const sortedPreviousResultsArray = sortArrayOfObjectsByPropertyValue(context.getters.damage.moldingQuestionAnswers, "partNum"); - const sortedMoldingQuestionAnswersArray = sortArrayOfObjectsByPropertyValue(moldingQuestionAnswers, "partNum"); - const haveMoldingQuestionAnswersChanged = sortedPreviousResultsArray?.length !== sortedMoldingQuestionAnswersArray.length || - !sortedPreviousResultsArray?.every((x, i) => x.partNum === sortedMoldingQuestionAnswersArray[i].partNum); + const sortedPreviousResultsArray = sortArrayOfObjectsByPropertyValue( + context.getters.damage.moldingQuestionAnswers, + "partNum" + ); + const sortedMoldingQuestionAnswersArray = sortArrayOfObjectsByPropertyValue( + moldingQuestionAnswers, + "partNum" + ); + const haveMoldingQuestionAnswersChanged = + sortedPreviousResultsArray?.length !== + sortedMoldingQuestionAnswersArray.length || + !sortedPreviousResultsArray?.every( + (x, i) => x.partNum === sortedMoldingQuestionAnswersArray[i].partNum + ); if (haveMoldingQuestionAnswersChanged) { context.commit(storeMutations.UPDATE_GLASS_PARTS, null); context.commit(storeMutations.UPDATE_CAPABILITY_QUESTION_ANSWERS, null); - context.commit(storeMutations.UPDATE_PAGE_DATA, { page: fmgPageValues.CAPABILITY_QUESTIONS, data: null }); + context.commit(storeMutations.UPDATE_PAGE_DATA, { + page: fmgPageValues.CAPABILITY_QUESTIONS, + data: null, + }); } //Save new values - context.commit(storeMutations.UPDATE_MOLDING_QUESTION_ANSWERS, moldingQuestionAnswers); + context.commit( + storeMutations.UPDATE_MOLDING_QUESTION_ANSWERS, + moldingQuestionAnswers + ); }, saveCapabilityQuestionAnswers(context, capabilityQuestionAnswers) { - const sortedPreviousResultsArray = sortArrayOfObjectsByPropertyValue(context.getters.damage.capabilityQuestionAnswers, "result"); - const sortedCapabilityQuestionAnswersArray = sortArrayOfObjectsByPropertyValue(capabilityQuestionAnswers, "result"); - const haveCapabilityQuestionAnswersChanged = sortedPreviousResultsArray?.length !== sortedCapabilityQuestionAnswersArray.length || - !sortedPreviousResultsArray?.every((x, i) => x.result === sortedCapabilityQuestionAnswersArray[i].result); + const sortedPreviousResultsArray = sortArrayOfObjectsByPropertyValue( + context.getters.damage.capabilityQuestionAnswers, + "result" + ); + const sortedCapabilityQuestionAnswersArray = + sortArrayOfObjectsByPropertyValue(capabilityQuestionAnswers, "result"); + const haveCapabilityQuestionAnswersChanged = + sortedPreviousResultsArray?.length !== + sortedCapabilityQuestionAnswersArray.length || + !sortedPreviousResultsArray?.every( + (x, i) => x.result === sortedCapabilityQuestionAnswersArray[i].result + ); if (haveCapabilityQuestionAnswersChanged) { context.commit(storeMutations.UPDATE_GLASS_PARTS, null); } //Save new values - context.commit(storeMutations.UPDATE_CAPABILITY_QUESTION_ANSWERS, capabilityQuestionAnswers); + context.commit( + storeMutations.UPDATE_CAPABILITY_QUESTION_ANSWERS, + capabilityQuestionAnswers + ); }, // Misc order actions saveServiceLocation(context, serviceLocationInfo) { @@ -1117,7 +1318,6 @@ export const actions = { saveVin(context, { isSelectedGlassAvailableForVehicle, vehicleInfo }) { //Reset dependent state when changing if (vehicleInfo.vin !== context.state.order.vehicle.vin) { - if (!isSelectedGlassAvailableForVehicle) { context.dispatch(storeActions.RESET_DAMAGE_STATE_AND_DEPENDENCIES); context.dispatch(storeActions.RESET_PARTS_STATE_AND_DEPENDENCIES); @@ -1132,12 +1332,11 @@ export const actions = { }, clearVin(context) { context.commit(storeMutations.UPDATE_VEHICLE_VIN, null); - } -} + }, +}; export default createStore({ plugins: [createPersistedState()], - // IMPORTANT: Be VERY careful when modifying these fields for at least a few reasons: // * The CMS can reference the fields by name // * Return users may have a previous "version" of the model, and we don't want @@ -1160,23 +1359,20 @@ function getHasRecalibrationPart(state) { } else { // Has 'requiresRecalibration' but no 'recalibrationType' at all return true; } - } else { // Does not have 'requiresRecalibration' + } else { + // Does not have 'requiresRecalibration' return false; } - } function sortArrayOfObjectsByPropertyValue(arrayOfObjects, propertyName) { if (!arrayOfObjects) return null; return arrayOfObjects.sort((a, b) => { - if (a[propertyName] < b[propertyName]) - return -1; - else if (a[propertyName] > b[propertyName]) - return 1; - else - return 0; - }) + if (a[propertyName] < b[propertyName]) return -1; + else if (a[propertyName] > b[propertyName]) return 1; + else return 0; + }); } function convertGlassPieceNamingForApi(glassArray) { @@ -1212,4 +1408,4 @@ function convertGlassPieceNamingFromApi(glassArray) { return glass; }); return glassArray; -} \ No newline at end of file +} diff --git a/src/styles/common-error-styles.scss b/src/styles/common-error-styles.scss index 3d4771ff5..ea39df63c 100644 --- a/src/styles/common-error-styles.scss +++ b/src/styles/common-error-styles.scss @@ -3,7 +3,6 @@ html { &.list-button, &.list-card, &.list-card.list-button { - border: none; color: $red; input[type=checkbox]:focus + label, input[type=radio]:focus + label { diff --git a/src/ux-components/button-main/button-main.vue b/src/ux-components/button-main/button-main.vue index 8396351f8..4342c18b1 100644 --- a/src/ux-components/button-main/button-main.vue +++ b/src/ux-components/button-main/button-main.vue @@ -2,15 +2,17 @@ @@ -33,11 +35,16 @@ export default { }; }, methods: { - removeLoader(){ + removeLoader() { this.isLoaderDisplayed = false; }, clicked() { - this.pushEventToGA(this.$route.query[this.queryStrings.FMG_PAGE], this.GaActions.CLICKED, this.buttonText, true); + this.pushEventToGA( + this.$route.query[this.queryStrings.FMG_PAGE], + this.GaActions.CLICKED, + this.buttonText, + true + ); if (!this.isDisabled) { this.isLoaderDisplayed = true; this.$emit("click-event"); @@ -98,7 +105,8 @@ export default { background: $blue-700; box-shadow: 0 0 0 3px, 0 0 0 5.5px $blue-700; } - &.delay {// fixes flicker while transitioning between states + &.delay { + // fixes flicker while transitioning between states transition: background 0s 0s ease-in-out; } } @@ -137,7 +145,8 @@ export default { color: $white; @include blue-gradient; } - &.delay {// fixes flicker while transitioning between states + &.delay { + // fixes flicker while transitioning between states transition: background 0s 0s ease-in-out; } } diff --git a/src/ux-components/list-button-horizontal/list-button-horizontal.spec.js b/src/ux-components/list-button-horizontal/list-button-horizontal.spec.js index db439af75..09a63b349 100644 --- a/src/ux-components/list-button-horizontal/list-button-horizontal.spec.js +++ b/src/ux-components/list-button-horizontal/list-button-horizontal.spec.js @@ -1,271 +1,139 @@ -import { shallowMount } from "@vue/test-utils"; +import { mount } from "@vue/test-utils"; import listButtonHorizontal from "./list-button-horizontal"; -import { nextTick } from "vue"; -import { GaActions } from "@/constants/analytics"; +import inputButtonWrapperMixin from "@/mixins/input-button-wrapper-mixin"; describe("list-button-horizontal.vue", () => { - it("Should return input type checkbox if isMultiSelect is true", async () => { - // Act - const wrapper = shallowMount(listButtonHorizontal, { - propsData: { - isMultiSelect: true, - }, + describe("styling/UI", () => { + it("Should return screen reader text", async () => { + // Act + const { wrapper } = setupMocks({ + mockData: { + propsData: { + screenReaderOnlyText: "Screen Reader Only Text", + }, + }, + }); + + // Assert + const paragraph = wrapper.find("span.sr-only"); + + expect(paragraph.text()).toEqual("Screen Reader Only Text"); + }); + + it("Should return text alignment class", async () => { + // Act + const { wrapper } = setupMocks({ + mockData: { + propsData: { + textPosition: "text-center", + }, + }, + }); + + // Assert + const paragraph = wrapper.find("span.m-0"); + + expect(paragraph.attributes("class")).toContain("text-center"); + }); + + it("Should return aria-required state", async () => { + // Act + const { wrapper } = setupMocks({ + mockData: { + propsData: { + isRequired: true, + }, + }, + }); + + // Assert + const input = wrapper.find("input"); + + expect(input.attributes()["aria-required"]).toEqual("true"); + }); + + it("is cash or insurance button => has 'radio-fancy' class", () => { + // Arrange/Act + const { wrapper } = setupMocks({ + mockData: { + propsData: { + isCashOrInsurance: true, + }, + }, + }); + + // Assert + const label = wrapper.find("label"); + + expect(label.classes()).toContain("radio-fancy"); + }); + + test("has buttonLabel => displays buttonLabel", () => { + // Arrange/Act + const { wrapper } = setupMocks({ + mockData: { + propsData: { + buttonLabel: "Surprise!", + }, + }, + }); + + // Assert + const content = wrapper.find(".list-button-horizontal-content"); + expect(content.isVisible()).toBe(true); + expect(content.text()).toContain("Surprise!"); + }); + + test("has buttonLabelSubCopy => displays buttonLabelSubCopy", () => { + // Arrange/Act + const { wrapper } = setupMocks({ + mockData: { + propsData: { + buttonLabel: "Surprise!", + buttonLabelSubCopy: "Super duper surprise :)", + }, + }, + }); + + // Assert + const content = wrapper.find(".list-button-horizontal-content"); + expect(content.isVisible()).toBe(true); + expect(content.text()).toContain("Super duper surprise :)"); + }); + + test("has screenReaderOnlyText => displays screenReaderOnlyText", () => { + // Arrange/Act + const { wrapper } = setupMocks({ + mockData: { + propsData: { + buttonLabel: "Surprise!", + buttonLabelSubCopy: "Super duper surprise :)", + screenReaderOnlyText: "Tests are fun!", + }, + }, + }); + + // Assert + const content = wrapper.find(".list-button-horizontal-content"); + const screenReaderOnlyText = wrapper.find(".sr-only"); + expect(content.isVisible()).toBe(true); + expect(screenReaderOnlyText.exists()).toBe(true); + expect(screenReaderOnlyText.text()).toContain("Tests are fun!"); + }); }); - - // Assert - const input = wrapper.find("input"); - - expect(input.attributes().type).toEqual("checkbox"); - }); - - it("Should return input type radio if isMultiSelect is false or not specified", async () => { - // Act - const wrapper = shallowMount(listButtonHorizontal, { - propsData: { - isMultiSelect: false, - }, - }); - - // Assert - const input = wrapper.find("input"); - - expect(input.attributes().type).toEqual("radio"); - }); - - it("Should return primary label text (buttonID)", async () => { - // Act - const wrapper = shallowMount(listButtonHorizontal, { - propsData: { - buttonID: "List Card Checkbox", - }, - }); - - // Assert - const label = wrapper.find("label"); - - expect(label.attributes().for).toEqual("List Card Checkbox"); - }); - - it("Should return screen reader text", async () => { - // Act - const wrapper = shallowMount(listButtonHorizontal, { - propsData: { - screenReaderOnlyText: "Screen Reader Only Text", - }, - }); - - // Assert - const paragraph = wrapper.find("span.sr-only"); - - expect(paragraph.text()).toEqual("Screen Reader Only Text"); - }); - - it("Should return text alignment class", async () => { - // Act - const wrapper = shallowMount(listButtonHorizontal, { - propsData: { - textPosition: "text-center", - }, - }); - - // Assert - const paragraph = wrapper.find("span.m-0"); - - expect(paragraph.attributes("class")).toContain("text-center"); - }); - - it("Should return aria-required state", async () => { - // Act - const wrapper = shallowMount(listButtonHorizontal, { - propsData: { - isRequired: true, - }, - }); - - // Assert - const input = wrapper.find("input"); - - expect(input.attributes()["aria-required"]).toEqual("true"); - }); - - it("Should return loader enabled true", async () => { - // Act - const wrapper = shallowMount(listButtonHorizontal, { - global: { - mocks: { - '$route': { query: { fmgPage: 'page-name' } }, - GaActions: GaActions, - pushEventToGA: jest.fn(), - } - }, - propsData: { - selectingInitiatesLoad: true, - }, - }); - - // Assert - - const label = wrapper.find("label"); - - wrapper.vm.handleCheckChange = jest.fn(); - wrapper.vm.triggerButton(); - - await nextTick(); - - const loader = wrapper.find("loader-stub"); - - expect(loader.exists()).toBe(true); - }); - - it("Should return loader color", async () => { - // Act - const wrapper = shallowMount(listButtonHorizontal, { - global: { - mocks: { - '$route': { query: { fmgPage: 'page-name' } }, - GaActions: GaActions, - pushEventToGA: jest.fn(), - } - }, - propsData: { - loaderColor: "blue", - selectingInitiatesLoad: true, - }, - }); - - // Assert - - const label = wrapper.find("label"); - - wrapper.vm.handleCheckChange = jest.fn(); - wrapper.vm.triggerButton(); - - await nextTick(); - - const loader = wrapper.find("loader-stub"); - - expect(loader.attributes("class")).toContain("blue"); - }); - - it("Should return loader position", async () => { - // Act - const wrapper = shallowMount(listButtonHorizontal, { - global: { - mocks: { - '$route': { query: { fmgPage: 'page-name' } }, - GaActions: GaActions, - pushEventToGA: jest.fn(), - } - }, - propsData: { - loaderPosition: "right", - selectingInitiatesLoad: true, - }, - }); - - // Assert - - const label = wrapper.find("label"); - - wrapper.vm.handleCheckChange = jest.fn(); - wrapper.vm.triggerButton(); - - await nextTick(); - - const loader = wrapper.find("loader-stub"); - - expect(loader.attributes("class")).toContain("right"); - }); - - it("Should emit button value on click", async () => { - // Act - const wrapper = shallowMount(listButtonHorizontal, { - propsData: { - isRadioHorizontal: true, - buttonLabel: "Windshield", - value: "List Card Checkbox", - groupID: "radio-demo-1", - groupName: "radio 1", - buttonImage: "windshield-damage.svg", - isRequired: true, - isWide: false, - modelValue: ["List Card Checkbox"], - }, - }); - wrapper.vm.handleCheckChange(); - // Assert - expect(wrapper.emitted()["isCheckedChanged"][0]).toEqual([{value: "List Card Checkbox", checkValue: Boolean, buttonId: undefined, checkValue: false}]); - }); - - it("Should set checkValue data if selectedButtonIDs has value(s)", async () => { - // Act - const wrapper = shallowMount(listButtonHorizontal, { - propsData: { - isRadioHorizontal: true, - buttonLabel: "Windshield", - buttonID: "List Card Checkbox", - groupID: "radio-demo-1", - groupName: "radio 1", - buttonImage: "windshield-damage.svg", - isRequired: true, - isWide: false, - modelValue: ["List Card Checkbox"], - isMultiSelect: false, - value: "Car-Front", - selectedValues: ["Car-Front"] - }, - }); - // Assert - expect(wrapper.vm.checkValue).toEqual(true); - }); - - it("Should run handleCheckChange if selectingInitiatesLoad is false and handleInputChange is triggered", async () => { - // Act - const wrapper = shallowMount(listButtonHorizontal, { - propsData: { - selectingInitiatesLoad: false, - }, - }); - - // Assert - wrapper.vm.handleInputChange(); - - await nextTick(); - - expect(wrapper.vm.handleCheckChange).toBeCalled; - }); - - it("Should do nothing if isMultiSelect is true and handleKeyupArrow is triggered", async () => { - // Act - const wrapper = shallowMount(listButtonHorizontal, { - propsData: { - isMultiSelect: true, - }, - }); - - // Assert - wrapper.vm.handleKeyupArrow(); - - await nextTick(); - - expect(wrapper.vm.handleKeyupArrow).toHaveReturned; - }); - - it("Should run handleCheckChange if selectingInitiatesLoad is false and handleKeyupArrow is triggered", async () => { - // Act - const wrapper = shallowMount(listButtonHorizontal, { - propsData: { - selectingInitiatesLoad: false, - isMultiSelect: false, - }, - }); - - // Assert - wrapper.vm.handleKeyupArrow(); - - await nextTick(); - - expect(wrapper.vm.handleCheckChange).toBeCalled; - }); - }); + +function setupMocks({ mockData }) { + const wrapper = mount(listButtonHorizontal, { + ...mockData, + propsData: { + ...mockData.propsData, + groupName: "my-group", + modelValue: mockData.propsData?.isMultiSelect ? ["5"] : "5", + value: mockData.propsData?.isMultiSelect ? ["4"] : "4", + }, + mixins: [inputButtonWrapperMixin], + }); + + return { wrapper }; +} diff --git a/src/ux-components/list-button-horizontal/list-button-horizontal.vue b/src/ux-components/list-button-horizontal/list-button-horizontal.vue index b9d11d098..c0371f9f7 100644 --- a/src/ux-components/list-button-horizontal/list-button-horizontal.vue +++ b/src/ux-components/list-button-horizontal/list-button-horizontal.vue @@ -1,345 +1,222 @@ diff --git a/src/ux-components/list-button/list-button.spec.js b/src/ux-components/list-button/list-button.spec.js index 53c7f5f65..0b4909420 100644 --- a/src/ux-components/list-button/list-button.spec.js +++ b/src/ux-components/list-button/list-button.spec.js @@ -1,263 +1,304 @@ -import { shallowMount } from "@vue/test-utils"; +import { mount } from "@vue/test-utils"; import listButton from "./list-button"; -import { nextTick } from "vue"; import { GaActions } from "@/constants/analytics"; +import inputButtonWrapperMixin from "@/mixins/input-button-wrapper-mixin"; describe("list-button.vue", () => { - it("Should return input type checkbox if isMultiSelect is true", async () => { - // Act - const wrapper = shallowMount(listButton, { - propsData: { - isMultiSelect: true, - }, + describe("loader", () => { + it("selectingInitiatesLoad is true and answer is changed => show the loader", async () => { + // Act + const { wrapper } = setupMocks({ + mockData: { + global: { + mocks: { + $route: { query: { fmgPage: "page-name" } }, + GaActions: GaActions, + pushEventToGA: jest.fn(), + }, + }, + propsData: { + selectingInitiatesLoad: true, + }, + }, + }); + + // Act + wrapper.vm.selectedValue = "something"; + await wrapper.vm.$nextTick(); + + // Assert + const loader = wrapper.findComponent({ name: "loader" }); + expect(loader.exists()).toBe(true); + }); + + it("Should return loader color", async () => { + // Arrange + const { wrapper } = setupMocks({ + mockData: { + global: { + mocks: { + $route: { query: { fmgPage: "page-name" } }, + GaActions: GaActions, + pushEventToGA: jest.fn(), + }, + }, + propsData: { + loaderColor: "blue", + selectingInitiatesLoad: true, + }, + }, + }); + + // Act + wrapper.vm.selectedValue = "something"; + await wrapper.vm.$nextTick(); + + // Assert + const loader = wrapper.findComponent({ name: "loader" }); + expect(loader.attributes("class")).toContain("blue"); + }); + + it("Should return loader position", async () => { + // Act + const { wrapper } = setupMocks({ + mockData: { + global: { + mocks: { + $route: { query: { fmgPage: "page-name" } }, + GaActions: GaActions, + pushEventToGA: jest.fn(), + }, + }, + propsData: { + loaderPosition: "right", + selectingInitiatesLoad: true, + }, + }, + }); + + // Act + wrapper.vm.selectedValue = "something"; + await wrapper.vm.$nextTick(); + + // Assert + const loader = wrapper.findComponent({ name: "loader" }); + expect(loader.attributes("class")).toContain("right"); + }); }); - // Assert - const input = wrapper.find("input"); + describe("baseInputButton checks", () => { + it("Should return input type checkbox if isMultiSelect is true", async () => { + // Act + const { wrapper } = setupMocks({ + mockData: { + propsData: { + isMultiSelect: true, + }, + }, + }); - expect(input.attributes().type).toEqual("checkbox"); - }); + // Assert + const input = wrapper.find("input"); - it("Should return input type radio if isMultiSelect is false or not specified", async () => { - // Act - const wrapper = shallowMount(listButton, { - propsData: { - isMultiSelect: false, - }, + expect(input.attributes().type).toEqual("checkbox"); + }); + + it("Should return input type radio if isMultiSelect is false or not specified", async () => { + // Act + const { wrapper } = setupMocks({ + mockData: { + propsData: { + isMultiSelect: false, + }, + }, + }); + + // Assert + const input = wrapper.find("input"); + expect(input.attributes().type).toEqual("radio"); + }); + + it("(Radio) Should emit button value on selectedValue change", async () => { + // Act + const { wrapper } = setupMocks({ + mockData: { + propsData: { + isRadioHorizontal: true, + buttonLabel: "Windshield", + value: "List Card Checkbox", + groupID: "radio-demo-1", + groupName: "radio1", + buttonImage: "windshield-damage.svg", + isRequired: true, + isWide: false, + modelValue: "List Card Checkbox", + buttonID: "list-card-id", + }, + }, + }); + + // Act + wrapper.vm.selectedValue = "test"; + + // Assert + expect(wrapper.emitted()["update:modelValue"][0][0]).toEqual("test"); + }); + + it("(Checkbox) Should emit button value on selectedValue change", async () => { + // Act + const { wrapper } = setupMocks({ + mockData: { + propsData: { + isRadioHorizontal: true, + buttonLabel: "Windshield", + value: "List Card Checkbox", + groupID: "radio-demo-1", + groupName: "radio 1", + buttonImage: "windshield-damage.svg", + isRequired: true, + isWide: false, + modelValue: "List Card Checkbox", + buttonID: "list-card-id", + isMultiSelect: true, + }, + }, + }); + + // Act + wrapper.vm.selectedValue = ["test"]; + + // Assert + expect(wrapper.emitted()["update:modelValue"][0][0]).toEqual(["test"]); + }); }); - // Assert - const input = wrapper.find("input"); + describe("styling/UI", () => { + test("has buttonLabel => displays buttonLabel", () => { + // Arrange/Act + const { wrapper } = setupMocks({ + mockData: { + propsData: { + buttonLabel: "Surprise!", + modelValue: "", + groupName: "groupName", + value: "myValue" + }, + }, + }); - expect(input.attributes().type).toEqual("radio"); - }); + // Assert + const content = wrapper.find(".list-button-content"); + expect(content.isVisible()).toBe(true); + expect(content.text()).toContain("Surprise!"); + }); - it("Should return primary label text (buttonID)", async () => { - // Act - const wrapper = shallowMount(listButton, { - propsData: { - buttonID: "List Card Checkbox", - }, + test("has buttonLabelSubCopy => displays buttonLabelSubCopy", () => { + // Arrange/Act + const { wrapper } = setupMocks({ + mockData: { + propsData: { + buttonLabel: "Surprise!", + buttonLabelSubCopy: "Super duper surprise :)", + modelValue: "", + groupName: "groupName", + value: "myValue" + }, + }, + }); + + // Assert + const content = wrapper.find(".list-button-content"); + expect(content.isVisible()).toBe(true); + expect(content.text()).toContain("Super duper surprise :)"); + }); + + test("has screenReaderOnlyText => displays screenReaderOnlyText", () => { + // Arrange/Act + const { wrapper } = setupMocks({ + mockData: { + propsData: { + buttonLabel: "Surprise!", + buttonLabelSubCopy: "Super duper surprise :)", + screenReaderOnlyText: "Tests are fun!", + modelValue: "", + groupName: "groupName", + value: "myValue" + }, + }, + }); + + // Assert + const content = wrapper.find(".list-button-content"); + const screenReaderOnlyText = wrapper.find(".sr-only"); + expect(content.isVisible()).toBe(true); + expect(screenReaderOnlyText.exists()).toBe(true); + expect(screenReaderOnlyText.text()).toContain("Tests are fun!"); + }); + + it("Should return screen reader text", async () => { + // Act + const { wrapper } = setupMocks({ + mockData: { + propsData: { + screenReaderOnlyText: "Screen Reader Only Text", + modelValue: "", + groupName: "groupName", + value: "myValue" + }, + }, + }); + + // Assert + const paragraph = wrapper.find("span.sr-only"); + + expect(paragraph.text()).toEqual("Screen Reader Only Text"); + }); + + it("Should return text alignment class", async () => { + // Act + const { wrapper } = setupMocks({ + mockData: { + propsData: { + textPosition: "text-center", + modelValue: "", + groupName: "groupName", + value: "myValue" + }, + }, + }); + + // Assert + const paragraph = wrapper.find("span.m-0"); + + expect(paragraph.attributes("class")).toContain("text-center"); + }); + + it("Should return aria-required state", async () => { + // Act + const { wrapper } = setupMocks({ + mockData: { + propsData: { + isRequired: true, + groupName: "groupName", + modelValue: "", + value: "myValue", + }, + }, + }); + + // Assert + const input = wrapper.find("input"); + + expect(input.attributes()["aria-required"]).toEqual("true"); + }); }); - - // Assert - const label = wrapper.find("label"); - - expect(label.attributes().for).toEqual("List Card Checkbox"); - }); - - it("Should return screen reader text", async () => { - // Act - const wrapper = shallowMount(listButton, { - propsData: { - screenReaderOnlyText: "Screen Reader Only Text", - }, - }); - - // Assert - const paragraph = wrapper.find("span.sr-only"); - - expect(paragraph.text()).toEqual("Screen Reader Only Text"); - }); - - it("Should return text alignment class", async () => { - // Act - const wrapper = shallowMount(listButton, { - propsData: { - textPosition: "text-center", - }, - }); - - // Assert - const paragraph = wrapper.find("span.m-0"); - - expect(paragraph.attributes("class")).toContain("text-center"); - }); - - it("Should return aria-required state", async () => { - // Act - const wrapper = shallowMount(listButton, { - propsData: { - isRequired: true, - }, - }); - - // Assert - const input = wrapper.find("input"); - - expect(input.attributes()["aria-required"]).toEqual("true"); - }); - - it("Should return loader enabled true", async () => { - // Act - const wrapper = shallowMount(listButton, { - global: { - mocks: { - '$route': { query: { fmgPage: 'page-name' } }, - GaActions: GaActions, - pushEventToGA: jest.fn(), - } - }, - propsData: { - selectingInitiatesLoad: true, - }, - }); - - // Assert - wrapper.vm.handleCheckChange = jest.fn(); - wrapper.vm.triggerButton(); - - await nextTick(); - - const loader = wrapper.find("loader-stub"); - - expect(loader.exists()).toBe(true); - }); - - it("Should return loader color", async () => { - // Act - const wrapper = shallowMount(listButton, { - global: { - mocks: { - '$route': { query: { fmgPage: 'page-name' } }, - GaActions: GaActions, - pushEventToGA: jest.fn(), - } - }, - propsData: { - loaderColor: "blue", - selectingInitiatesLoad: true, - }, - }); - - // Assert - wrapper.vm.handleCheckChange = jest.fn(); - wrapper.vm.triggerButton(); - await nextTick(); - - const loader = wrapper.find("loader-stub"); - - expect(loader.attributes("class")).toContain("blue"); - }); - - it("Should return loader position", async () => { - // Act - const wrapper = shallowMount(listButton, { - global: { - mocks: { - '$route': { query: { fmgPage: 'page-name' } }, - GaActions: GaActions, - pushEventToGA: jest.fn(), - } - }, - propsData: { - loaderPosition: "right", - selectingInitiatesLoad: true, - }, - }); - - // Assert - wrapper.vm.handleCheckChange = jest.fn(); - wrapper.vm.triggerButton(); - - await nextTick(); - - const loader = wrapper.find("loader-stub"); - - expect(loader.attributes("class")).toContain("right"); - }); - - it("Should emit button value on click", async () => { - // Act - const wrapper = shallowMount(listButton, { - propsData: { - isRadioHorizontal: true, - buttonLabel: "Windshield", - value: "List Card Checkbox", - groupID: "radio-demo-1", - groupName: "radio 1", - buttonImage: "windshield-damage.svg", - isRequired: true, - isWide: false, - modelValue: ["List Card Checkbox"], - buttonID: 'list-card-id' - }, - }); - - wrapper.vm.handleCheckChange(); - - // Assert - expect(wrapper.emitted()["isCheckedChanged"][0]).toEqual([{value: "List Card Checkbox", checkValue: false, buttonId: 'list-card-id'}]); - - }); - - it("Should set checkValue data if selectedButtonIDs has value(s)", async () => { - // Act - const wrapper = shallowMount(listButton, { - propsData: { - isRadioHorizontal: true, - buttonLabel: "Windshield", - buttonID: "List Card Checkbox", - groupID: "radio-demo-1", - groupName: "radio 1", - buttonImage: "windshield-damage.svg", - isRequired: true, - isWide: false, - modelValue: ["List Card Checkbox"], - selectedValues: "Car-Front" - }, - }); - // Assert - expect(wrapper.componentVM.checkValue).toEqual(false); - }); - - it("Should run handleCheckChange if selectingInitiatesLoad is false and handleInputChange is triggered", async () => { - // Act - const wrapper = shallowMount(listButton, { - propsData: { - selectingInitiatesLoad: false, - }, - }); - - // Assert - wrapper.vm.handleInputChange(); - - await nextTick(); - - expect(wrapper.vm.handleCheckChange).toBeCalled; - }); - - it("Should do nothing if isMultiSelect is true and handleKeyupArrow is triggered", async () => { - // Act - const wrapper = shallowMount(listButton, { - propsData: { - isMultiSelect: true, - }, - }); - - // Assert - wrapper.vm.handleKeyupArrow(); - - await nextTick(); - - expect(wrapper.vm.handleKeyupArrow).toHaveReturned; - }); - - it("Should run handleCheckChange if selectingInitiatesLoad is false and handleKeyupArrow is triggered", async () => { - // Act - const wrapper = shallowMount(listButton, { - propsData: { - selectingInitiatesLoad: false, - isMultiSelect: false, - }, - }); - - // Assert - wrapper.vm.handleKeyupArrow(); - - await nextTick(); - - expect(wrapper.vm.handleCheckChange).toBeCalled; - }); - }); + +function setupMocks({ mockData }) { + const wrapper = mount(listButton, { + ...mockData, + mixins: [inputButtonWrapperMixin], + }); + + return { wrapper }; +} diff --git a/src/ux-components/list-button/list-button.vue b/src/ux-components/list-button/list-button.vue index e51d5cc77..54ef98b35 100644 --- a/src/ux-components/list-button/list-button.vue +++ b/src/ux-components/list-button/list-button.vue @@ -1,242 +1,130 @@ diff --git a/src/ux-components/list-card/list-card.spec.js b/src/ux-components/list-card/list-card.spec.js index 4e9edf5dc..41d3555e2 100644 --- a/src/ux-components/list-card/list-card.spec.js +++ b/src/ux-components/list-card/list-card.spec.js @@ -1,12 +1,12 @@ -import { shallowMount } from "@vue/test-utils"; +import { mount } from "@vue/test-utils"; import listCard from "./list-card"; import { nextTick } from "vue"; import { GaActions } from "@/constants/analytics"; describe("list-card.vue", () => { - it("Should return input type checkbox if isMultiSelect is true", async () => { + it("Should return input type checkbox if isMultiSelect is true", () => { // Act - const wrapper = shallowMount(listCard, { + const wrapper = mount(listCard, { propsData: { isMultiSelect: true, buttonLabel: "Windshield", @@ -14,6 +14,7 @@ describe("list-card.vue", () => { groupID: "checkbox-demo-1", groupName: "Checkbox 1", buttonImage: "windshield-damage.svg", + value: "test value", }, }); @@ -22,9 +23,9 @@ describe("list-card.vue", () => { expect(input.attributes().type).toEqual("checkbox"); }); - it("Should return primary label text", async () => { + it("Should return primary label text", () => { // Act - const wrapper = shallowMount(listCard, { + const wrapper = mount(listCard, { propsData: { isRadioHorizontal: true, buttonLabel: "Windshield", @@ -32,6 +33,7 @@ describe("list-card.vue", () => { groupID: "radio-demo-1", groupName: "radio 1", buttonImage: "windshield-damage.svg", + value: "test value", }, }); @@ -40,9 +42,9 @@ describe("list-card.vue", () => { expect(paragraph.text()).toEqual("Windshield"); }); - it("Should return secondary (sub) label text", async () => { + it("Should return secondary (sub) label text", () => { // Act - const wrapper = shallowMount(listCard, { + const wrapper = mount(listCard, { propsData: { isRadioHorizontal: true, buttonLabel: "Windshield", @@ -51,6 +53,7 @@ describe("list-card.vue", () => { groupName: "radio 1", buttonImage: "windshield-damage.svg", buttonLabelSubCopy: "Test", + value: "test value", }, }); @@ -59,28 +62,9 @@ describe("list-card.vue", () => { expect(paragraph.text()).toEqual("Test"); }); - it("Should return value used for various text settings including the label 'for' and input id", async () => { + it("Should return input group name used for radio or checkbox", () => { // Act - const wrapper = shallowMount(listCard, { - propsData: { - isRadioHorizontal: true, - buttonLabel: "Windshield", - buttonID: "List Card Checkbox", - groupID: "radio-demo-1", - groupName: "radio 1", - buttonImage: "windshield-damage.svg", - buttonLabelSubCopy: "Test", - }, - }); - - // Assert - const label = wrapper.find("label"); - expect(label.attributes().for).toEqual("List Card Checkbox"); - }); - - it("Should return input group name used for radio or checkbox", async () => { - // Act - const wrapper = shallowMount(listCard, { + const wrapper = mount(listCard, { propsData: { isRadioHorizontal: true, buttonLabel: "Windshield", @@ -89,6 +73,7 @@ describe("list-card.vue", () => { groupName: "radio 1", buttonImage: "windshield-damage.svg", buttonLabelSubCopy: "Test", + value: "test value", }, }); @@ -97,9 +82,9 @@ describe("list-card.vue", () => { expect(input.attributes().name).toEqual("radio 1"); }); - it("Should return aria-required state", async () => { + it("Should return aria-required state", () => { // Act - const wrapper = shallowMount(listCard, { + const wrapper = mount(listCard, { propsData: { isRadioHorizontal: true, buttonLabel: "Windshield", @@ -108,6 +93,7 @@ describe("list-card.vue", () => { groupName: "radio 1", buttonImage: "windshield-damage.svg", isRequired: true, + value: "test value", }, }); @@ -116,9 +102,9 @@ describe("list-card.vue", () => { expect(input.attributes()["aria-required"]).toEqual("true"); }); - it("Should return flex row classes if isWide is true", async () => { + it("Should return flex row classes if isWide is true", () => { // Act - const wrapper = shallowMount(listCard, { + const wrapper = mount(listCard, { propsData: { isRadioHorizontal: true, buttonLabel: "Windshield", @@ -129,17 +115,21 @@ describe("list-card.vue", () => { isRequired: true, isWide: true, buttonLabelSubCopy: "", + value: "test value", }, }); // Assert - const label = wrapper.find("label"); - expect(label.classes()).toEqual(["d-flex", "w-100", "align-items-center", "px-2", "h-100", "flex-row", "py-2", "ps-4", "pe-4"]); + const label = wrapper.find(".list-card-content"); + expect(label.exists()).toBe(true); + const labelClasses = wrapper.vm.labelClasses; + expect(labelClasses).toContain("flex-row"); + expect(label.classes()).toContain("flex-row"); }); - it("Should return flex row classes if isWide is true and checkboxTop if buttonLabelSubCopy is true", async () => { + it("Should return flex row classes if isWide is true and checkboxTop if buttonLabelSubCopy is provided", () => { // Act - const wrapper = shallowMount(listCard, { + const wrapper = mount(listCard, { propsData: { isRadioHorizontal: true, buttonLabel: "Windshield", @@ -150,17 +140,23 @@ describe("list-card.vue", () => { isRequired: true, isWide: true, buttonLabelSubCopy: "Button Subcopy", + value: "test value", }, }); // Assert - const label = wrapper.find("label"); - expect(label.classes()).toEqual(["d-flex", "w-100", "align-items-center", "px-2", "h-100", "flex-row", "py-2", "ps-4", "pe-4", "checkboxTop"]); + const label = wrapper.find(".list-card-content"); + expect(label.exists()).toBe(true); + const labelClasses = wrapper.vm.labelClasses; + expect(labelClasses).toContain("flex-row"); + expect(label.classes()).toContain("flex-row"); + expect(labelClasses).toContain("checkboxTop"); + expect(label.classes()).toContain("checkboxTop"); }); - it("Should return flex column classes if isWide is false", async () => { + it("Should return flex column classes if isWide is false", () => { // Act - const wrapper = shallowMount(listCard, { + const wrapper = mount(listCard, { propsData: { isRadioHorizontal: true, buttonLabel: "Windshield", @@ -170,165 +166,15 @@ describe("list-card.vue", () => { buttonImage: "windshield-damage.svg", isRequired: true, isWide: false, + value: "test value", }, }); // Assert - const label = wrapper.find("label"); - expect(label.classes()).toEqual(["d-flex", "w-100", "align-items-center", "px-2", "h-100", "flex-column", "pt-4", "pb-3"]); + const label = wrapper.find(".list-card-content"); + expect(label.exists()).toBe(true); + const labelClasses = wrapper.vm.labelClasses; + expect(labelClasses).toContain("flex-column"); + expect(label.classes()).toContain("flex-column"); }); - - it("Should emit button value on click", async () => { - // Act - const wrapper = shallowMount(listCard, { - propsData: { - isRadioHorizontal: true, - buttonLabel: "Windshield", - value: "List Card Checkbox", - groupID: "radio-demo-1", - groupName: "radio 1", - buttonImage: "windshield-damage.svg", - isRequired: true, - isWide: false, - buttonID: 'list-card-id', - selectedValues: "List Card Checkbox" - }, - }); - wrapper.vm.handleCheckChange(); - // Assert - expect(wrapper.emitted()["isCheckedChanged"][0]).toEqual([{value: "List Card Checkbox", checkValue: true, buttonId: 'list-card-id'}]); - }); - - it("Should set checkValue data if selectedButtonIDs has value(s)", async () => { - // Act - const wrapper = shallowMount(listCard, { - propsData: { - isRadioHorizontal: true, - buttonLabel: "Windshield", - value: "List Card Checkbox", - groupID: "radio-demo-1", - groupName: "radio 1", - buttonImage: "windshield-damage.svg", - isRequired: true, - isWide: false, - selectedValues: "Car-Front" - }, - }); - // Assert - expect(wrapper.componentVM.checkValue).toEqual(false); - }); - - it("Should set an initial value for validation if selectedValues include the value", async () => { - // Arrange - const wrapper = shallowMount(listCard, { - propsData: { - value: "Windshield", - groupName: "radio 1", - selectedValues: ["Windshield"], - }, - }); - - // Assert - expect(wrapper.vm.fieldOptions.initialValue).toEqual([ 'Windshield' ]); - }); - - it("Should run handleCheckChange if selectingInitiatesLoad is false and handleInputChange is triggered", async () => { - // Act - const wrapper = shallowMount(listCard, { - propsData: { - selectingInitiatesLoad: false, - }, - }); - - // Assert - wrapper.vm.handleInputChange(); - - await nextTick(); - - expect(wrapper.vm.handleCheckChange).toBeCalled; - }); - - it("Should do nothing if isMultiSelect is true and handleKeyupArrow is triggered", async () => { - // Act - const wrapper = shallowMount(listCard, { - propsData: { - isMultiSelect: true, - }, - }); - - // Assert - wrapper.vm.handleKeyupArrow(); - - await nextTick(); - - expect(wrapper.vm.handleKeyupArrow).toHaveReturned; - }); - - it("Should run handleCheckChange if selectingInitiatesLoad is false and handleKeyupArrow is triggered", async () => { - // Act - const wrapper = shallowMount(listCard, { - propsData: { - selectingInitiatesLoad: false, - isMultiSelect: false, - }, - }); - - // Assert - wrapper.vm.handleKeyupArrow(); - - await nextTick(); - - expect(wrapper.vm.handleCheckChange).toBeCalled; - }); - - it("Should run handleChange if triggerButton is triggered", async () => { - - // Act - const wrapper = shallowMount(listCard, { - global: { - mocks: { - '$route': { query: { fmgPage: 'page-name' } }, - GaActions: GaActions, - pushEventToGA: jest.fn(), - } - }, - propsData: { - selectingInitiatesLoad: false, - }, - }); - - // Assert - wrapper.vm.triggerButton(); - - await nextTick(); - - expect(wrapper.vm.handleChange).toBeCalled; - expect(wrapper.vm.handleCheckChange).not.toBeCalled; - expect(wrapper.vm.displayLoader).not.toBeCalled; - }); - - it("Should run handleCheckChange and displayLoader if triggerButton is triggered and seletingInitiatesLoad is true", async () => { - // Act - const wrapper = shallowMount(listCard, { - global: { - mocks: { - '$route': { query: { fmgPage: 'page-name' } }, - GaActions: GaActions, - pushEventToGA: jest.fn(), - } - }, - propsData: { - selectingInitiatesLoad: true, - }, - }); - - // Assert - wrapper.vm.triggerButton(); - - await nextTick(); - - expect(wrapper.vm.handleCheckChange).toBeCalled; - expect(wrapper.vm.displayLoader).toBeCalled; - }); - }); diff --git a/src/ux-components/list-card/list-card.vue b/src/ux-components/list-card/list-card.vue index c0c2a71c9..7e27a6476 100644 --- a/src/ux-components/list-card/list-card.vue +++ b/src/ux-components/list-card/list-card.vue @@ -1,400 +1,252 @@ diff --git a/src/ux-components/loader/loader.vue b/src/ux-components/loader/loader.vue index 9b3473eb1..1fbd2c6c1 100644 --- a/src/ux-components/loader/loader.vue +++ b/src/ux-components/loader/loader.vue @@ -27,6 +27,7 @@ export default {