diff --git a/jest.config.js b/jest.config.js index a6be0c11a..a73aac7ea 100644 --- a/jest.config.js +++ b/jest.config.js @@ -18,12 +18,10 @@ module.exports = { "!src/layouts/vehicle-damage/windshield-options/windshield-options.vue", "!src/layouts/part-questions/**/*.vue", "!src/layouts/reveal/**/*.vue", - "!src/layouts/estimate/**/*.vue", - "!src/layouts/address-lookup/customer-questions/address-questions/address-questions.vue", // there are limitations of async script loading and third party scripts that prevent full coverage from being met for this component. + "!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/package.json b/package.json index ec3d65d79..304fc2afd 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "serve": "vue-cli-service serve", "build": "vue-cli-service build", "test:unit": "vue-cli-service test:unit --coverage --ci", + "test:unit:lite": "vue-cli-service test:unit --ci", "lint": "vue-cli-service lint" }, "dependencies": { diff --git a/src/common-components/funnel-header/menu-modal/menu-modal.vue b/src/common-components/funnel-header/menu-modal/menu-modal.vue index c980036e9..e275cc65a 100644 --- a/src/common-components/funnel-header/menu-modal/menu-modal.vue +++ b/src/common-components/funnel-header/menu-modal/menu-modal.vue @@ -80,7 +80,7 @@ export default { .bar3 { width: 14px; height: 2px; - background-color: $blue-400; + background-color: $blue; margin: 1px 0; transition: 0.25s; } diff --git a/src/common-components/loading-modal/loading-modal.vue b/src/common-components/loading-modal/loading-modal.vue index d06392755..2bcdc9c31 100644 --- a/src/common-components/loading-modal/loading-modal.vue +++ b/src/common-components/loading-modal/loading-modal.vue @@ -77,7 +77,7 @@ export default { left: 0; right: 0; bottom: 0; - z-index: 1055; + z-index: 1057; width: 100%; height: 100%; overflow-x: hidden; diff --git a/src/helpers/unit-test-helper.js b/src/helpers/unit-test-helper.js index 07632f5e5..592af9a5c 100644 --- a/src/helpers/unit-test-helper.js +++ b/src/helpers/unit-test-helper.js @@ -55,9 +55,11 @@ export function getMountOptions(mockData) { mocks.$store = mockData.store; mocks.$router = mockData.router; mocks.$route = mockData.route; + mocks.$loadScript = mockData.loadScript; const global = { mocks: mocks, + mixins: mockData.mixins, stubs: { Form } }; 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-lookup/customer-questions/address-questions/address-questions.spec.js b/src/layouts/address-lookup/customer-questions/address-questions/address-questions.spec.js index 11a0aa4ed..fb1154fe6 100644 --- a/src/layouts/address-lookup/customer-questions/address-questions/address-questions.spec.js +++ b/src/layouts/address-lookup/customer-questions/address-questions/address-questions.spec.js @@ -1,130 +1,630 @@ -import { shallowMount } from "@vue/test-utils"; +// Components import addressQuestions from "@/layouts/address-lookup/customer-questions/address-questions/address-questions"; +import alert from "@/ux-components/alert/alert"; -const addressModel = { - streetAddress: "", - city: "", - state: "", - zipCode: "", -} +// Supporting Files +import { mount, shallowMount } from "@vue/test-utils"; +import { getMountOptions } from "@/helpers/unit-test-helper.js"; +import { storeActions } from "@/constants/store-actions"; +import { storeMutations } from "@/constants/store-mutations"; +import store from "@/store"; -describe("addressQuestions.vue", () => { +let autocompleteElement; +describe("address-questions.vue", () => { + beforeEach(() => { + // Create the `addressField1` element (autocomplete's input) + autocompleteElement = document.createElement("input") + autocompleteElement.getPlace = jest.fn(); + document.getElementById = jest.fn().mockReturnValue(autocompleteElement); + }) - it("Should render addressQuestions sub-components (textbox-questions and dropdown-questions)", async () => { - // Arrange - const wrapper = shallowMount(addressQuestions); + describe("initial state", () => { + test("only street address field is shown", () => { + // Arrange + const { wrapper } = setupMocks({}); - // Act - const streetAddress = wrapper.findComponent({ ref: 'autocomplete' }); - const city = wrapper.findComponent({ ref: 'city' }); - const state = wrapper.findComponent({ ref: 'state' }); - const zipCode = wrapper.findComponent({ ref: 'zipCode' }); + // Assert + const streetAddressField = wrapper.findComponent({ ref: "autocomplete" }); + const cityField = wrapper.findComponent({ ref: "city" }); + const stateField = wrapper.findComponent({ ref: "state" }); + const zipCodeField = wrapper.findComponent({ ref: "zipCode" }); - // Assert - expect(streetAddress.exists()).toBe(true); - expect(city.exists()).toBe(true); - expect(state.exists()).toBe(true); - expect(zipCode.exists()).toBe(true); - + expect(streetAddressField.exists()).toBe(true); + expect(streetAddressField.isVisible()).toBe(true); + expect(cityField.exists()).toBe(true); + expect(cityField.isVisible()).toBe(false); + expect(stateField.exists()).toBe(true); + expect(stateField.isVisible()).toBe(false); + expect(zipCodeField.exists()).toBe(true); + expect(zipCodeField.isVisible()).toBe(false); + + const alerts = wrapper.findAllComponents(alert); + expect(alerts.length).toEqual(0); + }) + + test("Should render addressQuestions sub-components (textbox-questions and dropdown-questions)", async () => { + // Arrange + const { wrapper } = setupMocks({}); + + // Act + const streetAddress = wrapper.findComponent({ ref: 'autocomplete' }); + const city = wrapper.findComponent({ ref: 'city' }); + const state = wrapper.findComponent({ ref: 'state' }); + const zipCode = wrapper.findComponent({ ref: 'zipCode' }); + + // Assert + expect(streetAddress.exists()).toBe(true); + expect(city.exists()).toBe(true); + expect(state.exists()).toBe(true); + expect(zipCode.exists()).toBe(true); }); - it("Should call the watch handler for the address model when the addressModel is changed", async () => { - // Arrange - const wrapper = shallowMount(addressQuestions, { - propsData: { - modelValue: addressModel, + test("Should set this.displayNoMatchWarning to false when it is set to true, if the model if prepopulated", async () => { + // Arrange + const newAddressModel = { + streetAddress: "foo", + city: "foo", + state: "foo", + zipCode: "55555", + }; + const wrapper = shallowMount(addressQuestions, { + propsData: { + modelValue: newAddressModel, + }, + }); + + await wrapper.setData({ + displayNoMatchWarning: true + }) + expect(wrapper.vm.displayNoMatchWarning).toBeTruthy(); + + // Act + wrapper.vm.$options.watch.addressModel.handler.call(wrapper.vm, wrapper.vm.addressModel); + + // Assert + expect(wrapper.vm.displayNoMatchWarning).toBeFalsy(); + }); + + test("Should it set this.showAddressFields to true when the model is prepopulated", async () => { + // Arrange + // Act + const newAddressModel = { + streetAddress: "foo", + city: "foo", + state: "foo", + zipCode: "55555", + }; + const wrapper = shallowMount(addressQuestions, { + propsData: { + modelValue: newAddressModel, + }, + }); + + // Act + wrapper.vm.setupAddressLookup(); + + // Assert + expect(wrapper.vm.showAddressFields).toBe(true); + + }); + }); + + describe("happy paths", () => { + test("full street address is passed in => address fields are displayed", async () => { + // Arrange/Act + const { wrapper } = setupMocks({ + props: { + modelValue: { + streetAddress: "12345 Test Road", + city: "Tests", + state: "OH", + zipCode: "12312" + } + } + }); + + await wrapper.vm.$nextTick(); + + // Assert + const cityField = wrapper.findComponent({ ref: "city" }); + const stateField = wrapper.findComponent({ ref: "state" }); + const zipField = wrapper.findComponent({ ref: "zipCode" }); + expect(cityField.exists()).toBeTruthy(); + expect(cityField.isVisible()).toBeTruthy(); + expect(stateField.exists()).toBeTruthy(); + expect(cityField.isVisible()).toBeTruthy(); + expect(zipField.exists()).toBeTruthy(); + expect(cityField.isVisible()).toBeTruthy(); + }); + + test("full street address is passed in => don't load Google Autocomplete script", async () => { + // Arrange/Act + const { wrapper } = setupMocks({ + props: { + modelValue: { + streetAddress: "12345 Test Road", + city: "Tests", + state: "OH", + zipCode: "12312" + } + } + }); + + await wrapper.vm.$nextTick(); + + // Assert + expect(wrapper.vm.$loadScript).not.toHaveBeenCalled(); + }); + + test("address field is focused => disable autocomplete", async () => { + // Arrange + let focusEventCallbackFunction; + autocompleteElement.addEventListener = jest.fn().mockImplementation((eventName, callbackFunction) => { + if (eventName == "focus") { + focusEventCallbackFunction = callbackFunction; + } + }); + const { wrapper } = setupMocks({}); + await wrapper.vm.$nextTick(); + + // Act + focusEventCallbackFunction(); + await wrapper.vm.$nextTick(); + + // Assert + expect(autocompleteElement.getAttribute("autocomplete")).toEqual("do-not-autofill"); + }); + + test("street address is entered, user chooses good result from autocomplete results => other fields are filled in", async () => { + // Arrange + const { wrapper } = setupMocks({}); + await wrapper.setData({ + addressModel: { + streetAddress: "123 Test Street" + } + }) + + const selectedPlace = { + address_components: [ + { + long_name: "1234", + short_name: "1234", + types: ["street_number"] + }, + { + long_name: "Test Road", + short_name: "Test Road", + types: ["route"] + }, + { + long_name: "East Columbus", + short_name: "Columbus", + types: ["neighborhood", "political"] + }, + { + long_name: "Columbus", + short_name: "Columbus", + types: ["locality", "political"] + }, + { + long_name: "Franklin County", + short_name: "Franklin County", + types: ["administrative_area_level_2", "political"] + }, + { + long_name: "Ohio", + short_name: "OH", + types: ["administrative_area_level_1", "political"] + }, + { + long_name: "United States", + short_name: "US", + types: ["country", "political"] + }, + { + long_name: "43215", + short_name: "43215", + types: ["postal_code"] + }, + ] + } + + // Act + autocompleteElement.dispatchEvent(new CustomEvent("place_changed", { detail: selectedPlace })); + + // Assert + const addressModel = wrapper.vm.addressModel; + expect(addressModel.streetAddress).toEqual("1234 Test Road"); + expect(addressModel.city).toEqual("Columbus"); + expect(addressModel.state).toEqual("OH"); + expect(addressModel.zipCode).toEqual("43215"); + }) + + test("street address is entered, user chooses good result from autocomplete results => alerts are cleared", async () => { + // Arrange + const { wrapper } = setupMocks({}); + await wrapper.setData({ + addressModel: { + streetAddress: "123 Test Street" + }, + displayVerificationWarning: true, + displayNoMatchWarning: true + }) + + let alerts = wrapper.findAllComponents(alert); + alerts.forEach(alert => expect(alert.isVisible()).toBeTruthy()); + + const selectedPlace = { + address_components: [ + { + long_name: "1234", + short_name: "1234", + types: ["street_number"] + }, + { + long_name: "Test Road", + short_name: "Test Road", + types: ["route"] + }, + { + long_name: "East Columbus", + short_name: "Columbus", + types: ["neighborhood", "political"] + }, + { + long_name: "Columbus", + short_name: "Columbus", + types: ["locality", "political"] + }, + { + long_name: "Franklin County", + short_name: "Franklin County", + types: ["administrative_area_level_2", "political"] + }, + { + long_name: "Ohio", + short_name: "OH", + types: ["administrative_area_level_1", "political"] + }, + { + long_name: "United States", + short_name: "US", + types: ["country", "political"] + }, + { + long_name: "43215", + short_name: "43215", + types: ["postal_code"] + }, + ] + } + + // Act + autocompleteElement.dispatchEvent(new CustomEvent("place_changed", { detail: selectedPlace })); + await wrapper.vm.$nextTick(); + + // Assert + alerts = wrapper.findAllComponents(alert); + alerts.forEach(alert => expect(alert.exists()).toBeFalsy()); + }); + + test("street address is entered, but user clicks away => first result is selected and other fields are filled in", async () => { + // Arrange + let changeEventCallbackFunction; + autocompleteElement.addEventListener = jest.fn().mockImplementation((eventName, callbackFunction) => { + if (eventName == "change") { + changeEventCallbackFunction = callbackFunction; + } + }); + + const { wrapper } = setupMocks({ + querySelectorFunction: function (query) { + if (query == ".pac-container .pac-item") { + let element = document.createElement("div"); + element.textContent = "123 Test Street" + return element; + } + }, + geocoderResult: { + address_components: [ + { + long_name: "1234", + short_name: "1234", + types: ["street_number"] }, + { + long_name: "Test Road", + short_name: "Test Road", + types: ["route"] + }, + { + long_name: "East Columbus", + short_name: "Columbus", + types: ["neighborhood", "political"] + }, + { + long_name: "Columbus", + short_name: "Columbus", + types: ["locality", "political"] + }, + { + long_name: "Franklin County", + short_name: "Franklin County", + types: ["administrative_area_level_2", "political"] + }, + { + long_name: "Ohio", + short_name: "OH", + types: ["administrative_area_level_1", "political"] + }, + { + long_name: "United States", + short_name: "US", + types: ["country", "political"] + }, + { + long_name: "43215", + short_name: "43215", + types: ["postal_code"] + }, + ] + } + }); + + let noMatchAlert = wrapper.findComponent({ ref: "alertNoMatchWarning" }); + let verificationAlert = wrapper.findComponent({ ref: "alertVerificationWarning" }); + expect(noMatchAlert.exists()).toBeFalsy(); + expect(verificationAlert.exists()).toBeFalsy(); + + await wrapper.vm.$nextTick(); + + // Act + changeEventCallbackFunction(); + + await wrapper.vm.$nextTick(); + + // Assert + const addressModel = wrapper.vm.addressModel; + expect(addressModel.streetAddress).toEqual("1234 Test Road"); + expect(addressModel.city).toEqual("Columbus"); + expect(addressModel.state).toEqual("OH"); + expect(addressModel.zipCode).toEqual("43215"); + }); + }); + + describe("alerts", () => { + const places = [null, { address_components: null }, undefined, {}]; + test.each(places)("selected place/place properties is null => display verification alert", async (place) => { + // Arrange + const { wrapper } = setupMocks({}); + await wrapper.setData({ + addressModel: { + streetAddress: "123 Test Street" + }, + displayVerificationWarning: true, + displayNoMatchWarning: true + }) + + let alerts = wrapper.findAllComponents(alert); + alerts.forEach(alert => expect(alert.isVisible()).toBeTruthy()); + + const selectedPlace = place; + + // Act + autocompleteElement.dispatchEvent(new CustomEvent("place_changed", { detail: selectedPlace })); + await wrapper.vm.$nextTick(); + + // Assert + const verificationAlert = wrapper.findComponent({ ref: "alertVerificationWarning" }); + expect(verificationAlert.exists()).toBe(true); + expect(verificationAlert.isVisible()).toBe(true); + const noMatchAlert = wrapper.findComponent({ ref: "alertNoMatchWarning" }); + expect(noMatchAlert.exists()).toBe(false); + }); + + test("user enters address that yields no autocomplete results => show noMatch alert", async () => { + // Arrange + let changeEventCallbackFunction; + autocompleteElement.addEventListener = jest.fn().mockImplementation((eventName, callbackFunction) => { + if (eventName == "change") { + changeEventCallbackFunction = callbackFunction; + } + }); + + const { wrapper } = setupMocks({}); + + let noMatchAlert = wrapper.findComponent({ ref: "alertNoMatchWarning" }); + expect(noMatchAlert.exists()).toBeFalsy(); + + await wrapper.vm.$nextTick(); + + // Act + changeEventCallbackFunction(); + + await wrapper.vm.$nextTick(); + + // Assert + expect(wrapper.vm.displayNoMatchWarning).toBeTruthy(); + noMatchAlert = wrapper.findComponent({ ref: "alertNoMatchWarning" }); + expect(noMatchAlert.exists()).toBeTruthy(); + expect(noMatchAlert.isVisible()).toBeTruthy(); + }); + + test("user enters address that yields autocomplete results, but doesn't select => show verification alert", async () => { + // Arrange + let changeEventCallbackFunction; + autocompleteElement.addEventListener = jest.fn().mockImplementation((eventName, callbackFunction) => { + if (eventName == "change") { + changeEventCallbackFunction = callbackFunction; + } + }); + + const { wrapper } = setupMocks({ + querySelectorFunction: function (query) { + if (query == ".pac-container .pac-item") { + let element = document.createElement("div"); + element.textContent = "123 Test Street" + return element; + } + } + }); + + let noMatchAlert = wrapper.findComponent({ ref: "alertNoMatchWarning" }); + let verificationAlert = wrapper.findComponent({ ref: "alertVerificationWarning" }); + expect(noMatchAlert.exists()).toBeFalsy(); + expect(verificationAlert.exists()).toBeFalsy(); + + await wrapper.vm.$nextTick(); + + // Act + changeEventCallbackFunction(); + + await wrapper.vm.$nextTick(); + + // Assert + verificationAlert = wrapper.findComponent({ ref: "alertVerificationWarning" }); + expect(wrapper.vm.displayVerificationWarning).toBeTruthy(); + expect(verificationAlert.exists()).toBeTruthy(); + expect(verificationAlert.isVisible()).toBeTruthy(); + noMatchAlert = wrapper.findComponent({ ref: "alertNoMatchWarning" }); + expect(wrapper.vm.displayNoMatchWarning).toBeFalsy(); + expect(noMatchAlert.exists()).toBeFalsy(); + }); + + describe("noMatch alert is cleared on address change", () => { + test("user sees noMatch warning and enters city => noMatch warning is removed", async () => { + // Arrange + const { wrapper } = setupMocks({}); + + wrapper.setData({ + displayNoMatchWarning: true }); - wrapper.vm.displayNoMatchWarning = true; + await wrapper.vm.$nextTick(); + + let noMatchAlert = wrapper.findComponent({ ref: "alertNoMatchWarning" }); + expect(noMatchAlert.exists()).toBeTruthy(); + expect(noMatchAlert.isVisible()).toBeTruthy(); // Act - const newAddressModel = { - streetAddress: "foo", - city: "foo", - state: "foo", - zipCode: "55555", - }; - - wrapper.vm.$options.watch.addressModel.handler.call(wrapper.vm, newAddressModel); + wrapper.vm.$options.watch.addressModel.handler.call(wrapper.vm, { + city: "Somewhere" + }) + await wrapper.vm.$nextTick(); // Assert - expect(wrapper.vm.addressModel.handler).toHaveBeenCalled; + expect(wrapper.vm.displayNoMatchWarning).toBeFalsy(); + noMatchAlert = wrapper.findComponent({ ref: "alertNoMatchWarning" }); + expect(noMatchAlert.exists()).toBeFalsy(); + }); - }); + test("user sees noMatch warning and enters state => noMatch warning is removed", async () => { + // Arrange + const { wrapper } = setupMocks({}); - it("Should set this.displayNoMatchWarning to false, if the model changes when it is set to true", async () => { - // Arrange - const wrapper = shallowMount(addressQuestions, { - propsData: { - modelValue: addressModel, - }, + wrapper.setData({ + displayNoMatchWarning: true }); - wrapper.vm.displayNoMatchWarning = true; - - // Act - const newAddressModel = { - streetAddress: "foo", - city: "", - state: "", - zipCode: "", - }; + await wrapper.vm.$nextTick(); + + let noMatchAlert = wrapper.findComponent({ ref: "alertNoMatchWarning" }); + expect(noMatchAlert.exists()).toBeTruthy(); + expect(noMatchAlert.isVisible()).toBeTruthy(); // Act - wrapper.vm.$options.watch.addressModel.handler.call(wrapper.vm, newAddressModel); + wrapper.vm.$options.watch.addressModel.handler.call(wrapper.vm, { + state: "KO" + }) + await wrapper.vm.$nextTick(); // Assert - expect(wrapper.vm.displayNoMatchWarning === false); + expect(wrapper.vm.displayNoMatchWarning).toBeFalsy(); + noMatchAlert = wrapper.findComponent({ ref: "alertNoMatchWarning" }); + expect(noMatchAlert.exists()).toBeFalsy(); + }); - }); - - it("Should it not should set this.displayNoMatchWarning to false when it is set to true, if the model if prepopulated", async () => { - // Arrange - const wrapper = shallowMount(addressQuestions, { - propsData: { - modelValue: addressModel, - }, + test("user sees noMatch warning and enters zip code => noMatch warning is removed", async () => { + // Arrange + const { wrapper } = setupMocks({}); + + wrapper.setData({ + displayNoMatchWarning: true }); - wrapper.vm.displayNoMatchWarning = true; - - // Act - const newAddressModel = { - streetAddress: "foo", - city: "foo", - state: "foo", - zipCode: "55555", - }; + await wrapper.vm.$nextTick(); + + let noMatchAlert = wrapper.findComponent({ ref: "alertNoMatchWarning" }); + expect(noMatchAlert.exists()).toBeTruthy(); + expect(noMatchAlert.isVisible()).toBeTruthy(); // Act - wrapper.vm.$options.watch.addressModel.handler.call(wrapper.vm, newAddressModel); + wrapper.vm.$options.watch.addressModel.handler.call(wrapper.vm, { + zipCode: "12345" + }) + await wrapper.vm.$nextTick(); // Assert - expect(wrapper.vm.displayNoMatchWarning === true); + expect(wrapper.vm.displayNoMatchWarning).toBeFalsy(); + noMatchAlert = wrapper.findComponent({ ref: "alertNoMatchWarning" }); + expect(noMatchAlert.exists()).toBeFalsy(); + }); + }); + }); +}); - }); +function setupMocks({ mountOptions, props, isShallowMount = true, querySelectorFunction, geocoderResult = ["1234 Test Street"] }) { + store.commit(storeMutations.RESET_STATE); - it("Should it set this.showAddressFields to true when the model is prepopulated", async () => { - // Arrange - const wrapper = shallowMount(addressQuestions, { - propsData: { - modelValue: addressModel, - }, - }); - wrapper.vm.displayNoMatchWarning = true; - - // Act - const newAddressModel = { - streetAddress: "foo", - city: "foo", - state: "foo", - zipCode: "55555", - }; + const resultingMountOptions = getMountOptions({ + ...mountOptions, + router: { + navigate: jest.fn(), + navigateAfterSave: jest.fn() + }, + loadScript: jest.fn().mockResolvedValue() + }); - // Act - wrapper.vm.setupAddressLookup(); + window.google = { + maps: { + event: { + addListener: jest.fn().mockImplementation((element, eventName, callbackFunction) => { + function interceptedCallbackFunction(e) { + callbackFunction(e.detail); + } + // selectedPlace = "Woogly"; + element.addEventListener(eventName, interceptedCallbackFunction); + }), + removeListener: jest.fn(), + clearInstanceListeners: jest.fn() + }, + places: { + Autocomplete: jest.fn().mockImplementation((el) => el) + }, + Geocoder: class Geocoder { + // constructor(); - // Assert - expect(wrapper.vm.showAddressFields).toBe(true); + geocode(request, callback) { + callback([geocoderResult], true) + } + }, + GeocoderStatus: { + OK: true + } + } + }; - }); + if (props) + resultingMountOptions.propsData = props; -}) \ No newline at end of file + const wrapper = isShallowMount ? shallowMount(addressQuestions, resultingMountOptions) : mount(addressQuestions, resultingMountOptions); + document.querySelector = jest.fn().mockImplementation(query => { + let result = null; + if (query == ".pac-container") + result = document.createElement("div"); + else if (querySelectorFunction) { + result = querySelectorFunction(query); + } + + return result ?? null; + }); + + return { wrapper }; +} \ No newline at end of file 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 61062ba47..d93c2559b 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 @@ -185,11 +185,10 @@ export default ({ }, methods: { setupAddressLookup() { - - if (this.addressModel.streetAddress !== null & - this.addressModel.city !== null & - this.addressModel.state !== null & - this.addressModel.zipCode !== null) { + if (this.addressModel.streetAddress && + this.addressModel.city && + this.addressModel.state && + this.addressModel.zipCode) { this.showAddressFields = true; return; @@ -228,10 +227,9 @@ export default ({ if (autocompleteResultsContainer) { streetAddressField.appendChild(autocompleteResultsContainer); } - }) - addressField1.onchange = function() { + addressField1.addEventListener("change", () => { 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) { @@ -258,7 +256,7 @@ export default ({ self.displayNoMatchWarning = true; } } - }; + }); function fillInAddress(place) { if (!place) { @@ -310,7 +308,6 @@ export default ({ addressField1.onchange = null; const pacContainer = document.querySelector(".pac-container"); pacContainer.remove(); - } }) .catch(() => { @@ -325,16 +322,10 @@ export default ({ watch: { addressModel: { handler(newValue) { - // The first time the address model changes is when the page first loads - if (!newValue.city && - !newValue.state && - !newValue.zipCode) { - return; - } - - if (this.displayNoMatchWarning === true) { + // Clear no match warning on address change + if (newValue.city || newValue.state || newValue.zipCode) { this.displayNoMatchWarning = false; - } + } }, deep: true } 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/estimate/estimate.spec.js b/src/layouts/estimate/estimate.spec.js new file mode 100644 index 000000000..5f82fa508 --- /dev/null +++ b/src/layouts/estimate/estimate.spec.js @@ -0,0 +1,187 @@ +//Components +import estimate from "@/layouts/estimate/estimate.vue"; +import { shallowMount } from "@vue/test-utils"; +import { getMountOptions } from "@/helpers/unit-test-helper.js"; + +//Supporting files +import { storeKey } from "vuex"; +import store from "@/store"; +import { storeMutations } from "@/constants/store-mutations"; +import { settleAllPromises } from "@/helpers/layout-helper.js"; +import { fetchCmsContentForPage } from "@/helpers/cms-content-helper"; +import baseMixin from "../../mixins/base-mixin"; +import { vinLookupMethodSelections } from "@/constants/vin-lookup-method-selections.js"; + + +// 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("estimate.vue", () => { + test("Selected vin option is emitted upon selection.", async () => { + + //Arrange + const { wrapper } = setupMocks({ modelValueProp: ["Provide my VIN manually most specific to your vehicle"] }); + + //Act + wrapper.setValue({ modelValue: ["Provide my license plate # Most accurate VIN match"] }); + await wrapper.vm.$nextTick(); + + //Assesrt + expect(wrapper.emitted()["update:modelValue"][0]).toEqual([{ modelValue: ["Provide my license plate # Most accurate VIN match"] }]); + }); + test("isRepair is set to true, arePagePrerequisitesValid should return true", async () => { + + //Arrange + const { wrapper } = setupMocks({}); + + //Act + store.commit( storeMutations.UPDATE_IS_REPAIR, true ); + + let arePagePrerequisitesValid = wrapper.vm.arePagePrerequisitesValid(); + + //Assert + expect(arePagePrerequisitesValid).toBe(true); + }); + test("isRepair is set to false, arePagePrerequisitesValid should return true", async () => { + + //Arrange + const { wrapper } = setupMocks({}); + + //Act + store.commit( storeMutations.UPDATE_IS_REPAIR, false ); + + let arePagePrerequisitesValid = wrapper.vm.arePagePrerequisitesValid(); + + //Assert + expect(arePagePrerequisitesValid).toBe(true); + }); + test("isRepair is set to null, arePagePrerequisitesValid should return false", async () => { + + //Arrange + const { wrapper } = setupMocks({}); + + //Act + store.commit( storeMutations.UPDATE_IS_REPAIR, null ); + + let arePagePrerequisitesValid = wrapper.vm.arePagePrerequisitesValid(); + + //Assert + expect(arePagePrerequisitesValid).toBe(false); + }); + + test("After selecting provide my home address on ForwardButtonAction triggers a router.navigate", async () => { + + //Arrange + const { wrapper } = setupMocks({}); + await wrapper.setData({ + selectedValues: [vinLookupMethodSelections.HOMEADDRESS] + }) + + //Act + wrapper.vm.forwardButtonAction(); + + //Assert + expect(wrapper.vm.$router.navigate).toHaveBeenCalled(); + + }); + + test("After selecting provide my manual vin on ForwardButtonAction triggers a router.navigate", async () => { + + //Arrange + const { wrapper } = setupMocks({}); + await wrapper.setData({ + selectedValues: [vinLookupMethodSelections.MANUALVIN] + }) + + //Act + wrapper.vm.forwardButtonAction(); + + //Assert + expect(wrapper.vm.$router.navigate).toHaveBeenCalled(); + + }); + + test("BackButtonAction triggers a router.navigate change", async () => { + + //Arrange + const { wrapper } = setupMocks({}); + + //Act + estimate.beforeRouteEnter.call( + wrapper.vm, + { query: { fmgPage: "estimate" } }, + undefined, + (c) => c(wrapper.vm) + ); + + wrapper.vm.backButtonAction(); + + //Assert + expect(wrapper.vm.$router.navigate).toHaveBeenCalled(); + }); + + test("Provide my license plate on ForwardButtonAction triggers a router.navigate", async () => { + + //Arrange + const { wrapper } = setupMocks({}); + await wrapper.setData({ + selectedValues: [vinLookupMethodSelections.LICENSEPLATE] + }) + + //Act + wrapper.vm.forwardButtonAction(); + + //Assert + expect(wrapper.vm.$router.navigate).toHaveBeenCalled(); + + }); + +}); + +function setupMocks({ + modelValueProp = ["Provide my VIN manually most specific to your vehicle"], + isMultiSelect = false, + groupName = "estimate", + cmsQuestionText = "Let's get your VIN. Or we can look it up for you!", + cmsAnswers = [{ Name: "Provide my VIN manually Most specific to your vehicle" }, { Name: "Provide my license plate # Most accurate VIN match" }, { Name: "Provide my home address Most convenient VIN match" }], + dataFromApi = [], + mountOptionsMockData = { + router: { + navigate: jest.fn(), + }, + }, +}) { + + //Mock CMS Content + const cmsContent = { + groupName: groupName, + QuestionText: cmsQuestionText, + Answers: cmsAnswers + }; + + //Mock props + const mockMixin = { + methods: { + getCmsContent: jest.fn() + } + } + + const apiPromise = Promise.resolve(cmsContent); + settleAllPromises.mockImplementation(() => apiPromise); + fetchCmsContentForPage.mockImplementation(() => Promise.resolve()); + + const mountOptions = getMountOptions({ ...mountOptionsMockData, mixins: [baseMixin] }); + mountOptions['attachTo'] = document.body; + + const wrapper = shallowMount(estimate, mountOptions); + + return { wrapper, apiPromise }; + +} \ No newline at end of file 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 3d0843e10..1c580f4a6 100644 --- a/src/layouts/vin-lookup/vin-lookup.spec.js +++ b/src/layouts/vin-lookup/vin-lookup.spec.js @@ -1 +1,261 @@ -test.todo("some test to be written in the future"); +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" + }, + customer: { + emailAddress: "builddigitaltest@safelite.com" + } + }, + payment: { + insuranceCoverage: { + isVerified: true + } + }, + damage: { + glassToReplace: "windshield" + } + }, +})); + +import { getDamageString, getIsWindshieldOnly, isGlassAvailableForCarId } from "@/helpers/damage-helper"; + +jest.mock("@/helpers/damage-helper", () => ({ + isGlassAvailableForCarId: jest.fn(() => { + return Promise.resolve(); + }), + getIsWindshieldOnly: jest.fn(), + getDamageString: jest.fn(), +})); + + +describe("vin-lookup.vue", () => { + it("Should update the funnel-footer forward button when VIN is changed", (done) => { + //Arrange + const { wrapper } = setupMocks({}); + //Act + 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(); + + // Act + await wrapper.vm.forwardButtonAction(); + + //Assert + expect(wrapper.vm.navigateForward).toHaveBeenCalled(); + }); + + 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 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(); + + // Act + await wrapper.vm.forwardButtonAction(); + + //Assert + expect(wrapper.vm.navigateForward).not.toHaveBeenCalled(); + }); + + 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 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.previouslyEnteredCarId = 'new carId'; + + // Act + await wrapper.vm.forwardButtonAction(); + + //Assert + expect(wrapper.vm.navigateForward).toHaveBeenCalled(); + }); + + it("Should not call navigateForward() if zip service returns a non-serviceable flag", async () => { + // Arrange + const { wrapper } = setupMocks({}); + const zipValidationApiResponse = { + data: { + isServiceable: false + } + }; + + const zipPromise = Promise.resolve(zipValidationApiResponse); + + wrapper.vm.validateZip = jest.fn().mockImplementation(() => zipPromise); + wrapper.vm.navigateForward = jest.fn(); + + wrapper.vm.previouslyEnteredCarId = 'new carId'; + + // Act + await wrapper.vm.forwardButtonAction(); + + //Assert + expect(wrapper.vm.navigateForward).not.toHaveBeenCalled(); + }); + + it("Should not call navigateForward() when forward button is clicked but lookupVehicle errors out.", async () => { + // Arrange + 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.previouslyEnteredCarId = 'new carId'; + + // Act + await wrapper.vm.forwardButtonAction(); + + //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({ + ...customMountOptions + }); + + // Modify/augment default mount options + 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, mountOptions); + mockOutPromises(wrapper); + mockOutStubFunctions(wrapper); + return { wrapper }; +} + +function mockOutPromises(wrapper) { + const zipValidationApiResponse = { + data: { + isServiceable: true + } + }; + const vehicleLookupApiResponse = { + data: { + carId: 'initial carId' + } + }; + + const zipPromise = Promise.resolve(zipValidationApiResponse); + const vinPromise = Promise.resolve(vehicleLookupApiResponse); + + wrapper.vm.validateZip = jest.fn().mockImplementation(() => zipPromise); + wrapper.vm.lookupVehicle = jest.fn().mockImplementation(() => vinPromise); +} + +function mockOutStubFunctions(wrapper) { + wrapper.vm.$refs.funnelFooter.updateButtonText = jest.fn(); + wrapper.vm.$refs.funnelFooter.removeLoader = jest.fn(); +} + +const mockMixin = { + methods: { + getCmsContent: jest.fn(() => "placeholder CMS content"), + } +} \ 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 41d1d8aca..5e6f27799 100644 --- a/src/layouts/vin-lookup/vin-lookup.vue +++ b/src/layouts/vin-lookup/vin-lookup.vue @@ -8,7 +8,7 @@
- @@ -134,7 +134,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"; @@ -143,6 +142,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)); @@ -154,6 +154,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); @@ -313,9 +314,13 @@ export default { this.vinNotFound = true; this.$refs.funnelFooter.removeLoader(); this.noServiceZip = false; - return; + return false; }); + if (!vehicleLookupResponse) { + return; + } + if (!zipValidationResponse.data.isServiceable) { this.customAlertData.zip = this.zip; this.$refs.funnelFooter.removeLoader(); @@ -343,22 +348,21 @@ export default { }, navigateForward(){ - if (this.isCarIdDifferent && !this.isSelectedGlassAvailableForVehicle){ - this.$router.navigateAfterSave(this.navigationScenarios.CLICKED_FORWARD, this.$route, {}, { displayVehicleChangeAlert: true }, {}); + if (this.isCarIdDifferent && !this.isSelectedGlassAvailableForVehicle) { + 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/analytics-mixin.spec.js b/src/mixins/analytics-mixin.spec.js index 7d9cec1bb..f09dd0fc8 100644 --- a/src/mixins/analytics-mixin.spec.js +++ b/src/mixins/analytics-mixin.spec.js @@ -124,6 +124,32 @@ describe("analyticsMixin.js", () => { //Assert expect(obj!=null); }); + test("Obj method name does not include bound", () => { + //Arrange + const obj = {baseMethodName:"testMethodName", data:"testData"}; + const method = {name:"testMethodName", data:"testData" } + const action = "testAction"; + + //Act + analyticsMixin.methods.prependActionToMethod(obj, method, action); + + + //Assert + expect(method.name.startsWith("bound ")).toBe(false); + }); + test("Prepended action does not include bound", () => { + //Arrange + const obj = {baseMethodName:"testMethodName", data:"testData"}; + const method = {name:"testMethodName", data:"testData" } + const action = "testAction"; + + //Act + analyticsMixin.methods.prependActionToMethod(obj, method, action); + + + //Assert + expect(action.startsWith("bound ")).toBe(false); + }); test("analyticsPageEvents returns constants analyticsPageEvents", () => { //Act 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,