diff --git a/jest.config.js b/jest.config.js
index aaf2d9ff8..0a6fbc048 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -26,8 +26,7 @@ module.exports = {
testMatch: ["**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"],
coverageThreshold: {
global: {
- statements: 75,
- // Got the go ahead from Mark to temporarily lower this. Taking out initialize component made the year,make,model and style coverage drop a bit. Once unit tests for license plate lookup, vin lookup and address lookup are in the coverage should go back up to 90
+ statements: 80,
},
},
// Uncomment this to avoid the massive amount of warnings we are getting for onSubmit and onInvalidSubmit
diff --git a/src/constants/analytics.js b/src/constants/analytics.js
index b5b24660d..2b0fc1a8f 100644
--- a/src/constants/analytics.js
+++ b/src/constants/analytics.js
@@ -12,6 +12,7 @@ const GaEvents = {
const GaCategories = {
API_RESPONSE: "Api_Response",
EVOX: "Evox",
+ FUNNEL_ENTRY: "funnel_entry",
};
const GaActions = {
@@ -20,6 +21,7 @@ const GaActions = {
VIF: "vif",
SUBMITTED: "Submitted",
DISPLAYED: "Displayed",
+ ZIP_CODE_PROVIDED: "zip_code_provided",
};
const GaLabels = {
diff --git a/src/constants/store-actions.js b/src/constants/store-actions.js
index c2a297823..fc0daf323 100644
--- a/src/constants/store-actions.js
+++ b/src/constants/store-actions.js
@@ -62,6 +62,7 @@ const storeActions = {
SAVE_VEHICLE_MAKE: "saveVehicleMake",
SAVE_VEHICLE_MODEL: "saveVehicleModel",
SAVE_VEHICLE_STYLE: "saveVehicleStyle",
+ SAVE_VEHICLE: "saveVehicle",
SAVE_VEHICLE_DAMAGE: "saveVehicleDamage",
SAVE_VIN_LOOKUP: "saveVinLookup",
SAVE_SERVICE_ZIP_CODE_INFO: "saveServiceZipCodeInfo",
diff --git a/src/digital-components/button-question/button-question.vue b/src/digital-components/button-question/button-question.vue
index a53958dd5..13c81f815 100644
--- a/src/digital-components/button-question/button-question.vue
+++ b/src/digital-components/button-question/button-question.vue
@@ -74,6 +74,9 @@ https://safelite.atlassian.net/wiki/spaces/DC/pages/76644418/Button+Question+Com
@@ -85,6 +93,7 @@ import textBlock from "@/digital-components/text-block/text-block";
import vehicleReview from "@/layouts/review/review-sections/vehicle-review/vehicle-review";
import damageReview from "@/layouts/review/review-sections/damage-review/damage-review";
import servicePackageReview from "@/layouts/review/review-sections/service-package-review/service-package-review";
+import serviceLocationReview from "@/layouts/review/review-sections/service-location-review/service-location-review";
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import { settleAllPromises } from "@/helpers/layout-helper";
@@ -102,6 +111,8 @@ export default {
storeActions.GET_RAIN_DEFENSE
);
+ const servicePackageInitialDataPromise = servicePackageReview.methods.loadInitialData();
+
// Settle promises and get results
const promiseResultMap = [
{
@@ -109,12 +120,8 @@ export default {
promise: cmsContentPromise,
},
{
- resultKey: "wipers",
- promise: wipersPromise,
- },
- {
- resultKey: "rainDefense",
- promise: rainDefensePromise,
+ resultKey: "servicePackageData",
+ promise: servicePackageInitialDataPromise,
},
];
@@ -123,21 +130,19 @@ export default {
next((vm) => {
vm.setCmsContent(resultMap.cmsContent);
- vm.availableWipers = resultMap.wipers;
- vm.availableRainDefense = [resultMap.rainDefense];
+ vm.$refs.servicePackageReview.initializeComponent(resultMap.servicePackageData);
});
},
data() {
- return {
- availableWipers: null,
- availableRainDefense: null,
- };
+ return {};
},
methods: {
arePagePrerequisitesValid() {
return true;
},
- backButtonAction() {},
+ backButtonAction() {
+ this.$router.navigateWithoutSaving(this.navigationScenarios.CLICKED_BACK, this.$route);
+ },
forwardButtonAction() {},
editVehicle() {
this.$router.navigateWithoutSaving(
@@ -157,6 +162,12 @@ export default {
this.$route
);
},
+ editServiceLocation() {
+ this.$router.navigateWithoutSaving(
+ this.navigationScenarios.CLICKED_SERVICE_LOCATION_EDIT,
+ this.$route
+ );
+ },
},
computed: {
subHeaderTitle() {
@@ -174,12 +185,12 @@ export default {
damageInfo() {
return this.$store.getters.damage;
},
- availableVaps() {
- return [...(this.availableWipers ?? []), ...(this.availableRainDefense ?? [])];
- },
lineItems() {
return this.$store.getters.lineItems;
},
+ serviceLocationInfo() {
+ return this.$store.getters.order.serviceLocation;
+ },
},
components: {
funnelHeader,
@@ -190,6 +201,7 @@ export default {
vehicleReview,
damageReview,
servicePackageReview,
+ serviceLocationReview,
},
};
diff --git a/src/layouts/schedule/schedule.spec.js b/src/layouts/schedule/schedule.spec.js
new file mode 100644
index 000000000..654e0e4c9
--- /dev/null
+++ b/src/layouts/schedule/schedule.spec.js
@@ -0,0 +1,228 @@
+import { shallowMount } from "@vue/test-utils";
+import { getMountOptions } from "@/helpers/unit-test-helper.js";
+import { applicationConfig } from "@/constants/application-config";
+import { storeActions } from "@/constants/store-actions";
+import schedule from "@/layouts/schedule/schedule.vue";
+import store from "@/store";
+import * as navigateToHeritage from "@/helpers/heritage-integration/navigation-helper";
+import { nextTick } from "vue";
+
+jest.mock("@/store", () => ({
+ commit: jest.fn(),
+ dispatch: jest.fn(),
+}));
+
+// Mock fetchCmsContentForPage
+jest.mock("@/helpers/cms-content-helper", () => ({
+ fetchCmsContentForPage: () => Promise.resolve("content"),
+ splitCopyOnCMSPlaceHolder: jest.fn(() => ["A", "B"]),
+}));
+
+jest.mock(
+ "@/store",
+ () => {
+ return {};
+ },
+ { virtual: true }
+);
+
+store.getters = {
+ order: {
+ schedule: {
+ date: "2020-01-01",
+ },
+ lineItems: {
+ glassParts: [],
+ supportingItems: [],
+ },
+ serviceLocation: {
+ appointmentType: "Inshop",
+ },
+ },
+ lineItems: {
+ glassParts: [],
+ supportingItems: [],
+ },
+};
+
+describe("schedule.vue", () => {
+ test("Should navigateWithoutSaving", async () => {
+ //Arrange
+ const { wrapper } = setupMocks({
+ customMountOptions: {
+ router: {
+ navigateWithoutSaving: jest.fn(),
+ },
+ route: { schedule },
+ },
+ });
+
+ wrapper.vm.dispatchStoreAction = jest.fn(() => {
+ return {
+ data: [],
+ };
+ });
+
+ //Act
+ await wrapper.vm.forwardButtonAction();
+
+ //Assert
+ expect(wrapper.vm.$router.navigateWithoutSaving).toHaveBeenCalled();
+ });
+ test("should pass arePagePrerequisitesValid with a mobile order and no providerNumber", () => {
+ //Arrange
+ const { wrapper } = setupMocks({});
+ store.getters = {
+ order: {
+ schedule: {
+ date: "2020-01-01",
+ },
+ serviceLocation: {
+ zipCode: "12345",
+ zipCodeCtu: "value",
+ appointmentType: "Mobile",
+ },
+ damage: {
+ isRepair: true,
+ },
+ referralNumber: "1234567",
+ },
+ payment: {
+ isInsurance: true,
+ },
+ lineItems: {
+ supportingItems: [],
+ },
+ };
+
+ let arePagePrerequisitesValid = wrapper.vm.arePagePrerequisitesValid();
+
+ expect(arePagePrerequisitesValid).toBe(true);
+ });
+ test("should pass arePagePrerequisitesValid with a inshop order and providerNumber", () => {
+ //Arrange
+ const { wrapper } = setupMocks({});
+ store.getters = {
+ order: {
+ schedule: {
+ date: "2020-01-01",
+ },
+ serviceLocation: {
+ zipCode: "12345",
+ zipCodeCtu: "value",
+ appointmentType: "Inshop",
+ provider: {
+ providerNumber: "5",
+ },
+ },
+ damage: {
+ isRepair: true,
+ },
+ referralNumber: "1234567",
+ },
+ payment: {
+ isInsurance: true,
+ },
+ lineItems: {
+ supportingItems: [],
+ },
+ };
+
+ let arePagePrerequisitesValid = wrapper.vm.arePagePrerequisitesValid();
+
+ expect(arePagePrerequisitesValid).toBe(true);
+ });
+ test("should fail arePagePrerequisitesValid with a replace with no glass parts", () => {
+ //Arrange
+ const { wrapper } = setupMocks({});
+ store.getters = {
+ order: {
+ schedule: {
+ date: "2020-01-01",
+ },
+ serviceLocation: {
+ zipCode: "12345",
+ zipCodeCtu: "value",
+ appointmentType: "Inshop",
+ provider: {
+ providerNumber: "5",
+ },
+ },
+ damage: {
+ isRepair: false,
+ },
+ referralNumber: "1234567",
+ },
+ payment: {
+ isInsurance: true,
+ },
+ lineItems: {
+ supportingItems: [],
+ glassParts: [],
+ },
+ };
+
+ let arePagePrerequisitesValid = wrapper.vm.arePagePrerequisitesValid();
+
+ expect(arePagePrerequisitesValid).toBe(false);
+ });
+ test("should fail arePagePrerequisitesValid without isInsurance", () => {
+ //Arrange
+ const { wrapper } = setupMocks({});
+ store.getters = {
+ order: {
+ schedule: {
+ date: "2020-01-01",
+ },
+ serviceLocation: {
+ zipCode: "12345",
+ zipCodeCtu: "value",
+ appointmentType: "Inshop",
+ provider: {
+ providerNumber: "5",
+ },
+ },
+ damage: {
+ isRepair: true,
+ },
+ referralNumber: "1234567",
+ },
+ payment: {
+ isInsurance: null,
+ },
+ lineItems: {
+ supportingItems: [],
+ glassParts: [],
+ },
+ };
+
+ let arePagePrerequisitesValid = wrapper.vm.arePagePrerequisitesValid();
+
+ expect(arePagePrerequisitesValid).toBe(false);
+ });
+});
+
+const mockCmsContent = {};
+
+function setupMocks({ customMountOptions }) {
+ const mountOptions = getMountOptions({
+ ...customMountOptions,
+ });
+
+ mountOptions.global.mocks["$store"] = store;
+ mountOptions["attachTo"] = document.body;
+ mountOptions.mixins = [
+ {
+ methods: {
+ getCmsContent: jest.fn().mockImplementation((widgetName, fieldName) => {
+ if (mockCmsContent[widgetName] && mockCmsContent[widgetName][fieldName])
+ return mockCmsContent[widgetName][fieldName];
+ }),
+ },
+ },
+ ];
+
+ const wrapper = shallowMount(schedule, mountOptions);
+ wrapper.vm.setCmsContent = jest.fn();
+ return { wrapper };
+}
diff --git a/src/layouts/schedule/time-slot-modal-question/time-slot-modal-question.spec.js b/src/layouts/schedule/time-slot-modal-question/time-slot-modal-question.spec.js
new file mode 100644
index 000000000..43d997892
--- /dev/null
+++ b/src/layouts/schedule/time-slot-modal-question/time-slot-modal-question.spec.js
@@ -0,0 +1,712 @@
+import { shallowMount, flushPromises } from "@vue/test-utils";
+import { getMountOptions } from "@/helpers/unit-test-helper.js";
+import timeSlotModalQuestion from "@/layouts/schedule/time-slot-modal-question/time-slot-modal-question.vue";
+import store from "@/store";
+import {
+ RouteCodeFlags,
+ PREMIUM_TIME_SLOT_ID_FLAG,
+ PREMIUM_FEE_PART_TYPE,
+} from "@/constants/schedule-constants";
+
+describe("time-slot-modal-list-button-question.vue", () => {
+ test("When selected time slot is changed, a correctly formatted selectedTimeSlot should be emitted", async () => {
+ //Arrange
+ const date = "2023-07-01";
+ const timeSlotId = "testRouteCodeId";
+ const startTime = "8:00 AM";
+ const endTime = "8:30 AM";
+ const jobMaxMinutes = 100;
+ const expectedEmit = [
+ [
+ {
+ date: date,
+ routeCode: timeSlotId,
+ startTime: startTime,
+ endTime: endTime,
+ jobMaxMinutes: jobMaxMinutes.toString(),
+ },
+ ],
+ ];
+
+ const { wrapper } = setupMocks({
+ customMountOptions: {
+ propsData: {
+ estimatedServiceMinutesMaximum: jobMaxMinutes,
+ dateAndTimeSlotData: {
+ date: date,
+ timeSlots: [
+ {
+ id: timeSlotId,
+ startTime: startTime,
+ endTime: endTime,
+ },
+ ],
+ },
+ },
+ },
+ });
+
+ wrapper.vm.$refs.timeSlots.closeModal = jest.fn();
+ wrapper.vm.selectedTimeSlotId = timeSlotId;
+
+ //Act
+ wrapper.vm.setSelectedTimeSlot();
+
+ //Assert
+ expect(wrapper.emitted()["update:modelValue"]).toEqual(expectedEmit);
+ });
+});
+describe("time-slot-modal-list-button-question.vue Supplemental information", () => {
+ test("The correct supplemental information should be obtained from CMS - Mobile - Not Premium ", async () => {
+ //Arrange
+ const mobileCmsWidgetName = "MobileNotPremium";
+ const { wrapper } = setupMocks({
+ customMountOptions: {
+ propsData: {
+ modelValue: { routeCode: "test" },
+ appointmentType: "Mobile",
+ mobileCmsWidgetName: mobileCmsWidgetName,
+ },
+ },
+ });
+ //Assert
+ expect(wrapper.vm.supplementalInformationBlock).toEqual(
+ mockCmsContent[mobileCmsWidgetName]["BodyText"]
+ );
+ });
+ test("The correct supplemental information should be obtained from CMS - Mobile - Premium", async () => {
+ //Arrange
+ const mobileCmsWidgetName = "MobilePremium";
+ const { wrapper } = setupMocks({
+ customMountOptions: {
+ propsData: {
+ modelValue: { routeCode: "test" + PREMIUM_TIME_SLOT_ID_FLAG },
+ appointmentType: "Mobile",
+ mobileCmsWidgetName: mobileCmsWidgetName,
+ },
+ },
+ });
+ //Assert
+ expect(wrapper.vm.supplementalInformationBlock).toEqual(
+ mockCmsContent[mobileCmsWidgetName]["BodyText"]
+ );
+ });
+ test("No supplemental information should be obtained from CMS - Inshop", async () => {
+ //Arrange
+ const inshopWidgetName = "Inshop";
+ const { wrapper } = setupMocks({
+ customMountOptions: {
+ propsData: {
+ modelValue: { routeCode: "test" },
+ appointmentType: "Inshop",
+ cmsWidgetName: inshopWidgetName,
+ },
+ },
+ });
+ //Assert
+ expect(wrapper.vm.supplementalInformationBlock).toBeNull();
+ });
+ test("No supplemental information should be obtained from CMS - Drop off, nothing selected", async () => {
+ //Arrange
+ const dropOffWidgetName = "Dropoff";
+ const { wrapper } = setupMocks({
+ customMountOptions: {
+ propsData: {
+ modelValue: null,
+ appointmentType: "Dropoff",
+ cmsWidgetName: dropOffWidgetName,
+ },
+ },
+ });
+ //Assert
+ expect(wrapper.vm.supplementalInformationBlock).toBeNull();
+ });
+ test("Correct supplemental information should be retrieved, Dropoff regular", async () => {
+ //Arrange
+ const dropOffWidgetName = "Dropoff";
+ const { wrapper } = setupMocks({
+ customMountOptions: {
+ propsData: {
+ modelValue: { routeCode: "Test" + RouteCodeFlags.ALL_DAY_DROP_OFF },
+ appointmentType: "Dropoff",
+ dropoffCmsWidgetName: dropOffWidgetName,
+ },
+ },
+ });
+ //Assert
+ expect(wrapper.vm.supplementalInformationBlock).toEqual(
+ mockCmsContent[dropOffWidgetName]["BodyText"]
+ );
+ });
+ test("Correct supplemental information should be retrieved, Dropoff Overnight", async () => {
+ //Arrange
+ const overnightDropOffCmsWidgetName = "Overnight";
+ const { wrapper } = setupMocks({
+ customMountOptions: {
+ propsData: {
+ modelValue: { routeCode: "Test" + RouteCodeFlags.OVERNIGHT_DROP_OFF },
+ appointmentType: "Dropoff",
+ overnightDropOffCmsWidgetName: overnightDropOffCmsWidgetName,
+ },
+ },
+ });
+ //Assert
+ expect(wrapper.vm.supplementalInformationBlock).toEqual(
+ mockCmsContent[overnightDropOffCmsWidgetName]["BodyText"]
+ );
+ });
+ test("Correct supplemental information should be retrieved, Dropoff Sameday", async () => {
+ //Arrange
+ const sameDayDropOffCmsWidgetName = "Sameday";
+ const date = new Date().toISOString().split("T")[0];
+ const timeSlotId = "testRouteCodeId";
+ const startTime = "8:00 AM";
+ const endTime = "8:30 AM";
+ const jobMaxMinutes = 100;
+
+ const { wrapper } = setupMocks({
+ customMountOptions: {
+ propsData: {
+ estimatedServiceMinutesMaximum: jobMaxMinutes,
+ dateAndTimeSlotData: {
+ date: date,
+ timeSlots: [
+ {
+ id: timeSlotId,
+ startTime: startTime,
+ endTime: endTime,
+ },
+ ],
+ },
+ modelValue: { routeCode: "Test" + RouteCodeFlags.ALL_DAY_DROP_OFF },
+ appointmentType: "Dropoff",
+ sameDayDropOffCmsWidgetName: sameDayDropOffCmsWidgetName,
+ },
+ },
+ });
+
+ //Assert
+ expect(wrapper.vm.supplementalInformationBlock).toEqual(
+ mockCmsContent[sameDayDropOffCmsWidgetName]["BodyText"]
+ );
+ });
+});
+
+describe("time-slot-modal-list-button-question.vue Disclaimer", () => {
+ test("The correct disclaimer information should be obtained from CMS - Dropoff - Sameday ", async () => {
+ //Arrange
+ const sameDayDropOffCmsWidgetName = "Sameday";
+ const date = new Date().toISOString().split("T")[0];
+ const timeSlotId = "testRouteCodeId";
+ const startTime = "8:00 AM";
+ const endTime = "8:30 AM";
+ const jobMaxMinutes = 100;
+
+ const { wrapper } = setupMocks({
+ customMountOptions: {
+ propsData: {
+ estimatedServiceMinutesMaximum: jobMaxMinutes,
+ dateAndTimeSlotData: {
+ date: date,
+ timeSlots: [
+ {
+ id: timeSlotId,
+ startTime: startTime,
+ endTime: endTime,
+ },
+ ],
+ },
+ modelValue: { routeCode: "Test" + RouteCodeFlags.ALL_DAY_DROP_OFF },
+ appointmentType: "Dropoff",
+ sameDayDropOffCmsWidgetName: sameDayDropOffCmsWidgetName,
+ },
+ },
+ });
+
+ //Assert
+ expect(wrapper.vm.disclaimerTextBlockCopy).toEqual(
+ mockCmsContent[sameDayDropOffCmsWidgetName]["FooterText"]
+ );
+ });
+ test("Correct disclaimer information should be retrieved, Dropoff regular", async () => {
+ //Arrange
+ const dropOffWidgetName = "Dropoff";
+ const { wrapper } = setupMocks({
+ customMountOptions: {
+ propsData: {
+ modelValue: { routeCode: "Test" + RouteCodeFlags.ALL_DAY_DROP_OFF },
+ appointmentType: "Dropoff",
+ dropoffCmsWidgetName: dropOffWidgetName,
+ },
+ },
+ });
+ //Assert
+ expect(wrapper.vm.disclaimerTextBlockCopy).toEqual(
+ mockCmsContent[dropOffWidgetName]["FooterText"]
+ );
+ });
+ test("Correct disclaimer information should be retrieved, Dropoff overnight", async () => {
+ //Arrange
+ const overnightDropOffCmsWidgetName = "Overnight";
+ const { wrapper } = setupMocks({
+ customMountOptions: {
+ propsData: {
+ modelValue: { routeCode: "Test" + RouteCodeFlags.OVERNIGHT_DROP_OFF },
+ appointmentType: "Dropoff",
+ overnightDropOffCmsWidgetName: overnightDropOffCmsWidgetName,
+ },
+ },
+ });
+ //Assert
+ expect(wrapper.vm.disclaimerTextBlockCopy).toEqual(
+ mockCmsContent[overnightDropOffCmsWidgetName]["FooterText"]
+ );
+ });
+ test("No disclaimer information should be retrieved, non-dropoff", async () => {
+ //Arrange
+ const inshopWidgetName = "Inshop";
+ const { wrapper } = setupMocks({
+ customMountOptions: {
+ propsData: {
+ modelValue: { routeCode: "test" },
+ appointmentType: "Inshop",
+ cmsWidgetName: inshopWidgetName,
+ },
+ },
+ });
+ //Assert
+ expect(wrapper.vm.disclaimerTextBlockCopy).toBeNull();
+ });
+});
+
+describe("time-slot-modal-list-button-question.vue Duration", () => {
+ test("The correct duration information should be displayed - Dropoff - Sameday ", async () => {
+ //Arrange
+ const sameDayDropOffCmsWidgetName = "Sameday";
+ const date = new Date().toISOString().split("T")[0];
+ const timeSlotId = "testRouteCodeId";
+ const startTime = "8:00 AM";
+ const endTime = "8:30 AM";
+ const jobMaxMinutes = 100;
+
+ const { wrapper } = setupMocks({
+ customMountOptions: {
+ propsData: {
+ estimatedServiceMinutesMaximum: jobMaxMinutes,
+ dateAndTimeSlotData: {
+ date: date,
+ timeSlots: [
+ {
+ id: timeSlotId,
+ startTime: startTime,
+ endTime: endTime,
+ },
+ ],
+ },
+ modelValue: { routeCode: "Test" + RouteCodeFlags.ALL_DAY_DROP_OFF },
+ appointmentType: "Dropoff",
+ sameDayDropOffCmsWidgetName: sameDayDropOffCmsWidgetName,
+ },
+ },
+ });
+
+ //Assert
+ expect(wrapper.vm.durationTextBlockCopy).toEqual(
+ mockCmsContent[sameDayDropOffCmsWidgetName]["SubheaderText"]
+ );
+ });
+ test("Correct duration information should be retrieved, Dropoff overnight", async () => {
+ //Arrange
+ const overnightDropOffCmsWidgetName = "Overnight";
+ const { wrapper } = setupMocks({
+ customMountOptions: {
+ propsData: {
+ modelValue: { routeCode: "Test" + RouteCodeFlags.OVERNIGHT_DROP_OFF },
+ appointmentType: "Dropoff",
+ overnightDropOffCmsWidgetName: overnightDropOffCmsWidgetName,
+ },
+ },
+ });
+ //Assert
+ expect(wrapper.vm.durationTextBlockCopy).toEqual(
+ mockCmsContent[overnightDropOffCmsWidgetName]["SubheaderText"]
+ );
+ });
+ test("Correct duration information should be retrieved, Dropoff", async () => {
+ //Arrange
+ const dropoffCmsWidgetName = "Dropoff";
+ const { wrapper } = setupMocks({
+ customMountOptions: {
+ propsData: {
+ modelValue: { routeCode: "Test" + RouteCodeFlags.ALL_DAY_DROP_OFF },
+ appointmentType: "Dropoff",
+ dropoffCmsWidgetName: dropoffCmsWidgetName,
+ },
+ },
+ });
+ //Assert
+ expect(wrapper.vm.durationTextBlockCopy).toEqual(
+ mockCmsContent[dropoffCmsWidgetName]["SubheaderText"]
+ );
+ });
+ test("Correct duration information should be retrieved, Inshop - convert to hours", async () => {
+ //Arrange
+ const cmsWidgetName = "Inshop";
+ const { wrapper } = setupMocks({
+ customMountOptions: {
+ propsData: {
+ estimatedServiceMinutesMaximum: 180,
+ estimatedServiceMinutesMinimum: 120,
+ modelValue: { routeCode: "Test" },
+ appointmentType: "Inshop",
+ cmsWidgetName: cmsWidgetName,
+ },
+ },
+ });
+ //Assert
+ expect(wrapper.vm.durationTextBlockCopy).toEqual(
+ mockCmsContent[cmsWidgetName]["SubheaderText"] + " 2 - 3 hours"
+ );
+ });
+ test("Correct duration information should be retrieved, Inshop - convert to minutes", async () => {
+ //Arrange
+ const cmsWidgetName = "Inshop";
+ const { wrapper } = setupMocks({
+ customMountOptions: {
+ propsData: {
+ estimatedServiceMinutesMaximum: 90,
+ estimatedServiceMinutesMinimum: 60,
+ modelValue: { routeCode: "Test" },
+ appointmentType: "Inshop",
+ cmsWidgetName: cmsWidgetName,
+ },
+ },
+ });
+ //Assert
+ expect(wrapper.vm.durationTextBlockCopy).toEqual(
+ mockCmsContent[cmsWidgetName]["SubheaderText"] + " 60 - 90 minutes"
+ );
+ });
+ test("Correct duration information should be retrieved, Inshop - min = maximum time", async () => {
+ //Arrange
+ const cmsWidgetName = "Inshop";
+ const { wrapper } = setupMocks({
+ customMountOptions: {
+ propsData: {
+ estimatedServiceMinutesMaximum: 240,
+ estimatedServiceMinutesMinimum: 240,
+ modelValue: { routeCode: "Test" },
+ appointmentType: "Inshop",
+ cmsWidgetName: cmsWidgetName,
+ },
+ },
+ });
+ //Assert
+ expect(wrapper.vm.durationTextBlockCopy).toEqual(
+ mockCmsContent[cmsWidgetName]["SubheaderText"] + " 4 hours"
+ );
+ });
+ test("No duration information should be displayed, Mobile", async () => {
+ //Arrange
+ const mobileCmsWidgetName = "Mobile";
+ const { wrapper } = setupMocks({
+ customMountOptions: {
+ propsData: {
+ estimatedServiceMinutesMaximum: 240,
+ estimatedServiceMinutesMinimum: 240,
+ modelValue: { routeCode: "Test" },
+ appointmentType: "Mobile",
+ mobileCmsWidgetName: mobileCmsWidgetName,
+ },
+ },
+ });
+ //Assert
+ expect(wrapper.vm.durationTextBlockCopy).toBeNull();
+ });
+});
+
+describe("time-slot-modal-list-button-question.vue Available Timeslots", () => {
+ test("Available Timeslots should be formatted correctly - Dropoff - Sameday ", async () => {
+ //Arrange
+ const sameDayDropOffCmsWidgetName = "Sameday";
+ const date = new Date().toISOString().split("T")[0];
+ const timeSlotId = "testRouteCodeId";
+ const startTime = "8:00 AM";
+ const endTime = "8:30 AM";
+ const jobMaxMinutes = 100;
+
+ const { wrapper } = setupMocks({
+ customMountOptions: {
+ propsData: {
+ estimatedServiceMinutesMaximum: jobMaxMinutes,
+ dateAndTimeSlotData: {
+ date: date,
+ timeSlots: [
+ {
+ id: timeSlotId,
+ startTime: startTime,
+ endTime: endTime,
+ },
+ ],
+ },
+ appointmentType: "Dropoff",
+ sameDayDropOffCmsWidgetName: sameDayDropOffCmsWidgetName,
+ },
+ },
+ });
+
+ //Assert
+ const expectedTimeSlotObject = {
+ buttonLabel: mockCmsContent[sameDayDropOffCmsWidgetName]["HeaderText"],
+ value: timeSlotId,
+ };
+ expect(wrapper.vm.availableTimeSlots[0]).toEqual(expectedTimeSlotObject);
+ });
+ test("Available Timeslots should be formatted correctly - Dropoff - Overnight ", async () => {
+ //Arrange
+ const overnightDropOffCmsWidgetName = "Overnight";
+ const date = new Date().toISOString().split("T")[0];
+ const timeSlotId = "testRouteCodeId" + RouteCodeFlags.OVERNIGHT_DROP_OFF;
+ const startTime = "8:00 AM";
+ const endTime = "8:30 AM";
+ const jobMaxMinutes = 100;
+
+ const { wrapper } = setupMocks({
+ customMountOptions: {
+ propsData: {
+ estimatedServiceMinutesMaximum: jobMaxMinutes,
+ dateAndTimeSlotData: {
+ date: date,
+ timeSlots: [
+ {
+ id: timeSlotId,
+ startTime: startTime,
+ endTime: endTime,
+ },
+ ],
+ },
+ appointmentType: "Dropoff",
+ overnightDropOffCmsWidgetName: overnightDropOffCmsWidgetName,
+ },
+ },
+ });
+
+ //Assert
+ const expectedTimeSlotObject = {
+ buttonLabel: mockCmsContent[overnightDropOffCmsWidgetName]["HeaderText"],
+ value: timeSlotId,
+ };
+ expect(wrapper.vm.availableTimeSlots[0]).toEqual(expectedTimeSlotObject);
+ });
+ test("Available Timeslots should be formatted correctly - Dropoff", async () => {
+ //Arrange
+ const dropoffCmsWidgetName = "Dropoff";
+ const date = "2020-01-01";
+ const timeSlotId = "testRouteCodeId" + RouteCodeFlags.ALL_DAY_DROP_OFF;
+ const startTime = "8:00 AM";
+ const endTime = "8:30 AM";
+ const jobMaxMinutes = 100;
+
+ const { wrapper } = setupMocks({
+ customMountOptions: {
+ propsData: {
+ estimatedServiceMinutesMaximum: jobMaxMinutes,
+ dateAndTimeSlotData: {
+ date: date,
+ timeSlots: [
+ {
+ id: timeSlotId,
+ startTime: startTime,
+ endTime: endTime,
+ },
+ ],
+ },
+ appointmentType: "Dropoff",
+ dropoffCmsWidgetName: dropoffCmsWidgetName,
+ },
+ },
+ });
+
+ //Assert
+ const expectedTimeSlotObject = {
+ buttonLabel: mockCmsContent[dropoffCmsWidgetName]["HeaderText"],
+ value: timeSlotId,
+ };
+ expect(wrapper.vm.availableTimeSlots[0]).toEqual(expectedTimeSlotObject);
+ });
+ test("Available Timeslots should be formatted correctly - Mobile not premium", async () => {
+ //Arrange
+ const mobileCmsWidgetName = "MobileNotPremium";
+ const date = "2020-01-01";
+ const timeSlotId = "testRouteCodeId";
+ const startTime = "8:00";
+ const endTime = "8:30";
+ const jobMaxMinutes = 100;
+
+ const { wrapper } = setupMocks({
+ customMountOptions: {
+ propsData: {
+ estimatedServiceMinutesMaximum: jobMaxMinutes,
+ dateAndTimeSlotData: {
+ date: date,
+ timeSlots: [
+ {
+ id: timeSlotId,
+ startTime: startTime,
+ endTime: endTime,
+ },
+ ],
+ },
+ appointmentType: "Mobile",
+ mobileCmsWidgetName: mobileCmsWidgetName,
+ },
+ },
+ });
+
+ //Assert
+ const expectedTimeSlotObject = {
+ buttonLabel: startTime + " AM - " + endTime + " AM",
+ value: timeSlotId,
+ };
+ expect(wrapper.vm.availableTimeSlots[0]).toEqual(expectedTimeSlotObject);
+ });
+ test("Available Timeslots should be formatted correctly - Mobile with premium", async () => {
+ //Arrange
+ const mobilePremiumCmsWidgetName = "MobilePremium";
+ const date = "2020-01-01";
+ const timeSlotId = "testRouteCodeId";
+ const startTime = "8:00";
+ const endTime = "8:30";
+ const jobMaxMinutes = 100;
+
+ const { wrapper } = setupMocks({
+ customMountOptions: {
+ propsData: {
+ estimatedServiceMinutesMaximum: jobMaxMinutes,
+ premiumAppointmentFee: {
+ partType: PREMIUM_FEE_PART_TYPE,
+ },
+ dateAndTimeSlotData: {
+ date: date,
+ timeSlots: [
+ {
+ id: timeSlotId,
+ startTime: startTime,
+ endTime: endTime,
+ offerPremium: true,
+ },
+ ],
+ },
+ appointmentType: "Mobile",
+ mobilePremiumCmsWidgetName: mobilePremiumCmsWidgetName,
+ },
+ },
+ });
+
+ //Assert
+ const expectedTimeSlotObject = {
+ additionalButtonData: {
+ isPremiumAppointment: true,
+ },
+ buttonLabel: mockCmsContent[mobilePremiumCmsWidgetName]["HeaderText"],
+ buttonLabelSubCopy: "+$15.99",
+ value: timeSlotId + PREMIUM_TIME_SLOT_ID_FLAG,
+ };
+ expect(wrapper.vm.availableTimeSlots[0]).toEqual(expectedTimeSlotObject);
+ });
+ test("Available Timeslots should be formatted correctly - Inshop", async () => {
+ //Arrange
+ const cmsWidgetName = "Inshop";
+ const date = "2020-01-01";
+ const timeSlotId = "testRouteCodeId";
+ const startTime = "8:00";
+ const endTime = "8:30";
+ const jobMaxMinutes = 100;
+
+ const { wrapper } = setupMocks({
+ customMountOptions: {
+ propsData: {
+ estimatedServiceMinutesMaximum: jobMaxMinutes,
+ dateAndTimeSlotData: {
+ date: date,
+ timeSlots: [
+ {
+ id: timeSlotId,
+ startTime: startTime,
+ endTime: endTime,
+ },
+ ],
+ },
+ appointmentType: "Inshop",
+ cmsWidgetName: cmsWidgetName,
+ },
+ },
+ });
+
+ //Assert
+ const expectedTimeSlotObject = { buttonLabel: startTime + " AM", value: timeSlotId };
+ expect(wrapper.vm.availableTimeSlots[0]).toEqual(expectedTimeSlotObject);
+ });
+});
+
+const mockCmsContent = {
+ MobileNotPremium: {
+ BodyText: "Mobile, not premium BodyText",
+ FooterText: "Mobile, not premium FooterText",
+ SubheaderText: "Mobile, not premium SubheaderText",
+ HeaderText: "Mobile, not premium HeaderText",
+ },
+ MobilePremium: {
+ "BodyText:": "Mobile, with premium BodyText",
+ FooterText: "Mobile, with premium FooterText",
+ SubheaderText: "Mobile, with premium SubheaderText",
+ HeaderText: "Mobile, with premium HeaderText",
+ },
+ Inshop: {
+ BodyText: "Inshop BodyText",
+ FooterText: "Inshop FooterText",
+ SubheaderText: "Inshop SubheaderText",
+ HeaderText: "Inshop HeaderText",
+ },
+ Dropoff: {
+ BodyText: "Dropoff BodyText",
+ FooterText: "Dropoff FooterText",
+ SubheaderText: "Dropoff SubheaderText",
+ HeaderText: "Dropoff HeaderText",
+ },
+ Overnight: {
+ BodyText: "Dropoff Overnight",
+ FooterText: "Dropoff, Overnight FooterText",
+ SubheaderText: "Dropoff, Overnight SubheaderText",
+ HeaderText: "Dropoff, Overnight HeaderText",
+ },
+ Sameday: {
+ BodyText: "Dropoff Sameday",
+ FooterText: "Dropoff, Sameday FooterText",
+ SubheaderText: "Dropoff, Sameday SubheaderText",
+ HeaderText: "Dropoff, Sameday HeaderText",
+ },
+};
+
+function setupMocks({ customMountOptions }) {
+ const mountOptions = getMountOptions({
+ ...customMountOptions,
+ });
+ mountOptions.global.mocks["$store"] = store;
+ mountOptions["attachTo"] = document.body;
+ mountOptions.mixins = [
+ {
+ methods: {
+ getCmsContent: jest.fn().mockImplementation((widgetName, fieldName) => {
+ if (mockCmsContent[widgetName] && mockCmsContent[widgetName][fieldName])
+ return mockCmsContent[widgetName][fieldName];
+ }),
+ getTotalLineItemPrice: jest.fn().mockImplementation(() => 15.99),
+ },
+ },
+ ];
+
+ const wrapper = shallowMount(timeSlotModalQuestion, mountOptions);
+ wrapper.vm.setCmsContent = jest.fn();
+ return { wrapper };
+}
diff --git a/src/layouts/schedule/time-slot-modal-question/time-slot-modal-question.vue b/src/layouts/schedule/time-slot-modal-question/time-slot-modal-question.vue
index 13f33ae05..724facf6c 100644
--- a/src/layouts/schedule/time-slot-modal-question/time-slot-modal-question.vue
+++ b/src/layouts/schedule/time-slot-modal-question/time-slot-modal-question.vue
@@ -150,7 +150,7 @@ export default {
return this.modelValue?.routeCode;
},
set: function (newValue) {
- // Button Question only supports Number, or String data types so we must get the full object to emit
+ // Button Question only supports Number, or String data types so we must convert to the full object before emitting
this.selectedValue = this.getSelectedTimeSlotObject(newValue);
},
},
@@ -326,7 +326,7 @@ export default {
}
},
modal() {
- return this.$refs["timeSlots"];
+ return this.$refs[this.modalName];
},
},
methods: {
diff --git a/src/layouts/service-location/mobile-location-modal-questions/mobile-location-modal-questions.vue b/src/layouts/service-location/mobile-location-modal-questions/mobile-location-modal-questions.vue
index 07c6535dd..6873ce294 100644
--- a/src/layouts/service-location/mobile-location-modal-questions/mobile-location-modal-questions.vue
+++ b/src/layouts/service-location/mobile-location-modal-questions/mobile-location-modal-questions.vue
@@ -17,7 +17,10 @@
@click-event="openModal" />
-
+
{{ errorMessage }}
diff --git a/src/layouts/vehicle-damage/vehicle-damage.spec.js b/src/layouts/vehicle-damage/vehicle-damage.spec.js
index 28b85bf00..30e144310 100644
--- a/src/layouts/vehicle-damage/vehicle-damage.spec.js
+++ b/src/layouts/vehicle-damage/vehicle-damage.spec.js
@@ -52,6 +52,7 @@ jest.mock("@/store", () => ({
damage: {
glassToReplace: [],
},
+ order: { serviceLocation: { zipCode: "11111" } },
},
}));
@@ -133,6 +134,7 @@ describe("vehicle-damage.vue", () => {
getters: {
vehicle: {},
payment: { insuranceCoverage: { isVerified: false } },
+ order: { serviceLocation: { zipCode: "11111" } },
},
},
},
@@ -175,7 +177,7 @@ describe("vehicle-damage.vue", () => {
expect(wrapper.vm.selectedGlassToReplace()).toEqual(expectedGlassToReplace);
expect(baseMixin.methods.dispatchStoreAction).toBeCalledWith(
storeActions.GET_DAMAGE_OPTIONS,
- { carId: "C00000000" }
+ { carId: "C00000000", zipCode: "11111" }
);
});
@@ -241,6 +243,7 @@ describe("vehicle-damage.vue", () => {
getters: {
vehicle: {},
payment: { insuranceCoverage: { isVerified: false } },
+ order: { serviceLocation: { zipCode: "11111" } },
},
},
},
@@ -270,11 +273,10 @@ describe("vehicle-damage.vue", () => {
expect(wrapper.vm.selectedGlassToReplace()).toEqual(expectedGlassToReplace);
expect(baseMixin.methods.dispatchStoreAction).toBeCalledWith(
storeActions.GET_DAMAGE_OPTIONS,
- { carId: "C00000000" }
+ { carId: "C00000000", zipCode: "11111" }
);
});
});
-
describe("alert", () => {
test("when displayVehicleChangeAlert router params is true, the alert: 'vehicleChangeAlert' should be rendered", () => {
// Arrange & Act
@@ -321,7 +323,6 @@ describe("vehicle-damage.vue", () => {
expect(wrapper.findComponent({ ref: "vehicleChangeAlert" }).exists()).toBe(false);
});
});
-
describe("glass selections and corresponding variables", () => {
test("isWindshieldDamageLocation is true when windshield is selected", async () => {
//Arrange
@@ -453,7 +454,6 @@ describe("vehicle-damage.vue", () => {
expect(wrapper.vm.isPassengerSideReplace).toEqual(true);
});
});
-
describe("state validations", () => {
test("CarId set, arePagePrerequisitesValid should be true ", async () => {
//Arrange
@@ -474,7 +474,6 @@ describe("vehicle-damage.vue", () => {
expect(arePagePrerequisitesValid).toBe(true);
});
});
-
describe("input validations", () => {
// THE FOLLOWING TEST IS NOT NECESSARILY REQUIRED FOR COVERAGE
// BUT KEEP FOR AN EXAMPLE OF A VALIDATION TEST
@@ -501,7 +500,6 @@ describe("vehicle-damage.vue", () => {
});
});
});
-
describe("get glass options from store", () => {
const damageLocations = [
["Windshield", [damageLocationsSelected.WINDSHIELD]],
@@ -528,6 +526,7 @@ describe("vehicle-damage.vue", () => {
eventBusItem: jest.fn(),
damage: { glassToReplace: [{ glassLocation: damageLocation }] },
isRepair: true,
+ order: { serviceLocation: { zipCode: "11111" } },
};
var glassSelections = wrapper.vm.getDamageLocationsFromStore();
@@ -605,6 +604,7 @@ describe("vehicle-damage.vue", () => {
isRepair: isRepair,
numberOfChips: 2,
},
+ order: { serviceLocation: { zipCode: "11111" } },
};
var windshieldSelections = wrapper.vm.getWindshieldOptionsFromStore();
@@ -641,6 +641,7 @@ describe("vehicle-damage.vue", () => {
glassToReplace: [{ glassLocation: damageLocation, glassName: damageName }],
},
isRepair: true,
+ order: { serviceLocation: { zipCode: "11111" } },
};
var glassSelections = wrapper.vm.getDriverSideReplaceOptionsFromStore();
@@ -677,6 +678,7 @@ describe("vehicle-damage.vue", () => {
glassToReplace: [{ glassLocation: damageLocation, glassName: damageName }],
},
isRepair: true,
+ order: { serviceLocation: { zipCode: "11111" } },
};
var glassSelections = wrapper.vm.getPassengerSideReplaceOptionsFromStore();
@@ -711,6 +713,7 @@ describe("vehicle-damage.vue", () => {
glassToReplace: [{ glassLocation: damageLocation, glassName: damageName }],
},
isRepair: true,
+ order: { serviceLocation: { zipCode: "11111" } },
};
var glassSelections = wrapper.vm.getRearReplaceOptionsFromStore();
@@ -720,7 +723,6 @@ describe("vehicle-damage.vue", () => {
}
);
});
-
describe("hide back button", () => {
test("if claim registration is delayed => should hide back button", () => {
// Arrange
@@ -744,6 +746,7 @@ describe("vehicle-damage.vue", () => {
getters: {
vehicle: {},
payment: { insuranceCoverage: { isVerified: true } },
+ order: { serviceLocation: { zipCode: "11111" } },
},
},
},
@@ -784,6 +787,7 @@ function setupMocks({ pageHeaderWidgetHeaderText, mountOptionsMockData, funnelCo
isVerified: false,
},
},
+ order: { serviceLocation: { zipCode: "11111" } },
},
},
};
diff --git a/src/layouts/vehicle-damage/vehicle-damage.vue b/src/layouts/vehicle-damage/vehicle-damage.vue
index ae89a61e6..6ea4fe81d 100644
--- a/src/layouts/vehicle-damage/vehicle-damage.vue
+++ b/src/layouts/vehicle-damage/vehicle-damage.vue
@@ -81,7 +81,7 @@ import { getFunnelCookie } from "@/helpers/heritage-integration/cookie-helper";
import store from "@/store";
import baseMixin from "@/mixins/base-mixin";
-
+import { queryStrings } from "@/constants/query-strings";
// DEFINE VALIDATION RULES
defineRule("replace-options-required", required(errorMessages.REPLACE_OPTIONS_REQUIRED));
@@ -90,9 +90,20 @@ export default {
async beforeRouteEnter(to, from, next) {
// Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
+ const queryString = window.location.search;
+ const urlParams = new URLSearchParams(queryString);
+ const lowerCaseParams = new URLSearchParams();
+ for (const [name, value] of urlParams) {
+ lowerCaseParams.append(name.toLowerCase(), value);
+ }
+
+ const zip = lowerCaseParams.get(queryStrings.ZIP_CODE)
+ ? lowerCaseParams.get(queryStrings.ZIP_CODE)
+ : store.getters.order.serviceLocation.zipCode;
+
const damageOptionsPromise = baseMixin.methods.dispatchStoreAction(
storeActions.GET_DAMAGE_OPTIONS,
- { carId: store.getters.vehicle.carId }
+ { carId: store.getters.vehicle.carId, zipCode: zip }
);
// Settle promises and get results
@@ -135,6 +146,7 @@ export default {
},
selectedWindshieldOptions: this.getWindshieldOptionsFromStore(),
selectedRearReplaceOptions: this.getRearReplaceOptionsFromStore(),
+ serviceZipCode: this.getZipFromStore() ?? this.$route.query.zipcode,
};
},
mounted() {
@@ -157,6 +169,18 @@ export default {
true
);
}
+ if (this.serviceZipCode) {
+ this.pushEventToGA(
+ this.GaCategories.FUNNEL_ENTRY,
+ this.GaActions.ZIP_CODE_PROVIDED,
+ this.serviceZipCode,
+ true
+ );
+ }
+ },
+
+ getZipFromStore() {
+ return this.$store.getters.order.serviceLocation.zipCode;
},
backButtonAction() {
diff --git a/src/layouts/vehicle/vehicle-question/vehicle-question.vue b/src/layouts/vehicle/vehicle-question/vehicle-question.vue
index 283f26811..79b58bfaa 100644
--- a/src/layouts/vehicle/vehicle-question/vehicle-question.vue
+++ b/src/layouts/vehicle/vehicle-question/vehicle-question.vue
@@ -1,9 +1,5 @@
-
+
diff --git a/src/layouts/vehicle/vehicle.vue b/src/layouts/vehicle/vehicle.vue
index bea1fdd8e..24ae0d215 100644
--- a/src/layouts/vehicle/vehicle.vue
+++ b/src/layouts/vehicle/vehicle.vue
@@ -15,8 +15,9 @@
ref="vehicleYearQuestion"
class="mb-2 mt-4"
v-model="selectedYear"
+ :isDisabled="!yearOptions.length"
+ :options="yearOptions"
cmsWidgetName="VehicleYearQuestion"
- :updateValues="updateYearValues"
validationRules="year-required"
placeHolderText="Select year"
inputId="yearQuestionField" />
@@ -25,8 +26,9 @@
ref="vehicleMakeQuestion"
class="mb-2 mt-4"
v-model="selectedMake"
+ :isDisabled="!makeOptions.length"
+ :options="makeOptions"
cmsWidgetName="VehicleMakeQuestion"
- :updateValues="updateMakeValues"
validationRules="make-required"
placeHolderText="Select make"
inputId="makeQuestionField" />
@@ -36,7 +38,8 @@
class="mb-2 mt-4"
v-model="selectedModel"
cmsWidgetName="VehicleModelQuestion"
- :updateValues="updateModelValues"
+ :isDisabled="!modelOptions.length"
+ :options="modelOptions"
validationRules="model-required"
placeHolderText="Select model"
inputId="modelQuestionField" />
@@ -46,7 +49,8 @@
class="mb-2 mt-4"
v-model="selectedStyle"
cmsWidgetName="VehicleStyleQuestion"
- :updateValues="updateStyleValues"
+ :isDisabled="!styleOptions.length"
+ :options="styleOptions"
validationRules="style-required"
placeHolderText="Select style"
inputId="styleQuestionField" />
@@ -101,10 +105,14 @@ export default {
name: "vehicle",
data() {
return {
- selectedYear: null,
- selectedMake: null,
- selectedModel: null,
- selectedStyle: null,
+ selectedYear: this.selectedYearfromStore(),
+ selectedMake: this.selectedMakefromStore(),
+ selectedModel: this.selectedModelfromStore(),
+ selectedStyle: this.selectedStylefromStore(),
+ yearOptions: [],
+ makeOptions: [],
+ modelOptions: [],
+ styleOptions: [],
};
},
@@ -113,10 +121,6 @@ export default {
validationRules: String,
},
- mounted() {
- this.$refs["vehicleYearQuestion"].getNewValues(this.selectedYearfromStore);
- },
-
async beforeRouteEnter(to, from, next) {
// Call APIs
@@ -124,6 +128,43 @@ export default {
const experimentForLogging = store.getters.applicationUser.experiments.find(
(e) => e.universeName === experimentUniverses.CONCEPT_FUNNEL
);
+ const yearQuestionInitialDataPromise = baseMixin.methods.dispatchStoreAction(
+ storeActions.GET_VEHICLE_YEARS,
+ {}
+ );
+ var makeQuestionInitialDataPromise = null;
+ var modelQuestionInitialDataPromise = null;
+ var styleQuestionInitialDataPromise = null;
+
+ if (
+ store.getters.order.vehicle.make &&
+ store.getters.order.vehicle.model &&
+ store.getters.order.vehicle.style
+ ) {
+ makeQuestionInitialDataPromise = baseMixin.methods.dispatchStoreAction(
+ storeActions.GET_VEHICLE_MAKES,
+ {
+ year: store.getters.order.vehicle.year,
+ }
+ );
+
+ modelQuestionInitialDataPromise = baseMixin.methods.dispatchStoreAction(
+ storeActions.GET_VEHICLE_MODELS,
+ {
+ year: store.getters.order.vehicle.year,
+ make: store.getters.order.vehicle.make,
+ }
+ );
+
+ styleQuestionInitialDataPromise = baseMixin.methods.dispatchStoreAction(
+ storeActions.GET_VEHICLE_STYLES,
+ {
+ year: store.getters.order.vehicle.year,
+ make: store.getters.order.vehicle.make,
+ model: store.getters.order.vehicle.model,
+ }
+ );
+ }
// If the concept funnel experiment is found, as it should be when coming from safelite.com, then log the experiment exposure.
if (experimentForLogging !== undefined) {
@@ -146,6 +187,22 @@ export default {
resultKey: "cmsContent",
promise: cmsContentPromise,
},
+ {
+ resultKey: "yearQuestionInitialData",
+ promise: yearQuestionInitialDataPromise,
+ },
+ {
+ resultKey: "makeQuestionInitialData",
+ promise: makeQuestionInitialDataPromise,
+ },
+ {
+ resultKey: "modelQuestionInitialData",
+ promise: modelQuestionInitialDataPromise,
+ },
+ {
+ resultKey: "styleQuestionInitialData",
+ promise: styleQuestionInitialDataPromise,
+ },
];
let resultMap = await settleAllPromises(promiseResultMap);
@@ -153,48 +210,74 @@ export default {
// Call the "next" function to complete the transition to this page.
next((vm) => {
vm.setCmsContent(resultMap.cmsContent);
+ vm.initializeYearComponent(resultMap.yearQuestionInitialData);
+
+ if (
+ makeQuestionInitialDataPromise &&
+ modelQuestionInitialDataPromise &&
+ styleQuestionInitialDataPromise
+ )
+ vm.initializeMMSComponent(
+ resultMap.makeQuestionInitialData,
+ resultMap.modelQuestionInitialData,
+ resultMap.styleQuestionInitialData
+ );
});
},
watch: {
- selectedYear(year) {
- const parsedYear = parseInt(year);
- this.dispatchStoreAction(storeActions.SAVE_VEHICLE_YEAR, parsedYear);
+ async selectedYear(year) {
if (year) {
- this.$refs["vehicleMakeQuestion"].getNewValues(this.selectedMakefromStore);
+ const result = await this.getMakeOptions(year);
+ this.makeOptions = result?.data;
+ if (this.selectedYearfromStore() !== year) {
+ this.selectedMake = null;
+ this.selectedModel = null;
+ this.selectedStyle = null;
+ }
} else {
- this.$refs["vehicleMakeQuestion"].clearValues();
+ this.makeOptions = [];
}
},
- selectedMake(make) {
- this.dispatchStoreAction(storeActions.SAVE_VEHICLE_MAKE, make, false);
+ async selectedMake(make) {
if (make) {
- this.$refs["vehicleModelQuestion"].getNewValues(this.selectedModelfromStore);
+ const result = await this.getModelOptions(this.selectedYear, make);
+ this.modelOptions = result?.data;
+ if (this.selectedMakefromStore() !== make) {
+ this.selectedModel = null;
+ this.selectedStyle = null;
+ }
} else {
- this.$refs["vehicleModelQuestion"].clearValues();
+ this.modelOptions = [];
}
},
- selectedModel(model) {
- this.dispatchStoreAction(storeActions.SAVE_VEHICLE_MODEL, model, false);
+ async selectedModel(model) {
if (model) {
- this.$refs["vehicleStyleQuestion"].getNewValues(this.selectedStylefromStore);
+ const result = await this.getStyleOptions(
+ this.selectedYear,
+ this.selectedMake,
+ model
+ );
+ this.styleOptions = result?.data;
+ if (this.selectedModelfromStore() !== model) {
+ this.selectedStyle = null;
+ }
} else {
- this.$refs["vehicleStyleQuestion"].clearValues();
+ this.styleOptions = [];
}
},
selectedStyle(style) {
- this.dispatchStoreAction(storeActions.SAVE_VEHICLE_STYLE, style, false);
- this.setVehicle();
+ this.setVehicle(this.selectedYear, this.selectedMake, this.selectedModel, style);
},
},
methods: {
- setVehicle() {
+ setVehicle(year, make, model, style) {
return this.dispatchStoreAction(this.storeActions.SET_VEHICLE, {
- year: this.$store.getters.vehicle.year,
- make: this.$store.getters.vehicle.make,
- model: this.$store.getters.vehicle.model,
- style: this.$store.getters.vehicle.style,
+ year: year,
+ make: make,
+ model: model,
+ style: style,
});
},
@@ -207,42 +290,29 @@ export default {
},
navigateForward() {
+ this.dispatchStoreAction(
+ storeActions.SAVE_VEHICLE,
+ {
+ year: this.selectedYear,
+ make: this.selectedMake,
+ model: this.selectedModel,
+ style: this.selectedStyle,
+ },
+ false
+ );
this.$router.navigateWithSaving(this.navigationScenarios.CLICKED_FORWARD, this.$route);
},
- async updateYearValues() {
- return await baseMixin.methods.dispatchStoreAction(storeActions.GET_VEHICLE_YEARS, {});
+ initializeYearComponent(initialData) {
+ this.yearOptions = initialData;
},
-
- async updateMakeValues() {
- return await baseMixin.methods.dispatchStoreAction(storeActions.GET_VEHICLE_MAKES, {
- year: store.getters.vehicle.year,
- });
- },
-
- async updateModelValues() {
- return await baseMixin.methods.dispatchStoreAction(storeActions.GET_VEHICLE_MODELS, {
- year: store.getters.vehicle.year,
-
- make: store.getters.vehicle.make,
- });
- },
-
- async updateStyleValues() {
- return baseMixin.methods.dispatchStoreAction(storeActions.GET_VEHICLE_STYLES, {
- year: store.getters.vehicle.year,
- make: store.getters.vehicle.make,
- model: store.getters.vehicle.model,
- });
- },
- },
-
- computed: {
- displayGeneric() {
- return !this.selectedStyle;
+ initializeMMSComponent(makeOptions, modelOptions, styleOptions) {
+ this.makeOptions = makeOptions;
+ this.modelOptions = modelOptions;
+ this.styleOptions = styleOptions;
},
selectedYearfromStore() {
- return store.getters.vehicle.year;
+ return store.getters.vehicle.year?.toString();
},
selectedMakefromStore() {
return store.getters.vehicle.make;
@@ -253,6 +323,32 @@ export default {
selectedStylefromStore() {
return store.getters.vehicle.style;
},
+ async getMakeOptions(year) {
+ return await baseMixin.methods.dispatchStoreAction(storeActions.GET_VEHICLE_MAKES, {
+ year: year,
+ });
+ },
+
+ async getModelOptions(year, make) {
+ return await baseMixin.methods.dispatchStoreAction(storeActions.GET_VEHICLE_MODELS, {
+ year: year,
+ make: make,
+ });
+ },
+
+ async getStyleOptions(year, make, model) {
+ return await baseMixin.methods.dispatchStoreAction(storeActions.GET_VEHICLE_STYLES, {
+ year: year,
+ make: make,
+ model: model,
+ });
+ },
+ },
+
+ computed: {
+ displayGeneric() {
+ return !this.selectedStyle;
+ },
},
components: {
diff --git a/src/layouts/vin-lookup/vin-lookup.vue b/src/layouts/vin-lookup/vin-lookup.vue
index ba1764bd3..c0b1f1788 100644
--- a/src/layouts/vin-lookup/vin-lookup.vue
+++ b/src/layouts/vin-lookup/vin-lookup.vue
@@ -15,7 +15,9 @@
v-model="vin"
customInputId="vin"
isRequired
- validationRules="vin-required|vin-format"
+ :validationRules="
+ !vinPopulatedOnPageLoad ? 'vin-required|vin-format' : ''
+ "
:isDisabled="vinPopulatedOnPageLoad"
maxLength="17"
:mask="vinMask"
@@ -348,37 +350,22 @@ export default {
// If a VIN has already been found. Validate the Service Zip (in case of changes)
const zipCodeData = await this.getZipCodeData(this.serviceZipCode);
- // Check if Service Zip entered is serviceable then save the ZIP info and email address
+ // Check if Service Zip entered is serviceable then save the ZIP info
if (zipCodeData.isServiceable) {
- //Only save the service location if the zip changed or we lack zipCodeCtu
+ //Only save the zipCode, state, and zipCodeCtu if the zip changed or we lack zipCodeCtu
if (
this.$store.getters.order.serviceLocation.zipCode != this.serviceZipCode ||
!this.$store.getters.order.serviceLocation.zipCodeCtu
) {
- const vehicleRegistrationInfo = this.$store.getters.vehicle.registration;
- if (vehicleRegistrationInfo.zipCode == this.serviceZipCode) {
- await this.dispatchStoreAction(
- storeActions.SAVE_SERVICE_LOCATION,
- {
- address: vehicleRegistrationInfo.address,
- city: vehicleRegistrationInfo.city,
- state: vehicleRegistrationInfo.state,
- zipCode: vehicleRegistrationInfo.zipCode,
- zipCodeCtu: zipCodeData.zipCodeCtu,
- },
- false
- );
- } else {
- await this.dispatchStoreAction(
- storeActions.SAVE_SERVICE_ZIP_CODE_INFO,
- {
- state: zipCodeData.state,
- zipCode: this.serviceZipCode,
- zipCodeCtu: zipCodeData.zipCodeCtu,
- },
- false
- );
- }
+ await this.dispatchStoreAction(
+ storeActions.SAVE_SERVICE_ZIP_CODE_INFO,
+ {
+ state: zipCodeData.state,
+ zipCode: this.serviceZipCode,
+ zipCodeCtu: zipCodeData.zipCodeCtu,
+ },
+ false
+ );
}
await this.dispatchStoreAction(storeActions.SAVE_EMAIL, this.emailAddress, false);
diff --git a/src/mixins/vehicle-questions-mixin.js b/src/mixins/vehicle-questions-mixin.js
index 4a0c49c81..4b05067ea 100644
--- a/src/mixins/vehicle-questions-mixin.js
+++ b/src/mixins/vehicle-questions-mixin.js
@@ -446,7 +446,9 @@ export default {
const payment = store.getters.payment;
- if (payment.isInsurance && payment.insuranceCoverage.isVerified) {
+ if (store.getters.order.referralNumber?.length === 6) {
+ navigateToHeritageFunnel({ loadingModal: self.$refs.loadingModal });
+ } else if (payment.isInsurance && payment.insuranceCoverage.isVerified) {
navigateToHeritageFunnel({ loadingModal: self.$refs.loadingModal });
} else {
self.$router.navigateWithSaving(
diff --git a/src/router/router-constants/navigation-scenarios.js b/src/router/router-constants/navigation-scenarios.js
index 88ca110dd..ae1c880ae 100644
--- a/src/router/router-constants/navigation-scenarios.js
+++ b/src/router/router-constants/navigation-scenarios.js
@@ -50,6 +50,7 @@ const navigationScenarios = {
CLICKED_VEHICLE_EDIT: "CLICKED_VEHICLE_EDIT",
CLICKED_DAMAGE_EDIT: "CLICKED_DAMAGE_EDIT",
CLICKED_SERVICE_PACKAGE_EDIT: "CLICKED_SERVICE_PACKAGE_EDIT",
+ CLICKED_SERVICE_LOCATION_EDIT: "CLICKED_SERVICE_LOCATION_EDIT",
};
export { navigationScenarios };
diff --git a/src/router/router-constants/routing-table.js b/src/router/router-constants/routing-table.js
index 4238b659a..017e1372a 100644
--- a/src/router/router-constants/routing-table.js
+++ b/src/router/router-constants/routing-table.js
@@ -480,6 +480,10 @@ const routingTable = function (store) {
scenario: navigationScenarios.CLICKED_BACK,
destinationFmgPageValue: fmgPageValues.CUSTOMER_DETAILS,
},
+ {
+ scenario: navigationScenarios.CLICKED_SERVICE_LOCATION_EDIT,
+ destinationFmgPageValue: fmgPageValues.SERVICE_LOCATION,
+ },
],
},
];
diff --git a/src/store/index.js b/src/store/index.js
index 951887533..4bdc53977 100644
--- a/src/store/index.js
+++ b/src/store/index.js
@@ -535,6 +535,9 @@ export const getters = {
},
eventBus: (state) => state.applicationUser.eventBus,
damage: (state) => state.order.damage,
+ hasExactlyOneChip: (state) => {
+ return state.order.damage?.numberOfChips === 1;
+ },
hasAnyNonWindshieldGlassParts: (state) => {
const nonWindshieldItems = state.order.damage.glassToReplace?.filter(
(glassToReplace) => glassToReplace.glassLocation != "Windshield"
@@ -766,11 +769,12 @@ export const actions = {
});
},
- getDamageOptions(context, { carId }) {
+ getDamageOptions(context, { carId, zipCode }) {
return globalMethods.callHttpClient({
methods: endpoints.GetDamageOptions.method,
endpoint: `${endpoints.GetDamageOptions.url}/${carId}`,
payload: {},
+ additionalSuccessEventDataHandler: (response) => "QueryStringZip: " + zipCode,
});
},
@@ -1622,6 +1626,25 @@ export const actions = {
}
},
+ saveVehicle(context, { year, make, model, style }) {
+ context.dispatch(storeActions.RESET_DAMAGE_STATE_AND_DEPENDENCIES);
+ context.dispatch(storeActions.RESET_REGISTRATION_STATE_AND_DEPENDENCIES);
+ context.dispatch(storeActions.RESET_PARTS_STATE_AND_DEPENDENCIES);
+
+ if (context.state.order.vehicle.year !== year) {
+ context.commit(storeMutations.UPDATE_YEAR, year);
+ }
+ if (context.state.order.vehicle.make !== make) {
+ context.commit(storeMutations.UPDATE_MAKE, make);
+ }
+ if (context.state.order.vehicle.model !== model) {
+ context.commit(storeMutations.UPDATE_MODEL, model);
+ }
+ if (context.state.order.vehicle.style !== style) {
+ context.commit(storeMutations.UPDATE_STYLE, style);
+ }
+ },
+
saveVehicleDamage(
context,
{ isWindshieldRepair, selectedGlassToReplace, selectedWindshieldChipCount }
diff --git a/src/store/store.spec.js b/src/store/store.spec.js
index 4a6788433..08aa406c9 100644
--- a/src/store/store.spec.js
+++ b/src/store/store.spec.js
@@ -1702,6 +1702,59 @@ describe("Actions", () => {
expect(commit).toBeCalledWith(storeMutations.UPDATE_VEHICLE_IMAGE_COLOR, null);
});
+ it("saveVehicle, should save vehicle info", () => {
+ // Arrange
+ const context = state;
+
+ context.state = {
+ order: {
+ vehicle: {
+ year: "2016",
+ make: "Toyota",
+ model: "Accord",
+ style: "SUV",
+ },
+ },
+ };
+
+ const commit = jest.fn();
+ const dispatch = jest.fn();
+
+ context.commit = commit;
+ context.dispatch = dispatch;
+
+ //Act
+ const payload = {
+ year: "2015",
+ make: "Honda",
+ model: "Civic",
+ style: "Sedan",
+ };
+ actions.saveVehicle(context, payload);
+
+ //Assert
+ expect(dispatch).toHaveBeenNthCalledWith(
+ 1,
+ storeActions.RESET_DAMAGE_STATE_AND_DEPENDENCIES
+ );
+ expect(dispatch).toHaveBeenNthCalledWith(
+ 2,
+ storeActions.RESET_REGISTRATION_STATE_AND_DEPENDENCIES
+ );
+ if (context.state.order.vehicle.year !== payload.year) {
+ expect(commit).toBeCalledWith(storeMutations.UPDATE_YEAR, payload.year);
+ }
+ if (context.state.order.vehicle.make !== payload.make) {
+ expect(commit).toBeCalledWith(storeMutations.UPDATE_MAKE, payload.make);
+ }
+ if (context.state.order.vehicle.model !== payload.model) {
+ expect(commit).toBeCalledWith(storeMutations.UPDATE_MODEL, payload.model);
+ }
+ if (context.state.order.vehicle.style !== payload.style) {
+ expect(commit).toBeCalledWith(storeMutations.UPDATE_STYLE, payload.style);
+ }
+ });
+
it("saveVehicleDamage, should wipe out damage if different", () => {
// Arrange
const context = state;