Merge branch 'develop' into nation/CASH-3022

This commit is contained in:
Carl Nation 2026-07-11 06:51:00 -04:00
commit f55378b3fa
27 changed files with 696 additions and 63 deletions

View file

@ -15,6 +15,7 @@ export interface ITestData extends base {
isMSRZip?: boolean, isMSRZip?: boolean,
isPIAEnabled?: boolean, isPIAEnabled?: boolean,
isDualRecal?: boolean, isDualRecal?: boolean,
isNewInsuranceFlow?: boolean,
} }

View file

@ -33,6 +33,7 @@ import { EndorsementsPage } from "../pages/EndorsementsPage"
import { PolicyDriverPage } from "../pages/PolicyDriverPage" import { PolicyDriverPage } from "../pages/PolicyDriverPage"
import { ServiceZipPage } from "../pages/ServiceZipPage" import { ServiceZipPage } from "../pages/ServiceZipPage"
import { MobileDetailsPage } from "pages/MobileDetailsPage"; import { MobileDetailsPage } from "pages/MobileDetailsPage";
import { PolicyInfoPage } from "../pages/PolicyInfoPage";
import { BailoutPage } from '../pages/BailoutPage'; import { BailoutPage } from '../pages/BailoutPage';
import { BailoutSuccessPage } from '../pages/BailoutSuccessPage'; import { BailoutSuccessPage } from '../pages/BailoutSuccessPage';
@ -53,6 +54,7 @@ export interface ITestPages {
orderConfirmationPage: OrderConfirmationPage, orderConfirmationPage: OrderConfirmationPage,
partQuestionsPage: PartQuestionsPage, partQuestionsPage: PartQuestionsPage,
paymentMethodPage: PaymentMethodPage, paymentMethodPage: PaymentMethodPage,
policyInfoPage: PolicyInfoPage,
policyInfoSubmittedPage: PolicyInfoSubmittedPage, policyInfoSubmittedPage: PolicyInfoSubmittedPage,
policyVehiclesPage: PolicyVehiclesPage, policyVehiclesPage: PolicyVehiclesPage,
heritageProblemGlassQuestionsPage: HeritageProblemGlassQuestionsPage, heritageProblemGlassQuestionsPage: HeritageProblemGlassQuestionsPage,
@ -92,6 +94,7 @@ export const createTestPages: TestPagesFactory<ITestPages> = (page: Page) => {
orderConfirmationPage: new OrderConfirmationPage(page), orderConfirmationPage: new OrderConfirmationPage(page),
partQuestionsPage: new PartQuestionsPage(page), partQuestionsPage: new PartQuestionsPage(page),
paymentMethodPage: new PaymentMethodPage(page), paymentMethodPage: new PaymentMethodPage(page),
policyInfoPage: new PolicyInfoPage(page),
policyInfoSubmittedPage: new PolicyInfoSubmittedPage(page), policyInfoSubmittedPage: new PolicyInfoSubmittedPage(page),
policyVehiclesPage: new PolicyVehiclesPage(page), policyVehiclesPage: new PolicyVehiclesPage(page),
heritageProblemGlassQuestionsPage: new HeritageProblemGlassQuestionsPage(page), heritageProblemGlassQuestionsPage: new HeritageProblemGlassQuestionsPage(page),

View file

@ -20,7 +20,7 @@ export class BailoutSuccessPage extends BasePage {
await expect(this.thankYouHeading).toBeVisible(); await expect(this.thankYouHeading).toBeVisible();
await expect(this.bodyText).toBeVisible(); await expect(this.bodyText).toBeVisible();
await expect(this.bodyText).toHaveText( await expect(this.bodyText).toHaveText(
/We've got it from here! One of our experts will be in touch to schedule your appointment\. If you have any questions, please contact 1-888-238-4527/ /We've got it from here! One of our experts will be in touch to schedule your appointment\. If you have any questions, please contact 888-308-4948./
); );
await expect(this.returnToHomepageButton).toBeVisible(); await expect(this.returnToHomepageButton).toBeVisible();
} }

View file

@ -0,0 +1,20 @@
import { expect, type Page } from '@playwright/test';
import { BasePage } from './BasePage';
import { step } from 'framework/localTypes/Step';
import { TestSuccessAlert } from 'safelite-playwright-core';
import { ITestData } from 'framework/TestData';
export class PolicyInfoPage extends BasePage {
constructor(page: Page) {
super(page);
// TODO: Add locators for the page once the card is in test (CASH-2468)
}
@step("PolicyInfoPage >> Continue")
async handleNewPolicyInfoPage(testData: Partial<ITestData>): Promise<void> {
await expect(this.page).toHaveURL(/\/fmg\/policy-info/);
throw new TestSuccessAlert('Policy info page validated successfully.');
}
}

View file

@ -3,7 +3,7 @@ import { BasePage } from './BasePage';
import { IAppointmentDetails, ServiceLocation } from 'safelite-playwright-core'; import { IAppointmentDetails, ServiceLocation } from 'safelite-playwright-core';
import { formatDate, formatTime } from 'safelite-playwright-core'; import { formatDate, formatTime } from 'safelite-playwright-core';
import { AppointmentTimeslot } from 'safelite-playwright-core'; import { AppointmentTimeslot } from 'safelite-playwright-core';
import { ProgressBarPercentages } from 'framework/localTypes/Enums'; import { ProgressBarPercentages, PaymentMethod } from 'framework/localTypes/Enums';
import { time } from 'console'; import { time } from 'console';
import { step } from 'framework/localTypes/Step'; import { step } from 'framework/localTypes/Step';
import { ITestData } from 'framework/TestData'; import { ITestData } from 'framework/TestData';
@ -261,11 +261,11 @@ export class SchedulePage extends BasePage {
await this.waitForPageOrComponentload(); await this.waitForPageOrComponentload();
await this.validateProgressBar(ProgressBarPercentages.SchedulePage); await this.validateProgressBar(ProgressBarPercentages.SchedulePage);
//Looks like an issue with the insurance flow - remove comments once fixed //Labor2 fee will show for cash orders and some insurance orders but USAA will not show it
/*if (testData.isHeavyTruck) { if (testData.isHeavyTruck && (testData.paymentMethod !== PaymentMethod.Insurance || testData.claimDetails?.client !== 'USAA')) {
const vuexState = JSON.parse(await this.page.evaluate('localStorage.getItem(\'vuex\')')); const vuexState = JSON.parse(await this.page.evaluate('localStorage.getItem(\'vuex\')'));
expect(vuexState.order.lineItems.supportingItems.find((item: any) => item && item.partNumber == "labor2")).toBeTruthy(); expect(vuexState.order.lineItems.supportingItems[0].partNumber).toBe("LABOR2");
}*/ }
if (handleMobileFirstModal) { if (handleMobileFirstModal) {
return await this.handleMobileFirstPopUp(testData); return await this.handleMobileFirstPopUp(testData);

View file

@ -41,12 +41,15 @@ import { getTestObject, TestCase, prepareTest, RuleEngine, TestInfo } from 'fram
import { createTestPages } from "framework/TestPages"; import { createTestPages } from "framework/TestPages";
import cashReplaceSwitchToInsuranceProgressiveNoCompTests from "./CashReplaceSwitchToInsuranceProgressiveNoComp"; import cashReplaceSwitchToInsuranceProgressiveNoCompTests from "./CashReplaceSwitchToInsuranceProgressiveNoComp";
import insuranceUSAABigTruckVerifiedTests from "./InsuranceUSAABigTruckVerified"; import insuranceUSAABigTruckVerifiedTests from "./InsuranceUSAABigTruckVerified";
import insuranceAllstateBigTruckUnverifiedTests from "./InsuranceAllstateBigTruckUnverified";
import partsNotFoundBailoutTests from "./PartsNotFoundBailout"; import partsNotFoundBailoutTests from "./PartsNotFoundBailout";
import insuranceUnverifiedTests from "./InsuranceUnverified"; import insuranceUnverifiedTests from "./InsuranceUnverified";
import CashReplaceSplitWindshieldTests from "./CashReplaceSplitWindshield"; import CashReplaceSplitWindshieldTests from "./CashReplaceSplitWindshield";
import insuranceMeemicNearSchoolVerifiedTests from "./InsuranceMeemicNearSchoolVerified"; import insuranceMeemicNearSchoolVerifiedTests from "./InsuranceMeemicNearSchoolVerified";
import insuranceLibertyMutualTests from "./InsuranceLibertyMutual";
import cashReplaceDualMobileMSRTests from "./CashReplaceDualMobileMSR"; import cashReplaceDualMobileMSRTests from "./CashReplaceDualMobileMSR";
import cashReplaceDualNonMSRInshopTests from "./CashReplaceDualNonMSRInshop"; import cashReplaceDualNonMSRInshopTests from "./CashReplaceDualNonMSRInshop";
import cashBigTruckTests from "./CashBigTruck";
const test = getTestObject(); const test = getTestObject();
@ -84,6 +87,7 @@ const allStandardTests = [
{ name: "CashReplaceVinMobile", tests: cashReplaceVinMobileTests }, { name: "CashReplaceVinMobile", tests: cashReplaceVinMobileTests },
{ name: "CashReplaceWiperDropoff", tests: cashReplaceWiperDropoffTests }, { name: "CashReplaceWiperDropoff", tests: cashReplaceWiperDropoffTests },
{ name: "CashReplaceStaticInshop", tests: cashReplaceStaticInshopTests }, { name: "CashReplaceStaticInshop", tests: cashReplaceStaticInshopTests },
{ name: "CashBigTruck", tests: cashBigTruckTests },
{ name: "CashReplaceDualMobileMSR", tests: cashReplaceDualMobileMSRTests }, { name: "CashReplaceDualMobileMSR", tests: cashReplaceDualMobileMSRTests },
{ name: "CashReplaceDualNonMSRInshop", tests: cashReplaceDualNonMSRInshopTests }, { name: "CashReplaceDualNonMSRInshop", tests: cashReplaceDualNonMSRInshopTests },
{ name: "CashReplaceStaticMobileMSR", tests: cashReplaceStaticMobileMSRTests }, { name: "CashReplaceStaticMobileMSR", tests: cashReplaceStaticMobileMSRTests },
@ -97,12 +101,15 @@ const allStandardTests = [
{ name: "InsuranceITAC21stCentury", tests: insuranceITAC21stCenturyTests }, { name: "InsuranceITAC21stCentury", tests: insuranceITAC21stCenturyTests },
{ name: "InsuranceNoCompProgressive", tests: insuranceNoCompProgressiveTests }, { name: "InsuranceNoCompProgressive", tests: insuranceNoCompProgressiveTests },
{name: "InsuranceUSAABigTruckVerified", tests: insuranceUSAABigTruckVerifiedTests}, {name: "InsuranceUSAABigTruckVerified", tests: insuranceUSAABigTruckVerifiedTests},
{ name: "InsuranceAllstateBigTruckUnverified", tests: insuranceAllstateBigTruckUnverifiedTests },
{ name: "InsuranceOEMAllstate", tests: insuranceOEMAllstateTests }, { name: "InsuranceOEMAllstate", tests: insuranceOEMAllstateTests },
{ name: "InsuranceUnverified", tests: insuranceUnverifiedTests }, { name: "InsuranceUnverified", tests: insuranceUnverifiedTests },
// {name: "InsuranceGeico", tests: insuranceGeicoTests}, // {name: "InsuranceGeico", tests: insuranceGeicoTests},
// {name: "InsuranceITACOptimizedPriceValidationAllState", tests: insuranceITACOptimizedPriceValidationAllStateTests} // {name: "InsuranceITACOptimizedPriceValidationAllState", tests: insuranceITACOptimizedPriceValidationAllStateTests}
{ name: "InsuranceMeemicNearSchoolVerified", tests: insuranceMeemicNearSchoolVerifiedTests }, { name: "InsuranceMeemicNearSchoolVerified", tests: insuranceMeemicNearSchoolVerifiedTests },
// { name: "PartsNotFoundBailout", tests: partsNotFoundBailoutTests }, // code is not available in QA //{ name: "InsuranceLibertyMutual", tests: insuranceLibertyMutualTests }, // uncomment when new C&C pages are built out for this entire flow
{ name: "PartsNotFoundBailout", tests: partsNotFoundBailoutTests }
]; ];
// Alert validation scenarios // Alert validation scenarios
@ -357,7 +364,7 @@ async function runWorkflow(page: Page, testCase: TestCase) {
} }
async function handleInsuranceFlow(testCase: TestCase) { async function handleInsuranceFlow(testCase: TestCase) {
const { isPolicyFound, isPolicyDriver, endorsements, isRecalVehicle, isCashInsuranceFlow, isForcedOEM, flow } = testCase.testData; const { isPolicyFound, isPolicyDriver, endorsements, isRecalVehicle, isCashInsuranceFlow, isForcedOEM, flow, isNewInsuranceFlow } = testCase.testData;
// Check if the insurance policy has endorsements // Check if the insurance policy has endorsements
const hasEndorsements = endorsements && endorsements.length > 0; const hasEndorsements = endorsements && endorsements.length > 0;
@ -373,7 +380,12 @@ async function handleInsuranceFlow(testCase: TestCase) {
await HeritageProblemGlassQuestionsPage.handleHeritageProblemGlassQuestionsPage(testCase.testData); await HeritageProblemGlassQuestionsPage.handleHeritageProblemGlassQuestionsPage(testCase.testData);
} }
// Handle ccPolicyInfoPage if (isNewInsuranceFlow) {
let policyInfoPage = testCase.pages.policyInfoPage;
await policyInfoPage.handleNewPolicyInfoPage(testCase.testData);
} else {
// Handle ccPolicyInfoPage
let ccPolicyInfoPage = testCase.pages.ccPolicyInfoPage; let ccPolicyInfoPage = testCase.pages.ccPolicyInfoPage;
await ccPolicyInfoPage.handleCCPolicyInfoPage(testCase.testData); await ccPolicyInfoPage.handleCCPolicyInfoPage(testCase.testData);
@ -417,6 +429,8 @@ async function handleInsuranceFlow(testCase: TestCase) {
let coverageStatementPage = testCase.pages.coverageStatementPage; let coverageStatementPage = testCase.pages.coverageStatementPage;
await coverageStatementPage.handleCoverageStatementPage(testCase.testData); await coverageStatementPage.handleCoverageStatementPage(testCase.testData);
}
// Handle scenario where user selected Cash to Insurance and needs to go back through the flow // Handle scenario where user selected Cash to Insurance and needs to go back through the flow
// right now we are choosing pay at appointment as payment method for this scenario // right now we are choosing pay at appointment as payment method for this scenario
if (isCashInsuranceFlow) { if (isCashInsuranceFlow) {

View file

@ -0,0 +1,64 @@
import { ITestData, getDefaultExperimentsData } from 'framework/TestData'
import { ServiceLocation, PaymentType } from 'safelite-playwright-core';
import { ITestCase } from '../framework/Typedefs'
import { PaymentMethod } from "framework/localTypes/Enums";
import { VehicleLookupType } from 'safelite-playwright-core';
import { getDefaultTestData, setFakerSeedFromTestName } from 'safelite-playwright-core';
setFakerSeedFromTestName("CashBigTruckInshop");
const cashBigTruckData: Partial<ITestData> = {
...getDefaultTestData(),
paymentMethod: PaymentMethod.SelfPay,
isHeavyTruck: true,
isCanNotRecal: true,
customerDetails: {
...getDefaultTestData().customerDetails!,
firstName: 'Big',
lastName: 'Truck',
address: {
...getDefaultTestData().customerDetails!.address,
city: 'Urbancrest',
state: 'Ohio',
postalCode: '43123'
}
},
vehicleDetails: {
...getDefaultTestData().vehicleDetails!,
year: '2025',
make: 'Peterbilt',
model: '579',
style: 'conventional cab',
vin: '1XPBDP9X6SD693446',
vehicleLookupType: VehicleLookupType.Vin,
},
appointmentDetails: {
serviceLocation: ServiceLocation.InShop,
shopAddress: '3455 Centerpoint Dr, Urbancrest, OH 43123',
appointmentDate: getDefaultTestData().appointmentDetails?.appointmentDate
},
paymentDetails: {
paymentType: PaymentType.PayAtService
},
experiments: {
...getDefaultExperimentsData()
}
}
const cashBigTruckTests: ITestCase[] = [];
const tc = {
name: `CashBigTruck`,
tags: ['@E2E', '@CashBigTruck', '@test_report', '@CASH'],
testData: cashBigTruckData
};
cashBigTruckTests.push(tc);
export default cashBigTruckTests;

View file

@ -0,0 +1,74 @@
import { ITestData, getDefaultExperimentsData } from 'framework/TestData'
import { ServiceLocation, DamageType } from 'safelite-playwright-core';
import { ITestCase } from '../framework/Typedefs'
import { PaymentMethod } from "framework/localTypes/Enums";
import { VehicleLookupType } from 'safelite-playwright-core';
import { getDefaultTestData, setFakerSeedFromTestName } from 'safelite-playwright-core';
setFakerSeedFromTestName("InsuranceAllstateBigTruckUnverified");
const insuranceAllstateBigTruckUnverifiedData: Partial<ITestData> = {
...getDefaultTestData(),
paymentMethod: PaymentMethod.Insurance,
isPolicyFound: false,
isPolicyUnverified: true,
isHeavyTruck: true,
isCanNotRecal: true,
customerDetails: {
...getDefaultTestData().customerDetails!,
firstName: 'Big',
lastName: 'Truck',
address: {
...getDefaultTestData().customerDetails!.address,
city: 'Dayton',
state: 'Ohio',
postalCode: '45424'
}
},
claimDetails: {
client: 'Allstate',
policyNumber: 'MockUnverifiedBigTruck',
policyDeductible: "Unverified",
policyZip: '45424',
damageDate: new Date(new Date().setDate(new Date().getDate() - 1))
.toLocaleDateString('en-US', { month: '2-digit', day: '2-digit', year: 'numeric' }),
damageCause: DamageType.Rock
},
vehicleDetails: {
...getDefaultTestData().vehicleDetails!,
year: '2025',
make: 'Peterbilt',
model: '579',
style: 'conventional cab',
vin: '1XPBDP9X6SD693446',
vehicleLookupType: VehicleLookupType.Zip,
},
appointmentDetails: {
serviceLocation: ServiceLocation.InShop,
shopAddress: '5719 Brandt Pike, Dayton, OH 45424',
appointmentDate: getDefaultTestData().appointmentDetails?.appointmentDate
},
paymentDetails: {},
experiments: {
...getDefaultExperimentsData()
}
}
const insuranceAllstateBigTruckUnverifiedTests: ITestCase[] = [];
const tc = {
name: `InsuranceAllstateBigTruckUnverified`,
tags: ['@E2E', '@InsuranceAllstateBigTruckUnverified', '@test_report', '@Insurance'],
testData: insuranceAllstateBigTruckUnverifiedData
};
insuranceAllstateBigTruckUnverifiedTests.push(tc);
export default insuranceAllstateBigTruckUnverifiedTests;

View file

@ -0,0 +1,54 @@
import { ITestData, getDefaultExperimentsData } from 'framework/TestData'
import { DamageType, VehicleLookupType, VehicleDamage } from 'safelite-playwright-core';
import { PaymentMethod } from "framework/localTypes/Enums";
import { ITestCase } from '../framework/Typedefs'
import { getDefaultTestData, setFakerSeedFromTestName } from 'safelite-playwright-core';
setFakerSeedFromTestName("InsuranceLibertyMutual");
const insuranceLibertyMutualData: Partial<ITestData> = {
...getDefaultTestData(),
paymentMethod: PaymentMethod.Insurance,
isNewInsuranceFlow: true,
claimDetails: {
client: 'Liberty Mutual Insurance',
policyNumber: 'Mock123456LM',
policyDeductible: 0,
damageDate: new Date(new Date().setDate(new Date().getDate() - 1)).toLocaleDateString('en-US', { month: '2-digit', day: '2-digit', year: 'numeric' }),
damageCause: DamageType.Hail
},
// Vehicle details for truck with ZIP lookup
vehicleDetails: {
...getDefaultTestData().vehicleDetails!,
year: '2015',
make: 'Toyota',
model: 'Tacoma Pickup',
style: '4 door crew cab',
vehicleLookupType: VehicleLookupType.Zip,
},
// Driver door damage instead of windshield
vehicleDamage: [
VehicleDamage.DriverFrontDoor,
],
paymentDetails: {},
experiments: {
...getDefaultExperimentsData()
}
};
const insuranceLibertyMutualTests: ITestCase[] = [];
const tc = {
name: `InsuranceLibertyMutual`,
tags: ['@E2E', '@InsuranceLibertyMutual', '@test_report', '@Insurance'],
testData: insuranceLibertyMutualData
};
insuranceLibertyMutualTests.push(tc);
export default insuranceLibertyMutualTests;

View file

@ -32,7 +32,7 @@ const errorMessages = {
DATE_REQUIRED: "Please select a date", DATE_REQUIRED: "Please select a date",
PHONE_REQUIRED: "Please enter your phone number", PHONE_REQUIRED: "Please enter your phone number",
PHONE_FORMAT: "Phone number must be 10 digits", PHONE_FORMAT: "Phone number must be 10 digits",
SMS_CONSENT_REQUIRED: "Please select at least one consent option", SMS_CONSENT_REQUIRED: "Please select checkbox to receive text messages",
YEAR_REQUIRED: "Please select your vehicle year", YEAR_REQUIRED: "Please select your vehicle year",
MAKE_REQUIRED: "Please select your vehicle make", MAKE_REQUIRED: "Please select your vehicle make",
MODEL_REQUIRED: "Please select your vehicle model", MODEL_REQUIRED: "Please select your vehicle model",

View file

@ -244,10 +244,8 @@ describe("Question Chain component", () => {
test("should return answer object if returnedAnswer is a final matching answer", async () => { test("should return answer object if returnedAnswer is a final matching answer", async () => {
//Arrange //Arrange
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({
const testReturnedAnswer = "1|answer|DB10840|No"; questionDataProp: [
await wrapper.setData({
questions: [
{ {
questionSequence: 1, questionSequence: 1,
questionText: "Question here?", questionText: "Question here?",
@ -256,17 +254,21 @@ describe("Question Chain component", () => {
answerResult: "DB09410", answerResult: "DB09410",
answerText: "Yes", answerText: "Yes",
nextQuestionSequence: null, nextQuestionSequence: null,
problemQuestionId: 9529,
}, },
{ {
answerResult: "DB10840", answerResult: "DB10840",
answerText: "No", answerText: "No",
nextQuestionSequence: null, nextQuestionSequence: null,
problemQuestionId: 9531,
}, },
], ],
}, },
], ],
}); });
const testReturnedAnswer = "1|answer|DB10840|No";
await wrapper.setProps({ index: 0 }); await wrapper.setProps({ index: 0 });
await nextTick();
//Act //Act
const result = wrapper.vm.getQuestionChainAnswerIfComplete(testReturnedAnswer); const result = wrapper.vm.getQuestionChainAnswerIfComplete(testReturnedAnswer);
@ -274,16 +276,52 @@ describe("Question Chain component", () => {
//Assert //Assert
expect(result).toMatchObject({ expect(result).toMatchObject({
answerResult: "DB10840", answerResult: "DB10840",
answeredQuestions: [ problemQuestionId: 9531,
{ questionNum: 1, questionText: "Question here?", selectedAnswerText: "No" },
{
questionNum: 1,
questionText: "Does only your center sliding piece need to be replaced?",
selectedAnswerText: "No",
},
],
index: 0, index: 0,
}); });
expect(result.answeredQuestions).toEqual([
{
questionText: "Question here?",
selectedAnswer: "1|answer|DB10840|No",
selectedAnswerText: "No",
questionNum: 1,
problemQuestionId: 9531,
},
]);
});
test("should include problemQuestionId from parts-or-questions answer on created", async () => {
//Arrange
const { wrapper } = setupMocks({
questionDataProp: [
{
questionSequence: 1,
questionText:
"Does the rubber seal around your windshield have a chrome strip running through it?",
answers: [
{
answerResult: "WCR 848",
answerText: "Yes",
nextQuestionSequence: null,
problemQuestionId: 9531,
},
{
answerResult: "WCR 848",
answerText: "No",
nextQuestionSequence: null,
problemQuestionId: 9529,
},
],
},
],
});
//Act
await nextTick();
//Assert
expect(wrapper.vm.questions[0].answers[0].problemQuestionId).toBe(9531);
expect(wrapper.vm.questions[0].answers[1].problemQuestionId).toBe(9529);
}); });
}); });
}); });

View file

@ -60,6 +60,7 @@ export default {
: q.questionSequence + "|answer|" + a.answerResult + "|" + a.answerText, : q.questionSequence + "|answer|" + a.answerResult + "|" + a.answerText,
nextQuestionSequence: a.nextQuestionSequence, nextQuestionSequence: a.nextQuestionSequence,
answerResult: a.answerResult, answerResult: a.answerResult,
problemQuestionId: a.problemQuestionId,
questionSequence: q.questionSequence, questionSequence: q.questionSequence,
questionType: a.nextQuestionSequence ? "nextQuestion" : "answer", questionType: a.nextQuestionSequence ? "nextQuestion" : "answer",
}; };
@ -81,6 +82,17 @@ export default {
} }
}, },
methods: { methods: {
getProblemQuestionIdFromSelectedAnswer(question, selectedAnswer) {
if (!question?.answers || !selectedAnswer) {
return null;
}
const matchedAnswer = question.answers.find(
(answer) => answer.value === selectedAnswer
);
return matchedAnswer?.problemQuestionId ?? null;
},
handleAnswer(question, returnedAnswer) { handleAnswer(question, returnedAnswer) {
/* /*
returnedAnswer example format: returnedAnswer example format:
@ -126,6 +138,10 @@ export default {
selectedAnswer: q.answerSelected, selectedAnswer: q.answerSelected,
selectedAnswerText: q.answerSelected.split("|")[3], selectedAnswerText: q.answerSelected.split("|")[3],
questionNum: q.questionSequence, questionNum: q.questionSequence,
problemQuestionId: this.getProblemQuestionIdFromSelectedAnswer(
q,
q.answerSelected
),
}); });
} }
}); });
@ -144,10 +160,17 @@ export default {
} else { } else {
// reset current question index (removes .current-question class) // reset current question index (removes .current-question class)
this.currentQuestionNum = 0; // reset count this.currentQuestionNum = 0; // reset count
const answeredQuestion = this.questions.find(
(q) => q.questionSequence === questionNum
);
// return an object with the part answer, all the answered questions, and the part index // return an object with the part answer, all the answered questions, and the part index
return { return {
answerResult: questionAnswer, answerResult: questionAnswer,
problemQuestionId: this.getProblemQuestionIdFromSelectedAnswer(
answeredQuestion,
returnedAnswer
),
answeredQuestions: answeredQuestions, answeredQuestions: answeredQuestions,
index: this.index, index: this.index,
}; };

View file

@ -5,8 +5,24 @@ import cart from "@/fmg-components/cart/cart.vue";
import { shallowMount } from "@vue/test-utils"; import { shallowMount } from "@vue/test-utils";
import { getMountOptions } from "@/helpers/unit-test-helper.js"; import { getMountOptions } from "@/helpers/unit-test-helper.js";
import { packageNames } from "@/constants/package-names";
import store from "@/store"; import store from "@/store";
jest.mock("@/mixins/experiment-mixin.js", () => ({
methods: {
getSettingValue(settingName) {
return false;
},
hasSetting(settingName) {
return false;
},
hasSettingEqualTo(settingName, settingValue) {
return false;
},
},
}));
global.$logger = { global.$logger = {
logInformation: jest.fn(), logInformation: jest.fn(),
logWarning: jest.fn(), logWarning: jest.fn(),
@ -354,9 +370,73 @@ describe("cart.vue", () => {
expect(found).toBe(true); expect(found).toBe(true);
}); });
}); });
describe("servicePackageTitle", () => {
const servicePackageCmsContent = {
ServicePackageTitle: {
Answers: [
{
Name: packageNames.TIER_ONE,
SubWidgetName: "EconomyServiceTitle",
},
{
Name: packageNames.TIER_TWO,
SubWidgetName: "StandardServiceTitle",
},
{
Name: packageNames.TIER_THREE,
SubWidgetName: "PremiumServiceTitle",
},
],
},
EconomyServiceTitle: {
Text: "Glass service only",
},
};
const defaultServicePackageProps = {
servicePackageOptionsCmsName: "ServicePackageTitle",
damage: {
glassToReplace: [{ glassLocation: "Windshield" }],
isRepair: false,
},
modelValue: {
glassParts: [],
supportingItems: [],
vaps: [],
promos: [],
},
availableVaps: [],
};
test("uses CMS widget name when experiment is not active", () => {
const { wrapper } = setupMocks({
props: defaultServicePackageProps,
cmsContent: servicePackageCmsContent,
});
expect(wrapper.vm.servicePackageTitleWidget).toBe("EconomyServiceTitle");
expect(wrapper.vm.servicePackageTitleText).toBeNull();
});
test("uses experiment display name when ServicePackageNameTest is active", () => {
const getExperimentPackageLabelSpy = jest
.spyOn(cart.methods, "getExperimentPackageLabel")
.mockReturnValue("Essential");
const { wrapper } = setupMocks({
props: defaultServicePackageProps,
cmsContent: servicePackageCmsContent,
});
expect(wrapper.vm.servicePackageTitleText).toBe("Essential");
getExperimentPackageLabelSpy.mockRestore();
});
});
}); });
function setupMocks({ options, props }) { function setupMocks({ options, props, cmsContent }) {
const mountOptions = getMountOptions({ const mountOptions = getMountOptions({
...options, ...options,
}); });
@ -365,7 +445,9 @@ function setupMocks({ options, props }) {
methods: { methods: {
getTierOnePackagePrice: jest.fn(), getTierOnePackagePrice: jest.fn(),
filterOutFees: jest.fn(), filterOutFees: jest.fn(),
getCmsContent: jest.fn(), getCmsContent: jest.fn((widgetName, cmsFieldName) => {
return cmsContent?.[widgetName]?.[cmsFieldName];
}),
}, },
}; };

View file

@ -34,7 +34,9 @@
<!-- Service Type --> <!-- Service Type -->
<div class="service-type"> <div class="service-type">
<textBlock :cmsWidgetName="servicePackageTitleWidget" /> <textBlock
:cmsWidgetName="servicePackageTitleWidget"
:customText="servicePackageTitleText" />
<span> <span>
{{ getLineItemAmount(packagePrice) }} {{ getLineItemAmount(packagePrice) }}
</span> </span>
@ -193,6 +195,7 @@ import { formatToUSDollar } from "@/helpers/cms-content-helper";
// Constants // Constants
import { partTypeStrings } from "@/constants/part-type-strings"; import { partTypeStrings } from "@/constants/part-type-strings";
import { packageNames } from "@/constants/package-names";
import { cartItemCategories } from "@/constants/cart-item-categories"; import { cartItemCategories } from "@/constants/cart-item-categories";
import { cartItemTypes } from "@/constants/cart-item-types"; import { cartItemTypes } from "@/constants/cart-item-types";
import { coverageStatus, cartItemTypesCoveredByInsurance } from "@/constants/insurance"; import { coverageStatus, cartItemTypesCoveredByInsurance } from "@/constants/insurance";
@ -267,6 +270,24 @@ export default {
return subTotal; return subTotal;
}, },
getExperimentPackageLabel(tierName) {
if (
!experimentMixin.methods.hasSettingEqualTo(
experimentSettings.SHOW_SERVICE_PACKAGE_NAME_TEST,
"true"
)
) {
return null;
}
const tierSettingKey = {
[packageNames.TIER_ONE]: experimentSettings.TIER_ONE,
[packageNames.TIER_TWO]: experimentSettings.TIER_TWO,
[packageNames.TIER_THREE]: experimentSettings.TIER_THREE,
}[tierName];
return tierSettingKey ? experimentMixin.methods.getSettingValue(tierSettingKey) : null;
},
getQuotePageDiscount() { getQuotePageDiscount() {
const quotePageDiscountLineItems = this.quotePageDiscountCartItem; const quotePageDiscountLineItems = this.quotePageDiscountCartItem;
let subTotal = 0; let subTotal = 0;
@ -547,6 +568,9 @@ export default {
return currentPackage.SubWidgetName; return currentPackage.SubWidgetName;
}, },
servicePackageTitleText() {
return this.getExperimentPackageLabel(this.packageLevel);
},
packageLevel() { packageLevel() {
const tier = getHighestFullySatisfiedTier( const tier = getHighestFullySatisfiedTier(
this.glassToReplace, this.glassToReplace,

View file

@ -45,14 +45,14 @@ describe("save-progress-popup-question ", () => {
}); });
describe("contact method tabs", () => { describe("contact method tabs", () => {
test("should default to the phone tab", () => { test("should default to the email tab", () => {
const { wrapper } = setupMocks({ const { wrapper } = setupMocks({
props: { props: {
modalWidgetName: "SaveProgressPopupWidget", modalWidgetName: "SaveProgressPopupWidget",
}, },
}); });
expect(wrapper.vm.isPhoneTabSelected).toBe(true); expect(wrapper.vm.isPhoneTabSelected).toBe(false);
}); });
test("should use tab labels from phone and email specific CMS widgets", () => { test("should use tab labels from phone and email specific CMS widgets", () => {
@ -83,21 +83,24 @@ describe("save-progress-popup-question ", () => {
}, },
}); });
expect(wrapper.vm.modalDisclaimerText).toBe("Phone disclaimer"); expect(wrapper.vm.modalDisclaimerText).toBe("Email disclaimer");
wrapper.vm.selectContactMethod("EmailAnswer"); wrapper.vm.selectContactMethod("PhoneAnswer");
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
expect(wrapper.vm.modalDisclaimerText).toBe("Email disclaimer"); expect(wrapper.vm.modalDisclaimerText).toBe("Phone disclaimer");
}); });
test("should use phone question CMS widget on the phone tab", () => { test("should use phone question CMS widget on the phone tab", async () => {
const { wrapper } = setupMocks({ const { wrapper } = setupMocks({
props: { props: {
modalWidgetName: "SaveProgressPopupWidget", modalWidgetName: "SaveProgressPopupWidget",
}, },
}); });
wrapper.vm.selectContactMethod("PhoneAnswer");
await wrapper.vm.$nextTick();
expect(wrapper.vm.phoneQuestionWidgetName).toBe("SaveProgressPopupPhoneQuestionWidget"); expect(wrapper.vm.phoneQuestionWidgetName).toBe("SaveProgressPopupPhoneQuestionWidget");
}); });
@ -108,15 +111,26 @@ describe("save-progress-popup-question ", () => {
}, },
}); });
wrapper.vm.selectContactMethod("EmailAnswer");
await wrapper.vm.$nextTick();
expect(wrapper.vm.contactMethod).toBe("EmailAnswer"); expect(wrapper.vm.contactMethod).toBe("EmailAnswer");
expect(wrapper.vm.isPhoneTabSelected).toBe(false); expect(wrapper.vm.isPhoneTabSelected).toBe(false);
expect(wrapper.vm.userInput).toBe("");
expect(wrapper.vm.emailQuestionWidgetName).toBe("SaveProgressPopupEmailQuestionWidget"); expect(wrapper.vm.emailQuestionWidgetName).toBe("SaveProgressPopupEmailQuestionWidget");
}); });
test("should show phone content when the phone tab is selected", async () => {
const { wrapper } = setupMocks({
props: {
modalWidgetName: "SaveProgressPopupWidget",
},
});
wrapper.vm.selectContactMethod("PhoneAnswer");
await wrapper.vm.$nextTick();
expect(wrapper.vm.contactMethod).toBe("PhoneAnswer");
expect(wrapper.vm.isPhoneTabSelected).toBe(true);
expect(wrapper.vm.userInput).toBe("");
});
test("should reset sms consent when switching tabs", async () => { test("should reset sms consent when switching tabs", async () => {
const { wrapper } = setupMocks({ const { wrapper } = setupMocks({
props: { props: {
@ -126,7 +140,7 @@ describe("save-progress-popup-question ", () => {
wrapper.vm.smsConsent = { transactional: true, marketing: true }; wrapper.vm.smsConsent = { transactional: true, marketing: true };
wrapper.vm.showConsentErrors = true; wrapper.vm.showConsentErrors = true;
wrapper.vm.selectContactMethod("EmailAnswer"); wrapper.vm.selectContactMethod("PhoneAnswer");
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
expect(wrapper.vm.smsConsent).toEqual({ transactional: false, marketing: false }); expect(wrapper.vm.smsConsent).toEqual({ transactional: false, marketing: false });
@ -142,6 +156,7 @@ describe("save-progress-popup-question ", () => {
}, },
}); });
wrapper.vm.selectContactMethod("PhoneAnswer");
wrapper.vm.modal.validate = jest.fn().mockResolvedValue({ valid: true }); wrapper.vm.modal.validate = jest.fn().mockResolvedValue({ valid: true });
const resetSpy = jest.spyOn(wrapper.vm, "resetPhoneSendButtonStyle"); const resetSpy = jest.spyOn(wrapper.vm, "resetPhoneSendButtonStyle");
@ -158,6 +173,7 @@ describe("save-progress-popup-question ", () => {
}, },
}); });
wrapper.vm.selectContactMethod("PhoneAnswer");
wrapper.vm.modal.validate = jest.fn().mockResolvedValue({ valid: false }); wrapper.vm.modal.validate = jest.fn().mockResolvedValue({ valid: false });
const resetSpy = jest.spyOn(wrapper.vm, "resetPhoneSendButtonStyle"); const resetSpy = jest.spyOn(wrapper.vm, "resetPhoneSendButtonStyle");
@ -179,6 +195,7 @@ describe("save-progress-popup-question ", () => {
await flushPromises(); await flushPromises();
wrapper.vm.dispatchStoreAction = dispatchStoreAction; wrapper.vm.dispatchStoreAction = dispatchStoreAction;
wrapper.vm.selectContactMethod("PhoneAnswer");
wrapper.vm.userInput = "555-123-4567"; wrapper.vm.userInput = "555-123-4567";
wrapper.vm.smsConsent = { transactional: true, marketing: false }; wrapper.vm.smsConsent = { transactional: true, marketing: false };
wrapper.vm.modal.validate = jest.fn().mockResolvedValue({ valid: true }); wrapper.vm.modal.validate = jest.fn().mockResolvedValue({ valid: true });
@ -203,6 +220,7 @@ describe("save-progress-popup-question ", () => {
}); });
wrapper.vm.dispatchStoreAction = dispatchStoreAction; wrapper.vm.dispatchStoreAction = dispatchStoreAction;
wrapper.vm.selectContactMethod("PhoneAnswer");
wrapper.vm.userInput = "555-123-4567"; wrapper.vm.userInput = "555-123-4567";
wrapper.vm.smsConsent = { transactional: true, marketing: false }; wrapper.vm.smsConsent = { transactional: true, marketing: false };
@ -238,7 +256,6 @@ describe("save-progress-popup-question ", () => {
}); });
wrapper.vm.dispatchStoreAction = dispatchStoreAction; wrapper.vm.dispatchStoreAction = dispatchStoreAction;
wrapper.vm.selectContactMethod("EmailAnswer");
wrapper.vm.userInput = "test@example.com"; wrapper.vm.userInput = "test@example.com";
await wrapper.vm.saveProgress(); await wrapper.vm.saveProgress();
@ -267,6 +284,7 @@ describe("save-progress-popup-question ", () => {
}); });
wrapper.vm.dispatchStoreAction = dispatchStoreAction; wrapper.vm.dispatchStoreAction = dispatchStoreAction;
wrapper.vm.selectContactMethod("PhoneAnswer");
wrapper.vm.userInput = "555-123-4567"; wrapper.vm.userInput = "555-123-4567";
wrapper.vm.smsConsent = { transactional: true, marketing: false }; wrapper.vm.smsConsent = { transactional: true, marketing: false };
@ -289,7 +307,6 @@ describe("save-progress-popup-question ", () => {
}); });
wrapper.vm.dispatchStoreAction = dispatchStoreAction; wrapper.vm.dispatchStoreAction = dispatchStoreAction;
wrapper.vm.selectContactMethod("EmailAnswer");
wrapper.vm.userInput = "test@example.com"; wrapper.vm.userInput = "test@example.com";
await wrapper.vm.saveProgress(); await wrapper.vm.saveProgress();

View file

@ -118,7 +118,7 @@ export default {
data() { data() {
return { return {
userInput: "", userInput: "",
contactMethod: saveProgressPopupContactMethods.PHONE, contactMethod: saveProgressPopupContactMethods.EMAIL,
smsConsent: defaultSaveProgressSmsConsent(), smsConsent: defaultSaveProgressSmsConsent(),
smsConsentCopy: getSaveProgressSmsConsentFallbackCopy(), smsConsentCopy: getSaveProgressSmsConsentFallbackCopy(),
isProgressSaved: false, isProgressSaved: false,

View file

@ -7,15 +7,14 @@ import {
import { errorMessages } from "@/constants/error-messages"; import { errorMessages } from "@/constants/error-messages";
describe("save-progress-popup-sms-consent-question", () => { describe("save-progress-popup-sms-consent-question", () => {
it("should render two consent checkboxes with default copy", () => { it("should render the transactional consent checkbox with default copy", () => {
const wrapper = shallowMount(saveProgressPopupSmsConsentQuestion); const wrapper = shallowMount(saveProgressPopupSmsConsentQuestion);
const fallbackCopy = getSaveProgressSmsConsentFallbackCopy(); const fallbackCopy = getSaveProgressSmsConsentFallbackCopy();
const checkboxes = wrapper.findAllComponents({ name: "checkboxQuestion" }); const checkboxes = wrapper.findAllComponents({ name: "checkboxQuestion" });
expect(checkboxes).toHaveLength(2); expect(checkboxes).toHaveLength(1);
expect(checkboxes.at(0).props("labelText")).toBe(fallbackCopy.transactional); expect(checkboxes.at(0).props("labelText")).toBe(fallbackCopy.transactional);
expect(checkboxes.at(1).props("labelText")).toBe(fallbackCopy.marketing);
}); });
it("should emit updated consent when a checkbox changes", async () => { it("should emit updated consent when a checkbox changes", async () => {
@ -47,8 +46,8 @@ describe("save-progress-popup-sms-consent-question", () => {
const checkboxes = wrapper.findAllComponents({ name: "checkboxQuestion" }); const checkboxes = wrapper.findAllComponents({ name: "checkboxQuestion" });
expect(checkboxes).toHaveLength(1);
expect(checkboxes.at(0).props("labelText")).toBe("API transactional copy"); expect(checkboxes.at(0).props("labelText")).toBe("API transactional copy");
expect(checkboxes.at(1).props("labelText")).toBe("API marketing copy");
}); });
it("should disable consent checkboxes when isDisabled is true", () => { it("should disable consent checkboxes when isDisabled is true", () => {
@ -61,7 +60,6 @@ describe("save-progress-popup-sms-consent-question", () => {
const checkboxes = wrapper.findAllComponents({ name: "checkboxQuestion" }); const checkboxes = wrapper.findAllComponents({ name: "checkboxQuestion" });
expect(checkboxes.at(0).props("isDisabled")).toBe(true); expect(checkboxes.at(0).props("isDisabled")).toBe(true);
expect(checkboxes.at(1).props("isDisabled")).toBe(true);
expect(wrapper.find("fieldset").attributes("disabled")).toBe(""); expect(wrapper.find("fieldset").attributes("disabled")).toBe("");
}); });
@ -76,7 +74,6 @@ describe("save-progress-popup-sms-consent-question", () => {
const checkboxes = wrapper.findAllComponents({ name: "checkboxQuestion" }); const checkboxes = wrapper.findAllComponents({ name: "checkboxQuestion" });
expect(checkboxes.at(0).props("hasError")).toBe(false); expect(checkboxes.at(0).props("hasError")).toBe(false);
expect(checkboxes.at(1).props("hasError")).toBe(false);
expect(wrapper.text()).not.toContain(errorMessages.SMS_CONSENT_REQUIRED); expect(wrapper.text()).not.toContain(errorMessages.SMS_CONSENT_REQUIRED);
}); });
@ -94,7 +91,6 @@ describe("save-progress-popup-sms-consent-question", () => {
const checkboxes = wrapper.findAllComponents({ name: "checkboxQuestion" }); const checkboxes = wrapper.findAllComponents({ name: "checkboxQuestion" });
expect(checkboxes.at(0).props("hasError")).toBe(true); expect(checkboxes.at(0).props("hasError")).toBe(true);
expect(checkboxes.at(1).props("hasError")).toBe(true);
}); });
it("should clear consent errors when a selection is made while showConsentErrors is true", async () => { it("should clear consent errors when a selection is made while showConsentErrors is true", async () => {
@ -111,6 +107,5 @@ describe("save-progress-popup-sms-consent-question", () => {
const checkboxes = wrapper.findAllComponents({ name: "checkboxQuestion" }); const checkboxes = wrapper.findAllComponents({ name: "checkboxQuestion" });
expect(checkboxes.at(0).props("hasError")).toBe(false); expect(checkboxes.at(0).props("hasError")).toBe(false);
expect(checkboxes.at(1).props("hasError")).toBe(false);
}); });
}); });

View file

@ -9,12 +9,13 @@
:labelText="consentCopy.transactional" :labelText="consentCopy.transactional"
:hasError="showConsentError" :hasError="showConsentError"
:isDisabled="isDisabled" /> :isDisabled="isDisabled" />
<checkboxQuestion <!-- Temporarily hiding marketing consent checkbox. Will be re-added in a future release.
<checkboxQuestion
v-model="marketingConsent" v-model="marketingConsent"
checkboxName="saveProgressMarketingConsent" checkboxName="saveProgressMarketingConsent"
:labelText="consentCopy.marketing" :labelText="consentCopy.marketing"
:hasError="showConsentError" :hasError="showConsentError"
:isDisabled="isDisabled" /> :isDisabled="isDisabled" /> -->
<span <span
v-if="showConsentError" v-if="showConsentError"
class="save-progress-popup-sms-consent__error d-inline-flex small mt-1" class="save-progress-popup-sms-consent__error d-inline-flex small mt-1"

View file

@ -87,18 +87,36 @@ export default {
}).then( }).then(
(response) => { (response) => {
if (logApiCall) { if (logApiCall) {
let additionalEventData = "";
if (additionalSuccessEventDataHandler) {
additionalEventData = "_" + additionalSuccessEventDataHandler(response);
}
const endpointWithoutParams = const endpointWithoutParams =
analyticsMixIn.methods.removeParamsFromEndpoint(endpoint); analyticsMixIn.methods.removeParamsFromEndpoint(endpoint);
analyticsMixIn.methods.pushEventToGA( const gaAction = `${pageNameToLog}_${endpointWithoutParams}`;
GaCategories.API_RESPONSE,
`${pageNameToLog}_${endpointWithoutParams}`, if (additionalSuccessEventDataHandler) {
`${GaLabels.SUCCESS}${additionalEventData}`, const handlerResult = additionalSuccessEventDataHandler(response);
true const additionalEntries = Array.isArray(handlerResult)
); ? handlerResult
: [handlerResult];
additionalEntries.forEach((entry) => {
if (entry === undefined || entry === null || entry === "") {
return;
}
analyticsMixIn.methods.pushEventToGA(
GaCategories.API_RESPONSE,
gaAction,
`${GaLabels.SUCCESS}_${entry}`,
true
);
});
} else {
analyticsMixIn.methods.pushEventToGA(
GaCategories.API_RESPONSE,
gaAction,
GaLabels.SUCCESS,
true
);
}
} }
return resolve(response); return resolve(response);

View file

@ -30,6 +30,33 @@ it("Global Methods - Call Http Client - Should Resolve Promise", () => {
}); });
}); });
it("Global Methods - Call Http Client - Should log multiple success event entries", async () => {
const endpoint = "https://mock.safelite.com";
const httpArgs = setupMocksForHttpClient({ endpoint: endpoint });
httpArgs.additionalSuccessEventDataHandler = () => [
"Email provided: true",
"Phone provided: false",
];
analyticsMixIn.methods.pushEventToGA = jest.fn();
analyticsMixIn.methods.removeParamsFromEndpoint = jest.fn((url) => url);
await globalMethods.callHttpClient(httpArgs);
expect(analyticsMixIn.methods.pushEventToGA).toHaveBeenCalledTimes(2);
expect(analyticsMixIn.methods.pushEventToGA).toHaveBeenCalledWith(
"Api_Response",
expect.any(String),
"Success_Email provided: true",
true
);
expect(analyticsMixIn.methods.pushEventToGA).toHaveBeenCalledWith(
"Api_Response",
expect.any(String),
"Success_Phone provided: false",
true
);
});
it("Global Methods - Call Http Client - Should Reject Promise", () => { it("Global Methods - Call Http Client - Should Reject Promise", () => {
//Arrange //Arrange
const endpoint = "https://mock.safelite.com"; const endpoint = "https://mock.safelite.com";

View file

@ -320,6 +320,7 @@ describe("partQuestions.vue...", () => {
glassName: "Single", glassName: "Single",
isSuppressedPart: undefined, isSuppressedPart: undefined,
result: "FW04848", result: "FW04848",
problemQuestionId: null,
}, },
], ],
false false

View file

@ -176,6 +176,7 @@ export default {
glassLocation: glass.glassLocation, glassLocation: glass.glassLocation,
glassName: glass.glassName, glassName: glass.glassName,
result: (glass.answerData && glass.answerData.answerResult) || "", result: (glass.answerData && glass.answerData.answerResult) || "",
problemQuestionId: glass.answerData?.problemQuestionId ?? null,
answeredQuestions: glass.answerData?.answeredQuestions, answeredQuestions: glass.answerData?.answeredQuestions,
isSuppressedPart: glass.isSuppressedPart, isSuppressedPart: glass.isSuppressedPart,
}; };

View file

@ -24,6 +24,31 @@ describe("damage-location-question.vue", () => {
expect(wrapper.emitted()["update:modelValue"][0]).toEqual([{ modelValue: ["Backseat"] }]); expect(wrapper.emitted()["update:modelValue"][0]).toEqual([{ modelValue: ["Backseat"] }]);
}); });
test("when only one damage location is available, auto-selects it", async () => {
const { wrapper, damageOptions } = setupMocks({
modelValueProp: [],
dataFromStoreApi: {
driverSideOptions: {
availableReplacementOptions: [],
},
passengerSideOptions: {
availableReplacementOptions: [],
},
windshieldOptions: {
availableReplacementOptions: ["Single"],
},
backGlassOptions: {
availableReplacementOptions: [],
},
},
});
damageLocationQuestion.methods.initializeComponent.call(wrapper.vm, damageOptions);
await wrapper.vm.$nextTick();
expect(wrapper.emitted("update:modelValue")[0][0]).toEqual(["Windshield"]);
});
test("Answers to display filtered by data from api.", async () => { test("Answers to display filtered by data from api.", async () => {
//Arrange //Arrange
const { wrapper, cmsContent, damageOptions } = setupMocks({ const { wrapper, cmsContent, damageOptions } = setupMocks({
@ -31,6 +56,9 @@ describe("damage-location-question.vue", () => {
driverSideOptions: { driverSideOptions: {
availableReplacementOptions: ["Quarter", "Front"], availableReplacementOptions: ["Quarter", "Front"],
}, },
passengerSideOptions: {
availableReplacementOptions: [],
},
windshieldOptions: { windshieldOptions: {
availableReplacementOptions: ["Single"], availableReplacementOptions: ["Single"],
}, },
@ -51,6 +79,7 @@ describe("damage-location-question.vue", () => {
expect(wrapper.vm.damageOptions).toStrictEqual({ expect(wrapper.vm.damageOptions).toStrictEqual({
backGlassOptions: { availableReplacementOptions: [] }, backGlassOptions: { availableReplacementOptions: [] },
driverSideOptions: { availableReplacementOptions: ["Quarter", "Front"] }, driverSideOptions: { availableReplacementOptions: ["Quarter", "Front"] },
passengerSideOptions: { availableReplacementOptions: [] },
windshieldOptions: { availableReplacementOptions: ["Single"] }, windshieldOptions: { availableReplacementOptions: ["Single"] },
}); });
}); });
@ -79,7 +108,15 @@ function setupMocks({
//Mock props //Mock props
const mockMixin = { const mockMixin = {
methods: { methods: {
getCmsContent: jest.fn(), getCmsContent: jest.fn((widget, key) => {
if (key === "Answers") {
return cmsAnswers;
}
if (key === "QuestionText") {
return cmsQuestionText;
}
return "";
}),
}, },
}; };
mountOptions.propsData = { mountOptions.propsData = {
@ -87,6 +124,16 @@ function setupMocks({
isMultiSelect: isMultiSelect, isMultiSelect: isMultiSelect,
}; };
mountOptions.mixins = [mockMixin]; mountOptions.mixins = [mockMixin];
mountOptions.data = function () {
return {
damageOptions: {
driverSideOptions: { availableReplacementOptions: [] },
passengerSideOptions: { availableReplacementOptions: [] },
windshieldOptions: { availableReplacementOptions: [] },
backGlassOptions: { availableReplacementOptions: [] },
},
};
};
const wrapper = shallowMount(damageLocationQuestion, mountOptions); const wrapper = shallowMount(damageLocationQuestion, mountOptions);

View file

@ -37,6 +37,12 @@ export default {
methods: { methods: {
initializeComponent(damageOptions) { initializeComponent(damageOptions) {
this.damageOptions = damageOptions; this.damageOptions = damageOptions;
this.autoSelectSingleAnswer();
},
autoSelectSingleAnswer() {
if (this.answersToDisplay.length === 1) {
this.selectedValues = [this.answersToDisplay[0].Name];
}
}, },
}, },
computed: { computed: {

View file

@ -141,6 +141,7 @@ export default {
// add answerData to current glass // add answerData to current glass
glass.answerData = { glass.answerData = {
answerResult: answerResult, answerResult: answerResult,
problemQuestionId: answeredGlass.problemQuestionId ?? null,
answeredQuestions: answeredGlass.answeredQuestions, answeredQuestions: answeredGlass.answeredQuestions,
}; };
} }
@ -320,6 +321,7 @@ export default {
questionText: question.questionText, questionText: question.questionText,
selectedAnswerText: matchedAnswer.answerText, selectedAnswerText: matchedAnswer.answerText,
questionNum: question.questionSequence, questionNum: question.questionSequence,
problemQuestionId: matchedAnswer.problemQuestionId ?? null,
suppressThisQuestion: question.suppressThisQuestion, suppressThisQuestion: question.suppressThisQuestion,
}; };
// set the answerData (used as indicator that it has been already answered) // set the answerData (used as indicator that it has been already answered)
@ -327,6 +329,7 @@ export default {
answerResult: matchedAnswer.nextQuestionSequence answerResult: matchedAnswer.nextQuestionSequence
? matchedAnswer.nextQuestionSequence ? matchedAnswer.nextQuestionSequence
: matchedAnswer.answerResult, : matchedAnswer.answerResult,
problemQuestionId: matchedAnswer.problemQuestionId ?? null,
answeredQuestions: [answeredQuestionObj], answeredQuestions: [answeredQuestionObj],
}; };
@ -346,6 +349,7 @@ export default {
// set final answer data for the current answered glass part // set final answer data for the current answered glass part
self.questionsData[answer.index].answerData = { self.questionsData[answer.index].answerData = {
answerResult: answer.answerResult, answerResult: answer.answerResult,
problemQuestionId: answer.problemQuestionId ?? null,
answeredQuestions: answer.answeredQuestions, answeredQuestions: answer.answeredQuestions,
}; };

View file

@ -2941,8 +2941,10 @@ export const actions = {
}, },
logApiCall: true, logApiCall: true,
pageNameToLog: pageNameToLog, pageNameToLog: pageNameToLog,
additionalSuccessEventDataHandler: (response) => additionalSuccessEventDataHandler: (response) => [
"Email provided: " + (order.customer.emailAddress ? "true" : "false"), "Email provided: " + (order.customer.emailAddress ? "true" : "false"),
"Phone provided: " + (order.customer.phoneNumber ? "true" : "false"),
],
}); });
}, },
@ -4318,11 +4320,17 @@ function convertResultsForApi(resultsArray) {
if (!resultsArray) return []; if (!resultsArray) return [];
const converted = []; const converted = [];
resultsArray.forEach((answer) => { resultsArray.forEach((answer) => {
converted.push({ const convertedAnswer = {
location: answer.glassLocation, location: answer.glassLocation,
name: answer.glassName, name: answer.glassName,
result: answer.result, result: answer.result,
}); };
if (answer.problemQuestionId != null) {
convertedAnswer.problemQuestionId = answer.problemQuestionId;
}
converted.push(convertedAnswer);
}); });
return converted; return converted;
} }

View file

@ -4470,4 +4470,115 @@ describe("isVinOptionalVehicle", () => {
expect(vinOptionalResult).toEqual(expectedVinSkip); expect(vinOptionalResult).toEqual(expectedVinSkip);
} }
); );
it("getParts action, should include problemQuestionId in answerResults payload", async () => {
const context = {
getters: {
vehicle: { carId: "CR00065283", vin: "SAJWA6A73F8K13235" },
damage: {
glassToReplace: [{ glassLocation: "Windshield", glassName: "Single" }],
partQuestionAnswers: [
{
glassLocation: "Windshield",
glassName: "Single",
result: "FW04848",
problemQuestionId: 38560,
answeredQuestions: [
{
questionText:
"Is your vehicle equipped with the Panoramic Sunroof which can be identified by having a glass panel over the rear seats?",
selectedAnswer: "1|nextQuestion|2|Yes",
selectedAnswerText: "Yes",
questionNum: 1,
problemQuestionId: 38557,
},
{
questionText:
"Is your vehicle equipped with a heated windshield that melts snow and ice from underneath the windshield wiper blades?",
selectedAnswer: "2|answer|FW04848|Yes",
selectedAnswerText: "Yes",
questionNum: 2,
problemQuestionId: 38560,
},
],
},
],
},
payment: { parentAccountNumber: "167132" },
},
state: {
order: {
serviceLocation: { zipCode: "43085", appointmentType: null },
referralSequenceNumber: "11330779",
},
},
};
globalMethods.callHttpClient.mockImplementation(({ payload }) => {
return Promise.resolve({ data: { glassPieceParts: [] }, payload });
});
await actions.getParts(context, { pageNameToLog: "part-questions" });
expect(globalMethods.callHttpClient).toHaveBeenCalledWith(
expect.objectContaining({
endpoint: endpoints.GetParts.url,
payload: expect.objectContaining({
answerResults: [
{
location: "Windshield",
name: "Single",
result: "FW04848",
problemQuestionId: 38560,
},
],
}),
})
);
});
it("getParts action, should omit problemQuestionId when not saved on part question answer", async () => {
const context = {
getters: {
vehicle: { carId: "CR00065283", vin: "SAJWA6A73F8K13235" },
damage: {
glassToReplace: [{ glassLocation: "Windshield", glassName: "Single" }],
partQuestionAnswers: [
{
glassLocation: "Windshield",
glassName: "Single",
result: "FW04848",
},
],
},
payment: { parentAccountNumber: "167132" },
},
state: {
order: {
serviceLocation: { zipCode: "43085", appointmentType: null },
referralSequenceNumber: "11330779",
},
},
};
globalMethods.callHttpClient.mockImplementation(() => {
return Promise.resolve({ data: { glassPieceParts: [] } });
});
await actions.getParts(context, { pageNameToLog: "part-questions" });
expect(globalMethods.callHttpClient).toHaveBeenCalledWith(
expect.objectContaining({
payload: expect.objectContaining({
answerResults: [
{
location: "Windshield",
name: "Single",
result: "FW04848",
},
],
}),
})
);
});
}); });