diff --git a/jest.config.js b/jest.config.js index bfb4bf71f..ceeb6f781 100644 --- a/jest.config.js +++ b/jest.config.js @@ -15,7 +15,7 @@ module.exports = { "!src/layouts/vehicle-damage/windshield-options/windshield-options.vue", "!src/layouts/reveal/**/*.vue", "!src/ux-components/text-link/**/*.vue", - "!src/common-components/date-picker/**/*.vue", + "!src/common-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", // END diff --git a/src/constants/scheduling.js b/src/constants/scheduling.js new file mode 100644 index 000000000..c656ac60a --- /dev/null +++ b/src/constants/scheduling.js @@ -0,0 +1,16 @@ +const monthsOfYear = [ + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December", +]; + +export { monthsOfYear }; 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..f140e9adb --- /dev/null +++ b/src/digital-components/date-picker/date-picker.spec.js @@ -0,0 +1,437 @@ +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", () => { + // Arrange + const { wrapper } = setupMocks({}); + + // Act + const testResult = wrapper.vm.todayDate instanceof Date; + + // 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 1c83c0a4e..e3b5ff5d1 100644 --- a/src/digital-components/date-picker/date-picker.vue +++ b/src/digital-components/date-picker/date-picker.vue @@ -1,224 +1,376 @@ - diff --git a/src/digital-components/modal/modal.vue b/src/digital-components/modal/modal.vue index 7217e7478..cd47a408e 100644 --- a/src/digital-components/modal/modal.vue +++ b/src/digital-components/modal/modal.vue @@ -19,7 +19,7 @@ ref="closeButton" type="button" class="btn-close" - data-bs-dismiss="modal" + @mousedown="closeModal" aria-label="Close"> @@ -102,12 +100,7 @@ export default { const parentAccountNumber = store.getters.payment.parentAccountNumber; const billToAccountNumber = 87291; - const mobileFeePartPromise = getPricedMobileFeePart( - serviceZipCode, - serviceType, - parentAccountNumber, - billToAccountNumber - ); + const mobileFeePartPromise = getPricedMobileFeePart(serviceZipCode); // Settle promises and get results const promiseResultMap = [ @@ -181,8 +174,11 @@ export default { this.isVehicleProtected = newValue.isVehicleProtected; }, }, - shouldDisableForwardAction() { - return this.zipContainsMilitaryBase; + showMilitaryZipAlert() { + return ( + this.zipContainsMilitaryBase && + (this.isZipServiceableMobile == null || this.isZipServiceableMobile === true) + ); }, }, methods: { @@ -206,6 +202,11 @@ export default { this.mobileFeePart = mobileFeePart; } }, + setContainsMilitaryBase(val) { + if (this.zipContainsMilitaryBase !== val) { + this.zipContainsMilitaryBase = val; + } + }, getServiceAddressFromStore() { return store.getters.order.serviceLocation.address; }, @@ -247,15 +248,11 @@ export default { zipCode: { async handler(newValue, oldValue) { if (newValue !== oldValue) { - baseMixin.methods.getZipCodeData(newValue).then((r) => { - this.zipContainsMilitaryBase = r.containsMilitaryBase; - }); + const serviceZipCode = store.getters.order.serviceLocation.zipCode; + const serviceabilityDetails = await getServiceabilityDetails(serviceZipCode); + + this.setServiceabilityDetails(serviceabilityDetails.data); } - - const serviceZipCode = store.getters.order.serviceLocation.zipCode; - const serviceabilityDetails = await getServiceabilityDetails(serviceZipCode); - - this.setServiceabilityDetails(serviceabilityDetails.data); }, }, }, diff --git a/src/layouts/service-location/service-type-question/service-type-question.vue b/src/layouts/service-location/service-type-question/service-type-question.vue index 8f55b4b8a..b74ad989f 100644 --- a/src/layouts/service-location/service-type-question/service-type-question.vue +++ b/src/layouts/service-location/service-type-question/service-type-question.vue @@ -55,8 +55,10 @@ export default { return filteredAnswers; }, serviceability() { - const mobileAvailable = this.isGlassServiceableMobile && this.isRecalibrationServiceableMobile; - const inshopAvailable = this.isGlassServiceableInshop && this.isRecalibrationServiceableInshop; + const mobileAvailable = + this.isGlassServiceableMobile && this.isRecalibrationServiceableMobile; + const inshopAvailable = + this.isGlassServiceableInshop && this.isRecalibrationServiceableInshop; let result; if (inshopAvailable && mobileAvailable) { @@ -68,7 +70,7 @@ export default { } else if (!inshopAvailable && !mobileAvailable) { result = "None"; } - + return result; }, selectedValues: { diff --git a/src/layouts/service-location/service-zip-modal-question/service-zip-modal-question.vue b/src/layouts/service-location/service-zip-modal-question/service-zip-modal-question.vue index a53bec1dc..59c09fc9d 100644 --- a/src/layouts/service-location/service-zip-modal-question/service-zip-modal-question.vue +++ b/src/layouts/service-location/service-zip-modal-question/service-zip-modal-question.vue @@ -44,13 +44,12 @@ import { getPricedMobileFeePart } from "@/layouts/service-location/helpers/servi export default { name: "service-zip-modal-question", - emits: ["update:modelValue", "set-mobile-fee-part"], + emits: ["update:modelValue", "updated-mobile-fee-part", "updated-contains-military-base"], data() { return { internalModel: this.copyModel(this.modelValue), serviceZipCodeTextInputId: "", displayInvalidZipAlert: false, - }; }, props: { @@ -142,26 +141,32 @@ export default { }, async setZipCode() { - this.resetAlerts(); + if (this.internalModel.zipCode !== this.modelValue.zipCode) { + this.resetAlerts(); - const zipCodeData = await this.getZipCodeData(this.internalModel.zipCode); + const zipCodeData = await this.getZipCodeData(this.internalModel.zipCode); - if (!zipCodeData.isValid) { - this.displayInvalidZipAlert = true; - this.focusOnZipInput(); - this.resetModalButtonStyle(); + if (!zipCodeData.isValid) { + this.displayInvalidZipAlert = true; + this.focusOnZipInput(); + this.resetModalButtonStyle(); + } else { + this.internalModel.state = zipCodeData.state; + + // retrieve mobile fee part + const serviceZipCode = this.internalModel.zipCode; + const mobileFeePart = await getPricedMobileFeePart(serviceZipCode); + + // emit it to parent + this.$emit("updated-mobile-fee-part", mobileFeePart); + + // Update the page level model + this.$emit("update:modelValue", this.internalModel); + this.$emit("updated-contains-military-base", zipCodeData.containsMilitaryBase); + + this.closeModal(); + } } else { - this.internalModel.state = zipCodeData.state; - - // retrieve mobile fee part - const serviceZipCode = this.internalModel.zipCode; - const mobileFeePart = await getPricedMobileFeePart(serviceZipCode); - - // emit it to parent - this.$emit("set-mobile-fee-part", mobileFeePart); - - // Update the page level model - this.$emit("update:modelValue", this.internalModel); this.closeModal(); } }, diff --git a/src/layouts/vehicle-damage/vehicle-damage.vue b/src/layouts/vehicle-damage/vehicle-damage.vue index f2fc72b38..5e2ec0c71 100644 --- a/src/layouts/vehicle-damage/vehicle-damage.vue +++ b/src/layouts/vehicle-damage/vehicle-damage.vue @@ -325,6 +325,17 @@ export default { false ); + if (this.isWindshieldRepair) { + const supportingItems = await this.dispatchStoreAction( + storeActions.GET_SUPPORTING_ITEMS + ); + this.dispatchStoreAction( + this.storeActions.SAVE_SUPPORTING_ITEMS, + supportingItems.data, + false + ); + } + return this.navigateForward(); }, diff --git a/src/router/index.js b/src/router/index.js index 253eaa303..ffd5122e6 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -31,8 +31,14 @@ import { applicationConfig } from "../constants/application-config"; // Components import quote from "@/layouts/quote/quote.vue"; import datePicker from "@/digital-components/date-picker/date-picker.vue"; +import demoDatePicker from "@/layouts/demo-date-picker/demo-date-picker.vue"; const routes = [ + { + path: "/demo-date-picker", // This is a temporary route for testing. + name: "demo-date-picker", + component: demoDatePicker, + }, { path: "/date-picker", // This is a temporary route for testing. name: "date-picker", diff --git a/src/store/index.js b/src/store/index.js index a64c279b6..413d2f8b0 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -929,7 +929,7 @@ export const actions = { getServiceabilityDetails(context, { serviceZipCode }) { const lineItems = context.getters.order.lineItems; const lineItemsToSend = [...lineItems.supportingItems]; - const encodedLineItems = encodeURIComponent(JSON.stringify(lineItemsToSend)) + const encodedLineItems = encodeURIComponent(JSON.stringify(lineItemsToSend)); return globalMethods.callHttpClient({ method: endpoints.GetServiceabilityDetails.method, endpoint: `${endpoints.GetServiceabilityDetails.url}?zip=${serviceZipCode}&lineItems=${encodedLineItems}`, diff --git a/src/ux-components/button-main/button-main.spec.js b/src/ux-components/button-main/button-main.spec.js index 41e244a72..76e9afe28 100644 --- a/src/ux-components/button-main/button-main.spec.js +++ b/src/ux-components/button-main/button-main.spec.js @@ -9,7 +9,7 @@ describe("buttonMain.vue", () => { const wrapper = shallowMount( buttonMain, setupMocks({ - props: { + propsData: { isPrimary: true, }, }) @@ -25,7 +25,7 @@ describe("buttonMain.vue", () => { const wrapper = shallowMount( buttonMain, setupMocks({ - props: { + propsData: { isDisabled: true, }, }) @@ -41,7 +41,7 @@ describe("buttonMain.vue", () => { const wrapper = shallowMount( buttonMain, setupMocks({ - props: { + propsData: { loaderColor: "blue", loaderEnabled: true, }, @@ -62,7 +62,7 @@ describe("buttonMain.vue", () => { const wrapper = shallowMount( buttonMain, setupMocks({ - props: { + propsData: { loaderPosition: "right", loaderEnabled: true, }, @@ -83,7 +83,7 @@ describe("buttonMain.vue", () => { const wrapper = shallowMount( buttonMain, setupMocks({ - props: { + propsData: { loaderPosition: "right", loaderEnabled: true, }, @@ -108,7 +108,7 @@ describe("buttonMain.vue", () => { const wrapper = shallowMount( buttonMain, setupMocks({ - props: { + propsData: { loaderPosition: "right", loaderEnabled: true, }, @@ -132,7 +132,7 @@ describe("buttonMain.vue", () => { const wrapper = shallowMount( buttonMain, setupMocks({ - props: { + propsData: { loaderPosition: "right", loaderEnabled: true, isDisabled: false, @@ -156,7 +156,7 @@ describe("buttonMain.vue", () => { const wrapper = shallowMount( buttonMain, setupMocks({ - props: { + propsData: { loaderPosition: "right", loaderEnabled: true, isDisabled: true,