Merge branch 'develop' into feature/csr-1465

This commit is contained in:
Chloe Herd 2023-10-17 10:09:02 -04:00
commit 616cb187ed
29 changed files with 350 additions and 323 deletions

View file

@ -31,5 +31,5 @@ module.exports = {
}, },
}, },
// Uncomment this to avoid the massive amount of warnings we are getting for onSubmit and onInvalidSubmit // Uncomment this to avoid the massive amount of warnings we are getting for onSubmit and onInvalidSubmit
silent: true, //silent: true,
}; };

View file

@ -0,0 +1,6 @@
const serviceType = {
REPLACEMENT: "replacement",
REPAIR: "repair",
REPLACEMENT_AND_RECALIBRATION: "replacement and recalibration",
};
export { serviceType };

View file

@ -22,7 +22,7 @@
@mousedown="closeModal" @mousedown="closeModal"
aria-label="Close"></button> aria-label="Close"></button>
</div> </div>
<div class="modal-body ps-5 pe-5 pb-4 pt-0"> <div class="modal-body ps-5 pe-5 pb-4 pt-1">
<slot></slot> <slot></slot>
</div> </div>
<div class="modal-footer px-5 py-4"> <div class="modal-footer px-5 py-4">
@ -179,7 +179,7 @@ export default {
} }
} }
@include media-breakpoint-up(md) { @include media-breakpoint-up(md) {
overflow: scroll; overflow: auto;
flex: none; flex: none;
} }
} }

View file

@ -7,7 +7,7 @@
<funnelHeader cmsWidgetName="FunnelHeaderWidget" /> <funnelHeader cmsWidgetName="FunnelHeaderWidget" />
</div> </div>
</div> </div>
<div class="row justify-content-md-center"> <div class="row justify-content-center">
<div class="col-md-6"> <div class="col-md-6">
<vehicleBanner <vehicleBanner
cmsWidgetName="VehicleBannerWidget" cmsWidgetName="VehicleBannerWidget"
@ -16,7 +16,7 @@
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" /> <funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" />
</div> </div>
</div> </div>
<div class="row justify-content-md-center"> <div class="row justify-content-center">
<div class="col-md-6 col-xl-4"> <div class="col-md-6 col-xl-4">
<alert <alert
ref="alertFewMoreQuestions" ref="alertFewMoreQuestions"
@ -28,7 +28,7 @@
v-bind:isDismissible="false" /> v-bind:isDismissible="false" />
</div> </div>
</div> </div>
<div class="row justify-content-md-center"> <div class="row justify-content-center">
<div class="col-md-6 col-xl-4"> <div class="col-md-6 col-xl-4">
<div v-for="(questionsDatum, i) in questionsData" :key="questionsDatum.key"> <div v-for="(questionsDatum, i) in questionsData" :key="questionsDatum.key">
<questionChain <questionChain

View file

@ -7,22 +7,6 @@ export function getDateDifferenceInDays(startDate, endDate) {
// Convert milliseconds to days and return the result // Convert milliseconds to days and return the result
return difference / (1000 * 3600 * 24); return difference / (1000 * 3600 * 24);
} }
export function getFullDayName(date) {
// Use a ternary operator to check if the input is a valid date object
return date instanceof Date
? // Use the built-in method toLocaleDateString() to get the full day name in the current locale
date.toLocaleDateString(undefined, { weekday: "long" })
: // Return undefined if the input is not a valid date object
undefined;
}
export function getFullMonthName(date) {
// Use a ternary operator to check if the input is a valid date object
return date instanceof Date
? // Use the built-in method toLocaleDateString() to get the full month name in the current locale
date.toLocaleDateString(undefined, { month: "long" })
: // Return undefined if the input is not a valid date object
undefined;
}
export function get12HourTimeFormat(time) { export function get12HourTimeFormat(time) {
// Check correct time format and split into components // Check correct time format and split into components
time = time.toString().match(/^([01]\d|2[0-3])(:)([0-5]\d)?$/) || [time]; time = time.toString().match(/^([01]\d|2[0-3])(:)([0-5]\d)?$/) || [time];
@ -112,7 +96,7 @@ export function shortTimeString(date) {
// Use a ternary operator to check if the input is a valid date object // Use a ternary operator to check if the input is a valid date object
return date instanceof Date return date instanceof Date
? // Use the built-in method toLocaleTimeString() to get the short time string in the current locale ? // Use the built-in method toLocaleTimeString() to get the short time string in the current locale
date.toLocaleTimeString(undefined, { hour: "numeric", minute: "numeric", hour12: true }) date.toLocaleTimeString("en-us", { hour: "numeric", minute: "numeric", hour12: true })
: // Return undefined if the input is not a valid date object : // Return undefined if the input is not a valid date object
undefined; undefined;
} }

View file

@ -1,6 +1,4 @@
import { import {
getFullDayName,
getFullMonthName,
get12HourTimeFormat, get12HourTimeFormat,
get12HourTimeMobileFormat, get12HourTimeMobileFormat,
getDateFormat, getDateFormat,
@ -52,33 +50,17 @@ describe("date-helper.js", () => {
} }
}); });
// it("getFullMonthName should return full month format.", () => { it("getDateFormat should return date in the given format.", () => {
// // Arrange / Act // Arrange / Act
// const date = new Date("2023-10-01"); const dateString = "2023-10-01";
// const monthName = getFullMonthName(date); const dateParts = dateString.split("-");
const date = new Date(dateParts[0], parseInt(dateParts[1]) - 1, dateParts[2]);
const format = "yyyy-MM-dd";
const formattedDate = getDateFormat(date, format);
// // Assert // Assert
// expect(monthName).toEqual("October"); expect(formattedDate).toEqual("2023-10-01");
// }); });
// it("getFullDayName should return full Day Name format.", () => {
// // Arrange / Act
// const date = new Date("2023-10-01");
// const dayName = getFullDayName(date);
// // Assert
// expect(dayName).toEqual("Sunday");
// });
// it("getDateFormat should return date in the given format.", () => {
// // Arrange / Act
// const date = new Date("2023-10-01");
// const format = "yyyy-MM-dd";
// const formattedDate = getDateFormat(date, format);
// // Assert
// expect(formattedDate).toEqual("2023-10-01");
// });
it("should return the correct difference in days", function () { it("should return the correct difference in days", function () {
// Define some sample dates and their expected differences // Define some sample dates and their expected differences

View file

@ -77,9 +77,9 @@ describe("Add-to-calendar methods...", () => {
//Assert //Assert
expect(wrapper.vm.$refs.calendarModalQuestion.openModal).toBeCalled(); expect(wrapper.vm.$refs.calendarModalQuestion.openModal).toBeCalled();
}); });
test("serviceType should return 'replacement and recalibration' when isRepair and funnelHasRecalibrationPart true.", () => { test("serviceType should return 'replacement and recalibration' when isRepair is false and funnelHasRecalibrationPart true.", () => {
//Arrange //Arrange
store.getters.order.damage.isRepair = true; store.getters.order.damage.isRepair = false;
store.getters.funnelHasRecalibrationPart = true; store.getters.funnelHasRecalibrationPart = true;
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
@ -89,9 +89,9 @@ describe("Add-to-calendar methods...", () => {
//Assert //Assert
expect(testValue).toEqual("replacement and recalibration"); expect(testValue).toEqual("replacement and recalibration");
}); });
test("serviceType should return 'replacement' when isRepair is true and funnelHasRecalibrationPart is false.", () => { test("serviceType should return 'replacement' when isRepair is false and funnelHasRecalibrationPart is false.", () => {
//Arrange //Arrange
store.getters.order.damage.isRepair = true; store.getters.order.damage.isRepair = false;
store.getters.funnelHasRecalibrationPart = false; store.getters.funnelHasRecalibrationPart = false;
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
@ -101,9 +101,9 @@ describe("Add-to-calendar methods...", () => {
//Assert //Assert
expect(testValue).toEqual("replacement"); expect(testValue).toEqual("replacement");
}); });
test("serviceType should return 'repair' when isRepair is false.", () => { test("serviceType should return 'repair' when isRepair is true.", () => {
//Arrange //Arrange
store.getters.order.damage.isRepair = false; store.getters.order.damage.isRepair = true;
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
//Act //Act
@ -126,7 +126,7 @@ describe("Add-to-calendar methods...", () => {
test("getappointmentData should return expected model value for appointmentType mobile.", () => { test("getappointmentData should return expected model value for appointmentType mobile.", () => {
//Arrange //Arrange
store.getters.order.serviceLocation.appointmentType = AppointmentTypeStrings.MOBILE; store.getters.order.serviceLocation.appointmentType = AppointmentTypeStrings.MOBILE;
store.getters.order.damage.isRepair = true; store.getters.order.damage.isRepair = false;
store.getters.funnelHasRecalibrationPart = true; store.getters.funnelHasRecalibrationPart = true;
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
@ -176,7 +176,7 @@ describe("Add-to-calendar methods...", () => {
test("getappointmentData should return expected model value for appointmentType DROP_OFF.", () => { test("getappointmentData should return expected model value for appointmentType DROP_OFF.", () => {
//Arrange //Arrange
store.getters.order.serviceLocation.appointmentType = AppointmentTypeStrings.DROP_OFF; store.getters.order.serviceLocation.appointmentType = AppointmentTypeStrings.DROP_OFF;
store.getters.order.damage.isRepair = false; store.getters.order.damage.isRepair = true;
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
//Act //Act
@ -188,7 +188,7 @@ describe("Add-to-calendar methods...", () => {
test("getappointmentData should return expected model value for appointmentType IN_SHOP.", () => { test("getappointmentData should return expected model value for appointmentType IN_SHOP.", () => {
//Arrange //Arrange
store.getters.order.serviceLocation.appointmentType = AppointmentTypeStrings.IN_SHOP; store.getters.order.serviceLocation.appointmentType = AppointmentTypeStrings.IN_SHOP;
store.getters.order.damage.isRepair = false; store.getters.order.damage.isRepair = true;
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
//Act //Act
@ -239,12 +239,6 @@ function setupMocks({ customMountOptions }) {
}); });
const wrapper = shallowMount(addToCalendar, mountOptions); const wrapper = shallowMount(addToCalendar, 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.calendarModalQuestion.openModal = jest.fn(); wrapper.vm.$refs.calendarModalQuestion.openModal = jest.fn();
return { wrapper }; return { wrapper };
} }

View file

@ -32,6 +32,7 @@ import {
import { getCalendarFile, download } from "@/helpers/add-to-calendar-helper"; import { getCalendarFile, download } from "@/helpers/add-to-calendar-helper";
import { AppointmentTypeStrings, RouteCodeFlags } from "@/constants/schedule-constants"; import { AppointmentTypeStrings, RouteCodeFlags } from "@/constants/schedule-constants";
import store from "@/store"; import store from "@/store";
import { serviceType } from "@/constants/service-type";
export default { export default {
name: "add-to-calendar", name: "add-to-calendar",
@ -127,14 +128,14 @@ export default {
serviceType() { serviceType() {
const isRepair = store.getters.order.damage.isRepair; const isRepair = store.getters.order.damage.isRepair;
const funnelHasRecalibrationPart = store.getters.funnelHasRecalibrationPart; const funnelHasRecalibrationPart = store.getters.funnelHasRecalibrationPart;
if (isRepair) { if (!isRepair) {
if (funnelHasRecalibrationPart) { if (funnelHasRecalibrationPart) {
return "replacement and recalibration"; return serviceType.REPLACEMENT_AND_RECALIBRATION;
} else { } else {
return "replacement"; return serviceType.REPLACEMENT;
} }
} }
return "repair"; return serviceType.REPAIR;
}, },
routeCode() { routeCode() {
return store.getters.order.schedule.routeCode; return store.getters.order.schedule.routeCode;

View file

@ -11,7 +11,7 @@
<funnelHeader cmsWidgetName="FunnelHeaderWidget" ref="funnelHeader" /> <funnelHeader cmsWidgetName="FunnelHeaderWidget" ref="funnelHeader" />
</div> </div>
</div> </div>
<div class="row justify-content-md-center"> <div class="row justify-content-center">
<div class="col-md-6 col-xl-4"> <div class="col-md-6 col-xl-4">
<vehicleBanner <vehicleBanner
cmsWidgetName="VehicleBannerWidget" cmsWidgetName="VehicleBannerWidget"

View file

@ -6,7 +6,7 @@
<funnelHeader cmsWidgetName="FunnelHeaderWidget" /> <funnelHeader cmsWidgetName="FunnelHeaderWidget" />
</div> </div>
</div> </div>
<div class="row justify-content-md-center"> <div class="row justify-content-center">
<div class="col-md-6 col-xl-4"> <div class="col-md-6 col-xl-4">
<vehicleBanner <vehicleBanner
cmsWidgetName="VehicleBannerWidget" cmsWidgetName="VehicleBannerWidget"

View file

@ -4,7 +4,7 @@
<funnelHeader cmsWidgetName="FunnelHeaderWidget" /> <funnelHeader cmsWidgetName="FunnelHeaderWidget" />
<div> <div>
<div class="container-fluid pb-2"> <div class="container-fluid pb-2">
<div class="row justify-content-md-center"> <div class="row justify-content-center">
<div class="col-md-6 col-xl-4"> <div class="col-md-6 col-xl-4">
<div class="header-container"> <div class="header-container">
<img :src="ScheduleConfirmationImage" /> <img :src="ScheduleConfirmationImage" />
@ -55,15 +55,11 @@ import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import { AppointmentTypeStrings } from "@/constants/schedule-constants"; import { AppointmentTypeStrings } from "@/constants/schedule-constants";
import { settleAllPromises } from "@/helpers/layout-helper"; import { settleAllPromises } from "@/helpers/layout-helper";
import { applicationConfig } from "@/constants/application-config.js"; import { applicationConfig } from "@/constants/application-config.js";
import { convertDateStringToDate } from "@/layouts/schedule/helpers/schedule-helper";
import { Form } from "vee-validate"; import { Form } from "vee-validate";
import store from "@/store"; import store from "@/store";
import { import { get12HourTimeFormat, get12HourTimeMobileFormat } from "@/helpers/date-helper";
getFullDayName,
getFullMonthName,
get12HourTimeFormat,
get12HourTimeMobileFormat,
} from "@/helpers/date-helper";
export default { export default {
name: "confirmation", name: "confirmation",
async beforeRouteEnter(to, from, next) { async beforeRouteEnter(to, from, next) {
@ -174,10 +170,14 @@ export default {
return `${this.ProviderAddress},<br/> ${this.ProviderCity}, ${this.ProviderState} ${this.ProviderZipCode}`; return `${this.ProviderAddress},<br/> ${this.ProviderCity}, ${this.ProviderState} ${this.ProviderZipCode}`;
}, },
ScheduleDateFormatted() { ScheduleDateFormatted() {
const scheduleDate = new Date(this.ScheduleDate); // This conversion ensures we don't get get GMT induced date changes
return `${getFullDayName(scheduleDate)}, ${getFullMonthName( const dateObject = convertDateStringToDate(this.ScheduleDate);
scheduleDate // Ex: Tuesday, April 22
)} ${scheduleDate.getDate()}`; return dateObject.toLocaleDateString("en-us", {
weekday: "long",
month: "long",
day: "numeric",
});
}, },
ScheduleTimeFormatted() { ScheduleTimeFormatted() {
if (this.AppointmentType == AppointmentTypeStrings.MOBILE) { if (this.AppointmentType == AppointmentTypeStrings.MOBILE) {

View file

@ -6,7 +6,7 @@
<funnelHeader cmsWidgetName="FunnelHeaderWidget" /> <funnelHeader cmsWidgetName="FunnelHeaderWidget" />
</div> </div>
</div> </div>
<div class="row justify-content-md-center"> <div class="row justify-content-center">
<div class="col-md-6 col-xl-4"> <div class="col-md-6 col-xl-4">
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" class="my-5" /> <funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" class="my-5" />
<textboxQuestion <textboxQuestion

View file

@ -7,7 +7,7 @@
<funnelHeader cmsWidgetName="FunnelHeaderWidget" /> <funnelHeader cmsWidgetName="FunnelHeaderWidget" />
</div> </div>
</div> </div>
<div class="row justify-content-md-center"> <div class="row justify-content-center">
<div class="col-md-6 col-xl-4"> <div class="col-md-6 col-xl-4">
<vehicleBanner cmsWidgetName="VehicleBannerWidget" /> <vehicleBanner cmsWidgetName="VehicleBannerWidget" />
<funnelSubHeader class="pb-4" cmsWidgetName="FunnelSubHeaderWidget" /> <funnelSubHeader class="pb-4" cmsWidgetName="FunnelSubHeaderWidget" />
@ -33,12 +33,13 @@
<div v-else> <div v-else>
<alert <alert
class="my-4" class="mt-4 mb-4"
:cmsWidgetName="alertInfo" :cmsWidgetName="alertInfo"
alertClass="alert-info" alertClass="alert-info"
v-bind:isDismissible="false" /> v-bind:isDismissible="false" />
<textboxQuestion <textboxQuestion
class="mb-4"
cmsWidgetName="ServiceZipQuestionWidget" cmsWidgetName="ServiceZipQuestionWidget"
v-model="serviceZipCode" v-model="serviceZipCode"
inputId="serviceZipCode" inputId="serviceZipCode"
@ -47,6 +48,7 @@
validationRules="zip-required|zip-format" /> validationRules="zip-required|zip-format" />
<textboxQuestion <textboxQuestion
class="mb-0"
cmsWidgetName="EmailAddressQuestionWidget" cmsWidgetName="EmailAddressQuestionWidget"
v-model="emailAddress" v-model="emailAddress"
inputId="emailAddress" inputId="emailAddress"

View file

@ -6,7 +6,7 @@
<funnelHeader cmsWidgetName="FunnelHeaderWidget" /> <funnelHeader cmsWidgetName="FunnelHeaderWidget" />
</div> </div>
</div> </div>
<div class="row justify-content-md-center"> <div class="row justify-content-center">
<div class="col-md-6 col-xl-4"> <div class="col-md-6 col-xl-4">
<vehicleBanner <vehicleBanner
cmsWidgetName="VehicleBannerWidget" cmsWidgetName="VehicleBannerWidget"

View file

@ -7,7 +7,7 @@
<funnelHeader cmsWidgetName="FunnelHeaderWidget" /> <funnelHeader cmsWidgetName="FunnelHeaderWidget" />
</div> </div>
</div> </div>
<div class="row justify-content-md-center"> <div class="row justify-content-center">
<div class="col-md-6 col-xl-4"> <div class="col-md-6 col-xl-4">
<vehicleBanner <vehicleBanner
cmsWidgetName="VehicleBannerWidget" cmsWidgetName="VehicleBannerWidget"
@ -47,7 +47,6 @@
vapsTilesCmsName="VapsProductTiles" vapsTilesCmsName="VapsProductTiles"
v-on="{ 'buttonEvent.openModal': openModal }" /> v-on="{ 'buttonEvent.openModal': openModal }" />
<button @click="openModal('RainDefenseModal')">OPEN MODAL</button>
<contentGroupModal ref="RainDefenseModal" cmsWidgetName="RainDefenseModal" /> <contentGroupModal ref="RainDefenseModal" cmsWidgetName="RainDefenseModal" />
<paymentMethodQuestion <paymentMethodQuestion
v-if="!isPiaDisabled" v-if="!isPiaDisabled"

View file

@ -7,7 +7,7 @@
<funnelHeader cmsWidgetName="FunnelHeaderWidget" /> <funnelHeader cmsWidgetName="FunnelHeaderWidget" />
</div> </div>
</div> </div>
<div class="row justify-content-md-center"> <div class="row justify-content-center">
<div class="col-md-6 col-xl-4"> <div class="col-md-6 col-xl-4">
<vehicleBanner <vehicleBanner
cmsWidgetName="VehicleBannerWidget" cmsWidgetName="VehicleBannerWidget"

View file

@ -7,7 +7,7 @@
<funnelHeader cmsWidgetName="FunnelHeaderWidget" /> <funnelHeader cmsWidgetName="FunnelHeaderWidget" />
</div> </div>
</div> </div>
<div class="row justify-content-md-center"> <div class="row justify-content-center">
<div class="col-md-6 col-xl-4"> <div class="col-md-6 col-xl-4">
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" class="mt-5" /> <funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" class="mt-5" />
<template v-if="ChangeShopLink.length"> <template v-if="ChangeShopLink.length">

View file

@ -46,6 +46,18 @@ export async function getServiceabilityDetails(serviceZipCode, lineItems, pageNa
return Promise.resolve(serviceabilityDetails); return Promise.resolve(serviceabilityDetails);
} }
export async function getShopProviderData(serviceZipCode) {
const shopProviderData = await baseMixin.methods.dispatchStoreActionWithLogging(
storeActions.GET_PROVIDERS,
{
serviceZipCode: serviceZipCode,
},
"service-location"
);
return Promise.resolve(shopProviderData);
}
export async function getAvailabilityRating( export async function getAvailabilityRating(
startDate, startDate,
endDate, endDate,

View file

@ -145,9 +145,6 @@ export default {
alertInvalidZipWidgetName: String, alertInvalidZipWidgetName: String,
customComponentId: String, customComponentId: String,
validationRules: String, validationRules: String,
onZipUpdateCallback: {
type: Function,
},
}, },
computed: { computed: {
mobileLocationLinkPromptText() { mobileLocationLinkPromptText() {
@ -256,10 +253,7 @@ export default {
this.$emit("updated-serviceability", serviceabilityDetails.data); this.$emit("updated-serviceability", serviceabilityDetails.data);
this.$emit("updated-contains-military-base", zipCodeData.containsMilitaryBase); this.$emit("updated-contains-military-base", zipCodeData.containsMilitaryBase);
if (this.onZipUpdateCallback) { // update the page level model
await this.onZipUpdateCallback(serviceZipCode);
}
// Update the page level model
this.$emit("update:modelValue", this.internalModel); this.$emit("update:modelValue", this.internalModel);
this.closeModal(); this.closeModal();

View file

@ -7,10 +7,7 @@ import { getMountOptions } from "@/helpers/unit-test-helper";
import store from "@/store"; import store from "@/store";
import baseMixin from "@/mixins/base-mixin"; import baseMixin from "@/mixins/base-mixin";
import { import { getServiceabilityDetails } from "@/layouts/service-location/helpers/service-location-helper/service-location-helper";
getServiceabilityDetails,
Provider,
} from "@/layouts/service-location/helpers/service-location-helper/service-location-helper";
// Define Mocks // Define Mocks
jest.mock("@/helpers/cms-content-helper", () => ({ jest.mock("@/helpers/cms-content-helper", () => ({
@ -21,7 +18,7 @@ jest.mock("@/helpers/cms-content-helper", () => ({
jest.mock("./classes/provider"); jest.mock("./classes/provider");
const mockGetPricedMobileFeePart = (mockServiceZipCode) => { let mockGetPricedMobileFeePart = (mockServiceZipCode) => {
let mobileFeePart = {}; let mobileFeePart = {};
if (mockServiceZipCode === "43235") { if (mockServiceZipCode === "43235") {
@ -38,7 +35,7 @@ const mockGetPricedMobileFeePart = (mockServiceZipCode) => {
return Promise.resolve(mobileFeePart); return Promise.resolve(mobileFeePart);
}; };
const mockGetServiceabilityDetails = (mockServiceZipCode) => { let mockGetServiceabilityDetails = (mockServiceZipCode) => {
const serviceabilityDetails = { const serviceabilityDetails = {
isGlassServiceableInshop: true, isGlassServiceableInshop: true,
isRecalibrationServiceableInshop: true, isRecalibrationServiceableInshop: true,
@ -49,6 +46,17 @@ const mockGetServiceabilityDetails = (mockServiceZipCode) => {
return Promise.resolve(serviceabilityDetails); return Promise.resolve(serviceabilityDetails);
}; };
let mockGetShopProviderData = (mockServiceZipCode) => {
const result = {
data: {
mobileProviderNumber: "001820",
shopProviders: [{}],
},
};
return Promise.resolve(result);
};
jest.mock( jest.mock(
"@/layouts/service-location/helpers/service-location-helper/service-location-helper", "@/layouts/service-location/helpers/service-location-helper/service-location-helper",
() => ({ () => ({
@ -58,6 +66,9 @@ jest.mock(
getServiceabilityDetails: jest.fn((mockServiceZipCode) => { getServiceabilityDetails: jest.fn((mockServiceZipCode) => {
return mockGetServiceabilityDetails(mockServiceZipCode); return mockGetServiceabilityDetails(mockServiceZipCode);
}), }),
getShopProviderData: jest.fn((mockServiceZipCode) => {
return mockGetShopProviderData(mockServiceZipCode);
}),
}) })
); );
@ -554,14 +565,13 @@ describe("service-location.vue", () => {
describe("should be logical AND when recalibration is defined.", () => { describe("should be logical AND when recalibration is defined.", () => {
test("T & T => T", async () => { test("T & T => T", async () => {
// Arrange // Arrange
getServiceabilityDetails.mockImplementation(() => mockGetServiceabilityDetails = () =>
Promise.resolve({ Promise.resolve({
isGlassServiceableInshop: true, isGlassServiceableInshop: true,
isRecalibrationServiceableInshop: true, isRecalibrationServiceableInshop: true,
isGlassServiceableMobile: true, isGlassServiceableMobile: true,
isRecalibrationServiceableMobile: true, isRecalibrationServiceableMobile: true,
}) });
);
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
@ -582,14 +592,13 @@ describe("service-location.vue", () => {
test("T & F => F", async () => { test("T & F => F", async () => {
// Arrange // Arrange
getServiceabilityDetails.mockImplementation(() => mockGetServiceabilityDetails = () =>
Promise.resolve({ Promise.resolve({
isGlassServiceableInshop: true, isGlassServiceableInshop: true,
isRecalibrationServiceableInshop: false, isRecalibrationServiceableInshop: false,
isGlassServiceableMobile: true, isGlassServiceableMobile: true,
isRecalibrationServiceableMobile: false, isRecalibrationServiceableMobile: false,
}) });
);
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
@ -610,14 +619,13 @@ describe("service-location.vue", () => {
test("F & T => F", async () => { test("F & T => F", async () => {
// Arrange // Arrange
getServiceabilityDetails.mockImplementation(() => mockGetServiceabilityDetails = () =>
Promise.resolve({ Promise.resolve({
isGlassServiceableInshop: false, isGlassServiceableInshop: false,
isRecalibrationServiceableInshop: true, isRecalibrationServiceableInshop: true,
isGlassServiceableMobile: false, isGlassServiceableMobile: false,
isRecalibrationServiceableMobile: true, isRecalibrationServiceableMobile: true,
}) });
);
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
@ -638,14 +646,13 @@ describe("service-location.vue", () => {
test("F & F => F", async () => { test("F & F => F", async () => {
// Arrange // Arrange
getServiceabilityDetails.mockImplementation(() => mockGetServiceabilityDetails = () =>
Promise.resolve({ Promise.resolve({
isGlassServiceableInshop: false, isGlassServiceableInshop: false,
isRecalibrationServiceableInshop: false, isRecalibrationServiceableInshop: false,
isGlassServiceableMobile: false, isGlassServiceableMobile: false,
isRecalibrationServiceableMobile: false, isRecalibrationServiceableMobile: false,
}) });
);
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
@ -666,14 +673,13 @@ describe("service-location.vue", () => {
test("displayServiceableMobileOnly should be true if mobile is true and inshop is false.", async () => { test("displayServiceableMobileOnly should be true if mobile is true and inshop is false.", async () => {
// Arrange // Arrange
getServiceabilityDetails.mockImplementation(() => mockGetServiceabilityDetails = () =>
Promise.resolve({ Promise.resolve({
isGlassServiceableInshop: false, isGlassServiceableInshop: false,
isRecalibrationServiceableInshop: false, isRecalibrationServiceableInshop: false,
isGlassServiceableMobile: true, isGlassServiceableMobile: true,
isRecalibrationServiceableMobile: true, isRecalibrationServiceableMobile: true,
}) });
);
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
@ -695,14 +701,13 @@ describe("service-location.vue", () => {
test("displayServiceableMobileOnly should be false if mobile is false.", async () => { test("displayServiceableMobileOnly should be false if mobile is false.", async () => {
// Arrange // Arrange
getServiceabilityDetails.mockImplementation(() => mockGetServiceabilityDetails = () =>
Promise.resolve({ Promise.resolve({
isGlassServiceableInshop: false, isGlassServiceableInshop: false,
isRecalibrationServiceableInshop: false, isRecalibrationServiceableInshop: false,
isGlassServiceableMobile: false, isGlassServiceableMobile: false,
isRecalibrationServiceableMobile: false, isRecalibrationServiceableMobile: false,
}) });
);
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
@ -724,14 +729,13 @@ describe("service-location.vue", () => {
test("displayServiceableMobileOnly should be false if inShop is true", async () => { test("displayServiceableMobileOnly should be false if inShop is true", async () => {
// Arrange // Arrange
getServiceabilityDetails.mockImplementation(() => mockGetServiceabilityDetails = () =>
Promise.resolve({ Promise.resolve({
isGlassServiceableInshop: true, isGlassServiceableInshop: true,
isRecalibrationServiceableInshop: true, isRecalibrationServiceableInshop: true,
isGlassServiceableMobile: true, isGlassServiceableMobile: true,
isRecalibrationServiceableMobile: true, isRecalibrationServiceableMobile: true,
}) });
);
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
@ -753,14 +757,13 @@ describe("service-location.vue", () => {
test("displayNoShopsAlert should be true if inShop is false and mobile is false", async () => { test("displayNoShopsAlert should be true if inShop is false and mobile is false", async () => {
// Arrange // Arrange
getServiceabilityDetails.mockImplementation(() => mockGetServiceabilityDetails = () =>
Promise.resolve({ Promise.resolve({
isGlassServiceableInshop: false, isGlassServiceableInshop: false,
isRecalibrationServiceableInshop: false, isRecalibrationServiceableInshop: false,
isGlassServiceableMobile: false, isGlassServiceableMobile: false,
isRecalibrationServiceableMobile: false, isRecalibrationServiceableMobile: false,
}) });
);
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
@ -782,14 +785,13 @@ describe("service-location.vue", () => {
test("displayNoShopsAlert should be false if inShop is true and mobile is true", async () => { test("displayNoShopsAlert should be false if inShop is true and mobile is true", async () => {
// Arrange // Arrange
getServiceabilityDetails.mockImplementation(() => mockGetServiceabilityDetails = () =>
Promise.resolve({ Promise.resolve({
isGlassServiceableInshop: true, isGlassServiceableInshop: true,
isRecalibrationServiceableInshop: true, isRecalibrationServiceableInshop: true,
isGlassServiceableMobile: true, isGlassServiceableMobile: true,
isRecalibrationServiceableMobile: true, isRecalibrationServiceableMobile: true,
}) });
);
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
@ -811,14 +813,13 @@ describe("service-location.vue", () => {
test("displayNoShopsAlert should be false if inShop is true", async () => { test("displayNoShopsAlert should be false if inShop is true", async () => {
// Arrange // Arrange
getServiceabilityDetails.mockImplementation(() => mockGetServiceabilityDetails = () =>
Promise.resolve({ Promise.resolve({
isGlassServiceableInshop: true, isGlassServiceableInshop: true,
isRecalibrationServiceableInshop: true, isRecalibrationServiceableInshop: true,
isGlassServiceableMobile: false, isGlassServiceableMobile: false,
isRecalibrationServiceableMobile: false, isRecalibrationServiceableMobile: false,
}) });
);
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
@ -840,14 +841,13 @@ describe("service-location.vue", () => {
test("displayNoShopsAlert should be false if mobile is true", async () => { test("displayNoShopsAlert should be false if mobile is true", async () => {
// Arrange // Arrange
getServiceabilityDetails.mockImplementation(() => mockGetServiceabilityDetails = () =>
Promise.resolve({ Promise.resolve({
isGlassServiceableInshop: false, isGlassServiceableInshop: false,
isRecalibrationServiceableInshop: false, isRecalibrationServiceableInshop: false,
isGlassServiceableMobile: true, isGlassServiceableMobile: true,
isRecalibrationServiceableMobile: true, isRecalibrationServiceableMobile: true,
}) });
);
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
@ -869,14 +869,13 @@ describe("service-location.vue", () => {
test("displayServiceableInshopOnly should be true if inshop is true and mobile is false", async () => { test("displayServiceableInshopOnly should be true if inshop is true and mobile is false", async () => {
// Arrange // Arrange
getServiceabilityDetails.mockImplementation(() => mockGetServiceabilityDetails = () =>
Promise.resolve({ Promise.resolve({
isGlassServiceableInshop: true, isGlassServiceableInshop: true,
isRecalibrationServiceableInshop: true, isRecalibrationServiceableInshop: true,
isGlassServiceableMobile: false, isGlassServiceableMobile: false,
isRecalibrationServiceableMobile: false, isRecalibrationServiceableMobile: false,
}) });
);
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
@ -898,14 +897,13 @@ describe("service-location.vue", () => {
test("displayServiceableInshopOnly should be false if inshop is false", async () => { test("displayServiceableInshopOnly should be false if inshop is false", async () => {
// Arrange // Arrange
getServiceabilityDetails.mockImplementation(() => mockGetServiceabilityDetails = () =>
Promise.resolve({ Promise.resolve({
isGlassServiceableInshop: false, isGlassServiceableInshop: false,
isRecalibrationServiceableInshop: false, isRecalibrationServiceableInshop: false,
isGlassServiceableMobile: false, isGlassServiceableMobile: false,
isRecalibrationServiceableMobile: false, isRecalibrationServiceableMobile: false,
}) });
);
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
@ -927,14 +925,13 @@ describe("service-location.vue", () => {
test("displayServiceableInshopOnly should be false if mobile is true", async () => { test("displayServiceableInshopOnly should be false if mobile is true", async () => {
// Arrange // Arrange
getServiceabilityDetails.mockImplementation(() => mockGetServiceabilityDetails = () =>
Promise.resolve({ Promise.resolve({
isGlassServiceableInshop: true, isGlassServiceableInshop: true,
isRecalibrationServiceableInshop: true, isRecalibrationServiceableInshop: true,
isGlassServiceableMobile: true, isGlassServiceableMobile: true,
isRecalibrationServiceableMobile: true, isRecalibrationServiceableMobile: true,
}) });
);
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
@ -956,14 +953,13 @@ describe("service-location.vue", () => {
test("displayServiceableInshopOnly should be false in the dual/static recalibration scenario", async () => { test("displayServiceableInshopOnly should be false in the dual/static recalibration scenario", async () => {
// Arrange // Arrange
getServiceabilityDetails.mockImplementation(() => mockGetServiceabilityDetails = () =>
Promise.resolve({ Promise.resolve({
isGlassServiceableInshop: true, isGlassServiceableInshop: true,
isRecalibrationServiceableInshop: true, isRecalibrationServiceableInshop: true,
isGlassServiceableMobile: true, isGlassServiceableMobile: true,
isRecalibrationServiceableMobile: false, isRecalibrationServiceableMobile: false,
}) });
);
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});

View file

@ -7,7 +7,7 @@
<funnelHeader cmsWidgetName="FunnelHeaderWidget" /> <funnelHeader cmsWidgetName="FunnelHeaderWidget" />
</div> </div>
</div> </div>
<div class="row justify-content-md-center"> <div class="row justify-content-center">
<div class="col-md-6 col-xl-4"> <div class="col-md-6 col-xl-4">
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" class="mt-5" /> <funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" class="mt-5" />
@ -19,8 +19,7 @@
@updated-serviceability="setServiceabilityDetails" @updated-serviceability="setServiceabilityDetails"
@updated-contains-military-base="setContainsMilitaryBase" @updated-contains-military-base="setContainsMilitaryBase"
linkWidgetName="ServiceZipLinkWidget" linkWidgetName="ServiceZipLinkWidget"
modalWidgetName="ServiceZipModalWidget" modalWidgetName="ServiceZipModalWidget" />
:onZipUpdateCallback="reloadShopData" />
<alert <alert
ref="alertMilitaryBaseZip" ref="alertMilitaryBaseZip"
@ -59,7 +58,7 @@
alertClass="alert-warning" /> alertClass="alert-warning" />
</div> </div>
</div> </div>
<div class="row justify-content-md-center appointment-type"> <div class="row justify-content-center appointment-type">
<div class="col-md-6"> <div class="col-md-6">
<appointmentTypeQuestion <appointmentTypeQuestion
v-model="selectedAppointmentType" v-model="selectedAppointmentType"
@ -73,7 +72,7 @@
validationRules="option-required" /> validationRules="option-required" />
</div> </div>
</div> </div>
<div class="row justify-content-md-center"> <div class="row justify-content-center">
<div class="col-md-6 col-xl-4"> <div class="col-md-6 col-xl-4">
<mobileLocationModalQuestions <mobileLocationModalQuestions
customComponentId="mobileLocationQuestions" customComponentId="mobileLocationQuestions"
@ -86,16 +85,15 @@
validationRules="mobile-location-required" validationRules="mobile-location-required"
ref="mobileLocationQuestions" ref="mobileLocationQuestions"
linkWidgetName="MobileLocationLinkWidget" linkWidgetName="MobileLocationLinkWidget"
modalWidgetName="MobileLocationModalWidget" modalWidgetName="MobileLocationModalWidget" />
:onZipUpdateCallback="reloadShopData" />
<shopQuestion <shopQuestion
ref="shopQuestion" ref="shopQuestion"
v-show="isShopQuestionDisplayed" v-show="isShopQuestionDisplayed"
v-model="selectedProvider" v-model="selectedProvider"
:selectedAppointmentType="selectedAppointmentType" :selectedAppointmentType="selectedAppointmentType"
:shopProviderData="shopProviderData"
:isDisplayed="isShopQuestionDisplayed" :isDisplayed="isShopQuestionDisplayed"
@updated-shop-list="setUpdatedShopList"
cmsWidgetName="ShopQuestionWidget" /> cmsWidgetName="ShopQuestionWidget" />
<contentGroupModal ref="RecalModal" cmsWidgetName="RecalModal" /> <contentGroupModal ref="RecalModal" cmsWidgetName="RecalModal" />
@ -135,6 +133,7 @@ import { settleAllPromises } from "@/helpers/layout-helper";
import { import {
getPricedMobileFeePart, getPricedMobileFeePart,
getServiceabilityDetails, getServiceabilityDetails,
getShopProviderData,
} from "@/layouts/service-location/helpers/service-location-helper/service-location-helper"; } from "@/layouts/service-location/helpers/service-location-helper/service-location-helper";
import { Provider } from "@/layouts/service-location/classes/provider"; import { Provider } from "@/layouts/service-location/classes/provider";
@ -180,7 +179,7 @@ export default {
mobileFeePart: null, mobileFeePart: null,
zipContainsMilitaryBase: false, zipContainsMilitaryBase: false,
zipCodeCtu: null, zipCodeCtu: null,
providerData: null, shopProviderData: null,
}; };
}, },
async beforeRouteEnter(to, from, next) { async beforeRouteEnter(to, from, next) {
@ -188,7 +187,10 @@ export default {
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage); const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
const serviceZipCode = store.getters.order.serviceLocation.zipCode; const serviceZipCode = store.getters.order.serviceLocation.zipCode;
const getZipCodeData = baseMixin.methods.getZipCodeData(serviceZipCode, "service-location"); const zipCodeDataPromise = baseMixin.methods.getZipCodeData(
serviceZipCode,
"service-location"
);
const serviceabilityDetailsPromise = getServiceabilityDetails( const serviceabilityDetailsPromise = getServiceabilityDetails(
serviceZipCode, serviceZipCode,
@ -198,7 +200,7 @@ export default {
const mobileFeePartPromise = getPricedMobileFeePart(serviceZipCode, "service-location"); const mobileFeePartPromise = getPricedMobileFeePart(serviceZipCode, "service-location");
const shopQuestionInitialDataPromise = shopQuestion.methods.loadInitialData(serviceZipCode); const shopProviderDataPromise = getShopProviderData(serviceZipCode); // shopQuestion.methods.loadInitialData(serviceZipCode);
// Settle promises and get results // Settle promises and get results
const promiseResultMap = [ const promiseResultMap = [
@ -206,6 +208,10 @@ export default {
resultKey: "cmsContent", resultKey: "cmsContent",
promise: cmsContentPromise, promise: cmsContentPromise,
}, },
{
resultKey: "zipCodeData",
promise: zipCodeDataPromise,
},
{ {
resultKey: "mobileFeePart", resultKey: "mobileFeePart",
promise: mobileFeePartPromise, promise: mobileFeePartPromise,
@ -215,12 +221,8 @@ export default {
promise: serviceabilityDetailsPromise, promise: serviceabilityDetailsPromise,
}, },
{ {
resultKey: "zipCodeData", resultKey: "shopProviderData",
promise: getZipCodeData, promise: shopProviderDataPromise,
},
{
resultKey: "shopQuestionInitialData",
promise: shopQuestionInitialDataPromise,
}, },
]; ];
@ -232,14 +234,9 @@ export default {
vm.setData( vm.setData(
resultMap.zipCodeData, resultMap.zipCodeData,
resultMap.serviceabilityDetails, resultMap.serviceabilityDetails,
resultMap.mobileFeePart resultMap.mobileFeePart,
resultMap.shopProviderData
); );
// Initialize the Shop Question component
vm.$refs.shopQuestion.initializeComponent(resultMap.shopQuestionInitialData);
// Initialize the page level shop data
vm.providerData = resultMap.shopQuestionInitialData;
}); });
}, },
computed: { computed: {
@ -254,7 +251,7 @@ export default {
if (newValue.zipCode !== this.zipCode) { if (newValue.zipCode !== this.zipCode) {
this.resetMobileLocation(); this.resetMobileLocation();
this.selectedAppointmentType = null; this.selectedAppointmentType = null;
this.selectedProvider = null; this.selectedProvider = new Provider();
} }
this.state = newValue.state; this.state = newValue.state;
@ -277,20 +274,16 @@ export default {
}; };
}, },
set: function (newValue) { set: function (newValue) {
this.streetAddress = newValue.addressQuestions.streetAddress; if (newValue.addressQuestions.zipCode !== this.zipCode) {
this.apartmentNumberOrBusinessName = getShopProviderData(newValue.addressQuestions.zipCode).then((result) => {
newValue.addressQuestions.apartmentNumberOrBusinessName; this.shopProviderData = result.data;
this.city = newValue.addressQuestions.city; this.selectedProvider = new Provider(
this.state = newValue.addressQuestions.state; this.shopProviderData.mobileProviderNumber
this.zipCode = newValue.addressQuestions.zipCode; );
this.isVehicleProtected = newValue.isVehicleProtected; });
if (newValue.zipCode !== this.zipCode) {
if (!this.selectedAppointmentType == "Mobile") {
this.selectedAppointmentType = null;
}
this.selectedProvider = null;
} }
this.setMobileLocation(newValue);
}, },
}, },
isServiceableMobile() { isServiceableMobile() {
@ -355,7 +348,7 @@ export default {
store.getters.payment.isInsurance !== null store.getters.payment.isInsurance !== null
); );
}, },
setData(zipCodeData, serviceabilityDetails, mobileFeePart) { setData(zipCodeData, serviceabilityDetails, mobileFeePart, shopProviderData) {
if (zipCodeData) { if (zipCodeData) {
this.zipContainsMilitaryBase = zipCodeData.containsMilitaryBase; this.zipContainsMilitaryBase = zipCodeData.containsMilitaryBase;
this.zipCodeCtu = zipCodeData.zipCodeCtu; this.zipCodeCtu = zipCodeData.zipCodeCtu;
@ -368,6 +361,10 @@ export default {
if (mobileFeePart) { if (mobileFeePart) {
this.mobileFeePart = mobileFeePart; this.mobileFeePart = mobileFeePart;
} }
if (shopProviderData) {
this.shopProviderData = shopProviderData;
}
}, },
setContainsMilitaryBase(val) { setContainsMilitaryBase(val) {
if (this.zipContainsMilitaryBase !== val) { if (this.zipContainsMilitaryBase !== val) {
@ -416,12 +413,18 @@ export default {
this.isRecalibrationServiceableMobile = this.isRecalibrationServiceableMobile =
serviceabilityDetails.isRecalibrationServiceableMobile; serviceabilityDetails.isRecalibrationServiceableMobile;
}, },
setMobileLocation(mobileLocation) {
this.streetAddress = mobileLocation.addressQuestions.streetAddress;
this.apartmentNumberOrBusinessName =
mobileLocation.addressQuestions.apartmentNumberOrBusinessName;
this.city = mobileLocation.addressQuestions.city;
this.state = mobileLocation.addressQuestions.state;
this.zipCode = mobileLocation.addressQuestions.zipCode;
this.isVehicleProtected = mobileLocation.isVehicleProtected;
},
async reloadShopData(zipCode) { async reloadShopData(zipCode) {
await this.$refs.shopQuestion.reloadShopData(zipCode); await this.$refs.shopQuestion.reloadShopData(zipCode);
}, },
setUpdatedShopList(providerData) {
this.providerData = providerData;
},
openRecalibrationInformationModal() { openRecalibrationInformationModal() {
this.recalibrationInformationModal.openModal(); this.recalibrationInformationModal.openModal();
}, },
@ -494,21 +497,20 @@ export default {
this.updateAndSaveSupportingItems(); this.updateAndSaveSupportingItems();
this.$router.navigateWithSaving(this.navigationScenarios.CLICKED_FORWARD, this.$route); this.$router.navigateWithSaving(this.navigationScenarios.CLICKED_FORWARD, this.$route);
}, },
openModalAction(modalName) {
this.$refs[modalName].openModal();
},
}, },
watch: { watch: {
selectedAppointmentType: { selectedAppointmentType: {
handler(newValue) { async handler(newValue) {
if (newValue === "Mobile") { if (newValue === "Mobile") {
this.selectedProvider = new Provider(this.providerData.mobileProviderNumber); getShopProviderData(this.zipCode).then(async (result) => {
} else { this.shopProviderData = result.data;
this.selectedProvider = new Provider(); this.selectedProvider = new Provider(
} this.shopProviderData.mobileProviderNumber
}, );
}, });
providerData: {
handler(newValue) {
if (newValue === "Mobile") {
this.selectedProvider = new Provider(this.providerData.mobileProviderNumber);
} else { } else {
this.selectedProvider = new Provider(); this.selectedProvider = new Provider();
} }
@ -535,7 +537,6 @@ export default {
.appointment-type-question { .appointment-type-question {
.button-question { .button-question {
.col { .col {
padding: 0 0.5rem;
@include media-breakpoint-up(md) { @include media-breakpoint-up(md) {
padding: 0 0.75rem; padding: 0 0.75rem;
} }

View file

@ -68,9 +68,6 @@ export default {
}, },
linkWidgetName: String, linkWidgetName: String,
modalWidgetName: String, modalWidgetName: String,
onZipUpdateCallback: {
type: Function,
},
}, },
computed: { computed: {
serviceZipLinkText() { serviceZipLinkText() {
@ -164,13 +161,9 @@ export default {
this.$emit("updated-serviceability", serviceabilityDetails.data); this.$emit("updated-serviceability", serviceabilityDetails.data);
this.$emit("updated-contains-military-base", zipCodeData.containsMilitaryBase); this.$emit("updated-contains-military-base", zipCodeData.containsMilitaryBase);
// Update the page level model // update the page level model
this.$emit("update:modelValue", this.internalModel); this.$emit("update:modelValue", this.internalModel);
if (this.onZipUpdateCallback) {
await this.onZipUpdateCallback(serviceZipCode);
}
this.closeModal(); this.closeModal();
} }
} else { } else {

View file

@ -61,6 +61,7 @@ const mockCmsContent = {
const cmsWidgetName = "ShopQuestionWidget"; const cmsWidgetName = "ShopQuestionWidget";
const shopQuestionInitialData = { const shopQuestionInitialData = {
mobileProviderNumber: "001820",
shopProviders: [ shopProviders: [
{ {
address: { address: {
@ -162,15 +163,18 @@ describe("shop-question.vue", () => {
selectedAppointmentType: "Dropoff", selectedAppointmentType: "Dropoff",
cmsWidgetName: cmsWidgetName, cmsWidgetName: cmsWidgetName,
isDisplayed: true, isDisplayed: true,
shopProviderData: null,
}, },
mountOptions: { mountOptions: {
attachTo: document.body, attachTo: document.body,
}, },
}); });
// Act await wrapper.setProps({
wrapper.vm.initializeComponent(shopQuestionInitialData); shopProviderData: shopQuestionInitialData,
});
// Act
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
@ -199,47 +203,6 @@ describe("shop-question.vue", () => {
}); });
it("Should display the 'Show more locations' link when there are more than three locations to chose from", async () => { it("Should display the 'Show more locations' link when there are more than three locations to chose from", async () => {
// Arrange
const { wrapper } = setupMocks({
mixins: [mockMixin],
props: {
modelValue: {
address: {
city: "POWELL",
country: "US",
state: "OH",
streetAddress: "3938 POWELL RD",
zipCode: "43065",
},
distanceInMiles: 16.2690495685233,
providerNumber: "003341",
},
serviceZipCode: "43081",
selectedAppointmentType: "Dropoff",
cmsWidgetName: cmsWidgetName,
isDisplayed: true,
},
mountOptions: {
attachTo: document.body,
},
});
// Act
wrapper.vm.initializeComponent(shopQuestionInitialData);
await wrapper.vm.$nextTick();
await wrapper.vm.$nextTick();
await wrapper.vm.$nextTick();
await wrapper.vm.$nextTick();
const showMoreShopsLink = wrapper.findComponent({ ref: "showMoreShopsLink" });
// Assert
expect(showMoreShopsLink.exists()).toBe(true);
expect(showMoreShopsLink.isVisible()).toBe(true);
});
it("Should not display the 'Show more locations' link when there are fewer than three locations to chose from", async () => {
// Arrange // Arrange
const alsoShopQuestionInitialData = { const alsoShopQuestionInitialData = {
shopProviders: [ shopProviders: [
@ -276,6 +239,17 @@ describe("shop-question.vue", () => {
distanceInMiles: 11.738869544543, distanceInMiles: 11.738869544543,
providerNumber: "003343", providerNumber: "003343",
}, },
{
address: {
city: "COLUMBUS",
country: "US",
state: "OH",
streetAddress: "1670 HARMON AVE",
zipCode: "43223",
},
distanceInMiles: 15.9727889297435,
providerNumber: "006747",
},
], ],
}; };
@ -297,6 +271,7 @@ describe("shop-question.vue", () => {
selectedAppointmentType: "Dropoff", selectedAppointmentType: "Dropoff",
cmsWidgetName: cmsWidgetName, cmsWidgetName: cmsWidgetName,
isDisplayed: true, isDisplayed: true,
shopProviderData: null,
}, },
mountOptions: { mountOptions: {
attachTo: document.body, attachTo: document.body,
@ -304,17 +279,94 @@ describe("shop-question.vue", () => {
}); });
// Act // Act
wrapper.vm.initializeComponent(alsoShopQuestionInitialData); await wrapper.setProps({
shopProviderData: alsoShopQuestionInitialData,
});
await wrapper.vm.$nextTick();
await wrapper.vm.$nextTick();
await wrapper.vm.$nextTick();
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
const showMoreShopsLink = wrapper.findComponent({ ref: "showMoreShopsLink" }); const showMoreShopsLink = wrapper.findComponent({ ref: "showMoreShopsLink" });
// Assert // Assert
expect(showMoreShopsLink.exists()).toBe(false); expect(showMoreShopsLink.exists()).toBeTruthy();
expect(showMoreShopsLink.isVisible()).toBeTruthy();
}); });
it("Should display the next three shops when the 'Show more location' link is clicked", async () => { it("Should not display the 'Show more locations' link when there are fewer than three locations to chose from", async () => {
// Arrange
const alsoShopQuestionInitialData = {
shopProviders: [
{
address: {
city: "WESTERVILLE",
country: "US",
state: "OH",
streetAddress: "4403 EXECUTIVE PKWY",
zipCode: "43081",
},
distanceInMiles: 5.16769294095201,
providerNumber: "003335",
},
{
address: {
city: "WORTHINGTON",
country: "US",
state: "OH",
streetAddress: "760 DEARBORN PARK LN",
zipCode: "43085",
},
distanceInMiles: 10.5865432478478,
providerNumber: "001820",
},
],
};
const { wrapper } = setupMocks({
mixins: [mockMixin],
props: {
modelValue: {
address: {
city: "POWELL",
country: "US",
state: "OH",
streetAddress: "3938 POWELL RD",
zipCode: "43065",
},
distanceInMiles: 16.2690495685233,
providerNumber: "003341",
},
serviceZipCode: "43081",
selectedAppointmentType: "Dropoff",
cmsWidgetName: cmsWidgetName,
isDisplayed: true,
shopProviderData: null,
},
mountOptions: {
attachTo: document.body,
},
});
// Act
await wrapper.setProps({
shopProviderData: alsoShopQuestionInitialData,
});
await wrapper.vm.$nextTick();
const showMoreShopsLink = wrapper.findComponent({ ref: "showMoreShopsLink" });
await wrapper.vm.$nextTick();
// Assert
expect(showMoreShopsLink.exists()).toBeFalsy();
});
//TODO: Add test to ensure the "Show more locations" link is not visible when all the existing shops are displayed.
it("Should display the next three shops when the 'Show more locations' link is clicked", async () => {
// Arrange // Arrange
const displayedAnswers = [ const displayedAnswers = [
{ {
@ -355,6 +407,7 @@ describe("shop-question.vue", () => {
selectedAppointmentType: "Dropoff", selectedAppointmentType: "Dropoff",
cmsWidgetName: cmsWidgetName, cmsWidgetName: cmsWidgetName,
isDisplayed: true, isDisplayed: true,
shopProviderData: null,
}, },
mountOptions: { mountOptions: {
attachTo: document.body, attachTo: document.body,
@ -362,7 +415,9 @@ describe("shop-question.vue", () => {
}); });
// Act // Act
wrapper.vm.initializeComponent(shopQuestionInitialData); await wrapper.setProps({
shopProviderData: shopQuestionInitialData,
});
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
@ -384,7 +439,7 @@ describe("shop-question.vue", () => {
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
showMoreShopsLink.trigger("click"); showMoreShopsLink.trigger("click-event");
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
@ -443,6 +498,7 @@ describe("shop-question.vue", () => {
serviceZipCode: "43081", serviceZipCode: "43081",
selectedAppointmentType: "Dropoff", selectedAppointmentType: "Dropoff",
cmsWidgetName: cmsWidgetName, cmsWidgetName: cmsWidgetName,
shopProviderData: null,
}, },
mountOptions: { mountOptions: {
attachTo: document.body, attachTo: document.body,
@ -450,7 +506,9 @@ describe("shop-question.vue", () => {
}); });
// Act // Act
wrapper.vm.initializeComponent(shopQuestionInitialData); await wrapper.setProps({
shopProviderData: shopQuestionInitialData,
});
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
@ -468,15 +526,18 @@ describe("shop-question.vue", () => {
modelValue: null, modelValue: null,
selectedAppointmentType: "Dropoff", selectedAppointmentType: "Dropoff",
cmsWidgetName: cmsWidgetName, cmsWidgetName: cmsWidgetName,
shopProviderData: null,
}, },
mountOptions: { mountOptions: {
attachTo: document.body, attachTo: document.body,
}, },
}); });
// Act await wrapper.setProps({
await wrapper.vm.initializeComponent(shopQuestionInitialData); shopProviderData: shopQuestionInitialData,
});
// Act
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
expect(wrapper.vm.answers.length).toEqual(3); expect(wrapper.vm.answers.length).toEqual(3);

View file

@ -30,7 +30,7 @@
linkType="text" linkType="text"
:text="showMoreShopsLinkText" :text="showMoreShopsLinkText"
href="#!" href="#!"
@click-event="getNextShopsFromList" @click-event="getNextShopsFromList(3)"
:aria-label="showMoreShopsLinkText" /> :aria-label="showMoreShopsLinkText" />
</div> </div>
</transition> </transition>
@ -43,8 +43,6 @@ import buttonQuestion from "@/digital-components/button-question/button-question
import shopListButton from "./shop-list-button/shop-list-button"; import shopListButton from "./shop-list-button/shop-list-button";
import textLink from "@/ux-components/text-link/text-link"; import textLink from "@/ux-components/text-link/text-link";
import { storeActions } from "@/constants/store-actions.js";
// Supporting files // Supporting files
import { defineRule } from "vee-validate"; import { defineRule } from "vee-validate";
import { required } from "@/helpers/validation-rules"; import { required } from "@/helpers/validation-rules";
@ -63,7 +61,6 @@ export default {
mixins: [baseMixin], mixins: [baseMixin],
data() { data() {
return { return {
shopProviders: [],
shopListButton: shopListButton, shopListButton: shopListButton,
answers: [], answers: [],
shopIndex: 0, shopIndex: 0,
@ -76,6 +73,7 @@ export default {
default: () => null, default: () => null,
}, },
selectedAppointmentType: String, selectedAppointmentType: String,
shopProviderData: Object,
cmsWidgetName: String, cmsWidgetName: String,
validationRules: String, validationRules: String,
isDisplayed: Boolean, isDisplayed: Boolean,
@ -84,6 +82,9 @@ export default {
questionText() { questionText() {
return this.getCmsContent(this.cmsWidgetName, "QuestionText"); return this.getCmsContent(this.cmsWidgetName, "QuestionText");
}, },
shopProviders() {
return this.shopProviderData?.shopProviders ?? [];
},
selectedValue: { selectedValue: {
get: function () { get: function () {
return this.modelValue; return this.modelValue;
@ -124,22 +125,6 @@ export default {
}, },
}, },
methods: { methods: {
loadInitialData(serviceZipCode) {
return this.loadData(serviceZipCode);
},
loadData(serviceZipCode) {
return baseMixin.methods.dispatchStoreActionWithLogging(
storeActions.GET_PROVIDERS,
{
serviceZipCode: serviceZipCode,
},
"service-location"
);
},
initializeComponent(shopQuestionInitialData) {
this.shopProviders = shopQuestionInitialData.shopProviders;
this.$emit("updated-shop-list", shopQuestionInitialData);
},
async getNextShopsFromList(numberToGet = 3) { async getNextShopsFromList(numberToGet = 3) {
const shopIterator = (array, n) => { const shopIterator = (array, n) => {
const l = array.length; const l = array.length;
@ -199,15 +184,6 @@ export default {
this.answers = []; this.answers = [];
this.shopIndex = 0; this.shopIndex = 0;
}, },
async reloadShopData(serviceZipCode) {
const result = await this.loadData(serviceZipCode);
this.initializeComponent(result.data);
this.resetAnswers();
await nextTick();
await this.getNextShopsFromList();
},
getSelectedProviderObject(providerNumber) { getSelectedProviderObject(providerNumber) {
const provider = const provider =
this.shopProviders?.find((provider) => provider.providerNumber == providerNumber) ?? this.shopProviders?.find((provider) => provider.providerNumber == providerNumber) ??
@ -239,19 +215,16 @@ export default {
shopProviders: { shopProviders: {
async handler(newValue) { async handler(newValue) {
await nextTick(); await nextTick();
const selectedShopIndex = this.getSelectedProviderIndex(
newValue,
this.selectedProviderNumber
);
if (this.selectedAppointmentType) { if (selectedShopIndex >= 3) {
const selectedShopIndex = this.getSelectedProviderIndex( await this.getNextShopsFromList(selectedShopIndex + 1);
newValue, } else {
this.selectedProviderNumber await this.getNextShopsFromList();
); await nextTick();
if (selectedShopIndex >= 3) {
await this.getNextShopsFromList(selectedShopIndex + 1);
} else {
await this.getNextShopsFromList();
await nextTick();
}
} }
}, },
}, },

View file

@ -6,7 +6,7 @@
<funnelHeader cmsWidgetName="FunnelHeaderWidget" /> <funnelHeader cmsWidgetName="FunnelHeaderWidget" />
</div> </div>
</div> </div>
<div class="row justify-content-md-center"> <div class="row justify-content-center">
<div class="col-md-6 col-xl-4"> <div class="col-md-6 col-xl-4">
<vehicleBanner <vehicleBanner
cmsWidgetName="VehicleBannerWidget" cmsWidgetName="VehicleBannerWidget"
@ -23,7 +23,7 @@
:isDismissible="false" /> :isDismissible="false" />
</div> </div>
</div> </div>
<div class="row justify-content-md-center"> <div class="row justify-content-center">
<div class="col-md-6"> <div class="col-md-6">
<damageLocationQuestion <damageLocationQuestion
ref="damageLocation" ref="damageLocation"
@ -32,7 +32,7 @@
groupName="DamageLocationQuestion" /> groupName="DamageLocationQuestion" />
</div> </div>
</div> </div>
<div class="row justify-content-md-center"> <div class="row justify-content-center">
<div class="col-md-6 col-xl-4"> <div class="col-md-6 col-xl-4">
<windshieldOptions <windshieldOptions
class="windshield-options" class="windshield-options"
@ -66,7 +66,7 @@
validationRules="replace-options-required" /> validationRules="replace-options-required" />
</div> </div>
</div> </div>
<div class="row justify-content-md-center"> <div class="row justify-content-center">
<div class="col-md-6 col-xl-4"> <div class="col-md-6 col-xl-4">
<navbar <navbar
cmsWidgetName="FunnelFooterWidget" cmsWidgetName="FunnelFooterWidget"
@ -551,9 +551,19 @@ export default {
<style lang="scss"> <style lang="scss">
.replace-options-question { .replace-options-question {
.question-text {
margin: 1rem 0 0 0;
@include media-breakpoint-up(md) {
margin: 1rem 0;
}
}
.two-list-card-width { .two-list-card-width {
width: 100%; width: 100%;
flex: 0 auto; flex: 0 auto;
margin-top: 0.5rem;
&:first-child {
margin-top: 1rem;
}
} }
} }
</style> </style>

View file

@ -8,7 +8,7 @@
<funnelHeader cmsWidgetName="FunnelHeaderWidget" /> <funnelHeader cmsWidgetName="FunnelHeaderWidget" />
</div> </div>
</div> </div>
<div class="row justify-content-md-center"> <div class="row justify-content-center">
<div class="col-md-6 col-lg-4"> <div class="col-md-6 col-lg-4">
<vehicleBanner <vehicleBanner
ref="vehicleBanner" ref="vehicleBanner"
@ -17,20 +17,18 @@
<funnelSubHeader <funnelSubHeader
ref="funnelSubHeader" ref="funnelSubHeader"
cmsWidgetName="FunnelSubHeaderWidget" /> cmsWidgetName="FunnelSubHeaderWidget" />
<div class="prevent-squish my-5">
<div class="row">
<div class="col pb-0">
<alert
class="rounded border-0 shadow-sm"
alertClass="alert-warning"
cmsWidgetName="AlertWidget"
:isDismissible="false" />
</div>
</div>
</div>
</div> </div>
</div> </div>
<div class="row justify-content-md-center"> <div class="row justify-content-center">
<div class="col-md-6 col-xl-4 my-5">
<alert
class="rounded border-0 shadow-sm"
alertClass="alert-warning"
cmsWidgetName="AlertWidget"
:isDismissible="false" />
</div>
</div>
<div class="row justify-content-center">
<div class="col-md-6 col-xl-4"> <div class="col-md-6 col-xl-4">
<div v-for="(item, i) in PartsOrQuestions" :key="i"> <div v-for="(item, i) in PartsOrQuestions" :key="i">
<!-- Render horizontal lines if there is multi-glass (aka if i > 0) --> <!-- Render horizontal lines if there is multi-glass (aka if i > 0) -->

View file

@ -7,7 +7,7 @@
<funnelHeader cmsWidgetName="FunnelHeaderWidget" /> <funnelHeader cmsWidgetName="FunnelHeaderWidget" />
</div> </div>
</div> </div>
<div class="row justify-content-md-center"> <div class="row justify-content-center">
<div class="col-md-6 col-xl-4"> <div class="col-md-6 col-xl-4">
<vehicleBanner <vehicleBanner
cmsWidgetName="VehicleBannerWidget" cmsWidgetName="VehicleBannerWidget"
@ -16,7 +16,7 @@
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" /> <funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" />
<textboxQuestion <textboxQuestion
class="mb-4 mt-5" class="mb-2 mt-5"
cmsWidgetName="VinNumberQuestionWidget" cmsWidgetName="VinNumberQuestionWidget"
v-model="vin" v-model="vin"
customInputId="vin" customInputId="vin"

View file

@ -677,7 +677,10 @@ function getTimeSlotsAdditionalEventData(
) { ) {
var numberOfDays = null; var numberOfDays = null;
if (firstAvailableAppointmentDateString) if (firstAvailableAppointmentDateString)
numberOfDays = getDateDifferenceInDays(new Date(), firstAvailableAppointmentDateString); numberOfDays = getDateDifferenceInDays(
new Date().toISOString().split("T")[0],
firstAvailableAppointmentDateString
);
if (shopAppointmentType) if (shopAppointmentType)
return `FirstAvailableAppointment:${numberOfDays},Zip:${zipCode},ShopAppointmentType:${shopAppointmentType},ProvisionalTriggers:${provisionalTriggers.join( return `FirstAvailableAppointment:${numberOfDays},Zip:${zipCode},ShopAppointmentType:${shopAppointmentType},ProvisionalTriggers:${provisionalTriggers.join(

View file

@ -14,6 +14,24 @@ body {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
// Set max-width on columns to prevent overly-wide
// components on extra wide screens.
.col-md-6 {
max-width: 472px;
@include media-breakpoint-up(xl) {
max-width: 708px;
}
.col {
max-width: 236px;
}
}
.col-xl-4 {
max-width: 472px;
.col {
max-width: 100%;
}
}
//END set max-width on columns
} }
.pointer { .pointer {
cursor: pointer; cursor: pointer;