diff --git a/src/App.vue b/src/App.vue index a561a4fa..eceef56a 100644 --- a/src/App.vue +++ b/src/App.vue @@ -13,4 +13,5 @@ @import "@/styles/common-typography-styles.scss"; @import "@/styles/common-error-styles.scss"; @import "@/styles/common-animations.scss"; + @import "@/styles/shared-input-button-styles.scss"; 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 00000000..c0204058 --- /dev/null +++ b/src/common-components/base-input-button/base-input-button.vue @@ -0,0 +1,170 @@ + + + + + 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 00000000..1faa369e --- /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, + }, +}; diff --git a/src/common-components/button-question/button-question.spec.js b/src/common-components/button-question/button-question.spec.js index ae556efe..b958da8c 100644 --- a/src/common-components/button-question/button-question.spec.js +++ b/src/common-components/button-question/button-question.spec.js @@ -1,117 +1,1121 @@ import { shallowMount } from "@vue/test-utils"; import buttonQuestion from "@/common-components/button-question/button-question"; - -jest.mock("@/store", () => { return {}; }, { virtual: true }); +import { getMountOptions } from "@/helpers/unit-test-helper.js"; 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: { + buttonTypeString: "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: { + buttonTypeString: "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: { + buttonTypeString: "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'); - }); }); function setupMocks(mountOptionsMockData = {}) { - const defaultMountOptions = { route: { query: { issPage: 'page-name' } } }; + const defaultMountOptions = { route: { query: { issPage: "page-name" } } }; + const baseMountOptions = getMountOptions( + Object.assign(defaultMountOptions, mountOptionsMockData) + ); + const allMountOptions = Object.assign(defaultMountOptions, baseMountOptions); - return defaultMountOptions; + return allMountOptions; } diff --git a/src/common-components/button-question/button-question.vue b/src/common-components/button-question/button-question.vue index 4734ed12..1b7869fc 100644 --- a/src/common-components/button-question/button-question.vue +++ b/src/common-components/button-question/button-question.vue @@ -1,253 +1,271 @@ + - - - - - \ No newline at end of file + fieldset { + .ui-radio { + margin: 0; + } + } +} + diff --git a/src/common-components/site-footer/site-footer.vue b/src/common-components/site-footer/site-footer.vue index 2a611968..fc21f9ce 100644 --- a/src/common-components/site-footer/site-footer.vue +++ b/src/common-components/site-footer/site-footer.vue @@ -33,7 +33,7 @@ import textLink from "@/ux-components/text-link/text-link"; import buttonMain from "@/ux-components/button-main/button-main"; export default { - name: "funnelFooter", + name: "siteFooter", props: { isForwardActionDisabled: Boolean, isBackButtonHidden: { type: Boolean, default: false }, diff --git a/src/constants/damage-locations-cms.js b/src/constants/damage-locations-cms.js new file mode 100644 index 00000000..82a43175 --- /dev/null +++ b/src/constants/damage-locations-cms.js @@ -0,0 +1,9 @@ +const damageLocationsCms = { + WINDSHIELD: "WINDSHIELD", + SIDEDOOR: "SIDEDOOR", + REARWINDOW: "REARWINDOW", + DRIVERSIDE: "DRIVERSIDE", + PASSENGERSIDE: "PASSENGERSIDE", +}; + +export { damageLocationsCms }; diff --git a/src/constants/damage-locations-selected.js b/src/constants/damage-locations-selected.js new file mode 100644 index 00000000..7ede615c --- /dev/null +++ b/src/constants/damage-locations-selected.js @@ -0,0 +1,21 @@ +const damageLocationsSelected = { + WINDSHIELD: "Windshield", + SIDEDOOR: "SideDoor", + REARWINDOW: "RearWindow", + REPAIR: "Repair", + REPLACE: "Replace", + DRIVER: "Driver", + PASSENGER: "Passenger", + FRONT: "Front", + REAR: "Rear", + BACK: "Back", + QUARTER: "Quarter", + VENT: "Vent", + SINGLE: "Single", + DRIVERSIDE: "DriverSide", + PASSENGERSIDE: "PassengerSide", + STATIONARY: "Stationary", + SLIDER: "Slider", +}; + +export { damageLocationsSelected }; diff --git a/src/constants/endpoints.js b/src/constants/endpoints.js index 5a80175f..3aa3ce24 100644 --- a/src/constants/endpoints.js +++ b/src/constants/endpoints.js @@ -27,6 +27,10 @@ const endpoints = { url: "/vehicle/api/v1/vehicle/styles", method: "GET", }, + GetDamageOptions: { + url: "/parts/api/v1/parts/damage-options", + method: "GET", + }, GetVehicle: { url: "/vehicle/api/v1/vehicle/lookup", method: "GET", diff --git a/src/global-methods.js b/src/global-methods.js index ce66babb..3f7e75b8 100644 --- a/src/global-methods.js +++ b/src/global-methods.js @@ -33,7 +33,6 @@ export default { true ); } - return resolve(response); }, error => { diff --git a/src/helpers/button-question-focus-helper.js b/src/helpers/button-question-focus-helper.js new file mode 100644 index 00000000..427f2546 --- /dev/null +++ b/src/helpers/button-question-focus-helper.js @@ -0,0 +1,38 @@ +/** + * 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 }; + \ No newline at end of file diff --git a/src/layouts/vehicle-damage/damage-location-question/damage-location-question.vue b/src/layouts/vehicle-damage/damage-location-question/damage-location-question.vue new file mode 100644 index 00000000..b015907a --- /dev/null +++ b/src/layouts/vehicle-damage/damage-location-question/damage-location-question.vue @@ -0,0 +1,86 @@ + + + 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 new file mode 100644 index 00000000..e7884c9e --- /dev/null +++ b/src/layouts/vehicle-damage/replace-options-question/replace-options-question.vue @@ -0,0 +1,108 @@ + + + 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 new file mode 100644 index 00000000..5a44ccfb --- /dev/null +++ b/src/layouts/vehicle-damage/side-door-options/side-door-options.vue @@ -0,0 +1,167 @@ + + + diff --git a/src/layouts/vehicle-damage/vehicle-damage.vue b/src/layouts/vehicle-damage/vehicle-damage.vue new file mode 100644 index 00000000..3d5fcfe0 --- /dev/null +++ b/src/layouts/vehicle-damage/vehicle-damage.vue @@ -0,0 +1,464 @@ + + + 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 new file mode 100644 index 00000000..48b813f2 --- /dev/null +++ b/src/layouts/vehicle-damage/windshield-options/windshield-chip-count-question/windshield-chip-count-question.vue @@ -0,0 +1,50 @@ + + + diff --git a/src/layouts/vehicle-damage/windshield-options/windshield-damage-type-question/windshield-damage-type-question.vue b/src/layouts/vehicle-damage/windshield-options/windshield-damage-type-question/windshield-damage-type-question.vue new file mode 100644 index 00000000..01e14608 --- /dev/null +++ b/src/layouts/vehicle-damage/windshield-options/windshield-damage-type-question/windshield-damage-type-question.vue @@ -0,0 +1,50 @@ + + + diff --git a/src/layouts/vehicle-damage/windshield-options/windshield-options.vue b/src/layouts/vehicle-damage/windshield-options/windshield-options.vue new file mode 100644 index 00000000..b878e538 --- /dev/null +++ b/src/layouts/vehicle-damage/windshield-options/windshield-options.vue @@ -0,0 +1,238 @@ + + + diff --git a/src/layouts/vehicle-style/vehicle-style.vue b/src/layouts/vehicle-style/vehicle-style.vue index 1ce939af..4c48f960 100644 --- a/src/layouts/vehicle-style/vehicle-style.vue +++ b/src/layouts/vehicle-style/vehicle-style.vue @@ -7,6 +7,7 @@
diff --git a/src/mixins/base-mixin.js b/src/mixins/base-mixin.js index e003e989..32134711 100644 --- a/src/mixins/base-mixin.js +++ b/src/mixins/base-mixin.js @@ -5,6 +5,7 @@ import { navigationScenarios } from "@/router/router-constants/navigation-scenar import { vehicleCategories } from "@/constants/vehicle-categories.js"; import { queryStrings } from "@/constants/query-strings"; import { dynamicStrings } from "@/constants/dynamic-strings"; +import { routerParams } from "@/router/router-constants/router-params"; export default { data() { @@ -43,5 +44,8 @@ export default { cssClassNameForCmsWidget(){ return "widget-name-" + this.cmsWidgetName; }, + routerParams() { + return routerParams; + }, }, }; \ No newline at end of file diff --git a/src/mixins/input-button-wrapper-mixin.js b/src/mixins/input-button-wrapper-mixin.js new file mode 100644 index 00000000..1d34d652 --- /dev/null +++ b/src/mixins/input-button-wrapper-mixin.js @@ -0,0 +1,40 @@ +import { inputButtonProps } from "@/common-components/base-input-button/button-functionality-props"; + +export default { + model: { + prop: "modelValue", + event: "change", + }, + props: { + ...inputButtonProps, + buttonLabel: [Number, String], + buttonLabelSubCopy: String, + buttonBodyCopy: String, + buttonAuxillaryCopy: String, + buttonFooterCopy: String, + buttonImage: String, + buttonImageId: String, + altText: { + type: String, + default: "", + }, + textPosition: String, + screenReaderOnlyText: String, + isWide: Boolean, + additionalButtonStyling: String, + }, + computed: { + selectedValue: { + get() { + return this.modelValue; + }, + set(e) { + if (this.preHandleAnswerChange) { + this.preHandleAnswerChange(e); + } + + this.$emit("update:modelValue", e); + }, + }, + }, +}; diff --git a/src/router/router-constants/routing-table.js b/src/router/router-constants/routing-table.js index 4263b29c..717ef11e 100644 --- a/src/router/router-constants/routing-table.js +++ b/src/router/router-constants/routing-table.js @@ -52,10 +52,27 @@ const routingTable = function(store) { }, { scenario: navigationScenarios.SELECTED_STYLE, - destinationIssPageValue: issPageValues.WELCOME_PAGE, + destinationIssPageValue: issPageValues.VEHICLE_DAMAGE, } ] }, + { + issPageValue: issPageValues.VEHICLE_DAMAGE, + maps: [ + { + scenario: navigationScenarios.CLICKED_BACK, + destinationIssPageValue: issPageValues.VEHICLE_STYLE, + }, + { + scenario: navigationScenarios.CLICKED_FORWARD_WITH_VIN, + destinationIssPageValue: issPageValues.WELCOME_PAGE, + }, + { + scenario: navigationScenarios.CLICKED_FORWARD_WITHOUT_VIN, + destinationIssPageValue: issPageValues.WELCOME_PAGE, + }, + ], + }, { issPageValue: issPageValues.WELCOME_PAGE, maps: [ diff --git a/src/router/router-params.js b/src/router/router-params.js new file mode 100644 index 00000000..a8ee17b9 --- /dev/null +++ b/src/router/router-params.js @@ -0,0 +1,5 @@ +const routerParams = { + DISPLAY_VEHICLE_CHANGE_ALERT: "displayVehicleChangeAlert", +}; + +export { routerParams }; \ No newline at end of file diff --git a/src/store/index.js b/src/store/index.js index de4c65ac..91d82140 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -30,6 +30,14 @@ const getDefaultState = () => { lastName: null, }, }, + damage: { + isRepair: null, + numberOfChips: null, + glassToReplace: null, + partQuestionAnswers: null, + moldingQuestionAnswers: null, + capabilityQuestionAnswers: null, + }, referralNumber: null, referralDate: null, accountNumber: 0, @@ -56,6 +64,8 @@ export const useMainStore = defineStore({ id: storeId, state: () => state, getters: { + vehicle: (state) => state.order.vehicle, + damage: (state) => state.order.damage, eventBusItem: (state) => ( eventCategory, eventSubCategory) => { const matchedEvent = state.applicationUser.eventBus.find( @@ -190,6 +200,13 @@ export const useMainStore = defineStore({ }); }, + getDamageOptions(carId) { + return globalMethods.callHttpClient({ + methods: endpoints.GetDamageOptions.method, + endpoint: `${endpoints.GetDamageOptions.url}/${carId}`, + payload: {}, + }); + }, setVehicle() { return globalMethods .callHttpClient({ @@ -204,7 +221,11 @@ export const useMainStore = defineStore({ }, updateVehicle(vehicle) { - this.order.vehicle = { ...this.order.vehicle, ...vehicle }; + this.order.vehicle.carId = vehicle.carId; + this.order.vehicle.category = vehicle.category; + this.order.vehicle.imageUrl = vehicle.imageUrl; + this.order.vehicle.imageVifNumber = vehicle.imageVifNumber; + this.order.vehicle.imageColor = vehicle.imageColor; }, resetVehicleState() diff --git a/src/store/store.spec.js b/src/store/store.spec.js index a1e6e2f5..5bf8bbad 100644 --- a/src/store/store.spec.js +++ b/src/store/store.spec.js @@ -17,7 +17,7 @@ describe("Store", () => { store = useMainStore(); store.applicationUser.eventBus = []; jest.resetAllMocks(); - }) + }); it("Should Store Vehicle Year", () => { @@ -114,7 +114,7 @@ describe("Store", () => { expect(store.order.vehicle.imageUrl).toEqual(response.data.imageUrl); expect(store.order.vehicle.imageVifNumber).toEqual(response.data.imageVifNumber); - expect(store.order.vehicle.imageVifColor).toEqual(response.data.imageVifColor); + expect(store.order.vehicle.style).toEqual(response.data.style); }); it("setVehicle should call globalMethods.callHttpClient", () => { diff --git a/src/styles/mixins/customMixins.scss b/src/styles/mixins/customMixins.scss index 3576a8ac..cd3555cc 100644 --- a/src/styles/mixins/customMixins.scss +++ b/src/styles/mixins/customMixins.scss @@ -4,3 +4,7 @@ @mixin blue-gradient { background: linear-gradient(270deg, $blue 0%, $blue-800 100%); } + +@mixin box-shadow-hover($color) { + box-shadow: 0 0 0 4px $color; +} diff --git a/src/styles/shared-input-button-styles.scss b/src/styles/shared-input-button-styles.scss new file mode 100644 index 00000000..dbffa5b0 --- /dev/null +++ b/src/styles/shared-input-button-styles.scss @@ -0,0 +1,15 @@ +.base-input-button { + &:not(.has-error):hover { + cursor: pointer; + + &.list-button, + &.list-button-horizontal, + &.list-card { + &:not(.selected) { + position: relative; + z-index: 4; + @include box-shadow-hover($blue-300); + } + } + } +} diff --git a/src/styles/ux-variables.scss b/src/styles/ux-variables.scss index 52cf8916..d9b462af 100644 --- a/src/styles/ux-variables.scss +++ b/src/styles/ux-variables.scss @@ -1,186 +1,189 @@ //Colors // White/black -$white: #FFFFFF; -$black: #000000; +$white: #ffffff; +$black: #000000; // Blues -$blue-100: #E4F1F7;// Used in theme -$blue-200: #C1DFEE; -$blue-300: #9FCEE6; -$blue-400: #69ADCF;// Used in theme -$blue-500: #3B8FB8; -$blue: #1574A1;// Default Blue -$blue-700: #06577C; -$blue-800: #003D58; -$blue-900: #002433;// Used in theme +$blue-100: #e4f1f7; // Used in theme +$blue-200: #c1dfee; +$blue-300: #9fcee6; +$blue-400: #69adcf; // Used in theme +$blue-500: #3b8fb8; +$blue: #1574a1; // Default Blue +$blue-700: #06577c; +$blue-800: #003d58; +$blue-900: #002433; // Used in theme // Reds -$red-100: #FFE6E4; -$red-200: #FCBFBB; -$red-300: #F89892; -$red-400: #E65C53; -$red: #D4281C;// Default Red -$red-600: #AC160B; -$red-700: #840900; -$red-800: #5B0600; -$red-900: #330300; +$red-100: #ffe6e4; +$red-200: #fcbfbb; +$red-300: #f89892; +$red-400: #e65c53; +$red: #d4281c; // Default Red +$red-600: #ac160b; +$red-700: #840900; +$red-800: #5b0600; +$red-900: #330300; // Greens -$green-100: #E3F2EA; -$green-200: #BCEDD4; -$green-300: #94D7B6; -$green-400: #5ABC8C;// Used in theme -$green-500: #2CA168; -$green: #0C7E47;// Default Green -$green-700: #006A36; -$green-800: #004F28; -$green-900: #00331A; +$green-100: #e3f2ea; +$green-200: #bcedd4; +$green-300: #94d7b6; +$green-400: #5abc8c; // Used in theme +$green-500: #2ca168; +$green: #0c7e47; // Default Green +$green-700: #006a36; +$green-800: #004f28; +$green-900: #00331a; // Yellows -$yellow-100: #FFF5EB; -$yellow-200: #FFE1C6; -$yellow-300: #FFCAA0; -$yellow-400: #F5975D; -$yellow: #E86421;// Default Yellow -$yellow-600: #BB4B06; -$yellow-700: #8E3C00; -$yellow-800: #602D00; -$yellow-900: #331A00; +$yellow-100: #fff5eb; +$yellow-200: #ffe1c6; +$yellow-300: #ffcaa0; +$yellow-400: #f5975d; +$yellow: #e86421; // Default Yellow +$yellow-600: #bb4b06; +$yellow-700: #8e3c00; +$yellow-800: #602d00; +$yellow-900: #331a00; // Grays -$gray-100: #F5F5F5;// Used in theme -$gray-200: #E3E4E4;// Used in theme -$gray-300: #D2D4D4; -$gray: #B0B3B3;// Default Gray -$gray-500: #8E9292;// Used in theme -$gray-550: #727676;// Used in theme -$gray-600: #4D5151;// Used in theme -$gray-700: #303333;// Used in theme -$gray-800: #222424;// Used in theme -$gray-900: #181A1A;// Used in theme +$gray-100: #f5f5f5; // Used in theme +$gray-200: #e3e4e4; // Used in theme +$gray-300: #d2d4d4; +$gray: #b0b3b3; // Default Gray +$gray-500: #8e9292; // Used in theme +$gray-550: #727676; // Used in theme +$gray-600: #4d5151; // Used in theme +$gray-700: #303333; // Used in theme +$gray-800: #222424; // Used in theme +$gray-900: #181a1a; // Used in theme // Miscellaneous Colors -$indigo: #6610f2; -$purple: #6f42c1; -$pink: #d63384; -$orange: #fd7e14; -$teal: #20c997; -$cyan: #0dcaf0; +$indigo: #6610f2; +$purple: #6f42c1; +$pink: #d63384; +$orange: #fd7e14; +$teal: #20c997; +$cyan: #0dcaf0; // scss-docs-start colors-map $colors: ( -"blue": $blue, -"indigo": $indigo, -"purple": $purple, -"pink": $pink, -"red": $red, -"orange": $orange, -"yellow": $yellow, -"green": $green, -"teal": $teal, -"cyan": $cyan, -"white": $white, -"gray": $gray, -"gray-dark": $gray-500 + "blue": $blue, + "indigo": $indigo, + "purple": $purple, + "pink": $pink, + "red": $red, + "orange": $orange, + "yellow": $yellow, + "green": $green, + "teal": $teal, + "cyan": $cyan, + "white": $white, + "gray": $gray, + "gray-dark": $gray-500, ); // scss-docs-start theme-color-variables -$primary: $blue; -$secondary: $red; -$success: $green; -$info: $cyan; -$warning: $yellow; -$danger: $red; -$light: $gray-100; -$dark: $gray-500; +$primary: $blue; +$secondary: $red; +$success: $green; +$info: $cyan; +$warning: $yellow; +$danger: $red; +$light: $gray-100; +$dark: $gray-500; // scss-docs-start theme-colors-map $theme-colors: ( -"primary": $primary, -"secondary": $secondary, -"success": $success, -"info": $info, -"warning": $warning, -"danger": $danger, -"light": $light, -"dark": $dark + "primary": $primary, + "secondary": $secondary, + "success": $success, + "info": $info, + "warning": $warning, + "danger": $danger, + "light": $light, + "dark": $dark, ); //Default font color -$body-color: $gray-600; +$body-color: $gray-600; //Fonts -$font-family-sans-serif: Roboto, Arial, Helvetica, sans-serif; -$font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; +$font-family-sans-serif: Roboto, Arial, Helvetica, sans-serif; +$font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", + monospace; // stylelint-enable value-keyword-case -$font-family-base: $font-family-sans-serif; -$font-family-code: $font-family-monospace; -$font-size-base: 1rem; // Assumes the browser default, typically `16px` +$font-family-base: $font-family-sans-serif; +$font-family-code: $font-family-monospace; +$font-size-base: 1rem; // Assumes the browser default, typically `16px` //Custom Font size (extra small) -$font-size-xsm: $font-size-base * .75; +$font-size-xsm: $font-size-base * 0.75; $font-sizes: ( - 7: $font-size-xsm + 7: $font-size-xsm, ); //Font weight -$font-weight-lighter: lighter; -$font-weight-light: 300; -$font-weight-normal: 400; -$font-weight-bold: 500; -$font-weight-bolder: bolder; +$font-weight-lighter: lighter; +$font-weight-light: 300; +$font-weight-normal: 400; +$font-weight-bold: 500; +$font-weight-bolder: bolder; //Headings -$h1-font-size: $font-size-base * 3; -$h2-font-size: $font-size-base * 2.625; -$h3-font-size: $font-size-base * 2; -$h4-font-size: $font-size-base * 1.625; -$h5-font-size: $font-size-base * 1.25; -$h6-font-size: $font-size-base * .875; +$h1-font-size: $font-size-base * 3; +$h2-font-size: $font-size-base * 2.625; +$h3-font-size: $font-size-base * 2; +$h4-font-size: $font-size-base * 1.625; +$h5-font-size: $font-size-base * 1.25; +$h6-font-size: $font-size-base * 0.875; //Border Radius // Helper classes are rounded, rounded-1, rounded-2, rounded-3 -$border-radius: .25rem; -$border-radius-sm: .2rem; -$border-radius-lg: .5rem;//Used for buttons. Can be used for other things, of course. -$border-radius-pill: 50rem; +$border-radius: 0.25rem; +$border-radius-sm: 0.2rem; +$border-radius-lg: 0.5rem; //Used for buttons. Can be used for other things, of course. +$border-radius-pill: 50rem; //Spacing // 8 spacers available instead of the usual 5 $spacer: 1rem; $spacers: ( - 0: 0, - 1: $spacer * .25, /* 4px */ - 2: $spacer * .5, /* 8px */ - 3: $spacer * .75, /* 12px */ - 4: $spacer * 1, /* 16px */ - 5: $spacer * 1.5, /* 24px */ - 6: $spacer * 2, /* 32px */ - 7: $spacer * 2.5, /* 40px */ - 8: $spacer * 3, /* 48px */ + 0: 0, + 1: $spacer * 0.25, + /* 4px */ 2: $spacer * 0.5, + /* 8px */ 3: $spacer * 0.75, + /* 12px */ 4: $spacer * 1, + /* 16px */ 5: $spacer * 1.5, + /* 24px */ 6: $spacer * 2, + /* 32px */ 7: $spacer * 2.5, + /* 40px */ 8: $spacer * 3, + /* 48px */ ); //Grid breakpoints $grid-breakpoints: ( - xs: 0, - sm: 576px, - md: 838px, - lg: 1074px, - xl: 1416px + xs: 0, + sm: 576px, + md: 838px, + lg: 1074px, + xl: 1416px, ); //Shadow -$box-shadow: 0 .5rem 1rem rgba($black, .15); -$box-shadow-sm: 0 .125rem .25rem rgba($black, .075); -$box-shadow-lg: 0 1rem 3rem rgba($black, .25);//Safelite default -$box-shadow-inset: inset 0 1px 2px rgba($black, .075); +$box-shadow: 0 0.5rem 1rem rgba($black, 0.15); +$box-shadow-sm: 0 0.125rem 0.25rem rgba($black, 0.075); +$box-shadow-lg: 0 1rem 3rem rgba($black, 0.25); //Safelite default +$box-shadow-inset: inset 0 1px 2px rgba($black, 0.075); //Alerts -$alert-bg-scale: -90%; -$alert-border-scale: -100%; -$alert-color-scale: 40%; +$alert-bg-scale: -90%; +$alert-border-scale: -100%; +$alert-color-scale: 40%; //Modal animation -$modal-fade-transform: translate(0, 0); -$modal-backdrop-opacity: 0; +// This affects all [Bootstrap] modals +$modal-fade-transform: translate(0, 100%); +$modal-backdrop-opacity: 0; diff --git a/src/ux-components/list-button-horizontal/list-button-horizontal.spec.js b/src/ux-components/list-button-horizontal/list-button-horizontal.spec.js index 0d51bf57..43271781 100644 --- a/src/ux-components/list-button-horizontal/list-button-horizontal.spec.js +++ b/src/ux-components/list-button-horizontal/list-button-horizontal.spec.js @@ -1,264 +1,139 @@ -import { shallowMount } from "@vue/test-utils"; +import { mount } from "@vue/test-utils"; import listButtonHorizontal from "./list-button-horizontal"; -import { nextTick } from "vue"; +import inputButtonWrapperMixin from "@/mixins/input-button-wrapper-mixin"; describe("list-button-horizontal.vue", () => { - it("Should return input type checkbox if isMultiSelect is true", async () => { - // Act - const wrapper = shallowMount(listButtonHorizontal, { - propsData: { - isMultiSelect: true, - }, + describe("styling/UI", () => { + it("Should return screen reader text", async () => { + // Act + const { wrapper } = setupMocks({ + mockData: { + propsData: { + screenReaderOnlyText: "Screen Reader Only Text", + }, + }, + }); + + // Assert + const paragraph = wrapper.find("span.sr-only"); + + expect(paragraph.text()).toEqual("Screen Reader Only Text"); + }); + + it("Should return text alignment class", async () => { + // Act + const { wrapper } = setupMocks({ + mockData: { + propsData: { + textPosition: "text-center", + }, + }, + }); + + // Assert + const paragraph = wrapper.find("span.m-0"); + + expect(paragraph.attributes("class")).toContain("text-center"); + }); + + it("Should return aria-required state", async () => { + // Act + const { wrapper } = setupMocks({ + mockData: { + propsData: { + isRequired: true, + }, + }, + }); + + // Assert + const input = wrapper.find("input"); + + expect(input.attributes()["aria-required"]).toEqual("true"); + }); + + it("is cash or insurance button => has 'strong' class", () => { + // Arrange/Act + const { wrapper } = setupMocks({ + mockData: { + propsData: { + additionalButtonStyling: "listButtonHorizontalStrong", + }, + }, + }); + + // Assert + const label = wrapper.find("label"); + + expect(label.classes()).toContain("strong"); + }); + + test("has buttonLabel => displays buttonLabel", () => { + // Arrange/Act + const { wrapper } = setupMocks({ + mockData: { + propsData: { + buttonLabel: "Surprise!", + }, + }, + }); + + // Assert + const content = wrapper.find(".list-button-horizontal-content"); + expect(content.isVisible()).toBe(true); + expect(content.text()).toContain("Surprise!"); + }); + + test("has buttonLabelSubCopy => displays buttonLabelSubCopy", () => { + // Arrange/Act + const { wrapper } = setupMocks({ + mockData: { + propsData: { + buttonLabel: "Surprise!", + buttonLabelSubCopy: "Super duper surprise :)", + }, + }, + }); + + // Assert + const content = wrapper.find(".list-button-horizontal-content"); + expect(content.isVisible()).toBe(true); + expect(content.text()).toContain("Super duper surprise :)"); + }); + + test("has screenReaderOnlyText => displays screenReaderOnlyText", () => { + // Arrange/Act + const { wrapper } = setupMocks({ + mockData: { + propsData: { + buttonLabel: "Surprise!", + buttonLabelSubCopy: "Super duper surprise :)", + screenReaderOnlyText: "Tests are fun!", + }, + }, + }); + + // Assert + const content = wrapper.find(".list-button-horizontal-content"); + const screenReaderOnlyText = wrapper.find(".sr-only"); + expect(content.isVisible()).toBe(true); + expect(screenReaderOnlyText.exists()).toBe(true); + expect(screenReaderOnlyText.text()).toContain("Tests are fun!"); + }); }); - - // Assert - const input = wrapper.find("input"); - - expect(input.attributes().type).toEqual("checkbox"); - }); - - it("Should return input type radio if isMultiSelect is false or not specified", async () => { - // Act - const wrapper = shallowMount(listButtonHorizontal, { - propsData: { - isMultiSelect: false, - }, - }); - - // Assert - const input = wrapper.find("input"); - - expect(input.attributes().type).toEqual("radio"); - }); - - it("Should return primary label text (buttonID)", async () => { - // Act - const wrapper = shallowMount(listButtonHorizontal, { - propsData: { - buttonID: "List Card Checkbox", - }, - }); - - // Assert - const label = wrapper.find("label"); - - expect(label.attributes().for).toEqual("List Card Checkbox"); - }); - - it("Should return screen reader text", async () => { - // Act - const wrapper = shallowMount(listButtonHorizontal, { - propsData: { - screenReaderOnlyText: "Screen Reader Only Text", - }, - }); - - // Assert - const paragraph = wrapper.find("span.sr-only"); - - expect(paragraph.text()).toEqual("Screen Reader Only Text"); - }); - - it("Should return text alignment class", async () => { - // Act - const wrapper = shallowMount(listButtonHorizontal, { - propsData: { - textPosition: "text-center", - }, - }); - - // Assert - const paragraph = wrapper.find("span.m-0"); - - expect(paragraph.attributes("class")).toContain("text-center"); - }); - - it("Should return aria-required state", async () => { - // Act - const wrapper = shallowMount(listButtonHorizontal, { - propsData: { - isRequired: true, - }, - }); - - // Assert - const input = wrapper.find("input"); - - expect(input.attributes()["aria-required"]).toEqual("true"); - }); - - it("Should return loader enabled true", async () => { - // Act - const wrapper = shallowMount(listButtonHorizontal, { - global: { - mocks: { - '$route': { query: { issPage: 'page-name' } }, - } - }, - propsData: { - selectingInitiatesLoad: true, - }, - }); - - // Assert - - const label = wrapper.find("label"); - - wrapper.vm.handleCheckChange = jest.fn(); - wrapper.vm.triggerButton(); - - await nextTick(); - - const loader = wrapper.find("loader-stub"); - - expect(loader.exists()).toBe(true); - }); - - it("Should return loader color", async () => { - // Act - const wrapper = shallowMount(listButtonHorizontal, { - global: { - mocks: { - '$route': { query: { issPage: 'page-name' } }, - } - }, - propsData: { - loaderColor: "blue", - selectingInitiatesLoad: true, - }, - }); - - // Assert - - const label = wrapper.find("label"); - - wrapper.vm.handleCheckChange = jest.fn(); - wrapper.vm.triggerButton(); - - await nextTick(); - - const loader = wrapper.find("loader-stub"); - - expect(loader.attributes("class")).toContain("blue"); - }); - - it("Should return loader position", async () => { - // Act - const wrapper = shallowMount(listButtonHorizontal, { - global: { - mocks: { - '$route': { query: { issPage: 'page-name' } }, - } - }, - propsData: { - loaderPosition: "right", - selectingInitiatesLoad: true, - }, - }); - - // Assert - - const label = wrapper.find("label"); - - wrapper.vm.handleCheckChange = jest.fn(); - wrapper.vm.triggerButton(); - - await nextTick(); - - const loader = wrapper.find("loader-stub"); - - expect(loader.attributes("class")).toContain("right"); - }); - - it("Should emit button value on click", async () => { - // Act - const wrapper = shallowMount(listButtonHorizontal, { - propsData: { - isRadioHorizontal: true, - buttonLabel: "Windshield", - value: "List Card Checkbox", - groupID: "radio-demo-1", - groupName: "radio 1", - buttonImage: "windshield-damage.svg", - isRequired: true, - isWide: false, - modelValue: ["List Card Checkbox"], - }, - }); - wrapper.vm.handleCheckChange(); - // Assert - expect(wrapper.emitted()["isCheckedChanged"][0]).toEqual([{value: "List Card Checkbox", checkValue: Boolean, buttonId: undefined, checkValue: false}]); - }); - - it("Should set checkValue data if selectedButtonIDs has value(s)", async () => { - // Act - const wrapper = shallowMount(listButtonHorizontal, { - propsData: { - isRadioHorizontal: true, - buttonLabel: "Windshield", - buttonID: "List Card Checkbox", - groupID: "radio-demo-1", - groupName: "radio 1", - buttonImage: "windshield-damage.svg", - isRequired: true, - isWide: false, - modelValue: ["List Card Checkbox"], - isMultiSelect: false, - value: "Car-Front", - selectedValues: ["Car-Front"] - }, - }); - // Assert - expect(wrapper.vm.checkValue).toEqual(true); - }); - - it("Should run handleCheckChange if selectingInitiatesLoad is false and handleInputChange is triggered", async () => { - // Act - const wrapper = shallowMount(listButtonHorizontal, { - propsData: { - selectingInitiatesLoad: false, - }, - }); - - // Assert - wrapper.vm.handleInputChange(); - - await nextTick(); - - expect(wrapper.vm.handleCheckChange).toBeCalled; - }); - - it("Should do nothing if isMultiSelect is true and handleKeyupArrow is triggered", async () => { - // Act - const wrapper = shallowMount(listButtonHorizontal, { - propsData: { - isMultiSelect: true, - }, - }); - - // Assert - wrapper.vm.handleKeyupArrow(); - - await nextTick(); - - expect(wrapper.vm.handleKeyupArrow).toHaveReturned; - }); - - it("Should run handleCheckChange if selectingInitiatesLoad is false and handleKeyupArrow is triggered", async () => { - // Act - const wrapper = shallowMount(listButtonHorizontal, { - propsData: { - selectingInitiatesLoad: false, - isMultiSelect: false, - }, - }); - - // Assert - wrapper.vm.handleKeyupArrow(); - - await nextTick(); - - expect(wrapper.vm.handleCheckChange).toBeCalled; - }); - }); + +function setupMocks({ mockData }) { + const wrapper = mount(listButtonHorizontal, { + ...mockData, + propsData: { + ...mockData.propsData, + groupName: "my-group", + modelValue: mockData.propsData?.isMultiSelect ? ["5"] : "5", + value: mockData.propsData?.isMultiSelect ? ["4"] : "4", + }, + mixins: [inputButtonWrapperMixin], + }); + + return { wrapper }; +} diff --git a/src/ux-components/list-button-horizontal/list-button-horizontal.vue b/src/ux-components/list-button-horizontal/list-button-horizontal.vue index 534448b7..e51d9ecd 100644 --- a/src/ux-components/list-button-horizontal/list-button-horizontal.vue +++ b/src/ux-components/list-button-horizontal/list-button-horizontal.vue @@ -1,206 +1,84 @@ - - - - - \ No newline at end of file + + //Cash/insurance styling + &:first-of-type { + .list-button-horizontal.strong { + input[type="radio"] { + &:checked + .list-button-horizontal-content { + border-bottom-right-radius: 0; + border-top-right-radius: 0; + } + + &:checked:focus + .list-button-horizontal-content { + border-bottom-right-radius: 0.5rem; + border-top-right-radius: 0.5rem; + } + } + } + } + + &:last-of-type { + .list-button-horizontal.strong { + input[type="radio"] { + &:checked + .list-button-horizontal-content { + border-bottom-left-radius: 0; + border-top-left-radius: 0; + } + + &:checked:focus + .list-button-horizontal-content { + border-bottom-left-radius: 0.5rem; + border-top-left-radius: 0.5rem; + } + } + } + } +} + diff --git a/src/ux-components/list-button/list-button.spec.js b/src/ux-components/list-button/list-button.spec.js index e54710ed..3b7d267d 100644 --- a/src/ux-components/list-button/list-button.spec.js +++ b/src/ux-components/list-button/list-button.spec.js @@ -1,256 +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: { issPage: 'page-name' } }, - } - }, - 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: { issPage: 'page-name' } }, - } - }, - 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: { issPage: 'page-name' } }, - } - }, - 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 c789060e..d6770b1a 100644 --- a/src/ux-components/list-button/list-button.vue +++ b/src/ux-components/list-button/list-button.vue @@ -1,241 +1,114 @@ - - + + - \ No newline at end of file +} +.list-button-content { + color: $gray-600; + position: relative; + background: $white; + transition: all 150ms linear; + border-radius: $border-radius-lg; + border: 1px solid $gray-500; + width: 100%; + outline: none; + + span { + &.small { + font-size: 0.75rem; + color: $gray-550; + } + } +} + diff --git a/src/ux-components/list-card/list-card.spec.js b/src/ux-components/list-card/list-card.spec.js index 9c9e1986..25d4a353 100644 --- a/src/ux-components/list-card/list-card.spec.js +++ b/src/ux-components/list-card/list-card.spec.js @@ -1,329 +1,179 @@ -import { shallowMount } from "@vue/test-utils"; +import { mount } from "@vue/test-utils"; import listCard from "./list-card"; import { nextTick } from "vue"; describe("list-card.vue", () => { - it("Should return input type checkbox if isMultiSelect is true", async () => { - // Act - const wrapper = shallowMount(listCard, { - propsData: { - isMultiSelect: true, - buttonLabel: "Windshield", - buttonID: "List Card Checkbox", - groupID: "checkbox-demo-1", - groupName: "Checkbox 1", - buttonImage: "windshield-damage.svg", - }, - }); + it("Should return input type checkbox if isMultiSelect is true", () => { + // Act + const wrapper = mount(listCard, { + propsData: { + isMultiSelect: true, + buttonLabel: "Windshield", + buttonID: "List Card Checkbox", + groupID: "checkbox-demo-1", + groupName: "Checkbox 1", + buttonImage: "windshield-damage.svg", + value: "test value", + }, + }); - // Assert - const input = wrapper.find("input"); - expect(input.attributes().type).toEqual("checkbox"); + // Assert + const input = wrapper.find("input"); + expect(input.attributes().type).toEqual("checkbox"); }); - it("Should return primary label text", async () => { - // 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", - }, - }); + it("Should return primary label text", () => { + // Act + const wrapper = mount(listCard, { + propsData: { + isRadioHorizontal: true, + buttonLabel: "Windshield", + buttonID: "List Card Checkbox", + groupID: "radio-demo-1", + groupName: "radio 1", + buttonImage: "windshield-damage.svg", + value: "test value", + }, + }); - // Assert - const paragraph = wrapper.find("p"); - expect(paragraph.text()).toEqual("Windshield"); + // Assert + const paragraph = wrapper.find("p"); + expect(paragraph.text()).toEqual("Windshield"); }); - it("Should return secondary (sub) label text", async () => { - // 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", - }, - }); + it("Should return secondary (sub) label text", () => { + // Act + const wrapper = mount(listCard, { + propsData: { + isRadioHorizontal: true, + buttonLabel: "Windshield", + buttonID: "List Card Checkbox", + groupID: "radio-demo-1", + groupName: "radio 1", + buttonImage: "windshield-damage.svg", + buttonLabelSubCopy: "Test", + value: "test value", + }, + }); - // Assert - const paragraph = wrapper.find("p:nth-of-type(2)"); - expect(paragraph.text()).toEqual("Test"); + // Assert + const paragraph = wrapper.find("p:nth-of-type(2)"); + expect(paragraph.text()).toEqual("Test"); }); - it("Should return value used for various text settings including the label 'for' and input id", async () => { - // 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", - }, - }); + it("Should return input group name used for radio or checkbox", () => { + // Act + const wrapper = mount(listCard, { + propsData: { + isRadioHorizontal: true, + buttonLabel: "Windshield", + buttonID: "List Card Checkbox", + groupID: "radio-demo-1", + groupName: "radio 1", + buttonImage: "windshield-damage.svg", + buttonLabelSubCopy: "Test", + value: "test value", + }, + }); - // Assert - const label = wrapper.find("label"); - expect(label.attributes().for).toEqual("List Card Checkbox"); + // Assert + const input = wrapper.find("input"); + expect(input.attributes().name).toEqual("radio 1"); }); - it("Should return input group name used for radio or checkbox", async () => { - // 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", - }, - }); + it("Should return aria-required state", () => { + // Act + const wrapper = mount(listCard, { + propsData: { + isRadioHorizontal: true, + buttonLabel: "Windshield", + buttonID: "List Card Checkbox", + groupID: "radio-demo-1", + groupName: "radio 1", + buttonImage: "windshield-damage.svg", + isRequired: true, + value: "test value", + }, + }); - // Assert - const input = wrapper.find("input"); - expect(input.attributes().name).toEqual("radio 1"); + // Assert + const input = wrapper.find("input"); + expect(input.attributes()["aria-required"]).toEqual("true"); }); - it("Should return aria-required state", async () => { - // 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", - isRequired: true, - }, - }); + it("Should return flex row classes if isWide is true", () => { + // Act + const wrapper = mount(listCard, { + propsData: { + isRadioHorizontal: true, + buttonLabel: "Windshield", + buttonID: "List Card Checkbox", + groupID: "radio-demo-1", + groupName: "radio 1", + buttonImage: "windshield-damage.svg", + isRequired: true, + isWide: true, + buttonLabelSubCopy: "", + value: "test value", + }, + }); - // Assert - const input = wrapper.find("input"); - expect(input.attributes()["aria-required"]).toEqual("true"); + // Assert + 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", async () => { - // 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", - isRequired: true, - isWide: true, - buttonLabelSubCopy: "", - }, - }); + it("Should return flex row classes if isWide is true and checkboxTop if buttonLabelSubCopy is provided", () => { + // Act + const wrapper = mount(listCard, { + propsData: { + isRadioHorizontal: true, + buttonLabel: "Windshield", + buttonID: "List Card Checkbox", + groupID: "radio-demo-1", + groupName: "radio 1", + buttonImage: "windshield-damage.svg", + 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"]); + // Assert + 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 row classes if isWide is true and checkboxTop if buttonLabelSubCopy is true", async () => { - // 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", - isRequired: true, - isWide: true, - buttonLabelSubCopy: "Button Subcopy", - }, - }); + it("Should return flex column classes if isWide is false", () => { + // Act + const wrapper = mount(listCard, { + propsData: { + isRadioHorizontal: true, + buttonLabel: "Windshield", + buttonID: "List Card Checkbox", + groupID: "radio-demo-1", + groupName: "radio 1", + 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-row", "py-2", "ps-4", "pe-4", "checkboxTop"]); + // Assert + 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 return flex column classes if isWide is false", async () => { - // 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", - isRequired: true, - isWide: false, - }, - }); - - // 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"]); - }); - - 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: { issPage: 'page-name' } }, - } - }, - 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: { issPage: 'page-name' } }, - } - }, - 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 eb2897b2..ed70705c 100644 --- a/src/ux-components/list-card/list-card.vue +++ b/src/ux-components/list-card/list-card.vue @@ -1,399 +1,243 @@ - - - - - \ No newline at end of file + + input[type="radio"] { + + .list-card-content::before { + content: ""; + display: none; + } + + + .list-card-content::after { + content: ""; + display: none; + } + + + .list-card-content { + img { + margin-bottom: 0; + } + } + } + + &.horizontal { + img { + margin-bottom: 0; + width: 5.5rem; + } + + input[type="checkbox"], + input[type="radio"] { + + .list-card-content::before { + content: ""; + position: relative; + margin: 0 0.5rem 0 0; + order: 1; + } + + &:checked + .list-card-content::after { + content: ""; + margin: -0.15rem 0 0 0; + left: 1.175rem; + } + + &:checked + .list-card-content { + p { + color: $black; + font-weight: 500; + } + + .sub-copy { + color: $gray-600; + font-weight: 400; + } + } + + + .list-card-content { + outline: none; + min-height: 48px; + color: $gray-600; + + img { + margin-bottom: 0; + } + + p { + color: $gray-600; + text-align: left; + + &.sub-copy { + color: $gray-550; + } + } + } + } + } +} + diff --git a/src/ux-components/loader/loader.vue b/src/ux-components/loader/loader.vue index 25d7e02d..0656b44c 100644 --- a/src/ux-components/loader/loader.vue +++ b/src/ux-components/loader/loader.vue @@ -1,34 +1,34 @@ - - - - - \ No newline at end of file + &.red:after { + background-color: $red; + } + &.green:after { + background-color: $green; + } + &.white:after { + background-color: $white; + } + &.black:after { + background-color: $black; + } +} +