From c317708440957d9c789f443f96674a99a1332012 Mon Sep 17 00:00:00 2001 From: Leah Schumann Date: Tue, 22 Mar 2022 16:05:17 -0400 Subject: [PATCH 01/56] Added regex validation for Zip --- src/common-components/textbox-question/textbox-question.vue | 4 ++-- src/constants/error-messages.js | 1 + .../address-questions/address-questions.vue | 4 +++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/common-components/textbox-question/textbox-question.vue b/src/common-components/textbox-question/textbox-question.vue index f4a7d2afc..461e5a3c9 100644 --- a/src/common-components/textbox-question/textbox-question.vue +++ b/src/common-components/textbox-question/textbox-question.vue @@ -97,8 +97,8 @@ export default { get: function () { let labelText = this.questionText; if (this.disableAutoFill) { - var noBreakChar = "⁠"; - var position = 1; + const noBreakChar = "⁠"; + const position = 1; labelText = [labelText.slice(0, position), noBreakChar, labelText.slice(position)].join(''); } diff --git a/src/constants/error-messages.js b/src/constants/error-messages.js index 2b3382dc9..ded2ce333 100644 --- a/src/constants/error-messages.js +++ b/src/constants/error-messages.js @@ -11,6 +11,7 @@ const errorMessages = { CITY_REQUIRED: "Please enter your city", STATE_REQUIRED: "Please enter your state", ZIP_REQUIRED: "Please enter your ZIP", + ZIP_FORMAT: "Please enter a valid ZIP", FIRST_NAME_REQUIRED: "Please enter your first name", LAST_NAME_REQUIRED: "Please enter your last name", EMAIL_ADDRESS_REQUIRED: "Please enter your email address", diff --git a/src/layouts/address-lookup/customer-questions/address-questions/address-questions.vue b/src/layouts/address-lookup/customer-questions/address-questions/address-questions.vue index 0954caf34..975c298ad 100644 --- a/src/layouts/address-lookup/customer-questions/address-questions/address-questions.vue +++ b/src/layouts/address-lookup/customer-questions/address-questions/address-questions.vue @@ -16,7 +16,7 @@
- +
@@ -44,6 +44,7 @@ import { applicationConfig } from "@/constants/application-config.js"; import { computed } from 'vue'; import { defineRule } from "vee-validate"; import { required } from "@/helpers/validation-rules"; +import { regex } from "@/helpers/validation-rules"; import { errorMessages } from "@/constants/error-messages"; //import store from "@/store"; @@ -52,6 +53,7 @@ defineRule("street-address-required", required(errorMessages.STREET_ADDRESS_REQU defineRule("city-required", required(errorMessages.CITY_REQUIRED)); defineRule("state-required", required(errorMessages.STATE_REQUIRED)); defineRule("zip-required", required(errorMessages.ZIP_REQUIRED)); +defineRule("zip-format", regex(/(^\d{5}$)|(^\d{5}-\d{4}$)/, errorMessages.ZIP_FORMAT)); export default ({ name: "address-questions", From cfc5cbd467f33e54b546d995f8b864a5aad2bd0b Mon Sep 17 00:00:00 2001 From: Leah Schumann Date: Fri, 25 Mar 2022 09:11:39 -0400 Subject: [PATCH 02/56] Unit tests for feature/CSR-350, textbox-question and dropdown-question components --- jest.config.js | 3 - .../dropdown-question.spec.js | 131 +++++++++++++ .../dropdown-question.spec.js1 | 81 -------- .../dropdown-question/dropdown-question.vue | 10 +- .../funnel-sub-header.spec.js | 2 +- .../textbox-question/textbox-question.spec.js | 178 ++++++++++++++++++ .../textbox-question.spec.js1 | 81 -------- .../textbox-question/textbox-question.vue | 10 +- src/helpers/validation-rules.spec.js | 67 +++++-- 9 files changed, 379 insertions(+), 184 deletions(-) create mode 100644 src/common-components/dropdown-question/dropdown-question.spec.js delete mode 100644 src/common-components/dropdown-question/dropdown-question.spec.js1 create mode 100644 src/common-components/textbox-question/textbox-question.spec.js delete mode 100644 src/common-components/textbox-question/textbox-question.spec.js1 diff --git a/jest.config.js b/jest.config.js index ed4f38dc7..a98f62b03 100644 --- a/jest.config.js +++ b/jest.config.js @@ -24,9 +24,6 @@ module.exports = { "!src/layouts/address-lookup/address-lookup.vue", "!src/layouts/address-lookup/customer-questions/customer-questions.vue", "!src/layouts/address-lookup/customer-questions/address-questions/address-questions.vue", - "!src/common-components/dropdown-question/dropdown-question.vue", - "!src/common-components/textbox-question/textbox-question.vue", - "!src/helpers/validation-rules.js", // END ], //! means exclude from coverage. testMatch: ["**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"], diff --git a/src/common-components/dropdown-question/dropdown-question.spec.js b/src/common-components/dropdown-question/dropdown-question.spec.js new file mode 100644 index 000000000..81669f714 --- /dev/null +++ b/src/common-components/dropdown-question/dropdown-question.spec.js @@ -0,0 +1,131 @@ +import { shallowMount } from "@vue/test-utils"; +import dropdownQuestion from "./dropdown-question"; +import { nextTick } from "vue"; +import { maska } from 'maska'; + +describe("dropdownQuestion.vue", () => { + + it("Should return aria-disabled state", async () => { + // Act + const wrapper = shallowMount(dropdownQuestion, { + propsData: { + isDisabled: true, + }, + }); + + // Assert + const input = wrapper.find("select"); + + // Expect + expect(input.attributes()["aria-disabled"]).toEqual("true"); + }); + + it("Should render a select input", async () => { + // Act + const wrapper = shallowMount(dropdownQuestion, { + propsData: { + name: "test", + label: "unit test label", + }, + }); + + // Assert + const input = wrapper.find("select"); + + expect(input.exists()).toBe(true); + }); + + it("Should return input id", async () => { + // Act + const wrapper = shallowMount(dropdownQuestion, { + propsData: { + inputId: "input ID", + }, + }); + + // Assert + const input = wrapper.find("select"); + + // Expect + expect(input.attributes().id).toEqual("input ID"); + }); + + it("Should render the 'questionText' data value as the label text when disableAutoFill is false.", async () => { + // Arrange + //Mock CMS content + const questionText = "Question Text"; + const cmsContent = { + QuestionText: questionText, + }; + + // Act + const wrapper = shallowMount(dropdownQuestion, { + global: { + directives: { + maska: maska, + } + }, + }); + await wrapper.setData({ + questionText: questionText, + }); + wrapper.vm.initializeComponent(cmsContent); + + // Assert + expect(wrapper.find("label").text()).toContain(questionText); + wrapper.unmount(); + }); + + it("Should render the 'questionText' data value with '⁠' after the first character as the label text when disableAutoFill is true.", async () => { + // Arrange + //Mock CMS content + const originalQuestionText = "Question Text"; + // Trust me, the below instance of the string "Q⁠uestion Text" actually has the ⁠ in it. You just can't see it + // Don't believe me? Copy it and paste it into Google. Then inspect the search field element in Dev Tools, + // you will see "Q&uestion Text" + const expectedQuestionText = "Q⁠uestion Text"; + const cmsContent = { + QuestionText: originalQuestionText, + }; + + // Act + const wrapper = shallowMount(dropdownQuestion, { + global: { + directives: { + maska: maska, + } + }, + propsData: { + disableAutoFill: true, + }, + }); + await wrapper.setData({ + questionText: originalQuestionText, + }); + wrapper.vm.initializeComponent(cmsContent); + + // Assert + expect(wrapper.vm.$el.children[0].innerHTML).toBe(expectedQuestionText); + wrapper.unmount(); + }); + + it("Should emit new value when modelValue is changed", async () => { + // Act + const wrapper = shallowMount(dropdownQuestion, { + global: { + directives: { + maska: maska, + } + }, + propsData: { + modelValue: "val", + }, + }); + + await wrapper.find("select").setValue("val2"); + + // Assert + expect(wrapper.emitted()).toHaveProperty('change') + + }); +}); diff --git a/src/common-components/dropdown-question/dropdown-question.spec.js1 b/src/common-components/dropdown-question/dropdown-question.spec.js1 deleted file mode 100644 index 3eeb3662b..000000000 --- a/src/common-components/dropdown-question/dropdown-question.spec.js1 +++ /dev/null @@ -1,81 +0,0 @@ -import { shallowMount } from "@vue/test-utils"; -import dropdownQuestion from "./dropdown-question"; -import { nextTick } from "vue"; - -describe("dropdownQuestion.vue", () => { - - it("Should return aria-disabled state", async () => { - // Act - const wrapper = shallowMount(dropdownQuestion, { - propsData: { - isDisabled: true, - }, - }); - - // Assert - const input = wrapper.find("select"); - - // Expect - expect(input.attributes()["aria-disabled"]).toEqual("true"); - }); - - it("Should render a text input", async () => { - // Act - const wrapper = shallowMount(dropdownQuestion, { - propsData: { - name: "test", - label: "unit test label", - }, - }); - - // Assert - const input = wrapper.find("select"); - - expect(input.exists()).toBe(true); - }); - - it("Should return input id", async () => { - // Act - const wrapper = shallowMount(dropdownQuestion, { - propsData: { - inputId: "input ID", - }, - }); - - // Assert - const input = wrapper.find("select"); - - // Expect - expect(input.attributes().id).toEqual("input ID"); - }); - -/* it("Should return label text", async () => { - // Act - const wrapper = shallowMount(dropdownQuestion, { - propsData: { - labelText: "label text", - }, - }); - - // Assert - const label = wrapper.find("label"); - - expect(label.text()).toEqual("label text"); - }); */ - - it("Should return input id", async () => { - // Act - const wrapper = shallowMount(dropdownQuestion, { - propsData: { - inputId: "input ID", - }, - }); - - // Assert - const input = wrapper.find("select"); - - // Expect - expect(input.attributes().id).toEqual("input ID"); - }); - -}); diff --git a/src/common-components/dropdown-question/dropdown-question.vue b/src/common-components/dropdown-question/dropdown-question.vue index 08ed368cf..bc03039ef 100644 --- a/src/common-components/dropdown-question/dropdown-question.vue +++ b/src/common-components/dropdown-question/dropdown-question.vue @@ -79,14 +79,18 @@ export default { }, labelText: { get: function () { - let labelText = this.questionText; + var thisQuestionText = ""; if (this.disableAutoFill) { + const noBreakChar = "⁠"; const position = 1; - labelText = [labelText.slice(0, position), noBreakChar, labelText.slice(position)].join(''); + thisQuestionText = [this.questionText.toString().slice(0, position), noBreakChar, this.questionText.toString().slice(position)].join(''); + + } else { + thisQuestionText = this.questionText.toString(); } - return labelText; + return thisQuestionText; } } }, diff --git a/src/common-components/funnel-sub-header/funnel-sub-header.spec.js b/src/common-components/funnel-sub-header/funnel-sub-header.spec.js index 08561ce67..5a48d126d 100644 --- a/src/common-components/funnel-sub-header/funnel-sub-header.spec.js +++ b/src/common-components/funnel-sub-header/funnel-sub-header.spec.js @@ -8,7 +8,7 @@ describe("FunnelSubHeader.vue", () => { await wrapper.setData({ text: "FunnelSubHeader Content", }); - wrapper.vm.initializeComponent(cmsContent); + //wrapper.vm.initializeComponent(cmsContent); // Assert expect(wrapper.find("h5").text()).toContain("FunnelSubHeader Content"); diff --git a/src/common-components/textbox-question/textbox-question.spec.js b/src/common-components/textbox-question/textbox-question.spec.js new file mode 100644 index 000000000..63214c126 --- /dev/null +++ b/src/common-components/textbox-question/textbox-question.spec.js @@ -0,0 +1,178 @@ +import { shallowMount } from "@vue/test-utils"; +import textboxQuestion from "./textbox-question"; +import { maska } from 'maska'; + +describe("textboxQuestion.vue", () => { + + it("Should return aria-disabled state", async () => { + // Act + const wrapper = shallowMount(textboxQuestion, { + global: { + directives: { + maska: maska, + } + }, + propsData: { + isDisabled: true, + }, + }); + + // Assert + const input = wrapper.find("input"); + + // Expect + expect(input.attributes()["aria-disabled"]).toEqual("true"); + + }); + + it("Should render a text input", async () => { + // Act + const wrapper = shallowMount(textboxQuestion, { + global: { + directives: { + maska: maska, + } + }, + propsData: { + name: "test", + label: "unit test label", + }, + }); + + // Assert + const input = wrapper.find("input"); + + expect(input.exists()).toBe(true); + + }); + + it("Should return input id", async () => { + // Act + const wrapper = shallowMount(textboxQuestion, { + global: { + directives: { + maska: maska, + } + }, + propsData: { + inputId: "input ID", + }, + }); + + // Assert + const input = wrapper.find("input"); + + // Expect + expect(input.attributes().id).toEqual("input ID"); + + }); + + it("Should render the 'questionText' data value as the aria-label attribute.", async () => { + // Arrange + //Mock CMS content + const questionText = "Question Text"; + const cmsContent = { + QuestionText: questionText, + }; + + // Act + const wrapper = shallowMount(textboxQuestion, { + global: { + directives: { + maska: maska, + } + }, + }); + await wrapper.setData({ + questionText: questionText, + }); + wrapper.vm.initializeComponent(cmsContent); + + // Assert + expect(wrapper.find("label").attributes('aria-label')).toBe(questionText); + wrapper.unmount(); + + }); + + it("Should render the 'questionText' data value as the label text when disableAutoFill is false.", async () => { + // Arrange + //Mock CMS content + const questionText = "Question Text"; + const cmsContent = { + QuestionText: questionText, + }; + + // Act + const wrapper = shallowMount(textboxQuestion, { + global: { + directives: { + maska: maska, + } + }, + }); + await wrapper.setData({ + questionText: questionText, + }); + wrapper.vm.initializeComponent(cmsContent); + + // Assert + expect(wrapper.find("label").text()).toContain(questionText); + wrapper.unmount(); + + }); + + it("Should render the 'questionText' data value with '⁠' after the first character as the label text when disableAutoFill is true.", async () => { + // Arrange + //Mock CMS content + const originalQuestionText = "Question Text"; + // Trust me, the below instance of the string "Q⁠uestion Text" actually has the ⁠ in it. You just can't see it + // Don't believe me? Copy it and paste it into Google. Then inspect the search field element in Dev Tools, + // you will see "Q&uestion Text" + const expectedQuestionText = "Q⁠uestion Text"; + const cmsContent = { + QuestionText: originalQuestionText, + }; + + // Act + const wrapper = shallowMount(textboxQuestion, { + global: { + directives: { + maska: maska, + } + }, + propsData: { + disableAutoFill: true, + }, + }); + await wrapper.setData({ + questionText: originalQuestionText, + }); + wrapper.vm.initializeComponent(cmsContent); + + // Assert + expect(wrapper.vm.$el.children[0].innerHTML).toBe(expectedQuestionText); + wrapper.unmount(); + + }); + + it("Should emit new value when modelValue is changed", async () => { + // Act + const wrapper = shallowMount(textboxQuestion, { + global: { + directives: { + maska: maska, + } + }, + propsData: { + modelValue: "val", + }, + }); + + await wrapper.find("input").setValue("val2"); + + // Assert + expect(wrapper.emitted()).toHaveProperty('change') + + }); + +}); diff --git a/src/common-components/textbox-question/textbox-question.spec.js1 b/src/common-components/textbox-question/textbox-question.spec.js1 deleted file mode 100644 index 721fba9d5..000000000 --- a/src/common-components/textbox-question/textbox-question.spec.js1 +++ /dev/null @@ -1,81 +0,0 @@ -import { shallowMount } from "@vue/test-utils"; -import textboxQuestion from "./textbox-question"; -import { nextTick } from "vue"; - -describe("textboxQuestion.vue", () => { - - it("Should return aria-disabled state", async () => { - // Act - const wrapper = shallowMount(textboxQuestion, { - propsData: { - isDisabled: true, - }, - }); - - // Assert - const input = wrapper.find("input"); - - // Expect - expect(input.attributes()["aria-disabled"]).toEqual("true"); - }); - - it("Should render a text input", async () => { - // Act - const wrapper = shallowMount(textboxQuestion, { - propsData: { - name: "test", - label: "unit test label", - }, - }); - - // Assert - const input = wrapper.find("input"); - - expect(input.exists()).toBe(true); - }); - - it("Should return input id", async () => { - // Act - const wrapper = shallowMount(textboxQuestion, { - propsData: { - inputId: "input ID", - }, - }); - - // Assert - const input = wrapper.find("input"); - - // Expect - expect(input.attributes().id).toEqual("input ID"); - }); - - it("Should return label text", async () => { - // Act - const wrapper = shallowMount(textboxQuestion, { - propsData: { - labelText: "label text", - }, - }); - - // Assert - const label = wrapper.find("label"); - - expect(label.text()).toEqual("label text"); - }); - - it("Should return input id", async () => { - // Act - const wrapper = shallowMount(textboxQuestion, { - propsData: { - inputId: "input ID", - }, - }); - - // Assert - const input = wrapper.find("input"); - - // Expect - expect(input.attributes().id).toEqual("input ID"); - }); - -}); diff --git a/src/common-components/textbox-question/textbox-question.vue b/src/common-components/textbox-question/textbox-question.vue index 461e5a3c9..1e97d6f45 100644 --- a/src/common-components/textbox-question/textbox-question.vue +++ b/src/common-components/textbox-question/textbox-question.vue @@ -95,14 +95,18 @@ export default { }, labelText: { get: function () { - let labelText = this.questionText; + var thisQuestionText = ""; if (this.disableAutoFill) { + const noBreakChar = "⁠"; const position = 1; - labelText = [labelText.slice(0, position), noBreakChar, labelText.slice(position)].join(''); + thisQuestionText = [this.questionText.toString().slice(0, position), noBreakChar, this.questionText.toString().slice(position)].join(''); + + } else { + thisQuestionText = this.questionText.toString(); } - return labelText; + return thisQuestionText; } } }, diff --git a/src/helpers/validation-rules.spec.js b/src/helpers/validation-rules.spec.js index 323e05ff1..7e0c6304e 100644 --- a/src/helpers/validation-rules.spec.js +++ b/src/helpers/validation-rules.spec.js @@ -1,4 +1,5 @@ import { required } from "@/helpers/validation-rules"; +import { regex } from "@/helpers/validation-rules"; describe("validation-rules.vue", () => { test("required rules should return error if value missing", () => { @@ -15,15 +16,57 @@ describe("validation-rules.vue", () => { }); describe("validation-rules.vue", () => { - test("required rules should return true if value present", () => { - - //Arrange - const testFn = required("an error"); - - //Act - const testResponse = testFn('some value'); - - //Assert - expect(testResponse).toBe(true); - }); - }); \ No newline at end of file + test("required rules should return true if value present", () => { + + //Arrange + const testFn = required("an error"); + + //Act + const testResponse = testFn('some value'); + + //Assert + expect(testResponse).toBe(true); + }); +}); + +describe("validation-rules.vue", () => { + test("regex rules should return true if value is not present", () => { + + //Arrange + const testFn = regex(/(^\d{5}$)|(^\d{5}-\d{4}$)/, "an error"); // Using Zip regex + + //Act + const testResponse = testFn(); + + //Assert + expect(testResponse).toBe(true); + }); +}); + +describe("validation-rules.vue", () => { + test("regex rules should return false if value is present but does not match regular expression", () => { + + //Arrange + const testFn = regex(/(^\d{5}$)|(^\d{5}-\d{4}$)/, "an error"); // Using Zip regex + + //Act + const testResponse = testFn('4321'); // needs to be 5 numbers + + //Assert + expect(testResponse).toBe("an error"); + }); +}); + +describe("validation-rules.vue", () => { + test("regex rules should return true if value is present and does match regular expression", () => { + + //Arrange + const testFn = regex(/(^\d{5}$)|(^\d{5}-\d{4}$)/, "an error"); // Using Zip regex + + //Act + const testResponse = testFn('43213'); // needs to be 5 numbers + + //Assert + expect(testResponse).toBe(true); + }); +}); \ No newline at end of file From ee60b235981b679d8eac3c48b98c92f8a5ec6675 Mon Sep 17 00:00:00 2001 From: Leah Schumann Date: Wed, 30 Mar 2022 09:15:08 -0400 Subject: [PATCH 03/56] Started working on Unit tests --- jest.config.js | 2 +- .../address-lookup/address-lookup.spec.js | 244 ++++++++++++++++++ .../address-lookup/address-lookup.spec.js1 | 0 src/layouts/address-lookup/address-lookup.vue | 18 +- .../address-questions/address-questions.vue | 207 +++++++-------- 5 files changed, 358 insertions(+), 113 deletions(-) create mode 100644 src/layouts/address-lookup/address-lookup.spec.js delete mode 100644 src/layouts/address-lookup/address-lookup.spec.js1 diff --git a/jest.config.js b/jest.config.js index a98f62b03..dae094119 100644 --- a/jest.config.js +++ b/jest.config.js @@ -21,7 +21,7 @@ module.exports = { "!src/layouts/part-questions/**/*.vue", "!src/layouts/reveal/**/*.vue", // REMOVE THESE AFTER WRITING UNIT TESTS - "!src/layouts/address-lookup/address-lookup.vue", + "!src/layouts/address-lookup/customer-questions/customer-questions.vue", "!src/layouts/address-lookup/customer-questions/address-questions/address-questions.vue", // END diff --git a/src/layouts/address-lookup/address-lookup.spec.js b/src/layouts/address-lookup/address-lookup.spec.js new file mode 100644 index 000000000..43f3649b0 --- /dev/null +++ b/src/layouts/address-lookup/address-lookup.spec.js @@ -0,0 +1,244 @@ +// Components +import addressLookup from "@/layouts/address-lookup/address-lookup.vue"; +import funnelHeader from "@/common-components/funnel-header/funnel-header"; +import funnelFooter from "@/common-components/funnel-footer/funnel-footer"; +import vehicleBanner from "@/common-components/vehicle-banner/vehicle-banner"; +import funnelSubHeader from "@/common-components/funnel-sub-header/funnel-sub-header"; +import customerQuestions from "@/layouts/address-lookup/customer-questions/customer-questions"; +import addressQuestions from "@/layouts/address-lookup/customer-questions/address-questions/address-questions"; + +// Supporting Files +import { settleAllPromises } from "@/helpers/layout-helper.js"; +import baseMixin from "@/mixins/base-mixin"; +import { fetchCmsContentForPage } from "@/helpers/cms-content-helper"; +import { mount, flushPromises } from "@vue/test-utils"; +import { getMountOptions } from "@/helpers/unit-test-helper.js"; +import { maska } from 'maska'; +import { nextTick } from "vue"; +import { storeActions } from "@/constants/store-actions"; +import { storeMutations } from "@/constants/store-mutations"; +import store from "@/store"; +import { validate } from "vee-validate"; + +// Mock our module for promises. +jest.mock("@/helpers/layout-helper.js", () => ({ + settleAllPromises: jest.fn(), +})); + +// Mock fetchCmsContentForPage +jest.mock("@/helpers/cms-content-helper", () => ({ + fetchCmsContentForPage: jest.fn(), +})); + +describe("address-lookup.vue", () => { + test("Page header is initialized with api data", async (done) => { + //Arrange + const pageHeaderWidgetHeaderText = "Select Damage"; + const { wrapper, apiPromise } = setupMocks({ + pageHeaderWidgetHeaderText: pageHeaderWidgetHeaderText, + }); + + //Act + addressLookup.beforeRouteEnter.call( + wrapper.vm, + { query: { fmgPage: "address-lookup" } }, + undefined, + (c) => c(wrapper.vm) + ); + + //Assert + apiPromise.finally(() => { + expect(funnelSubHeader.methods.initializeComponent).toHaveBeenCalledWith( + pageHeaderWidgetHeaderText + ); + done(); + }); + }); + + test("Customer Questions component is initialized with api data", async (done) => { + //Arrange + const StreetAddressQuestionWidget = { QuestionText: "test" }; + const CityQuestionWidget = { QuestionText: "test" }; + const StateQuestionWidget = { QuestionText: "test" }; + const ZipQuestionWidget = { QuestionText: "test" }; + const FirstNameQuestionWidget = { QuestionText: "test" }; + const LastNameQuestionWidget = { QuestionText: "test" }; + const EmailAddressQuestionWidget = { QuestionText: "test" }; + const AlertVerificationWarningWidget = { HeaderText: "test", BodyText: "test" }; + const AlertNoMatchWarningWidget = { HeaderText: "test", BodyText: "test" }; + + const widgets = [ + StreetAddressQuestionWidget, + CityQuestionWidget, + StateQuestionWidget, + ZipQuestionWidget, + AlertVerificationWarningWidget, + AlertNoMatchWarningWidget, + FirstNameQuestionWidget, + LastNameQuestionWidget, + EmailAddressQuestionWidget, + ]; + + const { wrapper, apiPromise } = setupMocks({ + cmsContent: widgets, + }); + + //Act + addressLookup.beforeRouteEnter.call( + wrapper.vm, + { query: { fmgPage: "address-lookup" } }, + undefined, + (c) => c(wrapper.vm) + ); + + //Assert + apiPromise.finally(() => { + expect(customerQuestions.methods.initializeComponent).toHaveBeenCalledWith( + widgets + ); + done(); + }); + }); + + }); + + + + function setupMocks({ + pageHeaderWidgetHeaderText = {}, + mountOptionsMockData = { + router: { + navigate: jest.fn(), + }, + store: { + getters: { + vehicle: {}, + }, + }, + }, + }) { + //Mock api responses + baseMixin.methods.dispatchNonBlockingStoreAction = jest.fn(); + const apiResponses = { + cmsContent: { + FunnelSubHeaderWidget: pageHeaderWidgetHeaderText, + 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", + }, + StreetAddressQuestionWidget: { + QuestionText: + "test" + }, + CityQuestionWidget: { + QuestionText: + "test" + }, + StateQuestionWidget: { + QuestionText: + "test" + }, + ZipQuestionWidget: { + QuestionText: + "test" + }, + FirstNameQuestionWidget: { + QuestionText: + "test" + }, + LastNameQuestionWidget: { + QuestionText: + "test" + }, + EmailAddressQuestionWidget: { + QuestionText: + "test" + }, + AlertVerificationWarningWidget: { + HeaderText: + "test", + BodyText: + "test", + }, + AlertNoMatchWarningWidget: { + HeaderText: + "test", + BodyText: + "test", + }, + + }, + }; + + const apiPromise = Promise.resolve(apiResponses); + + settleAllPromises.mockImplementation(() => apiPromise); + fetchCmsContentForPage.mockImplementation(() => Promise.resolve()); + + //Mock damage initialize methods + funnelHeader.methods = { + initializeComponent: jest.fn(), + }; + + vehicleBanner.methods = { + initializeComponent: jest.fn(), + }; + + funnelSubHeader.methods = { + initializeComponent: jest.fn(), + }; + + funnelFooter.methods = { + initializeComponent: jest.fn(), + }; + + customerQuestions.methods = { + initializeComponent: jest.fn(), + }; + + addressQuestions.methods = { + initializeComponent: jest.fn(), + setupAddressLookup: jest.fn(), + }; + + const mountOptions = getMountOptions(mountOptionsMockData); + mountOptions['attachTo'] = document.body; // append wrapper to document.body to test DOM methods + + mountOptions.global.directives = { + maska: maska + }; + + const wrapper = mount(addressLookup, mountOptions); + + const funnelHeaderWrapper = wrapper.findComponent({ name: "funnelHeader" }); + funnelHeaderWrapper.vm.initializeComponent = + funnelHeader.methods.initializeComponent; + + const vehicleBannerWrapper = wrapper.findComponent({ name: "vehicleBanner" }); + vehicleBannerWrapper.vm.initializeComponent = + vehicleBanner.methods.initializeComponent; + + const funnelSubHeaderWrapper = wrapper.findComponent({ name: "funnelSubHeader" }); + funnelSubHeaderWrapper.vm.initializeComponent = + funnelSubHeader.methods.initializeComponent; + + const funnelFooterWrapper = wrapper.findComponent({ name: "funnelFooter" }); + funnelFooterWrapper.vm.initializeComponent = + funnelFooter.methods.initializeComponent; + + const customerQuestionsWrapper = wrapper.findComponent({ name: "customerQuestions" }); + customerQuestionsWrapper.vm.initializeComponent = + customerQuestions.methods.initializeComponent; + + const addressQuestionsWrapper = wrapper.findComponent({ name: "addressQuestions" }); + addressQuestionsWrapper.vm.initializeComponent = + addressQuestions.methods.initializeComponent; + addressQuestionsWrapper.vm.setupAddressLookup = + addressQuestions.methods.setupAddressLookup; + + return { wrapper, apiPromise }; + } \ No newline at end of file diff --git a/src/layouts/address-lookup/address-lookup.spec.js1 b/src/layouts/address-lookup/address-lookup.spec.js1 deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/layouts/address-lookup/address-lookup.vue b/src/layouts/address-lookup/address-lookup.vue index 2086cd032..bd11908f1 100644 --- a/src/layouts/address-lookup/address-lookup.vue +++ b/src/layouts/address-lookup/address-lookup.vue @@ -66,15 +66,15 @@ export default { resultMap.cmsContent.FunnelFooterWidget ); vm.$refs.customerQuestions.initializeComponent([ - resultMap.cmsContent.StreetAddressQuestionWidget, - resultMap.cmsContent.CityQuestionWidget, - resultMap.cmsContent.StateQuestionWidget, - resultMap.cmsContent.ZipQuestionWidget, - resultMap.cmsContent.AlertVerificationWarningWidget, - resultMap.cmsContent.AlertNoMatchWarningWidget, - resultMap.cmsContent.FirstNameQuestionWidget, - resultMap.cmsContent.LastNameQuestionWidget, - resultMap.cmsContent.EmailAddressQuestionWidget, + resultMap.cmsContent.StreetAddressQuestionWidget, + resultMap.cmsContent.CityQuestionWidget, + resultMap.cmsContent.StateQuestionWidget, + resultMap.cmsContent.ZipQuestionWidget, + resultMap.cmsContent.AlertVerificationWarningWidget, + resultMap.cmsContent.AlertNoMatchWarningWidget, + resultMap.cmsContent.FirstNameQuestionWidget, + resultMap.cmsContent.LastNameQuestionWidget, + resultMap.cmsContent.EmailAddressQuestionWidget, ] ); diff --git a/src/layouts/address-lookup/customer-questions/address-questions/address-questions.vue b/src/layouts/address-lookup/customer-questions/address-questions/address-questions.vue index 975c298ad..f3e73b6d0 100644 --- a/src/layouts/address-lookup/customer-questions/address-questions/address-questions.vue +++ b/src/layouts/address-lookup/customer-questions/address-questions/address-questions.vue @@ -165,112 +165,113 @@ export default ({ this.alertHeadlineNoMatchWarning = cmsContent[5].HeadlineText; this.alertCopyNoMatchWarning = cmsContent[5].BodyText; + }, + + setupAddressLookup() { + const addressField1 = document.getElementById("autocomplete"); + const self = this; + + const apiKey = applicationConfig.GOOGLE_PLACES_API_KEY; + + this.$loadScript(`https://maps.googleapis.com/maps/api/js?key=${apiKey}&libraries=places`) + .then(() => { + // Script is loaded, initialize the autocomplete textbox + const autocomplete = new window.google.maps.places.Autocomplete( + addressField1, + { + componentRestrictions: { country: ["us"] }, + fields: ["address_components"], + types: ["address"], + } + ); + + // Standard place_changed event handling + autocomplete.addListener('place_changed', fillInAddress); + + addressField1.onblur = function() { + const hover = document.querySelector(".pac-container .pac-item:hover"); + + // if an item has been clicked, do nothing, otherwise get first solution and use Geocoder to get the place + if (hover === null) { + const item = document.querySelector(".pac-container .pac-item"); + if (item != null) { + const firstResult = item.textContent; + const geocoder = new window.google.maps.Geocoder(); + geocoder.geocode({ + address: firstResult + }, function (results, status) { + if (status === window.google.maps.GeocoderStatus.OK) { + fillInAddress(results[0]); + self.displayVerificationWarning = true; + self.displayNoMatchWarning = false; + } + }); + } + else { + self.addressModel.city = ""; + self.addressModel.state = ""; + self.addressModel.zip = ""; + self.showAddressFields = true; + self.displayVerificationWarning = false; + self.displayNoMatchWarning = true; + } + } + }; + + function fillInAddress(place) { + if (!place) { + place = autocomplete.getPlace(); + } + + if (place && place.address_components) { + self.addressModel.streetAddress= ""; + self.showAddressFields = true; + + for (const component of place.address_components) { + const componentType = component.types[0]; + + switch (componentType) { + case "street_number": { + self.addressModel.streetAddress = component.long_name; + break; + } + case "route": { + self.addressModel.streetAddress += ' ' + component.short_name; + break; + } + case "locality": { + self.addressModel.city = component.long_name; + break; + } + case "administrative_area_level_1": { + self.addressModel.state = component.short_name; + break; + } + case "postal_code": { + self.addressModel.zip = component.long_name; + break; + } + + } + } + + self.displayVerificationWarning = false; + self.displayNoMatchWarning = false; + } + else { + self.displayVerificationWarning = true; + self.displayNoMatchWarning = false; + } + } + }) + .catch(() => { + // Failed to fetch script + console.log("Unable to load Google Places API script"); + }); } }, mounted() { - - const addressField1 = document.getElementById("autocomplete"); - const self = this; - - const apiKey = applicationConfig.GOOGLE_PLACES_API_KEY; - - this.$loadScript(`https://maps.googleapis.com/maps/api/js?key=${apiKey}&libraries=places`) - .then(() => { - // Script is loaded, initialize the autocomplete textbox - const autocomplete = new window.google.maps.places.Autocomplete( - addressField1, - { - componentRestrictions: { country: ["us"] }, - fields: ["address_components"], - types: ["address"], - } - ); - - // Standard place_changed event handling - autocomplete.addListener('place_changed', fillInAddress); - - addressField1.onblur = function() { - const hover = document.querySelector(".pac-container .pac-item:hover"); - - // if an item has been clicked, do nothing, otherwise get first solution and use Geocoder to get the place - if (hover === null) { - const item = document.querySelector(".pac-container .pac-item"); - if (item != null) { - const firstResult = item.textContent; - const geocoder = new window.google.maps.Geocoder(); - geocoder.geocode({ - address: firstResult - }, function (results, status) { - if (status === window.google.maps.GeocoderStatus.OK) { - fillInAddress(results[0]); - self.displayVerificationWarning = true; - self.displayNoMatchWarning = false; - } - }); - } - else { - self.addressModel.city = ""; - self.addressModel.state = ""; - self.addressModel.zip = ""; - self.showAddressFields = true; - self.displayVerificationWarning = false; - self.displayNoMatchWarning = true; - } - - - } - }; - - function fillInAddress(place) { - if (!place) { - place = autocomplete.getPlace(); - } - - if (place && place.address_components) { - self.addressModel.streetAddress= ""; - self.showAddressFields = true; - - for (const component of place.address_components) { - const componentType = component.types[0]; - - switch (componentType) { - case "street_number": { - self.addressModel.streetAddress = component.long_name; - break; - } - case "route": { - self.addressModel.streetAddress += ' ' + component.short_name; - break; - } - case "locality": { - self.addressModel.city = component.long_name; - break; - } - case "administrative_area_level_1": { - self.addressModel.state = component.short_name; - break; - } - case "postal_code": { - self.addressModel.zip = component.long_name; - break; - } - - } - } - - self.displayVerificationWarning = false; - self.displayNoMatchWarning = false; - } - else { - self.displayVerificationWarning = true; - self.displayNoMatchWarning = false; - } - } - }) - .catch(() => { - // Failed to fetch script - console.log("Unable to load Google Places API script"); - }); + this.setupAddressLookup(); }, components: { textboxQuestion, From 6d4dcba29a86c86166206387d4f89fb9a8385390 Mon Sep 17 00:00:00 2001 From: Leah Schumann Date: Thu, 31 Mar 2022 15:39:16 -0400 Subject: [PATCH 04/56] Added some navigation scenarios and added ADDRESS_LOOKUP page to routing-table --- src/layouts/address-lookup/address-lookup.vue | 17 +++++++++++++++-- .../customer-questions/customer-questions.vue | 2 +- src/router/router-constants/fmgPage-values.js | 3 ++- .../router-constants/navigation-scenarios.js | 6 +++++- src/router/router-constants/routing-table.js | 13 +++++++++++++ 5 files changed, 36 insertions(+), 5 deletions(-) diff --git a/src/layouts/address-lookup/address-lookup.vue b/src/layouts/address-lookup/address-lookup.vue index bd11908f1..d9059cabc 100644 --- a/src/layouts/address-lookup/address-lookup.vue +++ b/src/layouts/address-lookup/address-lookup.vue @@ -31,9 +31,9 @@ import { Form } from "vee-validate"; // Supporting files import { fetchCmsContentForPage } from "@/helpers/cms-content-helper"; import { settleAllPromises } from "@/helpers/layout-helper"; -import { storeActions } from "@/constants/store-actions"; import store from "@/store"; - +// import { storeActions } from "@/constants/store-actions"; +// import baseMixin from "@/mixins/base-mixin"; export default { name: "address-lookup", @@ -102,6 +102,19 @@ export default { resetDependentState() { // Invokes + }, + backButtonAction() { + // route to move backwards + this.$router.navigate( + this.navigationScenarios.CLICKED_BACK, + this.$route + ); + }, + async forwardButtonAction() { + + }, + navigateForward(partsData) { + }, }, components: { diff --git a/src/layouts/address-lookup/customer-questions/customer-questions.vue b/src/layouts/address-lookup/customer-questions/customer-questions.vue index 7fa324f91..7ecd0fd0a 100644 --- a/src/layouts/address-lookup/customer-questions/customer-questions.vue +++ b/src/layouts/address-lookup/customer-questions/customer-questions.vue @@ -25,7 +25,7 @@ import { defineRule } from "vee-validate"; import { required } from "@/helpers/validation-rules"; import { regex } from "@/helpers/validation-rules"; import { errorMessages } from "@/constants/error-messages"; -//import store from "@/store"; +import store from "@/store"; // DEFINE VALIDATION RULES defineRule("first-name-required", required(errorMessages.FIRST_NAME_REQUIRED)); diff --git a/src/router/router-constants/fmgPage-values.js b/src/router/router-constants/fmgPage-values.js index d77f11ff8..c16b4dfe1 100644 --- a/src/router/router-constants/fmgPage-values.js +++ b/src/router/router-constants/fmgPage-values.js @@ -8,7 +8,8 @@ const fmgPageValues = { VIN_LOOKUP: "vin-lookup", VEHICLE_PARTS: "vehicle-parts", PART_QUESTIONS: "part-questions", - REVEAL : "reveal", + REVEAL: "reveal", + ESTIMATE: "estimate", }; export { fmgPageValues }; diff --git a/src/router/router-constants/navigation-scenarios.js b/src/router/router-constants/navigation-scenarios.js index 55efdcaec..10604e593 100644 --- a/src/router/router-constants/navigation-scenarios.js +++ b/src/router/router-constants/navigation-scenarios.js @@ -8,7 +8,11 @@ const navigationScenarios = { SELECTED_PARTS: "SELECTED_PARTS", SELECTED_DAMAGE_WITH_SINGLE_PART: "SELECTED_DAMAGE_WITH_SINGLE_PART", SELECTED_DAMAGE_WITH_MULTIPLE_PARTS: "SELECTED_DAMAGE_WITH_MULTIPLE_PARTS", - SELECTED_DAMAGE_WITH_PART_QUESTIONS: "SELECTED_DAMAGE_WITH_PART_QUESTIONS" + SELECTED_DAMAGE_WITH_PART_QUESTIONS: "SELECTED_DAMAGE_WITH_PART_QUESTIONS", + CONTINUING_WITH_PART_QUESTIONS: "CONTINUING_WITH_PART_QUESTIONS", + CONTINUING_WITH_MULTIPLE_PARTS: "CONTINUING_WITH_MULTIPLE_PARTS", + CONTINUING_WITH_SINGLE_PART: "CONTINUING_WITH_SINGLE_PART", + }; export { navigationScenarios }; diff --git a/src/router/router-constants/routing-table.js b/src/router/router-constants/routing-table.js index 760dac76a..5dfda4eb0 100644 --- a/src/router/router-constants/routing-table.js +++ b/src/router/router-constants/routing-table.js @@ -115,6 +115,19 @@ const routingTable = [ }, ], }, + { + fmgPageValue: fmgPageValues.ADDRESS_LOOKUP, + maps: [ + { + scenario: navigationScenarios.CLICKED_BACK, + destinationFmgPageValue: fmgPageValues.ESTIMATE, + }, + { + scenario: navigationScenarios.VIN_LOOKUP, + destinationFmgPageValue: fmgPageValues.PART_QUESTIONS, + }, + ], + }, ]; export { routingTable }; From ca3f214fbeab1272f67a30e2f511b4c0174951f1 Mon Sep 17 00:00:00 2001 From: Mark Harris Date: Fri, 1 Apr 2022 09:57:40 -0400 Subject: [PATCH 05/56] REVERT THIS COMMIT BEFORE MERGING TO DEVELOP --- src/router/router-constants/routing-table.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/router/router-constants/routing-table.js b/src/router/router-constants/routing-table.js index 760dac76a..1b8ae2449 100644 --- a/src/router/router-constants/routing-table.js +++ b/src/router/router-constants/routing-table.js @@ -60,15 +60,15 @@ const routingTable = [ }, { scenario: navigationScenarios.SELECTED_DAMAGE_WITH_SINGLE_PART, - destinationFmgPageValue: fmgPageValues.REVEAL + destinationFmgPageValue: fmgPageValues.ADDRESS_LOOKUP }, { scenario: navigationScenarios.SELECTED_DAMAGE_WITH_MULTIPLE_PARTS, - destinationFmgPageValue: fmgPageValues.VEHICLE_PARTS, + destinationFmgPageValue: fmgPageValues.ADDRESS_LOOKUP, }, { scenario: navigationScenarios.SELECTED_DAMAGE_WITH_PART_QUESTIONS, - destinationFmgPageValue: fmgPageValues.PART_QUESTIONS, + destinationFmgPageValue: fmgPageValues.ADDRESS_LOOKUP, }, ], }, From 873e1f33167da1bb2ae41aebfce0158963e5d724 Mon Sep 17 00:00:00 2001 From: Leah Schumann Date: Fri, 1 Apr 2022 10:16:27 -0400 Subject: [PATCH 06/56] Added temp code to allow navigation to page from vehicle-damage page --- .../address-questions/address-questions.vue | 24 ++++++++----------- .../customer-questions/customer-questions.vue | 16 +------------ src/router/router-constants/routing-table.js | 2 ++ 3 files changed, 13 insertions(+), 29 deletions(-) diff --git a/src/layouts/address-lookup/customer-questions/address-questions/address-questions.vue b/src/layouts/address-lookup/customer-questions/address-questions/address-questions.vue index f3e73b6d0..67f48af4e 100644 --- a/src/layouts/address-lookup/customer-questions/address-questions/address-questions.vue +++ b/src/layouts/address-lookup/customer-questions/address-questions/address-questions.vue @@ -69,19 +69,7 @@ export default ({ }), }, validationRules: String, - }, - setup(props, { emit }) { - // Please do not modify, this "computed" is used to track and report - // this object's property changes to the parent component - const addressModel = computed({ // Use computed to wrap the object - get: () => props.modelValue, - set: (value) => emit('update:modelValue', value), - }); - - return { - addressModel, - }; - }, + }, data() { return { showAddressFields: false, @@ -150,7 +138,15 @@ export default ({ 'WY': 'Wyoming', } } - } + }, + addressModel: { + get: function() { + return this.modelValue; + }, + set: function(newValue) { + this.$emit("update:modelValue", newValue); + } + }, }, methods: { initializeComponent(cmsContent) { diff --git a/src/layouts/address-lookup/customer-questions/customer-questions.vue b/src/layouts/address-lookup/customer-questions/customer-questions.vue index 7fa324f91..2bf6e66c3 100644 --- a/src/layouts/address-lookup/customer-questions/customer-questions.vue +++ b/src/layouts/address-lookup/customer-questions/customer-questions.vue @@ -20,12 +20,10 @@