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 27778feed..cc6d9011f 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 = [{ location: "Windshield", name: "Single" }, { location: "Driver", name: "Back" }, { location: "Passenger", name: "Quarter" }, { location: "Rear", name: "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 f729b8b1a..86435e8e4 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.location === damageLocationsSelected.WINDSHIELD && glass.name === 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.location === damageLocationsSelected.REAR){ - rearReplaceOptions.push(glass.name); - } - }); + + getRearReplaceOptionsFromStore() { + var rearReplaceOptions = store.getters.damage.glassToReplace?.filter(glass => glass.location === damageLocationsSelected.REAR)[0]?.name; 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({ location: damageLocationsSelected.REAR, name: rearItem}); - }) + selectedGlassToReplace.push({ location: damageLocationsSelected.REAR, name: 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 2acf1d87d..c4b2bbd22 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 8154f2ca4..79d9d147d 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="`${location}-${name}`" - @isCheckedChanged="ResetTintAndPartSelections" - :validationRules="tintValidationRules" - > + :validationRules="tintValidationRules">
+ />
@@ -64,7 +62,10 @@ export default { location: 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.location}-${this.name}-tint-required`; - defineRule(validationRuleName, required(errorMessages.OPTION_REQUIRED)); + + defineRule( + validationRuleName, + required(errorMessages.OPTION_REQUIRED) + ); + return validationRuleName; }, partValidationRules() { const validationRuleName = `${this.location}-${this.name}-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.location, 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(dataForlocationAndName => - dataForlocationAndName.name == this.name && - dataForlocationAndName.location == this.location); - const matchingGlassParts = matchingGlass?.length == 1 ? matchingGlass[0].parts : []; - return matchingGlassParts.filter(part => part.color == this.selectedTint) ?? []; + const matchingGlass = this.PartDataFromApi.partsOrQuestions?.filter( + (dataForGlassLocationAndName) => + dataForGlassLocationAndName.name == this.name && + dataForGlassLocationAndName.location == + this.location + ); + 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 3f54b300c..c736701da 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 2c4583685..56c1b9da0 100644 --- a/src/layouts/vehicle-parts/vehicle-parts.vue +++ b/src/layouts/vehicle-parts/vehicle-parts.vue @@ -1,52 +1,45 @@ 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 {