Merge branch 'develop' into bryan-jason-vin-capture

This commit is contained in:
Jason Wheeler 2022-12-14 09:07:39 -05:00
commit 546dd494ac
47 changed files with 779 additions and 353 deletions

View file

@ -15,10 +15,6 @@
data-bs-target="#footerModal" data-bs-target="#footerModal"
data-bs-dismiss="modal" /> data-bs-dismiss="modal" />
</div> </div>
<!-- TODO KO DELETE FOR QUOTE MVP -->
<div class="col-auto">
<button @click="$emit('tempButtonClicked')">Heritage</button>
</div>
<div v-if="!isBackButtonHidden" class="col-auto link-col py-1 text-break"> <div v-if="!isBackButtonHidden" class="col-auto link-col py-1 text-break">
<textLink <textLink
linkType="navigation" linkType="navigation"

View file

@ -27,7 +27,7 @@
ref="funnelFooter" ref="funnelFooter"
cmsWidgetName="FunnelFooterWidget" cmsWidgetName="FunnelFooterWidget"
:isForwardActionDisabled="!isMetaValid" :isForwardActionDisabled="!isMetaValid"
@back-clicked="handleBackButtonClicked" @back-clicked="handleBackButtonAction"
@ForwardClicked="handleForwardButtonAction" /> @ForwardClicked="handleForwardButtonAction" />
</div> </div>
</div> </div>

View file

@ -2,6 +2,7 @@
<!-- Modal --> <!-- Modal -->
<div <div
class="modal fade modal-component" class="modal fade modal-component"
v-on="{ 'hidden.bs.modal': resetButtonStyle }"
:id="this.cmsWidgetName" :id="this.cmsWidgetName"
tabindex="-1" tabindex="-1"
aria-labelledby="ModalComponentLabel" aria-labelledby="ModalComponentLabel"
@ -60,11 +61,10 @@ export default {
return this.getCmsContent(this.cmsWidgetName, "FooterText"); return this.getCmsContent(this.cmsWidgetName, "FooterText");
}, },
}, },
mounted() { methods: {
const modal = document.querySelector("#" + this.cmsWidgetName); resetButtonStyle() {
modal.addEventListener("hidden.bs.modal", (event) => {
this.$refs.buttonMain.resetButtonStyle(); this.$refs.buttonMain.resetButtonStyle();
}); },
}, },
components: { components: {
buttonMain, buttonMain,

View file

@ -100,7 +100,7 @@ export default {
return false; return false;
} }
// Example returnedAnswers: // returnedAnswer examples:
// "1|nextQuestion|3|No" // "1|nextQuestion|3|No"
// "5|answer|DW02104|Yes" // "5|answer|DW02104|Yes"
@ -108,15 +108,12 @@ export default {
const questionNum = parseInt(returnedAnswerArray[0]); const questionNum = parseInt(returnedAnswerArray[0]);
const questionType = returnedAnswerArray[1]; const questionType = returnedAnswerArray[1];
const questionAnswer = returnedAnswerArray[2]; const questionAnswer = returnedAnswerArray[2];
const questionAnswerText = returnedAnswerArray[3];
const answeredQuestions = []; const answeredQuestions = [];
this.questions.forEach((q) => { this.questions.forEach((q) => {
// find this question and mark it as "answered" by populating answerSelected // find this question and mark it as "answered" by populating answerSelected
if (q.questionSequence === questionNum) { if (q.questionSequence === questionNum) {
q.answerSelected = returnedAnswer; q.answerSelected = returnedAnswer;
q.answerNumber = questionNum;
q.selectedAnswerText = questionAnswerText;
} }
// remove all answers AFTER this question... // remove all answers AFTER this question...
// (needed in case user is changing previously answered questions) // (needed in case user is changing previously answered questions)
@ -126,6 +123,7 @@ export default {
if (q.answerSelected) { if (q.answerSelected) {
answeredQuestions.push({ answeredQuestions.push({
questionText: q.questionText, questionText: q.questionText,
selectedAnswer: q.answerSelected,
selectedAnswerText: q.answerSelected.split("|")[3], selectedAnswerText: q.answerSelected.split("|")[3],
questionNum: q.questionSequence, questionNum: q.questionSequence,
}); });

View file

@ -53,7 +53,7 @@ describe("textboxQuestion.vue", () => {
expect(paragraph.attributes("class")).toContain("rounded-pill"); expect(paragraph.attributes("class")).toContain("rounded-pill");
}); });
it.only("Should return form-control class", async () => { it("Should return form-control class", async () => {
// Act // Act
const wrapper = shallowMount(textboxQuestion, { const wrapper = shallowMount(textboxQuestion, {
global: { global: {

View file

@ -8,6 +8,7 @@ const applicationConfig = {
APPLICATION_NAME: "FixMyGlass", APPLICATION_NAME: "FixMyGlass",
APPLICATION_ABBREVIATION: "fmg", APPLICATION_ABBREVIATION: "fmg",
SITE_ENTRY_TRIGGER_VALUE: "FixMyGlass", SITE_ENTRY_TRIGGER_VALUE: "FixMyGlass",
CASH_ACCOUNT_NUMBER: 167132,
}; };
export { applicationConfig }; export { applicationConfig };

View file

@ -96,7 +96,7 @@ const endpoints = {
}, },
PriceOrderItems: { PriceOrderItems: {
url: "/price/api/v1/price/order-items", url: "/price/api/v1/price/order-items",
method: "POST", method: "GET",
}, },
LogExperimentExposureIfAssigned: { LogExperimentExposureIfAssigned: {
url: "/experiments/api/v1/experiments/log-exposure", url: "/experiments/api/v1/experiments/log-exposure",

View file

@ -4,6 +4,7 @@ const experimentUniverses = {
const experimentSettings = { const experimentSettings = {
GOOGLE_CUSTOM_DIMENSION_INDEX: "Google Custom Dimension Index", GOOGLE_CUSTOM_DIMENSION_INDEX: "Google Custom Dimension Index",
SUPPRESS_VIN_CAPTURE: "SuppressVinCapture",
}; };
const experimentTriggers = { const experimentTriggers = {

View file

@ -67,6 +67,10 @@ const storeActions = {
SAVE_MOLDING_QUESTION_ANSWERS: "saveMoldingQuestionAnswers", SAVE_MOLDING_QUESTION_ANSWERS: "saveMoldingQuestionAnswers",
SAVE_CAPABILITY_QUESTION_ANSWERS: "saveCapabilityQuestionAnswers", SAVE_CAPABILITY_QUESTION_ANSWERS: "saveCapabilityQuestionAnswers",
SAVE_QUOTE_PAGE_SELECTIONS: "saveQuotePageSelections", SAVE_QUOTE_PAGE_SELECTIONS: "saveQuotePageSelections",
SAVE_PAYMENT_TYPE: "savePaymentType",
SAVE_ACCOUNT_NUMBER: "saveAccountNumber",
SAVE_SUPPORTING_ITEMS: "saveSupportingItems",
SAVE_VAPS: "saveVaps",
}; };
export { storeActions }; export { storeActions };

View file

@ -21,6 +21,7 @@ const storeMutations = {
UPDATE_GLASS_PARTS: "updateGlassParts", UPDATE_GLASS_PARTS: "updateGlassParts",
UPDATE_VAPS: "updateVaps", UPDATE_VAPS: "updateVaps",
UPDATE_SUPPORTING_ITEMS: "updateSupportingItems", UPDATE_SUPPORTING_ITEMS: "updateSupportingItems",
UPDATE_LINE_ITEMS_SERVER_DATA: "updateLineItemsServerData",
UPDATE_REGISTRATION_LICENSE_PLATE: "updateRegistrationLicensePlate", UPDATE_REGISTRATION_LICENSE_PLATE: "updateRegistrationLicensePlate",
UPDATE_REGISTRATION_ADDRESS: "updateRegistrationAddress", UPDATE_REGISTRATION_ADDRESS: "updateRegistrationAddress",
@ -31,8 +32,6 @@ const storeMutations = {
UPDATE_REGISTRATION_LAST_NAME: "updateRegistrationLastName", UPDATE_REGISTRATION_LAST_NAME: "updateRegistrationLastName",
UPDATE_REGISTRATION: "updateRegistration", UPDATE_REGISTRATION: "updateRegistration",
UPDATE_SERVICE_LOCATION_ZIP_CODE: "updateServiceLocationZipCode",
UPDATE_SERVICE_LOCATION_STATE: "updateServiceLocationState",
UPDATE_SERVICE_LOCATION: "updateServiceLocation", UPDATE_SERVICE_LOCATION: "updateServiceLocation",
UPDATE_CUSTOMER_EMAIL_ADDRESS: "updateCustomerEmailAddress", UPDATE_CUSTOMER_EMAIL_ADDRESS: "updateCustomerEmailAddress",
@ -56,8 +55,6 @@ const storeMutations = {
RESET_DAMAGE_STATE: "resetDamageState", RESET_DAMAGE_STATE: "resetDamageState",
RESET_REGISTRATION_STATE: "resetRegistrationState", RESET_REGISTRATION_STATE: "resetRegistrationState",
RESET_GLASS_PARTS_STATE: "resetGlassPartsState", RESET_GLASS_PARTS_STATE: "resetGlassPartsState",
RESET_SUPPORTING_ITEMS_STATE: "resetSupportingItemsState",
RESET_VAPS_STATE: "resetVapsState",
RESET_STATE: "resetState", RESET_STATE: "resetState",
RESET_SAVE_SESSION_PROMISE: "resetSaveSessionPromise", RESET_SAVE_SESSION_PROMISE: "resetSaveSessionPromise",

View file

@ -78,7 +78,7 @@ const tintMap = {
// Returns undefined if no items are found. // Returns undefined if no items are found.
export function getTintImage(glassLocation, colorString) { export function getTintImage(glassLocation, colorString) {
// Windshield glass has special images, all other glass uses the same though. // Windshield glass has special images, all other glass uses the same though.
if (glassLocation.toLowerCase() !== "windshield") { if (glassLocation?.toLowerCase() !== "windshield") {
glassLocation = "other"; glassLocation = "other";
} }

View file

@ -37,7 +37,9 @@ export function getDamageString() {
export function getIsWindshieldOnly() { export function getIsWindshieldOnly() {
const damageLocations = store.getters.damage.glassToReplace; const damageLocations = store.getters.damage.glassToReplace;
const returnString = const returnString =
damageLocations.length === 1 && damageLocations[0]?.glassLocation === "Windshield" damageLocations &&
damageLocations.length === 1 &&
damageLocations[0]?.glassLocation === "Windshield"
? "windshield" ? "windshield"
: "glass"; : "glass";
return returnString; return returnString;

View file

@ -5,6 +5,8 @@ import { saveSession } from "@/helpers/heritage-integration/order-helper.js";
import { fmgPageValues } from "@/router/router-constants/fmgPage-values"; import { fmgPageValues } from "@/router/router-constants/fmgPage-values";
import { storeActions } from "@/constants/store-actions.js"; import { storeActions } from "@/constants/store-actions.js";
import { settleAllPromises } from "@/helpers/layout-helper"; import { settleAllPromises } from "@/helpers/layout-helper";
import experimentMixin from "@/mixins/experiment-mixin";
import { experimentSettings } from "@/constants/experiments";
import store from "@/store"; import store from "@/store";
import router from "@/router"; import router from "@/router";
@ -41,19 +43,51 @@ export async function getPageToRouteExistingOrderTo(toRoute = {}, existingHerita
Used to navigate to the heritage funnel with the correct query string and url. Used to navigate to the heritage funnel with the correct query string and url.
*/ */
export async function navigateToHeritageFunnel(shouldSaveSession = true) { export async function navigateToHeritageFunnel({ shouldSaveSession = true, loadingModal }) {
// Create the order (or save existing order) when navigating to Heritage Funnel. // Create the order (or save existing order) when navigating to Heritage Funnel.
if (shouldSaveSession) { if (shouldSaveSession) {
await saveSession(); await saveSession();
} }
if (loadingModal && loadingModal.showModal) {
loadingModal.showModal();
}
router.navigateToExternalUrl(externalUrls.HERITAGE_FUNNEL, { router.navigateToExternalUrl(externalUrls.HERITAGE_FUNNEL, {
corid: store.getters.order.referralCorrelationId, corid: store.getters.order.referralCorrelationId,
src: "concept-funnel", src: "concept-funnel",
conceptsqid: store.getters.applicationUser.savedSessionId, conceptsqid: store.getters.applicationUser.savedSessionId,
isInsurance: store.getters.payment.isInsurance,
}); });
} }
export async function skipVinLookup() {
const isVinOptionalVehicle = store.getters.order.vehicle.make
? await store.dispatch(storeActions.IS_VIN_OPTIONAL_VEHICLE)
: false;
return (
store.getters.damage.isRepair ||
isVinOptionalVehicle ||
experimentMixin.methods.hasSettingEqualTo(experimentSettings.SUPPRESS_VIN_CAPTURE, "true")
);
}
export async function skipVinLookupNotRepair() {
const isVinOptionalVehicle = store.getters.order.vehicle.make
? await store.dispatch(storeActions.IS_VIN_OPTIONAL_VEHICLE)
: false;
return (
!store.getters.damage.isRepair &&
(isVinOptionalVehicle ||
experimentMixin.methods.hasSettingEqualTo(
experimentSettings.SUPPRESS_VIN_CAPTURE,
"true"
))
);
}
/* /*
Logic for getting the last "valid" page a user visited. Logic for getting the last "valid" page a user visited.
*/ */
@ -74,9 +108,7 @@ async function getLatestPageForRedirection() {
fmgPageValues.CAPABILITY_QUESTIONS fmgPageValues.CAPABILITY_QUESTIONS
); );
const isVinOptionalVehicle = store.getters.order.vehicle.make const skipVin = await skipVinLookup();
? await store.dispatch(storeActions.IS_VIN_OPTIONAL_VEHICLE)
: false;
if (!vehicleMakeComponent.methods.arePagePrerequisitesValid()) { if (!vehicleMakeComponent.methods.arePagePrerequisitesValid()) {
return fmgPageValues.VEHICLE_YEAR; return fmgPageValues.VEHICLE_YEAR;
@ -98,12 +130,13 @@ async function getLatestPageForRedirection() {
} else if (partQuestionsComponent.methods.arePagePrerequisitesValid()) { } else if (partQuestionsComponent.methods.arePagePrerequisitesValid()) {
return fmgPageValues.PART_QUESTIONS; return fmgPageValues.PART_QUESTIONS;
} else if ( } else if (
// capture vin
vinLookupComponent.methods.arePagePrerequisitesValid() && vinLookupComponent.methods.arePagePrerequisitesValid() &&
!store.getters.damage.isRepair && !skipVin
!isVinOptionalVehicle
) { ) {
return fmgPageValues.VIN_LOOKUP; return fmgPageValues.VIN_LOOKUP;
} else { } else {
// do not capture vin
return fmgPageValues.ESTIMATE; return fmgPageValues.ESTIMATE;
} }
} }

View file

@ -144,6 +144,16 @@ describe("getPageToRouteExistingOrderTo", () => {
store.commit(storeMutations.UPDATE_IS_REPAIR, false); store.commit(storeMutations.UPDATE_IS_REPAIR, false);
store.commit(storeMutations.UPDATE_MAKE, "acura"); store.commit(storeMutations.UPDATE_MAKE, "acura");
const mockExperimentsList = [
{
universeName: "ConceptFunnel",
settings: {
SuppressVinCapture: false,
},
},
];
store.commit(storeMutations.UPDATE_EXPERIMENTS, mockExperimentsList);
// Mock out the lazy load calls for all components. // Mock out the lazy load calls for all components.
mockLazyLoadComponentReturnValues({ mockLazyLoadComponentReturnValues({
[fmgPageValues.VEHICLE_MAKE]: true, [fmgPageValues.VEHICLE_MAKE]: true,
@ -367,7 +377,7 @@ describe("navigateToHeritageFunnel", () => {
router.navigateToExternalUrl = jest.fn(); router.navigateToExternalUrl = jest.fn();
// Act // Act
await navigateToHeritageFunnel(); await navigateToHeritageFunnel({});
// Assert // Assert
expect(saveSessionFunction).toHaveBeenCalled(); expect(saveSessionFunction).toHaveBeenCalled();
@ -414,7 +424,7 @@ describe("navigateToHeritageFunnel", () => {
router.navigateToExternalUrl = jest.fn(); router.navigateToExternalUrl = jest.fn();
// Act // Act
await navigateToHeritageFunnel(); await navigateToHeritageFunnel({});
// Assert // Assert
expect(router.navigateToExternalUrl).toHaveBeenCalled(); expect(router.navigateToExternalUrl).toHaveBeenCalled();
@ -426,7 +436,7 @@ describe("navigateToHeritageFunnel", () => {
); );
}); });
test("should not save session, but should still navigate", async () => { test("shouldSaveSession is false => should not save session, but should still navigate", async () => {
// Arrange // Arrange
const mockReferralNumber = "2"; const mockReferralNumber = "2";
const mockCorrelationId = "55"; const mockCorrelationId = "55";
@ -452,7 +462,7 @@ describe("navigateToHeritageFunnel", () => {
router.navigateToExternalUrl = jest.fn(); router.navigateToExternalUrl = jest.fn();
// Act // Act
await navigateToHeritageFunnel(false); await navigateToHeritageFunnel({ shouldSaveSession: false });
// Assert // Assert
expect(saveSessionFunction).not.toHaveBeenCalled(); expect(saveSessionFunction).not.toHaveBeenCalled();

View file

@ -30,7 +30,14 @@ export async function loadSessionIfPresent() {
} }
// Loads session including referral if there is a cookie, and it doesn't indicate it needs a state reset. // Loads session including referral if there is a cookie, and it doesn't indicate it needs a state reset.
return (await loadSession(funnelCookie.SavedSessionId)).data; return (
await loadSession(
funnelCookie.SavedSessionId,
funnelCookie.ReferralNumber,
funnelCookie.ReferralParentAccountNumber,
funnelCookie.ReferralCorrelationId
)
).data;
} }
/* /*
@ -61,13 +68,16 @@ export async function saveSession() {
Calls API to load session given the referral number, referralDate, and referralCorrelationId Calls API to load session given the referral number, referralDate, and referralCorrelationId
and returns the response. and returns the response.
*/ */
async function loadSession(savedSessionId) { async function loadSession(savedSessionId, referralNumber, accountNumber, referralCorrelationId) {
// await the saveSessionPromise in the store to make sure we're loading up to date information // await the saveSessionPromise in the store to make sure we're loading up to date information
await store.getters.applicationUser.saveSessionPromise; await store.getters.applicationUser.saveSessionPromise;
const response = await baseMixin.methods.dispatchStoreAction( const response = await baseMixin.methods.dispatchStoreAction(
storeActions.LOAD_SESSION, storeActions.LOAD_SESSION,
{ {
savedSessionId: savedSessionId?.toString(), savedSessionId: savedSessionId?.toString(),
referralNumber,
accountNumber,
referralCorrelationId,
}, },
false false
); );
@ -84,12 +94,13 @@ async function saveSessionHelper() {
await baseMixin.methods.dispatchStoreAction( await baseMixin.methods.dispatchStoreAction(
storeActions.UPDATE_STORE_WITH_SAVE_SESSION_RESPONSE, storeActions.UPDATE_STORE_WITH_SAVE_SESSION_RESPONSE,
{ {
referralNumber: savedSessionInfo.data.referralNumber.toString(), referralNumber: savedSessionInfo.data.referralNumber?.toString(),
referralCorrelationId: savedSessionInfo.data.referralCorrelationId, referralCorrelationId: savedSessionInfo.data.referralCorrelationId,
referralDate: savedSessionInfo.data.referralDate, referralDate: savedSessionInfo.data.referralDate,
accountNumber: savedSessionInfo.data.accountNumber.toString(), accountNumber: savedSessionInfo.data.accountNumber?.toString(),
savedSessionId: savedSessionInfo.data.savedSessionId, savedSessionId: savedSessionInfo.data.savedSessionId,
crmCustomerId: savedSessionInfo.data.crmCustomerId.toString(), crmCustomerId: savedSessionInfo.data.crmCustomerId?.toString(),
eon: savedSessionInfo.data.eon,
}, },
false false
); );

View file

@ -560,6 +560,7 @@ describe("address-lookup.vue", () => {
// Act // Act
await wrapper.vm.forwardButtonAction(); await wrapper.vm.forwardButtonAction();
//FIX THIS
// Assert // Assert
expect(wrapper.vm.dispatchStoreAction).not.toHaveBeenCalledWith( expect(wrapper.vm.dispatchStoreAction).not.toHaveBeenCalledWith(
storeActions.UPDATE_SERVICE_LOCATION_WITH_VEHICLE_REGISTRATION storeActions.UPDATE_SERVICE_LOCATION_WITH_VEHICLE_REGISTRATION

View file

@ -344,6 +344,7 @@ export default {
city: this.customerQuestions.addressQuestions.city, city: this.customerQuestions.addressQuestions.city,
zipCode: this.serviceZipCode, zipCode: this.serviceZipCode,
state: resultMap.serviceZipValidationResponse.state, state: resultMap.serviceZipValidationResponse.state,
zipCodeCtu: resultMap.serviceZipValidationResponse.zipCodeCtu,
}, },
false false
); );

View file

@ -21,7 +21,7 @@ describe("addressVehicles.vue", () => {
// Arrange // Arrange
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
store.commit(storeMutations.UPDATE_CAR_ID, "NOT NULL"); store.commit(storeMutations.UPDATE_CAR_ID, "NOT NULL");
store.commit(storeMutations.UPDATE_SERVICE_LOCATION_ZIP_CODE, "12345"); store.commit(storeMutations.UPDATE_SERVICE_LOCATION, { zipCode: "12345" });
store.commit(storeMutations.UPDATE_CUSTOMER_EMAIL_ADDRESS, "test@test.com"); store.commit(storeMutations.UPDATE_CUSTOMER_EMAIL_ADDRESS, "test@test.com");
// Act // Act

View file

@ -84,12 +84,14 @@ const baseStoreGettersDamage = () => {
{ {
questionText: questionText:
"Is your vehicle equipped with the Panoramic Sunroof which can be identified by having a glass panel over the rear seats?", "Is your vehicle equipped with the Panoramic Sunroof which can be identified by having a glass panel over the rear seats?",
selectedAnswer: "1|nextQuestion|3|Yes",
selectedAnswerText: "Yes", selectedAnswerText: "Yes",
questionNum: 1, questionNum: 1,
}, },
{ {
questionText: questionText:
"Is your vehicle equipped with a heated windshield that melts snow and ice from underneath the windshield wiper blades?", "Is your vehicle equipped with a heated windshield that melts snow and ice from underneath the windshield wiper blades?",
selectedAnswer: "2|nextQuestion|3|Yes",
selectedAnswerText: "Yes", selectedAnswerText: "Yes",
questionNum: 2, questionNum: 2,
}, },
@ -101,6 +103,7 @@ const baseStoreGettersDamage = () => {
store.getters = { store.getters = {
pageData: baseStoreGettersPageData, pageData: baseStoreGettersPageData,
damage: baseStoreGettersDamage, damage: baseStoreGettersDamage,
payment: { insuranceCoverage: {} },
}; };
store.commit = jest.fn(); store.commit = jest.fn();
@ -109,6 +112,7 @@ afterEach(() => {
store.getters = { store.getters = {
pageData: baseStoreGettersPageData, pageData: baseStoreGettersPageData,
damage: baseStoreGettersDamage, damage: baseStoreGettersDamage,
payment: { insuranceCoverage: {} },
}; };
}); });
@ -166,10 +170,10 @@ describe("capabilityQuestions.vue", () => {
}); });
describe("watch on selectedAnswers should be set up...", () => { describe("watch on selectedAnswers should be set up...", () => {
test("Should trigger handleAnswerUpdates if watched data changes", async () => { test("Should trigger handleCompletedQuestionChainAnswers if watched data changes", async () => {
// Arrange // Arrange
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
const spy = jest.spyOn(wrapper.vm, "handleAnswerUpdates"); const spy = jest.spyOn(wrapper.vm, "handleCompletedQuestionChainAnswers");
// Act // Act
wrapper.setData({ wrapper.setData({
@ -180,12 +184,14 @@ describe("capabilityQuestions.vue", () => {
{ {
questionText: questionText:
"Is your vehicle equipped with the Panoramic Sunroof which can be identified by having a glass panel over the rear seats?", "Is your vehicle equipped with the Panoramic Sunroof which can be identified by having a glass panel over the rear seats?",
selectedAnswer: "1|nextQuestion|3|Yes",
selectedAnswerText: "Yes", selectedAnswerText: "Yes",
questionNum: 1, questionNum: 1,
}, },
{ {
questionText: questionText:
"Is your vehicle equipped with a heated windshield that melts snow and ice from underneath the windshield wiper blades?", "Is your vehicle equipped with a heated windshield that melts snow and ice from underneath the windshield wiper blades?",
selectedAnswer: "2|nextQuestion|3|Yes",
selectedAnswerText: "Yes", selectedAnswerText: "Yes",
questionNum: 2, questionNum: 2,
}, },

View file

@ -112,7 +112,7 @@ export default {
"selectedAnswers." + glass.answerKey, "selectedAnswers." + glass.answerKey,
(newValue) => { (newValue) => {
if (newValue && Object.keys(newValue).length > 0) { if (newValue && Object.keys(newValue).length > 0) {
this.handleAnswerUpdates(newValue, glass.answerKey); this.handleCompletedQuestionChainAnswers(newValue, glass.answerKey);
} }
}, },
{ deep: true } { deep: true }
@ -174,9 +174,7 @@ export default {
).parts = partFromCapabilityQuestionAnswer; ).parts = partFromCapabilityQuestionAnswer;
} }
// this.navigateForward(partsOrQuestions); this.navigateForward(partsOrQuestions);
// TODO KO delete after quote MVP
this.navigateForward(partsOrQuestions, null, this.shouldGoToHeritageQuote);
}, },
}, },
components: { components: {

View file

@ -219,27 +219,32 @@ describe("estimate.vue", () => {
}); });
}); });
describe("skipVinLookup", () => { describe("test alertInfo and isRepair", () => {
const skipOptions = [ const skipOptions = [
[true, true, true], [true, false, "AlertQuoteReady"],
[true, false, true], [false, true, "AlertQuoteVinOptional"],
[false, true, true], [false, false, "AlertQuoteReady"],
[false, false, false], [true, true, "AlertQuoteVinOptional"],
]; ];
test.each(skipOptions)( test.each(skipOptions)(
"isRepair %s and isVinOptional %s should return %s", "isRepair %s, skipVinNotRepair %s alertInfo should return %s",
async (isRepair, isVinOptionalVehicle, expectedVinSkip) => { async (isRepair, skipVinNotRepair, expectedAlertInfo) => {
const { wrapper } = setupMocks({ isVinOptionalVehicle: isVinOptionalVehicle }); const { wrapper } = setupMocks({});
await wrapper.setData({
skipVinNotRepair: skipVinNotRepair,
});
store.commit(storeMutations.UPDATE_IS_REPAIR, isRepair); store.commit(storeMutations.UPDATE_IS_REPAIR, isRepair);
expect(wrapper.vm.skipVinLookup).toEqual(expectedVinSkip); expect(wrapper.vm.alertInfo).toEqual(expectedAlertInfo);
expect(wrapper.vm.isRepair).toEqual(isRepair);
} }
); );
}); });
function setupMocks({ function setupMocks({
groupName = "estimate", groupName = "estimate",
isVinOptionalVehicle = false, skipVin = false,
cmsQuestionText = "Let's get your VIN. Or we can look it up for you!", cmsQuestionText = "Let's get your VIN. Or we can look it up for you!",
cmsAnswers = [ cmsAnswers = [
{ Name: "Provide my VIN manually Most specific to your vehicle" }, { Name: "Provide my VIN manually Most specific to your vehicle" },
@ -271,7 +276,10 @@ function setupMocks({
mountOptions["attachTo"] = document.body; mountOptions["attachTo"] = document.body;
const wrapper = shallowMount(estimate, mountOptions); const wrapper = shallowMount(estimate, mountOptions);
wrapper.vm.isVinOptionalVehicle = isVinOptionalVehicle; wrapper.vm.skipVin = skipVin;
wrapper.vm.getZipCodeData = jest
.fn()
.mockReturnValue({ isValid: true, isServiceable: true, state: "OH" });
return { wrapper, apiPromise }; return { wrapper, apiPromise };
} }

View file

@ -1,11 +1,12 @@
<template> <template>
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }"> <Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }">
<div class="page-container-grouped-styles"> <div class="page-container-grouped-styles">
<loadingModal ref="loadingModal" />
<funnelHeader cmsWidgetName="FunnelHeaderWidget" /> <funnelHeader cmsWidgetName="FunnelHeaderWidget" />
<vehicleBanner cmsWidgetName="VehicleBannerWidget" /> <vehicleBanner cmsWidgetName="VehicleBannerWidget" />
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" /> <funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" />
<div class="fade-on-route-transition sub-container make-tall"> <div class="fade-on-route-transition sub-container make-tall">
<div v-if="!skipVinLookup"> <div v-if="!skipVin">
<alert <alert
class="vinLookupMethodHeading" class="vinLookupMethodHeading"
cmsWidgetName="AlertVinLookupQuestion" cmsWidgetName="AlertVinLookupQuestion"
@ -91,6 +92,7 @@ import buttonQuestion from "@/common-components/button-question/button-question"
import alert from "@/ux-components/alert/alert"; import alert from "@/ux-components/alert/alert";
import textboxQuestion from "@/common-components/textbox-question/textbox-question"; import textboxQuestion from "@/common-components/textbox-question/textbox-question";
import textBlock from "@/common-components/text-block/text-block"; import textBlock from "@/common-components/text-block/text-block";
import loadingModal from "@/common-components/loading-modal/loading-modal.vue";
//Supporting Files //Supporting Files
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper"; import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
@ -101,6 +103,13 @@ import { Form, defineRule } from "vee-validate";
import store from "@/store"; import store from "@/store";
import { storeActions } from "@/constants/store-actions"; import { storeActions } from "@/constants/store-actions";
import { vinLookupMethodSelections } from "@/constants/vin-lookup-method-selections.js"; import { vinLookupMethodSelections } from "@/constants/vin-lookup-method-selections.js";
import {
skipVinLookup,
skipVinLookupNotRepair,
} from "@/helpers/heritage-integration/navigation-helper";
import experimentMixin from "@/mixins/experiment-mixin";
import { experimentSettings } from "@/constants/experiments";
import vinPagesMixin from "@/mixins/vin-pages-mixin";
import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper"; import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper";
// Define Validation Rules // Define Validation Rules
@ -118,6 +127,7 @@ defineRule("option-required", required(errorMessages.OPTION_REQUIRED));
export default { export default {
name: "estimate", name: "estimate",
mixins: [vinPagesMixin],
data() { data() {
return { return {
selectedVinLookupMethod: null, selectedVinLookupMethod: null,
@ -125,7 +135,8 @@ export default {
emailAddress: this.getEmailFromStore(), emailAddress: this.getEmailFromStore(),
displayInvalidZipAlert: false, displayInvalidZipAlert: false,
displayNonServiceableZipAlert: false, displayNonServiceableZipAlert: false,
isVinOptionalVehicle: false, skipVin: false,
skipVinNotRepair: false,
}; };
}, },
@ -142,14 +153,16 @@ export default {
]; ];
const resultMap = await settleAllPromises(promiseResultMap); const resultMap = await settleAllPromises(promiseResultMap);
const isVinOptionalVehicle = await store.dispatch(storeActions.IS_VIN_OPTIONAL_VEHICLE); const skipVin = await skipVinLookup();
const skipVinNotRepair = await skipVinLookupNotRepair();
next((vm) => { next((vm) => {
vm.isVinOptionalVehicle = isVinOptionalVehicle; vm.skipVin = skipVin;
vm.skipVinNotRepair = skipVinNotRepair;
if (resultMap.cmsContent.FunnelFooterWidget.ForwardButtonText.includes("|")) { if (resultMap.cmsContent.FunnelFooterWidget.ForwardButtonText.includes("|")) {
const forwardTextOption = const forwardTextOption =
resultMap.cmsContent.FunnelFooterWidget.ForwardButtonText.split("|"); resultMap.cmsContent.FunnelFooterWidget.ForwardButtonText.split("|");
if (store.getters.damage.isRepair || vm.isVinOptionalVehicle) { if (skipVin) {
resultMap.cmsContent.FunnelFooterWidget.ForwardButtonText = resultMap.cmsContent.FunnelFooterWidget.ForwardButtonText =
forwardTextOption[1]; forwardTextOption[1];
} else { } else {
@ -176,15 +189,15 @@ export default {
this.$router.navigateWithoutSaving(this.navigationScenarios.CLICKED_BACK, this.$route); this.$router.navigateWithoutSaving(this.navigationScenarios.CLICKED_BACK, this.$route);
}, },
async forwardButtonAction() { async forwardButtonAction() {
if (this.skipVinLookup) { if (this.skipVin) {
const zipCodeData = await this.getZipCodeData(this.serviceZipCode); const zipCodeData = await this.getZipCodeData(this.serviceZipCode);
await this.dispatchStoreAction(storeActions.SAVE_EMAIL, this.emailAddress, false); await this.dispatchStoreAction(storeActions.SAVE_EMAIL, this.emailAddress, false);
await this.dispatchStoreAction( await this.dispatchStoreAction(
storeActions.SAVE_SERVICE_LOCATION, storeActions.SAVE_SERVICE_LOCATION,
{ {
zipCode: this.serviceZipCode, zipCode: this.serviceZipCode,
state: zipCodeData.state, state: zipCodeData.state,
zipCodeCtu: zipCodeData.zipCodeCtu,
}, },
false false
); );
@ -201,10 +214,20 @@ export default {
} }
this.displayNonServiceableZipAlert = false; this.displayNonServiceableZipAlert = false;
return this.$router.navigateWithSaving( const payment = this.$store.getters.payment;
this.navigationScenarios.CLICKED_FORWARD_WITH_NO_QUESTIONS, const vehicleChangedDuringPolicyLookupInHeritage =
this.$route payment.isInsurance && payment.insuranceCoverage.coverageStatus;
); if (vehicleChangedDuringPolicyLookupInHeritage) {
navigateToHeritageFunnel({ loadingModal: this.$refs.loadingModal });
} else if (this.isRepair) {
return this.$router.navigateWithSaving(
this.navigationScenarios.CLICKED_FORWARD_WITH_NO_QUESTIONS,
this.$route
);
} else {
// if we're skipping the vin-lookup but it's not a repair, we still need to get the parts
await this.navigateForwardWithSingleCarMatch();
}
} }
if (this.selectedVinLookupMethod === vinLookupMethodSelections.MANUALVIN) { if (this.selectedVinLookupMethod === vinLookupMethodSelections.MANUALVIN) {
@ -247,13 +270,8 @@ export default {
isRepair() { isRepair() {
return store.getters.damage.isRepair; return store.getters.damage.isRepair;
}, },
skipVinLookup() {
return this.isRepair || this.isVinOptionalVehicle;
},
alertInfo() { alertInfo() {
return this.isVinOptionalVehicle && !this.isRepair return this.skipVinNotRepair ? "AlertQuoteVinOptional" : "AlertQuoteReady";
? "AlertQuoteVinOptional"
: "AlertQuoteReady";
}, },
}, },
watch: { watch: {
@ -271,6 +289,7 @@ export default {
alert, alert,
textboxQuestion, textboxQuestion,
textBlock, textBlock,
loadingModal,
}, },
}; };
</script> </script>

View file

@ -39,22 +39,20 @@ describe("license-plate-lookup.vue", () => {
describe("get values from store", () => { describe("get values from store", () => {
test("getLicensePlateFromStore returns store license plate", async () => { test("getLicensePlateFromStore returns store license plate", async () => {
// Arrange // Arrange
const { wrapper } = setupMocks({}); const licensePlateInput = "TEST129";
const mockLicensePlate = "TESTPLATE"; const { wrapper } = setupMocks({ licensePlate: licensePlateInput });
store.commit(storeMutations.UPDATE_REGISTRATION_LICENSE_PLATE, mockLicensePlate);
// Act // Act
const licensePlate = wrapper.vm.getLicensePlateFromStore(); const licensePlate = wrapper.vm.getLicensePlateFromStore();
// Assert // Assert
expect(licensePlate).toEqual(mockLicensePlate); expect(licensePlate).toEqual(licensePlateInput);
}); });
test("getRegistrationZipFromStore returns store registration zip", async () => { test("getRegistrationZipFromStore returns store registration zip", async () => {
// Arrange // Arrange
const { wrapper } = setupMocks({}); const mockRegistrationZip = "77777";
const mockRegistrationZip = "12345"; const { wrapper } = setupMocks({ registrationZipCode: mockRegistrationZip });
store.commit(storeMutations.UPDATE_REGISTRATION_ZIP_CODE, mockRegistrationZip);
// ACT // ACT
const registrationZip = wrapper.vm.getRegistrationZipFromStore(); const registrationZip = wrapper.vm.getRegistrationZipFromStore();
@ -65,9 +63,8 @@ describe("license-plate-lookup.vue", () => {
test("getEmailFromStore returns store customer email", async () => { test("getEmailFromStore returns store customer email", async () => {
// Arrange // Arrange
const { wrapper } = setupMocks({}); const mockEmail = "test12345@test.com";
const mockEmail = "test@test.com"; const { wrapper } = setupMocks({ emailAddress: mockEmail });
store.commit(storeMutations.UPDATE_CUSTOMER_EMAIL_ADDRESS, mockEmail);
// ACT // ACT
const customerEmail = wrapper.vm.getEmailFromStore(); const customerEmail = wrapper.vm.getEmailFromStore();
@ -78,15 +75,13 @@ describe("license-plate-lookup.vue", () => {
test("getServiceZipFromStore returns store service zip", async () => { test("getServiceZipFromStore returns store service zip", async () => {
// Arrange // Arrange
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({ serviceLocationZipCode: "98765" });
const mockServiceZip = "12345";
store.commit(storeMutations.UPDATE_SERVICE_LOCATION_ZIP_CODE, mockServiceZip);
// ACT // ACT
const serviceZip = wrapper.vm.getServiceZipFromStore(); const serviceZip = wrapper.vm.getServiceZipFromStore();
// Assert // Assert
expect(serviceZip).toEqual(mockServiceZip); expect(serviceZip).toEqual("98765");
}); });
}); });
@ -113,7 +108,15 @@ describe("license-plate-lookup.vue", () => {
test("Navigate forward should be called and isCarIdDifferent should be set to false when data entered matches store data on forwardButtonAction click", async () => { test("Navigate forward should be called and isCarIdDifferent should be set to false when data entered matches store data on forwardButtonAction click", async () => {
// Arrange // Arrange
const mockCarId = "TESTID"; const mockCarId = "TESTID";
const { wrapper } = setupMocks({ carId: mockCarId, isServiceable: true }); const { wrapper } = setupMocks({
carId: mockCarId,
validateZipResponse: {
isServiceable: true,
isValid: true,
zipCodeCtu: "01820",
state: "OH",
},
});
wrapper.vm.getCmsContent = jest.fn().mockImplementation(() => ""); wrapper.vm.getCmsContent = jest.fn().mockImplementation(() => "");
wrapper.vm.navigateForward = jest.fn(); wrapper.vm.navigateForward = jest.fn();
@ -180,7 +183,15 @@ describe("license-plate-lookup.vue", () => {
test("Navigate forward should be called and isCarId should be set to true when carId entered matches previously entered carId and rest of data entered matches store data on forwardButtonAction click", async () => { test("Navigate forward should be called and isCarId should be set to true when carId entered matches previously entered carId and rest of data entered matches store data on forwardButtonAction click", async () => {
// Arrange // Arrange
const { wrapper } = setupMocks({ carId: "C10000", isServiceable: true }); const { wrapper } = setupMocks({
carId: "C10000",
validateZipResponse: {
isServiceable: true,
isValid: true,
zipCodeCtu: "01820",
state: "OH",
},
});
wrapper.vm.getCmsContent = jest.fn().mockImplementation(() => { wrapper.vm.getCmsContent = jest.fn().mockImplementation(() => {
return ""; return "";
@ -340,13 +351,34 @@ describe("license-plate-lookup.vue", () => {
describe("saving registrationZip and serviceZip on continue", () => { describe("saving registrationZip and serviceZip on continue", () => {
test("registrationZip is serviceable and vehicle match is found => sets service zip/state to registration zip/state", async () => { test("registrationZip is serviceable and vehicle match is found => sets service zip/state to registration zip/state", async () => {
// NEEDS FIX - Unsure what this test should be doing but it seems incorrect or at least very confusing. If you
// comment out the "Act" entirely, then the test passes which seems to say the test isn't testing anything
// Arrange // Arrange
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({
validateZipResponse: {
isServiceable: true,
isValid: true,
zipCodeCtu: "01820",
state: "OH",
},
serviceLocationZipCode: "12345",
registrationZipCode: "12345",
});
wrapper.vm.getCmsContent = jest.fn().mockImplementation(() => ""); wrapper.vm.getCmsContent = jest.fn().mockImplementation(() => "");
await wrapper.setData({ registrationZipCode: "00000" }); await wrapper.setData({ registrationZipCode: "00000" });
navigateToHeritage.navigateToHeritageFunnel = jest.fn(); navigateToHeritage.navigateToHeritageFunnel = jest.fn();
wrapper.vm.lookupVinByPlate = jest.fn(() => {
return { data: { vehicle: { carId: "C00000" } } };
});
storeMutations.lookupVinByPlate = jest.fn().mockImplementation(() => {
return { data: { vehicle: { carId: "C00000" } } };
});
wrapper.vm.dispatchStoreAction = jest.fn().mockImplementation(() => wrapper.vm.dispatchStoreAction = jest.fn().mockImplementation(() =>
Promise.resolve({ Promise.resolve({
data: { data: {
@ -364,13 +396,20 @@ describe("license-plate-lookup.vue", () => {
expect(wrapper.vm.$store.getters.order.serviceLocation.zipCode).toEqual( expect(wrapper.vm.$store.getters.order.serviceLocation.zipCode).toEqual(
wrapper.vm.$store.getters.vehicle.registration.zipCode wrapper.vm.$store.getters.vehicle.registration.zipCode
); );
expect(wrapper.vm.$store.getters.vehicle.registration.zipCode).toEqual("12345"); expect(wrapper.vm.$store.getters.vehicle.registration.zipCode).toEqual("12345");
expect(wrapper.vm.$store.getters.order.serviceLocation.zipCode).toEqual("12345"); expect(wrapper.vm.$store.getters.order.serviceLocation.zipCode).toEqual("12345");
}); });
test("vehicle match is found but registrationZip is not serviceable => shows service zip/state field", async () => { test("vehicle match is found but registrationZip is not serviceable => shows service zip/state field", async () => {
// Arrange // Arrange
const { wrapper } = setupMocks({}); // TODO FIX - seems like shouldn't have to set serviceLocationZipCode or registrationZipCode in state from the start
const { wrapper } = setupMocks({
validateZipResponse: { isServiceable: false, isValid: true },
serviceLocationZipCode: "12345",
registrationZipCode: "12345",
});
// TODO FIX - test succeeds even if you comment this line out, is it doing anything?
wrapper.vm.validateZip = jest.fn().mockImplementation(() => { wrapper.vm.validateZip = jest.fn().mockImplementation(() => {
return { data: { isServiceable: false, state: "XX" } }; return { data: { isServiceable: false, state: "XX" } };
}); });
@ -388,6 +427,7 @@ describe("license-plate-lookup.vue", () => {
}) })
); );
//TODO FIX - test succeeds even if comment out the Act section
// Act // Act
await wrapper.vm.forwardButtonAction(); await wrapper.vm.forwardButtonAction();
@ -440,20 +480,18 @@ describe("license-plate-lookup.vue", () => {
test("registrationZip is not serviceable so serviceZip field is shown, user enters serviceZip => user can continue", async () => { test("registrationZip is not serviceable so serviceZip field is shown, user enters serviceZip => user can continue", async () => {
// Arrange // Arrange
const { wrapper } = setupMocks({ isServiceable: false }); const { wrapper } = setupMocks({
validateZipResponse: { isServiceable: false, isValid: true },
});
const registrationZip = "00000"; const registrationZip = "00000";
const serviceZip = "99999"; const serviceZip = "99999";
wrapper.vm.navigateForward = jest.fn(); wrapper.vm.navigateForward = jest.fn();
wrapper.vm.dispatchStoreAction = jest.fn().mockImplementation(() =>
Promise.resolve({ const vinLookup = { data: { vehicle: { carId: "TESTID1" } } };
data: { wrapper.vm.lookupVin = jest.fn().mockImplementation(() => {
vehicle: { return new Promise((resolve) => resolve(vinLookup));
carId: "C00000", });
},
},
})
);
await wrapper.setData({ registrationZipCode: registrationZip }); await wrapper.setData({ registrationZipCode: registrationZip });
await wrapper.vm.forwardButtonAction(); await wrapper.vm.forwardButtonAction();
@ -476,20 +514,24 @@ describe("license-plate-lookup.vue", () => {
test("registrationZip is not serviceable so serviceZip field is shown, user enters serviceZip => service and registration zips/states saved", async () => { test("registrationZip is not serviceable so serviceZip field is shown, user enters serviceZip => service and registration zips/states saved", async () => {
// Arrange // Arrange
const { wrapper } = setupMocks({ isServiceable: true });
// TODO FIX - This test only works because 12345 is set in the state store when setupMocks is called. If u change registrationZip or serviceZip
// to any other value, then the test fails. Driving this home is you can comment out the entire "Act" portion (and the expect navigate forward be called)'
// and the test still succeeds so the checking ZIP fields part doesn't work.
const { wrapper } = setupMocks({
validateZipResponse: { isServiceable: false, isValid: true },
//serviceLocationZipCode: "12345",
//registrationZipCode: "12345"
});
const registrationZip = "12345"; const registrationZip = "12345";
const serviceZip = "12345"; const serviceZip = "12345";
console.log("this is the test I care about");
wrapper.vm.navigateForward = jest.fn(); wrapper.vm.navigateForward = jest.fn();
wrapper.vm.dispatchStoreAction = jest.fn().mockImplementation(() => const vinLookup = { data: { vehicle: { carId: "TESTID1" } } };
Promise.resolve({ wrapper.vm.lookupVin = jest.fn().mockImplementation(() => {
data: { return new Promise((resolve) => resolve(vinLookup));
vehicle: { });
carId: "C00000",
},
},
})
);
await wrapper.setData({ registrationZipCode: registrationZip }); await wrapper.setData({ registrationZipCode: registrationZip });
await wrapper.vm.forwardButtonAction(); await wrapper.vm.forwardButtonAction();
@ -497,15 +539,35 @@ describe("license-plate-lookup.vue", () => {
// At this point, serviceZip field is shown // At this point, serviceZip field is shown
await wrapper.setData({ serviceZip: serviceZip }); await wrapper.setData({ serviceZip: serviceZip });
const apiResponses = {
serviceZipValidationResponse: {
isValid: true,
isServiceable: true,
state: "OH",
zipCodeCtu: "01820",
},
registrationZipValidationResponse: {
isValid: true,
isServiceable: false,
},
};
const apiPromise = Promise.resolve(apiResponses);
settleAllPromises.mockImplementation(() => apiPromise);
// Act // Act
// Continue after entering value into service zip field // Continue after entering value into service zip field
await wrapper.vm.forwardButtonAction(); await wrapper.vm.forwardButtonAction();
// Assert // Assert
expect(wrapper.vm.$store.getters.vehicle.registration.zipCode).toEqual(registrationZip); // TODO - these two values have a value in pages "data" but not setting it in store; need to figure out why or mock it or something
expect(wrapper.vm.$store.getters.order.serviceLocation.zipCode).toEqual(serviceZip); //expect(wrapper.vm.$store.vehicle.registration.zipCode).toEqual(registrationZip);
expect(wrapper.vm.navigateForward).toHaveBeenCalled(); //expect(wrapper.vm.$store.order.serviceLocation.zipCode).toEqual(serviceZip);
//Verify we only navigateForward one time despite our calling forwardButtonAction twice
expect(wrapper.vm.navigateForward).toBeCalledTimes(1);
}); });
}); });
@ -529,6 +591,25 @@ describe("license-plate-lookup.vue", () => {
//Assert //Assert
expect(arePagePrerequisitesValid).toBe(true); expect(arePagePrerequisitesValid).toBe(true);
}); });
test("CarId not set, arePagePrerequisitesValid should be false ", async () => {
//Arrange
const { wrapper } = setupMocks({});
//Act
licensePlateLookup.beforeRouteEnter.call(
wrapper.vm,
{ query: { fmgPage: "license-plate-lookup" } },
undefined,
(c) => c(wrapper.vm)
);
let arePagePrerequisitesValid = wrapper.vm.arePagePrerequisitesValid();
await nextTick();
//Assert
expect(arePagePrerequisitesValid).toBe(false);
});
}); });
}); });
@ -536,8 +617,13 @@ function setupMocks({
pageHeaderWidgetHeaderText = {}, pageHeaderWidgetHeaderText = {},
mountOptionsMockData = {}, mountOptionsMockData = {},
partsOrQuestions = [], partsOrQuestions = [],
isServiceable = false, validateZipResponse = { zipCodeCtu: null, isServiceable: false, isValid: true, state: null },
carId = "", //Values to set in the state store
carId = null,
serviceLocationZipCode = null,
registrationZipCode = null,
licensePlate = null, //"TESTPLATE",
emailAddress = null, //"test@test.com"
}) { }) {
store.commit(storeMutations.RESET_STATE); store.commit(storeMutations.RESET_STATE);
//Mock api responses //Mock api responses
@ -554,11 +640,16 @@ function setupMocks({
}, },
}, },
serviceZipValidationResponse: { serviceZipValidationResponse: {
isValid: true, isValid: validateZipResponse.isValid,
isServiceable: isServiceable, isServiceable: validateZipResponse.isServiceable,
state: validateZipResponse.state,
zipCodeCtu: validateZipResponse.zipCodeCtu,
}, },
registrationZipValidationResponse: { registrationZipValidationResponse: {
state: "CO", isValid: validateZipResponse.isValid,
isServiceable: validateZipResponse.isServiceable,
state: validateZipResponse.state,
zipCodeCtu: validateZipResponse.zipCodeCtu,
}, },
}; };
@ -573,17 +664,17 @@ function setupMocks({
getters: { getters: {
vehicle: { vehicle: {
registration: { registration: {
licensePlate: "TESTPLATE", licensePlate: licensePlate,
zipCode: "12345", zipCode: registrationZipCode,
}, },
carId: carId, carId: carId,
}, },
order: { order: {
customer: { customer: {
emailAddress: "test@test.com", emailAddress: emailAddress,
}, },
serviceLocation: { serviceLocation: {
zipCode: "12345", zipCode: serviceLocationZipCode,
}, },
}, },
}, },

View file

@ -212,6 +212,7 @@ export default {
promise: registrationZipValidationResponse, promise: registrationZipValidationResponse,
}, },
{ {
// If serviceZipCode is not set, then sets the response to the registrationZipValidationResponse. Calls VALIDATE_ZIP if the serviceZipCode is set.
resultKey: "serviceZipValidationResponse", resultKey: "serviceZipValidationResponse",
promise: this.serviceZipCode promise: this.serviceZipCode
? this.dispatchStoreAction(storeActions.VALIDATE_ZIP, { ? this.dispatchStoreAction(storeActions.VALIDATE_ZIP, {
@ -224,13 +225,9 @@ export default {
const resultMap = await settleAllPromises(promiseResultMap); const resultMap = await settleAllPromises(promiseResultMap);
// Lookup vin // Lookup vin
const vinLookup = await this.dispatchStoreAction( const vinLookup = await this.lookupVin(
storeActions.LOOKUP_VIN_BY_PLATE, this.licensePlate,
{ resultMap.registrationZipValidationResponse.state
licensePlate: this.licensePlate,
licenseState: resultMap.registrationZipValidationResponse.state,
},
false
).catch(() => { ).catch(() => {
// No VIN found. // No VIN found.
this.displayVinNotFoundAlert = true; this.displayVinNotFoundAlert = true;
@ -309,12 +306,20 @@ export default {
{ {
zipCode: this.serviceZipCode, zipCode: this.serviceZipCode,
state: resultMap.serviceZipValidationResponse.state, state: resultMap.serviceZipValidationResponse.state,
zipCodeCtu: resultMap.serviceZipValidationResponse.zipCodeCtu,
}, },
false false
); );
return await this.navigateForward(); return await this.navigateForward();
}, },
lookupVin(plate, state) {
return this.dispatchStoreAction(
storeActions.LOOKUP_VIN_BY_PLATE,
{ licensePlate: plate, licenseState: state },
false
);
},
async navigateForward() { async navigateForward() {
if (this.isCarIdDifferent && !this.isSelectedGlassAvailableForVehicle) { if (this.isCarIdDifferent && !this.isSelectedGlassAvailableForVehicle) {
this.$router.navigateWithSaving( this.$router.navigateWithSaving(

View file

@ -88,12 +88,14 @@ const baseStoreGettersDamage = () => {
{ {
questionText: questionText:
"Is your vehicle equipped with the Panoramic Sunroof which can be identified by having a glass panel over the rear seats?", "Is your vehicle equipped with the Panoramic Sunroof which can be identified by having a glass panel over the rear seats?",
selectedAnswer: "1|nextQuestion|3|Yes",
selectedAnswerText: "Yes", selectedAnswerText: "Yes",
questionNum: 1, questionNum: 1,
}, },
{ {
questionText: questionText:
"Is your vehicle equipped with a heated windshield that melts snow and ice from underneath the windshield wiper blades?", "Is your vehicle equipped with a heated windshield that melts snow and ice from underneath the windshield wiper blades?",
selectedAnswer: "2|nextQuestion|3|Yes",
selectedAnswerText: "Yes", selectedAnswerText: "Yes",
questionNum: 2, questionNum: 2,
}, },
@ -113,6 +115,7 @@ afterEach(() => {
store.getters = { store.getters = {
pageData: baseStoreGettersPageData, pageData: baseStoreGettersPageData,
damage: baseStoreGettersDamage, damage: baseStoreGettersDamage,
payment: { insuranceCoverage: {} },
}; };
}); });
@ -170,10 +173,10 @@ describe("moldingQuestions.vue", () => {
}); });
describe("watch on selectedAnswers should be set up...", () => { describe("watch on selectedAnswers should be set up...", () => {
test("Should trigger handleAnswerUpdates if watched data changes", async () => { test("Should trigger handleCompletedQuestionChainAnswers if watched data changes", async () => {
// Arrange // Arrange
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
const spy = jest.spyOn(wrapper.vm, "handleAnswerUpdates"); const spy = jest.spyOn(wrapper.vm, "handleCompletedQuestionChainAnswers");
// Act // Act
wrapper.setData({ wrapper.setData({
@ -184,12 +187,14 @@ describe("moldingQuestions.vue", () => {
{ {
questionText: questionText:
"Is your vehicle equipped with the Panoramic Sunroof which can be identified by having a glass panel over the rear seats?", "Is your vehicle equipped with the Panoramic Sunroof which can be identified by having a glass panel over the rear seats?",
selectedAnswer: "1|nextQuestion|3|Yes",
selectedAnswerText: "Yes", selectedAnswerText: "Yes",
questionNum: 1, questionNum: 1,
}, },
{ {
questionText: questionText:
"Is your vehicle equipped with a heated windshield that melts snow and ice from underneath the windshield wiper blades?", "Is your vehicle equipped with a heated windshield that melts snow and ice from underneath the windshield wiper blades?",
selectedAnswer: "2|nextQuestion|3|Yes",
selectedAnswerText: "Yes", selectedAnswerText: "Yes",
questionNum: 2, questionNum: 2,
}, },

View file

@ -111,7 +111,7 @@ export default {
"selectedAnswers." + glass.answerKey, "selectedAnswers." + glass.answerKey,
(newValue) => { (newValue) => {
if (newValue && Object.keys(newValue).length > 0) { if (newValue && Object.keys(newValue).length > 0) {
this.handleAnswerUpdates(newValue, glass.answerKey); this.handleCompletedQuestionChainAnswers(newValue, glass.answerKey);
} }
}, },
{ deep: true } { deep: true }
@ -158,9 +158,7 @@ export default {
]; ];
} }
// this.navigateForward(partsOrQuestions); this.navigateForward(partsOrQuestions);
// TODO KO delete after quote MVP
this.navigateForward(partsOrQuestions, null, this.shouldGoToHeritageQuote);
}, },
}, },
components: { components: {

View file

@ -78,12 +78,14 @@ const baseStoreGettersDamage = () => {
{ {
questionText: questionText:
"Is your vehicle equipped with the Panoramic Sunroof which can be identified by having a glass panel over the rear seats?", "Is your vehicle equipped with the Panoramic Sunroof which can be identified by having a glass panel over the rear seats?",
selectedAnswer: "1|nextQuestion|3|Yes",
selectedAnswerText: "Yes", selectedAnswerText: "Yes",
questionNum: 1, questionNum: 1,
}, },
{ {
questionText: questionText:
"Is your vehicle equipped with a heated windshield that melts snow and ice from underneath the windshield wiper blades?", "Is your vehicle equipped with a heated windshield that melts snow and ice from underneath the windshield wiper blades?",
selectedAnswer: "1|nextQuestion|3|Yes",
selectedAnswerText: "Yes", selectedAnswerText: "Yes",
questionNum: 2, questionNum: 2,
}, },
@ -152,14 +154,14 @@ describe("partQuestions.vue...", () => {
}); });
describe("watch on selectedAnswers should be set up...", () => { describe("watch on selectedAnswers should be set up...", () => {
test("Should trigger handleAnswerUpdates if watched data changes", async () => { test("Should trigger handleCompletedQuestionChainAnswers if watched data changes", async () => {
// Arrange // Arrange
store.getters = { store.getters = {
pageData: baseStoreGettersPageData, pageData: baseStoreGettersPageData,
damage: baseStoreGettersDamage, damage: baseStoreGettersDamage,
}; };
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
const spy = jest.spyOn(wrapper.vm, "handleAnswerUpdates"); const spy = jest.spyOn(wrapper.vm, "handleCompletedQuestionChainAnswers");
// Act // Act
wrapper.setData({ wrapper.setData({
@ -169,11 +171,13 @@ describe("partQuestions.vue...", () => {
answeredQuestions: [ answeredQuestions: [
{ {
questionText: "One?", questionText: "One?",
selectedAnswer: "1|nextQuestion|3|Yes",
selectedAnswerText: "Yes", selectedAnswerText: "Yes",
questionNum: 1, questionNum: 1,
}, },
{ {
questionText: "Two?", questionText: "Two?",
selectedAnswer: "1|nextQuestion|3|Yes",
selectedAnswerText: "Yes", selectedAnswerText: "Yes",
questionNum: 2, questionNum: 2,
}, },
@ -216,6 +220,9 @@ describe("partQuestions.vue...", () => {
describe("forwardButtonAction", () => { describe("forwardButtonAction", () => {
test("Should clear out answerData", async () => { test("Should clear out answerData", async () => {
// Arrange // Arrange
store.getters.payment = {
insuranceCoverage: null,
};
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
await wrapper.setData({ await wrapper.setData({

View file

@ -109,7 +109,7 @@ export default {
"selectedAnswers." + glass.answerKey, "selectedAnswers." + glass.answerKey,
(newValue) => { (newValue) => {
if (newValue && Object.keys(newValue).length > 0) { if (newValue && Object.keys(newValue).length > 0) {
this.handleAnswerUpdates(newValue, glass.answerKey); this.handleCompletedQuestionChainAnswers(newValue, glass.answerKey);
} }
}, },
{ deep: true } { deep: true }

View file

@ -1,6 +1,7 @@
<template> <template>
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }"> <Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }">
<div class="page-container-grouped-styles"> <div class="page-container-grouped-styles">
<loadingModal ref="loadingModal" />
<funnelHeader cmsWidgetName="FunnelHeaderWidget" /> <funnelHeader cmsWidgetName="FunnelHeaderWidget" />
<vehicleBanner <vehicleBanner
cmsWidgetName="VehicleBannerWidget" cmsWidgetName="VehicleBannerWidget"
@ -21,19 +22,22 @@
ref="servicePackage" ref="servicePackage"
cashCmsWidgetName="CashServicePackageQuestionWidget" cashCmsWidgetName="CashServicePackageQuestionWidget"
insuranceCmsWidgetName="InsuranceServicePackageQuestionWidget" insuranceCmsWidgetName="InsuranceServicePackageQuestionWidget"
v-model="selectedPackage"
groupName="ServicePackageQuestion" groupName="ServicePackageQuestion"
:availableLineItems="availableLineItems" :availableLineItems="availableLineItems"
:isInsuranceSelected="isInsuranceSelected" /> :isInsuranceSelected="isInsuranceSelected"
@vapsItemsSelected="vapsItemsSelectedAction"
validationRules="option-required"
isRequired />
<textBlock <textBlock
cmsWidgetName="quoteDisclaimer" cmsWidgetName="quoteDisclaimer"
justifyText="left" justifyText="left"
typeStyle="caption" typeStyle="caption"
class="mt-4" /> class="mt-4" />
<modal cmsWidgetName="EstimateRainDefenseModal" /> <modal cmsWidgetName="RainDefenseModal" />
<modal cmsWidgetName="EstimateFrontWiperModal" /> <modal cmsWidgetName="FrontWiperModal" />
<modal cmsWidgetName="EstimateRearWiperModal" /> <modal cmsWidgetName="RearWiperModal" />
<modal cmsWidgetName="EstimateRecalModal" /> <modal cmsWidgetName="RecalModal" />
<funnel-footer <funnel-footer
cmsWidgetName="FunnelFooterWidget" cmsWidgetName="FunnelFooterWidget"
:isForwardActionDisabled="!meta.valid" :isForwardActionDisabled="!meta.valid"
@ -55,19 +59,26 @@ import cashOrInsuranceQuestion from "./cash-or-insurance-question/cash-or-insura
import servicePackageQuestion from "./service-package-question/service-package-question"; import servicePackageQuestion from "./service-package-question/service-package-question";
import textBlock from "@/common-components/text-block/text-block"; import textBlock from "@/common-components/text-block/text-block";
import modal from "@/common-components/modal/modal"; import modal from "@/common-components/modal/modal";
import loadingModal from "@/common-components/loading-modal/loading-modal.vue";
import baseMixin from "@/mixins/base-mixin.js"; import baseMixin from "@/mixins/base-mixin.js";
import vehicleQuestionsMixin from "../../mixins/vehicle-questions-mixin"; import vehicleQuestionsMixin from "../../mixins/vehicle-questions-mixin";
import { settleAllPromises } from "@/helpers/layout-helper"; import { settleAllPromises } from "@/helpers/layout-helper";
import { storeActions } from "@/constants/store-actions"; import { storeActions } from "@/constants/store-actions";
import store from "@/store"; import store from "@/store";
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper"; import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import { Form } from "vee-validate"; import { required } from "@/helpers/validation-rules";
import { errorMessages } from "@/constants/error-messages";
import { Form, defineRule } from "vee-validate";
import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper";
import { applicationConfig } from "@/constants/application-config";
defineRule("option-required", required(errorMessages.OPTION_REQUIRED));
export default { export default {
name: "quote", name: "quote",
async beforeRouteEnter(to, from, next) { async beforeRouteEnter(to, from, next) {
// Call APIs // Call APIs
const cmsContent = await fetchCmsContentForPage(to.query.fmgPage); const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
const wipersPromise = baseMixin.methods.dispatchStoreAction(storeActions.GET_WIPERS); const wipersPromise = baseMixin.methods.dispatchStoreAction(storeActions.GET_WIPERS);
const rainDefensePromise = baseMixin.methods.dispatchStoreAction( const rainDefensePromise = baseMixin.methods.dispatchStoreAction(
storeActions.GET_RAIN_DEFENSE storeActions.GET_RAIN_DEFENSE
@ -77,6 +88,10 @@ export default {
); );
const promiseResultMap = [ const promiseResultMap = [
{
resultKey: "cmsContent",
promise: cmsContentPromise,
},
{ {
resultKey: "wipers", resultKey: "wipers",
promise: wipersPromise, promise: wipersPromise,
@ -108,50 +123,79 @@ export default {
// Call the "next" function to complete the transition to this page. // Call the "next" function to complete the transition to this page.
next((vm) => { next((vm) => {
vm.setCmsContent(cmsContent); vm.setCmsContent(resultMap.cmsContent);
vm.supportingItems = resultMap.supportingItems; vm.supportingItems = resultMap.supportingItems;
vm.availableLineItems = pricingResults; vm.availableLineItems = pricingResults;
vm.isInsuranceSelected = vm.getDefaultIsInsuranceSelectedValue(); vm.isInsuranceSelected = vm.getDefaultIsInsuranceSelectedValue(vm.availableLineItems);
}); });
}, },
data() { data() {
return { return {
isInsuranceSelected: null, isInsuranceSelected: null,
selectedPackage: null, selectedVaps: null,
availableLineItems: null, availableLineItems: null,
supportingItems: null, supportingItems: null,
}; };
}, },
methods: { methods: {
arePagePrerequisitesValid() { arePagePrerequisitesValid() {
return true; return (
//return store.getters.order.damage.isRepair || (store.getters.order.lineItems?.glassParts != null && store.getters.order.lineItems.glassParts.length > 0); store.getters.order.serviceLocation.zipCode &&
store.getters.order.serviceLocation.zipCodeCtu &&
(store.getters.order.damage.isRepair ||
(store.getters.order.lineItems?.glassParts != null &&
store.getters.order.lineItems.glassParts.length > 0))
);
}, },
getDefaultIsInsuranceSelectedValue() { getDefaultIsInsuranceSelectedValue(availableLineItems) {
const defaultIsInsuranceSelectedValue = this.$store.getters.order.payment.isInsurance; const defaultIsInsuranceSelectedValue = this.$store.getters.order.payment.isInsurance;
if (defaultIsInsuranceSelectedValue != null) { if (defaultIsInsuranceSelectedValue != null) {
return defaultIsInsuranceSelectedValue; return defaultIsInsuranceSelectedValue;
} else { } else {
return this.availableLineItems return availableLineItems
? baseMixin.methods.getTierOnePackagePrice(this.availableLineItems) > 500 ? baseMixin.methods.getTierOnePackagePrice(availableLineItems) > 500
: null; : null;
} }
}, },
vapsItemsSelectedAction(vapsItemsSelected) {
this.selectedVaps = vapsItemsSelected;
},
filterOutFees(currentSupportingItems) {
const filteredSupportingItems = currentSupportingItems.filter((item) => {
return (
!item.partType.includes("FEE") ||
(item.partType === "REPAIR FEE" && item.partNumber != "SUPPLIES-REPAIR")
);
});
return filteredSupportingItems;
},
backButtonAction() { backButtonAction() {
vehicleQuestionsMixin.methods.navigateBack(this); vehicleQuestionsMixin.methods.navigateBack(this);
}, },
async forwardButtonAction() { forwardButtonAction() {
await this.dispatchStoreAction( this.dispatchStoreAction(
this.storeActions.SAVE_QUOTE_PAGE_SELECTIONS, this.storeActions.SAVE_PAYMENT_TYPE,
{ this.isInsuranceSelected,
isInsuranceSelected: this.isInsuranceSelected,
vapsItemsToAdd: this.selectedPackage,
supportingItemsToAdd: this.supportingItems,
},
false false
); );
if (!this.isInsuranceSelected) {
this.dispatchStoreAction(
this.storeActions.SAVE_ACCOUNT_NUMBER,
applicationConfig.CASH_ACCOUNT_NUMBER,
false
);
}
if (this.$store.getters.order.accountNumber != applicationConfig.CASH_ACCOUNT_NUMBER) {
this.supportingItems = this.filterOutFees(this.supportingItems);
}
this.dispatchStoreAction(
this.storeActions.SAVE_SUPPORTING_ITEMS,
this.supportingItems,
false
);
this.dispatchStoreAction(this.storeActions.SAVE_VAPS, this.selectedVaps, false);
//return this.navigateForward(); navigateToHeritageFunnel({ loadingModal: this.$refs.loadingModal });
}, },
}, },
components: { components: {
@ -164,6 +208,7 @@ export default {
cashOrInsuranceQuestion, cashOrInsuranceQuestion,
servicePackageQuestion, servicePackageQuestion,
modal, modal,
loadingModal,
}, },
}; };
</script> </script>

View file

@ -58,7 +58,7 @@ describe("service-package-question.vue", () => {
await nextTick(); await nextTick();
// Assert // Assert
expect(wrapper.emitted()["update:modelValue"][0][0]).toEqual([ expect(wrapper.emitted()["vapsItemsSelected"][0][0]).toEqual([
{ {
description: null, description: null,
partNumber: "RAIN DEFENSE", partNumber: "RAIN DEFENSE",

View file

@ -5,7 +5,8 @@
buttonTypeString="servicePackageRadio" buttonTypeString="servicePackageRadio"
:buttonTypeObject="servicePackageRadio" :buttonTypeObject="servicePackageRadio"
v-model="selectedPackageName" v-model="selectedPackageName"
isRequired /> :validationRules="validationRules"
:isRequired="isRequired" />
</template> </template>
<script> <script>
@ -25,10 +26,11 @@ const packageNames = {
export default { export default {
name: "servicePackageQuestion", name: "servicePackageQuestion",
props: { props: {
modelValue: String,
groupName: String, groupName: String,
cashCmsWidgetName: String, cashCmsWidgetName: String,
insuranceCmsWidgetName: String, insuranceCmsWidgetName: String,
validationRules: String,
isRequired: Boolean,
isInsuranceSelected: Boolean, isInsuranceSelected: Boolean,
availableLineItems: null, availableLineItems: null,
}, },
@ -46,7 +48,7 @@ export default {
}, },
selectedPackageName(newValue) { selectedPackageName(newValue) {
const VapsProductsInSelectedPackage = this.getVapsLineItemsForSelectedPackage(newValue); const VapsProductsInSelectedPackage = this.getVapsLineItemsForSelectedPackage(newValue);
this.$emit("update:modelValue", VapsProductsInSelectedPackage); this.$emit("vapsItemsSelected", VapsProductsInSelectedPackage);
}, },
}, },
computed: { computed: {
@ -186,7 +188,7 @@ export default {
item.partType.toUpperCase() === partTypeStrings.FRONT_WIPER) || item.partType.toUpperCase() === partTypeStrings.FRONT_WIPER) ||
(priceRearWipers && item.partType.toUpperCase() === partTypeStrings.REAR_WIPER) (priceRearWipers && item.partType.toUpperCase() === partTypeStrings.REAR_WIPER)
) { ) {
vapsPrice += item.price; vapsPrice += baseMixin.methods.getTotalLineItemPrice(item);
} }
}); });
return vapsPrice; return vapsPrice;
@ -205,7 +207,7 @@ export default {
(priceRainDefense && (priceRainDefense &&
item.partType.toUpperCase() === partTypeStrings.RAIN_DEFENSE) item.partType.toUpperCase() === partTypeStrings.RAIN_DEFENSE)
) { ) {
vapsPrice += item.price; vapsPrice += baseMixin.methods.getTotalLineItemPrice(item);
} }
}); });
return vapsPrice; return vapsPrice;
@ -315,9 +317,10 @@ export default {
return !!partTypeMatches.length; return !!partTypeMatches.length;
}, },
glassToReplaceContainsGlassLocation(glassLocation) { glassToReplaceContainsGlassLocation(glassLocation) {
const glassLocationMatches = this.$store.getters.order.damage.glassToReplace.filter( const glassLocationMatches =
(glassToReplace) => glassToReplace.glassLocation === glassLocation this.$store.getters.order.damage.glassToReplace?.filter(
); (glassToReplace) => glassToReplace.glassLocation === glassLocation
) ?? [];
return !!glassLocationMatches.length; return !!glassLocationMatches.length;
}, },
}, },

View file

@ -1,8 +1,11 @@
<template> <template>
<baseInputButton v-bind="$props" @buttonClicked="handleAnswerChange" v-model="selectedValue"> <baseInputButton v-bind="$props" @buttonClicked="handleAnswerChange" v-model="selectedValue">
<div class="package-label" for="testradio"> <div
class="package-label"
:class="[this.buttonLabelSubCopy ? 'has-subheader' : '']"
for="testradio">
<div class="package-specs"> <div class="package-specs">
<p :class="[this.buttonLabelSubCopy ? 'mb-2' : 'm-0']"> <p class="m-0">
<span v-html="this.buttonLabel"></span> <span v-html="this.buttonLabel"></span>
<span class="pricing-info" v-html="this.buttonAuxillaryCopy"></span> <span class="pricing-info" v-html="this.buttonAuxillaryCopy"></span>
</p> </p>
@ -113,6 +116,10 @@ export default {
overflow: hidden; overflow: hidden;
min-height: 58px; min-height: 58px;
&.has-subheader {
min-height: 78px;
}
&:before { &:before {
content: ""; content: "";
position: relative; position: relative;
@ -193,12 +200,16 @@ export default {
font-weight: 500; font-weight: 500;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
span.pricing-info { span {
color: $green; &.pricing-info {
color: $green;
font-size: 0.875rem;
}
} }
&.sub-label { &.sub-label {
color: $green; color: $green;
text-transform: uppercase; text-transform: uppercase;
font-size: 0.75rem;
} }
} }
ul { ul {
@ -206,6 +217,7 @@ export default {
padding: 0; padding: 0;
li { li {
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
font-size: 0.875rem;
} }
} }
} }

View file

@ -75,7 +75,7 @@ describe("vehicle-damage.vue", () => {
expect(wrapper.vm.$router.navigateWithoutSaving).toHaveBeenCalled(); expect(wrapper.vm.$router.navigateWithoutSaving).toHaveBeenCalled();
}); });
test("Replace several pieces of glass on ForwardButtonAction triggers a router.navigate and saves selections to store", async () => { test("Replace several pieces of glass on first pass through concept on ForwardButtonAction triggers a router.navigate and saves selections to store", async () => {
//Arrange //Arrange
const partsData = { const partsData = {
partsOrQuestions: [ partsOrQuestions: [

View file

@ -328,8 +328,28 @@ export default {
}, },
navigateForward() { navigateForward() {
const payment = this.$store.getters.payment;
console.log(payment);
const vehicleChangedDuringPolicyLookupInHeritage =
payment.isInsurance &&
payment.insuranceCoverage.coverageStatus &&
payment.insuranceCoverage.coverageStatus !== "";
if (vehicleChangedDuringPolicyLookupInHeritage) {
if (store.getters.damage.isRepair) {
this.$router.navigateWithSaving(
this.navigationScenarios.CLICKED_FORWARD_WITH_REPAIR_AND_VERIFIED_INSURANCE,
this.$route
);
} else {
this.$router.navigateWithSaving(
this.navigationScenarios
.CLICKED_FORWARD_WITH_REPLACE_AND_VERIFIED_INSURANCE,
this.$route
);
}
}
// If vin already exists, navigate directly to vin-lookup // If vin already exists, navigate directly to vin-lookup
if (store.getters.vehicle.vin) { else if (store.getters.vehicle.vin) {
this.$router.navigateWithSaving( this.$router.navigateWithSaving(
this.navigationScenarios.CLICKED_FORWARD_WITH_VIN, this.navigationScenarios.CLICKED_FORWARD_WITH_VIN,
this.$route this.$route

View file

@ -248,7 +248,9 @@ describe("vehicle-parts.vue", () => {
(c) => c(wrapper.vm) (c) => c(wrapper.vm)
); );
wrapper.vm.navigateBack(); store.dispatch = jest.fn(() => {});
await wrapper.vm.navigateBack();
//Assert //Assert
expect(wrapper.vm.$router.navigateWithoutSaving).toHaveBeenCalledWith( expect(wrapper.vm.$router.navigateWithoutSaving).toHaveBeenCalledWith(
@ -294,7 +296,8 @@ describe("vehicle-parts.vue", () => {
(c) => c(wrapper.vm) (c) => c(wrapper.vm)
); );
wrapper.vm.navigateBack(); store.dispatch = jest.fn(() => {});
await wrapper.vm.navigateBack();
//Assert //Assert
expect(wrapper.vm.$router.navigateWithoutSaving).toHaveBeenCalledWith( expect(wrapper.vm.$router.navigateWithoutSaving).toHaveBeenCalledWith(
@ -456,6 +459,7 @@ describe("vehicle-parts.vue", () => {
store.getters.pageData.mockReturnValueOnce(basePartResponse); store.getters.pageData.mockReturnValueOnce(basePartResponse);
store.getters.lineItems = { glassParts: {} }; store.getters.lineItems = { glassParts: {} };
store.getters.vehicle = { carId: "TEST_CAR_ID" }; store.getters.vehicle = { carId: "TEST_CAR_ID" };
store.getters.payment = { insuranceCoverage: {} };
const { wrapper } = setupMocks({ const { wrapper } = setupMocks({
mountOptionsMockData: { mountOptionsMockData: {
@ -538,6 +542,7 @@ function setupMocks({ pageHeaderWidgetHeaderText = {}, mountOptionsMockData = {}
wrapper.vm.setCmsContent = baseMixin.methods.setCmsContent; wrapper.vm.setCmsContent = baseMixin.methods.setCmsContent;
wrapper.vm.$refs.funnelFooter.removeLoader = jest.fn(); wrapper.vm.$refs.funnelFooter.removeLoader = jest.fn();
wrapper.vm.$refs.loadingModal.showModal = jest.fn();
// wrapper.vm.$refs.onSubmit = jest.fn(); // wrapper.vm.$refs.onSubmit = jest.fn();
// wrapper.vm.$refs.onInvalidSubmit = jest.fn(); // wrapper.vm.$refs.onInvalidSubmit = jest.fn();

View file

@ -1,6 +1,7 @@
<template> <template>
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm"> <Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm">
<div class="page-container-grouped-styles vehicle-parts"> <div class="page-container-grouped-styles vehicle-parts">
<loadingModal ref="loadingModal" />
<funnelHeader ref="funnelHeader" cmsWidgetName="FunnelHeaderWidget" /> <funnelHeader ref="funnelHeader" cmsWidgetName="FunnelHeaderWidget" />
<vehicleBanner <vehicleBanner
ref="vehicleBanner" ref="vehicleBanner"
@ -34,7 +35,6 @@
cmsWidgetName="FunnelFooterWidget" cmsWidgetName="FunnelFooterWidget"
ref="funnelFooter" ref="funnelFooter"
:isForwardActionDisabled="isForwardActionDisabled" :isForwardActionDisabled="isForwardActionDisabled"
@tempButtonClicked="() => handleTempButtonClicked(this)"
@back-click="navigateBack" @back-click="navigateBack"
@ForwardClicked="forwardButtonAction" /> @ForwardClicked="forwardButtonAction" />
</div> </div>
@ -50,6 +50,7 @@ import vehicleBanner from "@/common-components/vehicle-banner/vehicle-banner";
import funnelSubHeader from "@/common-components/funnel-sub-header/funnel-sub-header"; import funnelSubHeader from "@/common-components/funnel-sub-header/funnel-sub-header";
import funnelFooter from "@/common-components/funnel-footer/funnel-footer"; import funnelFooter from "@/common-components/funnel-footer/funnel-footer";
import alert from "@/ux-components/alert/alert"; import alert from "@/ux-components/alert/alert";
import loadingModal from "@/common-components/loading-modal/loading-modal.vue";
// Supporting Files // Supporting Files
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";
@ -187,10 +188,8 @@ export default {
false false
); );
// TODO KO delete after quote MVP
this.navigateForward(matchedParts, null, this.shouldGoToHeritageQuote);
// Navigate to the next page // Navigate to the next page
// this.navigateForward(matchedParts); this.navigateForward(matchedParts);
}, },
LoadInitialPartsData() { LoadInitialPartsData() {
@ -224,6 +223,7 @@ export default {
funnelSubHeader, funnelSubHeader,
funnelFooter, funnelFooter,
alert, alert,
loadingModal,
}, },
}; };
</script> </script>

View file

@ -1,6 +1,7 @@
<template> <template>
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }"> <Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }">
<div class="page-container-grouped-styles"> <div class="page-container-grouped-styles">
<loadingModal ref="loadingModal" />
<funnelHeader cmsWidgetName="FunnelHeaderWidget" /> <funnelHeader cmsWidgetName="FunnelHeaderWidget" />
<vehicleBanner <vehicleBanner
cmsWidgetName="VehicleBannerWidget" cmsWidgetName="VehicleBannerWidget"
@ -100,7 +101,6 @@
cmsWidgetName="FunnelFooterWidget" cmsWidgetName="FunnelFooterWidget"
ref="funnelFooter" ref="funnelFooter"
:isForwardActionDisabled="!meta.valid" :isForwardActionDisabled="!meta.valid"
@tempButtonClicked="() => handleTempButtonClicked(this)"
@back-clicked="backButtonAction" @back-clicked="backButtonAction"
@ForwardClicked="forwardButtonAction" /> @ForwardClicked="forwardButtonAction" />
</div> </div>
@ -118,6 +118,7 @@ import alert from "@/ux-components/alert/alert";
import textboxQuestion from "@/common-components/textbox-question/textbox-question"; import textboxQuestion from "@/common-components/textbox-question/textbox-question";
import vinInformation from "@/layouts/vin-lookup/vin-information/vin-information"; import vinInformation from "@/layouts/vin-lookup/vin-information/vin-information";
import textBlock from "@/common-components/text-block/text-block"; import textBlock from "@/common-components/text-block/text-block";
import loadingModal from "@/common-components/loading-modal/loading-modal.vue";
// Supporting files // Supporting files
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper"; import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
@ -314,6 +315,7 @@ export default {
{ {
zipCode: this.serviceZipCode, zipCode: this.serviceZipCode,
state: resultMap.zipCodeData.state, state: resultMap.zipCodeData.state,
zipCodeCtu: resultMap.zipCodeData.zipCodeCtu,
}, },
false false
); );
@ -346,6 +348,7 @@ export default {
{ {
zipCode: this.serviceZipCode, zipCode: this.serviceZipCode,
state: zipCodeData.state, state: zipCodeData.state,
zipCodeCtu: zipCodeData.zipCodeCtu,
}, },
false false
); );
@ -461,6 +464,7 @@ export default {
vinInformation, vinInformation,
textBlock, textBlock,
Form, Form,
loadingModal,
}, },
}; };
</script> </script>

View file

@ -60,6 +60,7 @@ export default {
isValid: serviceZipValidationResponse.data.isValid, isValid: serviceZipValidationResponse.data.isValid,
isServiceable: serviceZipValidationResponse.data.isServiceable, isServiceable: serviceZipValidationResponse.data.isServiceable,
state: serviceZipValidationResponse.data.state, state: serviceZipValidationResponse.data.state,
zipCodeCtu: serviceZipValidationResponse.data.zipCodeCtu,
}; };
}, },
getTierOnePackagePrice(lineItems) { getTierOnePackagePrice(lineItems) {
@ -71,11 +72,14 @@ export default {
partType != partTypeStrings.REAR_WIPER && partType != partTypeStrings.REAR_WIPER &&
partType != partTypeStrings.RAIN_DEFENSE partType != partTypeStrings.RAIN_DEFENSE
) { ) {
totalPrice += lineItem.price; totalPrice += this.getTotalLineItemPrice(lineItem);
} }
}); });
return totalPrice; return totalPrice;
}, },
getTotalLineItemPrice(lineItem) {
return lineItem.kitPrice + lineItem.laborAmount + lineItem.sellingPrice;
},
}, },
computed: { computed: {
storeActions() { storeActions() {

View file

@ -7,12 +7,6 @@ import baseMixin from "@/mixins/base-mixin.js";
import store from "@/store"; import store from "@/store";
export default { export default {
data() {
// TODO KO DELETE AFTER QUOTE MVP
return {
shouldGoToHeritageQuote: false,
};
},
methods: { methods: {
hasPartQuestions(partsOrQuestions) { hasPartQuestions(partsOrQuestions) {
return partsOrQuestions?.some((pq) => pq.partQuestions?.length > 0); return partsOrQuestions?.some((pq) => pq.partQuestions?.length > 0);
@ -48,7 +42,9 @@ export default {
requiresCapabilityQuestions: singlePart.requiresCapabilityQuestions, requiresCapabilityQuestions: singlePart.requiresCapabilityQuestions,
recalibrationType: singlePart.recalibrationType, recalibrationType: singlePart.recalibrationType,
childParts: singlePart.childParts, childParts: singlePart.childParts,
price: singlePart.price, kitPrice: singlePart.kitPrice,
sellingPrice: singlePart.sellingPrice,
laborAmount: singlePart.laborAmount,
}); });
} }
}); });
@ -150,7 +146,7 @@ export default {
return glass; return glass;
}, },
handleAnswerUpdates(answer, glassKey, vm) { handleCompletedQuestionChainAnswers(answer, glassKey, vm) {
// only runs when all questions in a question-chain have been answered // only runs when all questions in a question-chain have been answered
// when selectedAnswers updates, user has completed this part's question chain and has a final answer // when selectedAnswers updates, user has completed this part's question chain and has a final answer
// (does not get run for each invididual question's answer, only when // (does not get run for each invididual question's answer, only when
@ -176,51 +172,39 @@ export default {
self.selectedAnswers = {}; self.selectedAnswers = {};
// loop through every answered question on the currently answered glass part // loop through every answered question on the currently answered glass part
answer.answeredQuestions?.forEach((answeredQuestion) => { answer.answeredQuestions?.forEach((answeredQuestion, answeredQuestionIndex) => {
/* answeredQuestion example format: /* answeredQuestion example format:
{ {
"questionText": "Is your Grand Cherokee the Laredo model?", "questionText": "Is your Grand Cherokee the Laredo model?",
"selectedAnswer": "1|nextQuestion|3|Yes",
"selectedAnswerText": "Yes", "selectedAnswerText": "Yes",
"questionNum": 1, "questionNum": 1,
} }
*/ */
const answeredQuestionText = answeredQuestion.questionText.toUpperCase(); const answeredQuestionText = answeredQuestion.questionText.toUpperCase();
const answeredQuestionAnswer = answeredQuestion.selectedAnswerText.toUpperCase(); const answeredQuestionAnswer = answeredQuestion.selectedAnswer.toUpperCase();
const answeredQuestionAnswerText =
answeredQuestion.selectedAnswerText.toUpperCase();
const answeredQuestionNum = answeredQuestion.questionNum;
// HANDLE DUPLICATE QUESTIONS // HANDLE DUPLICATE QUESTIONS
// loop through all glass data // loop through all glass data
self.questionsData.forEach((glass, glassIndex) => { self.questionsData.forEach((glass, glassIndex) => {
/* glass example format: if (glassIndex === answer.index) {
{ glass.questions.forEach((question, questionIndex) => {
"glassName": "Single", // clear out any previously set answers on first pass with first answered question
"glassLocation": "Windshield", if (answeredQuestionIndex === 0) question.answerSelected = null;
"parts": null,
"questions": [
{
"questionSequence": 1,
"questionText": "Is your vehicle equipped with a black dotted pattern behind the rear view mirror, known as a third visor frit?",
"answers": [
{
"answerResult": "DW01537",
"answerText": "Yes",
"nextQuestionSequence": null
},
{
"answerResult": "DW01537b",
"answerText": "No",
"nextQuestionSequence": null
}
]
}
],
"answerKey": "Windshield-Single",
"answerData": null
}
*/
// limit duplicate search to glass pieces that follow after the currently being answered glass piece if (answeredQuestionNum - 1 === questionIndex) {
// on the right question
question.answerSelected = answeredQuestionAnswer;
}
});
}
// limit duplicate search to glass pieces that are in or follow after the currently being answered glass piece
if (glassIndex > answer.index) { if (glassIndex > answer.index) {
let indexToSuppressTo; let indexToSuppressTo;
// reset this glass piece, in case user is changing their previous answers // reset this glass piece, in case user is changing their previous answers
@ -229,8 +213,8 @@ export default {
// loop through this glass piece's questions, looking for a questionText match // loop through this glass piece's questions, looking for a questionText match
glass.questions.forEach((question, questionIndex) => { glass.questions.forEach((question, questionIndex) => {
// clear out any previously set answers // clear out any previously set answers on first pass with first answer
question.answerSelected = null; if (answeredQuestionIndex === 0) question.answerSelected = null;
// clear or set suppressThisQuestion property for each question // clear or set suppressThisQuestion property for each question
if (indexToSuppressTo && questionIndex + 1 < indexToSuppressTo) { if (indexToSuppressTo && questionIndex + 1 < indexToSuppressTo) {
@ -245,7 +229,9 @@ export default {
// handle matching answer in duplicated question // handle matching answer in duplicated question
question.answers.forEach((ans) => { question.answers.forEach((ans) => {
if (ans.answerText.toUpperCase() === answeredQuestionAnswer) { if (
ans.answerText.toUpperCase() === answeredQuestionAnswerText
) {
matchedAnswer = ans; matchedAnswer = ans;
ans.selected = true; ans.selected = true;
} else { } else {
@ -339,7 +325,7 @@ export default {
glass.isSuppressedPart = true; glass.isSuppressedPart = true;
} }
} }
}); }); // DONE looping through glass.questions
// Update key to force re-render of glass piece with duplicate question in case user changes previous related answer in the chain // Update key to force re-render of glass piece with duplicate question in case user changes previous related answer in the chain
self.questionsData[glassIndex].key = self.questionsData[glassIndex].key =
@ -363,9 +349,8 @@ export default {
} }
} }
}, },
// TODO KO Delete `shouldGoToHeritageQuote`
// Can't use `this` because navigateForward is also called from vin-pages-mixin // Can't use `this` because navigateForward is also called from vin-pages-mixin
async navigateForward(partsOrQuestions, vm, shouldGoToHeritageQuote) { async navigateForward(partsOrQuestions, vm) {
const self = vm ?? this; const self = vm ?? this;
const currentPage = self.$route.query.fmgPage; const currentPage = self.$route.query.fmgPage;
@ -459,33 +444,62 @@ export default {
// save to store lineItems.glassParts // save to store lineItems.glassParts
self.$store.commit(storeMutations.UPDATE_GLASS_PARTS, collectedGlassParts); self.$store.commit(storeMutations.UPDATE_GLASS_PARTS, collectedGlassParts);
shouldGoToHeritageQuote const payment = store.getters.payment;
? navigateToHeritageFunnel()
: self.$router.navigateWithSaving( if (payment.isInsurance && payment.insuranceCoverage.isVerified) {
self.navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS, navigateToHeritageFunnel({ loadingModal: self.$refs.loadingModal });
self.$route } else {
); self.$router.navigateWithSaving(
self.navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS,
self.$route
);
}
} }
}, },
// Can't use `this` because navigateForward is also called from quote // Can't use `this` because navigateForward is also called from quote
navigateBack(vm) { async navigateBack(vm) {
const self = vm ?? this; const self = vm ?? this;
const partsOrQuestions = ( const currentPage = self.$route.query.fmgPage;
self.$store.getters.pageData(fmgPageValues.CAPABILITY_QUESTIONS) ?? const pageDataCapabilityQuestions = self.$store.getters.pageData(
self.$store.getters.pageData(fmgPageValues.MOLDING_QUESTIONS) ?? fmgPageValues.CAPABILITY_QUESTIONS
self.$store.getters.pageData(fmgPageValues.VEHICLE_PARTS) ?? );
self.$store.getters.pageData(fmgPageValues.PART_QUESTIONS) const pageDataMoldingQuestions = self.$store.getters.pageData(
)?.partsOrQuestions; fmgPageValues.MOLDING_QUESTIONS
const hasPartQuestions = this.hasPartQuestions(partsOrQuestions); );
const pageDataVehicleParts = self.$store.getters.pageData(fmgPageValues.VEHICLE_PARTS);
const pageDataPartQuestions = self.$store.getters.pageData(
fmgPageValues.PART_QUESTIONS
);
let currentPartsOrQuestions = null;
if (
currentPage !== fmgPageValues.CAPABILITY_QUESTIONS &&
!!pageDataCapabilityQuestions
) {
currentPartsOrQuestions = pageDataCapabilityQuestions?.partsOrQuestions;
} else if (
currentPage !== fmgPageValues.MOLDING_QUESTIONS &&
!!pageDataMoldingQuestions
) {
currentPartsOrQuestions = pageDataMoldingQuestions?.partsOrQuestions;
} else if (currentPage !== fmgPageValues.VEHICLE_PARTS && !!pageDataVehicleParts) {
currentPartsOrQuestions = pageDataVehicleParts?.partsOrQuestions;
} else if (currentPage !== fmgPageValues.PARTS_QUESTIONS && !!pageDataPartQuestions) {
currentPartsOrQuestions = pageDataPartQuestions?.partsOrQuestions;
}
const hasPartQuestions = this.hasPartQuestions(currentPartsOrQuestions);
const hasGlassLocationWithMultipleParts = const hasGlassLocationWithMultipleParts =
this.hasGlassLocationWithMultipleParts(partsOrQuestions); this.hasGlassLocationWithMultipleParts(currentPartsOrQuestions);
const hasChildPartQuestions = this.hasChildPartQuestions(partsOrQuestions); const hasChildPartQuestions = this.hasChildPartQuestions(currentPartsOrQuestions);
const hasCapabilityQuestions = this.hasCapabilityQuestions(partsOrQuestions); const hasCapabilityQuestions = this.hasCapabilityQuestions(currentPartsOrQuestions);
const skipVinLookup = await store.dispatch(storeActions.IS_VIN_OPTIONAL_VEHICLE);
let backNavigationScenario = self.$store.getters.vehicle.vin let backNavigationScenario = self.$store.getters.vehicle.vin
? navigationScenarios.CLICKED_BACK_WITH_VIN_AND_NO_MORE_QUESTIONS ? navigationScenarios.CLICKED_BACK_WITH_VIN_AND_NO_MORE_QUESTIONS
: navigationScenarios.CLICKED_BACK_WITH_NO_VIN_NOR_QUESTIONS; : navigationScenarios.CLICKED_BACK_WITH_NO_VIN_NOR_QUESTIONS;
const currentPage = self.$route.query.fmgPage;
if ( if (
hasCapabilityQuestions && hasCapabilityQuestions &&
this.currentPageComesAfterPage(currentPage, fmgPageValues.CAPABILITY_QUESTIONS) this.currentPageComesAfterPage(currentPage, fmgPageValues.CAPABILITY_QUESTIONS)
@ -507,13 +521,11 @@ export default {
this.currentPageComesAfterPage(currentPage, fmgPageValues.PART_QUESTIONS) this.currentPageComesAfterPage(currentPage, fmgPageValues.PART_QUESTIONS)
) { ) {
backNavigationScenario = navigationScenarios.CLICKED_BACK_WITH_PART_QUESTIONS; backNavigationScenario = navigationScenarios.CLICKED_BACK_WITH_PART_QUESTIONS;
} else if (skipVinLookup) {
backNavigationScenario = navigationScenarios.CLICKED_BACK_TO_GO_TO_ESTIMATE;
} }
self.$router.navigateWithoutSaving(backNavigationScenario, self.$route); self.$router.navigateWithoutSaving(backNavigationScenario, self.$route);
}, },
// TODO KO delete this after quote mvp
async handleTempButtonClicked(vm) {
this.shouldGoToHeritageQuote = true;
},
}, },
}; };

View file

@ -3,6 +3,7 @@ import { shallowMount } from "@vue/test-utils";
import { setupMocksForJsFiles, getMountOptions } from "@/helpers/unit-test-helper.js"; import { setupMocksForJsFiles, getMountOptions } from "@/helpers/unit-test-helper.js";
import { fmgPageValues } from "@/router/router-constants/fmgPage-values"; import { fmgPageValues } from "@/router/router-constants/fmgPage-values";
import { storeMutations } from "@/constants/store-mutations"; import { storeMutations } from "@/constants/store-mutations";
import store from "@/store";
import { storeActions } from "@/constants/store-actions"; import { storeActions } from "@/constants/store-actions";
import { navigationScenarios } from "../router/router-constants/navigation-scenarios"; import { navigationScenarios } from "../router/router-constants/navigation-scenarios";
import { getters } from "@/store"; import { getters } from "@/store";
@ -410,7 +411,7 @@ describe("vehicle-questions-mixin", () => {
}); });
}); });
describe("handleAnswerUpdates", () => { describe("handleCompletedQuestionChainAnswers", () => {
describe("selectedAnswers", () => { describe("selectedAnswers", () => {
test("should be cleared to be empty", () => { test("should be cleared to be empty", () => {
// Arrange // Arrange
@ -419,6 +420,7 @@ describe("vehicle-questions-mixin", () => {
answeredQuestions: [ answeredQuestions: [
{ {
questionText: "Test duplicate question 1?", questionText: "Test duplicate question 1?",
selectedAnswer: "1|nextQuestion|3|Yes",
selectedAnswerText: "Yes", selectedAnswerText: "Yes",
questionNum: 1, questionNum: 1,
}, },
@ -438,7 +440,7 @@ describe("vehicle-questions-mixin", () => {
]; ];
// Act // Act
wrapper.vm.handleAnswerUpdates(answer, "", wrapper.vm); wrapper.vm.handleCompletedQuestionChainAnswers(answer, "", wrapper.vm);
// Assert // Assert
expect(wrapper.vm.selectedAnswers).toMatchObject({}); expect(wrapper.vm.selectedAnswers).toMatchObject({});
@ -453,6 +455,7 @@ describe("vehicle-questions-mixin", () => {
answeredQuestions: [ answeredQuestions: [
{ {
questionText: "Test duplicate question 1?", questionText: "Test duplicate question 1?",
selectedAnswer: "1|nextQuestion|3|Yes",
selectedAnswerText: "Yes", selectedAnswerText: "Yes",
questionNum: 1, questionNum: 1,
}, },
@ -477,7 +480,7 @@ describe("vehicle-questions-mixin", () => {
]; ];
// Act // Act
wrapper.vm.handleAnswerUpdates(answer, "", wrapper.vm); wrapper.vm.handleCompletedQuestionChainAnswers(answer, "", wrapper.vm);
// Assert // Assert
expect(wrapper.vm.questionsData[1].answerData).toEqual(null); expect(wrapper.vm.questionsData[1].answerData).toEqual(null);
@ -489,6 +492,7 @@ describe("vehicle-questions-mixin", () => {
answeredQuestions: [ answeredQuestions: [
{ {
questionText: "Test duplicate question 1?", questionText: "Test duplicate question 1?",
selectedAnswer: "1|nextQuestion|3|Yes",
selectedAnswerText: "Yes", selectedAnswerText: "Yes",
questionNum: 1, questionNum: 1,
}, },
@ -514,7 +518,7 @@ describe("vehicle-questions-mixin", () => {
]; ];
// Act // Act
wrapper.vm.handleAnswerUpdates(answer, "", wrapper.vm); wrapper.vm.handleCompletedQuestionChainAnswers(answer, "", wrapper.vm);
// Assert // Assert
expect(wrapper.vm.questionsData[1].isSuppressedPart).toEqual(null); expect(wrapper.vm.questionsData[1].isSuppressedPart).toEqual(null);
@ -526,6 +530,7 @@ describe("vehicle-questions-mixin", () => {
answeredQuestions: [ answeredQuestions: [
{ {
questionText: "Test duplicate question 1?", questionText: "Test duplicate question 1?",
selectedAnswer: "1|nextQuestion|3|Yes",
selectedAnswerText: "Yes", selectedAnswerText: "Yes",
questionNum: 1, questionNum: 1,
}, },
@ -587,7 +592,7 @@ describe("vehicle-questions-mixin", () => {
]; ];
// Act // Act
wrapper.vm.handleAnswerUpdates(answer, "", wrapper.vm); wrapper.vm.handleCompletedQuestionChainAnswers(answer, "", wrapper.vm);
// Assert // Assert
expect(wrapper.vm.questionsData[1].questions[0].answerSelected).toEqual(null); expect(wrapper.vm.questionsData[1].questions[0].answerSelected).toEqual(null);
@ -603,6 +608,7 @@ describe("vehicle-questions-mixin", () => {
answeredQuestions: [ answeredQuestions: [
{ {
questionText: "Test duplicate question 1?", questionText: "Test duplicate question 1?",
selectedAnswer: "1|nextQuestion|3|No",
selectedAnswerText: "No", selectedAnswerText: "No",
questionNum: 1, questionNum: 1,
}, },
@ -661,7 +667,7 @@ describe("vehicle-questions-mixin", () => {
]; ];
// Act // Act
wrapper.vm.handleAnswerUpdates(answerNo, "", wrapper.vm); wrapper.vm.handleCompletedQuestionChainAnswers(answerNo, "", wrapper.vm);
const glassWithDuplicate = wrapper.vm.questionsData[1]; const glassWithDuplicate = wrapper.vm.questionsData[1];
// Assert // Assert
@ -675,6 +681,7 @@ describe("vehicle-questions-mixin", () => {
answeredQuestions: [ answeredQuestions: [
{ {
questionText: "Test duplicate question 1?", questionText: "Test duplicate question 1?",
selectedAnswer: "1|nextQuestion|3|No",
selectedAnswerText: "No", selectedAnswerText: "No",
questionNum: 1, questionNum: 1,
}, },
@ -731,7 +738,7 @@ describe("vehicle-questions-mixin", () => {
]; ];
// Act // Act
wrapper.vm.handleAnswerUpdates(answerNo, "", wrapper.vm); wrapper.vm.handleCompletedQuestionChainAnswers(answerNo, "", wrapper.vm);
const duplicateQuestion = wrapper.vm.questionsData[1].questions[0]; const duplicateQuestion = wrapper.vm.questionsData[1].questions[0];
// Assert // Assert
@ -746,6 +753,7 @@ describe("vehicle-questions-mixin", () => {
answeredQuestions: [ answeredQuestions: [
{ {
questionText: "Test duplicate question 1?", questionText: "Test duplicate question 1?",
selectedAnswer: "1|nextQuestion|3|No",
selectedAnswerText: "No", selectedAnswerText: "No",
questionNum: 1, questionNum: 1,
}, },
@ -835,7 +843,7 @@ describe("vehicle-questions-mixin", () => {
]; ];
// Act // Act
wrapper.vm.handleAnswerUpdates(answerNo, "", wrapper.vm); wrapper.vm.handleCompletedQuestionChainAnswers(answerNo, "", wrapper.vm);
const nextQuestionAfterDuplicate = wrapper.vm.questionsData[1].questions[2]; const nextQuestionAfterDuplicate = wrapper.vm.questionsData[1].questions[2];
// Assert // Assert
@ -849,6 +857,7 @@ describe("vehicle-questions-mixin", () => {
answeredQuestions: [ answeredQuestions: [
{ {
questionText: "Test question 3?", questionText: "Test question 3?",
selectedAnswer: "1|nextQuestion|3|No",
selectedAnswerText: "No", selectedAnswerText: "No",
questionNum: 1, questionNum: 1,
}, },
@ -969,7 +978,7 @@ describe("vehicle-questions-mixin", () => {
]; ];
// Act // Act
wrapper.vm.handleAnswerUpdates(answerNo, "", wrapper.vm); wrapper.vm.handleCompletedQuestionChainAnswers(answerNo, "", wrapper.vm);
const duplicatedQuestion = wrapper.vm.questionsData[1].questions[2]; const duplicatedQuestion = wrapper.vm.questionsData[1].questions[2];
const duplicatedQuestionAnswer = duplicatedQuestion.answers.filter((a) => { const duplicatedQuestionAnswer = duplicatedQuestion.answers.filter((a) => {
return a.selected; return a.selected;
@ -997,6 +1006,7 @@ describe("vehicle-questions-mixin", () => {
answeredQuestions: [ answeredQuestions: [
{ {
questionText: "Test duplicate question 1?", questionText: "Test duplicate question 1?",
selectedAnswer: "1|nextQuestion|3|No",
selectedAnswerText: "No", selectedAnswerText: "No",
questionNum: 1, questionNum: 1,
}, },
@ -1085,7 +1095,7 @@ describe("vehicle-questions-mixin", () => {
]; ];
// Act // Act
wrapper.vm.handleAnswerUpdates(answerNo, "", wrapper.vm); wrapper.vm.handleCompletedQuestionChainAnswers(answerNo, "", wrapper.vm);
// Assert // Assert
expect( expect(
@ -1109,6 +1119,7 @@ describe("vehicle-questions-mixin", () => {
answeredQuestions: [ answeredQuestions: [
{ {
questionText: "Test question 3?", questionText: "Test question 3?",
selectedAnswer: "1|nextQuestion|3|No",
selectedAnswerText: "No", selectedAnswerText: "No",
questionNum: 1, questionNum: 1,
}, },
@ -1197,7 +1208,7 @@ describe("vehicle-questions-mixin", () => {
]; ];
// Act // Act
await wrapper.vm.handleAnswerUpdates(answerNo, "", wrapper.vm); await wrapper.vm.handleCompletedQuestionChainAnswers(answerNo, "", wrapper.vm);
const questionsToTest = wrapper.vm.questionsData[1].questions; const questionsToTest = wrapper.vm.questionsData[1].questions;
const answerLeadingToDuplicate = questionsToTest[0].answers[1]; const answerLeadingToDuplicate = questionsToTest[0].answers[1];
@ -2249,12 +2260,13 @@ describe("vehicle-questions-mixin", () => {
"current page is quote, there are no questions, and we don't have their vin => go to estimate" "current page is quote, there are no questions, and we don't have their vin => go to estimate"
); );
test("current page is quote, there are no questions, and we have their vin => go to vin-lookup", () => { test("current page is quote, there are no questions, and we have their vin => go to vin-lookup", async () => {
// Arrange // Arrange
const { wrapper } = setupMocks({ fmgPage: fmgPageValues.QUOTE, hasVin: true }); const { wrapper } = setupMocks({ fmgPage: fmgPageValues.QUOTE, hasVin: true });
// Act // Act
wrapper.vm.navigateBack(); store.dispatch = jest.fn(() => {});
await wrapper.vm.navigateBack();
// Assert // Assert
expect(wrapper.vm.$router.navigateWithoutSaving).toHaveBeenCalledWith( expect(wrapper.vm.$router.navigateWithoutSaving).toHaveBeenCalledWith(
@ -2263,13 +2275,14 @@ describe("vehicle-questions-mixin", () => {
); );
}); });
test("current page is quote and there are capability questions => go to capability questions", () => { test("current page is quote and there are capability questions => go to capability questions", async () => {
// Arrange // Arrange
const { wrapper } = setupMocks({ fmgPage: fmgPageValues.QUOTE }); const { wrapper } = setupMocks({ fmgPage: fmgPageValues.QUOTE });
wrapper.vm.hasCapabilityQuestions = jest.fn().mockReturnValue(true); wrapper.vm.hasCapabilityQuestions = jest.fn().mockReturnValue(true);
// Act // Act
wrapper.vm.navigateBack(); store.dispatch = jest.fn(() => {});
await wrapper.vm.navigateBack();
// Assert // Assert
expect(wrapper.vm.$router.navigateWithoutSaving).toHaveBeenCalledWith( expect(wrapper.vm.$router.navigateWithoutSaving).toHaveBeenCalledWith(
@ -2278,14 +2291,14 @@ describe("vehicle-questions-mixin", () => {
); );
}); });
test("current page is quote and there are part questions and molding questions => go to molding questions", () => { test("current page is quote and there are part questions and molding questions => go to molding questions", async () => {
// Arrange // Arrange
const { wrapper } = setupMocks({ fmgPage: fmgPageValues.QUOTE }); const { wrapper } = setupMocks({ fmgPage: fmgPageValues.QUOTE });
wrapper.vm.hasPartQuestions = jest.fn().mockReturnValue(true); wrapper.vm.hasPartQuestions = jest.fn().mockReturnValue(true);
wrapper.vm.hasChildPartQuestions = jest.fn().mockReturnValue(true); wrapper.vm.hasChildPartQuestions = jest.fn().mockReturnValue(true);
// Act // Act
wrapper.vm.navigateBack(); await wrapper.vm.navigateBack();
// Assert // Assert
expect(wrapper.vm.$router.navigateWithoutSaving).toHaveBeenCalledWith( expect(wrapper.vm.$router.navigateWithoutSaving).toHaveBeenCalledWith(
@ -2294,7 +2307,7 @@ describe("vehicle-questions-mixin", () => {
); );
}); });
test("current page is molding questions and there are part questions, multiple parts to choose, and capability questions => go to vehicle-parts", () => { test("current page is molding questions and there are part questions, multiple parts to choose, and capability questions => go to vehicle-parts", async () => {
// Arrange // Arrange
const { wrapper } = setupMocks({ fmgPage: fmgPageValues.MOLDING_QUESTIONS }); const { wrapper } = setupMocks({ fmgPage: fmgPageValues.MOLDING_QUESTIONS });
wrapper.vm.hasPartQuestions = jest.fn().mockReturnValue(true); wrapper.vm.hasPartQuestions = jest.fn().mockReturnValue(true);
@ -2303,7 +2316,8 @@ describe("vehicle-questions-mixin", () => {
wrapper.vm.hasCapabilityQuestions = jest.fn().mockReturnValue(true); wrapper.vm.hasCapabilityQuestions = jest.fn().mockReturnValue(true);
// Act // Act
wrapper.vm.navigateBack(); store.dispatch = jest.fn(() => {});
await wrapper.vm.navigateBack();
// Assert // Assert
expect(wrapper.vm.$router.navigateWithoutSaving).toHaveBeenCalledWith( expect(wrapper.vm.$router.navigateWithoutSaving).toHaveBeenCalledWith(
@ -2312,7 +2326,7 @@ describe("vehicle-questions-mixin", () => {
); );
}); });
test("current page is molding questions and there are part questions and capability questions => go to part-questions", () => { test("current page is molding questions and there are part questions and capability questions => go to part-questions", async () => {
// Arrange // Arrange
const { wrapper } = setupMocks({ fmgPage: fmgPageValues.MOLDING_QUESTIONS }); const { wrapper } = setupMocks({ fmgPage: fmgPageValues.MOLDING_QUESTIONS });
wrapper.vm.hasPartQuestions = jest.fn().mockReturnValue(true); wrapper.vm.hasPartQuestions = jest.fn().mockReturnValue(true);
@ -2321,7 +2335,8 @@ describe("vehicle-questions-mixin", () => {
wrapper.vm.hasCapabilityQuestions = jest.fn().mockReturnValue(true); wrapper.vm.hasCapabilityQuestions = jest.fn().mockReturnValue(true);
// Act // Act
wrapper.vm.navigateBack(); store.dispatch = jest.fn(() => {});
await wrapper.vm.navigateBack();
// Assert // Assert
expect(wrapper.vm.$router.navigateWithoutSaving).toHaveBeenCalledWith( expect(wrapper.vm.$router.navigateWithoutSaving).toHaveBeenCalledWith(

View file

@ -4,12 +4,6 @@ import vehicleQuestionsMixin from "@/mixins/vehicle-questions-mixin";
import { saveSession } from "@/helpers/heritage-integration/order-helper.js"; import { saveSession } from "@/helpers/heritage-integration/order-helper.js";
export default { export default {
data() {
// TODO KO DELETE AFTER QUOTE MVP
return {
shouldGoToHeritageQuote: false,
};
},
methods: { methods: {
async navigateForwardWithSingleCarMatch() { async navigateForwardWithSingleCarMatch() {
// If we have not already saved a session, we need to save one now before the lengthy call to getPartsOrQuestions // If we have not already saved a session, we need to save one now before the lengthy call to getPartsOrQuestions
@ -20,17 +14,7 @@ export default {
const result = await this.dispatchStoreAction(storeActions.GET_PARTS_OR_QUESTIONS); const result = await this.dispatchStoreAction(storeActions.GET_PARTS_OR_QUESTIONS);
const partsOrQuestions = result.data.partsOrQuestions; const partsOrQuestions = result.data.partsOrQuestions;
// TODO KO delete `this.shouldGoToHeritageQuote` vehicleQuestionsMixin.methods.navigateForward(partsOrQuestions, this);
vehicleQuestionsMixin.methods.navigateForward(
partsOrQuestions,
this,
this.shouldGoToHeritageQuote
);
},
// TODO KO delete this after quote mvp
async handleTempButtonClicked(vm) {
this.shouldGoToHeritageQuote = true;
await vm.forwardButtonAction();
}, },
}, },
}; };

View file

@ -51,7 +51,7 @@ const routes = [
} }
if (getFunnelCookie()?.SuppressConceptFunnel) { if (getFunnelCookie()?.SuppressConceptFunnel) {
await navigateToHeritageFunnel(false); await navigateToHeritageFunnel({ shouldSaveSession: false });
return next(false); return next(false);
} }
@ -66,6 +66,7 @@ const routes = [
// clear the saveSessionPromise - if it exists in the vuex store but a new instance was created // clear the saveSessionPromise - if it exists in the vuex store but a new instance was created
// the saveSessionPromise will no longer point to a valid promise // the saveSessionPromise will no longer point to a valid promise
baseMixin.methods.dispatchStoreAction(storeActions.RESET_SAVE_SESSION_PROMISE); baseMixin.methods.dispatchStoreAction(storeActions.RESET_SAVE_SESSION_PROMISE);
const loadSessionResponse = await loadSessionIfPresent(); const loadSessionResponse = await loadSessionIfPresent();
const pageToRedirectTo = await getPageToRouteExistingOrderTo( const pageToRedirectTo = await getPageToRouteExistingOrderTo(
to, to,

View file

@ -20,6 +20,10 @@ const navigationScenarios = {
// Vin selection // Vin selection
CLICKED_BACK_WITH_VIN: "CLICKED_BACK_WITH_VIN", CLICKED_BACK_WITH_VIN: "CLICKED_BACK_WITH_VIN",
CLICKED_FORWARD_WITH_VIN: "CLICKED_FORWARD_WITH_VIN", CLICKED_FORWARD_WITH_VIN: "CLICKED_FORWARD_WITH_VIN",
CLICKED_FORWARD_WITH_REPAIR_AND_VERIFIED_INSURANCE:
"CLICKED_FORWARD_WITH_REPAIR_AND_VERIFIED_INSURANCE",
CLICKED_FORWARD_WITH_REPLACE_AND_VERIFIED_INSURANCE:
"CLICKED_FORWARD_WITH_REPLACE_AND_VERIFIED_INSURANCE",
CLICKED_FORWARD_WITHOUT_VIN: "CLICKED_FORWARD_WITHOUT_VIN", CLICKED_FORWARD_WITHOUT_VIN: "CLICKED_FORWARD_WITHOUT_VIN",
CLICKED_FORWARD_WITH_MULTIPLE_VEHICLES: "CLICKED_FORWARD_WITH_MULTIPLE_VEHICLES", CLICKED_FORWARD_WITH_MULTIPLE_VEHICLES: "CLICKED_FORWARD_WITH_MULTIPLE_VEHICLES",
SELECTED_VIN_WITH_MISMATCHED_GLASS: "SELECTED_VIN_WITH_MISMATCHED_GLASS", SELECTED_VIN_WITH_MISMATCHED_GLASS: "SELECTED_VIN_WITH_MISMATCHED_GLASS",
@ -40,6 +44,7 @@ const navigationScenarios = {
CLICKED_BACK_WITH_CAPABILITY_QUESTIONS: "CLICKED_BACK_WITH_CAPABILITY_QUESTIONS", CLICKED_BACK_WITH_CAPABILITY_QUESTIONS: "CLICKED_BACK_WITH_CAPABILITY_QUESTIONS",
CLICKED_BACK_WITH_VIN_AND_NO_MORE_QUESTIONS: "CLICKED_BACK_WITH_VIN_AND_NO_MORE_QUESTIONS", CLICKED_BACK_WITH_VIN_AND_NO_MORE_QUESTIONS: "CLICKED_BACK_WITH_VIN_AND_NO_MORE_QUESTIONS",
CLICKED_BACK_WITH_NO_VIN_NOR_QUESTIONS: "CLICKED_BACK_WITH_NO_VIN_NOR_QUESTIONS", CLICKED_BACK_WITH_NO_VIN_NOR_QUESTIONS: "CLICKED_BACK_WITH_NO_VIN_NOR_QUESTIONS",
CLICKED_BACK_TO_GO_TO_ESTIMATE: "CLICKED_BACK_TO_GO_TO_ESTIMATE",
}; };
export { navigationScenarios }; export { navigationScenarios };

View file

@ -67,6 +67,16 @@ const routingTable = function (store) {
scenario: navigationScenarios.CLICKED_FORWARD_WITHOUT_VIN, scenario: navigationScenarios.CLICKED_FORWARD_WITHOUT_VIN,
destinationFmgPageValue: fmgPageValues.ESTIMATE, destinationFmgPageValue: fmgPageValues.ESTIMATE,
}, },
{
scenario:
navigationScenarios.CLICKED_FORWARD_WITH_REPAIR_AND_VERIFIED_INSURANCE,
destinationFmgPageValue: fmgPageValues.ESTIMATE,
},
{
scenario:
navigationScenarios.CLICKED_FORWARD_WITH_REPLACE_AND_VERIFIED_INSURANCE,
destinationFmgPageValue: fmgPageValues.VIN_LOOKUP,
},
], ],
}, },
{ {
@ -241,6 +251,22 @@ const routingTable = function (store) {
scenario: navigationScenarios.CLICKED_FORWARD_WITH_NO_QUESTIONS, scenario: navigationScenarios.CLICKED_FORWARD_WITH_NO_QUESTIONS,
destinationFmgPageValue: fmgPageValues.QUOTE, destinationFmgPageValue: fmgPageValues.QUOTE,
}, },
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_PART_QUESTIONS,
destinationFmgPageValue: fmgPageValues.PART_QUESTIONS,
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_MULTIPLE_PARTS_TO_CHOOSE,
destinationFmgPageValue: fmgPageValues.VEHICLE_PARTS,
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_MOLDING_QUESTIONS,
destinationFmgPageValue: fmgPageValues.MOLDING_QUESTIONS,
},
{
scenario: navigationScenarios.CLICKED_FORWARD_WITH_CAPABILITY_QUESTIONS,
destinationFmgPageValue: fmgPageValues.CAPABILITY_QUESTIONS,
},
], ],
}, },
{ {
@ -270,6 +296,10 @@ const routingTable = function (store) {
scenario: navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS, scenario: navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS,
destinationFmgPageValue: fmgPageValues.QUOTE, destinationFmgPageValue: fmgPageValues.QUOTE,
}, },
{
scenario: navigationScenarios.CLICKED_BACK_TO_GO_TO_ESTIMATE,
destinationFmgPageValue: fmgPageValues.ESTIMATE,
},
], ],
}, },
{ {
@ -303,6 +333,10 @@ const routingTable = function (store) {
scenario: navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS, scenario: navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS,
destinationFmgPageValue: fmgPageValues.QUOTE, destinationFmgPageValue: fmgPageValues.QUOTE,
}, },
{
scenario: navigationScenarios.CLICKED_BACK_TO_GO_TO_ESTIMATE,
destinationFmgPageValue: fmgPageValues.ESTIMATE,
},
], ],
}, },
{ {
@ -332,6 +366,10 @@ const routingTable = function (store) {
scenario: navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS, scenario: navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS,
destinationFmgPageValue: fmgPageValues.QUOTE, destinationFmgPageValue: fmgPageValues.QUOTE,
}, },
{
scenario: navigationScenarios.CLICKED_BACK_TO_GO_TO_ESTIMATE,
destinationFmgPageValue: fmgPageValues.ESTIMATE,
},
], ],
}, },
{ {
@ -361,6 +399,10 @@ const routingTable = function (store) {
scenario: navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS, scenario: navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS,
destinationFmgPageValue: fmgPageValues.QUOTE, destinationFmgPageValue: fmgPageValues.QUOTE,
}, },
{
scenario: navigationScenarios.CLICKED_BACK_TO_GO_TO_ESTIMATE,
destinationFmgPageValue: fmgPageValues.ESTIMATE,
},
], ],
}, },
{ {

View file

@ -9,6 +9,7 @@ import { applicationConfig } from "@/constants/application-config";
import { experimentTriggers } from "@/constants/experiments"; import { experimentTriggers } from "@/constants/experiments";
import { damageLocationsSelected } from "@/constants/damage-locations-selected"; import { damageLocationsSelected } from "@/constants/damage-locations-selected";
import { fmgPageValues } from "@/router/router-constants/fmgPage-values"; import { fmgPageValues } from "@/router/router-constants/fmgPage-values";
import router from "@/router";
// Export State // Export State
const getDefaultState = () => { const getDefaultState = () => {
@ -40,6 +41,7 @@ const getDefaultState = () => {
city: null, city: null,
state: null, state: null,
zipCode: null, zipCode: null,
zipCodeCtu: null,
}, },
customer: { customer: {
emailAddress: null, emailAddress: null,
@ -62,6 +64,7 @@ const getDefaultState = () => {
isInsurance: null, isInsurance: null,
insuranceCoverage: { insuranceCoverage: {
isVerified: null, isVerified: null,
coverageStatus: null,
}, },
}, },
referralNumber: null, referralNumber: null,
@ -81,12 +84,6 @@ const getDefaultState = () => {
experiments: [], experiments: [],
triggeredSiteEntry: false, triggeredSiteEntry: false,
}, },
// gaClickInformation: {
// currentlySelectedValues: {},
// firedGaClickEventValues: {},
// lastFocusedInputGroup: "",
// wasLastFocusedInputMultiselect: undefined,
// },
}; };
}; };
@ -152,6 +149,9 @@ export const mutations = {
updateSupportingItems(state, partsData) { updateSupportingItems(state, partsData) {
state.order.lineItems.supportingItems = partsData; state.order.lineItems.supportingItems = partsData;
}, },
updateLineItemsServerData(state, serverData) {
state.order.lineItems.serverData = serverData;
},
updatePageData(state, pageData) { updatePageData(state, pageData) {
state.applicationUser.pageData[pageData.page] = pageData.data; state.applicationUser.pageData[pageData.page] = pageData.data;
}, },
@ -191,12 +191,6 @@ export const mutations = {
updateRegistrationZipCode(state, registrationZipCode) { updateRegistrationZipCode(state, registrationZipCode) {
state.order.vehicle.registration.zipCode = registrationZipCode; state.order.vehicle.registration.zipCode = registrationZipCode;
}, },
updateServiceLocationZipCode(state, serviceLocationZip) {
state.order.serviceLocation.zipCode = serviceLocationZip;
},
updateServiceLocationState(state, serviceLocationState) {
state.order.serviceLocation.state = serviceLocationState;
},
updateRegistrationFirstName(state, firstName) { updateRegistrationFirstName(state, firstName) {
state.order.vehicle.registration.firstName = firstName; state.order.vehicle.registration.firstName = firstName;
}, },
@ -233,6 +227,7 @@ export const mutations = {
state.order.serviceLocation.city = serviceLocationInfo.city; state.order.serviceLocation.city = serviceLocationInfo.city;
state.order.serviceLocation.state = serviceLocationInfo.state; state.order.serviceLocation.state = serviceLocationInfo.state;
state.order.serviceLocation.zipCode = serviceLocationInfo.zipCode; state.order.serviceLocation.zipCode = serviceLocationInfo.zipCode;
state.order.serviceLocation.zipCodeCtu = serviceLocationInfo.zipCodeCtu;
}, },
// applicationUser MUTATIONS // applicationUser MUTATIONS
@ -302,12 +297,6 @@ export const mutations = {
state.applicationUser.pageData[fmgPageValues.MOLDING_QUESTIONS] = null; state.applicationUser.pageData[fmgPageValues.MOLDING_QUESTIONS] = null;
state.applicationUser.pageData[fmgPageValues.CAPABILITY_QUESTIONS] = null; state.applicationUser.pageData[fmgPageValues.CAPABILITY_QUESTIONS] = null;
}, },
resetSupportingItemsState(state) {
state.order.lineItems.supportingItems = null;
},
resetVapsState(state) {
state.order.lineItems.vaps = null;
},
resetState(state) { resetState(state) {
Object.assign(state, getDefaultState()); Object.assign(state, getDefaultState());
}, },
@ -365,11 +354,15 @@ export const mutations = {
(state.order.serviceLocation.city = sessionInformation.order.serviceLocation.city), (state.order.serviceLocation.city = sessionInformation.order.serviceLocation.city),
(state.order.serviceLocation.state = sessionInformation.order.serviceLocation.state), (state.order.serviceLocation.state = sessionInformation.order.serviceLocation.state),
(state.order.serviceLocation.zipCode = (state.order.serviceLocation.zipCode =
sessionInformation.order.serviceLocation.zipCode); sessionInformation.order.serviceLocation.zipCode),
(state.order.serviceLocation.zipCodeCtu =
sessionInformation.order.serviceLocation.zipCodeCtu);
state.order.payment.isInsurance = sessionInformation.order.payment.isInsurance; state.order.payment.isInsurance = sessionInformation.order.payment.isInsurance;
state.order.payment.insuranceCoverage.isVerified = state.order.payment.insuranceCoverage.isVerified =
sessionInformation?.order.payment.insuranceCoverage.isVerified; sessionInformation?.order.payment.insuranceCoverage.isVerified;
state.order.payment.insuranceCoverage.coverageStatus =
sessionInformation?.insuranceInfo?.coverageStatus;
state.order.customer.emailAddress = sessionInformation.order.customer.emailAddress; state.order.customer.emailAddress = sessionInformation.order.customer.emailAddress;
state.order.existingPromoCode = sessionInformation.order.existingPromoCode; state.order.existingPromoCode = sessionInformation.order.existingPromoCode;
@ -599,19 +592,17 @@ export const actions = {
resetDamageAndDependencies(context) { resetDamageAndDependencies(context) {
context.commit(storeMutations.RESET_DAMAGE_STATE); context.commit(storeMutations.RESET_DAMAGE_STATE);
context.commit(storeMutations.RESET_GLASS_PARTS_STATE); context.commit(storeMutations.RESET_GLASS_PARTS_STATE);
context.commit(storeMutations.RESET_SUPPORTING_ITEMS_STATE); context.commit(storeMutations.UPDATE_SUPPORTING_ITEMS, null);
context.commit(storeMutations.RESET_VAPS_STATE); context.commit(storeMutations.UPDATE_VAPS, null);
}, },
resetRegistrationAndDependencies(context) { resetRegistrationAndDependencies(context) {
context.commit(storeMutations.RESET_REGISTRATION_STATE); context.commit(storeMutations.RESET_REGISTRATION_STATE);
context.commit(storeMutations.RESET_GLASS_PARTS_STATE); context.commit(storeMutations.RESET_GLASS_PARTS_STATE);
context.commit(storeMutations.RESET_SUPPORTING_ITEMS_STATE); context.commit(storeMutations.UPDATE_SUPPORTING_ITEMS, null);
context.commit(storeMutations.RESET_VAPS_STATE);
}, },
resetPartsAndDependencies(context) { resetPartsAndDependencies(context) {
context.commit(storeMutations.RESET_GLASS_PARTS_STATE); context.commit(storeMutations.RESET_GLASS_PARTS_STATE);
context.commit(storeMutations.RESET_SUPPORTING_ITEMS_STATE); context.commit(storeMutations.UPDATE_SUPPORTING_ITEMS, null);
context.commit(storeMutations.RESET_VAPS_STATE);
}, },
resetState(context) { resetState(context) {
context.commit(storeMutations.RESET_STATE); context.commit(storeMutations.RESET_STATE);
@ -925,14 +916,14 @@ export const actions = {
const carId = context.getters.vehicle.carId; const carId = context.getters.vehicle.carId;
const isRepair = context.getters.damage.isRepair; const isRepair = context.getters.damage.isRepair;
const numberOfChips = context.getters.damage.numberOfChips; const numberOfChips = context.getters.damage.numberOfChips;
const parentAccountNumber = context.getters.order.accountNumber.toString();
return globalMethods.callHttpClient({ return globalMethods.callHttpClient({
method: endpoints.GetSupportingItems.method, method: endpoints.GetSupportingItems.method,
endpoint: endpoints.GetSupportingItems.url, endpoint: endpoints.GetSupportingItems.url,
payload: { payload: {
carId: carId, carId: carId,
serviceType: isRepair ? "Repair" : "Replace", serviceType: isRepair ? "Repair" : "Replace",
parentAccountNumber: parentAccountNumber, parentAccountNumber: applicationConfig.CASH_ACCOUNT_NUMBER,
parts: glassPartsArray, parts: glassPartsArray,
numberOfRepairChips: isRepair ? numberOfChips : 0, numberOfRepairChips: isRepair ? numberOfChips : 0,
}, },
@ -1029,29 +1020,40 @@ export const actions = {
}, },
isInsurance: order.payment.isInsurance ?? false, isInsurance: order.payment.isInsurance ?? false,
}, },
accountNumber: order.accountNumber?.toString(), accountNumber: order.accountNumber,
providerNumber: "", providerNumber: "",
serviceLocation: { serviceLocation: {
streetAddress: order.serviceLocation.address, streetAddress: order.serviceLocation.address,
city: order.serviceLocation.city, city: order.serviceLocation.city,
state: order.serviceLocation.state, state: order.serviceLocation.state,
zipCode: order.serviceLocation.zipCode, zipCode: order.serviceLocation.zipCode,
zipCodeCtu: order.serviceLocation.zipCodeCtu,
}, },
existingPromoCode: null, existingPromoCode: null,
referralCorrelationId: order.referralCorrelationId, referralCorrelationId: order.referralCorrelationId,
referralDate: order.referralDate, referralDate: order.referralDate,
referralNumber: order.referralNumber?.toString(), // TODO It'd be nice to save these as strings in the first place referralNumber: order.referralNumber?.toString(), // TODO It'd be nice to save these as strings in the first place
referralSequenceNumber: order.referralNumber?.toString(), // TODO Pass the referralSequence number once insurance flow creates it referralSequenceNumber: order.referralNumber?.toString(), // TODO Pass the referralSequence number once insurance flow creates it
eon: order.eon,
}, },
}, },
}); });
}, },
loadSession(context, { savedSessionId }) { loadSession(context, { savedSessionId, referralNumber, referralCorrelationId, accountNumber }) {
const order = context.state.order;
return globalMethods return globalMethods
.callHttpClient({ .callHttpClient({
method: endpoints.LoadSession.method, method: endpoints.LoadSession.method,
endpoint: endpoint: `${
endpoints.LoadSession.url + "?savedSessionId=" + savedSessionId?.toString(), endpoints.LoadSession.url
}?savedSessionId=${savedSessionId?.toString()}&referralNumber=${
referralNumber && referralNumber !== "" ? referralNumber : order.referralNumber
}&accountNumber=${accountNumber}&referralCorrelationId=${
referralCorrelationId !== ""
? referralCorrelationId
: order.referralCorrelationId
}`,
}) })
.then((response) => { .then((response) => {
// Flatten location and name properties // Flatten location and name properties
@ -1182,6 +1184,7 @@ export const actions = {
if (isDamageChanging) { if (isDamageChanging) {
//Reset dependent state when changing //Reset dependent state when changing
context.dispatch(storeActions.RESET_PARTS_STATE_AND_DEPENDENCIES); context.dispatch(storeActions.RESET_PARTS_STATE_AND_DEPENDENCIES);
context.commit(storeMutations.UPDATE_VAPS, null);
// Save new values // Save new values
context.commit(storeMutations.UPDATE_IS_REPAIR, isWindshieldRepair); context.commit(storeMutations.UPDATE_IS_REPAIR, isWindshieldRepair);
@ -1273,7 +1276,7 @@ export const actions = {
if (havePartQuestionAnswersChanged) { if (havePartQuestionAnswersChanged) {
context.commit(storeMutations.UPDATE_GLASS_PARTS, null); context.commit(storeMutations.UPDATE_GLASS_PARTS, null);
context.commit(storeMutations.RESET_SUPPORTING_ITEMS_STATE); context.commit(storeMutations.UPDATE_SUPPORTING_ITEMS, null);
context.commit(storeMutations.UPDATE_MOLDING_QUESTION_ANSWERS, null); context.commit(storeMutations.UPDATE_MOLDING_QUESTION_ANSWERS, null);
context.commit(storeMutations.UPDATE_CAPABILITY_QUESTION_ANSWERS, null); context.commit(storeMutations.UPDATE_CAPABILITY_QUESTION_ANSWERS, null);
context.commit(storeMutations.UPDATE_PAGE_DATA, { context.commit(storeMutations.UPDATE_PAGE_DATA, {
@ -1319,7 +1322,7 @@ export const actions = {
if (haveSelectedVehiclePartsChanged) { if (haveSelectedVehiclePartsChanged) {
context.commit(storeMutations.UPDATE_GLASS_PARTS, null); context.commit(storeMutations.UPDATE_GLASS_PARTS, null);
context.commit(storeMutations.RESET_SUPPORTING_ITEMS_STATE); context.commit(storeMutations.UPDATE_SUPPORTING_ITEMS, null);
context.commit(storeMutations.UPDATE_MOLDING_QUESTION_ANSWERS, null); context.commit(storeMutations.UPDATE_MOLDING_QUESTION_ANSWERS, null);
context.commit(storeMutations.UPDATE_CAPABILITY_QUESTION_ANSWERS, null); context.commit(storeMutations.UPDATE_CAPABILITY_QUESTION_ANSWERS, null);
context.commit(storeMutations.UPDATE_PAGE_DATA, { context.commit(storeMutations.UPDATE_PAGE_DATA, {
@ -1349,7 +1352,7 @@ export const actions = {
if (haveMoldingQuestionAnswersChanged) { if (haveMoldingQuestionAnswersChanged) {
context.commit(storeMutations.UPDATE_GLASS_PARTS, null); context.commit(storeMutations.UPDATE_GLASS_PARTS, null);
context.commit(storeMutations.RESET_SUPPORTING_ITEMS_STATE); context.commit(storeMutations.UPDATE_SUPPORTING_ITEMS, null);
context.commit(storeMutations.UPDATE_CAPABILITY_QUESTION_ANSWERS, null); context.commit(storeMutations.UPDATE_CAPABILITY_QUESTION_ANSWERS, null);
context.commit(storeMutations.UPDATE_PAGE_DATA, { context.commit(storeMutations.UPDATE_PAGE_DATA, {
page: fmgPageValues.CAPABILITY_QUESTIONS, page: fmgPageValues.CAPABILITY_QUESTIONS,
@ -1377,7 +1380,7 @@ export const actions = {
if (haveCapabilityQuestionAnswersChanged) { if (haveCapabilityQuestionAnswersChanged) {
context.commit(storeMutations.UPDATE_GLASS_PARTS, null); context.commit(storeMutations.UPDATE_GLASS_PARTS, null);
context.commit(storeMutations.RESET_SUPPORTING_ITEMS_STATE); context.commit(storeMutations.UPDATE_SUPPORTING_ITEMS, null);
} }
//Save new values //Save new values
@ -1386,23 +1389,47 @@ export const actions = {
capabilityQuestionAnswers capabilityQuestionAnswers
); );
}, },
// Quote Page savePaymentType(context, isInsurance) {
saveQuotePageSelections( context.commit(storeMutations.UPDATE_IS_INSURANCE, isInsurance);
context, },
{ isInsuranceSelected, vapsItemsToAdd, supportingItemsToAdd } saveAccountNumber(context, accountNumber) {
) { context.commit(storeMutations.UPDATE_PARENT_ACCT_NUMBER, accountNumber);
context.commit(storeMutations.RESET_SUPPORTING_ITEMS_STATE); },
context.commit(storeMutations.RESET_VAPS_STATE); saveSupportingItems(context, supportingItems) {
context.commit(storeMutations.UPDATE_IS_INSURANCE, isInsuranceSelected); context.commit(storeMutations.UPDATE_SUPPORTING_ITEMS, supportingItems);
context.commit(storeMutations.UPDATE_VAPS, vapsItemsToAdd); },
context.commit(storeMutations.UPDATE_SUPPORTING_ITEMS, supportingItemsToAdd); saveVaps(context, vaps) {
context.commit(storeMutations.UPDATE_VAPS, vaps);
}, },
// Price order actions // Price order actions
// PLACEHOLDER, WILL CHANGE WHEN PRICING END POINT IS IMPLEMENTED async priceOrderItems(context, availableLineItems) {
priceOrderItems(context, availableLineItems) { const availableLineItemsFormattedForRequest = availableLineItems
availableLineItems.forEach((lineItem) => { .map((lineItem) => `&LineItems=${lineItem.partNumber}`)
lineItem["price"] = parseFloat((Math.random() * 100).toFixed(2)); .join("");
const vehicle = context.getters.order.vehicle;
let queryString =
`ParentAccountNumber=${applicationConfig.CASH_ACCOUNT_NUMBER}` +
`&CTU=${context.getters.order.serviceLocation.zipCodeCtu}` +
`&CarId=${vehicle.carId}` +
`&Make=${vehicle.make}` +
`&Model=${vehicle.model}` +
`&Year=${vehicle.year}` +
`&EON=${context.getters.order.eon}` +
`&ZipCode=${context.getters.order.serviceLocation.zipCode}` +
`${availableLineItemsFormattedForRequest}`;
const lineItemServerData = context.getters.order.lineItems.serverData;
if (lineItemServerData) {
queryString += `&ServerData=${lineItemServerData}`;
}
const response = await globalMethods.callHttpClient({
method: endpoints.PriceOrderItems.method,
endpoint: `${endpoints.PriceOrderItems.url}?${queryString}`,
}); });
context.commit(storeMutations.UPDATE_LINE_ITEMS_SERVER_DATA, response.data.serverData);
availableLineItems = addPricesToLineItems(availableLineItems, response.data.lineItems);
return availableLineItems; return availableLineItems;
}, },
// Misc order actions // Misc order actions
@ -1430,7 +1457,6 @@ export const actions = {
clearVin(context) { clearVin(context) {
context.commit(storeMutations.UPDATE_VEHICLE_VIN, null); context.commit(storeMutations.UPDATE_VEHICLE_VIN, null);
}, },
isVinOptionalVehicle(context) { isVinOptionalVehicle(context) {
switch (context.state.order.vehicle.make.toLowerCase()) { switch (context.state.order.vehicle.make.toLowerCase()) {
case "mercedes benz": case "mercedes benz":
@ -1444,8 +1470,9 @@ export const actions = {
if ( if (
context.state.order.vehicle.make.toLowerCase() === "ford" && context.state.order.vehicle.make.toLowerCase() === "ford" &&
context.state.order.vehicle.year >= 2018 context.state.order.vehicle.year >= 2018
) ) {
return true; return true;
}
if ( if (
context.state.order.vehicle.make.toLowerCase() === "bmw" && context.state.order.vehicle.make.toLowerCase() === "bmw" &&
@ -1552,3 +1579,15 @@ function convertGlassPieceNamingFromApi(glassArray) {
}); });
return glassArray; return glassArray;
} }
function addPricesToLineItems(lineItems, pricingLineItems) {
lineItems.forEach((lineItem) => {
let pricedLineItem = pricingLineItems.find(
(pricingLineItem) => pricingLineItem.partNumber === lineItem.partNumber
);
lineItem.laborAmount = pricedLineItem.laborAmount;
lineItem.sellingPrice = pricedLineItem.sellingPrice;
lineItem.kitPrice = pricedLineItem.kitPrice;
});
return lineItems;
}

View file

@ -312,6 +312,28 @@ describe("Mutations", () => {
// Assert // Assert
expect(storeState.applicationUser.triggeredSiteEntry).toEqual(true); expect(storeState.applicationUser.triggeredSiteEntry).toEqual(true);
}); });
it("updateServiceLocation, should set serviceLocation in state", () => {
// Arrange
const storeState = state;
const serviceLocation = {
address: "123 Test Lane",
city: "Columbus",
zipCode: "43212",
state: "OH",
zipCodeCtu: "01820",
};
// Act
mutations.updateServiceLocation(storeState, serviceLocation);
// Assert
expect(storeState.order.serviceLocation.address).toEqual("123 Test Lane");
expect(storeState.order.serviceLocation.city).toEqual("Columbus");
expect(storeState.order.serviceLocation.zipCode).toEqual("43212");
expect(storeState.order.serviceLocation.state).toEqual("OH");
expect(storeState.order.serviceLocation.zipCodeCtu).toEqual("01820");
});
}); });
describe("Actions", () => { describe("Actions", () => {
@ -357,13 +379,13 @@ describe("Actions", () => {
// Act // Act
globalMethods.callHttpClient.mockImplementation(() => { globalMethods.callHttpClient.mockImplementation(() => {
return Promise.resolve({ data: { carId: "C00000001" } }); return Promise.resolve({ data: { carId: "C0000001" } });
}); });
// Assert // Assert
const response = await actions.lookupVehicleByVin(context, "12345678901234567"); const response = await actions.lookupVehicleByVin(context, "12345678901234567");
expect(response.data).toEqual({ carId: "C00000001" }); expect(response.data).toEqual({ carId: "C0000001" });
}); });
it("lookupVinByPlate action, should return car data", async () => { it("lookupVinByPlate action, should return car data", async () => {
@ -467,13 +489,25 @@ describe("Actions", () => {
// Act // Act
globalMethods.callHttpClient.mockImplementation(() => { globalMethods.callHttpClient.mockImplementation(() => {
return Promise.resolve({ data: "43201" }); return Promise.resolve({
data: {
isValid: true,
isServiceable: true,
state: "OH",
zipCodeCtu: "01820",
},
});
}); });
const response = await actions.validateZip(context, "C00000000"); const response = await actions.validateZip(context, "43212");
// Assert // Assert
expect(response.data).toEqual("43201"); expect(response.data).toEqual({
isValid: true,
isServiceable: true,
state: "OH",
zipCodeCtu: "01820",
});
}); });
it("resetDamageAndDependencies action", async () => { it("resetDamageAndDependencies action", async () => {
@ -866,18 +900,30 @@ describe("Actions", () => {
); );
}); });
it("saveServiceLocation, should call mutation", () => { it("saveServiceLocation, should call mutation and save service address to state", () => {
// Arrange // Arrange
const context = state; const context = state;
const commit = jest.fn(); const commit = jest.fn();
context.commit = commit; context.commit = commit;
const serviceLocation = {
address: "123 Test Lane",
city: "Columbus",
zipCode: "43212",
state: "OH",
zipCodeCtu: "01820",
};
// Act // Act
actions.saveServiceLocation(context, { zipCode: "80020" }); actions.saveServiceLocation(context, serviceLocation);
// Assert // Assert
expect(commit).toBeCalledWith(storeMutations.UPDATE_SERVICE_LOCATION, { zipCode: "80020" }); expect(commit).toBeCalledWith(storeMutations.UPDATE_SERVICE_LOCATION, serviceLocation);
expect(state.order.serviceLocation.address).toEqual("123 Test Lane");
expect(state.order.serviceLocation.city).toEqual("Columbus");
expect(state.order.serviceLocation.zipCode).toEqual("43212");
expect(state.order.serviceLocation.state).toEqual("OH");
expect(state.order.serviceLocation.zipCodeCtu).toEqual("01820");
}); });
it("saveGlassParts, should call mutation", () => { it("saveGlassParts, should call mutation", () => {

View file

@ -55,9 +55,6 @@ export default {
this.isLoaderDisplayed = false; this.isLoaderDisplayed = false;
}, },
}, },
mounted() {
this.isLoaderDisplayed = false;
},
components: { components: {
loader, loader,
}, },