CASH-2801 persist selected data to vuex store
CASH-2801 persist selected data to vuex store
This commit is contained in:
parent
8aec8ba5f3
commit
60435a018c
2 changed files with 371 additions and 6 deletions
|
|
@ -2,13 +2,23 @@ import { shallowMount } from "@vue/test-utils";
|
|||
import scheduling from "./scheduling";
|
||||
import { getMountOptions } from "@/helpers/unit-test-helper.js";
|
||||
import { settleAllPromises } from "@/helpers/layout-helper";
|
||||
import { AppointmentTypeStrings } from "@/constants/schedule-constants";
|
||||
import store from "@/store";
|
||||
|
||||
jest.mock("@/store", () => ({
|
||||
dispatch: jest.fn().mockResolvedValue(null),
|
||||
getters: {
|
||||
order: {
|
||||
serviceLocation: { zipCode: "43235", appointmentType: null },
|
||||
serviceLocation: {
|
||||
zipCode: "43235",
|
||||
zipCodeCtu: "1234",
|
||||
appointmentType: null,
|
||||
address: null,
|
||||
address2: null,
|
||||
city: null,
|
||||
state: "OH",
|
||||
isVehicleProtected: null,
|
||||
},
|
||||
payment: {
|
||||
isInsurance: false,
|
||||
insuranceCoverage: { isVerified: false },
|
||||
|
|
@ -19,6 +29,7 @@ jest.mock("@/store", () => ({
|
|||
lineItems: { glassParts: [] },
|
||||
policy: { isItac: false, isNoComp: false },
|
||||
},
|
||||
lineItems: { supportingItems: [] },
|
||||
applicationUser: {
|
||||
experiments: [],
|
||||
},
|
||||
|
|
@ -49,11 +60,34 @@ jest.mock("@/helpers/page-prerequisites-helper.js", () => ({
|
|||
}));
|
||||
|
||||
function setupMocks() {
|
||||
const dispatchStoreAction = jest.fn().mockResolvedValue(null);
|
||||
const baseMixin = {
|
||||
methods: {
|
||||
getCmsContent: jest.fn(() => ""),
|
||||
setCmsContent: jest.fn(),
|
||||
getTotalLineItemPrice: jest.fn(() => 0),
|
||||
dispatchStoreAction,
|
||||
},
|
||||
computed: {
|
||||
storeActions() {
|
||||
return {
|
||||
SAVE_SERVICE_LOCATION: "saveServiceLocation",
|
||||
SAVE_SCHEDULE: "saveSchedule",
|
||||
SAVE_WAITLIST_REQUESTED: "saveWaitListRequested",
|
||||
SAVE_SUPPORTING_ITEMS_SUPPRESSING_STATE_RESETTING:
|
||||
"saveSupportingItemsSuppressingStateResetting",
|
||||
};
|
||||
},
|
||||
navigationScenarios() {
|
||||
return {
|
||||
CLICKED_FORWARD: "clickedForward",
|
||||
CLICKED_FORWARD_WITH_MOBILE_SERVICE: "clickedForwardWithMobileService",
|
||||
CLICKED_BACK: "clickedBack",
|
||||
};
|
||||
},
|
||||
pageName() {
|
||||
return "scheduling";
|
||||
},
|
||||
},
|
||||
};
|
||||
const mountOptions = getMountOptions({
|
||||
|
|
@ -66,7 +100,7 @@ function setupMocks() {
|
|||
});
|
||||
mountOptions.global.mixins = [baseMixin];
|
||||
const wrapper = shallowMount(scheduling, mountOptions);
|
||||
return { wrapper };
|
||||
return { wrapper, dispatchStoreAction };
|
||||
}
|
||||
|
||||
describe("scheduling.vue", () => {
|
||||
|
|
@ -249,4 +283,132 @@ describe("scheduling.vue", () => {
|
|||
wrapper.unmount();
|
||||
});
|
||||
});
|
||||
|
||||
describe("forwardButtonAction", () => {
|
||||
test("saves service location, schedule, waitlist and navigates for inshop selection", async () => {
|
||||
const { wrapper, dispatchStoreAction } = setupMocks();
|
||||
wrapper.vm.selectedDate = "2026-07-22";
|
||||
wrapper.vm.isWaitlistRequested = true;
|
||||
wrapper.vm.selectedScheduling = {
|
||||
appointmentType: AppointmentTypeStrings.IN_SHOP,
|
||||
providerNumber: "05018",
|
||||
routeCodeId: "route-inshop",
|
||||
};
|
||||
wrapper.vm.inshopProvidersAndTimeSlots = [
|
||||
{
|
||||
provider: {
|
||||
providerNumber: "05018",
|
||||
address: {
|
||||
streetAddress: "123 Main",
|
||||
city: "Columbus",
|
||||
state: "OH",
|
||||
zipCode: "43235",
|
||||
zipCodeCtu: "9999",
|
||||
},
|
||||
},
|
||||
timeSlots: {
|
||||
estimatedServiceMinutesMinimum: 60,
|
||||
estimatedServiceMinutesMaximum: 120,
|
||||
days: [
|
||||
{
|
||||
date: "2026-07-22",
|
||||
timeSlots: [
|
||||
{
|
||||
id: "route-inshop",
|
||||
startTime: "09:00",
|
||||
endTime: "10:00",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
await wrapper.vm.forwardButtonAction();
|
||||
|
||||
expect(dispatchStoreAction).toHaveBeenCalledWith(
|
||||
"saveServiceLocation",
|
||||
expect.objectContaining({
|
||||
appointmentType: AppointmentTypeStrings.IN_SHOP,
|
||||
zipCodeCtu: "9999",
|
||||
provider: expect.objectContaining({ providerNumber: "05018" }),
|
||||
}),
|
||||
false
|
||||
);
|
||||
expect(dispatchStoreAction).toHaveBeenCalledWith(
|
||||
"saveSchedule",
|
||||
expect.objectContaining({
|
||||
date: "2026-07-22",
|
||||
routeCode: "route-inshop",
|
||||
startTime: "09:00",
|
||||
endTime: "10:00",
|
||||
jobMinMinutes: "60",
|
||||
jobMaxMinutes: "120",
|
||||
}),
|
||||
false
|
||||
);
|
||||
expect(dispatchStoreAction).toHaveBeenCalledWith("saveWaitListRequested", true, false);
|
||||
expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledWith(
|
||||
"clickedForward",
|
||||
"scheduling"
|
||||
);
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
test("navigates with mobile scenario and clears provider address for mobile selection", async () => {
|
||||
const { wrapper, dispatchStoreAction } = setupMocks();
|
||||
wrapper.vm.selectedDate = "2026-07-22";
|
||||
wrapper.vm.selectedScheduling = {
|
||||
appointmentType: AppointmentTypeStrings.MOBILE,
|
||||
providerNumber: "MOBILE1",
|
||||
routeCodeId: "route-mobile",
|
||||
isPremiumAppointment: false,
|
||||
};
|
||||
wrapper.vm.mobileProviderAndTimeSlot = {
|
||||
providerNumber: "MOBILE1",
|
||||
timeSlots: {
|
||||
estimatedServiceMinutesMinimum: 90,
|
||||
estimatedServiceMinutesMaximum: 150,
|
||||
days: [
|
||||
{
|
||||
date: "2026-07-22",
|
||||
timeSlots: [
|
||||
{
|
||||
id: "route-mobile",
|
||||
startTime: "08:00",
|
||||
endTime: "12:00",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
await wrapper.vm.forwardButtonAction();
|
||||
|
||||
expect(dispatchStoreAction).toHaveBeenCalledWith(
|
||||
"saveServiceLocation",
|
||||
expect.objectContaining({
|
||||
appointmentType: AppointmentTypeStrings.MOBILE,
|
||||
provider: {
|
||||
providerNumber: "MOBILE1",
|
||||
address: {
|
||||
streetAddress: null,
|
||||
city: null,
|
||||
state: null,
|
||||
zipCode: null,
|
||||
zipCodeCtu: null,
|
||||
},
|
||||
},
|
||||
}),
|
||||
false
|
||||
);
|
||||
expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledWith(
|
||||
"clickedForwardWithMobileService",
|
||||
"scheduling"
|
||||
);
|
||||
wrapper.unmount();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -102,6 +102,7 @@ import textLink from "@/ux-components/text-link/text-link";
|
|||
import store from "@/store";
|
||||
import { storeActions } from "@/constants/store-actions";
|
||||
import { AppointmentTypeStrings, PREMIUM_FEE_PART_TYPE } from "@/constants/schedule-constants";
|
||||
import { isDropOffRouteCode } from "@/layouts/schedule/helpers/schedule-helper";
|
||||
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
||||
import { settleAllPromises } from "@/helpers/layout-helper";
|
||||
import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper";
|
||||
|
|
@ -148,14 +149,23 @@ function appendDays(entry, newSlots) {
|
|||
/**
|
||||
* Assigns time slots from a v2 multi-provider response onto in-shop provider entries.
|
||||
* @param {Array<{ provider: { providerNumber: string }, timeSlots: any }>} entries
|
||||
* @param {{ providerTimeSlots?: Array<{ providerNumber: string, days: any[], provisionalTriggers?: string[] }> } | null | undefined} multiProviderResponse
|
||||
* @param {{ providerTimeSlots?: Array<{ providerNumber: string, days: any[], provisionalTriggers?: string[] }>, estimatedServiceMinutesMinimum?: number, estimatedServiceMinutesMaximum?: number } | null | undefined} multiProviderResponse
|
||||
*/
|
||||
function assignInshopTimeSlotsFromV2Response(entries, multiProviderResponse) {
|
||||
const providerTimeSlots = multiProviderResponse?.providerTimeSlots ?? [];
|
||||
entries.forEach((entry) => {
|
||||
entry.timeSlots =
|
||||
const matchedSlots =
|
||||
providerTimeSlots.find((pts) => pts.providerNumber === entry.provider.providerNumber) ??
|
||||
null;
|
||||
if (matchedSlots) {
|
||||
matchedSlots.estimatedServiceMinutesMinimum =
|
||||
matchedSlots.estimatedServiceMinutesMinimum ??
|
||||
multiProviderResponse?.estimatedServiceMinutesMinimum;
|
||||
matchedSlots.estimatedServiceMinutesMaximum =
|
||||
matchedSlots.estimatedServiceMinutesMaximum ??
|
||||
multiProviderResponse?.estimatedServiceMinutesMaximum;
|
||||
}
|
||||
entry.timeSlots = matchedSlots;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -585,9 +595,202 @@ export default {
|
|||
this.datePickerEndDate = newEndDate;
|
||||
this.isLoadingDates = false;
|
||||
},
|
||||
forwardButtonAction() {
|
||||
const appointmentType = store.getters.order.serviceLocation.appointmentType; // TODO: remove this once we have a proper appointment type
|
||||
getInShopOrDropOffApptType(selectedScheduling = this.selectedScheduling) {
|
||||
if (selectedScheduling?.appointmentType === AppointmentTypeStrings.MOBILE) {
|
||||
return AppointmentTypeStrings.MOBILE;
|
||||
}
|
||||
return isDropOffRouteCode(selectedScheduling?.routeCodeId)
|
||||
? AppointmentTypeStrings.DROP_OFF
|
||||
: AppointmentTypeStrings.IN_SHOP;
|
||||
},
|
||||
getSelectedProvider(appointmentType, selectedScheduling = this.selectedScheduling) {
|
||||
if (appointmentType === AppointmentTypeStrings.MOBILE) {
|
||||
return {
|
||||
providerNumber: selectedScheduling?.providerNumber,
|
||||
address: {
|
||||
streetAddress: null,
|
||||
city: null,
|
||||
state: null,
|
||||
zipCode: null,
|
||||
zipCodeCtu: null,
|
||||
},
|
||||
};
|
||||
}
|
||||
return (
|
||||
this.inshopProvidersAndTimeSlots.find(
|
||||
({ provider }) => provider.providerNumber === selectedScheduling?.providerNumber
|
||||
)?.provider ?? null
|
||||
);
|
||||
},
|
||||
getSelectedTimeSlot(
|
||||
appointmentType,
|
||||
selectedScheduling = this.selectedScheduling,
|
||||
selectedDate = this.selectedDate
|
||||
) {
|
||||
const routeCodeId = selectedScheduling?.routeCodeId;
|
||||
if (!routeCodeId || !selectedDate) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let timeSlots;
|
||||
if (appointmentType === AppointmentTypeStrings.MOBILE) {
|
||||
const day = this.mobileProviderAndTimeSlot?.timeSlots?.days?.find(
|
||||
(d) => d.date === selectedDate
|
||||
);
|
||||
timeSlots = day?.timeSlots ?? [];
|
||||
} else {
|
||||
const providerEntry = this.inshopProvidersAndTimeSlots.find(
|
||||
({ provider }) => provider.providerNumber === selectedScheduling.providerNumber
|
||||
);
|
||||
const day = providerEntry?.timeSlots?.days?.find((d) => d.date === selectedDate);
|
||||
timeSlots = day?.timeSlots ?? [];
|
||||
}
|
||||
|
||||
return timeSlots.find((timeSlot) => timeSlot.id === routeCodeId) ?? null;
|
||||
},
|
||||
getEstimatedServiceMinutes(appointmentType, selectedScheduling = this.selectedScheduling) {
|
||||
if (appointmentType === AppointmentTypeStrings.MOBILE) {
|
||||
return {
|
||||
minimum:
|
||||
this.mobileProviderAndTimeSlot?.timeSlots?.estimatedServiceMinutesMinimum,
|
||||
maximum:
|
||||
this.mobileProviderAndTimeSlot?.timeSlots?.estimatedServiceMinutesMaximum,
|
||||
};
|
||||
}
|
||||
|
||||
const providerEntry = this.inshopProvidersAndTimeSlots.find(
|
||||
({ provider }) => provider.providerNumber === selectedScheduling?.providerNumber
|
||||
);
|
||||
return {
|
||||
minimum: providerEntry?.timeSlots?.estimatedServiceMinutesMinimum,
|
||||
maximum: providerEntry?.timeSlots?.estimatedServiceMinutesMaximum,
|
||||
};
|
||||
},
|
||||
updateSupportingItems(selectedScheduling = this.selectedScheduling) {
|
||||
const supportingItems = store.getters.lineItems?.supportingItems;
|
||||
if (!supportingItems) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isPremiumMobile =
|
||||
selectedScheduling?.appointmentType === AppointmentTypeStrings.MOBILE &&
|
||||
selectedScheduling?.isPremiumAppointment;
|
||||
|
||||
if (isPremiumMobile && this.mobilePremiumAppointmentFee) {
|
||||
const premiumFeeIndex = supportingItems.findIndex(
|
||||
(item) => item.partType == PREMIUM_FEE_PART_TYPE
|
||||
);
|
||||
|
||||
if (premiumFeeIndex > -1) {
|
||||
supportingItems[premiumFeeIndex].laborAmount =
|
||||
this.mobilePremiumAppointmentFee.laborAmount;
|
||||
supportingItems[premiumFeeIndex].sellingPrice =
|
||||
this.mobilePremiumAppointmentFee.sellingPrice;
|
||||
supportingItems[premiumFeeIndex].kitPrice =
|
||||
this.mobilePremiumAppointmentFee.kitPrice;
|
||||
} else {
|
||||
supportingItems.push(this.mobilePremiumAppointmentFee);
|
||||
}
|
||||
} else {
|
||||
const removePremiumFeeIndex = supportingItems.findIndex(
|
||||
(item) => item.partType == PREMIUM_FEE_PART_TYPE
|
||||
);
|
||||
if (removePremiumFeeIndex >= 0) {
|
||||
supportingItems.splice(removePremiumFeeIndex, 1);
|
||||
}
|
||||
}
|
||||
|
||||
this.dispatchStoreAction(
|
||||
this.storeActions.SAVE_SUPPORTING_ITEMS_SUPPRESSING_STATE_RESETTING,
|
||||
supportingItems,
|
||||
false
|
||||
);
|
||||
},
|
||||
async forwardButtonAction() {
|
||||
const selectedScheduling = this.selectedScheduling;
|
||||
const selectedDate = this.selectedDate;
|
||||
|
||||
const appointmentType = this.getInShopOrDropOffApptType(selectedScheduling);
|
||||
const selectedProvider = this.getSelectedProvider(appointmentType, selectedScheduling);
|
||||
const serviceLocation = store.getters.order.serviceLocation;
|
||||
const isMobile = appointmentType === AppointmentTypeStrings.MOBILE;
|
||||
|
||||
let zipCodeCtu = serviceLocation.zipCodeCtu;
|
||||
if (
|
||||
!isMobile &&
|
||||
selectedProvider?.address?.zipCodeCtu &&
|
||||
zipCodeCtu !== selectedProvider.address.zipCodeCtu
|
||||
) {
|
||||
zipCodeCtu = selectedProvider.address.zipCodeCtu;
|
||||
}
|
||||
|
||||
await this.dispatchStoreAction(
|
||||
this.storeActions.SAVE_SERVICE_LOCATION,
|
||||
{
|
||||
address: isMobile ? serviceLocation.address : "",
|
||||
address2: isMobile ? serviceLocation.address2 : "",
|
||||
city: isMobile ? serviceLocation.city : "",
|
||||
state: serviceLocation.state,
|
||||
zipCode: serviceLocation.zipCode,
|
||||
zipCodeCtu: zipCodeCtu,
|
||||
appointmentType,
|
||||
isVehicleProtected: isMobile ? serviceLocation.isVehicleProtected : null,
|
||||
provider: {
|
||||
providerNumber: selectedProvider?.providerNumber,
|
||||
address: {
|
||||
streetAddress: selectedProvider?.address?.streetAddress ?? null,
|
||||
city: selectedProvider?.address?.city ?? null,
|
||||
state: selectedProvider?.address?.state ?? null,
|
||||
zipCode: selectedProvider?.address?.zipCode ?? null,
|
||||
zipCodeCtu: selectedProvider?.address?.zipCodeCtu ?? null,
|
||||
},
|
||||
},
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
if (this.billToAccountNumber) {
|
||||
this.dispatchStoreAction(
|
||||
this.storeActions.SAVE_BILL_TO_ACCOUNT_NUMBER,
|
||||
this.billToAccountNumber,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
this.updateSupportingItems(selectedScheduling);
|
||||
|
||||
const selectedTimeSlot = this.getSelectedTimeSlot(
|
||||
appointmentType,
|
||||
selectedScheduling,
|
||||
selectedDate
|
||||
);
|
||||
const estimatedServiceMinutes = this.getEstimatedServiceMinutes(
|
||||
appointmentType,
|
||||
selectedScheduling
|
||||
);
|
||||
|
||||
this.dispatchStoreAction(
|
||||
this.storeActions.SAVE_SCHEDULE,
|
||||
{
|
||||
date: selectedDate,
|
||||
routeCode: selectedScheduling?.routeCodeId ?? null,
|
||||
startTime: selectedTimeSlot?.startTime ?? null,
|
||||
endTime: selectedTimeSlot?.endTime ?? null,
|
||||
jobMinMinutes: estimatedServiceMinutes.minimum?.toString() ?? null,
|
||||
jobMaxMinutes: estimatedServiceMinutes.maximum?.toString() ?? null,
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
if (this.isWaitlistRequested !== null && this.isWaitlistRequested !== undefined) {
|
||||
this.dispatchStoreAction(
|
||||
this.storeActions.SAVE_WAITLIST_REQUESTED,
|
||||
this.isWaitlistRequested,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
if (isMobile) {
|
||||
this.$router.navigateWithSaving(
|
||||
this.navigationScenarios.CLICKED_FORWARD_WITH_MOBILE_SERVICE,
|
||||
this.pageName
|
||||
|
|
|
|||
Loading…
Reference in a new issue