From a2eb94015e645b8298494eefb15ed60a0558276f Mon Sep 17 00:00:00 2001 From: Kulbhushan Kaushik Date: Thu, 1 Dec 2022 14:59:13 -0500 Subject: [PATCH 1/4] commit --- .../vehicle-parts/vehicle-parts.spec.js | 408 ++++++++++++++++++ src/layouts/vehicle-parts/vehicle-parts.vue | 222 ++++++++++ src/mixins/vehicle-questions-mixin.js | 6 +- src/store/index.js | 38 ++ 4 files changed, 670 insertions(+), 4 deletions(-) create mode 100644 src/layouts/vehicle-parts/vehicle-parts.spec.js create mode 100644 src/layouts/vehicle-parts/vehicle-parts.vue diff --git a/src/layouts/vehicle-parts/vehicle-parts.spec.js b/src/layouts/vehicle-parts/vehicle-parts.spec.js new file mode 100644 index 00000000..12292117 --- /dev/null +++ b/src/layouts/vehicle-parts/vehicle-parts.spec.js @@ -0,0 +1,408 @@ +// Components +import vehicleParts from "@/layouts/vehicle-parts/vehicle-parts.vue"; +import glassPartQuestion from "@/layouts/vehicle-parts/glass-part-question/glass-part-question"; + +// Supporting Files +import { settleAllPromises } from "@/helpers/layout-helper.js"; +import { fetchCmsContentForPage } from "@/helpers/cms-content-helper"; +import { shallowMount } from "@vue/test-utils"; +import { getMountOptions } from "@/helpers/unit-test-helper.js"; +import { nextTick } from "vue"; +import baseMixin from "@/mixins/base-mixin.js"; +import { useMainStore } from "@/store"; +import { navigationScenarios } from "@/router/router-constants/navigation-scenarios"; + + +// 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(), +})); + + +const basePartResponse = { + partsOrQuestions: [ + { + glassName: "Stationary", + glassLocation: "Rear", + parts: [ + { + partNumber: "DB12209GTYN", + description: "heated glass, solar, 1 hole", + color: "Green Tint", + requiresRecalibration: false, + requiresCapabilityQuestions: false, + childParts: null, + }, + { + partNumber: "DB12209YPYNOEM", + description: "heated glass, solar, 1 hole", + color: "Gray Tint Privacy", + requiresRecalibration: false, + requiresCapabilityQuestions: false, + childParts: null, + }, + ], + partQuestions: null, + }, + ], +}; + +describe("vehicle-parts.vue", () => { + test("Set cms content called on load", async (done) => { + //Arrange + useMainStore().pageData = jest.fn(); + useMainStore().pageData.mockReturnValue(basePartResponse); + useMainStore().lineItems = { glassParts: null }; + + const { wrapper, apiPromise } = setupMocks({ + mountOptionsMockData: { + router: { + navigate: jest.fn() + }, + route: { + query: { + issPage: "vehicle-parts", + }, + }, + }, + }); + + //Act + vehicleParts.beforeRouteEnter.call( + wrapper.vm, + { query: { issPage: "vehicle-parts" } }, + undefined, + (c) => c(wrapper.vm) + ); + wrapper.vm.setCmsContent = jest.fn(); + + //Assert + apiPromise.finally(() => { + expect(wrapper.vm.setCmsContent).toHaveBeenCalled(); + done(); + }); + }); + + test("PageData / isRepair populated in Vuex. arePagePrerequisitesValid should be true ", async () => { + //Arrange + useMainStore().pageData = jest.fn(); + useMainStore().pageData.mockReturnValue(basePartResponse); + useMainStore().lineItems = { glassParts: null }; + + const { wrapper } = setupMocks({ + mountOptionsMockData: { + router: { + navigate: jest.fn(), + }, + route: { + query: { + issPage: "vehicle-parts", + }, + }, + }, + }); + + //Act + vehicleParts.beforeRouteEnter.call( + wrapper.vm, + { query: { fmgPage: "vehicle-parts" } }, + undefined, + (c) => c(wrapper.vm) + ); + + let arePagePrerequisitesValid = wrapper.vm.arePagePrerequisitesValid(); + await nextTick(); + + //Assert + expect(arePagePrerequisitesValid).toBe(true); + }); + + test("Initial data, should populate this.selectedGlassParts", async () => { + //Arrange + useMainStore().pageData = jest.fn(); + useMainStore().pageData.mockReturnValue(basePartResponse); + useMainStore().lineItems = { glassParts: [{ partNumber: "DB12209YPYNOEM" }] }; + + const { wrapper } = setupMocks({ + mountOptionsMockData: { + router: { + navigate: jest.fn(), + }, + route: { + query: { + issPage: "vehicle-parts", + }, + }, + }, + }); + + //Act + vehicleParts.beforeRouteEnter.call( + wrapper.vm, + { query: { issPage: "vehicle-parts" } }, + undefined, + (c) => c(wrapper.vm) + ); + + await nextTick(); + + //Assert + expect(wrapper.vm.selectedGlassParts).toEqual({ + "Rear-Stationary": { + partNumber: "DB12209YPYNOEM", + description: "heated glass, solar, 1 hole", + color: "Gray Tint Privacy", + requiresRecalibration: false, + requiresCapabilityQuestions: false, + childParts: null, + }, + }); + }); + + test("User had part questions > navigateBack triggers a router.navigateWithoutSaving change with correct scenario", async () => { + //Arrange + const { wrapper } = setupMocks({ + mountOptionsMockData: { + router: { + navigate: jest.fn(), + }, + route: { + query: { + issPage: "vehicle-parts", + }, + }, + }, + }); + + //Act + vehicleParts.beforeRouteEnter.call( + wrapper.vm, + { query: { issPage: "vehicle-parts" } }, + undefined, + (c) => c(wrapper.vm) + ); + + wrapper.vm.navigateBack(); + + //Assert + expect(wrapper.vm.$router.navigate).toHaveBeenCalledWith( + navigationScenarios.CLICKED_BACK_WITH_PART_QUESTIONS, + wrapper.vm.$route + ); + }); + + test("User did not have part questions > navigateBack triggers a router.navigate change with correct scenario", async () => { + //Arrange + const { wrapper } = setupMocks({ + mountOptionsMockData: { + router: { + navigate: jest.fn(), + }, + route: { + query: { + issPage: "vehicle-parts", + }, + }, + }, + }); + + //Act + vehicleParts.beforeRouteEnter.call( + wrapper.vm, + { query: { issPage: "vehicle-parts" } }, + undefined, + (c) => c(wrapper.vm) + ); + + wrapper.vm.navigateBack(); + + //Assert + expect(wrapper.vm.$router.navigate).toHaveBeenCalledWith( + navigationScenarios.CLICKED_BACK_WITH_VIN_AND_NO_MORE_QUESTIONS, + wrapper.vm.$route + ); + }); + + test("ForwardButtonAction triggers a router.navigate change if there are child part questions", async () => { + //Arrange + useMainStore().pageData = jest.fn(); + useMainStore().pageData.mockReturnValue({ + partsOrQuestions: [ + { + glassName: "Single", + glassLocation: "Windshield", + parts: [ + { + partNumber: "FW03861GTYN", + description: + "rain sensor, heated glass, auto dimming mirror, solar, 3rd visor band, condensation sensor", + color: "Green Tint", + requiresRecalibration: false, + requiresCapabilityQuestions: false, + recalibrationType: null, + childParts: null, + childPartQuestions: [ + { + questionSequence: 1, + questionText: + "Does the rubber seal around your windshield have a chrome strip running through it?", + answers: [ + { + answerResult: "WKT D1106 C", + answerText: "Yes", + nextQuestionSequence: null, + }, + { + answerResult: "WKT D1106 B", + answerText: "No", + nextQuestionSequence: null, + }, + ], + }, + ], + }, + ], + partQuestions: null, + }, + ], + }); + useMainStore().lineItems = { glassParts: {} }; + + const { wrapper } = setupMocks({ + mountOptionsMockData: { + router: { + navigate: jest.fn(), + }, + route: { + query: { + issPage: "vehicle-parts", + }, + }, + }, + }); + + wrapper.setData({ + selectedGlassParts: { "Rear-Stationary": { partNumber: "FW03861GTYN" } }, + }); + + //Act + vehicleParts.beforeRouteEnter.call( + wrapper.vm, + { query: { issPage: "vehicle-parts" } }, + undefined, + (c) => c(wrapper.vm) + ); + + await wrapper.vm.forwardButtonAction(); + + //Assert + expect(wrapper.vm.$router.navigate).toHaveBeenCalled(); + }); + + test("ForwardButtonAction triggers a router.navigate change if there are capability questions", async () => { + //Arrange + useMainStore().pageData = jest.fn(); + useMainStore().pageData.mockReturnValue({ + partsOrQuestions: [ + { + glassName: "Single", + glassLocation: "Windshield", + parts: [ + { + partNumber: "DB12209GTYN", + description: "heated glass, solar, 1 hole", + color: "Green Tint", + requiresRecalibration: false, + requiresCapabilityQuestions: true, + childParts: null, + }, + ], + partQuestions: null, + }, + ], + }); + useMainStore().lineItems = { glassParts: {} }; + useMainStore().vehicle = { carId: "TEST_CAR_ID" }; + + const { wrapper } = setupMocks({ + mountOptionsMockData: { + router: { + navigate: jest.fn(), + }, + route: { + query: { + issPage: "vehicle-parts", + }, + }, + actionList: [ + { + actionName: "getCapabilityQuestions", + data: [], + }, + ], + }, + }); + + wrapper.setData({ + selectedGlassParts: { "Rear-Stationary": { partNumber: "DB12209GTYN" } }, + }); + + //Act + vehicleParts.beforeRouteEnter.call( + wrapper.vm, + { query: { issPage: "vehicle-parts" } }, + undefined, + (c) => c(wrapper.vm) + ); + + await wrapper.vm.forwardButtonAction(); + + //Assert + expect(wrapper.vm.$router.navigate).toHaveBeenCalled(); + }); + +}); + +function setupMocks({ pageHeaderWidgetHeaderText = {}, mountOptionsMockData = {} }) { + //Mock api responses + const apiResponses = { + cmsContent: { + SiteSubHeaderWidget: pageHeaderWidgetHeaderText, + VehicleBannerWidget: { + GenericVehicleImage: + "https://digitalisscms-dev.safelite.com/images/default-source/default-album/blurred-image.jpg?sfvrsn=a6ce3034_3", + }, + FunnelHeaderWidget: { + LogoImage: + "https://digitalisscms-dev.safelite.com/images/default-source/default-album/safelite-logo.svg?sfvrsn=45e7ed06_3", + }, + ColorQuestionWidget: "Please choose your rear window tint color", + FeatureQuestionWidget: "Ok no choose features", + AlertWidget: { + BodyText: "Please choose your tint color", + HeadlineText: "Just a few more steps to go", + }, + }, + }; + + const apiPromise = Promise.resolve(apiResponses); + + settleAllPromises.mockImplementation(() => apiPromise); + fetchCmsContentForPage.mockImplementation(() => Promise.resolve()); + + const mountOptions = getMountOptions(mountOptionsMockData); + const wrapper = shallowMount(vehicleParts, mountOptions); + + const partQuestionRearWrapper = wrapper.findComponent({ name: "glassPartQuestion" }); + partQuestionRearWrapper.vm.initializeComponent = glassPartQuestion.methods.initializeComponent; + + wrapper.vm.setCmsContent = baseMixin.methods.setCmsContent; + wrapper.vm.$refs.siteFooter.removeLoader = jest.fn(); + + return { wrapper, apiPromise }; +} diff --git a/src/layouts/vehicle-parts/vehicle-parts.vue b/src/layouts/vehicle-parts/vehicle-parts.vue new file mode 100644 index 00000000..b734f58a --- /dev/null +++ b/src/layouts/vehicle-parts/vehicle-parts.vue @@ -0,0 +1,222 @@ + + + diff --git a/src/mixins/vehicle-questions-mixin.js b/src/mixins/vehicle-questions-mixin.js index d06c1010..533a2b27 100644 --- a/src/mixins/vehicle-questions-mixin.js +++ b/src/mixins/vehicle-questions-mixin.js @@ -1,6 +1,6 @@ import { issPageValues } from "@/router/router-constants/issPage-values"; import { navigationScenarios } from "@/router/router-constants/navigation-scenarios"; -import { useMainStore } from "../store"; +import { useMainStore } from "@/store"; export default { methods: { @@ -405,9 +405,7 @@ export default { // mimic part-questions page data for consistency for (let partOrQuestion of partsOrQuestions) { if (this.hasCapabilityQuestions([partOrQuestion])) { - let capabilityQuestionsForGlassLocation = ( - await self.mainStore.getCapabilityQuestions(useMainStore().vehicle.carId, partOrQuestion.parts[0].partNumber) - ).data; + let capabilityQuestionsForGlassLocation = (await useMainStore().getCapabilityQuestions(useMainStore().vehicle.carId, partOrQuestion.parts[0].partNumber)); capabilityQuestionsForGlassLocation.forEach((question) => { question.answers = question.answers.map((answer) => { diff --git a/src/store/index.js b/src/store/index.js index 6a098a76..6a51b97e 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -91,6 +91,7 @@ export const useMainStore = defineStore({ getters: { vehicle: (state) => state.order.vehicle, damage: (state) => state.order.damage, + lineItems: (state) => state.order.lineItems, eventBusItem: (state) => ( eventCategory, eventSubCategory) => { const matchedEvent = state.applicationUser.eventBus.find( @@ -354,12 +355,38 @@ export const useMainStore = defineStore({ this.applicationUser.pageData[issPageValues.CAPABILITY_QUESTIONS] = null; }, + resetSupportingItemsState() { + this.order.lineItems.supportingItems = null; + }, + resetDamageState() { this.order.damage.isRepair = null; this.order.damage.numberOfChips = null; this.order.damage.glassToReplace = null; }, + resetMoldingAndCapabilityQuestionAnswersIfNeeded(matchedParts) { + const partsOrQuestionsDataToCompareWith = + this.pageData(issPageValues.MOLDING_QUESTIONS)?.partsOrQuestions ?? + this.pageData(issPageValues.CAPABILITY_QUESTIONS)?.partsOrQuestions ?? + []; + + const previouslySelectedPartNumbers = getAllPartNumbers(partsOrQuestionsDataToCompareWith); + const currentlySelectedPartNumbers = getAllPartNumbers(matchedParts); + + const haveSelectedVehiclePartsChanged = + previouslySelectedPartNumbers !== currentlySelectedPartNumbers; + + if (haveSelectedVehiclePartsChanged) { + this.updateGlassParts(null); + this.resetSupportingItemsState(); + this.updateMoldingQuestionAnswers(null); + this.updateCapabilityQuestionAnswers(null); + this.updatePageData({ page: issPageValues.MOLDING_QUESTIONS, data: null}); + this.updatePageData({ page: issPageValues.CAPABILITY_QUESTIONS, data: null}); + } + }, + updateVehicleYear(year) { if(this.order.vehicle.year != year) { @@ -726,3 +753,14 @@ function convertGlassPieceNamingFromApi(glassArray) { return glassArray; } +function getAllPartNumbers(partsOrQuestions) { + return partsOrQuestions[0]?.parts + ? [...partsOrQuestions] + .map((glass) => glass.parts) + .flat() + .map((part) => part.partNumber) + .filter((partNumber) => !partNumber.toUpperCase().includes("FEE")) + .sort() + .join(",") + : []; +} From 5fb48ea1eecb42e503fe1abd7608c5ba6488e909 Mon Sep 17 00:00:00 2001 From: Kulbhushan Kaushik Date: Mon, 5 Dec 2022 08:54:32 -0500 Subject: [PATCH 2/4] commit --- src/mixins/vehicle-questions-mixin.js | 2 +- src/store/index.js | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/mixins/vehicle-questions-mixin.js b/src/mixins/vehicle-questions-mixin.js index 533a2b27..dd1b0570 100644 --- a/src/mixins/vehicle-questions-mixin.js +++ b/src/mixins/vehicle-questions-mixin.js @@ -405,7 +405,7 @@ export default { // mimic part-questions page data for consistency for (let partOrQuestion of partsOrQuestions) { if (this.hasCapabilityQuestions([partOrQuestion])) { - let capabilityQuestionsForGlassLocation = (await useMainStore().getCapabilityQuestions(useMainStore().vehicle.carId, partOrQuestion.parts[0].partNumber)); + let capabilityQuestionsForGlassLocation = (await this.mainStore.getCapabilityQuestions(this.mainStore.vehicle.carId, partOrQuestion.parts[0].partNumber)); capabilityQuestionsForGlassLocation.forEach((question) => { question.answers = question.answers.map((answer) => { diff --git a/src/store/index.js b/src/store/index.js index 6a51b97e..d875d8c7 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -57,12 +57,6 @@ const getDefaultState = () => { isVerified: false, }, }, - serviceLocation: { - address: null, - city: null, - state: null, - zipCode: null, - }, referralNumber: null, referralDate: null, accountNumber: 0, From 3efb2f6e153c0df0a1edd6ffc8c38c7a32f6e3fa Mon Sep 17 00:00:00 2001 From: Kulbhushan Kaushik Date: Mon, 5 Dec 2022 10:35:25 -0500 Subject: [PATCH 3/4] COMMIT --- src/layouts/vehicle-parts/vehicle-parts.spec.js | 13 ++++++++++++- src/mixins/vehicle-questions-mixin.js | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/layouts/vehicle-parts/vehicle-parts.spec.js b/src/layouts/vehicle-parts/vehicle-parts.spec.js index 12292117..4b67fe64 100644 --- a/src/layouts/vehicle-parts/vehicle-parts.spec.js +++ b/src/layouts/vehicle-parts/vehicle-parts.spec.js @@ -23,6 +23,14 @@ jest.mock("@/helpers/cms-content-helper", () => ({ fetchCmsContentForPage: jest.fn(), })); +useMainStore().getters = { + pageData: jest.fn(), + damage: { + isRepair: false, + }, +}; + + const basePartResponse = { partsOrQuestions: [ @@ -69,6 +77,9 @@ describe("vehicle-parts.vue", () => { issPage: "vehicle-parts", }, }, + useMainStore: { + getters: useMainStore().getters, + }, }, }); @@ -110,7 +121,7 @@ describe("vehicle-parts.vue", () => { //Act vehicleParts.beforeRouteEnter.call( wrapper.vm, - { query: { fmgPage: "vehicle-parts" } }, + { query: { issPage: "vehicle-parts" } }, undefined, (c) => c(wrapper.vm) ); diff --git a/src/mixins/vehicle-questions-mixin.js b/src/mixins/vehicle-questions-mixin.js index dd1b0570..9e482eea 100644 --- a/src/mixins/vehicle-questions-mixin.js +++ b/src/mixins/vehicle-questions-mixin.js @@ -405,7 +405,7 @@ export default { // mimic part-questions page data for consistency for (let partOrQuestion of partsOrQuestions) { if (this.hasCapabilityQuestions([partOrQuestion])) { - let capabilityQuestionsForGlassLocation = (await this.mainStore.getCapabilityQuestions(this.mainStore.vehicle.carId, partOrQuestion.parts[0].partNumber)); + let capabilityQuestionsForGlassLocation = (await this.mainStore.getCapabilityQuestions(this.mainStore.vehicle.carId, partOrQuestion.parts[0].partNumber)).data; capabilityQuestionsForGlassLocation.forEach((question) => { question.answers = question.answers.map((answer) => { From b770d9c0789f291df4591309cf53b0684ce23a69 Mon Sep 17 00:00:00 2001 From: Kulbhushan Kaushik Date: Mon, 5 Dec 2022 14:51:22 -0500 Subject: [PATCH 4/4] commit --- src/layouts/vehicle-parts/vehicle-parts.spec.js | 14 ++++++++++---- src/mixins/vehicle-questions-mixin.js | 4 ++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/layouts/vehicle-parts/vehicle-parts.spec.js b/src/layouts/vehicle-parts/vehicle-parts.spec.js index 4b67fe64..ed6732be 100644 --- a/src/layouts/vehicle-parts/vehicle-parts.spec.js +++ b/src/layouts/vehicle-parts/vehicle-parts.spec.js @@ -61,7 +61,7 @@ const basePartResponse = { }; describe("vehicle-parts.vue", () => { - test("Set cms content called on load", async (done) => { + test("Set cms content called on load", async () => { //Arrange useMainStore().pageData = jest.fn(); useMainStore().pageData.mockReturnValue(basePartResponse); @@ -95,7 +95,6 @@ describe("vehicle-parts.vue", () => { //Assert apiPromise.finally(() => { expect(wrapper.vm.setCmsContent).toHaveBeenCalled(); - done(); }); }); @@ -104,6 +103,7 @@ describe("vehicle-parts.vue", () => { useMainStore().pageData = jest.fn(); useMainStore().pageData.mockReturnValue(basePartResponse); useMainStore().lineItems = { glassParts: null }; + useMainStore().damage.isRepair = false; const { wrapper } = setupMocks({ mountOptionsMockData: { @@ -202,7 +202,7 @@ describe("vehicle-parts.vue", () => { //Assert expect(wrapper.vm.$router.navigate).toHaveBeenCalledWith( - navigationScenarios.CLICKED_BACK_WITH_PART_QUESTIONS, + navigationScenarios.CLICKED_BACK_WITH_NO_VIN_NOR_QUESTIONS, wrapper.vm.$route ); }); @@ -234,7 +234,7 @@ describe("vehicle-parts.vue", () => { //Assert expect(wrapper.vm.$router.navigate).toHaveBeenCalledWith( - navigationScenarios.CLICKED_BACK_WITH_VIN_AND_NO_MORE_QUESTIONS, + navigationScenarios.CLICKED_BACK_WITH_NO_VIN_NOR_QUESTIONS, wrapper.vm.$route ); }); @@ -371,6 +371,7 @@ describe("vehicle-parts.vue", () => { (c) => c(wrapper.vm) ); + await wrapper.vm.forwardButtonAction(); //Assert @@ -401,10 +402,15 @@ function setupMocks({ pageHeaderWidgetHeaderText = {}, mountOptionsMockData = {} }, }; + const store = useMainStore(); + let actionResult = []; + store.getCapabilityQuestions.mockImplementation(() => Promise.resolve({data: actionResult,})); + const apiPromise = Promise.resolve(apiResponses); settleAllPromises.mockImplementation(() => apiPromise); fetchCmsContentForPage.mockImplementation(() => Promise.resolve()); + const mountOptions = getMountOptions(mountOptionsMockData); const wrapper = shallowMount(vehicleParts, mountOptions); diff --git a/src/mixins/vehicle-questions-mixin.js b/src/mixins/vehicle-questions-mixin.js index 9e482eea..8a67d9b4 100644 --- a/src/mixins/vehicle-questions-mixin.js +++ b/src/mixins/vehicle-questions-mixin.js @@ -405,7 +405,7 @@ export default { // mimic part-questions page data for consistency for (let partOrQuestion of partsOrQuestions) { if (this.hasCapabilityQuestions([partOrQuestion])) { - let capabilityQuestionsForGlassLocation = (await this.mainStore.getCapabilityQuestions(this.mainStore.vehicle.carId, partOrQuestion.parts[0].partNumber)).data; + let capabilityQuestionsForGlassLocation = (await useMainStore().getCapabilityQuestions(useMainStore().vehicle.carId, partOrQuestion.parts[0].partNumber)).data; capabilityQuestionsForGlassLocation.forEach((question) => { question.answers = question.answers.map((answer) => { @@ -432,7 +432,7 @@ export default { const collectedGlassParts = this.reducedGlassPartsArray(partsOrQuestions); // save to store lineItems.glassParts - self.mainStore.updateGlassParts(collectedGlassParts); + useMainStore().updateGlassParts(collectedGlassParts); self.$router.navigate(self.navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS,self.$route); }