diff --git a/jest.config.js b/jest.config.js index c5309b45d..a73aac7ea 100644 --- a/jest.config.js +++ b/jest.config.js @@ -20,9 +20,8 @@ module.exports = { "!src/layouts/reveal/**/*.vue", "!src/layouts/estimate/**/*.vue", // TODO REMOVE THESE AFTER WRITING UNIT TESTS - "!src/layouts/address-vehicles/address-vehicles.vue", "!src/layouts/address-vehicles/address-vehicles-question/address-vehicles-question.vue", - "!src/ux-components/alert\alert.vue", + "!src/ux-components/alert/alert.vue", "!src/helpers/validation-rules.js", // END ], // ! means exclude from coverage. diff --git a/src/layouts/address-lookup/address-lookup.spec.js b/src/layouts/address-lookup/address-lookup.spec.js index d1f261f9a..c66ead3c7 100644 --- a/src/layouts/address-lookup/address-lookup.spec.js +++ b/src/layouts/address-lookup/address-lookup.spec.js @@ -48,9 +48,9 @@ describe("address-lookup.vue", () => { await wrapper.vm.forwardButtonAction(); // Assert - expect(wrapper.findComponent({ ref: "alertNonServiceableZip" }).isVisible()).toBe(true); - }); - + expect(wrapper.findComponent({ ref: "alertNonServiceableZip" }).isVisible()).toBe(true); + }); + test("if the address matches a different vehicle display the Matched Different VehicleAlert", async () => { // Arrange const mockRegistrationAddress = { @@ -76,8 +76,8 @@ describe("address-lookup.vue", () => { await wrapper.vm.forwardButtonAction(); // Assert - expect(wrapper.findComponent({ ref: "alertMatchedDifferentVehicle" }).isVisible()).toBe(true); - }); + expect(wrapper.findComponent({ ref: "alertMatchedDifferentVehicle" }).isVisible()).toBe(true); + }); test("if the looking up VIN by address is not allowed in the state selected display the Vin Lookup By HomeAddress Not Allowed Alert", async () => { // Arrange @@ -121,7 +121,7 @@ describe("address-lookup.vue", () => { } return Promise.resolve({ data }); - }) + }) await wrapper.setData({ customerQuestions: { @@ -133,7 +133,7 @@ describe("address-lookup.vue", () => { await wrapper.vm.forwardButtonAction(); // Assert - expect(wrapper.findComponent({ ref: "alertVinLookupsByHomeAddressNotAllowed" }).isVisible()).toBe(true); + expect(wrapper.findComponent({ ref: "alertVinLookupsByHomeAddressNotAllowed" }).isVisible()).toBe(true); }); @@ -169,7 +169,7 @@ describe("address-lookup.vue", () => { return Promise.resolve({ data }); - }) + }) await wrapper.setData({ customerQuestions: { @@ -183,13 +183,13 @@ describe("address-lookup.vue", () => { await wrapper.vm.forwardButtonAction(); // Assert - expect(wrapper.findComponent({ ref: "alertVinNotFound" }).isVisible()).toBe(true); + expect(wrapper.findComponent({ ref: "alertVinNotFound" }).isVisible()).toBe(true); }); - }); + }); describe("navigation", () => { - + test("if the back button is clicked, navigate back", async () => { // Arrange const { wrapper } = setupMocks(addressLookup, { @@ -197,7 +197,7 @@ describe("address-lookup.vue", () => { }); // Act - await wrapper.vm.backButtonAction(); + await wrapper.vm.backButtonAction(); // Assert expect(wrapper.vm.$router.navigate).toHaveBeenCalled(); @@ -246,7 +246,7 @@ describe("address-lookup.vue", () => { } return Promise.resolve({ data }); - }) + }) await wrapper.setData({ customerQuestions: { @@ -261,7 +261,7 @@ describe("address-lookup.vue", () => { // Assert expect(wrapper.vm.navigateForward).toHaveBeenCalled(); - }); + }); test("if the car entered matches one of multiple vehicles found, update vehicle info and navigate to the heritage funnel", async () => { // Arrange @@ -306,7 +306,7 @@ describe("address-lookup.vue", () => { return Promise.resolve({ data }); - }) + }) await wrapper.setData({ customerQuestions: { @@ -322,8 +322,7 @@ describe("address-lookup.vue", () => { // Assert expect(wrapper.vm.updateVehicleInfo).toHaveBeenCalled(); expect(navigateAfterSaveToHeritageFunnel).toHaveBeenCalled(); - - }); + }); test("if the car entered does not match any of the multiple vehicles found, navigate to address-vehicles page", async () => { // Arrange @@ -368,20 +367,20 @@ describe("address-lookup.vue", () => { return Promise.resolve({ data }); - }) - + }) + const carsFound = [{ - vin: "TEST_VIN", - vehicle: { - carId: "CARID" - } - }, - { - vin: "TEST_VIN2", - vehicle: { - carId: "CARID2" - } - }] + vin: "TEST_VIN", + vehicle: { + carId: "CARID" + } + }, + { + vin: "TEST_VIN2", + vehicle: { + carId: "CARID2" + } + }] await wrapper.setData({ customerQuestions: { @@ -396,8 +395,7 @@ describe("address-lookup.vue", () => { // Assert expect(wrapper.vm.$router.navigateAfterSave).toHaveBeenCalledWith(navigationScenarios.CONTINUING_WITH_MULTIPLE_VEHICLES, undefined, {}, {}, carsFound); - - }); + }); test("if the car entered matches one of the vehicles found but the zip is NOT serviceable, do not navigate forward", async () => { // Arrange @@ -442,7 +440,7 @@ describe("address-lookup.vue", () => { return Promise.resolve({ data }); - }) + }) await wrapper.setData({ customerQuestions: { @@ -458,7 +456,7 @@ describe("address-lookup.vue", () => { // Assert expect(wrapper.vm.navigateForward).toHaveBeenCalledTimes(0); - }); + }); test("if a different vehicle is found than the one entered and the selected glass is not available for that vehicle, navigate back to vehicle-damage page", async () => { // Arrange @@ -492,7 +490,7 @@ describe("address-lookup.vue", () => { return Promise.resolve({ data }); - }) + }) await wrapper.setData({ customerQuestions: { @@ -520,13 +518,74 @@ describe("address-lookup.vue", () => { await wrapper.vm.navigateForward(carEntered, carsFound); // Assert - expect(wrapper.vm.$router.navigateAfterSave).toHaveBeenCalledWith(navigationScenarios.CONTINUING_WITH_DIFFERENT_GLASS, undefined, {}, {"displayVehicleChangeAlert": true}, {}); + expect(wrapper.vm.$router.navigateAfterSave).toHaveBeenCalledWith(navigationScenarios.SELECTED_VIN_HAS_MISMATCHED_GLASS, undefined, {}, { "displayVehicleChangeAlert": true }, {}); }); + test("single car was found and matches entered vehicle => navigateForwardWithSingleCarMatch", async () => { + // Arrange + const carEntered = { + carId: "CARID2" + }; + + const carsFound = [ + { + vin: "TEST_VIN_2", + vehicle: { + carId: "CARID2" + } + } + ]; + + const { wrapper } = setupMocks({}, {}); + wrapper.vm.navigateForwardWithSingleCarMatch = jest.fn(); + + // Act + wrapper.vm.navigateForward(carEntered, carsFound); + + // Assert + expect(wrapper.vm.navigateForwardWithSingleCarMatch).toHaveBeenCalledTimes(1); + }); + + test("multiple cars were found and one matches entered vehicle => navigateForwardWithSingleCarMatch", async () => { + // Arrange + const carEntered = { + carId: "CARID2" + }; + + const carsFound = [ + { + vin: "TEST_VIN_1", + vehicle: { + carId: "CARID1" + } + }, + { + vin: "TEST_VIN_2", + vehicle: { + carId: "CARID2" + } + }, + { + vin: "TEST_VIN_3", + vehicle: { + carId: "CARID3" + } + } + ]; + + const { wrapper } = setupMocks({}, {}); + wrapper.vm.navigateForwardWithSingleCarMatch = jest.fn(); + + // Act + wrapper.vm.navigateForward(carEntered, carsFound); + + // Assert + expect(wrapper.vm.navigateForwardWithSingleCarMatch).toHaveBeenCalledTimes(1); + }); }); - describe("reseting dependent state", () => { + describe("resetting dependent state", () => { test("when reseting dependent state, license plate is set to null and parts state and dependencies are reset", async () => { // Arrange const commitSpy = jest.spyOn(store, "commit"); @@ -544,7 +603,7 @@ describe("address-lookup.vue", () => { }); }); - + describe("registration and service zips", () => { describe("if registration zip is serviceable", () => { test("if registration address is provided => update service address on successful continue", async () => { @@ -556,7 +615,7 @@ describe("address-lookup.vue", () => { zipCode: "43215" } - const { wrapper } = setupMocks(addressLookup,{ + const { wrapper } = setupMocks(addressLookup, { isZipServiceable: true }); @@ -572,7 +631,7 @@ describe("address-lookup.vue", () => { await wrapper.vm.forwardButtonAction(); // Assert - expect(baseMixin.methods.dispatchStoreAction).toHaveBeenCalledWith(storeActions.UPDATE_SERVICE_LOCATION_WITH_VEHICLE_REGISTRATION); + expect(wrapper.vm.dispatchStoreAction).toHaveBeenCalledWith(storeActions.UPDATE_SERVICE_LOCATION_WITH_VEHICLE_REGISTRATION); }); }); @@ -619,8 +678,8 @@ describe("address-lookup.vue", () => { } const { wrapper } = setupMocks(addressLookup, { - isZipServiceable: false - } + isZipServiceable: false + } ); expect(wrapper.vm.showServiceZipField).toBeFalsy(); @@ -651,8 +710,8 @@ describe("address-lookup.vue", () => { } const { wrapper } = setupMocks(addressLookup, { - isZipServiceable: false - } + isZipServiceable: false + } ); store.commit(storeMutations.UPDATE_CAR_ID, "CARID"); @@ -735,7 +794,7 @@ describe("address-lookup.vue", () => { }); }); -function setupMocks(mountOptions, { isZipServiceable = true, lookupVinbyAddressResponse }) { +function setupMocks(mountOptions, { isZipServiceable = true, lookupVinbyAddressResponse, partsOrQuestions = [] }) { store.commit(storeMutations.RESET_STATE); const wrapper = shallowMount(addressLookup, getMountOptions({ ...mountOptions, @@ -757,7 +816,13 @@ function setupMocks(mountOptions, { isZipServiceable = true, lookupVinbyAddressR } }] } - } + }, + { + actionName: storeActions.GET_PARTS_OR_QUESTIONS, + data: { + partsOrQuestions: partsOrQuestions + } + }, ], router: { navigate: jest.fn(), @@ -772,6 +837,5 @@ function setupMocks(mountOptions, { isZipServiceable = true, lookupVinbyAddressR wrapper.vm.$refs.loadingModal.showModal = jest.fn(); return { wrapper }; - } diff --git a/src/layouts/address-lookup/address-lookup.vue b/src/layouts/address-lookup/address-lookup.vue index 2e13299de..aa5d9fa90 100644 --- a/src/layouts/address-lookup/address-lookup.vue +++ b/src/layouts/address-lookup/address-lookup.vue @@ -85,12 +85,14 @@ import { storeMutations } from "@/constants/store-mutations"; import baseMixin from "@/mixins/base-mixin"; import { navigateAfterSaveToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper"; import { getDamageString, isGlassAvailableForCarId } from "@/helpers/damage-helper"; +import vinPagesMixin from "@/mixins/vin-pages-mixin"; defineRule("service-zip-required", required(errorMessages.SERVICE_ZIP_REQUIRED)); defineRule("service-zip-format", regex(/(^\d{5}$)|(^\d{5}-\d{4}$)/, errorMessages.SERVICE_ZIP_FORMAT)); export default { name: "address-lookup", + mixins: [vinPagesMixin], async beforeRouteEnter(to, from, next) { // Call APIs const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage); @@ -291,26 +293,23 @@ export default { this.$router.navigateAfterSave( // then navigate back to "vehicle-damage", and display vehicle changed alert // on that page - this.navigationScenarios.CONTINUING_WITH_DIFFERENT_GLASS, + this.navigationScenarios.SELECTED_VIN_HAS_MISMATCHED_GLASS, this.$route, {}, { displayVehicleChangeAlert: true }, {} ); } else { // otherwise - this.$refs.loadingModal.showModal(); - navigateAfterSaveToHeritageFunnel(this.$route); + this.navigateForwardWithSingleCarMatch(); } } else if (carsFound.length > 1) { // if multiple cars were found let matchingCars = carsFound.filter(car => car.vehicle.carId === carEntered.carId); if (matchingCars.length === 1) { - this.$refs.loadingModal.showModal(); - // and one and only of them matches the car id entered const matchingCar = matchingCars[0]; this.updateVehicleInfo(matchingCar.vin, matchingCar.vehicle); - navigateAfterSaveToHeritageFunnel(this.$route); + this.navigateForwardWithSingleCarMatch(); } else { // if there are no matches or there are multiple matches, navigate to "address-vehicles" page this.$router.navigateAfterSave(this.navigationScenarios.CONTINUING_WITH_MULTIPLE_VEHICLES, this.$route, {}, {}, carsFound); @@ -329,8 +328,8 @@ export default { { licenseLastName: lastName, licenseStreetAddress: streetAddress, - licenseZip: zip, - licenseState: state, + licenseZip: zip, + licenseState: state }, false ); }, @@ -361,9 +360,7 @@ export default { const serviceLocation = store.getters.order.serviceLocation; if (!serviceLocation.address && serviceLocation.zipCode && serviceLocation.zipCode == store.getters.vehicle.registration.zipCode) { - baseMixin.methods.dispatchStoreAction( - storeActions.UPDATE_SERVICE_LOCATION_WITH_VEHICLE_REGISTRATION - ); + this.dispatchStoreAction(storeActions.UPDATE_SERVICE_LOCATION_WITH_VEHICLE_REGISTRATION); } } }, diff --git a/src/layouts/address-vehicles/address-vehicles.spec.js b/src/layouts/address-vehicles/address-vehicles.spec.js index 90eeaecbe..0a89f214c 100644 --- a/src/layouts/address-vehicles/address-vehicles.spec.js +++ b/src/layouts/address-vehicles/address-vehicles.spec.js @@ -7,7 +7,6 @@ import { getMountOptions } from "@/helpers/unit-test-helper.js"; import store from "@/store"; import * as navigateToHeritage from "@/helpers/heritage-integration/navigation-helper"; - // Mock our module for promises. jest.mock("@/helpers/damage-helper", () => ({ isGlassAvailableForCarId: () => { @@ -17,7 +16,6 @@ jest.mock("@/helpers/damage-helper", () => ({ describe("addressVehicles.vue", () => { - test("Should return true for valid page requisites if carId / zipCode / emailAddress / pageData exists", async () => { // Arrange const { wrapper } = setupMocks({}); @@ -225,40 +223,32 @@ describe("addressVehicles.vue", () => { wrapper.unmount(); }); - test("Should navigate to navigateAfterSaveToHeritageFunnel if carId is not different on navigateForward", async () => { + test("carId is not different on navigateForward (car was found) => Should handle navigating forward with car match", async () => { // Arrange const { wrapper } = setupMocks({}); wrapper.vm.$refs.funnelFooter.updateButtonText = jest.fn(); wrapper.vm.$refs.loadingModal.showModal = jest.fn(); navigateToHeritage.navigateAfterSaveToHeritageFunnel = jest.fn(); + wrapper.vm.navigateForwardWithSingleCarMatch = jest.fn(); // Act wrapper.setData({ - selectedVehicleVin: ['5NMS3CADXLH233004'], - isSelectedGlassAvailableForVehicle: true, isCarIdDifferent: false, }); await wrapper.vm.navigateForward(); //Assert - expect(navigateToHeritage.navigateAfterSaveToHeritageFunnel).toBeCalledTimes(1); + expect(wrapper.vm.navigateForwardWithSingleCarMatch).toBeCalledTimes(1); wrapper.unmount(); }); - }); - -function setupMocks({ - // modelValueProp = "1900", - cmsQuestionText = "CMS text goes here", - // dataFromStoreApi = [], -}) { +function setupMocks({}) { //Mock store store.dispatch = jest.fn(() => {}); store.getters = { pageData: jest.fn((pageName) => { - // console.log('pageName: ', pageName) // address-vehicles return [ { vehicle: { @@ -295,9 +285,6 @@ function setupMocks({ } }; - // baseMixin.methods.dispatchStoreAction = jest.fn(); - - const mountOptions = getMountOptions({ store: { dispatch: store.dispatch, @@ -320,16 +307,6 @@ function setupMocks({ } return null; }), - // dispatchStoreAction: jest.fn(() => { - // console.log("23424243") - // }), - // isGlassAvailableForCarId: () => { - // console.log("%%%%%%%%%%%%%%%") - // return Promise.resolve(true) - // } - - // lookupVin: jest.fn(() => Promise.resolve(lookupVinResponse)), - }, computed: { dynamicStrings() { @@ -337,18 +314,10 @@ function setupMocks({ } } } - // mountOptions.propsData = { - // modelValue: modelValueProp, - // }; mountOptions.mixins = [mockMixin]; const wrapper = shallowMount(addressVehicles, mountOptions); - //Mock CMS content - const cmsContent = { - QuestionText: cmsQuestionText, - }; - return { wrapper }; } diff --git a/src/layouts/address-vehicles/address-vehicles.vue b/src/layouts/address-vehicles/address-vehicles.vue index faf71e1fb..db4000956 100644 --- a/src/layouts/address-vehicles/address-vehicles.vue +++ b/src/layouts/address-vehicles/address-vehicles.vue @@ -71,13 +71,15 @@ import { isGlassAvailableForCarId } from "@/helpers/damage-helper"; import { doesCopyContainRouterLink, splitCopyOnCMSPlaceHolder, getRouterLinkRouteFromCopy, - getRouterLinkDisplayTextFromCopy, } from "@/helpers/cms-content-helper" + getRouterLinkDisplayTextFromCopy, } from "@/helpers/cms-content-helper"; +import vinPagesMixin from "@/mixins/vin-pages-mixin"; // DEFINE VALIDATION RULES defineRule("vehicle-required", required(errorMessages.VEHICLE_REQUIRED)); export default { name: "address-vehicles", + mixins: [vinPagesMixin], async beforeRouteEnter(to, from, next) { // Call APIs const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage); @@ -188,8 +190,7 @@ export default { ); return; } else { - this.$refs.loadingModal.showModal(); - navigateAfterSaveToHeritageFunnel(this.$route); + this.navigateForwardWithSingleCarMatch(); return; } }, diff --git a/src/layouts/license-plate-lookup/license-plate-lookup.spec.js b/src/layouts/license-plate-lookup/license-plate-lookup.spec.js index 44ab645dd..d711fa8bb 100644 --- a/src/layouts/license-plate-lookup/license-plate-lookup.spec.js +++ b/src/layouts/license-plate-lookup/license-plate-lookup.spec.js @@ -9,6 +9,7 @@ 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 { storeActions } from "@/constants/store-actions"; import { storeMutations } from "@/constants/store-mutations"; import store from "@/store"; @@ -223,7 +224,7 @@ describe("license-plate-lookup.vue", () => { }); describe("navigateForward", () => { - test("NavigateAfterSave should be called if isCarIdDifferent is true and isSelectedGlassAvailableForVehicle is false when navigateForward is called", async () => { + test("navigateAfterSave should be called if isCarIdDifferent is true and isSelectedGlassAvailableForVehicle is false when navigateForward is called", async () => { // Arrange const { wrapper } = setupMocks({}); @@ -259,6 +260,38 @@ describe("license-plate-lookup.vue", () => { //Assert expect(navigateToHeritage.navigateAfterSaveToHeritageFunnel).toHaveBeenCalled(); }); + + test("carId matches returned vehicle => navigateForwardWithSingleCarMatch", async () => { + // Arrange + const { wrapper } = setupMocks({}); + await wrapper.setData({ + isCarIdDifferent: false + }) + + wrapper.vm.navigateForwardWithSingleCarMatch = jest.fn(); + + // Act + await wrapper.vm.navigateForward(); + + //Assert + expect(wrapper.vm.navigateForwardWithSingleCarMatch).toHaveBeenCalledTimes(1); + }); + + test("selected glass is available for returned vehicle => navigateForwardWithSingleCarMatch", async () => { + // Arrange + const { wrapper } = setupMocks({}); + await wrapper.setData({ + isSelectedGlassAvailableForVehicle: true + }) + + wrapper.vm.navigateForwardWithSingleCarMatch = jest.fn(); + + // Act + await wrapper.vm.navigateForward(); + + //Assert + expect(wrapper.vm.navigateForwardWithSingleCarMatch).toHaveBeenCalledTimes(1); + }); }); }); @@ -484,7 +517,7 @@ describe("license-plate-lookup.vue", () => { expect(store.dispatch).toHaveBeenCalled(); }) - test("dispatch non blocking store action called on validate zip", async () => { + test("dispatchStoreAction called on validate zip", async () => { // Arrange const { wrapper } = setupMocks({}); @@ -497,7 +530,7 @@ describe("license-plate-lookup.vue", () => { expect(baseMixin.methods.dispatchStoreAction).toHaveBeenCalled(); }); - test("dispatch non blocking store action called on lookup vin", async () => { + test("dispatchStoreAction called on lookup vin", async () => { // Arrange const { wrapper } = setupMocks({}); @@ -514,11 +547,8 @@ describe("license-plate-lookup.vue", () => { function setupMocks({ pageHeaderWidgetHeaderText = {}, - mountOptionsMockData = { - router: { - navigate: jest.fn(), - }, - }, + mountOptionsMockData = {}, + partsOrQuestions = [] }) { store.commit(storeMutations.RESET_STATE); //Mock api responses @@ -537,6 +567,21 @@ function setupMocks({ }, }; + mountOptionsMockData = { + ...mountOptionsMockData, + router: { + navigate: jest.fn(), + }, + actionList: [ + { + actionName: storeActions.GET_PARTS_OR_QUESTIONS, + data: { + partsOrQuestions: partsOrQuestions + } + }, + ] + } + const apiPromise = Promise.resolve(apiResponses); settleAllPromises.mockImplementation(() => apiPromise); diff --git a/src/layouts/license-plate-lookup/license-plate-lookup.vue b/src/layouts/license-plate-lookup/license-plate-lookup.vue index f3a5d3e83..b0360776c 100644 --- a/src/layouts/license-plate-lookup/license-plate-lookup.vue +++ b/src/layouts/license-plate-lookup/license-plate-lookup.vue @@ -98,10 +98,10 @@ import baseMixin from "@/mixins/base-mixin.js"; import { storeActions } from "@/constants/store-actions"; import { storeMutations } from "@/constants/store-mutations"; import { errorMessages } from "@/constants/error-messages"; -import { navigateAfterSaveToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper"; import { getDamageString, isGlassAvailableForCarId, } from "@/helpers/damage-helper"; import { required, regex, } from "@/helpers/validation-rules"; import { Form, defineRule, } from "vee-validate"; +import vinPagesMixin from "@/mixins/vin-pages-mixin"; // DEFINE VALIDATION RULES defineRule("license-plate-required", required(errorMessages.LICENSE_PLATE_REQUIRED)); @@ -112,6 +112,7 @@ defineRule("email-address-format", regex(/^([a-zA-Z0-9_\-.+]+)@([a-zA-Z0-9_\-.]+ export default { name: "license-plate-lookup", + mixins: [vinPagesMixin], async beforeRouteEnter(to, from, next) { // Call APIs const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage); @@ -297,8 +298,7 @@ export default { ); return; } else { - this.$refs.loadingModal.showModal(); - navigateAfterSaveToHeritageFunnel(this.$route); + this.navigateForwardWithSingleCarMatch(); return; } }, @@ -312,7 +312,7 @@ export default { }, updateCustomerInfo(vin, vehicleInfo, registrationState, serviceState) { if (this.isCarIdDifferent && !this.isSelectedGlassAvailableForVehicle) { - store.dispatch(storeActions.RESET_DAMAGE_STATE_AND_DEPENDENCIES); + store.dispatch(storeActions.RESET_DAMAGE_STATE_AND_DEPENDENCIES); } store.commit(storeMutations.UPDATE_VEHICLE_VIN, vin); store.commit(storeMutations.UPDATE_YEAR, vehicleInfo.year); diff --git a/src/layouts/part-questions/part-questions.vue b/src/layouts/part-questions/part-questions.vue index e8c021954..36a7d6465 100644 --- a/src/layouts/part-questions/part-questions.vue +++ b/src/layouts/part-questions/part-questions.vue @@ -5,9 +5,7 @@

Part Questions Page Placeholder

- +
@@ -65,10 +63,7 @@ export default { ); }, arePagePrerequisitesValid() { - if (Object.keys(store.getters.pageData(fmgPageValues.PART_QUESTIONS)).length !== 0) { - return true; - } - return false; + return Object.keys(store.getters.pageData(fmgPageValues.PART_QUESTIONS)).length !== 0; }, resetDependentState() { // Set diff --git a/src/layouts/vehicle-parts/glass-part-question/glass-part-question.vue b/src/layouts/vehicle-parts/glass-part-question/glass-part-question.vue index 86045e52e..4afbfd467 100644 --- a/src/layouts/vehicle-parts/glass-part-question/glass-part-question.vue +++ b/src/layouts/vehicle-parts/glass-part-question/glass-part-question.vue @@ -168,7 +168,7 @@ export default { // Check if only a single part is present for the tint and set the v-model if it is. AutoSelectIfSinglePart() { // Check if the selected tint only has a single feature - Object.keys(this.PartDataFromApi.partsOrQuestions).forEach((key) => { + Object.keys(this.PartDataFromApi.partsOrQuestions ?? {}).forEach((key) => { const currentGlassSelection = this.PartDataFromApi.partsOrQuestions[key]; diff --git a/src/layouts/vin-lookup/vin-lookup.spec.js b/src/layouts/vin-lookup/vin-lookup.spec.js index 8574b2505..1c580f4a6 100644 --- a/src/layouts/vin-lookup/vin-lookup.spec.js +++ b/src/layouts/vin-lookup/vin-lookup.spec.js @@ -1,37 +1,38 @@ -import { shallowMount } from "@vue/test-utils"; +import { shallowMount } from "@vue/test-utils"; import vinLookup from "./vin-lookup.vue"; import { getMountOptions } from "@/helpers/unit-test-helper.js"; +import { navigationScenarios } from "@/router/router-constants/navigation-scenarios.js"; import store from "@/store"; jest.mock("@/store", () => ({ - commit: jest.fn(), - dispatch: jest.fn(), - getters: { - vehicle: { - year: 2019, - carId: 'initial carId' - }, - order: { - serviceLocation: { - zipCode: "45253" + commit: jest.fn(), + dispatch: jest.fn(), + getters: { + vehicle: { + year: 2019, + carId: 'initial carId' }, - customer: { - emailAddress: "builddigitaltest@safelite.com" + order: { + serviceLocation: { + zipCode: "45253" + }, + customer: { + emailAddress: "builddigitaltest@safelite.com" + } + }, + payment: { + insuranceCoverage: { + isVerified: true + } + }, + damage: { + glassToReplace: "windshield" } }, - payment: { - insuranceCoverage: { - isVerified: true - } - }, - damage: { - glassToReplace: "windshield" - } - }, })); -import { getDamageString, getIsWindshieldOnly,isGlassAvailableForCarId } from "@/helpers/damage-helper"; +import { getDamageString, getIsWindshieldOnly, isGlassAvailableForCarId } from "@/helpers/damage-helper"; jest.mock("@/helpers/damage-helper", () => ({ isGlassAvailableForCarId: jest.fn(() => { @@ -45,21 +46,21 @@ jest.mock("@/helpers/damage-helper", () => ({ describe("vin-lookup.vue", () => { it("Should update the funnel-footer forward button when VIN is changed", (done) => { //Arrange - const { wrapper } = setupMocks({ }); + const { wrapper } = setupMocks({}); //Act - wrapper.setData({vin: "newValue"}); + wrapper.setData({ vin: "newValue" }); //Assert wrapper.vm.$nextTick(() => { expect(wrapper.vm.$refs.funnelFooter.updateButtonText).toBeCalled(); done(); }); - + }); it("Should call navigateForward() if the store carId matches the vin response carId and forward button is clicked", async () => { // Arrange - const { wrapper } = setupMocks({ }); - wrapper.vm.navigateForward = jest.fn(); + const { wrapper } = setupMocks({}); + wrapper.vm.navigateForward = jest.fn(); // Act await wrapper.vm.forwardButtonAction(); @@ -70,17 +71,17 @@ describe("vin-lookup.vue", () => { it("Should not call navigateForward() if the store carId does not match the vin response carId and forward button is clicked", async () => { // Arrange - const { wrapper } = setupMocks({ }); + const { wrapper } = setupMocks({}); const vehicleLookupApiResponse = { data: { carId: 'new carId' // does not match the store value } - }; + }; const vinPromise = Promise.resolve(vehicleLookupApiResponse); - + wrapper.vm.lookupVehicle = jest.fn().mockImplementation(() => vinPromise); - wrapper.vm.navigateForward = jest.fn(); + wrapper.vm.navigateForward = jest.fn(); // Act await wrapper.vm.forwardButtonAction(); @@ -91,16 +92,16 @@ describe("vin-lookup.vue", () => { it("Should call navigateForward() if the store carId does not match the vin response carId but does match previously enterted carId and forward button is clicked", async () => { // Arrange - const { wrapper } = setupMocks({ }); + const { wrapper } = setupMocks({}); const vehicleLookupApiResponse = { data: { carId: 'new carId' // does not match the store value } - }; + }; const vinPromise = Promise.resolve(vehicleLookupApiResponse); - + wrapper.vm.lookupVehicle = jest.fn().mockImplementation(() => vinPromise); - wrapper.vm.navigateForward = jest.fn(); + wrapper.vm.navigateForward = jest.fn(); wrapper.vm.previouslyEnteredCarId = 'new carId'; @@ -113,7 +114,7 @@ describe("vin-lookup.vue", () => { it("Should not call navigateForward() if zip service returns a non-serviceable flag", async () => { // Arrange - const { wrapper } = setupMocks({ }); + const { wrapper } = setupMocks({}); const zipValidationApiResponse = { data: { isServiceable: false @@ -121,9 +122,9 @@ describe("vin-lookup.vue", () => { }; const zipPromise = Promise.resolve(zipValidationApiResponse); - + wrapper.vm.validateZip = jest.fn().mockImplementation(() => zipPromise); - wrapper.vm.navigateForward = jest.fn(); + wrapper.vm.navigateForward = jest.fn(); wrapper.vm.previouslyEnteredCarId = 'new carId'; @@ -136,16 +137,16 @@ describe("vin-lookup.vue", () => { it("Should not call navigateForward() when forward button is clicked but lookupVehicle errors out.", async () => { // Arrange - const { wrapper } = setupMocks({ }); + const { wrapper } = setupMocks({}); const vehicleLookupApiResponse = { data: { carId: 'new carId' // does not match the store value } - }; + }; const vinPromise = Promise.reject(vehicleLookupApiResponse); - + wrapper.vm.lookupVehicle = jest.fn().mockImplementation(() => vinPromise); - wrapper.vm.navigateForward = jest.fn(); + wrapper.vm.navigateForward = jest.fn(); wrapper.vm.previouslyEnteredCarId = 'new carId'; @@ -155,19 +156,75 @@ describe("vin-lookup.vue", () => { //Assert expect(wrapper.vm.navigateForward).not.toHaveBeenCalled(); }); + + describe("navigateForward", () => { + test("carId is different from returned vehicle and selected glass isn't available => continue with different glass", async () => { + // Arrange + const { wrapper } = setupMocks({ + customMountOptions: { + router: { + navigateAfterSave: jest.fn() + } + } + }); + wrapper.vm.navigateForwardWithSingleCarMatch = jest.fn(); + wrapper.setData({ + isCarIdDifferent: true, + isSelectedGlassAvailableForVehicle: false + }); + + // Act + await wrapper.vm.navigateForward(); + + //Assert + expect(wrapper.vm.$router.navigateAfterSave).toBeCalledTimes(1); + expect(wrapper.vm.$router.navigateAfterSave).toHaveBeenCalledWith(navigationScenarios.SELECTED_VIN_HAS_MISMATCHED_GLASS, wrapper.vm.$route, expect.anything(), expect.anything(), expect.anything()); + }) + + test("carId matches => navigateForwardWithSingleCarMatch", async () => { + // Arrange + const { wrapper } = setupMocks({}); + wrapper.vm.navigateForwardWithSingleCarMatch = jest.fn(); + wrapper.setData({ + isCarIdDifferent: false, + }); + + // Act + await wrapper.vm.navigateForward(); + + //Assert + expect(wrapper.vm.navigateForwardWithSingleCarMatch).toBeCalledTimes(1); + }) + + test("selected glass is available for returned vehicle => navigateForwardWithSingleCarMatch", async () => { + // Arrange + const { wrapper } = setupMocks({}); + wrapper.vm.navigateForwardWithSingleCarMatch = jest.fn(); + wrapper.setData({ + isSelectedGlassAvailableForVehicle: true, + }); + + // Act + await wrapper.vm.navigateForward(); + + //Assert + expect(wrapper.vm.navigateForwardWithSingleCarMatch).toBeCalledTimes(1); + }) + }) }); function setupMocks({ customMountOptions }) { - const mountOptions = getMountOptions({}); + const mountOptions = getMountOptions({ + ...customMountOptions + }); - const finalMountOptions = Object.assign(mountOptions, customMountOptions); // Modify/augment default mount options - finalMountOptions.global.mocks["$store"] = store; - finalMountOptions.global.mixins = [mockMixin]; - finalMountOptions['attachTo'] = document.body; // append wrapper to document.body to test DOM methods + mountOptions.global.mocks["$store"] = store; + mountOptions.global.mixins = [mockMixin]; + mountOptions['attachTo'] = document.body; // append wrapper to document.body to test DOM methods - const wrapper = shallowMount(vinLookup, finalMountOptions); + const wrapper = shallowMount(vinLookup, mountOptions); mockOutPromises(wrapper); mockOutStubFunctions(wrapper); return { wrapper }; @@ -183,8 +240,8 @@ function mockOutPromises(wrapper) { data: { carId: 'initial carId' } - }; - + }; + const zipPromise = Promise.resolve(zipValidationApiResponse); const vinPromise = Promise.resolve(vehicleLookupApiResponse); @@ -197,8 +254,8 @@ function mockOutStubFunctions(wrapper) { wrapper.vm.$refs.funnelFooter.removeLoader = jest.fn(); } - const mockMixin = { +const mockMixin = { methods: { - getCmsContent: jest.fn(() => "placeholder CMS content"), + getCmsContent: jest.fn(() => "placeholder CMS content"), } - } \ No newline at end of file +} \ No newline at end of file diff --git a/src/layouts/vin-lookup/vin-lookup.vue b/src/layouts/vin-lookup/vin-lookup.vue index 9dc81f719..08e63ebf6 100644 --- a/src/layouts/vin-lookup/vin-lookup.vue +++ b/src/layouts/vin-lookup/vin-lookup.vue @@ -8,7 +8,7 @@
- @@ -132,7 +132,6 @@ import loadingModal from '@/common-components/loading-modal/loading-modal.vue'; import { fetchCmsContentForPage } from "@/helpers/cms-content-helper"; import { settleAllPromises } from "@/helpers/layout-helper"; import store from "@/store"; -import baseMixin from "@/mixins/base-mixin.js"; import { storeActions } from "@/constants/store-actions"; import { storeMutations } from "@/constants/store-mutations"; import { errorMessages } from "@/constants/error-messages"; @@ -141,6 +140,7 @@ import { required, regex } from "@/helpers/validation-rules"; import { Form, defineRule } from "vee-validate"; import { navigateAfterSaveToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper"; import { getFunnelCookie } from "@/helpers/heritage-integration/cookie-helper"; +import vinPagesMixin from "@/mixins/vin-pages-mixin"; // DEFINE VALIDATION RULES defineRule("zip-required", required(errorMessages.SERVICE_ZIP_REQUIRED)); @@ -152,6 +152,7 @@ defineRule("vin-format", regex(/^[a-hA-Hj-nJ-NpPr-zR-Z0-9]{17}$/, errorMessages. export default { name: "vin-lookup", + mixins: [vinPagesMixin], async beforeRouteEnter(to, from, next) { // Call APIs const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage); @@ -322,21 +323,20 @@ export default { }, navigateForward(){ if(this.isCarIdDifferent && !this.isSelectedGlassAvailableForVehicle){ - this.$router.navigateAfterSave(this.navigationScenarios.CLICKED_FORWARD, this.$route, {}, { displayVehicleChangeAlert: true }, {}); + this.$router.navigateAfterSave(this.navigationScenarios.SELECTED_VIN_HAS_MISMATCHED_GLASS, this.$route, {}, { displayVehicleChangeAlert: true }, {}); return; } else { - this.$refs.loadingModal.showModal(); - navigateAfterSaveToHeritageFunnel(this.$route); + this.navigateForwardWithSingleCarMatch(); return; } }, validateZip(zip) { - return baseMixin.methods.dispatchStoreAction(storeActions.VALIDATE_ZIP, { + return this.dispatchStoreAction(storeActions.VALIDATE_ZIP, { zip, }); }, lookupVehicle(vin) { - return baseMixin.methods.dispatchStoreAction( + return this.dispatchStoreAction( storeActions.LOOKUP_VEHICLE_BY_VIN, { vin } ); diff --git a/src/mixins/vin-pages-mixin.js b/src/mixins/vin-pages-mixin.js new file mode 100644 index 000000000..8f33fdf92 --- /dev/null +++ b/src/mixins/vin-pages-mixin.js @@ -0,0 +1,28 @@ +import store from "@/store"; +import { storeActions } from "@/constants/store-actions.js"; +import { storeMutations } from "@/constants/store-mutations.js"; +import { navigateAfterSaveToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper"; + +export default { + methods: { + async navigateForwardWithSingleCarMatch() { + const result = await this.dispatchStoreAction(storeActions.GET_PARTS_OR_QUESTIONS); + + const partsOrQuestions = result.data.partsOrQuestions; + const hasPartsQuestions = partsOrQuestions.some(pq => pq.partQuestions?.length > 0); + const hasGlassLocationWithMultipleParts = partsOrQuestions.some(pq => pq.parts?.length > 1); + + if (hasPartsQuestions) { + this.$router.navigateAfterSave(this.navigationScenarios.SELECTED_VIN_WITH_PART_QUESTIONS, this.$route, {}, {}, result.data); + } + else if (hasGlassLocationWithMultipleParts) { + this.$router.navigateAfterSave(this.navigationScenarios.SELECTED_VIN_WITH_MULTIPLE_PARTS, this.$route, {}, {}, result.data); + } + else { + store.commit(storeMutations.UPDATE_PARTS, result.data); + this.$refs.loadingModal.showModal(); + navigateAfterSaveToHeritageFunnel(this.$route); + } + } + } +} \ No newline at end of file diff --git a/src/mixins/vin-pages-mixin.spec.js b/src/mixins/vin-pages-mixin.spec.js new file mode 100644 index 000000000..747b62793 --- /dev/null +++ b/src/mixins/vin-pages-mixin.spec.js @@ -0,0 +1,921 @@ +import vinPagesMixin from "@/mixins/vin-pages-mixin"; +import { shallowMount } from "@vue/test-utils"; +import { setupMocksForJsFiles, getMountOptions } from "@/helpers/unit-test-helper.js"; +import { storeActions } from "@/constants/store-actions"; +import { storeMutations } from "@/constants/store-mutations"; +import { navigationScenarios } from "@/router/router-constants/navigation-scenarios"; +import store from "@/store"; +import loadingModal from '@/common-components/loading-modal/loading-modal.vue'; +import { navigateAfterSaveToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper"; + +jest.mock("@/helpers/heritage-integration/navigation-helper", () => ({ + navigateAfterSaveToHeritageFunnel: jest.fn() +})); + +describe("vin-pages-mixin", () => { + afterEach(() => { + jest.clearAllMocks(); + }) + + describe("navigateForwardWithSingleCarMatch", () => { + describe("should go to parts-questions", () => { + test("single glass location has part question => go to parts-questions", async () => { + // Arrange + const partsOrQuestions = [ + { + "glassName": "Single", + "glassLocation": "Windshield", + "parts": null, + "partQuestions": [ + { + "questionSequence": 1, + "questionText": "Is there a line running across the bottom, driver's side then up the center of the Windshield?", + "answers": [ + { + "answerText": "Yes", + "nextQuestionSequence": null, + "answerResult": "DW01144" + }, + { + "answerText": "No", + "nextQuestionSequence": null, + "answerResult": "DW01143" + } + ] + } + ] + } + ]; + + const { wrapper } = setupMocks({ + partsOrQuestions: partsOrQuestions + }); + + // Act + await wrapper.vm.navigateForwardWithSingleCarMatch(); + + // Assert + expect(wrapper.vm.$router.navigateAfterSave).toHaveBeenCalledTimes(1); + expect(wrapper.vm.$router.navigateAfterSave).toHaveBeenCalledWith(navigationScenarios.SELECTED_VIN_WITH_PART_QUESTIONS, wrapper.vm.$route, {}, {}, { partsOrQuestions }); + }); + + test("multiple glass locations have part questions => go to parts-questions", async () => { + // Arrange + const partsOrQuestions = [ + { + "glassName": "Single", + "glassLocation": "Windshield", + "parts": null, + "partQuestions": [ + { + "questionSequence": 1, + "questionText": "Is there a line running across the bottom, driver's side then up the center of the Windshield?", + "answers": [ + { + "answerText": "Yes", + "nextQuestionSequence": null, + "answerResult": "DW01144" + }, + { + "answerText": "No", + "nextQuestionSequence": null, + "answerResult": "DW01143" + } + ] + } + ] + }, + { + "glassName": "Front", + "glassLocation": "Driver", + "parts": [ + { + "partNumber": "DD08158GTYN", + "description": "driver side, front", + "color": "Green Tint", + "requiresRecalibration": false, + "requiresCapabilityQuestions": false, + "childParts": null + } + ], + "partQuestions": null + }, + { + "glassName": "Quarter", + "glassLocation": "Driver", + "parts": [ + { + "partNumber": "DQ08162GTYN", + "description": "driver side, 1 hole", + "color": "Green Tint", + "requiresRecalibration": false, + "requiresCapabilityQuestions": false, + "childParts": null + } + ], + "partQuestions": null + }, + { + "glassName": "SideDoor", + "glassLocation": "Driver", + "parts": null, + "partQuestions": [ + { + "questionSequence": 2, + "questionText": "Is this a super awesome question?", + "answers": [ + { + "answerText": "Yes", + "nextQuestionSequence": null, + "answerResult": "DW01144000" + }, + { + "answerText": "Super yes", + "nextQuestionSequence": null, + "answerResult": "DW01143001" + } + ] + } + ] + }, + { + "glassName": "Stationary", + "glassLocation": "Rear", + "parts": [ + { + "partNumber": "DB08165GTNN", + "description": "heated glass, stationary", + "color": "Green Tint", + "requiresRecalibration": false, + "requiresCapabilityQuestions": false, + "childParts": null + } + ], + "partQuestions": null + } + ]; + + const { wrapper } = setupMocks({ + partsOrQuestions: partsOrQuestions + }); + + // Act + await wrapper.vm.navigateForwardWithSingleCarMatch(); + + // Assert + expect(wrapper.vm.$router.navigateAfterSave).toHaveBeenCalledTimes(1); + expect(wrapper.vm.$router.navigateAfterSave).toHaveBeenCalledWith(navigationScenarios.SELECTED_VIN_WITH_PART_QUESTIONS, wrapper.vm.$route, {}, {}, { partsOrQuestions }); + }); + + test("multiple glass locations selected, one has part question => go to parts-questions", async () => { + // Arrange + const partsOrQuestions = [ + { + "glassName": "Single", + "glassLocation": "Windshield", + "parts": null, + "partQuestions": [ + { + "questionSequence": 1, + "questionText": "Is there a line running across the bottom, driver's side then up the center of the Windshield?", + "answers": [ + { + "answerText": "Yes", + "nextQuestionSequence": null, + "answerResult": "DW01144" + }, + { + "answerText": "No", + "nextQuestionSequence": null, + "answerResult": "DW01143" + } + ] + } + ] + }, + { + "glassName": "Front", + "glassLocation": "Driver", + "parts": [ + { + "partNumber": "DD08158GTYN", + "description": "driver side, front", + "color": "Green Tint", + "requiresRecalibration": false, + "requiresCapabilityQuestions": false, + "childParts": null + } + ], + "partQuestions": null + }, + { + "glassName": "Quarter", + "glassLocation": "Driver", + "parts": [ + { + "partNumber": "DQ08162GTYN", + "description": "driver side, 1 hole", + "color": "Green Tint", + "requiresRecalibration": false, + "requiresCapabilityQuestions": false, + "childParts": null + } + ], + "partQuestions": null + }, + { + "glassName": "SideDoor", + "glassLocation": "Driver", + "parts": [ + { + "partNumber": "DD08160GTYN", + "description": "driver side, body side, 1 hole", + "color": "Green Tint", + "requiresRecalibration": false, + "requiresCapabilityQuestions": false, + "childParts": null + } + ], + "partQuestions": null + }, + { + "glassName": "Stationary", + "glassLocation": "Rear", + "parts": [ + { + "partNumber": "DB08165GTNN", + "description": "heated glass, stationary", + "color": "Green Tint", + "requiresRecalibration": false, + "requiresCapabilityQuestions": false, + "childParts": null + } + ], + "partQuestions": null + } + ]; + + const { wrapper } = setupMocks({ + partsOrQuestions: partsOrQuestions + }); + + // Act + await wrapper.vm.navigateForwardWithSingleCarMatch(); + + // Assert + expect(wrapper.vm.$router.navigateAfterSave).toHaveBeenCalledTimes(1); + expect(wrapper.vm.$router.navigateAfterSave).toHaveBeenCalledWith(navigationScenarios.SELECTED_VIN_WITH_PART_QUESTIONS, wrapper.vm.$route, {}, {}, { partsOrQuestions }); + }); + + test("a selected glass location has part questions and multiple parts => go to parts-questions", async () => { + // Arrange + const partsOrQuestions = [ + { + "glassName": "Single", + "glassLocation": "Windshield", + "parts": null, + "partQuestions": [ + { + "questionSequence": 1, + "questionText": "Is there a line running across the bottom, driver's side then up the center of the Windshield?", + "answers": [ + { + "answerText": "Yes", + "nextQuestionSequence": null, + "answerResult": "DW01144" + }, + { + "answerText": "No", + "nextQuestionSequence": null, + "answerResult": "DW01143" + } + ] + } + ] + }, + { + "glassName": "Front", + "glassLocation": "Driver", + "parts": [ + { + "partNumber": "DD08158GTYN", + "description": "driver side, front", + "color": "Green Tint", + "requiresRecalibration": false, + "requiresCapabilityQuestions": false, + "childParts": null + } + ], + "partQuestions": null + }, + { + "glassName": "Quarter", + "glassLocation": "Driver", + "parts": [ + { + "partNumber": "DQ08162GTYN", + "description": "driver side, 1 hole", + "color": "Green Tint", + "requiresRecalibration": false, + "requiresCapabilityQuestions": false, + "childParts": null + }, + { + "partNumber": "DQ08162YPYN", + "description": "driver side, 1 hole", + "color": "Gray Tint Privacy", + "requiresRecalibration": false, + "requiresCapabilityQuestions": false, + "childParts": null + } + ], + "partQuestions": null + }, + { + "glassName": "SideDoor", + "glassLocation": "Driver", + "parts": [ + { + "partNumber": "DD08160GTYN", + "description": "driver side, body side, 1 hole", + "color": "Green Tint", + "requiresRecalibration": false, + "requiresCapabilityQuestions": false, + "childParts": null + }, + { + "partNumber": "DD08160YPYN", + "description": "driver side, body side, 1 hole", + "color": "Gray Tint Privacy", + "requiresRecalibration": false, + "requiresCapabilityQuestions": false, + "childParts": null + } + ], + "partQuestions": null + }, + { + "glassName": "Stationary", + "glassLocation": "Rear", + "parts": [ + { + "partNumber": "DB08165GTNN", + "description": "heated glass, stationary", + "color": "Green Tint", + "requiresRecalibration": false, + "requiresCapabilityQuestions": false, + "childParts": null + }, + { + "partNumber": "DB08165YPNN", + "description": "heated glass, stationary", + "color": "Gray Tint Privacy", + "requiresRecalibration": false, + "requiresCapabilityQuestions": false, + "childParts": null + }, + { + "partNumber": "DB08166GTNN", + "description": "stationary", + "color": "Green Tint", + "requiresRecalibration": false, + "requiresCapabilityQuestions": false, + "childParts": null + }, + { + "partNumber": "DB08167GTNN", + "description": "heated glass, movable, 8 hole", + "color": "Green Tint", + "requiresRecalibration": false, + "requiresCapabilityQuestions": false, + "childParts": null + }, + { + "partNumber": "DB08167YPNN", + "description": "heated glass, movable, 8 hole", + "color": "Gray Tint Privacy", + "requiresRecalibration": false, + "requiresCapabilityQuestions": false, + "childParts": null + } + ], + "partQuestions": null + } + ]; + + const { wrapper } = setupMocks({ + partsOrQuestions: partsOrQuestions + }); + + // Act + await wrapper.vm.navigateForwardWithSingleCarMatch(); + + // Assert + expect(wrapper.vm.$router.navigateAfterSave).toHaveBeenCalledTimes(1); + expect(wrapper.vm.$router.navigateAfterSave).toHaveBeenCalledWith(navigationScenarios.SELECTED_VIN_WITH_PART_QUESTIONS, wrapper.vm.$route, {}, {}, { partsOrQuestions }); + }); + }); + + describe("should go to vehicle-parts", () => { + test("single glass location has multiple parts => go to vehicle-parts", async () => { + // Arrange + const partsOrQuestions = [ + { + "glassName": "Stationary", + "glassLocation": "Rear", + "parts": [ + { + "partNumber": "FB25724GTYN", + "description": "heated glass, solar, antenna", + "color": "Green Tint", + "requiresRecalibration": false, + "requiresCapabilityQuestions": false, + "childParts": null + }, + { + "partNumber": "FB25759GTYN", + "description": "heated glass, solar, antenna, w/diversity antenna", + "color": "Green Tint", + "requiresRecalibration": false, + "requiresCapabilityQuestions": false, + "childParts": null + } + ], + "partQuestions": null + } + ]; + + const { wrapper } = setupMocks({ + partsOrQuestions: partsOrQuestions + }); + + // Act + await wrapper.vm.navigateForwardWithSingleCarMatch(); + + // Assert + expect(wrapper.vm.$router.navigateAfterSave).toHaveBeenCalledTimes(1); + expect(wrapper.vm.$router.navigateAfterSave).toHaveBeenCalledWith(navigationScenarios.SELECTED_VIN_WITH_MULTIPLE_PARTS, wrapper.vm.$route, {}, {}, { partsOrQuestions }); + }); + + test("multiple glass locations selected, one of them has multiple parts => go to vehicle parts", async () => { + // Arrange + const partsOrQuestions = [ + { + "glassName": "Single", + "glassLocation": "Windshield", + "parts": [ + { + "partNumber": "FW03647GTNN", + "description": "solar, 3rd visor band", + "color": "Green Tint", + "requiresRecalibration": false, + "requiresCapabilityQuestions": false, + "childParts": [ + { + "partNumber": "MWF03647", + "partType": "MOULDING", + "description": "Upper " + } + ] + } + ], + "partQuestions": null + }, + { + "glassName": "Back", + "glassLocation": "Driver", + "parts": [ + { + "partNumber": "FD25747GTYN", + "description": "solar, driver side, rear, ex models and above", + "color": "Green Tint", + "requiresRecalibration": false, + "requiresCapabilityQuestions": false, + "childParts": null + } + ], + "partQuestions": null + }, + { + "glassName": "Front", + "glassLocation": "Driver", + "parts": [ + { + "partNumber": "FD25719GTYN", + "description": "solar, driver side, front, ex models and above", + "color": "Green Tint", + "requiresRecalibration": false, + "requiresCapabilityQuestions": false, + "childParts": null + } + ], + "partQuestions": null + }, + { + "glassName": "Vent", + "glassLocation": "Driver", + "parts": [ + { + "partNumber": "FV25749GTNN", + "description": "solar, driver side, rear", + "color": "Green Tint", + "requiresRecalibration": false, + "requiresCapabilityQuestions": false, + "childParts": null + } + ], + "partQuestions": null + }, + { + "glassName": "Stationary", + "glassLocation": "Rear", + "parts": [ + { + "partNumber": "FB25724GTYN", + "description": "heated glass, solar, antenna", + "color": "Green Tint", + "requiresRecalibration": false, + "requiresCapabilityQuestions": false, + "childParts": null + }, + { + "partNumber": "FB25759GTYN", + "description": "heated glass, solar, antenna, w/diversity antenna", + "color": "Green Tint", + "requiresRecalibration": false, + "requiresCapabilityQuestions": false, + "childParts": null + } + ], + "partQuestions": null + } + ]; + + const { wrapper } = setupMocks({ + partsOrQuestions: partsOrQuestions + }); + + // Act + await wrapper.vm.navigateForwardWithSingleCarMatch(); + + // Assert + expect(wrapper.vm.$router.navigateAfterSave).toHaveBeenCalledTimes(1); + expect(wrapper.vm.$router.navigateAfterSave).toHaveBeenCalledWith(navigationScenarios.SELECTED_VIN_WITH_MULTIPLE_PARTS, wrapper.vm.$route, {}, {}, { partsOrQuestions }); + }); + + test("multiple glass locations selected, multiple have multiple parts => go to vehicle-parts", async () => { + // Arrange + const partsOrQuestions = [ + { + "glassName": "Single", + "glassLocation": "Windshield", + "parts": [ + { + "partNumber": "DW02101GTYN", + "description": "solar", + "color": "Green Tint", + "requiresRecalibration": false, + "requiresCapabilityQuestions": false, + "childParts": null + } + ], + "partQuestions": null + }, + { + "glassName": "Back", + "glassLocation": "Driver", + "parts": [ + { + "partNumber": "DD12202GTYN", + "description": "solar, driver side, rear", + "color": "Green Tint", + "requiresRecalibration": false, + "requiresCapabilityQuestions": false, + "childParts": null + }, + { + "partNumber": "DD12202YPYN", + "description": "solar, driver side, rear", + "color": "Gray Tint Privacy", + "requiresRecalibration": false, + "requiresCapabilityQuestions": false, + "childParts": null + } + ], + "partQuestions": null + }, + { + "glassName": "Front", + "glassLocation": "Driver", + "parts": [ + { + "partNumber": "DD12198GTYN", + "description": "solar, driver side, front", + "color": "Green Tint", + "requiresRecalibration": false, + "requiresCapabilityQuestions": false, + "childParts": null + }, + { + "partNumber": "DD12200GTYN", + "description": "solar, driver side, front, laminated, soundproofing", + "color": "Green Tint", + "requiresRecalibration": false, + "requiresCapabilityQuestions": false, + "childParts": null + } + ], + "partQuestions": null + }, + { + "glassName": "Quarter", + "glassLocation": "Driver", + "parts": [ + { + "partNumber": "DQ12204GTYNOEM", + "description": "solar, driver side, encap", + "color": "Green Tint", + "requiresRecalibration": false, + "requiresCapabilityQuestions": false, + "childParts": null + }, + { + "partNumber": "DQ12204YPYNOEM", + "description": "solar, driver side, encap", + "color": "Gray Tint Privacy", + "requiresRecalibration": false, + "requiresCapabilityQuestions": false, + "childParts": null + }, + { + "partNumber": "DQ12205GTYNOEM", + "description": "solar, antenna, driver side, encap", + "color": "Green Tint", + "requiresRecalibration": false, + "requiresCapabilityQuestions": false, + "childParts": null + }, + { + "partNumber": "DQ12205YPYNOEM", + "description": "solar, antenna, driver side, encap", + "color": "Gray Tint Privacy", + "requiresRecalibration": false, + "requiresCapabilityQuestions": false, + "childParts": null + }, + { + "partNumber": "DQ12207GTYN", + "description": "solar, driver side, encap, chrome molding", + "color": "Green Tint", + "requiresRecalibration": false, + "requiresCapabilityQuestions": false, + "childParts": null + }, + { + "partNumber": "DQ12207YPYNOEM", + "description": "solar, driver side, encap, chrome molding", + "color": "Gray Tint Privacy", + "requiresRecalibration": false, + "requiresCapabilityQuestions": false, + "childParts": null + }, + { + "partNumber": "DQ12208GTYNOEM", + "description": "solar, antenna, driver side, encap, chrome molding", + "color": "Green Tint", + "requiresRecalibration": false, + "requiresCapabilityQuestions": false, + "childParts": null + }, + { + "partNumber": "DQ12208YPYNOEM", + "description": "solar, antenna, driver side, encap, chrome molding", + "color": "Gray Tint Privacy", + "requiresRecalibration": false, + "requiresCapabilityQuestions": false, + "childParts": null + } + ], + "partQuestions": null + }, + { + "glassName": "Stationary", + "glassLocation": "Rear", + "parts": [ + { + "partNumber": "DB12209GTYN", + "description": "heated glass, solar, 1 hole", + "color": "Green Tint", + "requiresRecalibration": false, + "requiresCapabilityQuestions": false, + "childParts": null + }, + { + "partNumber": "DB12209YPYN", + "description": "heated glass, solar, 1 hole", + "color": "Gray Tint Privacy", + "requiresRecalibration": false, + "requiresCapabilityQuestions": false, + "childParts": null + } + ], + "partQuestions": null + } + ]; + + const { wrapper } = setupMocks({ + partsOrQuestions: partsOrQuestions + }); + + // Act + await wrapper.vm.navigateForwardWithSingleCarMatch(); + + // Assert + expect(wrapper.vm.$router.navigateAfterSave).toHaveBeenCalledTimes(1); + expect(wrapper.vm.$router.navigateAfterSave).toHaveBeenCalledWith(navigationScenarios.SELECTED_VIN_WITH_MULTIPLE_PARTS, wrapper.vm.$route, {}, {}, { partsOrQuestions }); + }); + }); + + describe("should go to heritage funnel", () => { + test("single glass location selected, has no part questions and has one part => go to heritage funnel", async () => { + // Arrange + const partsOrQuestions = [ + { + "glassName": "Single", + "glassLocation": "Windshield", + "parts": [ + { + "partNumber": "FW04186GTYN", + "description": "solar, soundproofing, lane keep assist", + "color": "Green Tint", + "requiresRecalibration": true, + "requiresCapabilityQuestions": false, + "childParts": [ + { + "partNumber": "GGG 3563 KIT", + "partType": "MOULDING", + "description": "Kit, Top & Sides " + } + ] + } + ], + "partQuestions": null + } + ] + + const { wrapper } = setupMocks({ + partsOrQuestions: partsOrQuestions + }); + + // Act + await wrapper.vm.navigateForwardWithSingleCarMatch(); + + // Assert + expect(navigateAfterSaveToHeritageFunnel).toHaveBeenCalledTimes(1); + }); + + test("multiple glass locations selected, each has one part and no part questions => go to heritage funnel", async () => { + // Arrange + const partsOrQuestions = [ + { + "glassName": "Single", + "glassLocation": "Windshield", + "parts": [ + { + "partNumber": "FW04186GTYN", + "description": "solar, soundproofing, lane keep assist", + "color": "Green Tint", + "requiresRecalibration": true, + "requiresCapabilityQuestions": false, + "childParts": [ + { + "partNumber": "GGG 3563 KIT", + "partType": "MOULDING", + "description": "Kit, Top & Sides " + } + ] + } + ], + "partQuestions": null + }, + { + "glassName": "Back", + "glassLocation": "Driver", + "parts": [ + { + "partNumber": "FD25457GTYN", + "description": "solar, driver side, rear", + "color": "Green Tint", + "requiresRecalibration": false, + "requiresCapabilityQuestions": false, + "childParts": null + } + ], + "partQuestions": null + }, + { + "glassName": "Front", + "glassLocation": "Driver", + "parts": [ + { + "partNumber": "FD27090GTYN", + "description": "solar, driver side, front", + "color": "Green Tint", + "requiresRecalibration": false, + "requiresCapabilityQuestions": false, + "childParts": null + } + ], + "partQuestions": null + }, + { + "glassName": "Vent", + "glassLocation": "Driver", + "parts": [ + { + "partNumber": "FV25459GTNN", + "description": "solar, driver side, rear", + "color": "Green Tint", + "requiresRecalibration": false, + "requiresCapabilityQuestions": false, + "childParts": null + } + ], + "partQuestions": null + }, + { + "glassName": "Stationary", + "glassLocation": "Rear", + "parts": [ + { + "partNumber": "FB25460GTYN", + "description": "heated glass, solar", + "color": "Green Tint", + "requiresRecalibration": false, + "requiresCapabilityQuestions": false, + "childParts": null + } + ], + "partQuestions": null + } + ]; + + const { wrapper } = setupMocks({ + partsOrQuestions: partsOrQuestions + }); + + // wrapper.vm.navigateAfterSaveToHeritageFunnel = jest.fn(); + store.commit = jest.fn(); + + // Act + await wrapper.vm.navigateForwardWithSingleCarMatch(); + + // Assert + expect(store.commit).toHaveBeenCalledTimes(1); + expect(store.commit).toHaveBeenCalledWith(storeMutations.UPDATE_PARTS, { partsOrQuestions }) + expect(wrapper.vm.$refs.loadingModal.showModal).toHaveBeenCalledTimes(1); + expect(navigateAfterSaveToHeritageFunnel).toHaveBeenCalledTimes(1); + }); + }); + }); +}); + +function setupMocks({ partsOrQuestions = [] }) { + const baseMixin = setupMocksForJsFiles({ + actionList: [ + { + actionName: storeActions.GET_PARTS_OR_QUESTIONS, + data: { + partsOrQuestions: partsOrQuestions + } + }, + ], + }); + + const mocks = getMountOptions({ + router: { + navigateAfterSave: jest.fn() + }, + + }); + + const mockVinComponent = { + components: { loadingModal }, + template: '', + // render() { + // return '
' + // }, + mixins: [vinPagesMixin, baseMixin.baseMixin] + }; + + // const mockVinComponent = Vue.component("mockcomponent", { + // template: '', + // mixins: [vinPagesMixin, baseMixin.baseMixin] + // }) + + const wrapper = shallowMount(mockVinComponent, mocks); + + wrapper.vm.$refs.loadingModal.showModal = jest.fn(); + + return { wrapper }; +} \ No newline at end of file diff --git a/src/router/router-constants/navigation-scenarios.js b/src/router/router-constants/navigation-scenarios.js index d7491a6bf..7bf425efc 100644 --- a/src/router/router-constants/navigation-scenarios.js +++ b/src/router/router-constants/navigation-scenarios.js @@ -8,11 +8,11 @@ const navigationScenarios = { CLICKED_FORWARD: "CLICKED_FORWARD", CLICKED_FORWARD_WITH_VIN: "CLICKED_FORWARD_WITH_VIN", SELECTED_PARTS: "SELECTED_PARTS", - CONTINUING_WITH_PARTS_QUESTION: "CONTINUING_WITH_PARTS_QUESTION", - CONTINUING_WITH_MULTIPLE_PARTS: "CONTINUING_WITH_MULTIPLE_PARTS", + SELECTED_VIN_WITH_PART_QUESTIONS: "SELECTED_VIN_WITH_PART_QUESTIONS", + SELECTED_VIN_WITH_MULTIPLE_PARTS: "SELECTED_VIN_WITH_MULTIPLE_PARTS", CONTINUING_WITH_SINGLE_PART: "CONTINUING_WITH_SINGLE_PART", CONTINUING_WITH_MULTIPLE_VEHICLES: "CONTINUING_WITH_MULTIPLE_VEHICLES", - CONTINUING_WITH_DIFFERENT_GLASS: "CONTINUING_WITH_DIFFERENT_GLASS", + SELECTED_VIN_HAS_MISMATCHED_GLASS: "SELECTED_VIN_HAS_MISMATCHED_GLASS", CLICKED_FORWARD_WITHOUT_VIN: "CLICKED_FORWARD_WITHOUT_VIN", SELECTED_MANUAL_VIN: "SELECTED_MANUAL_VIN", SELECTED_LICENSE_PLATE: "SELECTED_LICENSE_PLATE", diff --git a/src/router/router-constants/routing-table.js b/src/router/router-constants/routing-table.js index 4c8ef5363..78c5ac242 100644 --- a/src/router/router-constants/routing-table.js +++ b/src/router/router-constants/routing-table.js @@ -72,7 +72,7 @@ const routingTable = [ maps: [ { scenario: navigationScenarios.CLICKED_BACK, - destinationFmgPageValue: fmgPageValues.VEHICLE_DAMAGE, + destinationFmgPageValue: fmgPageValues.VIN_LOOKUP, }, { scenario: navigationScenarios.SELECTED_PARTS, @@ -105,12 +105,20 @@ const routingTable = [ destinationFmgPageValue: fmgPageValues.ESTIMATE, }, { - scenario: navigationScenarios.CLICKED_FORWARD, + scenario: navigationScenarios.SELECTED_VIN_HAS_MISMATCHED_GLASS, destinationFmgPageValue: fmgPageValues.VEHICLE_DAMAGE, }, { scenario: navigationScenarios.CLICKED_BACK_WITH_VIN, destinationFmgPageValue: fmgPageValues.VEHICLE_DAMAGE, + }, + { + scenario: navigationScenarios.SELECTED_VIN_WITH_PART_QUESTIONS, + destinationFmgPageValue: fmgPageValues.PART_QUESTIONS + }, + { + scenario: navigationScenarios.SELECTED_VIN_WITH_MULTIPLE_PARTS, + destinationFmgPageValue: fmgPageValues.VEHICLE_PARTS } ], }, @@ -125,6 +133,14 @@ const routingTable = [ scenario: navigationScenarios.CLICKED_FORWARD, destinationFmgPageValue: fmgPageValues.VEHICLE_DAMAGE, }, + { + scenario: navigationScenarios.SELECTED_VIN_WITH_PART_QUESTIONS, + destinationFmgPageValue: fmgPageValues.PART_QUESTIONS + }, + { + scenario: navigationScenarios.SELECTED_VIN_WITH_MULTIPLE_PARTS, + destinationFmgPageValue: fmgPageValues.VEHICLE_PARTS + } ], }, { @@ -139,9 +155,17 @@ const routingTable = [ destinationFmgPageValue: fmgPageValues.ADDRESS_VEHICLES, }, { - scenario: navigationScenarios.CONTINUING_WITH_DIFFERENT_GLASS, + scenario: navigationScenarios.SELECTED_VIN_HAS_MISMATCHED_GLASS, destinationFmgPageValue: fmgPageValues.VEHICLE_DAMAGE, }, + { + scenario: navigationScenarios.SELECTED_VIN_WITH_PART_QUESTIONS, + destinationFmgPageValue: fmgPageValues.PART_QUESTIONS + }, + { + scenario: navigationScenarios.SELECTED_VIN_WITH_MULTIPLE_PARTS, + destinationFmgPageValue: fmgPageValues.VEHICLE_PARTS + } ], }, { @@ -154,7 +178,15 @@ const routingTable = [ { scenario: navigationScenarios.CLICKED_FORWARD, destinationFmgPageValue: fmgPageValues.VEHICLE_DAMAGE, - }, + }, + { + scenario: navigationScenarios.SELECTED_VIN_WITH_PART_QUESTIONS, + destinationFmgPageValue: fmgPageValues.PART_QUESTIONS + }, + { + scenario: navigationScenarios.SELECTED_VIN_WITH_MULTIPLE_PARTS, + destinationFmgPageValue: fmgPageValues.VEHICLE_PARTS + } ], }, { @@ -178,6 +210,15 @@ const routingTable = [ }, ], }, + { + fmgPageValue: fmgPageValues.PART_QUESTIONS, + maps: [ + { + scenario: navigationScenarios.CLICKED_BACK, + destinationFmgPageValue: fmgPageValues.VIN_LOOKUP + } + ] + }, ]; export { routingTable }; diff --git a/src/store/index.js b/src/store/index.js index dda0fab35..c3e54d27b 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -539,7 +539,16 @@ export const actions = { // Parts API Actions - getPartsOrQuestions(context, { carId, glassArray, zipCode, vin = '' }) { + getPartsOrQuestions(context) { + const vehicle = context.getters.vehicle; + const damage = context.getters.damage; + const order = context.state.order; + + const carId = vehicle.carId; + const glassArray = damage.glassToReplace; + const zipCode = order.serviceLocation.zipCode; + const vin = vehicle.vin; + return globalMethods.callHttpClient({ method: endpoints.GetPartsOrQuestions.method, endpoint: endpoints.GetPartsOrQuestions.url,