diff --git a/jest.config.js b/jest.config.js index 0a6fbc048..633a99e67 100644 --- a/jest.config.js +++ b/jest.config.js @@ -15,10 +15,8 @@ module.exports = { "!src/layouts/vehicle-damage/windshield-options/windshield-options.vue", "!src/layouts/reveal/**/*.vue", "!src/ux-components/text-link/**/*.vue", - "!src/digital-components/date-picker/**/*.vue", // Temp until unit tests completed "!src/layouts/vin-lookup/**/*.vue", //Temporary for Quote page testing "!src/common-components/funnel-header/menu-modal/**/*.vue", - "!src/layouts/schedule/*.vue", // Temp test exclusion while in development "!src/layouts/schedule/helpers/schedule-helper.js", // Temp test exclusion while in development "!src/layouts/review/*.vue", // Temp test exclusion while in development // END diff --git a/src/constants/analytics.js b/src/constants/analytics.js index 2b0fc1a8f..b5b24660d 100644 --- a/src/constants/analytics.js +++ b/src/constants/analytics.js @@ -12,7 +12,6 @@ const GaEvents = { const GaCategories = { API_RESPONSE: "Api_Response", EVOX: "Evox", - FUNNEL_ENTRY: "funnel_entry", }; const GaActions = { @@ -21,7 +20,6 @@ const GaActions = { VIF: "vif", SUBMITTED: "Submitted", DISPLAYED: "Displayed", - ZIP_CODE_PROVIDED: "zip_code_provided", }; const GaLabels = { diff --git a/src/constants/endpoints.js b/src/constants/endpoints.js index 05a55cc5c..373779380 100644 --- a/src/constants/endpoints.js +++ b/src/constants/endpoints.js @@ -119,7 +119,7 @@ const endpoints = { method: "GET", }, SaveSession: { - url: "/order/api/v1/order/save-session", + url: "/order/api/v1/order/save-session/fmg", method: "POST", }, LoadSession: { diff --git a/src/constants/pay-with-insurance-states.js b/src/constants/pay-with-insurance-states.js new file mode 100644 index 000000000..e65589557 --- /dev/null +++ b/src/constants/pay-with-insurance-states.js @@ -0,0 +1 @@ +export const payWithInsuranceStates = ["AZ", "CT", "FL", "KY", "MA", "MN", "NY", "SC"]; diff --git a/src/constants/store-actions.js b/src/constants/store-actions.js index fc0daf323..f23a167e1 100644 --- a/src/constants/store-actions.js +++ b/src/constants/store-actions.js @@ -9,7 +9,7 @@ const storeActions = { GET_VEHICLE_MAKES: "getVehicleMakes", GET_VEHICLE_MODELS: "getVehicleModels", GET_VEHICLE_STYLES: "getVehicleStyles", - SET_VEHICLE: "setVehicle", + GET_VEHICLE: "getVehicle", GET_DAMAGE_OPTIONS: "getDamageOptions", GET_EVOX_IMAGE: "getEvoxImage", IS_VIN_OPTIONAL_VEHICLE: "isVinOptionalVehicle", @@ -58,10 +58,6 @@ const storeActions = { RESET_STATE: "resetState", // SAVE COMPONENT STATE - SAVE_VEHICLE_YEAR: "saveVehicleYear", - SAVE_VEHICLE_MAKE: "saveVehicleMake", - SAVE_VEHICLE_MODEL: "saveVehicleModel", - SAVE_VEHICLE_STYLE: "saveVehicleStyle", SAVE_VEHICLE: "saveVehicle", SAVE_VEHICLE_DAMAGE: "saveVehicleDamage", SAVE_VIN_LOOKUP: "saveVinLookup", diff --git a/src/constants/store-mutations.js b/src/constants/store-mutations.js index 7b57187c2..fd568ead7 100644 --- a/src/constants/store-mutations.js +++ b/src/constants/store-mutations.js @@ -23,28 +23,20 @@ const storeMutations = { UPDATE_SUPPORTING_ITEMS: "updateSupportingItems", UPDATE_LINE_ITEMS_SERVER_DATA: "updateLineItemsServerData", - UPDATE_REGISTRATION_LICENSE_PLATE: "updateRegistrationLicensePlate", - UPDATE_REGISTRATION_ADDRESS: "updateRegistrationAddress", - UPDATE_REGISTRATION_CITY: "updateRegistrationCity", - UPDATE_REGISTRATION_STATE: "updateRegistrationState", - UPDATE_REGISTRATION_ZIP_CODE: "updateRegistrationZipCode", - UPDATE_REGISTRATION_FIRST_NAME: "updateRegistrationFirstName", - UPDATE_REGISTRATION_LAST_NAME: "updateRegistrationLastName", UPDATE_REGISTRATION: "updateRegistration", UPDATE_SERVICE_ZIP: "updateServiceZip", UPDATE_SERVICE_LOCATION: "updateServiceLocation", UPDATE_SERVICE_LOCATION_TECH_NOTES: "updateServiceLocationTechNotes", - UPDATE_TECH_NOTES: "updateTechNotes", UPDATE_SCHEDULE: "updateSchedule", - UPDATE_CUSTOMER_EMAIL_ADDRESS: "updateCustomerEmailAddress", - // CUSTOMER MUTATIONS + UPDATE_CUSTOMER_EMAIL_ADDRESS: "updateCustomerEmailAddress", UPDATE_CUSTOMER_DETAILS: "updateCustomerDetails", // ORDER MUTATIONS UPDATE_REFERRAL_NUMBER: "updateReferralNumber", + UPDATE_REFERRAL_SEQUENCE_NUMBER: "updateReferralSequenceNumber", UPDATE_REFERRAL_DATE: "updateReferralDate", UPDATE_REFERRAL_CORRELATION_ID: "updateReferralCorrelationId", UPDATE_PARENT_ACCT_NUMBER: "updateParentAcctNumber", diff --git a/src/digital-components/date-picker/date-picker.spec.js b/src/digital-components/date-picker/date-picker.spec.js new file mode 100644 index 000000000..e4416d37a --- /dev/null +++ b/src/digital-components/date-picker/date-picker.spec.js @@ -0,0 +1,1348 @@ +import datePicker from "./date-picker"; + +// Supporting Files +import { shallowMount } from "@vue/test-utils"; +import { getMountOptions } from "@/helpers/unit-test-helper.js"; +import store from "@/store"; + +// Constants +const mockValuesForSetDate01 = { + overrideDate: "2023-12-01", + monthEnd: "2023-12-31", + initialViewWeeks: [ + { + weekNum: 1, + weekStartDate: "2023-12-01", + weekEndDate: "2023-12-02", + }, + { + weekNum: 2, + weekStartDate: "2023-12-03", + weekEndDate: "2023-12-09", + }, + { + weekNum: 3, + weekStartDate: "2023-12-10", + weekEndDate: "2023-12-16", + }, + { + weekNum: 4, + weekStartDate: "2023-12-17", + weekEndDate: "2023-12-23", + }, + { + weekNum: 5, + weekStartDate: "2023-12-24", + weekEndDate: "2023-12-30", + }, + ], + config: { + todayDate: "2023-12-01", + initialViewStartDate: "2023-12-01", + initialViewEndDate: "2023-12-30", + calendarViewDirection: "future", + initialShopTimeSlotsResponse: { + days: [ + { + date: "2023-12-30", + timeSlots: [ + { + id: "06747-01820-S-B*20453*7 AM", + startTime: "07:00", + endTime: "08:00", + offerPremium: false, + }, + ], + }, + ], + estimatedServiceMinutesMinimum: 90, + estimatedServiceMinutesMaximum: 120, + }, + hideSomeDaysForInitialView: true, + hideSecondMonth: true, + preSelectedDate: null, + }, + monthsData: [ + { + monthLabel: "December", + monthIndex: 12, + monthString: "December-2023", + yearNum: 2023, + dates: [ + { + dateNum: 1, + dayClasses: " current-day", + inputValue: "2023-12-01", + isSelectable: false, + }, + { + dateNum: 2, + dayClasses: "", + inputValue: "2023-12-02", + isSelectable: false, + }, + { + dateNum: 3, + dayClasses: " sunday", + inputValue: "2023-12-03", + isSelectable: false, + }, + { + dateNum: 4, + dayClasses: "", + inputValue: "2023-12-04", + isSelectable: false, + }, + { + dateNum: 5, + dayClasses: "", + inputValue: "2023-12-05", + isSelectable: false, + }, + { + dateNum: 6, + dayClasses: "", + inputValue: "2023-12-06", + isSelectable: false, + }, + { + dateNum: 7, + dayClasses: "", + inputValue: "2023-12-07", + isSelectable: false, + }, + { + dateNum: 8, + dayClasses: "", + inputValue: "2023-12-08", + isSelectable: false, + }, + { + dateNum: 9, + dayClasses: "", + inputValue: "2023-12-09", + isSelectable: false, + }, + { + dateNum: 10, + dayClasses: " sunday", + inputValue: "2023-12-10", + isSelectable: false, + }, + { + dateNum: 11, + dayClasses: "", + inputValue: "2023-12-11", + isSelectable: false, + }, + { + dateNum: 12, + dayClasses: "", + inputValue: "2023-12-12", + isSelectable: false, + }, + { + dateNum: 13, + dayClasses: "", + inputValue: "2023-12-13", + isSelectable: false, + }, + { + dateNum: 14, + dayClasses: "", + inputValue: "2023-12-14", + isSelectable: false, + }, + { + dateNum: 15, + dayClasses: "", + inputValue: "2023-12-15", + isSelectable: false, + }, + { + dateNum: 16, + dayClasses: "", + inputValue: "2023-12-16", + isSelectable: false, + }, + { + dateNum: 17, + dayClasses: " sunday", + inputValue: "2023-12-17", + isSelectable: false, + }, + { + dateNum: 18, + dayClasses: "", + inputValue: "2023-12-18", + isSelectable: false, + }, + { + dateNum: 19, + dayClasses: "", + inputValue: "2023-12-19", + isSelectable: false, + }, + { + dateNum: 20, + dayClasses: "", + inputValue: "2023-12-20", + isSelectable: false, + }, + { + dateNum: 21, + dayClasses: "", + inputValue: "2023-12-21", + isSelectable: false, + }, + { + dateNum: 22, + dayClasses: "", + inputValue: "2023-12-22", + isSelectable: false, + }, + { + dateNum: 23, + dayClasses: "", + inputValue: "2023-12-23", + isSelectable: false, + }, + { + dateNum: 24, + dayClasses: " sunday", + inputValue: "2023-12-24", + isSelectable: false, + }, + { + dateNum: 25, + dayClasses: "", + inputValue: "2023-12-25", + isSelectable: false, + }, + { + dateNum: 26, + dayClasses: "", + inputValue: "2023-12-26", + isSelectable: false, + }, + { + dateNum: 27, + dayClasses: "", + inputValue: "2023-12-27", + isSelectable: false, + }, + { + dateNum: 28, + dayClasses: "", + inputValue: "2023-12-28", + isSelectable: false, + }, + { + dateNum: 29, + dayClasses: "", + inputValue: "2023-12-29", + isSelectable: false, + }, + { + dateNum: 30, + dayClasses: "", + inputValue: "2023-12-30", + isSelectable: true, + }, + { + dateNum: 31, + dayClasses: " sunday day-hidden", + inputValue: "2023-12-31", + isSelectable: false, + }, + ], + startDateDayIndex: 5, + monthClass: "", + isMonthThatHidesSomeDaysForInitialView: true, + }, + { + monthLabel: "January", + monthIndex: 1, + monthString: "January-2024", + yearNum: 2024, + dates: [ + { + dateNum: 1, + dayClasses: "", + inputValue: "2024-01-01", + isSelectable: false, + }, + { + dateNum: 2, + dayClasses: "", + inputValue: "2024-01-02", + isSelectable: false, + }, + { + dateNum: 30, + dayClasses: "", + inputValue: "2024-01-30", + isSelectable: false, + }, + { + dateNum: 31, + dayClasses: "", + inputValue: "2024-01-31", + isSelectable: false, + }, + ], + startDateDayIndex: 1, + monthClass: " month-hidden", + }, + ], +}; + +const mockInitialData = { + todayDate: "2023-07-04", + initialViewStartDate: "2023-07-05", + initialViewEndDate: "2023-08-19", + calendarViewDirection: "future", + initialShopTimeSlotsResponse: { + days: [ + { + date: "2023-07-25", + timeSlots: [ + { + id: "06747-01820-S-B*20295*7 AM", + startTime: "07:00", + endTime: "08:00", + offerPremium: false, + }, + { + id: "06747-01820-S-B*20295*730 AM", + startTime: "07:30", + endTime: "08:30", + offerPremium: false, + }, + ], + }, + { + date: "2023-07-26", + timeSlots: [ + { + id: "06747-01820-S-B*20296*7 AM", + startTime: "07:00", + endTime: "08:00", + offerPremium: false, + }, + ], + }, + ], + estimatedServiceMinutesMinimum: 90, + estimatedServiceMinutesMaximum: 120, + }, + hideSomeDaysForInitialView: true, + hideSecondMonth: false, + preSelectedDate: null, +}; + +const mockCallbackResponse = { + days: [ + { + date: "2024-12-30", + timeSlots: [ + { + id: "06747-01820-S-B*20453*7 AM", + startTime: "07:00", + endTime: "08:00", + offerPremium: false, + }, + ], + }, + { + date: "2024-01-02", + timeSlots: [ + { + id: "06747-01820-S-B*20453*7 AM", + startTime: "07:00", + endTime: "08:00", + offerPremium: false, + }, + ], + }, + ], + estimatedServiceMinutesMinimum: 90, + estimatedServiceMinutesMaximum: 120, +}; + +const mockPromiseResolve = (returnValue) => + jest.fn(async () => Promise.resolve({ valid: returnValue })); + +describe("date-picker.vue", () => { + describe("initial setup", () => { + test("Expect initializeComponent to be run", () => { + // Arrange + const { wrapper } = setupMocks({ + propsData: { + selectableDatesSetting: "custom", + }, + }); + wrapper.vm.initializeComponent = jest.fn().mockImplementation(); + wrapper.vm.initializeComponent(mockInitialData); + + // Assert + expect(wrapper.vm.initializeComponent).toHaveBeenCalled(); + + wrapper.unmount(); + }); + + test("When initializeComponent runs, expect will run setCalendarData method", () => { + // Arrange + const { wrapper } = setupMocks({ + propsData: { + selectableDatesSetting: "custom", + }, + }); + const spy = jest.spyOn(wrapper.vm, "setCalendarData"); + + wrapper.vm.$nextTick(); + + // Act + wrapper.vm.initializeComponent(mockInitialData); + + // Assert + expect(spy).toHaveBeenCalled(); + + wrapper.unmount(); + }); + + test("If prop customComponentId is supplied, expect it will be used for componentId in setup method", () => { + // Arrange + const { wrapper } = setupMocks({ + propsData: { + selectableDatesSetting: "custom", + customComponentId: "dateQuestion", + }, + }); + + // Act + const componentIdValue = wrapper.vm.componentId; + + // Assert + expect(componentIdValue).toEqual("dateQuestion"); + + wrapper.unmount(); + }); + + test("If prop customSelectableDatesCallback is not supplied, expect the default function will return empty array", () => { + // Arrange + const { wrapper } = setupMocks({ + propsData: { + selectableDatesSetting: "custom", + }, + }); + + // Act + const callbackResponse = wrapper.vm.customSelectableDatesCallback( + "2023-07-24", + "2023-08-19", + "Inshop", + "006747" + ); + + // Assert + expect(callbackResponse).toEqual([]); + + wrapper.unmount(); + }); + + test("expect a date string created from today", () => { + // Arrange + const { wrapper } = setupMocks({ + propsData: { + selectableDatesSetting: "custom", + }, + }); + const todayDate = new Date(); + const todayDateString = + todayDate.getFullYear() + + "-" + + ("0" + (todayDate.getMonth() + 1)).slice(-2) + + "-" + + ("0" + todayDate.getDate()).slice(-2); + + // Assert + expect(wrapper.vm.todayString).toEqual(todayDateString); + + wrapper.unmount(); + }); + + test("expect a date string created from todayOverrideDateString", () => { + // Arrange + const { wrapper } = setupMocks({ + propsData: { + selectableDatesSetting: "custom", + todayOverrideDateString: "2023-07-02", + }, + }); + + // Assert + expect(wrapper.vm.todayString).toEqual("2023-07-02"); + + wrapper.unmount(); + }); + + test("expect computed todayDayIndex returns the correct day index", () => { + // Arrange + const { wrapper } = setupMocks({ + propsData: { + selectableDatesSetting: "custom", + todayOverrideDateString: "2023-07-02", + }, + }); + + // Assert + expect(wrapper.vm.todayDayIndex).toEqual(0); + + wrapper.unmount(); + }); + + test("expect computed todayDateNum returns the correct date number", () => { + // Arrange + const { wrapper } = setupMocks({ + propsData: { + selectableDatesSetting: "custom", + todayOverrideDateString: "2023-07-02", + }, + }); + + // Assert + expect(wrapper.vm.todayDateNum).toEqual(2); + + wrapper.unmount(); + }); + + test("expect computed currentWeekStartDateNum returns the correct date number when todayDayIndex is < todayDateNum", () => { + // Arrange + const { wrapper } = setupMocks({ + propsData: { + selectableDatesSetting: "custom", + todayOverrideDateString: "2023-07-05", + }, + }); + + // Assert + expect(wrapper.vm.currentWeekStartDateNum).toEqual(2); + + wrapper.unmount(); + }); + + test("expect computed currentWeekStartDateNum returns 1 when todayDayIndex is >= todayDateNum", () => { + // Arrange + const { wrapper } = setupMocks({ + propsData: { + selectableDatesSetting: "custom", + todayOverrideDateString: "2023-07-01", + }, + }); + + // Assert + expect(wrapper.vm.currentWeekStartDateNum).toEqual(1); + + wrapper.unmount(); + }); + + test("expect computed currentWeekEndDateNum returns correct value", () => { + // Arrange + const { wrapper } = setupMocks({ + propsData: { + selectableDatesSetting: "custom", + todayOverrideDateString: "2023-07-05", + }, + }); + + // Assert + expect(wrapper.vm.currentWeekEndDateNum).toEqual(8); + + wrapper.unmount(); + }); + + test("expect computed calendarViewDirection returns 'past' when prop selectableDatesSetting is 'past'", () => { + // Arrange + const { wrapper } = setupMocks({ + propsData: { + selectableDatesSetting: "past", + todayOverrideDateString: "2023-07-05", + }, + }); + + // Assert + expect(wrapper.vm.calendarViewDirection).toEqual("past"); + + wrapper.unmount(); + }); + + test("expect computed calendarViewDirection returns 'future' when prop selectableDatesSetting is 'custom'", () => { + // Arrange + const { wrapper } = setupMocks({ + propsData: { + selectableDatesSetting: "custom", + todayOverrideDateString: "2023-07-05", + }, + }); + + // Assert + expect(wrapper.vm.calendarViewDirection).toEqual("future"); + + wrapper.unmount(); + }); + + describe("when loadInitialData is run...", () => { + test("and this.todayString exists, expect it should have a todayDate matching today", async () => { + // Arrange + const { wrapper } = setupMocks({}); + const todayDate = new Date(); + const todayDateString = + todayDate.getFullYear() + + "-" + + ("0" + (todayDate.getMonth() + 1)).slice(-2) + + "-" + + ("0" + todayDate.getDate()).slice(-2); + + // Act + const testResult = await wrapper.vm.loadInitialData({ + selectableDatesSetting: "custom", + initialViewRowsToShow: 5, + customSelectableDatesCallback: mockPromiseResolve, + preSelectedDate: null, + }); + + // Assert + expect(testResult).toMatchObject({ + todayDate: todayDateString, + }); + + wrapper.unmount(); + }); + + test("and overrride date exists, expect it should have a todayDate matching the override", async () => { + // Arrange + const overrideDate = mockValuesForSetDate01.overrideDate; + const { wrapper } = setupMocks({ + propsData: { + selectableDatesSetting: "custom", + todayOverrideDateString: overrideDate, + }, + }); + + // Act + const localThis = { + todayString: null, + todayOverrideDateString: overrideDate, + getMonthEnd: () => mockValuesForSetDate01.monthEnd, + getInitialViewWeeks: () => mockValuesForSetDate01.initialViewWeeks, + }; + const testResult = await datePicker.methods.loadInitialData.call(localThis, { + selectableDatesSetting: "custom", + initialViewRowsToShow: 5, + customSelectableDatesCallback: mockPromiseResolve, + preSelectedDate: null, + + todayOverrideDateString: overrideDate, + }); + + // Assert + expect(testResult).toMatchObject({ + todayDate: overrideDate, + }); + + wrapper.unmount(); + }); + + test("with no this.todayString or overrride date, expect it should have a todayDate matching today", async () => { + // Arrange + const { wrapper } = setupMocks({}); + const todayDate = new Date(); + const todayDateString = + todayDate.getFullYear() + + "-" + + ("0" + (todayDate.getMonth() + 1)).slice(-2) + + "-" + + ("0" + todayDate.getDate()).slice(-2); + + // Act + const localThis = { + todayString: null, + getMonthEnd: () => mockValuesForSetDate01.monthEnd, + getInitialViewWeeks: () => mockValuesForSetDate01.initialViewWeeks, + }; + + //Act + const testResult = await datePicker.methods.loadInitialData.call(localThis, { + selectableDatesSetting: "custom", + initialViewRowsToShow: 5, + customSelectableDatesCallback: mockPromiseResolve, + preSelectedDate: null, + }); + + // Assert + expect(testResult).toMatchObject({ + todayDate: todayDateString, + }); + + wrapper.unmount(); + }); + + test("and config.selectableDatesSetting is PAST, expect it should have a calendarViewDirection of PAST", async () => { + // Arrange + const { wrapper } = setupMocks({}); + + // Act + const testResult = await wrapper.vm.loadInitialData({ + selectableDatesSetting: "past", + initialViewRowsToShow: 5, + customSelectableDatesCallback: mockPromiseResolve, + }); + + // Assert + expect(testResult).toMatchObject({ + calendarViewDirection: "past", + }); + + wrapper.unmount(); + }); + + test("and config.selectableDatesSetting is CUSTOM, expect it should have a calendarViewDirection of FUTURE", async () => { + // Arrange + const { wrapper } = setupMocks({}); + + // Act + const testResult = await wrapper.vm.loadInitialData({ + selectableDatesSetting: "custom", + initialViewRowsToShow: 5, + customSelectableDatesCallback: mockPromiseResolve, + }); + + // Assert + expect(testResult).toMatchObject({ + calendarViewDirection: "future", + }); + + wrapper.unmount(); + }); + }); + + describe("when setCalendarData is run...", () => { + test("expect that data elements are updated properly", async () => { + // Arrange + const { wrapper } = setupMocks({ + propsData: { + selectableDatesSetting: "custom", + todayOverrideDateString: mockValuesForSetDate01.overrideDate, + }, + }); + + // Act + await wrapper.vm.setCalendarData(mockValuesForSetDate01.config); + + // Assert + expect(wrapper.vm.hideSomeDaysForInitialView).toBe(true); + expect(wrapper.vm.selectableDatesData[0]).toMatchObject({ + date: "2023-12-30", + }); + expect(wrapper.vm.selectableDatesData[0].timeSlots.length).toBeGreaterThan(0); + expect(wrapper.vm.months.length).toBe(7); + expect(wrapper.vm.months[0]).toMatchObject({ + monthLabel: "December", + monthIndex: 12, + monthString: "December-2023", + yearNum: 2023, + startDateDayIndex: 5, + monthClass: "", + isMonthThatHidesSomeDaysForInitialView: true, + }); + + wrapper.unmount(); + }); + + test("expect that this.months are reversed if direction is past", async () => { + // Arrange + const { wrapper } = setupMocks({ + propsData: { + selectableDatesSetting: "past", + todayOverrideDateString: mockValuesForSetDate01.overrideDate, + }, + }); + const modifiedConfig = { ...mockValuesForSetDate01.config }; + modifiedConfig.calendarViewDirection = "past"; + + // Act + await wrapper.vm.setCalendarData(modifiedConfig); + + // Assert + expect(wrapper.vm.months.length).toBe(37); + expect(wrapper.vm.months[0]).toMatchObject({ + monthString: "December-2020", + startDateDayIndex: 2, + monthClass: " month-hidden last-available-month", + }); + + wrapper.unmount(); + }); + + test("expect that if there's a pre-selected date that it will scroll to it", async () => { + // Arrange + const { wrapper } = setupMocks({ + propsData: { + selectableDatesSetting: "custom", + todayOverrideDateString: mockValuesForSetDate01.overrideDate, + }, + }); + const modifiedConfig = { ...mockValuesForSetDate01.config }; + modifiedConfig.preSelectedDate = "2023-12-25"; + wrapper.vm.scrollToElement = jest.fn().mockImplementation(); + + // Act + await wrapper.vm.setCalendarData(modifiedConfig); + await wrapper.vm.$nextTick(); + + // Assert + expect(wrapper.vm.scrollToElement).toHaveBeenCalledWith("December-2023"); + + wrapper.unmount(); + }); + + test("expect that if there's a pre-selected date within the last month available then the View More Dates button will be hidden", async () => { + // Arrange + const { wrapper } = setupMocks({ + propsData: { + selectableDatesSetting: "custom", + todayOverrideDateString: mockValuesForSetDate01.overrideDate, + }, + }); + const modifiedConfig = { ...mockValuesForSetDate01.config }; + modifiedConfig.preSelectedDate = "2024-06-11"; + wrapper.vm.scrollToElement = jest.fn().mockImplementation(); + + // Act + await wrapper.vm.setCalendarData(modifiedConfig); + await wrapper.vm.$nextTick(); + + // Assert + expect(wrapper.vm.disableViewMoreDatesButton).toBe(true); + + wrapper.unmount(); + }); + + test("expect that this.months are reversed if direction is past", async () => { + // Arrange + const { wrapper } = setupMocks({ + propsData: { + selectableDatesSetting: "past", + todayOverrideDateString: mockValuesForSetDate01.overrideDate, + }, + }); + const modifiedConfig = { ...mockValuesForSetDate01.config }; + modifiedConfig.calendarViewDirection = "past"; + + // Act + await wrapper.vm.setCalendarData(modifiedConfig); + + // Assert + expect(wrapper.vm.months.length).toBe(37); + expect(wrapper.vm.months[0]).toMatchObject({ + monthString: "December-2020", + startDateDayIndex: 2, + monthClass: " month-hidden last-available-month", + }); + + wrapper.unmount(); + }); + + test("expect that if there's a pre-selected date that it will scroll to it", async () => { + // Arrange + const { wrapper } = setupMocks({ + propsData: { + selectableDatesSetting: "custom", + todayOverrideDateString: mockValuesForSetDate01.overrideDate, + }, + }); + const modifiedConfig = { ...mockValuesForSetDate01.config }; + modifiedConfig.preSelectedDate = "2023-12-25"; + wrapper.vm.scrollToElement = jest.fn().mockImplementation(); + + // Act + await wrapper.vm.setCalendarData(modifiedConfig); + await wrapper.vm.$nextTick(); + + // Assert + expect(wrapper.vm.scrollToElement).toHaveBeenCalledWith("December-2023"); + + wrapper.unmount(); + }); + }); + + describe("when getInitialViewWeeks is run...", () => { + test("expect that 5 weeks will be returned when no preselected date", () => { + // Arrange + const { wrapper } = setupMocks({}); + const todayString = "2023-02-01"; + + // Act + const testResult = wrapper.vm.getInitialViewWeeks(todayString, 5); + + // Assert + expect(testResult.length).toBe(5); + + wrapper.unmount(); + }); + + test("expect that today will be in the start/end range of dates of the first week when no preselected date", () => { + // Arrange + const { wrapper } = setupMocks({}); + const todayString = "2023-02-01"; + + // Act + const testResult = wrapper.vm.getInitialViewWeeks(todayString, 5); + + // Assert + expect( + testResult[0].weekStartDate <= todayString && + todayString <= testResult[0].weekEndDate + ).toBe(true); + + wrapper.unmount(); + }); + + test("expect that single weeks that span 2 months will be split into separate shorter 'week' spans", () => { + // Arrange + const { wrapper } = setupMocks({}); + const todayString = "2023-02-15"; + + // Act + const testResult = wrapper.vm.getInitialViewWeeks(todayString, 5); + + // Assert + expect(testResult.length).toBe(5); + expect(testResult[0]).toMatchObject({ + weekStartDate: "2023-02-12", + }); + expect(testResult[testResult.length - 1]).toMatchObject({ + weekEndDate: "2023-03-11", + }); + + wrapper.unmount(); + }); + + test("expect that if today is 7/31/23 and there's a preselected date of 10/1/23 that there will be 14 initial weeks returned", () => { + // Arrange + const { wrapper } = setupMocks({}); + const mockPreSelectedDate = "2023-10-01"; + + // Act + const testResult = wrapper.vm.getInitialViewWeeks( + "2023-07-31", + 5, + mockPreSelectedDate + ); + + // Assert + expect(testResult.length).toBe(14); + + wrapper.unmount(); + }); + + test("expect that if there's a preselected date that the last week end date will be the end of the month of the preselected date", () => { + // Arrange + const { wrapper } = setupMocks({}); + const mockPreSelectedDate = "2023-10-01"; + + // Act + const testResult = wrapper.vm.getInitialViewWeeks( + "2023-07-31", + 5, + mockPreSelectedDate + ); + + // Assert + expect(testResult[testResult.length - 1]).toMatchObject({ + weekEndDate: "2023-10-31", + }); + + wrapper.unmount(); + }); + }); + + describe("when there's a pre-selected date...", () => { + test("and loadInitialData runs, expect hideSomeDaysForInitialView and hideSecondMonth to be false", async () => { + // Arrange + // uses "2023-12-01" which would set both hideSomeDaysForInitialView and hideSecondMonth to true, unless there was a pre-selected date + const overrideDate = mockValuesForSetDate01.overrideDate; + const mockMonthEnd = mockValuesForSetDate01.monthEnd; + const mockInitialViewWeeks = mockValuesForSetDate01.initialViewWeeks; + const { wrapper } = setupMocks({ + propsData: { + selectableDatesSetting: "custom", + todayOverrideDateString: overrideDate, + }, + }); + + // Act + const localThis = { + todayString: null, + todayOverrideDateString: overrideDate, + getMonthEnd: () => mockMonthEnd, + getInitialViewWeeks: () => mockInitialViewWeeks, + }; + const testResult = await datePicker.methods.loadInitialData.call(localThis, { + selectableDatesSetting: "custom", + initialViewRowsToShow: 5, + customSelectableDatesCallback: mockPromiseResolve, + preSelectedDate: "2024-01-01", + todayOverrideDateString: overrideDate, + }); + + // Assert + expect(testResult).toMatchObject({ + hideSomeDaysForInitialView: false, + hideSecondMonth: false, + }); + + wrapper.unmount(); + }); + }); + }); + + describe("showAnotherMonth method...", () => { + test("expect that when hideSomeDaysForInitialView is true that it scrolls to correct month", async () => { + // Arrange + const { wrapper } = setupMocks({ + propsData: { + selectableDatesSetting: "custom", + todayOverrideDateString: mockValuesForSetDate01.overrideDate, + }, + }); + wrapper.vm.months = mockValuesForSetDate01.monthsData; + wrapper.vm.updateSelectableDates = jest.fn().mockImplementation(); + wrapper.vm.scrollToElement = jest.fn().mockImplementation(); + wrapper.vm.hideSomeDaysForInitialView = true; + + // Act + await wrapper.vm.showAnotherMonth(); + + // Assert + expect(wrapper.vm.updateSelectableDates).toHaveBeenCalledWith( + "2023-12-31", + "2023-12-31" + ); + expect(wrapper.vm.scrollToElement).toHaveBeenCalledWith("December-2023"); + + wrapper.unmount(); + }); + + test("expect that when hideSomeDaysForInitialView is false and direction is FUTURE that it scrolls to correct month", async () => { + // Arrange + const { wrapper } = setupMocks({ + propsData: { + selectableDatesSetting: "custom", + todayOverrideDateString: mockValuesForSetDate01.overrideDate, + }, + }); + wrapper.vm.months = [...mockValuesForSetDate01.monthsData]; // copy array w/o reference to original + wrapper.vm.updateSelectableDates = jest.fn().mockImplementation(); + wrapper.vm.scrollToElement = jest.fn().mockImplementation(); + wrapper.vm.hideSomeDaysForInitialView = false; + + // Act + await wrapper.vm.showAnotherMonth(); + + // Assert + expect(wrapper.vm.updateSelectableDates).toHaveBeenCalledWith( + "2024-01-01", + "2024-01-31" + ); + expect(wrapper.vm.scrollToElement).toHaveBeenCalledWith("January-2024"); + + wrapper.unmount(); + }); + + test("expect that when hideSomeDaysForInitialView is false and direction is PAST that it scrolls to correct month", async () => { + // Arrange + const { wrapper } = setupMocks({ + propsData: { + selectableDatesSetting: "past", + todayOverrideDateString: mockValuesForSetDate01.overrideDate, + }, + }); + // Force correct state for this test. // TODO - figure out why original array is getting mutated here, making next line necessary + mockValuesForSetDate01.monthsData[1].monthClass = " month-hidden"; + + wrapper.vm.months = [...mockValuesForSetDate01.monthsData]; // copy array w/o reference to original + wrapper.vm.updateSelectableDates = jest.fn().mockImplementation(); + wrapper.vm.scrollToElement = jest.fn().mockImplementation(); + wrapper.vm.hideSomeDaysForInitialView = false; + + // Act + await wrapper.vm.showAnotherMonth(); + + // Assert + expect(wrapper.vm.updateSelectableDates).toHaveBeenCalledWith( + "2024-01-01", + "2024-01-31" + ); + expect(wrapper.vm.scrollToElement).toHaveBeenCalledWith("January-2024"); + + wrapper.unmount(); + }); + + test("expect that when the month to scroll to is the last available month that it will disable the View More Dates button", async () => { + // Arrange + const { wrapper } = setupMocks({ + propsData: { + selectableDatesSetting: "custom", + todayOverrideDateString: mockValuesForSetDate01.overrideDate, + }, + }); + wrapper.vm.months = [...mockValuesForSetDate01.monthsData]; // copy array w/o reference to original + wrapper.vm.updateSelectableDates = jest.fn().mockImplementation(); + wrapper.vm.scrollToElement = jest.fn().mockImplementation(); + wrapper.vm.disableViewMoreDatesButton = false; + mockValuesForSetDate01.monthsData[1].monthClass = " month-hidden last-available-month"; // Force correct state for this test. + + // Act + await wrapper.vm.showAnotherMonth(); + + // Assert + expect(wrapper.vm.disableViewMoreDatesButton).toBe(true); + + wrapper.unmount(); + }); + }); + + describe("updateSelectableDates method...", () => { + test("expect that new dates from callback are added to selectableDatesData", async () => { + // Arrange + const { wrapper } = setupMocks({ + propsData: { + selectableDatesSetting: "custom", + todayOverrideDateString: mockValuesForSetDate01.overrideDate, + customSelectableDatesCallback: jest.fn(() => mockCallbackResponse), + }, + }); + wrapper.vm.selectableDatesData = [ + { + date: "2023-12-30", + timeSlots: [ + { + id: "06747-01820-S-B*20453*7 AM", + startTime: "07:00", + endTime: "08:00", + offerPremium: false, + }, + ], + }, + ]; + wrapper.vm.months = mockValuesForSetDate01.monthsData; + + // Act + await wrapper.vm.updateSelectableDates("2024-01-01", "2024-01-05"); + const isFoundInArray = wrapper.vm.selectableDatesData.find((obj) => { + return obj.date === "2024-01-02"; + }); + + // Assert + expect(isFoundInArray).not.toBeUndefined(); + + wrapper.unmount(); + }); + + test("expect that isSelectable flag on selectable dates is set to true", async () => { + // Arrange + const { wrapper } = setupMocks({ + propsData: { + selectableDatesSetting: "custom", + todayOverrideDateString: mockValuesForSetDate01.overrideDate, + customSelectableDatesCallback: jest.fn(() => mockCallbackResponse), + }, + }); + wrapper.vm.selectableDatesData = [ + { + date: "2023-12-30", + timeSlots: [ + { + id: "06747-01820-S-B*20453*7 AM", + startTime: "07:00", + endTime: "08:00", + offerPremium: false, + }, + ], + }, + ]; + wrapper.vm.months = mockValuesForSetDate01.monthsData; + + // Act + await wrapper.vm.updateSelectableDates("2024-01-01", "2024-01-05"); + const targetMonth = wrapper.vm.months.find((month) => { + return month.monthString === "January-2024"; + }); + const dateInArray = targetMonth.dates.find((date) => { + return date.inputValue === "2024-01-02"; + }); + + // Assert + expect(dateInArray).toMatchObject({ + isSelectable: true, + }); + + wrapper.unmount(); + }); + }); + + describe("scrollToElement method...", () => { + test("expect that new dates from callback are added to selectableDatesData", () => { + // Arrange + const container = document.createElement("div"); + container.classList.add("page-container-grouped-styles"); + const targetElement = document.createElement("div"); + targetElement.setAttribute("id", "January-2024"); + container.appendChild(targetElement); + document.body.appendChild(container); + window.requestAnimationFrame = jest.fn(); + const { wrapper } = setupMocks({ + propsData: { + selectableDatesSetting: "custom", + todayOverrideDateString: mockValuesForSetDate01.overrideDate, + }, + }); + + // Act + wrapper.vm.scrollToElement("January-2024"); + + // Assert + expect(window.requestAnimationFrame).toBeCalled(); + + wrapper.unmount(); + }); + }); + + describe("If user selects a date...", () => { + test("expect it will emit update:modelValue", () => { + // Arrange + const { wrapper } = setupMocks({ + propsData: { + selectableDatesSetting: "custom", + todayOverrideDateString: "2023-07-05", + }, + }); + const dateToSelect = "2023-07-07"; + + // Act + wrapper.setValue({ selectedDate: dateToSelect }); + + // Assert + expect(wrapper.emitted()["update:modelValue"][0]).toEqual([ + { selectedDate: "2023-07-07" }, + ]); + + wrapper.unmount(); + }); + + test("expect watched modelValue will be triggered", () => { + // Arrange + const { wrapper } = setupMocks({ + propsData: { + selectableDatesSetting: "custom", + todayOverrideDateString: "2023-07-05", + }, + }); + const dateToSelect = "2023-07-07"; + wrapper.vm.resetField = jest.fn(); + + // Act + wrapper.vm.$options.watch.modelValue.call(wrapper.vm, dateToSelect); + + // Assert + expect(wrapper.vm.resetField).toBeCalled(); + expect(wrapper.vm.resetField).toBeCalledWith({ value: dateToSelect }); + + wrapper.unmount(); + }); + }); + + describe("If fireDateSelectedEvent runs... ", () => { + test("expect it will emit date-clicked on mouse clicks ", () => { + // Arrange + const { wrapper } = setupMocks({ + propsData: { + selectableDatesSetting: "custom", + }, + }); + + // Act + wrapper.vm.fireDateSelectedEvent({}); + const flattenedEmitted = JSON.stringify(wrapper.emitted()); + + // Assert + expect(wrapper.emitted()).toHaveProperty("date-clicked"); + expect(flattenedEmitted.includes("date-clicked")).toBeTruthy; + + wrapper.unmount(); + }); + + test("expect it will NOT emit date-clicked on keyboard arrow navigation ", () => { + // Arrange + const { wrapper } = setupMocks({ + propsData: { + selectableDatesSetting: "custom", + }, + }); + + // Act + wrapper.vm.fireDateSelectedEvent({ screenX: 0, screenY: 0 }); + const flattenedEmitted = JSON.stringify(wrapper.emitted()); + + // Assert + expect(wrapper.emitted()).not.toHaveProperty("date-clicked"); + expect(flattenedEmitted.includes("date-clicked")).toBe(false); + + wrapper.unmount(); + }); + }); + + describe("If user triggers a validation error...", () => { + test("expect that watched errorMessage will be triggered", () => { + // Arrange + const { wrapper } = setupMocks({ + propsData: { + selectableDatesSetting: "custom", + todayOverrideDateString: "2023-07-05", + }, + }); + wrapper.vm.scrollToElement = jest.fn(); + + // Act + wrapper.vm.$options.watch.errorMessage.call(wrapper.vm, "anything"); + + // Assert + expect(wrapper.vm.scrollToElement).toBeCalled(); + expect(wrapper.vm.scrollToElement).toBeCalledWith("date-of-month-error"); + + wrapper.unmount(); + }); + }); +}); + +function setupMocks(mountOptionsMockData = {}) { + store.getters = { + order: { + serviceLocation: { appointmentType: "Inshop", provider: { providerNumber: "12345" } }, + }, + }; + const initialMountOptionsMockData = { + store: { + getters: store.getters, + }, + }; + const mountOptions = getMountOptions( + Object.assign(initialMountOptionsMockData, mountOptionsMockData) + ); + + const wrapper = shallowMount(datePicker, mountOptions); + + return { wrapper }; +} diff --git a/src/digital-components/date-picker/date-picker.spec.js1 b/src/digital-components/date-picker/date-picker.spec.js1 deleted file mode 100644 index fd2747d83..000000000 --- a/src/digital-components/date-picker/date-picker.spec.js1 +++ /dev/null @@ -1,477 +0,0 @@ -import datePicker from "./date-picker"; - -// Supporting Files -import { shallowMount } from "@vue/test-utils"; -import { getMountOptions } from "@/helpers/unit-test-helper.js"; - -describe("date-picker.vue", () => { - describe("initial setup", () => { - test("Creates a date object from today", async () => { - // Arrange - const { wrapper } = await setupMocks({ - propsData: { - selectableDatesSetting: "custom" - }, - }); - - wrapper.vm.loadInitialData = jest.fn().mockImplementation( - () => - new Promise((resolve, reject) => { - resolve({ - data: [responseValue], - }); - }) - ); - - await wrapper.vm.$nextTick(); - await wrapper.vm.$nextTick(); - await wrapper.vm.$nextTick(); - await wrapper.vm.$nextTick(); - await wrapper.vm.$nextTick(); - await wrapper.vm.$nextTick(); - await wrapper.vm.$nextTick(); - await wrapper.vm.$nextTick(); - await wrapper.vm.$nextTick(); - await wrapper.vm.$nextTick(); - await wrapper.vm.$nextTick(); - await wrapper.vm.$nextTick(); - await wrapper.vm.$nextTick(); - await wrapper.vm.$nextTick(); - await wrapper.vm.$nextTick(); - await wrapper.vm.$nextTick(); - await wrapper.vm.$nextTick(); - await wrapper.vm.$nextTick(); - await wrapper.vm.$nextTick(); - await wrapper.vm.$nextTick(); - await wrapper.vm.$nextTick(); - - expect(wrapper.vm.loadInitialData).toHaveBeenCalled(); - - // console.log("wrapper.vm.today: ", wrapper.vm.today) - - // // Act - // const testResult = wrapper.vm.today instanceof Date; - // console.log("testResult: ", testResult) - - // // Assert - // expect(testResult).toEqual(true); - - wrapper.unmount(); - }); - }); - // describe("formatNumberToSetDigits", () => { - // const testScenarios = [ - // [0, 3, "000"], - // [8, 2, "08"], - // [345, 2, "345"], - // [4567, 0, "4567"], - // ]; - // test.each(testScenarios)( - // "when num is %s and digits is %s, formatNumberToSetDigits should return %s", - // async (num, digits, expectedReturn) => { - // // Arrange - // const { wrapper } = setupMocks({}); - - // // Act - // const fnReturn = wrapper.vm.formatNumberToSetDigits(num, digits); - - // // Assert - // expect(fnReturn).toEqual(expectedReturn); - // } - // ); - // }); - // describe("formatDate", () => { - // test("Can format a date as MM/dd/yyyy", () => { - // // Arrange - // const { wrapper } = setupMocks({}); - // const testDate = new Date("01-26-2023"); - - // // Act - // const testResult = wrapper.vm.formatDate(testDate); - - // // Assert - // expect(testResult).toEqual("01/26/2023"); - - // wrapper.unmount(); - // }); - - // // test("Can get correct initialFirstDate from today", () => { - // // // Arrange - // // const { wrapper } = setupMocks({ - // // propsData: { - // // todayDate: "Thu Jan 26 2023", - // // }, - // // }); - - // // // Act - // // const testResult = wrapper.vm.initialFirstDate; - // // const testResultAsDateString = testResult.toDateString(); - - // // // Assert - // // expect(testResultAsDateString).toEqual("Sun Jan 22 2023"); - - // // wrapper.unmount(); - // // }); - // }); - // describe("calendarData", () => { - // test("should return an array", async () => { - // // Arrange - // const { wrapper } = setupMocks({}); - - // // Act - // const testResult = await wrapper.vm.calendarData; - - // // Assert - // expect(Array.isArray(testResult)).toBe(true); - - // wrapper.unmount(); - // }); - // describe("each month should have keys monthLabel, yearNum, dates, firstDateDayIndex", () => { - // const testScenarios = [ - // ["monthLabel", true, "string"], - // ["yearNum", true, "number"], - // ["dates", true, "object"], - // ["firstDateDayIndex", true, "number"], - // ["monthClass", true, "string"], - // ]; - - // test.each(testScenarios)( - // "test for %s key should return %s and be type %s", - // async (label, test, type) => { - // // Arrange - // const { wrapper } = setupMocks({}); - // const containsMonthLabel = (obj) => Object.hasOwn(obj, label); - // const isCorrectType = (obj) => typeof obj[label] === type; - // const isTruthyOrZero = (obj) => (obj[label] || obj[label] === 0 ? true : false); - - // // Act - // const calendarData = await wrapper.vm.calendarData; - // // console.log("calendarData: ", calendarData); - - // // Assert - // expect(calendarData.every(containsMonthLabel)).toBe(true); - // expect(calendarData.every(isCorrectType)).toBe(true); - // expect(calendarData.every(isTruthyOrZero)).toBe(true); - - // wrapper.unmount(); - // } - // ); - // }); - - // describe("the current month's week which includes today", () => { - // const testScenarios = [ - // ["2022-02-05T03:00:00", 1, 2, true], - // ["2024-02-05T03:00:00", 4, 0, true], - // ["2020-01-15T03:00:00", 12, 0, true], - // ]; - - // test.each(testScenarios)( - // "the first date of the week including the date %s should be the date number %s and be day index %s", - // async (todayDateString, startDate, dayOfWeekIndex) => { - // // Arrange - // const { wrapper } = setupMocks({ - // propsData: { - // todayOverrideDateString: todayDateString, - // }, - // }); - - // // Act - // const calendarData = await wrapper.vm.calendarData; - // const currentMonthIndex = calendarData.findIndex((month) => { - // console.log("month: ", month); - // return month.monthClass === "currentMonth"; - // }); - // console.log("calendarData ", calendarData); - // console.log("currentMonthIndex ", currentMonthIndex); - - // // Assert - // expect(calendarData[currentMonthIndex].dates[0].dateNum === startDate).toBe( - // true - // ); - // expect( - // calendarData[currentMonthIndex].dates[0].firstDateDayIndex === - // dayOfWeekIndex - // ).toBe(true); - - // wrapper.unmount(); - // } - // ); - // }); - - // test("the current month's dates array should start with the first day of the week which includes today", async () => { - // // Arrange - // const { wrapper } = setupMocks({}); - // const hasAFirstInDates = (obj) => obj?.dates[0].dateNum === 1; - - // // Act - // const calendarData = await wrapper.vm.calendarData; - // const currentMonthIndex = calendarData.findIndex((month) => { - // return month.monthClass === "currentMonth"; - // }); - - // // Assert - // expect(calendarData[currentMonthIndex].dates[0].dateNum === 1).toBe(true); - - // wrapper.unmount(); - // }); - - // test("for each month (other than current month), the dates array should have the 1st of the month", async () => { - // // Arrange - // const { wrapper } = setupMocks({}); - // const hasAFirstInDates = (obj) => obj?.dates[0].dateNum === 1; - - // // Act - // const calendarData = await wrapper.vm.calendarData; - - // // Assert - // expect(calendarData.every(hasAFirstInDates)).toBe(true); - - // wrapper.unmount(); - // }); - - // describe("for the current month in calendarData, the dates array should have correct days of month", () => { - // const testScenarios = [ - // ["2022-02-05T03:00:00", 28, true], - // ["2024-02-05T03:00:00", 29, true], - // ["2020-01-15T03:00:00", 31, true], - // ]; - - // test.each(testScenarios)( - // "the current month including the date %s should have %s days in its dates array", - // async (dateString, noOfDays) => { - // // Arrange - // const { wrapper } = setupMocks({ - // propsData: { - // todayOverrideDateString: dateString, - // }, - // }); - - // // Act - // const calendarData = await wrapper.vm.calendarData; - // const currentMonthIndex = calendarData.findIndex((month) => { - // return month.monthClass === "currentMonth"; - // }); - - // // Assert - // expect(calendarData[currentMonthIndex].dates.length === noOfDays).toBe(true); - - // wrapper.unmount(); - // } - // ); - - // // test.each(testScenarios)( - // // "the date %s should have %s days in dates array", - // // async (dateString, noOfDays) => { - // // // Arrange - // // const { wrapper } = setupMocks({ - // // propsData: { - // // todayOverrideDateString: dateString, - // // }, - // // }); - // // const hasCorrectNumberOfDates = (obj) => obj?.dates.length === noOfDays; - - // // // Act - // // const calendarData = await wrapper.vm.calendarData; - - // // // Assert - // // expect(calendarData.every(hasCorrectNumberOfDates)).toBe(true); - - // // wrapper.unmount(); - // // } - // // ); - // }); - - // // describe("for each object in array, the dates array should have correct days of month", () => { - // // const testScenarios = [ - // // ["2022-02-05T03:00:00", 28, true], - // // ["2024-02-05T03:00:00", 29, true], - // // ["2020-01-15T03:00:00", 31, true], - // // ]; - - // // test.each(testScenarios)( - // // "the date %s should have %s days in dates array", - // // async (dateString, noOfDays) => { - // // // Arrange - // // const { wrapper } = setupMocks({ - // // propsData: { - // // todayOverrideDateString: dateString, - // // }, - // // }); - // // const hasCorrectNumberOfDates = (obj) => obj?.dates.length === noOfDays; - - // // // Act - // // const calendarData = await wrapper.vm.calendarData; - - // // // Assert - // // expect(calendarData.every(hasCorrectNumberOfDates)).toBe(true); - - // // wrapper.unmount(); - // // } - // // ); - // // }); - - // // test.only("each object in array should have a monthLabel", () => { - // // // Arrange - // // const { wrapper } = setupMocks({}); - // // // const testTodayDate = new Date("01-26-2023"); - - // // // Act - // // const testResult = wrapper.vm.calendarData; - // // const containsMonthLabel = (month) => Object.hasOwn(month, 'monthLabel'); - - // // // Assert - // // expect(testResult.every(containsMonthLabel)).toBe(true); - - // // }); - - // // test.only("array should have a month object with same month as today", () => { - // // // Arrange - // // const { wrapper } = setupMocks({}); - // // // const testTodayDate = new Date("01-26-2023"); - - // // // Act - // // // const testResult = wrapper.vm.calendarData; - // // console.log("wrapper.vm.calendarData: ", wrapper.vm.calendarData) - - // // // Assert - // // // expect(wrapper.vm.calendarData).toEqual("something"); - // // // expect(wrapper.vm.calendarDataOld).toEqual("something"); - - // // expect(wrapper.vm.calendarData[1]).toMatchObject({ - // // "dates": [ - // // { - // // "dateNum": 1 - // // }, - // // { - // // "dateNum": 2 - // // }, - // // { - // // "dateNum": 3 - // // }, - // // { - // // "dateNum": 4 - // // }, - // // { - // // "dateNum": 5 - // // }, - // // { - // // "dateNum": 6 - // // }, - // // { - // // "dateNum": 7 - // // }, - // // { - // // "dateNum": 8 - // // }, - // // { - // // "dateNum": 9 - // // }, - // // { - // // "dateNum": 10 - // // }, - // // { - // // "dateNum": 11 - // // }, - // // { - // // "dateNum": 12 - // // }, - // // { - // // "dateNum": 13 - // // }, - // // { - // // "dateNum": 14 - // // }, - // // { - // // "dateNum": 15 - // // }, - // // { - // // "dateNum": 16 - // // }, - // // { - // // "dateNum": 17 - // // }, - // // { - // // "dateNum": 18 - // // }, - // // { - // // "dateNum": 19 - // // }, - // // { - // // "dateNum": 20 - // // }, - // // { - // // "dateNum": 21 - // // }, - // // { - // // "dateNum": 22 - // // }, - // // { - // // "dateNum": 23 - // // }, - // // { - // // "dateNum": 24 - // // }, - // // { - // // "dateNum": 25 - // // }, - // // { - // // "dateNum": 26 - // // }, - // // { - // // "dateNum": 27 - // // }, - // // { - // // "dateNum": 28 - // // }, - // // { - // // "dateNum": 29 - // // }, - // // { - // // "dateNum": 30 - // // }, - // // { - // // "dateNum": 31 - // // } - // // ], - // // "monthLabel": "January", - // // "yearNum": 2023 - // // }); - - // // wrapper.unmount(); - // // }); - // }); -}); - -function setupMocks(mountOptionsMockData = {}) { - const initialMountOptionsMockData = { - // router: { - // navigate: jest.fn(), - // navigateWithSaving: jest.fn(), - // navigateWithoutSaving: jest.fn(), - // }, - // actionList: [ - // // { - // // actionName: storeActions.SAVE_PART_QUESTION_ANSWERS, - // // data: {}, - // // }, - // // { - // // actionName: storeActions.GET_PARTS, - // // data: {}, - // // }, - // ], - // store: { - // // getters: store.getters, - // // commit: store.commit, - // }, - }; - - const mountOptions = getMountOptions( - Object.assign(initialMountOptionsMockData, mountOptionsMockData) - ); - - // console.log("mountOptions: ", mountOptions) - - const wrapper = shallowMount(datePicker, mountOptions); - - return { wrapper }; -} diff --git a/src/digital-components/date-picker/date-picker.vue b/src/digital-components/date-picker/date-picker.vue index bd1ce8fe2..0683a9180 100644 --- a/src/digital-components/date-picker/date-picker.vue +++ b/src/digital-components/date-picker/date-picker.vue @@ -174,9 +174,8 @@ export default { return this.todayDateNum + (6 - this.todayDayIndex); }, calendarViewDirection() { - if (this.selectableDatesSetting === "past") return "past"; if (this.selectableDatesSetting === "custom") return "future"; - return "none"; + return "past"; }, selectedDate: { get() { @@ -428,7 +427,7 @@ export default { } else if (direction === "past") { // first 0, then -1 for (let i = 0; i >= 0 - monthsBeforeToLoadOffset; i--) { - months.unshift(this.getMonthData(i, options)); + months.unshift(await this.getMonthData(i, options)); } } else { // TODO - IF A CALENDAR WITH BOTH PAST AND FUTURE WAS EVER NEEDED @@ -610,7 +609,7 @@ export default { // find the first day-hidden to become the next api call start date monthStartDateNum = monthToShow.dates.find(({ dayClasses }) => dayClasses.includes("day-hidden")) - .dateNum - 1; // TRY 2 + .dateNum - 1; } else { if (this.calendarViewDirection === "future") { monthToShow = this.months.find((month) => @@ -646,11 +645,12 @@ export default { this.$store.getters.order.serviceLocation.appointmentType, this.$store.getters.order.serviceLocation.provider.providerNumber ); + moreSelectableDates.days.forEach((selectableDate) => { const index = this.selectableDatesData.findIndex( (dateObj) => dateObj.date === selectableDate.date ); - if (index === -1) this.selectableDatesData.push(selectableDate.date); + if (index === -1) this.selectableDatesData.push(selectableDate); this.months.forEach((month) => { // TODO: avoid checking all calendar dates; maybe only ones between monthStart and monthEnd as defined above? month.dates.forEach((date) => { diff --git a/src/digital-components/phone-number-question/phone-number-question.vue b/src/digital-components/phone-number-question/phone-number-question.vue index f3fdc6cd6..86dfc3f04 100644 --- a/src/digital-components/phone-number-question/phone-number-question.vue +++ b/src/digital-components/phone-number-question/phone-number-question.vue @@ -8,7 +8,7 @@ :mask="mask" :isRequired="isRequired" :validationRules="validationRulesForTextBoxQuestion" - cmsWidgetName="PhoneNumberQuestionWidget" /> + :cmsWidgetName="cmsWidgetName" /> diff --git a/src/fmg-components/vehicle-banner/vehicle-banner.spec.js b/src/fmg-components/vehicle-banner/vehicle-banner.spec.js index b4d723e18..1a9222299 100644 --- a/src/fmg-components/vehicle-banner/vehicle-banner.spec.js +++ b/src/fmg-components/vehicle-banner/vehicle-banner.spec.js @@ -18,6 +18,7 @@ describe("vehicleBanner", () => { const { wrapper } = setupMocks({ displayGenericVehicleImageProp: true, imageUrlValue: "NULL", + displayUnmatchedVehicleIconProp: true, }); // Assert @@ -87,7 +88,12 @@ describe("vehicleBanner", () => { ); }); -function setupMocks({ displayGenericVehicleImageProp, imageUrlValue, categoryValue = "CAR" }) { +function setupMocks({ + displayGenericVehicleImageProp, + displayUnmatchedVehicleIconProp, + imageUrlValue, + categoryValue = "CAR", +}) { const mockGetCmsContent = jest.fn(); mockGetCmsContent((cmsWidget, field) => { return field; @@ -108,7 +114,10 @@ function setupMocks({ displayGenericVehicleImageProp, imageUrlValue, categoryVal }); //Mock props - mountOptions.propsData = { displayGenericVehicleImage: displayGenericVehicleImageProp }; + mountOptions.propsData = { + displayGenericVehicleImage: displayGenericVehicleImageProp, + displayUnmatchedVehicleIconProp: displayUnmatchedVehicleIconProp, + }; mountOptions.mixins = [mockMixin]; const wrapper = shallowMount(vehicleBanner, mountOptions); diff --git a/src/fmg-components/vehicle-banner/vehicle-banner.vue b/src/fmg-components/vehicle-banner/vehicle-banner.vue index 8f7be0179..8bff4e9e2 100644 --- a/src/fmg-components/vehicle-banner/vehicle-banner.vue +++ b/src/fmg-components/vehicle-banner/vehicle-banner.vue @@ -13,16 +13,34 @@ export default { required: true, }, cmsWidgetName: String, + displayVehicleImage: { + type: String, + required: false, + default: null, + }, + displayUnmatchedVehicleIcon: { + type: Boolean, + required: false, + default: false, + }, + vehicleCategory: { + type: String, + required: false, + default: null, + }, }, computed: { vehicleImageToDisplay() { + if (this.displayVehicleImage !== null) { + return this.displayVehicleImage; + } if (this.displayGenericVehicleImage) { return this.genericVehicleImage; } - if ( this.$store.getters.vehicle.imageUrl === null || - this.$store.getters.vehicle.imageUrl === "NULL" + this.$store.getters.vehicle.imageUrl === "NULL" || + this.displayUnmatchedVehicleIcon ) { return this.getUnmatchedVehicleIcon(); } @@ -50,7 +68,8 @@ export default { }, methods: { getUnmatchedVehicleIcon() { - switch (this.$store.getters.vehicle.category) { + const category = this.vehicleCategory ?? this.$store.getters.vehicle.category; + switch (category) { case this.vehicleCategories.CAR: return this.carUnmatchedVehicleIcon; case this.vehicleCategories.SUV: diff --git a/src/helpers/heritage-integration/navigation-helper.js b/src/helpers/heritage-integration/navigation-helper.js index 913fbcb1b..fe6a16591 100644 --- a/src/helpers/heritage-integration/navigation-helper.js +++ b/src/helpers/heritage-integration/navigation-helper.js @@ -31,7 +31,8 @@ export async function getPageToRouteExistingOrderTo(toRoute = {}, existingHerita // Return that page, so that it can navigate like normal. if ( toRoute.query[queryStrings.FMG_PAGE] !== undefined && - toRoute.query[queryStrings.FMG_PAGE] !== fmgPageValues.QUOTE && + toRoute.query[queryStrings.FMG_PAGE] !== fmgPageValues.SERVICE_LOCATION && + toRoute.query[queryStrings.FMG_PAGE] !== fmgPageValues.SCHEDULE && !isVinRelatedPage(toRoute) ) { return overrideYmmsDirectionIfNeeded(toRoute); @@ -102,9 +103,6 @@ async function getLatestPageForRedirection() { // If this is a non-CTA navigation, determine where to send the user based on page prerequisites. // This also works if a user has a 'fmg' start_type query string but no current order. // That shouldn't happen, but it's possible. - const vehicleMakeComponent = await getLazyLoadedComponent(fmgPageValues.VEHICLE_MAKE); - const vehicleModelComponent = await getLazyLoadedComponent(fmgPageValues.VEHICLE_MODEL); - const vehicleStyleComponent = await getLazyLoadedComponent(fmgPageValues.VEHICLE_STYLE); const vehicleDamageComponent = await getLazyLoadedComponent(fmgPageValues.VEHICLE_DAMAGE); const estimateComponent = await getLazyLoadedComponent(fmgPageValues.ESTIMATE); const vinLookupComponent = await getLazyLoadedComponent(fmgPageValues.VIN_LOOKUP); @@ -115,21 +113,21 @@ async function getLatestPageForRedirection() { fmgPageValues.CAPABILITY_QUESTIONS ); const quoteComponent = await getLazyLoadedComponent(fmgPageValues.QUOTE); + const serviceLocationComponent = await getLazyLoadedComponent(fmgPageValues.SERVICE_LOCATION); + const scheduleComponent = await getLazyLoadedComponent(fmgPageValues.SCHEDULE); const skipVin = await skipVinLookup(); - if (!vehicleMakeComponent.methods.arePagePrerequisitesValid()) { - return fmgPageValues.VEHICLE_YEAR; - } else if (!vehicleModelComponent.methods.arePagePrerequisitesValid()) { - return fmgPageValues.VEHICLE_MAKE; - } else if (!vehicleStyleComponent.methods.arePagePrerequisitesValid()) { - return fmgPageValues.VEHICLE_MODEL; - } else if (!vehicleDamageComponent.methods.arePagePrerequisitesValid()) { - return fmgPageValues.VEHICLE_STYLE; + if (!vehicleDamageComponent.methods.arePagePrerequisitesValid()) { + return fmgPageValues.VEHICLE; } else if (!estimateComponent.methods.arePagePrerequisitesValid()) { return fmgPageValues.VEHICLE_DAMAGE; } else { - if (quoteComponent.methods.arePagePrerequisitesValid()) { + if (scheduleComponent.methods.arePagePrerequisitesValid()) { + return fmgPageValues.SCHEDULE; + } else if (serviceLocationComponent.methods.arePagePrerequisitesValid()) { + return fmgPageValues.SERVICE_LOCATION; + } else if (quoteComponent.methods.arePagePrerequisitesValid()) { return fmgPageValues.QUOTE; } else if (capabilityQuestionsComponent.methods.arePagePrerequisitesValid()) { return fmgPageValues.CAPABILITY_QUESTIONS; @@ -163,10 +161,7 @@ function overrideYmmsDirectionIfNeeded(toRoute) { if (store.getters.payment.insuranceCoverage.isVerified) { switch (fmgPageValue) { - case fmgPageValues.VEHICLE_YEAR: - case fmgPageValues.VEHICLE_MAKE: - case fmgPageValues.VEHICLE_MODEL: - case fmgPageValues.VEHICLE_STYLE: { + case fmgPageValues.VEHICLE: { return fmgPageValues.VEHICLE_DAMAGE; } default: { diff --git a/src/helpers/heritage-integration/navigation-helper.spec.js b/src/helpers/heritage-integration/navigation-helper.spec.js index d08ce92ec..cb0664f78 100644 --- a/src/helpers/heritage-integration/navigation-helper.spec.js +++ b/src/helpers/heritage-integration/navigation-helper.spec.js @@ -20,7 +20,7 @@ jest.mock("@/router/dynamic-routing/component-loader.js", () => ({ })); describe("getPageToRouteExistingOrderTo", () => { - test("should return vehicle-year", async () => { + test("should return vehicle", async () => { // Arrange const toRoute = { query: {}, @@ -31,75 +31,7 @@ describe("getPageToRouteExistingOrderTo", () => { // Mock out the lazy load calls for all components. mockLazyLoadComponentReturnValues({ - [fmgPageValues.VEHICLE_MAKE]: false, - }); - - // Act - const result = await getPageToRouteExistingOrderTo(toRoute, false); - - //Assert - expect(result).toBe(fmgPageValues.VEHICLE_YEAR); - }); - - test("should return vehicle-make", async () => { - // Arrange - const toRoute = { - query: {}, - }; - - store.commit(storeMutations.UPDATE_IS_REPAIR, false); - store.commit(storeMutations.UPDATE_MAKE, "acura"); - - // Mock out the lazy load calls for all components. - mockLazyLoadComponentReturnValues({ - [fmgPageValues.VEHICLE_MAKE]: true, - [fmgPageValues.VEHICLE_MODEL]: false, - }); - - // Act - const result = await getPageToRouteExistingOrderTo(toRoute, false); - - //Assert - expect(result).toBe(fmgPageValues.VEHICLE_MAKE); - }); - - test("should return vehicle-model", async () => { - // Arrange - const toRoute = { - query: {}, - }; - - store.commit(storeMutations.UPDATE_IS_REPAIR, false); - store.commit(storeMutations.UPDATE_MAKE, "acura"); - - // Mock out the lazy load calls for all components. - mockLazyLoadComponentReturnValues({ - [fmgPageValues.VEHICLE_MAKE]: true, - [fmgPageValues.VEHICLE_MODEL]: true, - [fmgPageValues.VEHICLE_STYLE]: false, - }); - - // Act - const result = await getPageToRouteExistingOrderTo(toRoute, false); - - //Assert - expect(result).toBe(fmgPageValues.VEHICLE_MODEL); - }); - - test("should return vehicle-style", async () => { - // Arrange - const toRoute = { - query: {}, - }; - - store.commit(storeMutations.UPDATE_IS_REPAIR, false); - store.commit(storeMutations.UPDATE_MAKE, "acura"); - - // Mock out the lazy load calls for all components. - mockLazyLoadComponentReturnValues({ - [fmgPageValues.VEHICLE_MAKE]: true, - [fmgPageValues.VEHICLE_MODEL]: true, - [fmgPageValues.VEHICLE_STYLE]: true, + [fmgPageValues.VEHICLE]: true, [fmgPageValues.VEHICLE_DAMAGE]: false, }); @@ -107,7 +39,7 @@ describe("getPageToRouteExistingOrderTo", () => { const result = await getPageToRouteExistingOrderTo(toRoute, false); //Assert - expect(result).toBe(fmgPageValues.VEHICLE_STYLE); + expect(result).toBe(fmgPageValues.VEHICLE); }); test("should return vehicle-damage", async () => { @@ -121,9 +53,7 @@ describe("getPageToRouteExistingOrderTo", () => { // Mock out the lazy load calls for all components. mockLazyLoadComponentReturnValues({ - [fmgPageValues.VEHICLE_MAKE]: true, - [fmgPageValues.VEHICLE_MODEL]: true, - [fmgPageValues.VEHICLE_STYLE]: true, + [fmgPageValues.VEHICLE]: true, [fmgPageValues.VEHICLE_DAMAGE]: true, [fmgPageValues.ESTIMATE]: false, }); @@ -156,9 +86,7 @@ describe("getPageToRouteExistingOrderTo", () => { // Mock out the lazy load calls for all components. mockLazyLoadComponentReturnValues({ - [fmgPageValues.VEHICLE_MAKE]: true, - [fmgPageValues.VEHICLE_MODEL]: true, - [fmgPageValues.VEHICLE_STYLE]: true, + [fmgPageValues.VEHICLE]: true, [fmgPageValues.VEHICLE_DAMAGE]: true, [fmgPageValues.ESTIMATE]: true, [fmgPageValues.CAPABILITY_QUESTIONS]: false, @@ -186,9 +114,7 @@ describe("getPageToRouteExistingOrderTo", () => { // Mock out the lazy load calls for all components. mockLazyLoadComponentReturnValues({ - [fmgPageValues.VEHICLE_MAKE]: true, - [fmgPageValues.VEHICLE_MODEL]: true, - [fmgPageValues.VEHICLE_STYLE]: true, + [fmgPageValues.VEHICLE]: true, [fmgPageValues.VEHICLE_DAMAGE]: true, [fmgPageValues.ESTIMATE]: true, [fmgPageValues.CAPABILITY_QUESTIONS]: false, @@ -216,9 +142,7 @@ describe("getPageToRouteExistingOrderTo", () => { // Mock out the lazy load calls for all components. mockLazyLoadComponentReturnValues({ - [fmgPageValues.VEHICLE_MAKE]: true, - [fmgPageValues.VEHICLE_MODEL]: true, - [fmgPageValues.VEHICLE_STYLE]: true, + [fmgPageValues.VEHICLE]: true, [fmgPageValues.VEHICLE_DAMAGE]: true, [fmgPageValues.ESTIMATE]: true, [fmgPageValues.CAPABILITY_QUESTIONS]: true, @@ -246,9 +170,7 @@ describe("getPageToRouteExistingOrderTo", () => { // Mock out the lazy load calls for all components. mockLazyLoadComponentReturnValues({ - [fmgPageValues.VEHICLE_MAKE]: true, - [fmgPageValues.VEHICLE_MODEL]: true, - [fmgPageValues.VEHICLE_STYLE]: true, + [fmgPageValues.VEHICLE]: true, [fmgPageValues.VEHICLE_DAMAGE]: true, [fmgPageValues.ESTIMATE]: true, [fmgPageValues.CAPABILITY_QUESTIONS]: false, @@ -276,9 +198,7 @@ describe("getPageToRouteExistingOrderTo", () => { // Mock out the lazy load calls for all components. mockLazyLoadComponentReturnValues({ - [fmgPageValues.VEHICLE_MAKE]: true, - [fmgPageValues.VEHICLE_MODEL]: true, - [fmgPageValues.VEHICLE_STYLE]: true, + [fmgPageValues.VEHICLE]: true, [fmgPageValues.VEHICLE_DAMAGE]: true, [fmgPageValues.ESTIMATE]: true, [fmgPageValues.CAPABILITY_QUESTIONS]: false, @@ -306,9 +226,7 @@ describe("getPageToRouteExistingOrderTo", () => { // Mock out the lazy load calls for all components. mockLazyLoadComponentReturnValues({ - [fmgPageValues.VEHICLE_MAKE]: true, - [fmgPageValues.VEHICLE_MODEL]: true, - [fmgPageValues.VEHICLE_STYLE]: true, + [fmgPageValues.VEHICLE]: true, [fmgPageValues.VEHICLE_DAMAGE]: true, [fmgPageValues.ESTIMATE]: true, [fmgPageValues.CAPABILITY_QUESTIONS]: false, diff --git a/src/helpers/heritage-integration/order-helper.js b/src/helpers/heritage-integration/order-helper.js index 010c39ec9..01f5a5d67 100644 --- a/src/helpers/heritage-integration/order-helper.js +++ b/src/helpers/heritage-integration/order-helper.js @@ -114,6 +114,7 @@ async function saveSessionHelper() { storeActions.UPDATE_STORE_WITH_SAVE_SESSION_RESPONSE, { referralNumber: savedSessionInfo.data.referralNumber?.toString(), + referralSequenceNumber: savedSessionInfo.data.referralSequenceNumber, referralCorrelationId: savedSessionInfo.data.referralCorrelationId, referralDate: savedSessionInfo.data.referralDate, parentAccountNumber: savedSessionInfo.data.parentAccountNumber?.toString(), diff --git a/src/helpers/querystring-helper.js b/src/helpers/querystring-helper.js new file mode 100644 index 000000000..38583b1ae --- /dev/null +++ b/src/helpers/querystring-helper.js @@ -0,0 +1,10 @@ +export function getQuerystringParameter(key) { + 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); + } + + return lowerCaseParams.get(key) ? lowerCaseParams.get(key) : null; +} diff --git a/src/layouts/address-lookup/address-lookup.spec.js b/src/layouts/address-lookup/address-lookup.spec.js index 2c4b44950..63847588b 100644 --- a/src/layouts/address-lookup/address-lookup.spec.js +++ b/src/layouts/address-lookup/address-lookup.spec.js @@ -681,7 +681,6 @@ function setupMocks({ }, ], router: { - navigate: jest.fn(), navigate: jest.fn(), navigateWithSaving: jest.fn(), navigateWithoutSaving: jest.fn(), diff --git a/src/layouts/address-lookup/address-lookup.vue b/src/layouts/address-lookup/address-lookup.vue index fe3b00320..48ab0dd08 100644 --- a/src/layouts/address-lookup/address-lookup.vue +++ b/src/layouts/address-lookup/address-lookup.vue @@ -323,10 +323,6 @@ export default { registrationInfo: { firstName: this.customerQuestions.firstName, lastName: this.customerQuestions.lastName, - address: this.customerQuestions.addressQuestions.streetAddress, - city: this.customerQuestions.addressQuestions.city, - state: resultMap.serviceZipValidationResponse.state, - zipCode: this.customerQuestions.addressQuestions.zipCode, }, }, false diff --git a/src/layouts/customer-details/customer-details.vue b/src/layouts/customer-details/customer-details.vue index be533117f..55e1b01b8 100644 --- a/src/layouts/customer-details/customer-details.vue +++ b/src/layouts/customer-details/customer-details.vue @@ -25,14 +25,14 @@ validationRules="email-address-required|email-address-format" /> + v-model="isSmsOptIn" />
- +
diff --git a/src/layouts/license-plate-lookup/license-plate-lookup.vue b/src/layouts/license-plate-lookup/license-plate-lookup.vue index 49ff8ff26..326215243 100644 --- a/src/layouts/license-plate-lookup/license-plate-lookup.vue +++ b/src/layouts/license-plate-lookup/license-plate-lookup.vue @@ -292,8 +292,6 @@ export default { vehicleInfo: Object.assign(vinLookup.data.vehicle, { vin: vinLookup.data.vin }), registrationInfo: { licensePlate: this.licensePlate, - state: resultMap.registrationZipValidationResponse.state, - zipCode: this.registrationZipCode, }, }, false diff --git a/src/layouts/quote/quote.spec.js b/src/layouts/quote/quote.spec.js index 6895b5d0b..b4a3df9fb 100644 --- a/src/layouts/quote/quote.spec.js +++ b/src/layouts/quote/quote.spec.js @@ -263,6 +263,9 @@ describe("quote.vue", () => { payment: { isInsurance: null, }, + serviceLocation: { + state: null, + }, }, }; wrapper.vm.$route = { query: { isInsurance: "true" } }; @@ -290,6 +293,9 @@ describe("quote.vue", () => { payment: { isInsurance: null, }, + serviceLocation: { + state: null, + }, }, }; wrapper.vm.$route = { query: { isInsurance: "false" } }; @@ -317,6 +323,9 @@ describe("quote.vue", () => { payment: { isInsurance: true, }, + serviceLocation: { + state: null, + }, }, }; // Ensure that query param isn't overriding selection @@ -345,6 +354,9 @@ describe("quote.vue", () => { payment: { isInsurance: false, }, + serviceLocation: { + state: null, + }, }, }; // Ensure that query param isn't overriding selection @@ -374,6 +386,9 @@ describe("quote.vue", () => { payment: { isInsurance: null, // Ensure that previous selection isn't overriding selection }, + serviceLocation: { + state: null, + }, }, }; mockTierOnePrice = 200; @@ -404,6 +419,9 @@ describe("quote.vue", () => { payment: { isInsurance: null, // Ensure that previous selection isn't overriding selection }, + serviceLocation: { + state: null, + }, }, }; mockTierOnePrice = 505; @@ -421,6 +439,33 @@ describe("quote.vue", () => { //Assert expect(wrapper.vm.isInsuranceSelected).toBe(true); }); + test("Should default to insurence if user Service zip is from certain States", async () => { + //Arrange + const { wrapper } = setupMocks({}); + store.getters = { + order: { + lineItems: { + glassParts: ["item", "item2"], + }, + payment: { + isInsurance: null, + }, + serviceLocation: { + state: "AZ", + }, + }, + }; + wrapper.vm.$route = { query: null }; + //Act + await quote.beforeRouteEnter.call( + wrapper.vm, + { query: { fmgPage: "quote" } }, + undefined, + (c) => c(wrapper.vm) + ); + //Assert + expect(wrapper.vm.isInsuranceSelected).toBe(true); + }); }); function setupMocks({ customMountOptions }) { diff --git a/src/layouts/quote/quote.vue b/src/layouts/quote/quote.vue index b17b0f3ac..72d519028 100644 --- a/src/layouts/quote/quote.vue +++ b/src/layouts/quote/quote.vue @@ -67,7 +67,7 @@ import { errorMessages } from "@/constants/error-messages"; import { Form, defineRule } from "vee-validate"; import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper"; import { applicationConfig } from "@/constants/application-config"; - +import { payWithInsuranceStates } from "@/constants/pay-with-insurance-states"; defineRule("option-required", required(errorMessages.OPTION_REQUIRED)); export default { @@ -154,11 +154,18 @@ export default { ); }, getDefaultIsInsuranceSelectedValue(availableLineItems) { + const serviceLocationState = store.getters.order.serviceLocation.state; // Override if coming back from QuoteDetails. Remove override after Quote release const isInsuranceOverrideValue = this.$route.query?.isInsurance; const defaultIsInsuranceSelectedValue = this.$store.getters.order.payment.isInsurance; - if (isInsuranceOverrideValue != null) { + //Default to Insurance Tab if user Service zip is from certain States + if ( + serviceLocationState != null && + payWithInsuranceStates.find((item) => item === serviceLocationState) + ) + return true; + else if (isInsuranceOverrideValue != null) { return isInsuranceOverrideValue == "true"; } else if (defaultIsInsuranceSelectedValue != null) { return defaultIsInsuranceSelectedValue; diff --git a/src/layouts/quote/service-package-question/service-package-question.spec.js b/src/layouts/quote/service-package-question/service-package-question.spec.js index af04ff36a..dc0d2005c 100644 --- a/src/layouts/quote/service-package-question/service-package-question.spec.js +++ b/src/layouts/quote/service-package-question/service-package-question.spec.js @@ -135,64 +135,9 @@ describe("service-package-question.vue", () => { expectedModifiedAnswers[packageNameKey].tierThree ); }); - it("should not select a default package if ANY glass part or supporting item has no prices", async () => { + it("should not select a default package if isInsurance is null", async () => { // Note, only the first WSREPAIR item has a 0 for laborAmount, this is enough to not try and select a default package - const wrapper = setupMocks({ - mountOptionsMockData: { - store: { - getters: { - lineItems: { - glassParts: [ - { - partNumber: "FW02627GBYNOEE", - description: "solar, 3rd visor band", - color: "Green Tint, Blue Shade", - partType: "WINDSHIELD", - canSafeliteRecalibrate: false, - requiresRecalibration: false, - requiresCapabilityQuestions: false, - recalibrationType: null, - childParts: null, - laborAmount: 5.0, - }, - ], - supportingItems: [ - { - partNumber: "SUPPLIES-REPAIR", - description: null, - partType: "REPAIR FEE", - laborAmount: 5.0, - }, - { - partNumber: "WSREPAIR", - description: null, - partType: "REPAIR FEE", - laborAmount: 0, - }, - { - partNumber: "WSREPAIR", - description: null, - partType: "REPAIR FEE", - laborAmount: 5.0, - }, - { - partNumber: "WSREPAIR", - description: null, - partType: "REPAIR FEE", - laborAmount: 5.0, - }, - ], - }, - order: { - damage: { - isRepair: false, - glassToReplace: [{ glassLocation: "Windshield" }], - }, - }, - }, - }, - }, - }); + const wrapper = setupMocks({}); // Act wrapper.setProps({ availableLineItems: null }); @@ -202,59 +147,24 @@ describe("service-package-question.vue", () => { // Assert expect(wrapper.vm.selectedPackageName).toBe(null); }); - it("should select a default package if ALL glass parts and supporting items have prices", async () => { + it("should select a default package if isInsurance is NOT null", async () => { const wrapper = setupMocks({ mountOptionsMockData: { store: { getters: { - lineItems: { - glassParts: [ - { - partNumber: "FW02627GBYNOEE", - description: "solar, 3rd visor band", - color: "Green Tint, Blue Shade", - partType: "WINDSHIELD", - canSafeliteRecalibrate: false, - requiresRecalibration: false, - requiresCapabilityQuestions: false, - recalibrationType: null, - childParts: null, - laborAmount: 5.0, - }, - ], - supportingItems: [ - { - partNumber: "SUPPLIES-REPAIR", - description: null, - partType: "REPAIR FEE", - laborAmount: 5.0, - }, - { - partNumber: "WSREPAIR", - description: null, - partType: "REPAIR FEE", - laborAmount: 5.0, - }, - { - partNumber: "WSREPAIR", - description: null, - partType: "REPAIR FEE", - laborAmount: 5.0, - }, - { - partNumber: "WSREPAIR", - description: null, - partType: "REPAIR FEE", - laborAmount: 5.0, - }, - ], - }, order: { damage: { isRepair: false, glassToReplace: [{ glassLocation: "Windshield" }], }, }, + lineItems: { + vaps: [], + }, + hasAnyNonWindshieldGlassParts: false, + payment: { + isInsurance: false, + }, }, }, }, @@ -329,6 +239,9 @@ describe("service-package-question.vue", () => { glassToReplace: [{ glassLocation: "Windshield" }], }, }, + payment: { + isInsurance: false, + }, }, }, }, @@ -758,6 +671,9 @@ function setupMocks({ mountOptionsMockData, props = mockProps }) { }, }, hasAnyNonWindshieldGlassParts: false, + payment: { + isInsurance: null, + }, }, }, }; diff --git a/src/layouts/quote/service-package-question/service-package-question.vue b/src/layouts/quote/service-package-question/service-package-question.vue index 51598631b..e3a4a499a 100644 --- a/src/layouts/quote/service-package-question/service-package-question.vue +++ b/src/layouts/quote/service-package-question/service-package-question.vue @@ -49,7 +49,7 @@ export default { }, watch: { availableLineItems() { - if (this.allGlassPartsAndSupportingItemsHavePrices(this.$store.getters.lineItems)) { + if (this.$store.getters.payment.isInsurance != null) { this.selectDefaultPackage(); } }, @@ -196,30 +196,6 @@ export default { vapsFromStore ); }, - allGlassPartsAndSupportingItemsHavePrices(lineItems) { - if (lineItems?.glassParts) { - for (let i = 0; i < lineItems.glassParts.length; i++) { - if (this.priceIsNullOrZero(lineItems.glassParts[i])) { - return false; - } - } - } - if (lineItems?.supportingItems) { - for (let i = 0; i < lineItems.supportingItems.length; i++) { - if (this.priceIsNullOrZero(lineItems.supportingItems[i])) { - return false; - } - } - } - return true; - }, - priceIsNullOrZero(lineItem) { - return ( - (lineItem.kitPrice == null || lineItem.kitPrice == 0) && - (lineItem.laborAmount == null || lineItem.laborAmount == 0) && - (lineItem.sellingPrice == null || lineItem.sellingPrice == 0) - ); - }, getVapsLineItemsForSelectedPackage(packageName) { const packageContentTypes = getPackageContents( this.glassToReplace, diff --git a/src/layouts/review/review-block/review-block.vue b/src/layouts/review/review-block/review-block.vue index 67e639da9..497aa3634 100644 --- a/src/layouts/review/review-block/review-block.vue +++ b/src/layouts/review/review-block/review-block.vue @@ -3,6 +3,7 @@
{ + beforeEach(() => { + cmsContent = {}; + }); + + describe("Submit", () => { + test("Should push to store if a field has changed", async () => { + // Arrange + let props = generateDefaultProps(); + + const { wrapper } = setupMocks({ + propsData: props, + }); + + // Act + wrapper.vm.onModalOpened(); + + wrapper.vm.firstName = testConstants.newValues.firstName; + + await wrapper.vm.setContactDetails(); + await wrapper.vm.$nextTick(); + + // Assert + expect(wrapper.vm.dispatchStoreAction).toHaveBeenCalled(); + expect(wrapper.vm.closeModal).toHaveBeenCalled(); + }); + + test("Should not push to store if no fields have changed", async () => { + // Arrange + let props = generateDefaultProps(); + + const { wrapper } = setupMocks({ + propsData: props, + }); + + // Act + wrapper.vm.onModalOpened(); + + await wrapper.vm.setContactDetails(); + await wrapper.vm.$nextTick(); + + // Assert + expect(wrapper.vm.dispatchStoreAction).not.toHaveBeenCalled(); + expect(wrapper.vm.closeModal).toHaveBeenCalled(); + }); + }); + + describe("Default values", () => { + test("Should populate on open", () => { + // Arrange + let props = generateDefaultProps(); + + const { wrapper } = setupMocks({ + propsData: props, + }); + + // Act + wrapper.vm.onModalOpened(); + + // Assert + expect(wrapper.vm.firstName).toBe(testConstants.previousCustomerValues.firstName); + expect(wrapper.vm.lastName).toBe(testConstants.previousCustomerValues.lastName); + expect(wrapper.vm.emailAddress).toBe(testConstants.previousCustomerValues.emailAddress); + expect(wrapper.vm.phoneNumber).toBe(testConstants.previousCustomerValues.phoneNumber); + expect(wrapper.vm.isSmsOptIn).toBe(testConstants.previousCustomerValues.isSmsOptIn); + }); + + test("Should replace old values on open", async () => { + // Arrange + let props = generateDefaultProps(); + + const { wrapper } = setupMocks({ + propsData: props, + }); + + // Act + wrapper.vm.firstName = testConstants.newValues.firstName; + wrapper.vm.lastName = testConstants.newValues.lastName; + wrapper.vm.emailAddress = testConstants.newValues.emailAddress; + wrapper.vm.phoneNumber = testConstants.newValues.phoneNumber; + wrapper.vm.isSmsOptIn = testConstants.newValues.isSmsOptIn; + + wrapper.vm.onModalOpened(); + + // Assert + expect(wrapper.vm.firstName).toBe(testConstants.previousCustomerValues.firstName); + expect(wrapper.vm.lastName).toBe(testConstants.previousCustomerValues.lastName); + expect(wrapper.vm.emailAddress).toBe(testConstants.previousCustomerValues.emailAddress); + expect(wrapper.vm.phoneNumber).toBe(testConstants.previousCustomerValues.phoneNumber); + expect(wrapper.vm.isSmsOptIn).toBe(testConstants.previousCustomerValues.isSmsOptIn); + }); + }); +}); + +function generateDefaultProps() { + return { + previousCustomerValues: { + firstName: testConstants.previousCustomerValues.firstName, + lastName: testConstants.previousCustomerValues.lastName, + emailAddress: testConstants.previousCustomerValues.emailAddress, + phoneNumber: testConstants.previousCustomerValues.phoneNumber, + isSmsOptIn: testConstants.previousCustomerValues.isSmsOptIn, + }, + }; +} + +function setupMocks(customMountOptions) { + const mountOptions = getMountOptions(customMountOptions); + + const mockMixin = { + methods: { + getCmsContent: jest.fn((widgetName, cmsFieldName) => { + return cmsContent?.[widgetName]?.[cmsFieldName] ?? ""; + }), + }, + }; + + mountOptions.global.mixins = [mockMixin]; + + const wrapper = shallowMount(customerDetailsModalQuestion, mountOptions); + wrapper.vm.setCmsContent = jest.fn(); + wrapper.vm.openModal = jest.fn(); + wrapper.vm.closeModal = jest.fn(); + wrapper.vm.dispatchStoreAction = jest.fn(); + return { wrapper }; +} diff --git a/src/layouts/review/review-sections/customer-review/customer-details-modal-question/customer-details-modal-question.vue b/src/layouts/review/review-sections/customer-review/customer-details-modal-question/customer-details-modal-question.vue new file mode 100644 index 000000000..0b08abee7 --- /dev/null +++ b/src/layouts/review/review-sections/customer-review/customer-details-modal-question/customer-details-modal-question.vue @@ -0,0 +1,148 @@ + + + diff --git a/src/layouts/review/review-sections/customer-review/customer-review.spec.js b/src/layouts/review/review-sections/customer-review/customer-review.spec.js new file mode 100644 index 000000000..1bcbbe83b --- /dev/null +++ b/src/layouts/review/review-sections/customer-review/customer-review.spec.js @@ -0,0 +1,159 @@ +import { shallowMount } from "@vue/test-utils"; +import { getMountOptions } from "@/helpers/unit-test-helper.js"; + +import customerReview from "@/layouts/review/review-sections/customer-review/customer-review"; + +const testConstants = { + cms: { + header: { + text: "Header", + }, + sms: { + template: "Test {custom:smsOptInJoiner}", + expected: { + ifTrue: "Test in to", + ifFalse: "Test out of", + }, + }, + }, + customer: { + firstName: "First", + lastName: "Last", + phoneNumber: "111-111-1111", + emailAddress: "builddigitaltest@safelite.com", + isSmsOptIn: false, + }, + displayContent: { + fullName: "First Last", + phoneNumber: "111-111-1111", + emailAddress: "builddigitaltest@safelite.com", + smsOptIn: "Test out of", + }, +}; + +let cmsContent; + +describe("Customer Review Block", () => { + beforeEach(() => { + cmsContent = { + CustomerWidget: { + HeaderText: testConstants.cms.header.text, + SubheaderText: testConstants.cms.sms.template, + }, + }; + }); + + test("Should display header text from cms", async () => { + // Arrange + let props = generateDefaultProps(); + + const { wrapper } = setupMocks({ + propsData: props, + }); + + // Act + await wrapper.vm.$nextTick(); + + // Assert + expect(wrapper.vm.header).toEqual(testConstants.cms.header.text); + }); + + describe("SMS Opt In Text", () => { + test("Should render correctly when opt in is true:", async () => { + // Arrange + let props = generateDefaultProps(); + props.customer.isSmsOptIn = true; + + const { wrapper } = setupMocks({ + propsData: props, + }); + + // Act + await wrapper.vm.$nextTick(); + + // Assert + expect(wrapper.vm.smsOptIn).toEqual(testConstants.cms.sms.expected.ifTrue); + }); + test("Should render correctly when opt in is false:", async () => { + // Arrange + let props = generateDefaultProps(); + props.customer.isSmsOptIn = false; + + const { wrapper } = setupMocks({ + propsData: props, + }); + + // Act + await wrapper.vm.$nextTick(); + + // Assert + expect(wrapper.vm.smsOptIn).toEqual(testConstants.cms.sms.expected.ifFalse); + }); + test("Should render correctly when opt in is null:", async () => { + // Arrange + let props = generateDefaultProps(); + props.customer.isSmsOptIn = null; + + const { wrapper } = setupMocks({ + propsData: props, + }); + + // Act + await wrapper.vm.$nextTick(); + + // Assert + expect(wrapper.vm.smsOptIn).toEqual(testConstants.cms.sms.expected.ifFalse); + }); + }); + + test("Should render correct display content", async () => { + // Arrange + let props = generateDefaultProps(); + + const { wrapper } = setupMocks({ + propsData: props, + }); + + // Act + await wrapper.vm.$nextTick(); + + // Assert + expect(wrapper.vm.displayContent).toEqual([ + testConstants.displayContent.fullName, + testConstants.displayContent.emailAddress, + testConstants.displayContent.phoneNumber, + testConstants.displayContent.smsOptIn, + ]); + }); +}); + +function generateDefaultProps() { + return { + cmsWidgetName: "CustomerWidget", + customer: { + firstName: testConstants.customer.firstName, + lastName: testConstants.customer.lastName, + phoneNumber: testConstants.customer.phoneNumber, + emailAddress: testConstants.customer.emailAddress, + isSmsOptIn: testConstants.customer.isSmsOptIn, + }, + }; +} + +function setupMocks(customMountOptions) { + const mountOptions = getMountOptions(customMountOptions); + + const mockMixin = { + methods: { + getCmsContent: jest.fn((widgetName, cmsFieldName) => { + return cmsContent?.[widgetName]?.[cmsFieldName] ?? ""; + }), + }, + }; + + mountOptions.global.mixins = [mockMixin]; + + const wrapper = shallowMount(customerReview, mountOptions); + wrapper.vm.setCmsContent = jest.fn(); + return { wrapper }; +} diff --git a/src/layouts/review/review-sections/customer-review/customer-review.vue b/src/layouts/review/review-sections/customer-review/customer-review.vue new file mode 100644 index 000000000..d8777d9e4 --- /dev/null +++ b/src/layouts/review/review-sections/customer-review/customer-review.vue @@ -0,0 +1,49 @@ + + + diff --git a/src/layouts/review/review-sections/schedule-review/schedule-review.vue b/src/layouts/review/review-sections/schedule-review/schedule-review.vue new file mode 100644 index 000000000..b95ae5b88 --- /dev/null +++ b/src/layouts/review/review-sections/schedule-review/schedule-review.vue @@ -0,0 +1,40 @@ + + + diff --git a/src/layouts/review/review-sections/service-location-review/service-location-review.vue b/src/layouts/review/review-sections/service-location-review/service-location-review.vue index db4f8188a..0da8361c3 100644 --- a/src/layouts/review/review-sections/service-location-review/service-location-review.vue +++ b/src/layouts/review/review-sections/service-location-review/service-location-review.vue @@ -10,7 +10,7 @@ import reviewBlock from "@/layouts/review/review-block/review-block"; import { AppointmentTypeStrings } from "@/constants/schedule-constants"; export default { - name: "service-package-review", + name: "service-location-review", props: { cmsWidgetName: String, serviceLocation: Object, diff --git a/src/layouts/review/review.vue b/src/layouts/review/review.vue index 59320bce9..fe14e8f7e 100644 --- a/src/layouts/review/review.vue +++ b/src/layouts/review/review.vue @@ -10,13 +10,13 @@ + +
+ + + +
+ +

+ + diff --git a/src/layouts/schedule/helpers/schedule-helper.js b/src/layouts/schedule/helpers/schedule-helper.js index 82c756b5b..0f595b435 100644 --- a/src/layouts/schedule/helpers/schedule-helper.js +++ b/src/layouts/schedule/helpers/schedule-helper.js @@ -45,3 +45,33 @@ export function sumDateString(dateString, daysToAdd) { date.setDate(date.getDate() + daysToAdd); return convertDateToDateString(date); } + +export function militaryToTwelveHourTime(timeString) { + // Expected input: "HH:MM" + if (typeof timeString !== "string") return; + let hours = parseInt(timeString.split(":")[0]); + const minutes = timeString.split(":")[1]; + const meridianNotation = hours > 11 ? "PM" : "AM"; + + if (hours > 12) { + hours -= 12; + } + + return `${hours}:${minutes} ${meridianNotation}`; +} + +export function getDisplayTextForDurationLength(durationMinimum, durationMaximum) { + const isLongAppointment = durationMaximum >= 120; + const isDurationRange = durationMinimum !== durationMaximum; + + const adjustedMinimum = isLongAppointment ? durationMinimum / 60 : durationMinimum; + const adjustedMaximum = isLongAppointment ? durationMaximum / 60 : durationMaximum; + + const durationText = isDurationRange + ? `${adjustedMinimum} - ${adjustedMaximum}` + : adjustedMinimum; + + const unitText = isLongAppointment ? "hours" : "minutes"; + + return `${durationText} ${unitText}`; +} diff --git a/src/layouts/schedule/schedule.spec.js b/src/layouts/schedule/schedule.spec.js index 2c4bafc33..b47036c10 100644 --- a/src/layouts/schedule/schedule.spec.js +++ b/src/layouts/schedule/schedule.spec.js @@ -1,15 +1,77 @@ +// Components +import schedule from "@/layouts/schedule/schedule.vue"; + +// Supporting Files 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 router from "@/router"; import { nextTick } from "vue"; +import baseMixin from "../../mixins/base-mixin"; -jest.mock("@/store", () => ({ - commit: jest.fn(), - dispatch: jest.fn(), +// Mock basemixin +jest.mock("@/mixins/base-mixin.js", () => ({ + methods: { + dispatchStoreAction: jest.fn().mockImplementation((storeAction) => { + if (storeAction === "getShopTimeSlots") { + return { + data: { + estimatedServiceMinutesMinimum: 90, + estimatedServiceMinutesMaximum: 120, + days: [ + { + date: "2023-12-01", + timeSlots: [ + { + id: "06747-01820-S-B*20424*7 AM", + startTime: "07:00", + endTime: "08:00", + offerPremium: false, + }, + ], + }, + ], + }, + }; + } + if (storeAction === "getMobilePremiumFee") { + return Promise.resolve({ + data: { + partNumber: "EARLY BIRD", + description: null, + partType: "EARLY BIRD", + laborAmount: 0, + sellingPrice: 14.99, + kitPrice: 0, + }, + }); + } + if (storeAction === "priceOrderItemsAndSaveServerData") { + return Promise.resolve([ + { + partNumber: "EARLY BIRD", + description: null, + partType: "EARLY BIRD", + laborAmount: 0, + sellingPrice: 14.99, + kitPrice: 0, + }, + ]); + } + if (storeAction === "saveSupportingItemsSuppressingStateResetting") { + return Promise.resolve([ + { + partNumber: "EARLY BIRD", + description: null, + partType: "EARLY BIRD", + laborAmount: 0, + sellingPrice: 14.99, + kitPrice: 0, + }, + ]); + } + }), + }, })); // Mock fetchCmsContentForPage @@ -18,50 +80,402 @@ jest.mock("@/helpers/cms-content-helper", () => ({ splitCopyOnCMSPlaceHolder: jest.fn(() => ["A", "B"]), })); -jest.mock( - "@/store", - () => { - return {}; - }, - { virtual: true } -); - -store.getters = { - order: { - schedule: { - date: "2020-01-01", +beforeEach(() => { + jest.restoreAllMocks(); + jest.clearAllMocks(); + store.getters = { + order: { + schedule: { + date: "2019-01-01", + startTime: "09:00", + endTime: "10:00", + routeCode: "000", + }, + lineItems: { + glassParts: [ + { + partNumber: "ABC123", + }, + ], + supportingItems: [], + }, + serviceLocation: { + appointmentType: "Inshop", + zipCode: "12345", + zipCodeCtu: "01234", + provider: { + providerNumber: "123", + }, + }, + damage: { + isRepair: false, + }, + referralNumber: "1234567", + }, + payment: { + isInsurance: true, }, lineItems: { glassParts: [], supportingItems: [], }, - serviceLocation: { - appointmentType: "Inshop", - }, - }, - lineItems: { - glassParts: [], - supportingItems: [], - }, -}; + }; +}); +afterEach(() => { + store.getters = {}; + jest.restoreAllMocks(); + jest.clearAllMocks(); +}); -describe("schedule.vue", () => { - test("Should navigateWithoutSaving", async () => { - //Arrange - const { wrapper } = setupMocks({ - customMountOptions: { - router: { - navigateWithSaving: jest.fn(), - }, - route: { schedule }, - }, +describe("schedule.vue...", () => { + describe("initial load", () => { + test("should pass arePagePrerequisitesValid with a mobile order and no providerNumber", () => { + //Arrange + const { wrapper } = setupMocks({}); + store.getters.order.serviceLocation.appointmentType = "Mobile"; + store.getters.order.serviceLocation.provider = null; + + //Act + const arePagePrerequisitesValid = wrapper.vm.arePagePrerequisitesValid(); + + //Assert + expect(arePagePrerequisitesValid).toBe(true); + }); + test("should pass arePagePrerequisitesValid with a inshop order and providerNumber", () => { + //Arrange + const { wrapper } = setupMocks({}); + + //Act + const arePagePrerequisitesValid = wrapper.vm.arePagePrerequisitesValid(); + + //Assert + expect(arePagePrerequisitesValid).toBe(true); + }); + test("should fail arePagePrerequisitesValid with a replace with no glass parts", async () => { + //Arrange + const { wrapper } = setupMocks({}); + store.getters.order.lineItems.glassParts = []; + + //Act + const arePagePrerequisitesValid2 = await wrapper.vm.arePagePrerequisitesValid(); + + //Assert + expect(arePagePrerequisitesValid2).toBe(false); + }); + test("should fail arePagePrerequisitesValid without isInsurance", () => { + //Arrange + const { wrapper } = setupMocks({}); + store.getters.payment.isInsurance = null; + + //Act + const arePagePrerequisitesValid = wrapper.vm.arePagePrerequisitesValid(); + + //Assert + expect(arePagePrerequisitesValid).toBe(false); }); + test("should return newShopTimeSlots when getAvailableDatesMethod is called", async () => { + //Arrange + const { wrapper } = setupMocks({}); + wrapper.vm.selectableDatesData = { + days: [], + }; + + //Act + const newShopTimeSlots = await wrapper.vm.getAvailableDatesMethod( + "2023-01-01", + "2023-01-31" + ); + + //Assert + expect(newShopTimeSlots).toStrictEqual({ + days: [ + { + date: "2023-12-01", + timeSlots: [ + { + endTime: "08:00", + id: "06747-01820-S-B*20424*7 AM", + offerPremium: false, + startTime: "07:00", + }, + ], + }, + ], + estimatedServiceMinutesMinimum: 90, + estimatedServiceMinutesMaximum: 120, + }); + }); + + test("should call API service in day ranges of 34 or less when getAvailableDatesMethod is called with large date ranges", async () => { + //Arrange + const { wrapper } = setupMocks({}); + wrapper.vm.selectableDatesData = { + days: [], + }; + + //Act + await wrapper.vm.getAvailableDates.call( + wrapper.vm, + "2023-01-01", + "2023-03-31", + "Inshop", + "123" + ); + + //Assert + expect(baseMixin.methods.dispatchStoreAction).toHaveBeenCalledTimes(3); + expect(baseMixin.methods.dispatchStoreAction).toHaveBeenCalledWith( + "getShopTimeSlots", + expect.anything(), + expect.anything() + ); + }); + + describe("beforeRouteEnter function... ", () => { + test("should call next() and call all functions within next", async () => { + //Arrange + const { wrapper } = setupMocks({}); + wrapper.vm.selectableDatesData = { + days: [], + }; + wrapper.vm.updateFooterButtonText = jest.fn(); + const nextFunction = jest.fn((c) => { + c(wrapper.vm); + }); + + //Act + await schedule.beforeRouteEnter.call( + wrapper.vm, + { query: { fmgPage: "schedule" } }, + undefined, + nextFunction + ); + + //Assert + expect(nextFunction).toHaveBeenCalled(); + expect(wrapper.vm.setCmsContent).toHaveBeenCalledWith("content"); + expect(wrapper.vm.$refs.datePicker.initializeComponent).toHaveBeenCalledWith( + expect.objectContaining({ + calendarViewDirection: "future", + }) + ); + expect(wrapper.vm.$refs.locationAlerts.initializeComponent).toHaveBeenCalled(); + expect(wrapper.vm.selectableDatesData).toStrictEqual( + expect.objectContaining({ + days: expect.any(Array), + estimatedServiceMinutesMaximum: expect.any(Number), + estimatedServiceMinutesMinimum: expect.any(Number), + }) + ); + expect(wrapper.vm.mobilePremiumAppointmentFee).toStrictEqual( + expect.objectContaining({ + partNumber: expect.any(String), + }) + ); + expect(wrapper.vm.updateFooterButtonText).toHaveBeenCalled(); + }); + }); + + describe("computed properties...", () => { + test("timeSlotsForSelectedDate should return timeslots if selected date is available", () => { + //Arrange + const { wrapper } = setupMocks({}); + wrapper.vm.selectableDatesData = { + days: [ + { + date: "2022-11-11", + timeSlots: [ + { + id: "1820I-01820-M-I*20425*AM", + startTime: "08:00", + endTime: "12:00", + offerPremium: true, + }, + { + id: "1820I-01820-M-I*20425*PM", + startTime: "12:00", + endTime: "17:00", + offerPremium: false, + }, + ], + }, + ], + }; + wrapper.setData({ + selectedDate: "2022-11-11", + }); + + //Act + const testValue = wrapper.vm.timeSlotsForSelectedDate; + + //Assert + expect(testValue).toStrictEqual( + expect.objectContaining({ + date: "2022-11-11", + }) + ); + }); + + test("timeSlotsForSelectedDate should be null if no date has been selected", () => { + //Arrange + const { wrapper } = setupMocks({}); + wrapper.vm.selectableDatesData = { + days: [ + { + date: "2022-11-11", + timeSlots: [ + { + id: "1820I-01820-M-I*20425*AM", + startTime: "08:00", + endTime: "12:00", + offerPremium: true, + }, + { + id: "1820I-01820-M-I*20425*PM", + startTime: "12:00", + endTime: "17:00", + offerPremium: false, + }, + ], + }, + ], + }; + wrapper.setData({ + selectedDate: undefined, + }); + + //Act + const testValue = wrapper.vm.timeSlotsForSelectedDate; + + //Assert + expect(testValue).toBe(null); + }); + }); + }); + + describe("schedule page methods...", () => { + test("getServiceZipCtuCodeFromStore should return zipCodeCtu", () => { + //Arrange + const { wrapper } = setupMocks({}); + wrapper.vm.selectableDatesData = { + days: [], + }; + + //Act + const testValue = wrapper.vm.getServiceZipCtuCodeFromStore(); + + //Assert + expect(testValue).toStrictEqual("01234"); + }); + + test("openInshopTimeSlotsModal should trigger openModal method", () => { + //Arrange + const { wrapper } = setupMocks({}); + wrapper.vm.selectableDatesData = { + days: [], + }; + + //Act + wrapper.vm.openInshopTimeSlotsModal(); + + //Assert + expect(wrapper.vm.$refs.timeSlotModalQuestion.openModal).toBeCalled(); + }); + + test("getSelectedRouteCode should return schedule routeCode", () => { + //Arrange + const { wrapper } = setupMocks({}); + + wrapper.vm.selectableDatesData = { + days: [], + }; + + //Act + const testValue = wrapper.vm.getSelectedRouteCode(); + + //Assert + expect(testValue).toStrictEqual("000"); + }); + + test("timeSlotModalClosed should null any selected date when there's no route code", () => { + //Arrange + const { wrapper } = setupMocks({}); + wrapper.vm.selectableDatesData = { + days: [], + }; + wrapper.setData({ + selectedDate: "1980-05-05", + }); + wrapper.setData({ + selectedTimeSlot: { + date: "2019-01-01", + startTime: "09:00", + endTime: "10:00", + routeCode: null, + }, + }); + + //Act + wrapper.vm.timeSlotModalClosed(); + + //Assert + expect(wrapper.vm.selectedDate).toBe(null); + }); + + test("getDisplayTextForMilitaryTime should return the correctly formatted string", () => { + //Arrange + const { wrapper } = setupMocks({}); + wrapper.vm.selectableDatesData = { + days: [], + }; + const timeInput1 = "15:00"; + const timeInput2 = "15:30"; + + //Act + const testOutput1 = wrapper.vm.getDisplayTextForMilitaryTime(timeInput1); + const testOutput2 = wrapper.vm.getDisplayTextForMilitaryTime(timeInput2); + const testOutput3 = wrapper.vm.getDisplayTextForMilitaryTime(timeInput1, true); + const testOutput4 = wrapper.vm.getDisplayTextForMilitaryTime(timeInput2, true); + + //Assert + expect(testOutput1).toBe("3:00 PM"); + expect(testOutput2).toBe("3:30 PM"); + expect(testOutput3).toBe("3 PM"); + expect(testOutput4).toBe("3:30 PM"); + }); + + test("getDisplayTextForMilitaryTime should return the correctly formatted string", () => { + //Arrange + const { wrapper } = setupMocks({}); + wrapper.vm.selectableDatesData = { + days: [], + }; + wrapper.vm.$router.navigateWithoutSaving = jest.fn(); + wrapper.vm.$route = "testRoute"; + + //Act + wrapper.vm.backButtonAction(); + + //Assert + expect(wrapper.vm.$router.navigateWithoutSaving).toBeCalledWith( + "CLICKED_BACK", + "testRoute" + ); + }); + }); + + test("forwardButtonAction should call route method navigateWithoutSaving", async () => { + //Arrange + const { wrapper } = setupMocks({}); wrapper.vm.dispatchStoreAction = jest.fn(() => { return { data: [], }; }); + wrapper.vm.$router.navigateWithSaving = jest.fn(() => { + return {}; + }); //Act await wrapper.vm.forwardButtonAction(); @@ -69,136 +483,123 @@ describe("schedule.vue", () => { //Assert expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalled(); }); - test("should pass arePagePrerequisitesValid with a mobile order and no providerNumber", () => { + + test("for mobile appts, updateSupportingItems should call store action to save supporting items", async () => { //Arrange + store.getters.order.serviceLocation.appointmentType = "Mobile"; + store.getters.lineItems.supportingItems = [ + { + partNumber: "EARLY BIRD", + description: null, + partType: "EARLY BIRD", + laborAmount: 0, + sellingPrice: 0, + kitPrice: 0, + }, + ]; const { wrapper } = setupMocks({}); - store.getters = { - order: { - schedule: { - date: "2020-01-01", - }, - serviceLocation: { - zipCode: "12345", - zipCodeCtu: "value", - appointmentType: "Mobile", - }, - damage: { - isRepair: true, - }, - referralNumber: "1234567", + wrapper.vm.dispatchStoreAction = jest.fn(() => { + return { + data: [], + }; + }); + wrapper.vm.mobilePremiumAppointmentFee = 14.99; + wrapper.setData({ + selectedTimeSlot: { + date: "2019-01-01", + startTime: "09:00", + endTime: "10:00", + routeCode: null, + isPremiumAppointment: true, }, - payment: { - isInsurance: true, - }, - lineItems: { - supportingItems: [], - }, - }; + }); - let arePagePrerequisitesValid = wrapper.vm.arePagePrerequisitesValid(); + //Act + await wrapper.vm.updateSupportingItems(); - expect(arePagePrerequisitesValid).toBe(true); + //Assert + expect(wrapper.vm.dispatchStoreAction).toBeCalledWith( + "saveSupportingItemsSuppressingStateResetting", + expect.arrayContaining([ + expect.objectContaining({ + partType: "EARLY BIRD", + }), + ]), + expect.anything() + ); }); - test("should pass arePagePrerequisitesValid with a inshop order and providerNumber", () => { + + test("for Inshop appts, updateSupportingItems should call store action to save supporting items WITHOUT the EARLY BIRD supporting item", async () => { //Arrange + store.getters.order.serviceLocation.appointmentType = "Inshop"; + store.getters.lineItems.supportingItems = [ + { + partNumber: "EARLY BIRD", + description: null, + partType: "EARLY BIRD", + laborAmount: 0, + sellingPrice: 0, + kitPrice: 0, + }, + ]; 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", + wrapper.vm.dispatchStoreAction = jest.fn(() => { + return { + data: [], + }; + }); + wrapper.vm.mobilePremiumAppointmentFee = 14.99; + wrapper.setData({ + selectedTimeSlot: { + date: "2019-01-01", + startTime: "09:00", + endTime: "10:00", + routeCode: null, + isPremiumAppointment: true, }, - payment: { - isInsurance: true, - }, - lineItems: { - supportingItems: [], - }, - }; + }); - let arePagePrerequisitesValid = wrapper.vm.arePagePrerequisitesValid(); + //Act + await wrapper.vm.updateSupportingItems(); - expect(arePagePrerequisitesValid).toBe(true); + //Assert + expect(wrapper.vm.dispatchStoreAction).toBeCalledWith( + "saveSupportingItemsSuppressingStateResetting", + expect.not.arrayContaining([ + expect.objectContaining({ + partType: "EARLY BIRD", + }), + ]), + expect.anything() + ); }); - test("should fail arePagePrerequisitesValid with a replace with no glass parts", () => { + + test("if no EARLY BIRD supporting item, then updateSupportingItems should NOT call store action", async () => { //Arrange + store.getters.order.serviceLocation.appointmentType = "Mobile"; + store.getters.lineItems.supportingItems = []; 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", + wrapper.vm.dispatchStoreAction = jest.fn(() => { + return { + data: [], + }; + }); + wrapper.vm.mobilePremiumAppointmentFee = 14.99; + wrapper.setData({ + selectedTimeSlot: { + date: "2019-01-01", + startTime: "09:00", + endTime: "10:00", + routeCode: null, + isPremiumAppointment: false, }, - payment: { - isInsurance: true, - }, - lineItems: { - supportingItems: [], - glassParts: [], - }, - }; + }); - let arePagePrerequisitesValid = wrapper.vm.arePagePrerequisitesValid(); + //Act + await wrapper.vm.updateSupportingItems(); - 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); + //Assert + expect(wrapper.vm.dispatchStoreAction).not.toBeCalled(); }); }); @@ -210,6 +611,7 @@ function setupMocks({ customMountOptions }) { }); mountOptions.global.mocks["$store"] = store; + mountOptions.global.mocks["$router"] = router; mountOptions["attachTo"] = document.body; mountOptions.mixins = [ { @@ -223,6 +625,12 @@ function setupMocks({ customMountOptions }) { ]; const wrapper = shallowMount(schedule, mountOptions); + wrapper.vm.setCmsContent = jest.fn(); + wrapper.vm.$refs.datePicker.initializeComponent = jest.fn(); + wrapper.vm.$refs.locationAlerts.initializeComponent = jest.fn(); + wrapper.vm.$refs.funnelFooter.updateButtonText = jest.fn(); + wrapper.vm.$refs.timeSlotModalQuestion.openModal = jest.fn(); + return { wrapper }; } diff --git a/src/layouts/schedule/schedule.spec.js1 b/src/layouts/schedule/schedule.spec.js1 new file mode 100644 index 000000000..e69de29bb diff --git a/src/layouts/schedule/schedule.vue b/src/layouts/schedule/schedule.vue index 5bebe26fb..00b3e796b 100644 --- a/src/layouts/schedule/schedule.vue +++ b/src/layouts/schedule/schedule.vue @@ -320,6 +320,7 @@ export default { ); return newShopTimeSlots; }, + getAvailableDates, getServiceZipCtuCodeFromStore() { return store.getters.order.serviceLocation.zipCodeCtu; }, @@ -338,12 +339,6 @@ export default { getSupportingItems() { return store.getters.lineItems.supportingItems; }, - isMobilePremiumFeeOnOrderInVuex() { - const supportingItemsFromVuex = store.getters.lineItems.supportingItems; - return !!supportingItemsFromVuex.filter( - (lineItem) => lineItem.partType === PREMIUM_FEE_PART_TYPE - ).length; - }, timeSlotModalClosed() { // Clear the selectedDate if no timeSlot has been selected if (this.selectedTimeSlot.routeCode == null) { @@ -459,6 +454,7 @@ export default { startTime: null, endTime: null, jobMaxMinutes: null, + jobMinMinutes: null, }; } }, 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 index 43d997892..93f8cfe01 100644 --- 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 @@ -16,6 +16,7 @@ describe("time-slot-modal-list-button-question.vue", () => { const startTime = "8:00 AM"; const endTime = "8:30 AM"; const jobMaxMinutes = 100; + const jobMinMinutes = 50; const expectedEmit = [ [ { @@ -24,6 +25,7 @@ describe("time-slot-modal-list-button-question.vue", () => { startTime: startTime, endTime: endTime, jobMaxMinutes: jobMaxMinutes.toString(), + jobMinMinutes: jobMinMinutes.toString(), }, ], ]; @@ -32,6 +34,7 @@ describe("time-slot-modal-list-button-question.vue", () => { customMountOptions: { propsData: { estimatedServiceMinutesMaximum: jobMaxMinutes, + estimatedServiceMinutesMinimum: jobMinMinutes, dateAndTimeSlotData: { date: date, timeSlots: [ @@ -163,11 +166,13 @@ describe("time-slot-modal-list-button-question.vue Supplemental information", () const startTime = "8:00 AM"; const endTime = "8:30 AM"; const jobMaxMinutes = 100; + const jobMinMinutes = 50; const { wrapper } = setupMocks({ customMountOptions: { propsData: { estimatedServiceMinutesMaximum: jobMaxMinutes, + estimatedServiceMinutesMinimum: jobMinMinutes, dateAndTimeSlotData: { date: date, timeSlots: [ @@ -201,11 +206,13 @@ describe("time-slot-modal-list-button-question.vue Disclaimer", () => { const startTime = "8:00 AM"; const endTime = "8:30 AM"; const jobMaxMinutes = 100; + const jobMinMinutes = 50; const { wrapper } = setupMocks({ customMountOptions: { propsData: { estimatedServiceMinutesMaximum: jobMaxMinutes, + estimatedServiceMinutesMinimum: jobMinMinutes, dateAndTimeSlotData: { date: date, timeSlots: [ @@ -288,11 +295,13 @@ describe("time-slot-modal-list-button-question.vue Duration", () => { const startTime = "8:00 AM"; const endTime = "8:30 AM"; const jobMaxMinutes = 100; + const jobMinMinutes = 50; const { wrapper } = setupMocks({ customMountOptions: { propsData: { estimatedServiceMinutesMaximum: jobMaxMinutes, + estimatedServiceMinutesMinimum: jobMinMinutes, dateAndTimeSlotData: { date: date, timeSlots: [ @@ -434,11 +443,13 @@ describe("time-slot-modal-list-button-question.vue Available Timeslots", () => { const startTime = "8:00 AM"; const endTime = "8:30 AM"; const jobMaxMinutes = 100; + const jobMinMinutes = 50; const { wrapper } = setupMocks({ customMountOptions: { propsData: { estimatedServiceMinutesMaximum: jobMaxMinutes, + estimatedServiceMinutesMinimum: jobMinMinutes, dateAndTimeSlotData: { date: date, timeSlots: [ @@ -470,11 +481,13 @@ describe("time-slot-modal-list-button-question.vue Available Timeslots", () => { const startTime = "8:00 AM"; const endTime = "8:30 AM"; const jobMaxMinutes = 100; + const jobMinMinutes = 50; const { wrapper } = setupMocks({ customMountOptions: { propsData: { estimatedServiceMinutesMaximum: jobMaxMinutes, + estimatedServiceMinutesMinimum: jobMinMinutes, dateAndTimeSlotData: { date: date, timeSlots: [ @@ -506,11 +519,13 @@ describe("time-slot-modal-list-button-question.vue Available Timeslots", () => { const startTime = "8:00 AM"; const endTime = "8:30 AM"; const jobMaxMinutes = 100; + const jobMinMinutes = 50; const { wrapper } = setupMocks({ customMountOptions: { propsData: { estimatedServiceMinutesMaximum: jobMaxMinutes, + estimatedServiceMinutesMinimum: jobMinMinutes, dateAndTimeSlotData: { date: date, timeSlots: [ @@ -542,11 +557,13 @@ describe("time-slot-modal-list-button-question.vue Available Timeslots", () => { const startTime = "8:00"; const endTime = "8:30"; const jobMaxMinutes = 100; + const jobMinMinutes = 50; const { wrapper } = setupMocks({ customMountOptions: { propsData: { estimatedServiceMinutesMaximum: jobMaxMinutes, + estimatedServiceMinutesMinimum: jobMinMinutes, dateAndTimeSlotData: { date: date, timeSlots: [ @@ -578,11 +595,13 @@ describe("time-slot-modal-list-button-question.vue Available Timeslots", () => { const startTime = "8:00"; const endTime = "8:30"; const jobMaxMinutes = 100; + const jobMinMinutes = 50; const { wrapper } = setupMocks({ customMountOptions: { propsData: { estimatedServiceMinutesMaximum: jobMaxMinutes, + estimatedServiceMinutesMinimum: jobMinMinutes, premiumAppointmentFee: { partType: PREMIUM_FEE_PART_TYPE, }, @@ -622,11 +641,13 @@ describe("time-slot-modal-list-button-question.vue Available Timeslots", () => { const startTime = "8:00"; const endTime = "8:30"; const jobMaxMinutes = 100; + const jobMinMinutes = 50; const { wrapper } = setupMocks({ customMountOptions: { propsData: { estimatedServiceMinutesMaximum: jobMaxMinutes, + estimatedServiceMinutesMinimum: jobMinMinutes, dateAndTimeSlotData: { date: date, timeSlots: [ 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 724facf6c..cce2c0503 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 @@ -47,7 +47,11 @@ import buttonQuestion from "@/digital-components/button-question/button-question import timeSlotModalListButton from "./time-slot-modal-list-button/time-slot-modal-list-button"; // Helpers -import { convertDateStringToDate } from "@/layouts/schedule/helpers/schedule-helper"; +import { + convertDateStringToDate, + militaryToTwelveHourTime, + getDisplayTextForDurationLength, +} from "@/layouts/schedule/helpers/schedule-helper"; import { deepClone } from "@/helpers/object-helper"; // Validation - TODO: Move this somewhere more global? @@ -88,6 +92,7 @@ export default { startTime: null, endTime: null, jobMaxMinutes: null, + jobMinMinutes: null, }), }, cmsWidgetName: String, @@ -263,7 +268,7 @@ export default { cmsWidgetFieldMappings.DURATION ); - const inshopDurationTime = this.getDisplayTextForDurationLength( + const inshopDurationTime = getDisplayTextForDurationLength( this.estimatedServiceMinutesMinimum, this.estimatedServiceMinutesMaximum ); @@ -346,33 +351,6 @@ export default { this.$emit("update:modelValue", this.selectedValue); this.closeModal(); }, - getDisplayTextForMilitaryTime(militaryTimeInput) { - // Expected input: "HH:MM" - let hours = parseInt(militaryTimeInput.split(":")[0]); - const minutes = militaryTimeInput.split(":")[1]; - const meridianNotation = hours > 11 ? "PM" : "AM"; - - if (hours > 12) { - hours -= 12; - } - - return `${hours}:${minutes} ${meridianNotation}`; - }, - getDisplayTextForDurationLength(durationMinimum, durationMaximum) { - const isLongAppointment = durationMaximum >= 120; - const isDurationRange = durationMinimum !== durationMaximum; - - const adjustedMinimum = isLongAppointment ? durationMinimum / 60 : durationMinimum; - const adjustedMaximum = isLongAppointment ? durationMaximum / 60 : durationMaximum; - - const durationText = isDurationRange - ? `${adjustedMinimum} - ${adjustedMaximum}` - : adjustedMinimum; - - const unitText = isLongAppointment ? "hours" : "minutes"; - - return `${durationText} ${unitText}`; - }, getRelevantDropOffCmsWidgetNameForSelectedTimeSlot( selectedTimeSlotId, isSameDayRelevant = false @@ -387,7 +365,7 @@ export default { }, getAvailableTimeSlotsForInshop(timeSlotsForSelectedDate) { return timeSlotsForSelectedDate.map((timeSlot) => { - const readableTime = this.getDisplayTextForMilitaryTime(timeSlot.startTime); + const readableTime = militaryToTwelveHourTime(timeSlot.startTime); return { value: timeSlot.id, buttonLabel: readableTime, @@ -415,9 +393,9 @@ export default { }, getAvailableTimeSlotsForMobile(timeSlotsForSelectedDate) { const availableTimeSlots = timeSlotsForSelectedDate.map((timeSlot) => { - const readableTime = `${this.getDisplayTextForMilitaryTime( + const readableTime = `${militaryToTwelveHourTime( timeSlot.startTime - )} - ${this.getDisplayTextForMilitaryTime(timeSlot.endTime)}`; + )} - ${militaryToTwelveHourTime(timeSlot.endTime)}`; return { value: timeSlot.id, buttonLabel: readableTime, @@ -472,6 +450,7 @@ export default { startTime: timeSlot.startTime, endTime: timeSlot.endTime, jobMaxMinutes: this.estimatedServiceMinutesMaximum.toString(), + jobMinMinutes: this.estimatedServiceMinutesMinimum.toString(), }; } @@ -481,6 +460,7 @@ export default { endTime: null, routeCode: null, jobMaxMinutes: null, + jobMinMinutes: null, }; }, }, diff --git a/src/layouts/vehicle-damage/vehicle-damage.spec.js b/src/layouts/vehicle-damage/vehicle-damage.spec.js index 30e144310..b26b5e31c 100644 --- a/src/layouts/vehicle-damage/vehicle-damage.spec.js +++ b/src/layouts/vehicle-damage/vehicle-damage.spec.js @@ -52,7 +52,6 @@ jest.mock("@/store", () => ({ damage: { glassToReplace: [], }, - order: { serviceLocation: { zipCode: "11111" } }, }, })); @@ -134,7 +133,6 @@ describe("vehicle-damage.vue", () => { getters: { vehicle: {}, payment: { insuranceCoverage: { isVerified: false } }, - order: { serviceLocation: { zipCode: "11111" } }, }, }, }, @@ -177,7 +175,7 @@ describe("vehicle-damage.vue", () => { expect(wrapper.vm.selectedGlassToReplace()).toEqual(expectedGlassToReplace); expect(baseMixin.methods.dispatchStoreAction).toBeCalledWith( storeActions.GET_DAMAGE_OPTIONS, - { carId: "C00000000", zipCode: "11111" } + { carId: "C00000000" } ); }); @@ -243,7 +241,6 @@ describe("vehicle-damage.vue", () => { getters: { vehicle: {}, payment: { insuranceCoverage: { isVerified: false } }, - order: { serviceLocation: { zipCode: "11111" } }, }, }, }, @@ -273,7 +270,7 @@ describe("vehicle-damage.vue", () => { expect(wrapper.vm.selectedGlassToReplace()).toEqual(expectedGlassToReplace); expect(baseMixin.methods.dispatchStoreAction).toBeCalledWith( storeActions.GET_DAMAGE_OPTIONS, - { carId: "C00000000", zipCode: "11111" } + { carId: "C00000000" } ); }); }); @@ -526,7 +523,6 @@ describe("vehicle-damage.vue", () => { eventBusItem: jest.fn(), damage: { glassToReplace: [{ glassLocation: damageLocation }] }, isRepair: true, - order: { serviceLocation: { zipCode: "11111" } }, }; var glassSelections = wrapper.vm.getDamageLocationsFromStore(); @@ -604,7 +600,6 @@ describe("vehicle-damage.vue", () => { isRepair: isRepair, numberOfChips: 2, }, - order: { serviceLocation: { zipCode: "11111" } }, }; var windshieldSelections = wrapper.vm.getWindshieldOptionsFromStore(); @@ -641,7 +636,6 @@ describe("vehicle-damage.vue", () => { glassToReplace: [{ glassLocation: damageLocation, glassName: damageName }], }, isRepair: true, - order: { serviceLocation: { zipCode: "11111" } }, }; var glassSelections = wrapper.vm.getDriverSideReplaceOptionsFromStore(); @@ -678,7 +672,6 @@ describe("vehicle-damage.vue", () => { glassToReplace: [{ glassLocation: damageLocation, glassName: damageName }], }, isRepair: true, - order: { serviceLocation: { zipCode: "11111" } }, }; var glassSelections = wrapper.vm.getPassengerSideReplaceOptionsFromStore(); @@ -713,7 +706,6 @@ describe("vehicle-damage.vue", () => { glassToReplace: [{ glassLocation: damageLocation, glassName: damageName }], }, isRepair: true, - order: { serviceLocation: { zipCode: "11111" } }, }; var glassSelections = wrapper.vm.getRearReplaceOptionsFromStore(); @@ -746,7 +738,6 @@ describe("vehicle-damage.vue", () => { getters: { vehicle: {}, payment: { insuranceCoverage: { isVerified: true } }, - order: { serviceLocation: { zipCode: "11111" } }, }, }, }, @@ -787,7 +778,6 @@ 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 6ea4fe81d..c03fabda1 100644 --- a/src/layouts/vehicle-damage/vehicle-damage.vue +++ b/src/layouts/vehicle-damage/vehicle-damage.vue @@ -81,7 +81,6 @@ 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,20 +89,10 @@ 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, zipCode: zip } + { carId: store.getters.vehicle.carId } ); // Settle promises and get results @@ -146,7 +135,6 @@ export default { }, selectedWindshieldOptions: this.getWindshieldOptionsFromStore(), selectedRearReplaceOptions: this.getRearReplaceOptionsFromStore(), - serviceZipCode: this.getZipFromStore() ?? this.$route.query.zipcode, }; }, mounted() { @@ -169,18 +157,6 @@ 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-make/make-question/make-question.spec.js b/src/layouts/vehicle-make/make-question/make-question.spec.js deleted file mode 100644 index 7025c118c..000000000 --- a/src/layouts/vehicle-make/make-question/make-question.spec.js +++ /dev/null @@ -1,79 +0,0 @@ -import makeQuestion from "@/layouts/vehicle-make/make-question/make-question"; -import { shallowMount } from "@vue/test-utils"; -import { getMountOptions } from "@/helpers/unit-test-helper.js"; -import store from "@/store"; -jest.mock( - "@/store", - () => { - return {}; - }, - { virtual: true } -); - -describe("make-question.vue", () => { - test("Selected make is emitted upon selection.", async () => { - //Arrange - const { wrapper } = setupMocks({ modelValueProp: "honda" }); - const makeToSelect = "ford"; - - //Act - wrapper.setValue({ selectedMake: makeToSelect }); - await wrapper.vm.$nextTick(); - - //Assert - expect(wrapper.emitted()["update:modelValue"][0]).toEqual([{ selectedMake: "ford" }]); - }); -}); - -describe("make-question.vue", () => { - test("Data from store api are used as radio question answers.", async () => { - //Arrange - const { wrapper, cmsContent } = setupMocks({ - dataFromStoreApi: ["honda", "ford", "dodge"], - }); - - //Act - const initialData = makeQuestion.methods.loadInitialData.call(wrapper.vm); - makeQuestion.methods.initializeComponent.call(wrapper.vm, initialData); - - //Assert - const buttonQuestionComponent = await wrapper.findComponent({ - name: "buttonQuestion", - }); - expect(buttonQuestionComponent.attributes("answers")).toBe("honda,ford,dodge"); - }); -}); - -function setupMocks({ - modelValueProp = "1900", - cmsQuestionText = "CMS text goes here", - dataFromStoreApi = [], -}) { - //Mock store - store.dispatch = jest.fn(() => dataFromStoreApi); - store.getters = { vehicle: { year: 2019 } }; - const mountOptions = getMountOptions({ - store: { - dispatch: store.dispatch, - getters: store.getters, - }, - }); - - //Mock props - const mockMixin = { - methods: { - getCmsContent: jest.fn(), - }, - }; - mountOptions.propsData = { - modelValue: modelValueProp, - }; - mountOptions.mixins = [mockMixin]; - const wrapper = shallowMount(makeQuestion, mountOptions); - - //Mock CMS content - const cmsContent = { - QuestionText: cmsQuestionText, - }; - return { wrapper, cmsContent }; -} diff --git a/src/layouts/vehicle-make/make-question/make-question.vue b/src/layouts/vehicle-make/make-question/make-question.vue deleted file mode 100644 index 2d3551c53..000000000 --- a/src/layouts/vehicle-make/make-question/make-question.vue +++ /dev/null @@ -1,59 +0,0 @@ - - - diff --git a/src/layouts/vehicle-make/vehicle-make.spec.js b/src/layouts/vehicle-make/vehicle-make.spec.js deleted file mode 100644 index 3b169a9b9..000000000 --- a/src/layouts/vehicle-make/vehicle-make.spec.js +++ /dev/null @@ -1,228 +0,0 @@ -// Supporting Files -import { shallowMount } from "@vue/test-utils"; -import { getMountOptions } from "@/helpers/unit-test-helper.js"; -import { settleAllPromises } from "@/helpers/layout-helper.js"; -import { nextTick } from "vue"; -import { fetchCmsContentForPage } from "@/helpers/cms-content-helper"; -import baseMixin from "@/mixins/base-mixin.js"; -import store from "@/store"; - -// Components -import vehicleMake from "@/layouts/vehicle-make/vehicle-make.vue"; -import makeQuestion from "@/layouts/vehicle-make/make-question/make-question"; - -jest.mock("@/store", () => ({ - commit: jest.fn(), - dispatch: jest.fn(), - // getters: jest.fn().mockImplementation(() => ({ - // vehicle: { - // year: 2019, - // }, - // })), -})); - -// Mock fetchCmsContentForPage -jest.mock("@/helpers/cms-content-helper", () => ({ - fetchCmsContentForPage: jest.fn(), -})); - -// Mock our module for promises. -jest.mock("@/helpers/layout-helper.js", () => ({ - settleAllPromises: jest.fn(), -})); - -describe("vehicle-make.vue", () => { - test("Make question component is initized with api data", async (done) => { - //Arrange - const makeQuestionInitialData = ["honda", "ford", "dodge"]; - const { wrapper, apiPromise } = setupMocks({ - makeQuestionInitialData: makeQuestionInitialData, - }); - - //Act - vehicleMake.beforeRouteEnter.call( - wrapper.vm, - { query: { fmgPage: "vehicle-make" } }, - undefined, - (c) => c(wrapper.vm) - ); - - //Assert - apiPromise.finally(() => { - expect(makeQuestion.methods.initializeComponent).toHaveBeenCalledWith( - makeQuestionInitialData - ); - done(); - }); - }); -}); - -describe("vehicle-make.vue", () => { - test("BackButtonAction triggers a router.navigateWithoutSaving change", async (done) => { - //Arrange - const { wrapper, apiPromise } = setupMocks({ - pageHeaderWidgetHeaderText: "Select a make to get started", - mountOptionsMockData: { - router: { - navigate: jest.fn(), - navigateWithoutSaving: jest.fn(), - }, - }, - }); - - //Act - vehicleMake.beforeRouteEnter.call( - wrapper.vm, - { query: { fmgPage: "vehicle-make" } }, - undefined, - (c) => c(wrapper.vm) - ); - wrapper.vm.backButtonAction(); - await nextTick(); - - //Assert - apiPromise.finally(() => { - expect(wrapper.vm.$router.navigateWithoutSaving).toHaveBeenCalled(); - done(); - }); - }); -}); - -describe("vehicle-make.vue", () => { - describe("arePagePrerequisitesValue", () => { - test("Year set, arePagePrerequisitesValid should be true", async () => { - //Arrange - const { wrapper } = setupMocks({ - vehicleData: { - year: 2019, - }, - }); - - //Act - vehicleMake.beforeRouteEnter.call( - wrapper.vm, - { query: { fmgPage: "vehicle-make" } }, - undefined, - (c) => c(wrapper.vm) - ); - - let arePagePrerequisitesValid = wrapper.vm.arePagePrerequisitesValid(); - - //Assert - expect(arePagePrerequisitesValid).toBe(true); - }); - - test("Year not set, arePagePrerequisitesValid should be false", async () => { - //Arrange - store.getters.vehicle.year = jest.fn().mockReturnValueOnce(undefined); - const { wrapper } = setupMocks({}); - - //Act - let arePagePrerequisitesValid = wrapper.vm.arePagePrerequisitesValid(); - - //Assert - expect(arePagePrerequisitesValid).toBe(false); - }); - }); - - test("selectedMake changes => save make in store", async () => { - //Arrange - const { wrapper } = setupMocks({ - mountOptionsMockData: { - router: { - navigate: jest.fn(), - navigateWithSaving: jest.fn(), - }, - }, - }); - - // Act - await wrapper.setData({ - selectedMake: "Make", - }); - await wrapper.vm.$nextTick(); - - // Assert - expect(wrapper.vm.dispatchStoreAction).toHaveBeenCalledTimes(1); - expect(wrapper.vm.dispatchStoreAction).toHaveBeenCalledWith( - "saveVehicleMake", - "Make", - false - ); - }); - - test("selectedMake changes => navigate with saving", async () => { - //Arrange - const { wrapper } = setupMocks({ - mountOptionsMockData: { - router: { - navigate: jest.fn(), - navigateWithSaving: jest.fn(), - }, - route: { - fmgPage: "test", - }, - }, - }); - - // Act - await wrapper.setData({ - selectedMake: "Make", - }); - - // Assert - expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledTimes(1); - expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledWith( - "SELECTED_MAKE", - expect.anything() - ); - }); -}); - -function setupMocks({ - vehicleMakeQuestionCmsContent = {}, - makeQuestionInitialData = {}, - pageHeaderWidgetHeaderText = {}, - mountOptionsMockData = {}, - vehicleData = {}, -}) { - //Mock api responses - const apiResponses = { - cmsContent: { - FunnelSubHeaderWidget: pageHeaderWidgetHeaderText, - VehicleMakeQuestion: vehicleMakeQuestionCmsContent, - VehicleBannerWidget: { - GenericVehicleImage: - "https://digitalconsumercms-dev.safelite.com/images/default-source/default-album/blurred-image.jpg?sfvrsn=a6ce3034_3", - }, - FunnelHeaderWidget: { - LogoImage: - "https://digitalconsumercms-dev.safelite.com/images/default-source/default-album/safelite-logo.svg?sfvrsn=45e7ed06_3", - }, - }, - makeQuestionInitialData: makeQuestionInitialData, - }; - - const apiPromise = Promise.resolve(apiResponses); - - store.getters = { - vehicle: vehicleData, - }; - - fetchCmsContentForPage.mockImplementation(() => Promise.resolve()); - settleAllPromises.mockImplementation(() => apiPromise); - - //Mock make question methods - makeQuestion.methods = { - loadInitialData: jest.fn(), - initializeComponent: jest.fn(), - }; - - const mountOptions = getMountOptions(mountOptionsMockData); - const wrapper = shallowMount(vehicleMake, mountOptions); - const makeQuestionWrapper = wrapper.findComponent({ name: "makeQuestion" }); - makeQuestionWrapper.vm.initializeComponent = makeQuestion.methods.initializeComponent; - wrapper.vm.setCmsContent = baseMixin.methods.setCmsContent; - - return { wrapper, apiPromise }; -} diff --git a/src/layouts/vehicle-make/vehicle-make.vue b/src/layouts/vehicle-make/vehicle-make.vue deleted file mode 100644 index a50440e35..000000000 --- a/src/layouts/vehicle-make/vehicle-make.vue +++ /dev/null @@ -1,95 +0,0 @@ - - - diff --git a/src/layouts/vehicle-model/model-question/model-question.spec.js b/src/layouts/vehicle-model/model-question/model-question.spec.js deleted file mode 100644 index 16ca638d9..000000000 --- a/src/layouts/vehicle-model/model-question/model-question.spec.js +++ /dev/null @@ -1,79 +0,0 @@ -import modelQuestion from "@/layouts/vehicle-model/model-question/model-question"; -import { shallowMount } from "@vue/test-utils"; -import { getMountOptions } from "@/helpers/unit-test-helper.js"; -import store from "@/store"; -jest.mock( - "@/store", - () => { - return {}; - }, - { virtual: true } -); - -describe("model-question.vue", () => { - test("Selected model is emitted upon selection.", async () => { - //Arrange - const { wrapper } = setupMocks({ modelValueProp: "Accord" }); - const modelToSelect = "Civic"; - - //Act - wrapper.setValue({ selectedModel: modelToSelect }); - await wrapper.vm.$nextTick(); - - //Assert - expect(wrapper.emitted()["update:modelValue"][0]).toEqual([{ selectedModel: "Civic" }]); - }); -}); - -describe("model-question.vue", () => { - test("Data from store api are used as radio question answers.", async () => { - //Arrange - const { wrapper, cmsContent } = setupMocks({ - dataFromStoreApi: ["accord", "civic", "insight"], - }); - - //Act - const initialData = modelQuestion.methods.loadInitialData.call(wrapper.vm); - modelQuestion.methods.initializeComponent.call(wrapper.vm, initialData); - - //Assert - const buttonQuestionComponent = await wrapper.findComponent({ - name: "buttonQuestion", - }); - expect(buttonQuestionComponent.attributes("answers")).toBe("accord,civic,insight"); - }); -}); - -function setupMocks({ - modelValueProp = "1900", - cmsQuestionText = "CMS text goes here", - dataFromStoreApi = [], -}) { - //Mock store - store.dispatch = jest.fn(() => dataFromStoreApi); - store.getters = { vehicle: { year: 2019, make: "honda" } }; - const mountOptions = getMountOptions({ - store: { - dispatch: store.dispatch, - getters: store.getters, - }, - }); - - //Mock props - const mockMixin = { - methods: { - getCmsContent: jest.fn(), - }, - }; - mountOptions.propsData = { - modelValue: modelValueProp, - }; - mountOptions.mixins = [mockMixin]; - const wrapper = shallowMount(modelQuestion, mountOptions); - - //Mock CMS content - const cmsContent = { - QuestionText: cmsQuestionText, - }; - return { wrapper, cmsContent }; -} diff --git a/src/layouts/vehicle-model/model-question/model-question.vue b/src/layouts/vehicle-model/model-question/model-question.vue deleted file mode 100644 index 5ca926abc..000000000 --- a/src/layouts/vehicle-model/model-question/model-question.vue +++ /dev/null @@ -1,60 +0,0 @@ - - - diff --git a/src/layouts/vehicle-model/vehicle-model.spec.js b/src/layouts/vehicle-model/vehicle-model.spec.js deleted file mode 100644 index 33b74482d..000000000 --- a/src/layouts/vehicle-model/vehicle-model.spec.js +++ /dev/null @@ -1,147 +0,0 @@ -// Components -import vehicleModel from "@/layouts/vehicle-model/vehicle-model.vue"; -import modelQuestion from "@/layouts/vehicle-model/model-question/model-question"; - -// Supporting files -import { settleAllPromises } from "@/helpers/layout-helper.js"; -import { shallowMount } from "@vue/test-utils"; -import { getMountOptions } from "@/helpers/unit-test-helper.js"; -import { nextTick } from "vue"; -import { fetchCmsContentForPage } from "@/helpers/cms-content-helper"; -import baseMixin from "@/mixins/base-mixin.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(), -})); - -// Mock Store -jest.mock("@/store", () => ({ - commit: jest.fn(), - dispatch: jest.fn(), - getters: { - vehicle: { - make: "Acura", - }, - }, -})); - -describe("vehicle-model.vue", () => { - test("Model question component is initized with api data", async (done) => { - //Arange - const modelQuestionInitialData = ["accord", "civic", "insight"]; - const { wrapper, apiPromise } = setupMocks({ - modelQuestionInitialData: modelQuestionInitialData, - }); - //Act - vehicleModel.beforeRouteEnter.call( - wrapper.vm, - { query: { fmgPage: "vehicle-model" } }, - undefined, - (c) => c(wrapper.vm) - ); - //Assert - apiPromise.finally(() => { - expect(modelQuestion.methods.initializeComponent).toHaveBeenCalledWith( - modelQuestionInitialData - ); - done(); - }); - }); -}); - -describe("vehicle-model.vue", () => { - test("BackButtonAction triggers a router.navigateWithoutSaving change", async (done) => { - //Arrange - const { wrapper, apiPromise } = setupMocks({ - pageHeaderWidgetHeaderText: "Select a model to get started", - mountOptionsMockData: { - router: { - navigate: jest.fn(), - navigateWithoutSaving: jest.fn(), - }, - }, - }); - //Act - vehicleModel.beforeRouteEnter.call( - wrapper.vm, - { query: { fmgPage: "vehicle-model" } }, - undefined, - (c) => c(wrapper.vm) - ); - wrapper.vm.backButtonAction(); - await nextTick(); - //Assert - apiPromise.finally(() => { - expect(wrapper.vm.$router.navigateWithoutSaving).toHaveBeenCalled(); - done(); - }); - }); -}); -describe("vehicle-model.vue", () => { - test("Make set, arePagePrerequisitesValid should be true ", async () => { - //Arrange - const { wrapper } = setupMocks({}); - - //Act - vehicleModel.beforeRouteEnter.call( - wrapper.vm, - { query: { fmgPage: "vehicle-model" } }, - undefined, - (c) => c(wrapper.vm) - ); - - let arePagePrerequisitesValid = wrapper.vm.arePagePrerequisitesValid(); - await nextTick(); - - //Assert - expect(arePagePrerequisitesValid).toBe(true); - }); -}); - -function setupMocks({ - buttonQuestionContent = {}, - modelQuestionInitialData = {}, - pageHeaderWidgetHeaderText = {}, - mountOptionsMockData = {}, -}) { - //Mock api responses - const apiResponses = { - cmsContent: { - FunnelSubHeaderWidget: pageHeaderWidgetHeaderText, - VehicleModelQuestion: buttonQuestionContent, - VehicleBannerWidget: { - GenericVehicleImage: - "https://digitalconsumercms-dev.safelite.com/images/default-source/default-album/blurred-image.jpg?sfvrsn=a6ce3034_3", - }, - FunnelHeaderWidget: { - LogoImage: - "https://digitalconsumercms-dev.safelite.com/images/default-source/default-album/safelite-logo.svg?sfvrsn=45e7ed06_3", - }, - }, - modelQuestionInitialData: modelQuestionInitialData, - }; - const apiPromise = Promise.resolve(apiResponses); - - settleAllPromises.mockImplementation(() => apiPromise); - fetchCmsContentForPage.mockImplementation(() => Promise.resolve()); - - //Mock model question methods - modelQuestion.methods = { - loadInitialData: jest.fn(), - initializeComponent: jest.fn(), - }; - const mountOptions = getMountOptions(mountOptionsMockData); - const wrapper = shallowMount(vehicleModel, mountOptions); - wrapper.vm.setCmsContent = baseMixin.methods.setCmsContent; - - const modelQuestionWrapper = wrapper.findComponent({ name: "modelQuestion" }); - modelQuestionWrapper.vm.initializeComponent = modelQuestion.methods.initializeComponent; - - return { wrapper, apiPromise }; -} diff --git a/src/layouts/vehicle-model/vehicle-model.vue b/src/layouts/vehicle-model/vehicle-model.vue deleted file mode 100644 index d15c170c6..000000000 --- a/src/layouts/vehicle-model/vehicle-model.vue +++ /dev/null @@ -1,98 +0,0 @@ - - - diff --git a/src/layouts/vehicle-style/style-question/style-question.spec.js b/src/layouts/vehicle-style/style-question/style-question.spec.js deleted file mode 100644 index c02301316..000000000 --- a/src/layouts/vehicle-style/style-question/style-question.spec.js +++ /dev/null @@ -1,79 +0,0 @@ -import styleQuestion from "@/layouts/vehicle-style/style-question/style-question"; -import { shallowMount } from "@vue/test-utils"; -import { getMountOptions } from "@/helpers/unit-test-helper.js"; -import store from "@/store"; -jest.mock( - "@/store", - () => { - return {}; - }, - { virtual: true } -); - -describe("style-question.vue", () => { - test("Selected style is emitted upon selection.", async () => { - //Arrange - const { wrapper } = setupMocks({ modelValueProp: "2 Door" }); - const styleToSelect = "4 Door"; - - //Act - wrapper.setValue({ selectedStyle: styleToSelect }); - await wrapper.vm.$nextTick(); - - //Assert - expect(wrapper.emitted()["update:modelValue"][0]).toEqual([{ selectedStyle: "4 Door" }]); - }); -}); - -describe("style-question.vue", () => { - test("Data from store api are used as radio question answers.", async () => { - //Arrange - const { wrapper, cmsContent } = setupMocks({ - dataFromStoreApi: ["2 Door", "4 Door"], - }); - - //Act - const initialData = styleQuestion.methods.loadInitialData.call(wrapper.vm); - styleQuestion.methods.initializeComponent.call(wrapper.vm, initialData); - - //Assert - const buttonQuestionComponent = await wrapper.findComponent({ - name: "buttonQuestion", - }); - expect(buttonQuestionComponent.attributes("answers")).toBe("2 Door,4 Door"); - }); -}); - -function setupMocks({ - modelValueProp = "1900", - cmsQuestionText = "CMS text goes here", - dataFromStoreApi = [], -}) { - //Mock store - store.dispatch = jest.fn(() => dataFromStoreApi); - store.getters = { vehicle: { year: 2019, make: "honda", model: "civc" } }; - const mountOptions = getMountOptions({ - store: { - dispatch: store.dispatch, - getters: store.getters, - }, - }); - - //Mock props - const mockMixin = { - methods: { - getCmsContent: jest.fn(), - }, - }; - mountOptions.propsData = { - modelValue: modelValueProp, - }; - mountOptions.mixins = [mockMixin]; - const wrapper = shallowMount(styleQuestion, mountOptions); - - //Mock CMS content - const cmsContent = { - QuestionText: cmsQuestionText, - }; - return { wrapper, cmsContent }; -} diff --git a/src/layouts/vehicle-style/style-question/style-question.vue b/src/layouts/vehicle-style/style-question/style-question.vue deleted file mode 100644 index 0bfd00f7c..000000000 --- a/src/layouts/vehicle-style/style-question/style-question.vue +++ /dev/null @@ -1,61 +0,0 @@ - - - diff --git a/src/layouts/vehicle-style/vehicle-style.spec.js b/src/layouts/vehicle-style/vehicle-style.spec.js deleted file mode 100644 index f7dacb6b4..000000000 --- a/src/layouts/vehicle-style/vehicle-style.spec.js +++ /dev/null @@ -1,256 +0,0 @@ -// Components -import vehicleStyle from "@/layouts/vehicle-style/vehicle-style.vue"; -import styleQuestion from "@/layouts/vehicle-style/style-question/style-question"; - -// Supporting files -import { settleAllPromises } from "@/helpers/layout-helper.js"; -import { nextTick } from "vue"; -import { shallowMount } from "@vue/test-utils"; -import { getMountOptions } from "@/helpers/unit-test-helper.js"; -import { storeActions } from "@/constants/store-actions"; -import { fetchCmsContentForPage } from "@/helpers/cms-content-helper"; -import baseMixin from "@/mixins/base-mixin.js"; -import router from "@/router"; -import store from "@/store"; -import { storeMutations } from "@/constants/store-mutations"; - -// 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(), -})); - -// Mock fetchCmsContentForPage -jest.mock("@/router", () => ({ - overrideNavigation: jest.fn(), -})); - -describe("vehicle-style.vue", () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - test("Style question component is initized with api data", async (done) => { - //Arrange - const styleQuestionInitialData = ["2 Door", "4 Door"]; - const { wrapper, apiPromise } = setupMocks({ - styleQuestionInitialData: styleQuestionInitialData, - }); - - //Act - vehicleStyle.beforeRouteEnter.call( - wrapper.vm, - { query: { fmgPage: "vehicle-style" } }, - undefined, - (c) => c(wrapper.vm) - ); - - //Assert - apiPromise.finally(() => { - expect(styleQuestion.methods.initializeComponent).toHaveBeenCalledWith( - styleQuestionInitialData - ); - done(); - }); - }); - - test("BackButtonAction triggers a router.navigateWithoutSaving change", async (done) => { - //Arrange - const { wrapper, apiPromise } = setupMocks({ - pageHeaderWidgetHeaderText: "Select a style to get started", - mountOptionsMockData: { - router: { - navigate: jest.fn(), - navigateWithSaving: jest.fn(), - navigateWithoutSaving: jest.fn(), - }, - }, - }); - - //Act - vehicleStyle.beforeRouteEnter.call( - wrapper.vm, - { query: { fmgPage: "vehicle-style" } }, - undefined, - (c) => c(wrapper.vm) - ); - wrapper.vm.backButtonAction(); - await nextTick(); - - //Assert - apiPromise.finally(() => { - expect(wrapper.vm.$router.navigateWithoutSaving).toHaveBeenCalled(); - done(); - }); - }); - - test("setVehicle triggers a dispatchStoreAction commit", async (done) => { - //Arrange - const { wrapper, apiPromise } = setupMocks({ - pageHeaderWidgetHeaderText: "Select a style to get started", - mountOptionsMockData: { - actionList: [ - { - actionName: storeActions.SET_VEHICLE, - data: "mockData", - }, - ], - }, - }); - - //Act - vehicleStyle.beforeRouteEnter.call( - wrapper.vm, - { query: { fmgPage: "vehicle-style" } }, - undefined, - (c) => c(wrapper.vm) - ); - wrapper.vm.setVehicle(); - await nextTick(); - - //Assert - apiPromise.finally(() => { - expect(wrapper.vm.dispatchStoreAction).toHaveBeenCalled(); - done(); - }); - }); - - test("Model set, arePagePrerequisitesValid should be true ", async () => { - //Arrange - const { wrapper } = setupMocks({}); - - //Act - vehicleStyle.beforeRouteEnter.call( - wrapper.vm, - { query: { fmgPage: "vehicle-style" } }, - undefined, - (c) => c(wrapper.vm) - ); - - let arePagePrerequisitesValid = wrapper.vm.arePagePrerequisitesValid(); - await nextTick(); - - //Assert - expect(arePagePrerequisitesValid).toBe(true); - }); - - test("there is only one vehicle style => autoselect and move to vehicle damage", async () => { - //Arrange - const { wrapper } = setupMocks({ - styleQuestionInitialData: ["2 door sedan"], - }); - - // Act - await vehicleStyle.beforeRouteEnter.call( - wrapper.vm, - { query: { fmgPage: "vehicle-style" } }, - undefined, - (c) => c(wrapper.vm) - ); - - // Assert - expect(store.commit).toHaveBeenCalledWith(storeMutations.UPDATE_STYLE, "2 door sedan"); - expect(router.overrideNavigation).toHaveBeenCalled(); - }); - - test("there is only one vehicle style and vehicle-damage was visited => don't autoselect or move to vehicle damage", async () => { - //Arrange - const { wrapper } = setupMocks({ - styleQuestionInitialData: ["2 door sedan"], - mountOptionsMockData: { - store: { - getters: { - applicationUser: { - pageData: { - "part-questions": null, - "vehicle-make": {}, - "vehicle-model": {}, - "vehicle-style": {}, - "vehicle-damage": {}, - }, - }, - }, - }, - }, - }); - - // Act - await vehicleStyle.beforeRouteEnter.call( - wrapper.vm, - { query: { fmgPage: "vehicle-style" } }, - undefined, - (c) => c(wrapper.vm) - ); - - // Assert - expect(store.commit).not.toHaveBeenCalledWith(storeMutations.UPDATE_STYLE, "2 door sedan"); - expect(router.overrideNavigation).not.toHaveBeenCalled(); - }); -}); - -function setupMocks({ - vehicleStyleQuestionCmsContent = {}, - styleQuestionInitialData = {}, - pageHeaderWidgetHeaderText = {}, - mountOptionsMockData = {}, -}) { - //Mock api responses - const apiResponses = { - cmsContent: { - FunnelSubHeaderWidget: pageHeaderWidgetHeaderText, - VehicleStyleQuestion: vehicleStyleQuestionCmsContent, - VehicleBannerWidget: { - GenericVehicleImage: - "https://digitalconsumercms-dev.safelite.com/images/default-source/default-album/blurred-image.jpg?sfvrsn=a6ce3034_3", - }, - FunnelHeaderWidget: { - LogoImage: - "https://digitalconsumercms-dev.safelite.com/images/default-source/default-album/safelite-logo.svg?sfvrsn=45e7ed06_3", - }, - }, - styleQuestionInitialData: styleQuestionInitialData, - }; - - const apiPromise = Promise.resolve(apiResponses); - - settleAllPromises.mockImplementation(() => apiPromise); - fetchCmsContentForPage.mockImplementation(() => Promise.resolve()); - - //Mock style question methods - styleQuestion.methods = { - loadInitialData: jest.fn(), - initializeComponent: jest.fn(), - }; - - store.commit = jest.fn(); - store.dispatch = jest.fn(); - store.getters = mountOptionsMockData.store?.getters ?? { - vehicle: { - model: "TL", - }, - applicationUser: { - pageData: { - "part-questions": null, - "vehicle-make": {}, - "vehicle-model": {}, - "vehicle-style": {}, - }, - }, - }; - - const mountOptions = getMountOptions({ - ...mountOptionsMockData, - store, - }); - const wrapper = shallowMount(vehicleStyle, mountOptions); - wrapper.vm.setCmsContent = baseMixin.methods.setCmsContent; - - const styleQuestionWrapper = wrapper.findComponent({ name: "styleQuestion" }); - styleQuestionWrapper.vm.initializeComponent = styleQuestion.methods.initializeComponent; - - return { wrapper, apiPromise }; -} diff --git a/src/layouts/vehicle-style/vehicle-style.vue b/src/layouts/vehicle-style/vehicle-style.vue deleted file mode 100644 index 2d2a4a534..000000000 --- a/src/layouts/vehicle-style/vehicle-style.vue +++ /dev/null @@ -1,137 +0,0 @@ - - - diff --git a/src/layouts/vehicle-year/vehicle-year.spec.js b/src/layouts/vehicle-year/vehicle-year.spec.js deleted file mode 100644 index ad024f131..000000000 --- a/src/layouts/vehicle-year/vehicle-year.spec.js +++ /dev/null @@ -1,122 +0,0 @@ -import { shallowMount, flushPromises } from "@vue/test-utils"; -import { getMountOptions } from "@/helpers/unit-test-helper.js"; -import { settleAllPromises } from "@/helpers/layout-helper.js"; -import { nextTick } from "vue"; -import { storeMutations } from "@/constants/store-mutations"; -import { storeActions } from "@/constants/store-actions"; -import { fetchCmsContentForPage } from "@/helpers/cms-content-helper"; -import baseMixin from "@/mixins/base-mixin.js"; - -import vehicleYear from "@/layouts/vehicle-year/vehicle-year.vue"; -import yearQuestion from "@/layouts/vehicle-year/year-question/year-question"; - -import store from "@/store"; - -jest.mock("@/store", () => ({ - commit: jest.fn(), - dispatch: jest.fn(), - getters: { - applicationUser: { - experiments: [], - }, - }, -})); - -// 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("vehicle-year.vue", () => { - test("Year question component is initized with api data", async (done) => { - //Arrange - const yearQuestionInitialData = ["2023", "2022", "2021"]; - const { wrapper, apiPromise } = setupMocks({ - yearQuestionInitialData: yearQuestionInitialData, - }); - - //Act - vehicleYear.beforeRouteEnter.call( - wrapper.vm, - { query: { fmgPage: "vehicle-year" } }, - undefined, - (c) => c(wrapper.vm) - ); - - //Assert - apiPromise.finally(() => { - expect(yearQuestion.methods.initializeComponent).toHaveBeenCalledWith( - yearQuestionInitialData - ); - done(); - }); - }); -}); - -describe("vehicle-year.vue", () => { - test("arePagePrerequisitesValid should be true ", async () => { - //Arrange - const { wrapper } = setupMocks({}); - - //Act - vehicleYear.beforeRouteEnter.call( - wrapper.vm, - { query: { fmgPage: "vehicle-make" } }, - undefined, - (c) => c(wrapper.vm) - ); - - let arePagePrerequisitesValid = wrapper.vm.arePagePrerequisitesValid(); - await nextTick(); - - //Assert - expect(arePagePrerequisitesValid).toBe(true); - }); -}); - -function setupMocks({ - vehicleYearQuestionCmsContent = {}, - yearQuestionInitialData = {}, - pageHeaderWidgetHeaderText = {}, - mountOptionsMockData = {}, -}) { - //Mock api responses - const apiResponses = { - cmsContent: { - FunnelSubHeaderWidget: { HeaderText: pageHeaderWidgetHeaderText }, - VehicleYearQuestion: vehicleYearQuestionCmsContent, - VehicleBannerWidget: { - GenericVehicleImage: - "https://digitalconsumercms-dev.safelite.com/images/default-source/default-album/blurred-image.jpg?sfvrsn=a6ce3034_3", - }, - FunnelHeaderWidget: { - LogoImage: - "https://digitalconsumercms-dev.safelite.com/images/default-source/default-album/safelite-logo.svg?sfvrsn=45e7ed06_3", - }, - }, - yearQuestionInitialData: yearQuestionInitialData, - }; - const apiPromise = Promise.resolve(apiResponses); - - settleAllPromises.mockImplementation(() => apiPromise); - fetchCmsContentForPage.mockImplementation(() => Promise.resolve()); - - //Mock year question methods - yearQuestion.methods = { - loadInitialData: jest.fn(), - initializeComponent: jest.fn(), - }; - - const mountOptions = getMountOptions(mountOptionsMockData); - const wrapper = shallowMount(vehicleYear, mountOptions); - const yearQuestionWrapper = wrapper.findComponent({ name: "yearQuestion" }); - yearQuestionWrapper.vm.initializeComponent = yearQuestion.methods.initializeComponent; - wrapper.vm.setCmsContent = baseMixin.methods.setCmsContent; - - return { wrapper, apiPromise }; -} diff --git a/src/layouts/vehicle-year/vehicle-year.vue b/src/layouts/vehicle-year/vehicle-year.vue deleted file mode 100644 index 5b166151a..000000000 --- a/src/layouts/vehicle-year/vehicle-year.vue +++ /dev/null @@ -1,110 +0,0 @@ - - - diff --git a/src/layouts/vehicle-year/year-question/year-question.spec.js b/src/layouts/vehicle-year/year-question/year-question.spec.js deleted file mode 100644 index f6a1e5e71..000000000 --- a/src/layouts/vehicle-year/year-question/year-question.spec.js +++ /dev/null @@ -1,78 +0,0 @@ -import yearQuestion from "@/layouts/vehicle-year/year-question/year-question"; -import { shallowMount } from "@vue/test-utils"; -import { getMountOptions } from "@/helpers/unit-test-helper.js"; -import store from "@/store"; -jest.mock( - "@/store", - () => { - return {}; - }, - { virtual: true } -); - -describe("year-question.vue", () => { - test("Selected year is emitted upon selection.", async () => { - //Arrange - const { wrapper } = setupMocks({ modelValueProp: "2020" }); - const yearToSelect = "2021"; - - //Act - wrapper.setValue({ modelValue: yearToSelect }); - await wrapper.vm.$nextTick(); - - //Assert - expect(wrapper.emitted()["update:modelValue"][0]).toEqual([{ modelValue: "2021" }]); - }); -}); - -describe("year-question.vue", () => { - test("Data from store api are used as radio question answers.", async () => { - //Arrange - const { wrapper, cmsContent } = setupMocks({ - dataFromStoreApi: ["2023", "2022", "2021"], - }); - - //Act - const initialData = yearQuestion.methods.loadInitialData.call(wrapper.vm); - yearQuestion.methods.initializeComponent.call(wrapper.vm, initialData); - - //Assert - const buttonQuestionComponent = await wrapper.findComponent({ - name: "buttonQuestion", - }); - expect(buttonQuestionComponent.attributes("answers")).toBe("2023,2022,2021"); - }); -}); - -function setupMocks({ - modelValueProp = "1900", - cmsQuestionText = "CMS text goes here", - dataFromStoreApi = [], -}) { - //Mock store - store.dispatch = jest.fn(() => dataFromStoreApi); - const mountOptions = getMountOptions({ - store: { - dispatch: store.dispatch, - }, - }); - - //Mock props - const mockMixin = { - methods: { - getCmsContent: jest.fn(), - }, - }; - mountOptions.propsData = { - modelValue: modelValueProp, - }; - mountOptions.mixins = [mockMixin]; - - const wrapper = shallowMount(yearQuestion, mountOptions); - - //Mock CMS content - const cmsContent = { - QuestionText: cmsQuestionText, - }; - return { wrapper, cmsContent }; -} diff --git a/src/layouts/vehicle-year/year-question/year-question.vue b/src/layouts/vehicle-year/year-question/year-question.vue deleted file mode 100644 index 6d8110216..000000000 --- a/src/layouts/vehicle-year/year-question/year-question.vue +++ /dev/null @@ -1,55 +0,0 @@ - - - diff --git a/src/layouts/vehicle/vehicle.spec.js b/src/layouts/vehicle/vehicle.spec.js index 210281423..56b46292d 100644 --- a/src/layouts/vehicle/vehicle.spec.js +++ b/src/layouts/vehicle/vehicle.spec.js @@ -1,19 +1,17 @@ // Components import vehicle from "@/layouts/vehicle/vehicle.vue"; -// Supporting files +// Supporting Files +import { shallowMount } from "@vue/test-utils"; import { nextTick } from "vue"; -import { mount } from "@vue/test-utils"; +import { getMountOptions } from "@/helpers/unit-test-helper.js"; + +// Mock our module for promises. +jest.mock("@/helpers/layout-helper.js", () => ({ + settleAllPromises: jest.fn(), +})); describe("vehicle.vue", () => { - test('"Continue" button is enabled after YMMS is selected.', async () => { - const { wrapper } = setupMocks(); - - const continueButton = wrapper.get('[data-test-id="funnel-footer-main-button"]'); - - expect(continueButton.attributes()["aria-disabled"]).toBe("false"); - }); - test("arePagePrerequisitesValid should be true ", async () => { //Arrange const { wrapper } = setupMocks(); @@ -26,66 +24,33 @@ describe("vehicle.vue", () => { expect(arePagePrerequisitesValid).toBe(true); }); }); +describe("vehicle.vue", () => { + describe("navigation", () => { + test("if the continue button is clicked, navigate forward", async () => { + // Arrange + const { wrapper } = setupMocks(); -describe("navigation", () => { - test("forwardButtonAction should trigger navigateForward", async () => { - // Arrange - const { wrapper } = setupMocks(); - wrapper.vm.navigateForward = jest.fn(); + // Act + await wrapper.vm.forwardButtonAction(); - // Act - await wrapper.vm.forwardButtonAction(); - - //Assert - expect(wrapper.vm.navigateForward).toHaveBeenCalled(); + // Assert + expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalled(); + }); }); }); -const FunnelFooterWidgetMockData = { - ForwardButtonText: "Continue", -}; - function setupMocks() { - const mockRoute = { - query: { - fmgPage: "vehicle", - }, - }; - - const mockRouter = { - navigate: jest.fn(), - }; - - const wrapper = mount(vehicle, { - global: { - mixins: [ - { - methods: { - getCmsContent: jest.fn((cmsWidgetName, fieldName) => { - if (cmsWidgetName === "FunnelFooterWidget") { - if (fieldName === "ForwardButtonText") { - return FunnelFooterWidgetMockData.ForwardButtonText; - } - } - return ""; - }), - getFooterInfoBoxHeight: jest.fn(() => 80), - }, - }, - ], - - mocks: { - $route: mockRoute, - $router: mockRouter, + const wrapper = shallowMount( + vehicle, + getMountOptions({ + router: { + navigate: jest.fn(), + navigate: jest.fn(), + navigateWithSaving: jest.fn(), + navigateWithoutSaving: jest.fn(), }, + }) + ); - stubs: { - FunnelHeader: true, - FunnelSubHeader: true, - VehicleBanner: true, - }, - }, - }); - - return { mockRoute, mockRouter, wrapper }; + return { wrapper }; } diff --git a/src/layouts/vehicle/vehicle.vue b/src/layouts/vehicle/vehicle.vue index 24ae0d215..37e813bed 100644 --- a/src/layouts/vehicle/vehicle.vue +++ b/src/layouts/vehicle/vehicle.vue @@ -58,6 +58,9 @@ @@ -109,18 +112,20 @@ export default { selectedMake: this.selectedMakefromStore(), selectedModel: this.selectedModelfromStore(), selectedStyle: this.selectedStylefromStore(), + carId: null, + category: null, + imageUrl: null, + imageVifNumber: null, + imageVifColor: null, yearOptions: [], makeOptions: [], modelOptions: [], styleOptions: [], + displayGeneric: this.displayGenericFromStore(), + unmatchedVehicleIcon: false, }; }, - props: { - cmsWidgetName: String, - validationRules: String, - }, - async beforeRouteEnter(to, from, next) { // Call APIs @@ -244,7 +249,8 @@ export default { const result = await this.getModelOptions(this.selectedYear, make); this.modelOptions = result?.data; if (this.selectedMakefromStore() !== make) { - this.selectedModel = null; + if (this.modelOptions.length === 1) this.selectedModel = this.modelOptions[0]; + else this.selectedModel = null; this.selectedStyle = null; } } else { @@ -260,36 +266,60 @@ export default { ); this.styleOptions = result?.data; if (this.selectedModelfromStore() !== model) { - this.selectedStyle = null; + if (this.styleOptions.length === 1) this.selectedStyle = this.styleOptions[0]; + else this.selectedStyle = null; } } else { this.styleOptions = []; } }, - selectedStyle(style) { - this.setVehicle(this.selectedYear, this.selectedMake, this.selectedModel, style); + async selectedStyle(style) { + if (style) { + const result = await this.getVehicle( + this.selectedYear, + this.selectedMake, + this.selectedModel, + style + ); + this.carId = result?.data.carId; + this.category = result?.data.category; + this.imageUrl = result?.data.imageUrl; + this.imageVifNumber = result?.data.imageVifNumber; + this.imageVifColor = result?.data.imageVifColor; + if (this.imageUrl == null) { + this.displayGeneric = false; + this.unmatchedVehicleIcon = true; + } else this.unmatchedVehicleIcon = false; + } else { + this.imageUrl = null; + this.displayGeneric = true; + } }, }, methods: { - setVehicle(year, make, model, style) { - return this.dispatchStoreAction(this.storeActions.SET_VEHICLE, { + getVehicle(year, make, model, style) { + return this.dispatchStoreAction(this.storeActions.GET_VEHICLE, { year: year, make: make, model: model, style: style, }); }, - + displayGenericFromStore() { + if (store.getters.vehicle.imageUrl !== null) return false; + else if ( + store.getters.vehicle.imageUrl === null && + store.getters.vehicle.carId !== null + ) + return false; + else return true; + }, arePagePrerequisitesValid() { return true; }, async forwardButtonAction() { - return this.navigateForward(); - }, - - navigateForward() { this.dispatchStoreAction( storeActions.SAVE_VEHICLE, { @@ -297,12 +327,16 @@ export default { make: this.selectedMake, model: this.selectedModel, style: this.selectedStyle, + carId: this.carId, + category: this.category, + imageUrl: this.imageUrl, + imageVifNumber: this.imageVifNumber, + imageVifColor: this.imageVifColor, }, false ); this.$router.navigateWithSaving(this.navigationScenarios.CLICKED_FORWARD, this.$route); }, - initializeYearComponent(initialData) { this.yearOptions = initialData; }, @@ -345,12 +379,6 @@ export default { }, }, - computed: { - displayGeneric() { - return !this.selectedStyle; - }, - }, - components: { funnelHeader, funnelFooter, diff --git a/src/mixins/analytics-mixin.js b/src/mixins/analytics-mixin.js index 88b4772f8..37615f68b 100644 --- a/src/mixins/analytics-mixin.js +++ b/src/mixins/analytics-mixin.js @@ -38,6 +38,8 @@ export default { event: pageEvent, shouldUseSessionId: false, experimentsForUser: store.getters.applicationUser.experiments, + referralSequenceNumber: store.getters.order.referralSequenceNumber, + parentAccountNumber: store.getters.order.payment.parentAccountNumber, }; baseMixin.methods.dispatchStoreAction(storeActions.LOG_PAGE_VIEW, payload, false); @@ -57,6 +59,8 @@ export default { value: value, shouldUseSessionId: false, experimentsForUser: store.getters.applicationUser.experiments, + referralSequenceNumber: store.getters.order.referralSequenceNumber, + parentAccountNumber: store.getters.order.payment.parentAccountNumber, }; baseMixin.methods.dispatchStoreAction(storeActions.LOG_CUSTOM_EVENT, payload, false); diff --git a/src/router/index.js b/src/router/index.js index 6d812f847..e8fbf4d26 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -31,11 +31,6 @@ import { applicationConfig } from "../constants/application-config"; import review from "@/layouts/review/review"; import paymentMethod from "@/layouts/payment-method/payment-method"; const routes = [ - { - path: "/review", // This is a temporary route for testing. - name: "review", - component: review, - }, { path: "/payment-method", // This is a temporary route for testing. name: "payment-method", diff --git a/src/router/router-constants/fmgPage-values.js b/src/router/router-constants/fmgPage-values.js index fc8841c13..ba931f77d 100644 --- a/src/router/router-constants/fmgPage-values.js +++ b/src/router/router-constants/fmgPage-values.js @@ -1,8 +1,4 @@ const fmgPageValues = { - VEHICLE_YEAR: "vehicle-year", - VEHICLE_MAKE: "vehicle-make", - VEHICLE_MODEL: "vehicle-model", - VEHICLE_STYLE: "vehicle-style", VEHICLE: "vehicle", VEHICLE_DAMAGE: "vehicle-damage", ADDRESS_LOOKUP: "address-lookup", diff --git a/src/router/router-constants/navigation-scenarios.js b/src/router/router-constants/navigation-scenarios.js index ae1c880ae..bf89f4f3f 100644 --- a/src/router/router-constants/navigation-scenarios.js +++ b/src/router/router-constants/navigation-scenarios.js @@ -12,12 +12,6 @@ const navigationScenarios = { CLICKED_FORWARD: "CLICKED_FORWARD", CLICKED_FORWARD_WITH_CASH: "CLICKED_FORWARD_WITH_CASH", - // YMMS - SELECTED_YEAR: "SELECTED_YEAR", - SELECTED_MODEL: "SELECTED_MODEL", - SELECTED_MAKE: "SELECTED_MAKE", - SELECTED_STYLE: "SELECTED_STYLE", - // Vin selection CLICKED_BACK_WITH_VIN: "CLICKED_BACK_WITH_VIN", CLICKED_FORWARD_WITH_VIN: "CLICKED_FORWARD_WITH_VIN", @@ -51,6 +45,7 @@ const navigationScenarios = { CLICKED_DAMAGE_EDIT: "CLICKED_DAMAGE_EDIT", CLICKED_SERVICE_PACKAGE_EDIT: "CLICKED_SERVICE_PACKAGE_EDIT", CLICKED_SERVICE_LOCATION_EDIT: "CLICKED_SERVICE_LOCATION_EDIT", + CLICKED_SCHEDULE_EDIT: "CLICKED_SCHEDULE_EDIT", }; export { navigationScenarios }; diff --git a/src/router/router-constants/routing-table.js b/src/router/router-constants/routing-table.js index 017e1372a..b90806e94 100644 --- a/src/router/router-constants/routing-table.js +++ b/src/router/router-constants/routing-table.js @@ -13,54 +13,7 @@ const routingTable = function (store) { }, ], }, - { - fmgPageValue: fmgPageValues.VEHICLE_YEAR, - maps: [ - { - scenario: navigationScenarios.SELECTED_YEAR, - destinationFmgPageValue: fmgPageValues.VEHICLE_MAKE, - }, - ], - }, - { - fmgPageValue: fmgPageValues.VEHICLE_MAKE, - maps: [ - { - scenario: navigationScenarios.SELECTED_MAKE, - destinationFmgPageValue: fmgPageValues.VEHICLE_MODEL, - }, - { - scenario: navigationScenarios.CLICKED_BACK, - destinationFmgPageValue: fmgPageValues.VEHICLE_YEAR, - }, - ], - }, - { - fmgPageValue: fmgPageValues.VEHICLE_MODEL, - maps: [ - { - scenario: navigationScenarios.SELECTED_MODEL, - destinationFmgPageValue: fmgPageValues.VEHICLE_STYLE, - }, - { - scenario: navigationScenarios.CLICKED_BACK, - destinationFmgPageValue: fmgPageValues.VEHICLE_MAKE, - }, - ], - }, - { - fmgPageValue: fmgPageValues.VEHICLE_STYLE, - maps: [ - { - scenario: navigationScenarios.SELECTED_STYLE, - destinationFmgPageValue: fmgPageValues.VEHICLE_DAMAGE, - }, - { - scenario: navigationScenarios.CLICKED_BACK, - destinationFmgPageValue: fmgPageValues.VEHICLE_MODEL, - }, - ], - }, + { fmgPageValue: fmgPageValues.VEHICLE_DAMAGE, maps: [ @@ -484,6 +437,10 @@ const routingTable = function (store) { scenario: navigationScenarios.CLICKED_SERVICE_LOCATION_EDIT, destinationFmgPageValue: fmgPageValues.SERVICE_LOCATION, }, + { + scenario: navigationScenarios.CLICKED_SCHEDULE_EDIT, + destinationFmgPageValue: fmgPageValues.SCHEDULE, + }, ], }, ]; diff --git a/src/store/index.js b/src/store/index.js index 2d5a2e898..022db132c 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -13,7 +13,14 @@ import { fmgPageValues } from "@/router/router-constants/fmgPage-values"; import { deleteFunnelCookie } from "@/helpers/heritage-integration/cookie-helper.js"; import { deepEqual } from "@/helpers/object-helper"; import { AppointmentTypeStrings, PREMIUM_FEE_PART_TYPE } from "@/constants/schedule-constants"; +import { getQuerystringParameter } from "@/helpers/querystring-helper"; +import { queryStrings } from "@/constants/query-strings"; import { partTypeStrings } from "@/constants/part-type-strings"; +import { + convertDateStringToDate, + militaryToTwelveHourTime, + getDisplayTextForDurationLength, +} from "@/layouts/schedule/helpers/schedule-helper"; // Export State const getDefaultState = () => { @@ -32,12 +39,6 @@ const getDefaultState = () => { imageColor: null, registration: { licensePlate: null, - address: null, - city: null, - state: null, - zipCode: null, - firstName: null, - lastName: null, }, }, serviceLocation: { @@ -66,7 +67,7 @@ const getDefaultState = () => { lastName: null, emailAddress: null, phoneNumber: null, - isSmsOptin: null, + isSmsOptIn: null, }, damage: { isRepair: null, @@ -96,8 +97,10 @@ const getDefaultState = () => { endTime: null, routeCode: null, jobMaxMinutes: null, + jobMinMinutes: null, }, referralNumber: null, + referralSequenceNumber: null, referralDate: null, referralCorrelationId: null, eon: null, @@ -190,6 +193,9 @@ export const mutations = { updateReferralNumber(state, referralNumber) { state.order.referralNumber = referralNumber; }, + updateReferralSequenceNumber(state, referralSequenceNumber) { + state.order.referralSequenceNumber = referralSequenceNumber; + }, updateReferralDate(state, referralDate) { state.order.referralDate = referralDate; }, @@ -205,27 +211,6 @@ export const mutations = { updateInsuranceVerifiedStatus(state, isVerified) { state.order.payment.insuranceCoverage.isVerified = isVerified; }, - updateRegistrationLicensePlate(state, licensePlate) { - state.order.vehicle.registration.licensePlate = licensePlate; - }, - updateRegistrationAddress(state, registrationAddress) { - state.order.vehicle.registration.address = registrationAddress; - }, - updateRegistrationCity(state, registrationCity) { - state.order.vehicle.registration.city = registrationCity; - }, - updateRegistrationState(state, registrationState) { - state.order.vehicle.registration.state = registrationState; - }, - updateRegistrationZipCode(state, registrationZipCode) { - state.order.vehicle.registration.zipCode = registrationZipCode; - }, - updateRegistrationFirstName(state, firstName) { - state.order.vehicle.registration.firstName = firstName; - }, - updateRegistrationLastName(state, lastName) { - state.order.vehicle.registration.lastName = lastName; - }, updateCustomerEmailAddress(state, customerEmailAddress) { state.order.customer.emailAddress = customerEmailAddress; }, @@ -283,6 +268,7 @@ export const mutations = { state.order.schedule.endTime = scheduleInfo.endTime; state.order.schedule.routeCode = scheduleInfo.routeCode; state.order.schedule.jobMaxMinutes = scheduleInfo.jobMaxMinutes; + state.order.schedule.jobMinMinutes = scheduleInfo.jobMinMinutes; } }, updateCustomerDetails(state, customerDetails) { @@ -291,7 +277,7 @@ export const mutations = { state.order.customer.lastName = customerDetails.lastName; state.order.customer.emailAddress = customerDetails.emailAddress; state.order.customer.phoneNumber = customerDetails.phoneNumber; - state.order.customer.isSmsOptin = customerDetails.isSmsOptin; + state.order.customer.isSmsOptIn = customerDetails.isSmsOptIn; } }, @@ -345,12 +331,8 @@ export const mutations = { }, resetRegistrationState(state) { state.order.vehicle.registration.licensePlate = null; - state.order.vehicle.registration.address = null; - state.order.vehicle.registration.city = null; - state.order.vehicle.registration.state = null; - state.order.vehicle.registration.zipCode = null; - state.order.vehicle.registration.firstName = null; - state.order.vehicle.registration.lastName = null; + state.order.customer.firstName = null; + state.order.customer.lastName = null; }, resetGlassPartsState(state) { state.order.lineItems.glassParts = null; @@ -368,6 +350,7 @@ export const mutations = { state.order.schedule.endTime = null; state.order.schedule.routeCode = null; state.order.schedule.jobMaxMinutes = null; + state.order.schedule.jobMinMinutes = null; //premium appointment fee used on schedule page also needs reset when schedule is reset const supportingItems = state.order.lineItems.supportingItems; @@ -411,6 +394,7 @@ export const mutations = { // Misc Mutations updateStateWithOrderInformation(state, sessionInformation) { state.order.referralNumber = sessionInformation.order.referralNumber; + state.order.referralSequenceNumber = sessionInformation.order.referralSequenceNumber; state.order.referralDate = sessionInformation.order.referralDate; state.order.referralCorrelationId = sessionInformation.order.referralCorrelationId; state.order.eon = sessionInformation.order.eon; @@ -492,8 +476,8 @@ export const mutations = { state.order.customer.emailAddress = sessionInformation.order.customer.emailAddress; state.order.customer.firstName = sessionInformation.order.customer.firstName; state.order.customer.lastName = sessionInformation.order.customer.lastName; - state.order.customer.phoneNumber = sessionInformation.order.customer.phoneNumber; - state.order.customer.isSmsOptin = sessionInformation.order.customer.isSmsOptin; + state.order.customer.phoneNumber = sessionInformation.order.customer.servicePhoneNumber; + state.order.customer.isSmsOptIn = sessionInformation.order.customer.isSmsOptIn; state.order.existingPromoCode = sessionInformation.order.existingPromoCode; state.applicationUser.experiments = sessionInformation.applicationUser.experiments; @@ -506,6 +490,7 @@ export const mutations = { state.order.schedule.endTime = sessionInformation.order.schedule?.endTime; state.order.schedule.routeCode = sessionInformation.order.schedule?.routeCode; state.order.schedule.jobMaxMinutes = sessionInformation.order.schedule?.jobMaxMinutes; + state.order.schedule.jobMinMinutes = sessionInformation.order.schedule?.jobMinMinutes; }, updateExperiments(state, experiments) { state.applicationUser.experiments = experiments; @@ -540,6 +525,9 @@ export const getters = { isMobileAppointment: (state) => { return state.order.serviceLocation.appointmentType === AppointmentTypeStrings.MOBILE; }, + isDropOffAppointment: (state) => { + return state.order.serviceLocation.appointmentType === AppointmentTypeStrings.DROP_OFF; + }, isRecalibrationOnOrder: (state) => { return getHasRecalibrationPart(state); }, @@ -548,6 +536,30 @@ export const getters = { (vap) => vap.partType.toUpperCase() === partTypeStrings.REAR_WIPER.toUpperCase() ); }, + scheduleDisplayText: (state) => { + const dateModel = convertDateStringToDate(state.order.schedule.date); + const readableDate = dateModel?.toLocaleDateString("en-us", { + weekday: "long", + month: "long", + day: "numeric", + year: "numeric", + }); + + const readableStartTime = militaryToTwelveHourTime(state.order.schedule.startTime); + const readableEndTime = militaryToTwelveHourTime(state.order.schedule.endTime); + + const readableDuration = getDisplayTextForDurationLength( + state.order.schedule.jobMinMinutes, + state.order.schedule.jobMaxMinutes + ); + + return { + date: readableDate, + startTime: readableStartTime, + endTime: readableEndTime, + duration: readableDuration, + }; + }, lineItems: (state) => state.order.lineItems, pageData: (state) => (page) => { return state.applicationUser.pageData[page]; @@ -739,7 +751,7 @@ export const actions = { }); }, - setVehicle(context, { year, make, model, style }) { + getVehicle(context, { year, make, model, style }) { return globalMethods .callHttpClient({ methods: endpoints.GetVehicle.method, @@ -747,27 +759,17 @@ export const actions = { payload: {}, }) .then((response) => { - context.commit(storeMutations.UPDATE_CAR_ID, response.data.carId); - context.commit(storeMutations.UPDATE_VEHICLE_CATEGORY, response.data.category); - context.commit(storeMutations.UPDATE_VEHICLE_IMAGE_URL, response.data.imageUrl); - context.commit( - storeMutations.UPDATE_VEHICLE_IMAGE_VIF_NUMBER, - response.data.imageVifNumber - ); - context.commit( - storeMutations.UPDATE_VEHICLE_IMAGE_COLOR, - response.data.imageVifColor - ); return response; }); }, - getDamageOptions(context, { carId, zipCode }) { + getDamageOptions(context, { carId }) { return globalMethods.callHttpClient({ methods: endpoints.GetDamageOptions.method, endpoint: `${endpoints.GetDamageOptions.url}/${carId}`, payload: {}, - additionalSuccessEventDataHandler: (response) => "QueryStringZip: " + zipCode, + additionalSuccessEventDataHandler: (response) => + "QueryStringZip: " + getQuerystringParameter(queryStrings.ZIP_CODE), }); }, @@ -883,6 +885,7 @@ export const actions = { context, { referralNumber, + referralSequenceNumber, referralDate, referralCorrelationId, eon, @@ -892,6 +895,7 @@ export const actions = { } ) { context.commit(storeMutations.UPDATE_REFERRAL_NUMBER, referralNumber); + context.commit(storeMutations.UPDATE_REFERRAL_SEQUENCE_NUMBER, referralSequenceNumber); context.commit(storeMutations.UPDATE_REFERRAL_DATE, referralDate); context.commit(storeMutations.UPDATE_REFERRAL_CORRELATION_ID, referralCorrelationId); context.commit(storeMutations.UPDATE_EON, eon); @@ -911,6 +915,8 @@ export const actions = { event, shouldUseSessionId, experimentsForUser, + referralSequenceNumber, + parentAccountNumber, } ) { var payload = { @@ -923,6 +929,8 @@ export const actions = { event: event, shouldUseSessionId: shouldUseSessionId, experimentsForUser: experimentsForUser, + referralSequenceNumber: referralSequenceNumber, + parentAccountNumber: parentAccountNumber, }; return globalMethods @@ -955,6 +963,8 @@ export const actions = { value, shouldUseSessionId, experimentsForUser, + referralSequenceNumber, + parentAccountNumber, } ) { var payload = { @@ -969,6 +979,8 @@ export const actions = { value: value, shouldUseSessionId: shouldUseSessionId, experimentsForUser: experimentsForUser, + referralSequenceNumber: referralSequenceNumber, + parentAccountNumber: parentAccountNumber, }; return globalMethods @@ -1417,8 +1429,8 @@ export const actions = { emailAddress: order.customer.emailAddress, firstName: order.customer.firstName, lastName: order.customer.lastName, - isSmsOptin: order.customer.isSmsOptin, - phoneNumber: order.customer.phoneNumber, + isSmsOptIn: order.customer.isSmsOptIn, + servicePhoneNumber: order.customer.phoneNumber, }, damage: { numberOfChips: damage.numberOfChips, @@ -1438,7 +1450,7 @@ export const actions = { InsuranceCoverage: { isVerified: order.payment.insuranceCoverage.isVerified ?? false, }, - isInsurance: order.payment.isInsurance ?? false, + isInsurance: order.payment.isInsurance, parentAccountNumber: order.payment.parentAccountNumber, }, serviceLocation: { @@ -1469,12 +1481,13 @@ export const actions = { endTime: order.schedule?.endTime, routeCode: order.schedule?.routeCode, jobMaxMinutes: order.schedule?.jobMaxMinutes, + jobMinMinutes: order.schedule?.jobMinMinutes, }, existingPromoCode: null, referralCorrelationId: order.referralCorrelationId, referralDate: order.referralDate, - referralNumber: order.referralNumber?.toString(), // TODO It'd be nice to save these as strings in the first place - referralSequenceNumber: order.referralNumber?.toString(), // TODO Pass the referralSequence number once insurance flow creates it + referralNumber: order.referralNumber?.toString(), + referralSequenceNumber: order.referralSequenceNumber, eon: order.eon, }, }, @@ -1542,92 +1555,12 @@ export const actions = { // Business domain actions // Vehicle - saveVehicleYear(context, year) { - //Reset dependent state when changing - if (context.state.order.vehicle.year !== year) { - context.commit(storeMutations.UPDATE_MAKE, null); - context.commit(storeMutations.UPDATE_MODEL, null); - context.commit(storeMutations.UPDATE_STYLE, null); - context.commit(storeMutations.UPDATE_CAR_ID, null); - context.commit(storeMutations.UPDATE_VEHICLE_CATEGORY, null); - context.commit(storeMutations.UPDATE_VEHICLE_VIN, null); - context.commit(storeMutations.UPDATE_VEHICLE_IMAGE_URL, null); - context.commit(storeMutations.UPDATE_VEHICLE_IMAGE_VIF_NUMBER, null); - context.commit(storeMutations.UPDATE_VEHICLE_IMAGE_COLOR, null); - - context.dispatch(storeActions.RESET_DAMAGE_STATE_AND_DEPENDENCIES); - context.dispatch(storeActions.RESET_REGISTRATION_STATE_AND_DEPENDENCIES); - context.dispatch(storeActions.RESET_PARTS_STATE_AND_DEPENDENCIES); - - //Save new values - context.commit(storeMutations.UPDATE_YEAR, year); - } - }, - - saveVehicleMake(context, make) { - //Reset dependent state when changing - if (context.state.order.vehicle.make !== make) { - context.commit(storeMutations.UPDATE_MODEL, null); - context.commit(storeMutations.UPDATE_STYLE, null); - context.commit(storeMutations.UPDATE_CAR_ID, null); - context.commit(storeMutations.UPDATE_VEHICLE_CATEGORY, null); - context.commit(storeMutations.UPDATE_VEHICLE_VIN, null); - context.commit(storeMutations.UPDATE_VEHICLE_IMAGE_URL, null); - context.commit(storeMutations.UPDATE_VEHICLE_IMAGE_VIF_NUMBER, null); - context.commit(storeMutations.UPDATE_VEHICLE_IMAGE_COLOR, null); - - context.dispatch(storeActions.RESET_DAMAGE_STATE_AND_DEPENDENCIES); - context.dispatch(storeActions.RESET_REGISTRATION_STATE_AND_DEPENDENCIES); - context.dispatch(storeActions.RESET_PARTS_STATE_AND_DEPENDENCIES); - - //Save new values - context.commit(storeMutations.UPDATE_MAKE, make); - } - }, - - saveVehicleModel(context, model) { - //Reset dependent state when changing - if (context.state.order.vehicle.model !== model) { - context.commit(storeMutations.UPDATE_STYLE, null); - context.commit(storeMutations.UPDATE_CAR_ID, null); - context.commit(storeMutations.UPDATE_VEHICLE_CATEGORY, null); - context.commit(storeMutations.UPDATE_VEHICLE_VIN, null); - context.commit(storeMutations.UPDATE_VEHICLE_IMAGE_URL, null); - context.commit(storeMutations.UPDATE_VEHICLE_IMAGE_VIF_NUMBER, null); - context.commit(storeMutations.UPDATE_VEHICLE_IMAGE_COLOR, null); - - context.dispatch(storeActions.RESET_DAMAGE_STATE_AND_DEPENDENCIES); - context.dispatch(storeActions.RESET_REGISTRATION_STATE_AND_DEPENDENCIES); - context.dispatch(storeActions.RESET_PARTS_STATE_AND_DEPENDENCIES); - - //Save new values - context.commit(storeMutations.UPDATE_MODEL, model); - } - }, - - saveVehicleStyle(context, style) { - //Reset dependent state when changing - if (context.state.order.vehicle.style !== style) { - context.commit(storeMutations.UPDATE_CAR_ID, null); - context.commit(storeMutations.UPDATE_VEHICLE_CATEGORY, null); - context.commit(storeMutations.UPDATE_VEHICLE_VIN, null); - context.commit(storeMutations.UPDATE_VEHICLE_IMAGE_URL, null); - context.commit(storeMutations.UPDATE_VEHICLE_IMAGE_VIF_NUMBER, null); - context.commit(storeMutations.UPDATE_VEHICLE_IMAGE_COLOR, null); - - context.dispatch(storeActions.RESET_DAMAGE_STATE_AND_DEPENDENCIES); - context.dispatch(storeActions.RESET_REGISTRATION_STATE_AND_DEPENDENCIES); - context.dispatch(storeActions.RESET_PARTS_STATE_AND_DEPENDENCIES); - - //Save new values - context.commit(storeMutations.UPDATE_STYLE, style); - } - }, - - saveVehicle(context, { year, make, model, style }) { + saveVehicle( + context, + { year, make, model, style, carId, category, imageUrl, imageVifNumber, imageVifColor } + ) { 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); @@ -1641,6 +1574,11 @@ export const actions = { if (context.state.order.vehicle.style !== style) { context.commit(storeMutations.UPDATE_STYLE, style); } + context.commit(storeMutations.UPDATE_CAR_ID, carId); + context.commit(storeMutations.UPDATE_VEHICLE_CATEGORY, category); + context.commit(storeMutations.UPDATE_VEHICLE_IMAGE_URL, imageUrl); + context.commit(storeMutations.UPDATE_VEHICLE_IMAGE_VIF_NUMBER, imageVifNumber); + context.commit(storeMutations.UPDATE_VEHICLE_IMAGE_COLOR, imageVifColor); }, saveVehicleDamage( @@ -1728,12 +1666,8 @@ export const actions = { ) { //Reset dependent state when changing if ( - registrationInfo?.address !== context.state.order.vehicle.registration?.address || - registrationInfo?.city !== context.state.order.vehicle.registration?.city || - registrationInfo?.state !== context.state.order.vehicle.registration?.state || - registrationInfo?.zipCode !== context.state.order.vehicle.registration?.zipCode || - registrationInfo?.firstName !== context.state.order.vehicle.registration?.firstName || - registrationInfo?.lastName !== context.state.order.vehicle.registration?.lastName + registrationInfo?.firstName !== context.state.order.customer?.firstName || + registrationInfo?.lastName !== context.state.order.customer?.lastName ) { context.dispatch(storeActions.RESET_REGISTRATION_STATE_AND_DEPENDENCIES); @@ -2064,12 +1998,6 @@ export const actions = { return true; default: } - if ( - context.state.order.vehicle.make.toLowerCase() === "ford" && - context.state.order.vehicle.year >= 2018 - ) { - return true; - } if ( context.state.order.vehicle.make.toLowerCase() === "bmw" && context.state.order.vehicle.year <= 2017 diff --git a/src/store/store.spec.js b/src/store/store.spec.js index 08aa406c9..ded194ba8 100644 --- a/src/store/store.spec.js +++ b/src/store/store.spec.js @@ -335,6 +335,28 @@ describe("Mutations", () => { expect(storeState.order.serviceLocation.state).toEqual("OH"); expect(storeState.order.serviceLocation.zipCodeCtu).toEqual("01820"); }); + + it("updateCustomerDetails, should set customer details in state", () => { + // Arrange + const storeState = state; + const customerDetails = { + firstName: "foo", + lastName: "bar", + emailAddress: "foo@bar.com", + phoneNumber: "555-555-5555", + isSmsOptIn: true, + }; + + // Act + mutations.updateCustomerDetails(storeState, customerDetails); + + // Assert + expect(state.order.customer.firstName).toEqual("foo"); + expect(state.order.customer.lastName).toEqual("bar"); + expect(state.order.customer.emailAddress).toEqual("foo@bar.com"); + expect(state.order.customer.phoneNumber).toEqual("555-555-5555"); + expect(state.order.customer.isSmsOptIn).toEqual(true); + }); }); describe("Actions", () => { @@ -484,12 +506,9 @@ describe("Actions", () => { expect(response.data).toEqual({ style: "4 DOOR SEDAN" }); }); - it("setVehicle action, should get vehicle data and set carId and vehicle category", async () => { + it("getVehicle action, should get vehicle data ", async () => { // Arrange const context = state; - const commit = jest.fn(); - - context.commit = commit; // Act globalMethods.callHttpClient.mockImplementation(() => { @@ -497,10 +516,8 @@ describe("Actions", () => { }); // Assert - const response = await actions.setVehicle(context, "C00000000"); + const response = await actions.getVehicle(context, "C00000000"); - expect(commit).toBeCalledWith(storeMutations.UPDATE_CAR_ID, "C00000000"); - expect(commit).toBeCalledWith(storeMutations.UPDATE_VEHICLE_CATEGORY, "CAR"); expect(response.data).toEqual({ carId: "C00000000", category: "CAR" }); }); @@ -802,6 +819,7 @@ describe("Actions", () => { // Act actions.updateStoreWithSaveSessionResponse(context, { referralNumber: "123", + referralSequenceNumber: 123, referralDate: new Date().toUTCString(), referralCorrelationId: "xxx-xxx-xxx", parentAccountNumber: "167132", @@ -943,7 +961,7 @@ describe("Actions", () => { ); }); - it("saveServiceZipCodeInfo, should call mutation and save zip code to state", () => { + it("saveServiceZipCodeInfo, should call mutation", () => { // Arrange const context = state; const commit = jest.fn(); @@ -958,7 +976,6 @@ describe("Actions", () => { // Assert expect(commit).toBeCalledWith(storeMutations.UPDATE_SERVICE_LOCATION, serviceZipCodeInfo); - expect(state.order.serviceLocation.zipCode).toEqual("43212"); }); it("saveServiceZipCodeInfo, should reset if zip code is different", () => { @@ -1027,7 +1044,7 @@ describe("Actions", () => { ); }); - it("saveServiceLocation, should call mutation and save service address to state", () => { + it("saveServiceLocation, should call mutation", () => { // Arrange const context = state; const commit = jest.fn(); @@ -1046,11 +1063,6 @@ describe("Actions", () => { // Assert expect(commit).toBeCalledWith(storeMutations.UPDATE_SERVICE_LOCATION, serviceLocation); - expect(state.order.serviceLocation.address).toEqual("123 Test Lane"); - expect(state.order.serviceLocation.city).toEqual("Columbus"); - expect(state.order.serviceLocation.zipCode).toEqual("43212"); - expect(state.order.serviceLocation.state).toEqual("OH"); - expect(state.order.serviceLocation.zipCodeCtu).toEqual("01820"); }); it("saveServiceLocation, should reset if zipcode is different", () => { @@ -1297,6 +1309,27 @@ describe("Actions", () => { expect(commit).not.toHaveBeenCalledWith(storeMutations.RESET_SCHEDULE); }); + it("saveCustomerDetails, should call mutation", () => { + // Arrange + const context = state; + const commit = jest.fn(); + context.commit = commit; + + const customerDetails = { + firstName: "foo", + lastName: "bar", + emailAddress: "foo@bar.com", + phoneNumber: "555-555-5555", + isSmsOptIn: true, + }; + + // Act + actions.saveCustomerDetails(context, customerDetails); + + // Assert + expect(commit).toBeCalledWith(storeMutations.UPDATE_CUSTOMER_DETAILS, customerDetails); + }); + it("saveGlassParts, should call mutation", () => { // Arrange const context = { @@ -1519,7 +1552,7 @@ describe("Actions", () => { isCarIdDifferent: true, isSelectedGlassAvailableForVehicle: false, vehicleInfo: { carId: "C010101", vin: "XXXXX" }, - registrationInfo: { zipCode: "80020", address: "123 Marys Ave" }, + registrationInfo: { firstName: "abc", lastName: "123" }, serviceLocationInfo: { state: "CO" }, customerEmail: "test@safelite.com", }; @@ -1540,168 +1573,6 @@ describe("Actions", () => { expect(commit).toBeCalledWith(storeMutations.UPDATE_REGISTRATION, payload.registrationInfo); }); - it("saveVehicleYear, should wipe out vehicle info if year changes", () => { - // Arrange - const context = state; - - context.state = { - order: { - vehicle: { - year: "2015", - }, - }, - }; - - const commit = jest.fn(); - const dispatch = jest.fn(); - - context.commit = commit; - context.dispatch = dispatch; - - // Act - actions.saveVehicleYear(context, "2016"); - - // Assert - expect(dispatch).toHaveBeenNthCalledWith( - 1, - storeActions.RESET_DAMAGE_STATE_AND_DEPENDENCIES - ); - expect(dispatch).toHaveBeenNthCalledWith( - 2, - storeActions.RESET_REGISTRATION_STATE_AND_DEPENDENCIES - ); - - expect(commit).toBeCalledWith(storeMutations.UPDATE_MAKE, null); - expect(commit).toBeCalledWith(storeMutations.UPDATE_MODEL, null); - expect(commit).toBeCalledWith(storeMutations.UPDATE_STYLE, null); - expect(commit).toBeCalledWith(storeMutations.UPDATE_CAR_ID, null); - expect(commit).toBeCalledWith(storeMutations.UPDATE_VEHICLE_CATEGORY, null); - expect(commit).toBeCalledWith(storeMutations.UPDATE_VEHICLE_VIN, null); - expect(commit).toBeCalledWith(storeMutations.UPDATE_VEHICLE_IMAGE_URL, null); - expect(commit).toBeCalledWith(storeMutations.UPDATE_VEHICLE_IMAGE_VIF_NUMBER, null); - expect(commit).toBeCalledWith(storeMutations.UPDATE_VEHICLE_IMAGE_COLOR, null); - }); - - it("saveVehicleMake, should wipe out vehicle info if make changes", () => { - // Arrange - const context = state; - - context.state = { - order: { - vehicle: { - make: "Honda", - }, - }, - }; - - const commit = jest.fn(); - const dispatch = jest.fn(); - - context.commit = commit; - context.dispatch = dispatch; - - // Act - actions.saveVehicleMake(context, "Toyota"); - - // Assert - expect(dispatch).toHaveBeenNthCalledWith( - 1, - storeActions.RESET_DAMAGE_STATE_AND_DEPENDENCIES - ); - expect(dispatch).toHaveBeenNthCalledWith( - 2, - storeActions.RESET_REGISTRATION_STATE_AND_DEPENDENCIES - ); - - expect(commit).toBeCalledWith(storeMutations.UPDATE_MODEL, null); - expect(commit).toBeCalledWith(storeMutations.UPDATE_STYLE, null); - expect(commit).toBeCalledWith(storeMutations.UPDATE_CAR_ID, null); - expect(commit).toBeCalledWith(storeMutations.UPDATE_VEHICLE_CATEGORY, null); - expect(commit).toBeCalledWith(storeMutations.UPDATE_VEHICLE_VIN, null); - expect(commit).toBeCalledWith(storeMutations.UPDATE_VEHICLE_IMAGE_URL, null); - expect(commit).toBeCalledWith(storeMutations.UPDATE_VEHICLE_IMAGE_VIF_NUMBER, null); - expect(commit).toBeCalledWith(storeMutations.UPDATE_VEHICLE_IMAGE_COLOR, null); - }); - - it("saveVehicle model, should wipe out vehicle info if model changes", () => { - // Arrange - const context = state; - - context.state = { - order: { - vehicle: { - model: "Civic", - }, - }, - }; - - const commit = jest.fn(); - const dispatch = jest.fn(); - - context.commit = commit; - context.dispatch = dispatch; - - // Act - actions.saveVehicleModel(context, "Accord"); - - // Assert - expect(dispatch).toHaveBeenNthCalledWith( - 1, - storeActions.RESET_DAMAGE_STATE_AND_DEPENDENCIES - ); - expect(dispatch).toHaveBeenNthCalledWith( - 2, - storeActions.RESET_REGISTRATION_STATE_AND_DEPENDENCIES - ); - - expect(commit).toBeCalledWith(storeMutations.UPDATE_STYLE, null); - expect(commit).toBeCalledWith(storeMutations.UPDATE_CAR_ID, null); - expect(commit).toBeCalledWith(storeMutations.UPDATE_VEHICLE_CATEGORY, null); - expect(commit).toBeCalledWith(storeMutations.UPDATE_VEHICLE_VIN, null); - expect(commit).toBeCalledWith(storeMutations.UPDATE_VEHICLE_IMAGE_URL, null); - expect(commit).toBeCalledWith(storeMutations.UPDATE_VEHICLE_IMAGE_VIF_NUMBER, null); - expect(commit).toBeCalledWith(storeMutations.UPDATE_VEHICLE_IMAGE_COLOR, null); - }); - - it("saveVehicleStyle, should wipe out vehicle info if style changes", () => { - // Arrange - const context = state; - - context.state = { - order: { - vehicle: { - style: "Sedan", - }, - }, - }; - - const commit = jest.fn(); - const dispatch = jest.fn(); - - context.commit = commit; - context.dispatch = dispatch; - - // Act - actions.saveVehicleStyle(context, "SUV"); - - // Assert - expect(dispatch).toHaveBeenNthCalledWith( - 1, - storeActions.RESET_DAMAGE_STATE_AND_DEPENDENCIES - ); - expect(dispatch).toHaveBeenNthCalledWith( - 2, - storeActions.RESET_REGISTRATION_STATE_AND_DEPENDENCIES - ); - - expect(commit).toBeCalledWith(storeMutations.UPDATE_CAR_ID, null); - expect(commit).toBeCalledWith(storeMutations.UPDATE_VEHICLE_CATEGORY, null); - expect(commit).toBeCalledWith(storeMutations.UPDATE_VEHICLE_VIN, null); - expect(commit).toBeCalledWith(storeMutations.UPDATE_VEHICLE_IMAGE_URL, null); - expect(commit).toBeCalledWith(storeMutations.UPDATE_VEHICLE_IMAGE_VIF_NUMBER, null); - expect(commit).toBeCalledWith(storeMutations.UPDATE_VEHICLE_IMAGE_COLOR, null); - }); - it("saveVehicle, should save vehicle info", () => { // Arrange const context = state; @@ -1729,6 +1600,8 @@ describe("Actions", () => { make: "Honda", model: "Civic", style: "Sedan", + carId: "C00000000", + category: "CAR", }; actions.saveVehicle(context, payload); @@ -1753,6 +1626,8 @@ describe("Actions", () => { if (context.state.order.vehicle.style !== payload.style) { expect(commit).toBeCalledWith(storeMutations.UPDATE_STYLE, payload.style); } + context.commit(storeMutations.UPDATE_CAR_ID, payload.carId); + context.commit(storeMutations.UPDATE_VEHICLE_CATEGORY, payload.category); }); it("saveVehicleDamage, should wipe out damage if different", () => { @@ -3291,8 +3166,6 @@ describe("Getters", () => { describe("isVinOptionalVehicle", () => { const testVehicles = [ ["2017", "acura", false], - ["2017", "ford", false], - ["2018", "ford", true], ["2018", "bmw", false], ["2017", "bmw", true], ["2016", "bmw", true],