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
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"
aria-label="Close"></button>
</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>
</div>
<div class="modal-footer px-5 py-4">
@ -179,7 +179,7 @@ export default {
}
}
@include media-breakpoint-up(md) {
overflow: scroll;
overflow: auto;
flex: none;
}
}

View file

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

View file

@ -7,22 +7,6 @@ export function getDateDifferenceInDays(startDate, endDate) {
// Convert milliseconds to days and return the result
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) {
// Check correct time format and split into components
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
return date instanceof Date
? // 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
undefined;
}

View file

@ -1,6 +1,4 @@
import {
getFullDayName,
getFullMonthName,
get12HourTimeFormat,
get12HourTimeMobileFormat,
getDateFormat,
@ -52,33 +50,17 @@ describe("date-helper.js", () => {
}
});
// it("getFullMonthName should return full month format.", () => {
// // Arrange / Act
// const date = new Date("2023-10-01");
// const monthName = getFullMonthName(date);
it("getDateFormat should return date in the given format.", () => {
// Arrange / Act
const dateString = "2023-10-01";
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
// expect(monthName).toEqual("October");
// });
// 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");
// });
// Assert
expect(formattedDate).toEqual("2023-10-01");
});
it("should return the correct difference in days", function () {
// Define some sample dates and their expected differences

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -46,6 +46,18 @@ export async function getServiceabilityDetails(serviceZipCode, lineItems, pageNa
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(
startDate,
endDate,

View file

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

View file

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

View file

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

View file

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

View file

@ -61,6 +61,7 @@ const mockCmsContent = {
const cmsWidgetName = "ShopQuestionWidget";
const shopQuestionInitialData = {
mobileProviderNumber: "001820",
shopProviders: [
{
address: {
@ -162,15 +163,18 @@ describe("shop-question.vue", () => {
selectedAppointmentType: "Dropoff",
cmsWidgetName: cmsWidgetName,
isDisplayed: true,
shopProviderData: null,
},
mountOptions: {
attachTo: document.body,
},
});
// Act
wrapper.vm.initializeComponent(shopQuestionInitialData);
await wrapper.setProps({
shopProviderData: shopQuestionInitialData,
});
// Act
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 () => {
// 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
const alsoShopQuestionInitialData = {
shopProviders: [
@ -276,6 +239,17 @@ describe("shop-question.vue", () => {
distanceInMiles: 11.738869544543,
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",
cmsWidgetName: cmsWidgetName,
isDisplayed: true,
shopProviderData: null,
},
mountOptions: {
attachTo: document.body,
@ -304,17 +279,94 @@ describe("shop-question.vue", () => {
});
// 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();
const showMoreShopsLink = wrapper.findComponent({ ref: "showMoreShopsLink" });
// 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
const displayedAnswers = [
{
@ -355,6 +407,7 @@ describe("shop-question.vue", () => {
selectedAppointmentType: "Dropoff",
cmsWidgetName: cmsWidgetName,
isDisplayed: true,
shopProviderData: null,
},
mountOptions: {
attachTo: document.body,
@ -362,7 +415,9 @@ describe("shop-question.vue", () => {
});
// Act
wrapper.vm.initializeComponent(shopQuestionInitialData);
await wrapper.setProps({
shopProviderData: shopQuestionInitialData,
});
await wrapper.vm.$nextTick();
@ -384,7 +439,7 @@ describe("shop-question.vue", () => {
await wrapper.vm.$nextTick();
showMoreShopsLink.trigger("click");
showMoreShopsLink.trigger("click-event");
await wrapper.vm.$nextTick();
@ -443,6 +498,7 @@ describe("shop-question.vue", () => {
serviceZipCode: "43081",
selectedAppointmentType: "Dropoff",
cmsWidgetName: cmsWidgetName,
shopProviderData: null,
},
mountOptions: {
attachTo: document.body,
@ -450,7 +506,9 @@ describe("shop-question.vue", () => {
});
// Act
wrapper.vm.initializeComponent(shopQuestionInitialData);
await wrapper.setProps({
shopProviderData: shopQuestionInitialData,
});
await wrapper.vm.$nextTick();
await wrapper.vm.$nextTick();
@ -468,15 +526,18 @@ describe("shop-question.vue", () => {
modelValue: null,
selectedAppointmentType: "Dropoff",
cmsWidgetName: cmsWidgetName,
shopProviderData: null,
},
mountOptions: {
attachTo: document.body,
},
});
// Act
await wrapper.vm.initializeComponent(shopQuestionInitialData);
await wrapper.setProps({
shopProviderData: shopQuestionInitialData,
});
// Act
await wrapper.vm.$nextTick();
expect(wrapper.vm.answers.length).toEqual(3);

View file

@ -30,7 +30,7 @@
linkType="text"
:text="showMoreShopsLinkText"
href="#!"
@click-event="getNextShopsFromList"
@click-event="getNextShopsFromList(3)"
:aria-label="showMoreShopsLinkText" />
</div>
</transition>
@ -43,8 +43,6 @@ import buttonQuestion from "@/digital-components/button-question/button-question
import shopListButton from "./shop-list-button/shop-list-button";
import textLink from "@/ux-components/text-link/text-link";
import { storeActions } from "@/constants/store-actions.js";
// Supporting files
import { defineRule } from "vee-validate";
import { required } from "@/helpers/validation-rules";
@ -63,7 +61,6 @@ export default {
mixins: [baseMixin],
data() {
return {
shopProviders: [],
shopListButton: shopListButton,
answers: [],
shopIndex: 0,
@ -76,6 +73,7 @@ export default {
default: () => null,
},
selectedAppointmentType: String,
shopProviderData: Object,
cmsWidgetName: String,
validationRules: String,
isDisplayed: Boolean,
@ -84,6 +82,9 @@ export default {
questionText() {
return this.getCmsContent(this.cmsWidgetName, "QuestionText");
},
shopProviders() {
return this.shopProviderData?.shopProviders ?? [];
},
selectedValue: {
get: function () {
return this.modelValue;
@ -124,22 +125,6 @@ export default {
},
},
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) {
const shopIterator = (array, n) => {
const l = array.length;
@ -199,15 +184,6 @@ export default {
this.answers = [];
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) {
const provider =
this.shopProviders?.find((provider) => provider.providerNumber == providerNumber) ??
@ -239,19 +215,16 @@ export default {
shopProviders: {
async handler(newValue) {
await nextTick();
const selectedShopIndex = this.getSelectedProviderIndex(
newValue,
this.selectedProviderNumber
);
if (this.selectedAppointmentType) {
const selectedShopIndex = this.getSelectedProviderIndex(
newValue,
this.selectedProviderNumber
);
if (selectedShopIndex >= 3) {
await this.getNextShopsFromList(selectedShopIndex + 1);
} else {
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" />
</div>
</div>
<div class="row justify-content-md-center">
<div class="row justify-content-center">
<div class="col-md-6 col-xl-4">
<vehicleBanner
cmsWidgetName="VehicleBannerWidget"
@ -23,7 +23,7 @@
:isDismissible="false" />
</div>
</div>
<div class="row justify-content-md-center">
<div class="row justify-content-center">
<div class="col-md-6">
<damageLocationQuestion
ref="damageLocation"
@ -32,7 +32,7 @@
groupName="DamageLocationQuestion" />
</div>
</div>
<div class="row justify-content-md-center">
<div class="row justify-content-center">
<div class="col-md-6 col-xl-4">
<windshieldOptions
class="windshield-options"
@ -66,7 +66,7 @@
validationRules="replace-options-required" />
</div>
</div>
<div class="row justify-content-md-center">
<div class="row justify-content-center">
<div class="col-md-6 col-xl-4">
<navbar
cmsWidgetName="FunnelFooterWidget"
@ -551,9 +551,19 @@ export default {
<style lang="scss">
.replace-options-question {
.question-text {
margin: 1rem 0 0 0;
@include media-breakpoint-up(md) {
margin: 1rem 0;
}
}
.two-list-card-width {
width: 100%;
flex: 0 auto;
margin-top: 0.5rem;
&:first-child {
margin-top: 1rem;
}
}
}
</style>

View file

@ -8,7 +8,7 @@
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
</div>
</div>
<div class="row justify-content-md-center">
<div class="row justify-content-center">
<div class="col-md-6 col-lg-4">
<vehicleBanner
ref="vehicleBanner"
@ -17,20 +17,18 @@
<funnelSubHeader
ref="funnelSubHeader"
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 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 v-for="(item, i) in PartsOrQuestions" :key="i">
<!-- Render horizontal lines if there is multi-glass (aka if i > 0) -->

View file

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

View file

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

View file

@ -14,6 +14,24 @@ body {
display: flex;
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 {
cursor: pointer;