CASH-2563

This commit is contained in:
credelinghuys 2026-05-07 12:52:02 -04:00
parent 1fb36e7ee7
commit dd291a980c
9 changed files with 172 additions and 34 deletions

View file

@ -1,5 +1,5 @@
const bailoutCodes = { const bailoutCodes = {
PARTS_NOT_FOUND: "PartsNotFound", PARTS_NOT_FOUND: 10,
}; };
export { bailoutCodes }; export { bailoutCodes };

View file

@ -57,15 +57,13 @@ export async function saveSession({
shouldAwaitSaveSessionQueue = false, shouldAwaitSaveSessionQueue = false,
submitAfterSave = false, submitAfterSave = false,
createUnscheduledStatusWorkOrderForPIA = false, createUnscheduledStatusWorkOrderForPIA = false,
bailoutCode = null,
}) { }) {
// Add next save session to end of queue // Add next save session to end of queue
const saveSessionPromise = flushSaveSessionQueue().then(() => { const saveSessionPromise = flushSaveSessionQueue().then(() => {
return saveSessionHelper( return saveSessionHelper(
pageNameToLog, pageNameToLog,
submitAfterSave, submitAfterSave,
createUnscheduledStatusWorkOrderForPIA, createUnscheduledStatusWorkOrderForPIA
bailoutCode
); );
}); });
@ -108,12 +106,11 @@ export async function saveQuote({ pageNameToLog }) {
return; return;
} }
export async function submitBailout({ pageNameToLog, bailoutCode }) { export async function submitBailout({ pageNameToLog }) {
await saveSession({ await saveSession({
pageNameToLog: pageNameToLog, pageNameToLog: pageNameToLog,
shouldAwaitSaveSessionQueue: true, shouldAwaitSaveSessionQueue: true,
submitAfterSave: false, submitAfterSave: false,
bailoutCode: bailoutCode,
}); });
} }
@ -158,15 +155,13 @@ async function loadSession(
async function saveSessionHelper( async function saveSessionHelper(
pageNameToLog, pageNameToLog,
submitAfterSave = false, submitAfterSave = false,
createUnscheduledStatusWorkOrderForPIA = false, createUnscheduledStatusWorkOrderForPIA = false
bailoutCode = null
) { ) {
const savedSessionInfo = await baseMixin.methods.dispatchStoreActionWithLogging( const savedSessionInfo = await baseMixin.methods.dispatchStoreActionWithLogging(
storeActions.SAVE_SESSION, storeActions.SAVE_SESSION,
{ {
submitAfterSave: submitAfterSave, submitAfterSave: submitAfterSave,
createUnscheduledStatusWorkOrderForPIA: createUnscheduledStatusWorkOrderForPIA, createUnscheduledStatusWorkOrderForPIA: createUnscheduledStatusWorkOrderForPIA,
bailoutCode: bailoutCode,
}, },
pageNameToLog pageNameToLog
); );

View file

@ -0,0 +1,56 @@
// Components
import bailoutSuccess from "@/layouts/bailout-success/bailout-success.vue";
// Supporting Files
import { shallowMount } from "@vue/test-utils";
import { getMountOptions } from "@/helpers/unit-test-helper.js";
// Mock our module for promises.
jest.mock("@/helpers/layout-helper.js", () => ({
settleAllPromises: jest.fn(),
}));
// Mock fetchCmsContentForPage
jest.mock("@/helpers/cms-content-helper", () => ({
fetchCmsContentForPage: jest.fn(),
}));
describe("bailout-success.vue", () => {
test("renders funnelHeader component", () => {
const { wrapper } = setupMocks();
expect(wrapper.findComponent({ name: "funnelHeader" }).exists()).toBe(true);
});
test("renders funnelSubHeader component", () => {
const { wrapper } = setupMocks();
expect(wrapper.findComponent({ name: "funnelSubHeader" }).exists()).toBe(true);
});
test("renders Form component", () => {
const { wrapper } = setupMocks();
expect(wrapper.findComponent({ name: "Form" }).exists()).toBe(true);
});
test("renders buttonMain component", () => {
const { wrapper } = setupMocks();
expect(wrapper.findComponent({ name: "buttonMain" }).exists()).toBe(true);
});
});
function setupMocks() {
const mountOptions = getMountOptions({});
//Mock props
const mockMixin = {
methods: {
getCmsContent: jest.fn(),
},
};
mountOptions.mixins = [mockMixin];
const wrapper = shallowMount(bailoutSuccess, mountOptions);
wrapper.vm.setCmsContent = jest.fn();
return { wrapper };
}

View file

@ -22,7 +22,7 @@
</template> </template>
<script> <script>
import Form from "vee-validate"; import { Form } from "vee-validate";
import funnelHeader from "@/fmg-components/funnel-header/funnel-header"; import funnelHeader from "@/fmg-components/funnel-header/funnel-header";
import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header"; import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header";
import buttonMain from "@/ux-components/button-main/button-main"; import buttonMain from "@/ux-components/button-main/button-main";
@ -62,7 +62,7 @@ export default {
async forwardButtonAction() { async forwardButtonAction() {
this.$router.navigateWithoutSaving( this.$router.navigateWithoutSaving(
this.navigationScenarios.CLICKED_BACK_TO_HOMEPAGE, this.navigationScenarios.CLICKED_BACK_TO_HOMEPAGE,
"bailout-success" this.pageName
); );
}, },
arePagePrerequisitesValid() { arePagePrerequisitesValid() {

View file

@ -16,6 +16,26 @@ jest.mock("@/helpers/cms-content-helper", () => ({
})); }));
describe("bailout.vue", () => { describe("bailout.vue", () => {
test("renders funnelHeader component", () => {
const { wrapper } = setupMocks();
expect(wrapper.findComponent({ name: "funnelHeader" }).exists()).toBe(true);
});
test("renders funnelSubHeader component", () => {
const { wrapper } = setupMocks();
expect(wrapper.findComponent({ name: "funnelSubHeader" }).exists()).toBe(true);
});
test("renders Form component", () => {
const { wrapper } = setupMocks();
expect(wrapper.findComponent({ name: "Form" }).exists()).toBe(true);
});
test("renders navbar component", () => {
const { wrapper } = setupMocks();
expect(wrapper.findComponent({ name: "navbar" }).exists()).toBe(true);
});
test("arePagePrerequisitesValid should be true ", async () => { test("arePagePrerequisitesValid should be true ", async () => {
//Arrange //Arrange
const { wrapper } = setupMocks(); const { wrapper } = setupMocks();

View file

@ -42,6 +42,23 @@
v-model="phoneNumber" v-model="phoneNumber"
validationRules="phone-number-required" /> validationRules="phone-number-required" />
<textboxQuestion
class="mb-4"
cmsWidgetName="ServiceZipQuestionWidget"
v-model="serviceZipCode"
ref="serviceZip"
customInputId="serviceZip"
mask="#####"
validationRules="service-zip-required|service-zip-format" />
<alert
ref="alertInvalidZip"
v-if="displayInvalidZipAlert"
class="mb-4"
cmsWidgetName="AlertInvalidZipWidget"
alertClass="alert-danger"
v-bind:isDismissible="false" />
<checkboxQuestion <checkboxQuestion
class="mb-5" class="mb-5"
cmsWidgetName="TextMeQuestionWidget" cmsWidgetName="TextMeQuestionWidget"
@ -69,6 +86,7 @@ import { Form, defineRule } from "vee-validate";
import funnelHeader from "@/fmg-components/funnel-header/funnel-header"; import funnelHeader from "@/fmg-components/funnel-header/funnel-header";
import navbar from "@/fmg-components/nav-bar/nav-bar"; import navbar from "@/fmg-components/nav-bar/nav-bar";
import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header"; import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header";
import alert from "@/ux-components/alert/alert";
import textboxQuestion from "@/digital-components/textbox-question/textbox-question"; import textboxQuestion from "@/digital-components/textbox-question/textbox-question";
import phoneNumberQuestion from "@/digital-components/phone-number-question/phone-number-question"; import phoneNumberQuestion from "@/digital-components/phone-number-question/phone-number-question";
import textBlock from "@/digital-components/text-block/text-block"; import textBlock from "@/digital-components/text-block/text-block";
@ -95,6 +113,11 @@ defineRule(
errorMessages.EMAIL_ADDRESS_FORMAT errorMessages.EMAIL_ADDRESS_FORMAT
) )
); );
defineRule("service-zip-required", required(errorMessages.SERVICE_ZIP_REQUIRED));
defineRule(
"service-zip-format",
regex(/(^\d{5}$)|(^\d{5}-\d{4}$)/, errorMessages.SERVICE_ZIP_FORMAT)
);
export default { export default {
name: "bailout", name: "bailout",
@ -106,7 +129,9 @@ export default {
lastName: this.getLastNameFromStore(), lastName: this.getLastNameFromStore(),
emailAddress: this.getEmailAddressFromStore(), emailAddress: this.getEmailAddressFromStore(),
phoneNumber: this.getPhoneNumberFromStore(), phoneNumber: this.getPhoneNumberFromStore(),
serviceZipCode: this.getServiceZipFromStore(),
isSmsOptIn: this.getIsSmsOptInFromStore(), isSmsOptIn: this.getIsSmsOptInFromStore(),
displayInvalidZipAlert: false,
}; };
}, },
@ -146,6 +171,9 @@ export default {
getPhoneNumberFromStore() { getPhoneNumberFromStore() {
return store.getters.order.customer.phoneNumber; return store.getters.order.customer.phoneNumber;
}, },
getServiceZipFromStore() {
return store.getters.order.serviceLocation.zipCode;
},
getIsSmsOptInFromStore() { getIsSmsOptInFromStore() {
return store.getters.order.customer.isSmsOptIn; return store.getters.order.customer.isSmsOptIn;
}, },
@ -153,7 +181,17 @@ export default {
this.$router.go(-1); this.$router.go(-1);
}, },
async forwardButtonAction() { async forwardButtonAction() {
await this.dispatchStoreAction( this.displayInvalidZipAlert = false;
const validateZipResponse = this.dispatchStoreActionWithLogging(
this.storeActions.VALIDATE_ZIP,
{
zip: this.serviceZipCode,
},
this.pageName
);
const saveCustomerDetailsResponse = this.dispatchStoreAction(
this.storeActions.SAVE_CUSTOMER_DETAILS, this.storeActions.SAVE_CUSTOMER_DETAILS,
{ {
firstName: this.firstName, firstName: this.firstName,
@ -163,15 +201,45 @@ export default {
isSmsOptIn: this.isSmsOptIn, isSmsOptIn: this.isSmsOptIn,
}, },
false false
).then(() => { );
this.$router.navigateWithPageData(
this.navigationScenarios.CLICKED_FORWARD, const promiseResultMap = [
this.pageName, {
resultKey: "validateZipResponse",
promise: validateZipResponse,
},
{
resultKey: "saveCustomerDetailsResponse",
promise: saveCustomerDetailsResponse,
},
];
const resultMap = await settleAllPromises(promiseResultMap);
const isZipValid = resultMap.validateZipResponse.isValid;
if (isZipValid) {
await this.dispatchStoreActionWithLogging(
this.storeActions.SAVE_SERVICE_ZIP_CODE_INFO,
{ {
bailoutCode: this.bailoutCode, zipCode: this.serviceZipCode,
} state: resultMap.validateZipResponse.state,
); zipCodeCtu: resultMap.validateZipResponse.zipCodeCtu,
}); },
false
).then(() => {
this.$router.navigateWithPageData(
this.navigationScenarios.CLICKED_FORWARD,
this.pageName,
{
bailoutCode: this.bailoutCode,
submit: true,
}
);
});
} else {
this.displayInvalidZipAlert = true;
return this.$refs.navbar.removeLoader();
}
}, },
arePagePrerequisitesValid() { arePagePrerequisitesValid() {
return true; return true;
@ -192,6 +260,7 @@ export default {
Form, Form,
funnelHeader, funnelHeader,
funnelSubHeader, funnelSubHeader,
alert,
navbar, navbar,
textBlock, textBlock,
phoneNumberQuestion, phoneNumberQuestion,

View file

@ -816,7 +816,7 @@ const routingTable = function () {
maps: [ maps: [
{ {
scenario: navigationScenarios.CLICKED_BACK_TO_HOMEPAGE, scenario: navigationScenarios.CLICKED_BACK_TO_HOMEPAGE,
destinationPageData: routeData.VEHICLE, destinationPageData: routeData.RESTART,
}, },
], ],
}, },

View file

@ -7,13 +7,7 @@ import store from "@/store";
import { handleSoftError } from "@/router/methods/error"; import { handleSoftError } from "@/router/methods/error";
async function navigate( async function navigate(scenario, currentPageName, withSaving = false, forceTopLevelNav = false) {
scenario,
currentPageName,
withSaving = false,
forceTopLevelNav = false,
bailoutCode = null
) {
// Check to see if calling page is same as current page. // Check to see if calling page is same as current page.
// If not, cancel navigation before it begins. // If not, cancel navigation before it begins.
const callingRoute = currentPageName; const callingRoute = currentPageName;
@ -45,7 +39,7 @@ async function navigate(
store.getters?.applicationUser?.savedSessionId || store.getters?.applicationUser?.savedSessionId ||
store.getters?.order?.customer?.emailAddress store.getters?.order?.customer?.emailAddress
) { ) {
await saveSession({ pageNameToLog: nextPage.name, bailoutCode: bailoutCode }); await saveSession({ pageNameToLog: nextPage.name });
} }
} }
@ -76,13 +70,15 @@ export async function navigateWithSaving(scenario, currentPageName) {
export async function navigateWithPageData(scenario, currentPageName, pageData = {}) { export async function navigateWithPageData(scenario, currentPageName, pageData = {}) {
const nextPage = getDestination(currentPageName, scenario); const nextPage = getDestination(currentPageName, scenario);
if (pageData && pageData.bailoutCode) { if (pageData && pageData.bailoutCode) {
pageData.AppName = "FixMyGlass";
await savePageData(currentPageName, pageData); await savePageData(currentPageName, pageData);
return await navigate(scenario, currentPageName, true, false, pageData.bailoutCode);
} else { } else {
await savePageData(nextPage.name, pageData); await savePageData(nextPage.name, pageData);
return await navigate(scenario, currentPageName, true);
} }
return await navigate(scenario, currentPageName, true);
} }
export async function navigateAndForceTopLevelNavigation( export async function navigateAndForceTopLevelNavigation(

View file

@ -462,9 +462,11 @@ export const mutations = {
state.order.vehicle.registration.licensePlate = registrationInfo?.licensePlate; state.order.vehicle.registration.licensePlate = registrationInfo?.licensePlate;
}, },
updateServiceZip(state, serviceZipInfo) { updateServiceZip(state, serviceZipInfo) {
state.order.serviceLocation.state = serviceZipInfo.state; state.order.serviceLocation.state = serviceZipInfo.state || serviceZipInfo.payload?.state;
state.order.serviceLocation.zipCode = serviceZipInfo.zipCode; state.order.serviceLocation.zipCode =
state.order.serviceLocation.zipCodeCtu = serviceZipInfo.zipCodeCtu; serviceZipInfo.zipCode || serviceZipInfo.payload?.zipCode;
state.order.serviceLocation.zipCodeCtu =
serviceZipInfo.zipCodeCtu || serviceZipInfo.payload?.zipCodeCtu;
}, },
updateServiceLocation(state, serviceLocationInfo) { updateServiceLocation(state, serviceLocationInfo) {
state.order.serviceLocation.address = serviceLocationInfo.address; state.order.serviceLocation.address = serviceLocationInfo.address;