Merge branch 'release/2026.08.13' into feature/CASH-1524
This commit is contained in:
commit
4ee0c0a2c1
2 changed files with 171 additions and 1 deletions
|
|
@ -3,6 +3,7 @@ import scheduling from "./scheduling";
|
||||||
import { getMountOptions } from "@/helpers/unit-test-helper.js";
|
import { getMountOptions } from "@/helpers/unit-test-helper.js";
|
||||||
import { settleAllPromises } from "@/helpers/layout-helper";
|
import { settleAllPromises } from "@/helpers/layout-helper";
|
||||||
import { AppointmentTypeStrings } from "@/constants/schedule-constants";
|
import { AppointmentTypeStrings } from "@/constants/schedule-constants";
|
||||||
|
import { GaActions, GaCategories, GaLabels } from "@/constants/analytics";
|
||||||
import store from "@/store";
|
import store from "@/store";
|
||||||
|
|
||||||
jest.mock("@/store", () => ({
|
jest.mock("@/store", () => ({
|
||||||
|
|
@ -28,6 +29,7 @@ jest.mock("@/store", () => ({
|
||||||
damage: { isRepair: false },
|
damage: { isRepair: false },
|
||||||
lineItems: { glassParts: [] },
|
lineItems: { glassParts: [] },
|
||||||
policy: { isItac: false, isNoComp: false },
|
policy: { isItac: false, isNoComp: false },
|
||||||
|
isRecalAcknowledgedForScheduling: "",
|
||||||
},
|
},
|
||||||
lineItems: { supportingItems: [] },
|
lineItems: { supportingItems: [] },
|
||||||
applicationUser: {
|
applicationUser: {
|
||||||
|
|
@ -84,6 +86,7 @@ function setupMocks({ isAppleBrowser = false } = {}) {
|
||||||
CLICKED_FORWARD: "clickedForward",
|
CLICKED_FORWARD: "clickedForward",
|
||||||
CLICKED_FORWARD_WITH_MOBILE_SERVICE: "clickedForwardWithMobileService",
|
CLICKED_FORWARD_WITH_MOBILE_SERVICE: "clickedForwardWithMobileService",
|
||||||
CLICKED_BACK: "clickedBack",
|
CLICKED_BACK: "clickedBack",
|
||||||
|
ZIP_CODE_CHANGED_RECAL_ACK: "ZIP_CODE_CHANGED_RECAL_ACK",
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
pageName() {
|
pageName() {
|
||||||
|
|
@ -283,6 +286,60 @@ describe("scheduling.vue", () => {
|
||||||
expect(wrapper.vm.$refs.schedulingZipSearch.focusZipInput).toHaveBeenCalled();
|
expect(wrapper.vm.$refs.schedulingZipSearch.focusZipInput).toHaveBeenCalled();
|
||||||
wrapper.unmount();
|
wrapper.unmount();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test.each([
|
||||||
|
["yes", "12345", GaLabels.YES],
|
||||||
|
["no", null, GaLabels.NO],
|
||||||
|
])(
|
||||||
|
"pushes mobile_available %s when mobileProviderNumber is %s",
|
||||||
|
async (_label, mobileProviderNumber, expectedGaLabel) => {
|
||||||
|
settleAllPromises.mockResolvedValueOnce({});
|
||||||
|
const { wrapper } = setupMocks();
|
||||||
|
wrapper.vm.pushEventToGA.mockClear();
|
||||||
|
|
||||||
|
await wrapper.vm.loadSchedulingData("43235", {
|
||||||
|
providersResult: { shopProviders: [], mobileProviderNumber },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(wrapper.vm.pushEventToGA).toHaveBeenCalledWith(
|
||||||
|
GaCategories.APPOINTMENT,
|
||||||
|
GaActions.MOBILE_AVAILABLE,
|
||||||
|
expectedGaLabel,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
wrapper.unmount();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
test("navigates to service-zip when zip changes and recal acknowledgement applies", async () => {
|
||||||
|
store.getters.order.lineItems = {
|
||||||
|
glassParts: [
|
||||||
|
{
|
||||||
|
partType: "WINDSHIELD",
|
||||||
|
requiresRecalibration: true,
|
||||||
|
canSafeliteRecalibrate: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
const { wrapper } = setupMocks();
|
||||||
|
await wrapper.setData({
|
||||||
|
isRecalAcknowledgedForScheduling: "",
|
||||||
|
isLoadingDates: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
await wrapper.vm.onZipSearched({ zipCode: "44101", billToAccountNumber: "87291" });
|
||||||
|
|
||||||
|
expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledWith(
|
||||||
|
"ZIP_CODE_CHANGED_RECAL_ACK",
|
||||||
|
"scheduling"
|
||||||
|
);
|
||||||
|
expect(wrapper.vm.isLoadingDates).toBe(false);
|
||||||
|
wrapper.unmount();
|
||||||
|
} finally {
|
||||||
|
store.getters.order.lineItems = { glassParts: [] };
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("forwardButtonAction", () => {
|
describe("forwardButtonAction", () => {
|
||||||
|
|
@ -413,6 +470,56 @@ describe("scheduling.vue", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("forwardButtonAction (recal acknowledgement)", () => {
|
||||||
|
test("opens recal modal and does not navigate when required", async () => {
|
||||||
|
store.getters.order.lineItems = {
|
||||||
|
glassParts: [
|
||||||
|
{
|
||||||
|
partType: "WINDSHIELD",
|
||||||
|
requiresRecalibration: true,
|
||||||
|
canSafeliteRecalibrate: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const { wrapper } = setupMocks();
|
||||||
|
wrapper.vm.$refs.navbar.removeLoader = jest.fn();
|
||||||
|
wrapper.vm.$refs.recalAckModal.openModal = jest.fn();
|
||||||
|
await wrapper.setData({ isRecalAcknowledgedForScheduling: "" });
|
||||||
|
|
||||||
|
await wrapper.vm.forwardButtonAction();
|
||||||
|
|
||||||
|
expect(wrapper.vm.$refs.recalAckModal.openModal).toHaveBeenCalled();
|
||||||
|
expect(wrapper.vm.$router.navigateWithSaving).not.toHaveBeenCalled();
|
||||||
|
wrapper.unmount();
|
||||||
|
store.getters.order.lineItems = { glassParts: [] };
|
||||||
|
});
|
||||||
|
|
||||||
|
test("navigates forward after recal acknowledgement is saved", async () => {
|
||||||
|
const { wrapper } = setupMocks();
|
||||||
|
await wrapper.setData({ isRecalAcknowledgedForScheduling: "Yes" });
|
||||||
|
|
||||||
|
await wrapper.vm.forwardButtonAction();
|
||||||
|
|
||||||
|
expect(store.dispatch).toHaveBeenCalledWith(
|
||||||
|
"saveIsRecalAcknowledgedForScheduling",
|
||||||
|
"Yes"
|
||||||
|
);
|
||||||
|
expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalled();
|
||||||
|
wrapper.unmount();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("onRecalAcknowledged sets acknowledgement and continues forward", async () => {
|
||||||
|
const { wrapper } = setupMocks();
|
||||||
|
wrapper.vm.forwardButtonAction = jest.fn();
|
||||||
|
|
||||||
|
wrapper.vm.onRecalAcknowledged("Yes");
|
||||||
|
|
||||||
|
expect(wrapper.vm.isRecalAcknowledgedForScheduling).toBe("Yes");
|
||||||
|
expect(wrapper.vm.forwardButtonAction).toHaveBeenCalled();
|
||||||
|
wrapper.unmount();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("in-shop Maps link", () => {
|
describe("in-shop Maps link", () => {
|
||||||
const address = {
|
const address = {
|
||||||
city: "Lewis Center",
|
city: "Lewis Center",
|
||||||
|
|
|
||||||
|
|
@ -84,6 +84,12 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<recalAckModal
|
||||||
|
modalWidgetName="RecalAckModalWidget"
|
||||||
|
agreementWidgetName="RecalAgreementQuestionWidget"
|
||||||
|
groupName="recalAckGroup"
|
||||||
|
@recalAcknowledged="onRecalAcknowledged"
|
||||||
|
ref="recalAckModal" />
|
||||||
</Form>
|
</Form>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -98,15 +104,21 @@ import inshopSchedulingCard from "@/layouts/scheduling/inshop-scheduling-card/in
|
||||||
import interceptOverlay from "@/ux-components/intercept-overlay/intercept-overlay";
|
import interceptOverlay from "@/ux-components/intercept-overlay/intercept-overlay";
|
||||||
import waitlistQuestion from "@/layouts/scheduling/waitlist-question/waitlist-question";
|
import waitlistQuestion from "@/layouts/scheduling/waitlist-question/waitlist-question";
|
||||||
import schedulingZipSearch from "@/layouts/scheduling/scheduling-zip-search/scheduling-zip-search";
|
import schedulingZipSearch from "@/layouts/scheduling/scheduling-zip-search/scheduling-zip-search";
|
||||||
|
import recalAckModal from "@/layouts/schedule/recal-ack-modal/recal-ack-modal.vue";
|
||||||
import textLink from "@/ux-components/text-link/text-link";
|
import textLink from "@/ux-components/text-link/text-link";
|
||||||
import store from "@/store";
|
import store from "@/store";
|
||||||
import { storeActions } from "@/constants/store-actions";
|
import { storeActions } from "@/constants/store-actions";
|
||||||
import { AppointmentTypeStrings, PREMIUM_FEE_PART_TYPE } from "@/constants/schedule-constants";
|
import {
|
||||||
|
AppointmentTypeStrings,
|
||||||
|
PREMIUM_FEE_PART_TYPE,
|
||||||
|
RECAL_ACK_YES,
|
||||||
|
} from "@/constants/schedule-constants";
|
||||||
import { isDropOffRouteCode } from "@/layouts/schedule/helpers/schedule-helper";
|
import { isDropOffRouteCode } from "@/layouts/schedule/helpers/schedule-helper";
|
||||||
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
||||||
import { settleAllPromises } from "@/helpers/layout-helper";
|
import { settleAllPromises } from "@/helpers/layout-helper";
|
||||||
import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper";
|
import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper";
|
||||||
import { getDisplayTextForDurationLength } from "@/helpers/duration-length-helper";
|
import { getDisplayTextForDurationLength } from "@/helpers/duration-length-helper";
|
||||||
|
import { partTypeStrings } from "@/constants/part-type-strings";
|
||||||
import {
|
import {
|
||||||
flushPagePrereqsLogs,
|
flushPagePrereqsLogs,
|
||||||
hasServiceZipInfo,
|
hasServiceZipInfo,
|
||||||
|
|
@ -394,6 +406,8 @@ export default {
|
||||||
zipSearchCode: store.getters.order.serviceLocation.zipCode ?? "",
|
zipSearchCode: store.getters.order.serviceLocation.zipCode ?? "",
|
||||||
datePickerKey: 0,
|
datePickerKey: 0,
|
||||||
billToAccountNumber: store.getters.order.payment?.billToAccountNumber ?? null,
|
billToAccountNumber: store.getters.order.payment?.billToAccountNumber ?? null,
|
||||||
|
isRecalAcknowledgedForScheduling:
|
||||||
|
store.getters.order.isRecalAcknowledgedForScheduling ?? "",
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
@ -425,6 +439,17 @@ export default {
|
||||||
? { providerNumber: mobileProviderNumber, timeSlots: null }
|
? { providerNumber: mobileProviderNumber, timeSlots: null }
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
|
var gaLabel = this.GaLabels.NO;
|
||||||
|
if (this.mobileProviderAndTimeSlot) {
|
||||||
|
gaLabel = this.GaLabels.YES;
|
||||||
|
}
|
||||||
|
this.pushEventToGA(
|
||||||
|
this.GaCategories.APPOINTMENT,
|
||||||
|
this.GaActions.MOBILE_AVAILABLE,
|
||||||
|
gaLabel,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
const startDate = toDateString(0);
|
const startDate = toDateString(0);
|
||||||
const endDate = toDateString(SCHEDULE_FETCH_DAYS - 1);
|
const endDate = toDateString(SCHEDULE_FETCH_DAYS - 1);
|
||||||
const providerNumbers = providers.map((provider) => provider.providerNumber);
|
const providerNumbers = providers.map((provider) => provider.providerNumber);
|
||||||
|
|
@ -503,6 +528,15 @@ export default {
|
||||||
this.$refs.schedulingZipSearch?.focusZipInput();
|
this.$refs.schedulingZipSearch?.focusZipInput();
|
||||||
},
|
},
|
||||||
async onZipSearched({ zipCode, billToAccountNumber }) {
|
async onZipSearched({ zipCode, billToAccountNumber }) {
|
||||||
|
// When recal ack applies, changing zip must re-run parts selection via service-zip.
|
||||||
|
if (this.shouldShowRecalAckModal()) {
|
||||||
|
this.$router.navigateWithSaving(
|
||||||
|
this.navigationScenarios.ZIP_CODE_CHANGED_RECAL_ACK,
|
||||||
|
this.pageName
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.billToAccountNumber = billToAccountNumber;
|
this.billToAccountNumber = billToAccountNumber;
|
||||||
this.isLoadingDates = true;
|
this.isLoadingDates = true;
|
||||||
try {
|
try {
|
||||||
|
|
@ -731,9 +765,20 @@ export default {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
async forwardButtonAction() {
|
async forwardButtonAction() {
|
||||||
|
if (this.shouldShowRecalAckModal()) {
|
||||||
|
this.$refs.navbar.removeLoader();
|
||||||
|
this.$refs.recalAckModal.openModal();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const selectedScheduling = this.selectedScheduling;
|
const selectedScheduling = this.selectedScheduling;
|
||||||
const selectedDate = this.selectedDate;
|
const selectedDate = this.selectedDate;
|
||||||
|
|
||||||
|
await store.dispatch(
|
||||||
|
storeActions.SAVE_IS_RECAL_ACKNOWLEDGED_FOR_SCHEDULING,
|
||||||
|
this.isRecalAcknowledgedForScheduling
|
||||||
|
);
|
||||||
|
|
||||||
const appointmentType = this.getInShopOrDropOffApptType(selectedScheduling);
|
const appointmentType = this.getInShopOrDropOffApptType(selectedScheduling);
|
||||||
const selectedProvider = this.getSelectedProvider(appointmentType, selectedScheduling);
|
const selectedProvider = this.getSelectedProvider(appointmentType, selectedScheduling);
|
||||||
const serviceLocation = store.getters.order.serviceLocation;
|
const serviceLocation = store.getters.order.serviceLocation;
|
||||||
|
|
@ -826,6 +871,23 @@ export default {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
shouldShowRecalAckModal() {
|
||||||
|
if (this.isRecalAcknowledgedForScheduling === RECAL_ACK_YES) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const glassParts = store.getters.order.lineItems?.glassParts ?? [];
|
||||||
|
return glassParts.some(
|
||||||
|
(part) =>
|
||||||
|
part.partType === partTypeStrings.WINDSHIELD &&
|
||||||
|
part.requiresRecalibration === true &&
|
||||||
|
part.canSafeliteRecalibrate === false
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onRecalAcknowledged(isAcknowledged) {
|
||||||
|
this.isRecalAcknowledgedForScheduling = isAcknowledged;
|
||||||
|
this.forwardButtonAction();
|
||||||
|
},
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
funnelHeader,
|
funnelHeader,
|
||||||
|
|
@ -838,6 +900,7 @@ export default {
|
||||||
interceptOverlay,
|
interceptOverlay,
|
||||||
waitlistQuestion,
|
waitlistQuestion,
|
||||||
schedulingZipSearch,
|
schedulingZipSearch,
|
||||||
|
recalAckModal,
|
||||||
textLink,
|
textLink,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue