From 368267d123c928cb6314eb0cb97699181b4a3f7f Mon Sep 17 00:00:00 2001 From: JennyNou Date: Mon, 11 May 2026 15:29:52 -0400 Subject: [PATCH 01/23] Add 2.0 policy info page --- playwright-tests/framework/TestPages.ts | 3 +++ playwright-tests/pages/PolicyInfoPage.ts | 17 +++++++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 playwright-tests/pages/PolicyInfoPage.ts diff --git a/playwright-tests/framework/TestPages.ts b/playwright-tests/framework/TestPages.ts index bc98e44b5..54078e266 100644 --- a/playwright-tests/framework/TestPages.ts +++ b/playwright-tests/framework/TestPages.ts @@ -33,6 +33,7 @@ import { EndorsementsPage } from "../pages/EndorsementsPage" import { PolicyDriverPage } from "../pages/PolicyDriverPage" import { ServiceZipPage } from "../pages/ServiceZipPage" import { MobileDetailsPage } from "pages/MobileDetailsPage"; +import { PolicyInfoPage } from "../pages/PolicyInfoPage"; export interface ITestPages { capabilityQuestionsPage: CapabilityQuestionsPage, @@ -49,6 +50,7 @@ export interface ITestPages { orderConfirmationPage: OrderConfirmationPage, partQuestionsPage: PartQuestionsPage, paymentMethodPage: PaymentMethodPage, + policyInfoPage: PolicyInfoPage, policyInfoSubmittedPage: PolicyInfoSubmittedPage, policyVehiclesPage: PolicyVehiclesPage, heritageProblemGlassQuestionsPage: HeritageProblemGlassQuestionsPage, @@ -86,6 +88,7 @@ export const createTestPages: TestPagesFactory = (page: Page) => { orderConfirmationPage: new OrderConfirmationPage(page), partQuestionsPage: new PartQuestionsPage(page), paymentMethodPage: new PaymentMethodPage(page), + policyInfoPage: new PolicyInfoPage(page), policyInfoSubmittedPage: new PolicyInfoSubmittedPage(page), policyVehiclesPage: new PolicyVehiclesPage(page), heritageProblemGlassQuestionsPage: new HeritageProblemGlassQuestionsPage(page), diff --git a/playwright-tests/pages/PolicyInfoPage.ts b/playwright-tests/pages/PolicyInfoPage.ts new file mode 100644 index 000000000..22b6c07e7 --- /dev/null +++ b/playwright-tests/pages/PolicyInfoPage.ts @@ -0,0 +1,17 @@ +import { type Page } from '@playwright/test'; +import { BasePage } from './BasePage'; +import { step } from 'framework/localTypes/Step'; +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 handlePolicyInfoPage(testData: Partial): Promise { + await this.nextPage(); + } +} From 0e32aa0f11cf165d78ac50db753b48185a57a1ca Mon Sep 17 00:00:00 2001 From: JennyNou Date: Mon, 11 May 2026 17:12:58 -0400 Subject: [PATCH 02/23] Add Liberty Mutual test --- playwright-tests/framework/TestData.ts | 1 + playwright-tests/pages/PolicyInfoPage.ts | 5 +- playwright-tests/tests/0000__M.test.ts | 10 ++++ .../tests/InsuranceLibertyMutual.ts | 54 +++++++++++++++++++ 4 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 playwright-tests/tests/InsuranceLibertyMutual.ts diff --git a/playwright-tests/framework/TestData.ts b/playwright-tests/framework/TestData.ts index 332e3dc26..0fe04a1f9 100644 --- a/playwright-tests/framework/TestData.ts +++ b/playwright-tests/framework/TestData.ts @@ -15,6 +15,7 @@ export interface ITestData extends base { isMSRZip?: boolean, isPIAEnabled?: boolean, isDualRecal?: boolean, + isPolicyInfoPage?: boolean, } diff --git a/playwright-tests/pages/PolicyInfoPage.ts b/playwright-tests/pages/PolicyInfoPage.ts index 22b6c07e7..7974e3099 100644 --- a/playwright-tests/pages/PolicyInfoPage.ts +++ b/playwright-tests/pages/PolicyInfoPage.ts @@ -1,4 +1,4 @@ -import { type Page } from '@playwright/test'; +import { expect, type Page } from '@playwright/test'; import { BasePage } from './BasePage'; import { step } from 'framework/localTypes/Step'; import { ITestData } from 'framework/TestData'; @@ -12,6 +12,7 @@ export class PolicyInfoPage extends BasePage { @step("PolicyInfoPage >> Continue") async handlePolicyInfoPage(testData: Partial): Promise { - await this.nextPage(); + + await expect(this.page).toHaveURL(/\/fmg\/policy-info/); } } diff --git a/playwright-tests/tests/0000__M.test.ts b/playwright-tests/tests/0000__M.test.ts index 79cfc89f8..9eed669b4 100644 --- a/playwright-tests/tests/0000__M.test.ts +++ b/playwright-tests/tests/0000__M.test.ts @@ -44,6 +44,7 @@ import insuranceBigTruckVerifiedTests from "./InsuranceBigTruckVerified"; import insuranceUnverifiedTests from "./InsuranceUnverified"; import CashReplaceSplitWindshieldTests from "./CashReplaceSplitWindshield"; import insuranceMeemicNearSchoolVerifiedTests from "./InsuranceMeemicNearSchoolVerified"; +import insuranceLibertyMutualTests from "./InsuranceLibertyMutual"; import cashReplaceDualMobileMSRTests from "./CashReplaceDualMobileMSR"; import cashReplaceDualNonMSRInshopTests from "./CashReplaceDualNonMSRInshop"; @@ -101,6 +102,7 @@ const allStandardTests = [ // {name: "InsuranceGeico", tests: insuranceGeicoTests}, // {name: "InsuranceITACOptimizedPriceValidationAllState", tests: insuranceITACOptimizedPriceValidationAllStateTests} { name: "InsuranceMeemicNearSchoolVerified", tests: insuranceMeemicNearSchoolVerifiedTests }, + { name: "InsuranceLibertyMutual", tests: insuranceLibertyMutualTests }, ]; @@ -308,6 +310,7 @@ async function runWorkflow(page: Page, testCase: TestCase) { // Insurance flow - if user selected Insurance as Payment Method if (paymentMethod == PaymentMethod.Insurance) { await handleInsuranceFlow(testCase); + if (testCase.testData.isPolicyInfoPage) return; // If Liberty Mutual and on PolicyInfoPage, skip the rest of the flow until the page is built out } //============================= SERVICE SCHEDULING ============================= @@ -364,6 +367,13 @@ async function handleInsuranceFlow(testCase: TestCase) { await HeritageProblemGlassQuestionsPage.handleHeritageProblemGlassQuestionsPage(testCase.testData); } + // Liberty Mutual route directly to PolicyInfoPage instead of CCPolicyInfoPage + if (testCase.testData.isPolicyInfoPage) { + let policyInfoPage = testCase.pages.policyInfoPage; + await policyInfoPage.handlePolicyInfoPage(testCase.testData); + return; + } + // Handle ccPolicyInfoPage let ccPolicyInfoPage = testCase.pages.ccPolicyInfoPage; await ccPolicyInfoPage.handleCCPolicyInfoPage(testCase.testData); diff --git a/playwright-tests/tests/InsuranceLibertyMutual.ts b/playwright-tests/tests/InsuranceLibertyMutual.ts new file mode 100644 index 000000000..0c89d6084 --- /dev/null +++ b/playwright-tests/tests/InsuranceLibertyMutual.ts @@ -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 = { + ...getDefaultTestData(), + + paymentMethod: PaymentMethod.Insurance, + isPolicyInfoPage: 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; From 73dd248f0c1e05cd7641bd60afc9bc087d1e37c8 Mon Sep 17 00:00:00 2001 From: JennyNou Date: Thu, 14 May 2026 17:21:13 -0400 Subject: [PATCH 03/23] Add unverified big truck with labor2 fee test --- playwright-tests/pages/SchedulePage.ts | 8 +- .../InsuranceAllstateBigTruckUnverified.ts | 74 +++++++++++++++++++ 2 files changed, 78 insertions(+), 4 deletions(-) create mode 100644 playwright-tests/tests/InsuranceAllstateBigTruckUnverified.ts diff --git a/playwright-tests/pages/SchedulePage.ts b/playwright-tests/pages/SchedulePage.ts index 8d863e94f..03106fa00 100644 --- a/playwright-tests/pages/SchedulePage.ts +++ b/playwright-tests/pages/SchedulePage.ts @@ -261,11 +261,11 @@ export class SchedulePage extends BasePage { await this.waitForPageOrComponentload(); await this.validateProgressBar(ProgressBarPercentages.SchedulePage); - //Looks like an issue with the insurance flow - remove comments once fixed - /*if (testData.isHeavyTruck) { + //Labor2 fee will show for cash orders and some insurance orders but USAA will not show it + if (testData.isHeavyTruck && testData.claimDetails?.client !== 'USAA') { 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) { return await this.handleMobileFirstPopUp(testData); diff --git a/playwright-tests/tests/InsuranceAllstateBigTruckUnverified.ts b/playwright-tests/tests/InsuranceAllstateBigTruckUnverified.ts new file mode 100644 index 000000000..ef374411d --- /dev/null +++ b/playwright-tests/tests/InsuranceAllstateBigTruckUnverified.ts @@ -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 = { + ...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; From 27aaf2f186b29d07ecfeea9ea0892aa128aca2fc Mon Sep 17 00:00:00 2001 From: JennyNou Date: Thu, 14 May 2026 17:23:14 -0400 Subject: [PATCH 04/23] Enable unverified big truck and parts bailout tests --- playwright-tests/tests/0000__M.test.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/playwright-tests/tests/0000__M.test.ts b/playwright-tests/tests/0000__M.test.ts index 6ef799455..eef0709fe 100644 --- a/playwright-tests/tests/0000__M.test.ts +++ b/playwright-tests/tests/0000__M.test.ts @@ -41,6 +41,7 @@ import { getTestObject, TestCase, prepareTest, RuleEngine, TestInfo } from 'fram import { createTestPages } from "framework/TestPages"; import cashReplaceSwitchToInsuranceProgressiveNoCompTests from "./CashReplaceSwitchToInsuranceProgressiveNoComp"; import insuranceUSAABigTruckVerifiedTests from "./InsuranceUSAABigTruckVerified"; +import insuranceAllstateBigTruckUnverifiedTests from "./InsuranceAllstateBigTruckUnverified"; import partsNotFoundBailoutTests from "./PartsNotFoundBailout"; import insuranceUnverifiedTests from "./InsuranceUnverified"; import CashReplaceSplitWindshieldTests from "./CashReplaceSplitWindshield"; @@ -98,14 +99,15 @@ const allStandardTests = [ { name: "InsuranceITAC21stCentury", tests: insuranceITAC21stCenturyTests }, { name: "InsuranceNoCompProgressive", tests: insuranceNoCompProgressiveTests }, {name: "InsuranceUSAABigTruckVerified", tests: insuranceUSAABigTruckVerifiedTests}, + { name: "InsuranceAllstateBigTruckUnverified", tests: insuranceAllstateBigTruckUnverifiedTests }, { name: "InsuranceOEMAllstate", tests: insuranceOEMAllstateTests }, { name: "InsuranceUnverified", tests: insuranceUnverifiedTests }, // {name: "InsuranceGeico", tests: insuranceGeicoTests}, // {name: "InsuranceITACOptimizedPriceValidationAllState", tests: insuranceITACOptimizedPriceValidationAllStateTests} { name: "InsuranceMeemicNearSchoolVerified", tests: insuranceMeemicNearSchoolVerifiedTests }, - { name: "InsuranceLibertyMutual", tests: insuranceLibertyMutualTests }, + //{ name: "InsuranceLibertyMutual", tests: insuranceLibertyMutualTests }, // uncomment when new C&C pages are built out for this entire flow - // { name: "PartsNotFoundBailout", tests: partsNotFoundBailoutTests }, // code is not available in QA + { name: "PartsNotFoundBailout", tests: partsNotFoundBailoutTests } ]; // Alert validation scenarios From de4554c31a948f4623d64555e1ca6e31eccd7318 Mon Sep 17 00:00:00 2001 From: JennyNou Date: Thu, 14 May 2026 18:05:19 -0400 Subject: [PATCH 05/23] Add big truck test for cash scenario --- playwright-tests/pages/SchedulePage.ts | 4 +- playwright-tests/tests/0000__M.test.ts | 2 + playwright-tests/tests/CashBigTruck.ts | 64 ++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 playwright-tests/tests/CashBigTruck.ts diff --git a/playwright-tests/pages/SchedulePage.ts b/playwright-tests/pages/SchedulePage.ts index 03106fa00..9ba237a8e 100644 --- a/playwright-tests/pages/SchedulePage.ts +++ b/playwright-tests/pages/SchedulePage.ts @@ -3,7 +3,7 @@ import { BasePage } from './BasePage'; import { IAppointmentDetails, ServiceLocation } from 'safelite-playwright-core'; import { formatDate, formatTime } 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 { step } from 'framework/localTypes/Step'; import { ITestData } from 'framework/TestData'; @@ -262,7 +262,7 @@ export class SchedulePage extends BasePage { await this.validateProgressBar(ProgressBarPercentages.SchedulePage); //Labor2 fee will show for cash orders and some insurance orders but USAA will not show it - if (testData.isHeavyTruck && testData.claimDetails?.client !== 'USAA') { + if (testData.isHeavyTruck && (testData.paymentMethod !== PaymentMethod.Insurance || testData.claimDetails?.client !== 'USAA')) { const vuexState = JSON.parse(await this.page.evaluate('localStorage.getItem(\'vuex\')')); expect(vuexState.order.lineItems.supportingItems[0].partNumber).toBe("LABOR2"); } diff --git a/playwright-tests/tests/0000__M.test.ts b/playwright-tests/tests/0000__M.test.ts index eef0709fe..58d5cc675 100644 --- a/playwright-tests/tests/0000__M.test.ts +++ b/playwright-tests/tests/0000__M.test.ts @@ -49,6 +49,7 @@ import insuranceMeemicNearSchoolVerifiedTests from "./InsuranceMeemicNearSchoolV import insuranceLibertyMutualTests from "./InsuranceLibertyMutual"; import cashReplaceDualMobileMSRTests from "./CashReplaceDualMobileMSR"; import cashReplaceDualNonMSRInshopTests from "./CashReplaceDualNonMSRInshop"; +import cashBigTruckTests from "./CashBigTruck"; const test = getTestObject(); @@ -86,6 +87,7 @@ const allStandardTests = [ { name: "CashReplaceVinMobile", tests: cashReplaceVinMobileTests }, { name: "CashReplaceWiperDropoff", tests: cashReplaceWiperDropoffTests }, { name: "CashReplaceStaticInshop", tests: cashReplaceStaticInshopTests }, + { name: "CashBigTruck", tests: cashBigTruckTests }, { name: "CashReplaceDualMobileMSR", tests: cashReplaceDualMobileMSRTests }, { name: "CashReplaceDualNonMSRInshop", tests: cashReplaceDualNonMSRInshopTests }, { name: "CashReplaceStaticMobileMSR", tests: cashReplaceStaticMobileMSRTests }, diff --git a/playwright-tests/tests/CashBigTruck.ts b/playwright-tests/tests/CashBigTruck.ts new file mode 100644 index 000000000..47daffe86 --- /dev/null +++ b/playwright-tests/tests/CashBigTruck.ts @@ -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 = { + ...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; From b0b96787b525807737b64aa4037bb1182bb47a28 Mon Sep 17 00:00:00 2001 From: JennyNou Date: Thu, 21 May 2026 14:21:49 -0400 Subject: [PATCH 06/23] Fix naming and test for new policy info page --- playwright-tests/framework/TestData.ts | 2 +- playwright-tests/pages/PolicyInfoPage.ts | 2 ++ playwright-tests/tests/0000__M.test.ts | 5 ++--- playwright-tests/tests/InsuranceLibertyMutual.ts | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/playwright-tests/framework/TestData.ts b/playwright-tests/framework/TestData.ts index 0fe04a1f9..8100fdf09 100644 --- a/playwright-tests/framework/TestData.ts +++ b/playwright-tests/framework/TestData.ts @@ -15,7 +15,7 @@ export interface ITestData extends base { isMSRZip?: boolean, isPIAEnabled?: boolean, isDualRecal?: boolean, - isPolicyInfoPage?: boolean, + isNewInsuranceFlow?: boolean, } diff --git a/playwright-tests/pages/PolicyInfoPage.ts b/playwright-tests/pages/PolicyInfoPage.ts index 7974e3099..04f35c346 100644 --- a/playwright-tests/pages/PolicyInfoPage.ts +++ b/playwright-tests/pages/PolicyInfoPage.ts @@ -1,6 +1,7 @@ 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 { @@ -14,5 +15,6 @@ export class PolicyInfoPage extends BasePage { async handlePolicyInfoPage(testData: Partial): Promise { await expect(this.page).toHaveURL(/\/fmg\/policy-info/); + throw new TestSuccessAlert('Policy info page validated successfully.'); } } diff --git a/playwright-tests/tests/0000__M.test.ts b/playwright-tests/tests/0000__M.test.ts index 58d5cc675..88e4101f5 100644 --- a/playwright-tests/tests/0000__M.test.ts +++ b/playwright-tests/tests/0000__M.test.ts @@ -324,7 +324,6 @@ async function runWorkflow(page: Page, testCase: TestCase) { // Insurance flow - if user selected Insurance as Payment Method if (paymentMethod == PaymentMethod.Insurance) { await handleInsuranceFlow(testCase); - if (testCase.testData.isPolicyInfoPage) return; // If Liberty Mutual and on PolicyInfoPage, skip the rest of the flow until the page is built out } //============================= SERVICE SCHEDULING ============================= @@ -365,7 +364,7 @@ async function runWorkflow(page: Page, 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 const hasEndorsements = endorsements && endorsements.length > 0; @@ -382,7 +381,7 @@ async function handleInsuranceFlow(testCase: TestCase) { } // Liberty Mutual route directly to PolicyInfoPage instead of CCPolicyInfoPage - if (testCase.testData.isPolicyInfoPage) { + if (isNewInsuranceFlow) { let policyInfoPage = testCase.pages.policyInfoPage; await policyInfoPage.handlePolicyInfoPage(testCase.testData); return; diff --git a/playwright-tests/tests/InsuranceLibertyMutual.ts b/playwright-tests/tests/InsuranceLibertyMutual.ts index 0c89d6084..9edff185f 100644 --- a/playwright-tests/tests/InsuranceLibertyMutual.ts +++ b/playwright-tests/tests/InsuranceLibertyMutual.ts @@ -10,7 +10,7 @@ const insuranceLibertyMutualData: Partial = { ...getDefaultTestData(), paymentMethod: PaymentMethod.Insurance, - isPolicyInfoPage: true, + isNewInsuranceFlow: true, claimDetails: { client: 'Liberty Mutual Insurance', From 6bd115fa02106c728ec3b9ac9efe81d9f475ae31 Mon Sep 17 00:00:00 2001 From: credelinghuys Date: Wed, 8 Jul 2026 12:22:17 -0400 Subject: [PATCH 07/23] CASH-2948: ServicePackageNameTest experiment --- src/constants/experiments.js | 6 ++ src/helpers/service-package-helper.js | 29 ++++++++ src/helpers/service-package-helper.spec.js | 62 ++++++++++++++++ .../service-package-question.spec.js | 73 +++++++++++++++++++ .../service-package-question.vue | 25 ++++++- 5 files changed, 194 insertions(+), 1 deletion(-) diff --git a/src/constants/experiments.js b/src/constants/experiments.js index b470cc425..1cd1260a7 100644 --- a/src/constants/experiments.js +++ b/src/constants/experiments.js @@ -52,6 +52,12 @@ const experimentSettings = { // Claim and Coverage for 2.0 ENABLE_NON_CLAIM_AND_COVERAGE_FLOW: "EnableNonC&CFlow", NON_CLAIM_AND_COVERAGE_TEST_PARENT_ACCOUNT: "NonC&CTestParentAccount", + + // Service Package Name + SHOW_SERVICE_PACKAGE_NAME_TEST: "ShowServicePackageNameTest", + TIER_ONE: "TierOne", + TIER_TWO: "TierTwo", + TIER_THREE: "TierThree", }; const experimentTriggers = { diff --git a/src/helpers/service-package-helper.js b/src/helpers/service-package-helper.js index d3fc3f01b..f43680dd2 100644 --- a/src/helpers/service-package-helper.js +++ b/src/helpers/service-package-helper.js @@ -1,12 +1,20 @@ import { partTypeStrings } from "@/constants/part-type-strings"; import { damageLocationsSelected as glassLocations } from "@/constants/damage-locations-selected"; import { packageNames, externalParamPackageLabels } from "@/constants/package-names"; +import { experimentSettings } from "@/constants/experiments"; import baseMixin from "@/mixins/base-mixin.js"; import { getItemsWithoutRecalParts } from "@/helpers/recal-helper"; import { getPromosThatMatchLineItemsOnOrder, removeVapsPromosFromPromoArray, } from "@/helpers/promotions-helper"; +import * as experimentHelper from "@/helpers/experiment-helper"; + +const tierExperimentSettingKeys = { + [packageNames.TIER_ONE]: experimentSettings.TIER_ONE, + [packageNames.TIER_TWO]: experimentSettings.TIER_TWO, + [packageNames.TIER_THREE]: experimentSettings.TIER_THREE, +}; export function containsLineItemWithPartType(typeToFind, itemsToSearch) { const partTypeMatches = findLineItemsWithPartType(typeToFind, itemsToSearch); @@ -267,6 +275,27 @@ export function getPackageLabel(packageName) { } } +export function isServicePackageNameTestActive(experimentSettingsMap) { + return experimentHelper.hasSettingEqualTo( + experimentSettings.SHOW_SERVICE_PACKAGE_NAME_TEST, + "true", + experimentSettingsMap + ); +} + +export function getExperimentPackageDisplayName(tierName, experimentSettingsMap) { + if (!isServicePackageNameTestActive(experimentSettingsMap)) { + return null; + } + + const settingKey = tierExperimentSettingKeys[tierName]; + if (!settingKey) { + return null; + } + + return experimentHelper.getSettingValue(settingKey, experimentSettingsMap); +} + function maxTier(tierA, tierB) { if (tierA === packageNames.TIER_THREE || tierB === packageNames.TIER_THREE) { return packageNames.TIER_THREE; diff --git a/src/helpers/service-package-helper.spec.js b/src/helpers/service-package-helper.spec.js index 65ed91fe9..495436c8d 100644 --- a/src/helpers/service-package-helper.spec.js +++ b/src/helpers/service-package-helper.spec.js @@ -966,6 +966,68 @@ describe("service-package-helper.js", () => { expect(result).toEqual(packageNames.TIER_ONE); }); }); + + describe("getExperimentPackageDisplayName", () => { + const { experimentSettings } = require("@/constants/experiments"); + + it("Returns null when the experiment is not active", () => { + const result = servicePackageHelper.getExperimentPackageDisplayName( + packageNames.TIER_ONE, + {} + ); + + expect(result).toBeNull(); + }); + + it("Returns null when the test flag is not true", () => { + const result = servicePackageHelper.getExperimentPackageDisplayName( + packageNames.TIER_ONE, + { + [experimentSettings.SHOW_SERVICE_PACKAGE_NAME_TEST]: "false", + [experimentSettings.TIER_ONE]: "Essential", + } + ); + + expect(result).toBeNull(); + }); + + it("Returns the experiment display name for each tier when active", () => { + const experimentSettingsMap = { + [experimentSettings.SHOW_SERVICE_PACKAGE_NAME_TEST]: "true", + [experimentSettings.TIER_ONE]: "Essential", + [experimentSettings.TIER_TWO]: "Plus", + [experimentSettings.TIER_THREE]: "Full Service", + }; + + expect( + servicePackageHelper.getExperimentPackageDisplayName( + packageNames.TIER_ONE, + experimentSettingsMap + ) + ).toBe("Essential"); + expect( + servicePackageHelper.getExperimentPackageDisplayName( + packageNames.TIER_TWO, + experimentSettingsMap + ) + ).toBe("Plus"); + expect( + servicePackageHelper.getExperimentPackageDisplayName( + packageNames.TIER_THREE, + experimentSettingsMap + ) + ).toBe("Full Service"); + }); + + it("Returns null for an unknown tier", () => { + const result = servicePackageHelper.getExperimentPackageDisplayName("UnknownTier", { + [experimentSettings.SHOW_SERVICE_PACKAGE_NAME_TEST]: "true", + [experimentSettings.TIER_ONE]: "Essential", + }); + + expect(result).toBeNull(); + }); + }); }); // Constants diff --git a/src/layouts/quote/service-package-question/service-package-question.spec.js b/src/layouts/quote/service-package-question/service-package-question.spec.js index f87faeec8..1d0f78c1a 100644 --- a/src/layouts/quote/service-package-question/service-package-question.spec.js +++ b/src/layouts/quote/service-package-question/service-package-question.spec.js @@ -817,6 +817,79 @@ describe("service-package-question.vue, matching business rules for package disp expectedModifiedAnswers[packageNameKey].tierThree ); }); + it("should use experiment package names when ServicePackageNameTest is active", () => { + const getExperimentPackageLabelSpy = jest + .spyOn(servicePackageQuestion.methods, "getExperimentPackageLabel") + .mockImplementation((tierName) => { + const experimentPackageNames = { + [packageNames.TIER_ONE]: "Essential", + [packageNames.TIER_TWO]: "Plus", + [packageNames.TIER_THREE]: "Full Service", + }; + + return experimentPackageNames[tierName] ?? null; + }); + + const packageNameKey = "05_01_CSR_Quote_Standard_Repair"; + Object.assign(processedCmsContent, mockProcessedCmsContent[packageNameKey]); + mockProps.availableLineItems.push(driverFrontWiperLineItem); + mockProps.availableLineItems.push(passengerFrontWiperLineItem); + const wrapper = setupMocks({ + mountOptionsMockData: { + store: { + getters: { + order: { + damage: { + isRepair: true, + glassToReplace: [], + }, + }, + hasAnyNonWindshieldGlassParts: false, + }, + }, + }, + }); + + expect(wrapper.vm.servicePackageAnswers[0].buttonLabel).toBe("Essential"); + expect(wrapper.vm.servicePackageAnswers[1].buttonLabel).toBe("Plus"); + expect(wrapper.vm.servicePackageAnswers[2].buttonLabel).toBe("Full Service"); + + getExperimentPackageLabelSpy.mockRestore(); + }); + it("should use CMS package names when ServicePackageNameTest is not active", () => { + const packageNameKey = "05_01_CSR_Quote_Standard_Repair"; + Object.assign(processedCmsContent, mockProcessedCmsContent[packageNameKey]); + mockProps.availableLineItems.push(driverFrontWiperLineItem); + mockProps.availableLineItems.push(passengerFrontWiperLineItem); + const wrapper = setupMocks({ + mountOptionsMockData: { + store: { + getters: { + order: { + damage: { + isRepair: true, + glassToReplace: [], + }, + }, + hasAnyNonWindshieldGlassParts: false, + }, + }, + }, + }); + + runPackageAnswerExpectStatements( + wrapper.vm.servicePackageAnswers[0], + expectedModifiedAnswers[packageNameKey].tierOne + ); + runPackageAnswerExpectStatements( + wrapper.vm.servicePackageAnswers[1], + expectedModifiedAnswers[packageNameKey].tierTwo + ); + runPackageAnswerExpectStatements( + wrapper.vm.servicePackageAnswers[2], + expectedModifiedAnswers[packageNameKey].tierThree + ); + }); }); function runPackageAnswerExpectStatements(servicePackageAnswer, expectedOutput) { diff --git a/src/layouts/quote/service-package-question/service-package-question.vue b/src/layouts/quote/service-package-question/service-package-question.vue index 72472e195..4a54ee3f5 100644 --- a/src/layouts/quote/service-package-question/service-package-question.vue +++ b/src/layouts/quote/service-package-question/service-package-question.vue @@ -141,7 +141,7 @@ export default { const modifiedAnswers = cmsAnswersContent.map((answer) => ({ value: answer.Name, - buttonLabel: this.getHeaderTextFromCms(answer.SubWidgetName), + buttonLabel: this.getPackageButtonLabel(answer.Name, answer.SubWidgetName), buttonLabelSubCopy: this.getSubheaderTextFromCms(answer.SubWidgetName), buttonBodyCopy: this.getBodyTextFromCms(answer.SubWidgetName), //The package supports service-package-discount if it has the text value in CMS @@ -259,6 +259,29 @@ export default { } return this.servicePackageRadio; }, + getPackageButtonLabel(tierName, subWidgetName) { + return ( + this.getExperimentPackageLabel(tierName) ?? this.getHeaderTextFromCms(subWidgetName) + ); + }, + 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; + }, getHeaderTextFromCms(cmsWidgetName) { return this.getCmsContent(cmsWidgetName, "HeaderText"); }, From cf621394ad6845d37c66c68c35da384ab8e471b8 Mon Sep 17 00:00:00 2001 From: credelinghuys Date: Wed, 8 Jul 2026 13:51:33 -0400 Subject: [PATCH 08/23] CASH-2934: Normalize phone number --- .../save-progress-popup-question.spec.js | 2 +- src/helpers/save-progress-popup-contact-helper.js | 14 +++++++++++++- .../save-progress-popup-contact-helper.spec.js | 14 +++++++++++++- 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/fmg-components/save-progress-popup-question/save-progress-popup-question.spec.js b/src/fmg-components/save-progress-popup-question/save-progress-popup-question.spec.js index ad42fa617..9979a4162 100644 --- a/src/fmg-components/save-progress-popup-question/save-progress-popup-question.spec.js +++ b/src/fmg-components/save-progress-popup-question/save-progress-popup-question.spec.js @@ -210,7 +210,7 @@ describe("save-progress-popup-question ", () => { expect(dispatchStoreAction).toHaveBeenCalledWith( storeActions.SAVE_PHONE_NUMBER, - "555-123-4567", + "5551234567", false ); expect(dispatchStoreAction).not.toHaveBeenCalledWith( diff --git a/src/helpers/save-progress-popup-contact-helper.js b/src/helpers/save-progress-popup-contact-helper.js index 2853d8c8e..d19d1611f 100644 --- a/src/helpers/save-progress-popup-contact-helper.js +++ b/src/helpers/save-progress-popup-contact-helper.js @@ -10,12 +10,24 @@ export function isPhoneContactMethod(contactMethod) { return contactMethod === saveProgressPopupContactMethods.PHONE; } +export function normalizePhoneNumberForStore(phoneNumber) { + if (!phoneNumber) { + return phoneNumber; + } + + return String(phoneNumber).replace(/\D/g, ""); +} + export async function saveProgressPopupContactToStore( dispatchStoreAction, { contactMethod, userInput, smsConsent, pageName } ) { if (isPhoneContactMethod(contactMethod)) { - await dispatchStoreAction(storeActions.SAVE_PHONE_NUMBER, userInput, false); + await dispatchStoreAction( + storeActions.SAVE_PHONE_NUMBER, + normalizePhoneNumberForStore(userInput), + false + ); await dispatchStoreAction(storeActions.SAVE_IS_SMS_OPT_IN, smsConsent.transactional, false); const existingPageData = store.getters.pageData(pageName) ?? {}; diff --git a/src/helpers/save-progress-popup-contact-helper.spec.js b/src/helpers/save-progress-popup-contact-helper.spec.js index 5935874d9..a2a252c01 100644 --- a/src/helpers/save-progress-popup-contact-helper.spec.js +++ b/src/helpers/save-progress-popup-contact-helper.spec.js @@ -2,6 +2,7 @@ import { storeActions } from "@/constants/store-actions"; import store from "@/store"; import { isPhoneContactMethod, + normalizePhoneNumberForStore, saveProgressPopupContactMethods, saveProgressPopupContactToStore, } from "./save-progress-popup-contact-helper"; @@ -28,6 +29,17 @@ describe("save-progress-popup-contact-helper", () => { }); }); + describe("normalizePhoneNumberForStore", () => { + it("should strip non-digit characters from a formatted phone number", () => { + expect(normalizePhoneNumberForStore("555-123-4567")).toBe("5551234567"); + }); + + it("should return empty values unchanged", () => { + expect(normalizePhoneNumberForStore("")).toBe(""); + expect(normalizePhoneNumberForStore(null)).toBe(null); + }); + }); + describe("saveProgressPopupContactToStore", () => { it("should save phone number and persist sms consent on phone tab", async () => { const dispatchStoreAction = jest.fn().mockResolvedValue(undefined); @@ -42,7 +54,7 @@ describe("save-progress-popup-contact-helper", () => { expect(dispatchStoreAction).toHaveBeenCalledWith( storeActions.SAVE_PHONE_NUMBER, - "555-123-4567", + "5551234567", false ); expect(dispatchStoreAction).not.toHaveBeenCalledWith( From 14934ff62b210f4a3a4285964ddedd4a663e3fd2 Mon Sep 17 00:00:00 2001 From: credelinghuys Date: Thu, 9 Jul 2026 13:08:33 -0400 Subject: [PATCH 09/23] CASH-2967: Show ServicePackageNameTest names in cart --- src/fmg-components/cart/cart.spec.js | 86 +++++++++++++++++++++++++++- src/fmg-components/cart/cart.vue | 26 ++++++++- 2 files changed, 109 insertions(+), 3 deletions(-) diff --git a/src/fmg-components/cart/cart.spec.js b/src/fmg-components/cart/cart.spec.js index f06a49a90..a920e39f6 100644 --- a/src/fmg-components/cart/cart.spec.js +++ b/src/fmg-components/cart/cart.spec.js @@ -5,8 +5,24 @@ import cart from "@/fmg-components/cart/cart.vue"; import { shallowMount } from "@vue/test-utils"; import { getMountOptions } from "@/helpers/unit-test-helper.js"; +import { packageNames } from "@/constants/package-names"; + 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 = { logInformation: jest.fn(), logWarning: jest.fn(), @@ -354,9 +370,73 @@ describe("cart.vue", () => { 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({ ...options, }); @@ -365,7 +445,9 @@ function setupMocks({ options, props }) { methods: { getTierOnePackagePrice: jest.fn(), filterOutFees: jest.fn(), - getCmsContent: jest.fn(), + getCmsContent: jest.fn((widgetName, cmsFieldName) => { + return cmsContent?.[widgetName]?.[cmsFieldName]; + }), }, }; diff --git a/src/fmg-components/cart/cart.vue b/src/fmg-components/cart/cart.vue index 5d48dc5ae..1bbfb0b76 100644 --- a/src/fmg-components/cart/cart.vue +++ b/src/fmg-components/cart/cart.vue @@ -34,7 +34,9 @@
- + {{ getLineItemAmount(packagePrice) }} @@ -193,6 +195,7 @@ import { formatToUSDollar } from "@/helpers/cms-content-helper"; // Constants import { partTypeStrings } from "@/constants/part-type-strings"; +import { packageNames } from "@/constants/package-names"; import { cartItemCategories } from "@/constants/cart-item-categories"; import { cartItemTypes } from "@/constants/cart-item-types"; import { coverageStatus, cartItemTypesCoveredByInsurance } from "@/constants/insurance"; @@ -267,6 +270,24 @@ export default { 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() { const quotePageDiscountLineItems = this.quotePageDiscountCartItem; let subTotal = 0; @@ -547,6 +568,9 @@ export default { return currentPackage.SubWidgetName; }, + servicePackageTitleText() { + return this.getExperimentPackageLabel(this.packageLevel); + }, packageLevel() { const tier = getHighestFullySatisfiedTier( this.glassToReplace, From 8fcd2c7f527f6354e3dc1dfe27b1b55fe365779c Mon Sep 17 00:00:00 2001 From: Minojhini Valaiyapathi Date: Thu, 9 Jul 2026 14:45:27 -0400 Subject: [PATCH 10/23] CASH-2981 - Added ProblemQuestionId --- .../question-chain/question-chain.spec.js | 62 ++++++++-- .../question-chain/question-chain.vue | 23 ++++ .../part-questions/part-questions.spec.js | 1 + src/layouts/part-questions/part-questions.vue | 1 + src/mixins/vehicle-questions-mixin.js | 4 + src/store/index.js | 10 +- src/store/store.spec.js | 111 ++++++++++++++++++ 7 files changed, 198 insertions(+), 14 deletions(-) diff --git a/src/digital-components/question-chain/question-chain.spec.js b/src/digital-components/question-chain/question-chain.spec.js index d516afc03..7435feefd 100644 --- a/src/digital-components/question-chain/question-chain.spec.js +++ b/src/digital-components/question-chain/question-chain.spec.js @@ -244,10 +244,8 @@ describe("Question Chain component", () => { test("should return answer object if returnedAnswer is a final matching answer", async () => { //Arrange - const { wrapper } = setupMocks({}); - const testReturnedAnswer = "1|answer|DB10840|No"; - await wrapper.setData({ - questions: [ + const { wrapper } = setupMocks({ + questionDataProp: [ { questionSequence: 1, questionText: "Question here?", @@ -256,17 +254,21 @@ describe("Question Chain component", () => { answerResult: "DB09410", answerText: "Yes", nextQuestionSequence: null, + problemQuestionId: 9529, }, { answerResult: "DB10840", answerText: "No", nextQuestionSequence: null, + problemQuestionId: 9531, }, ], }, ], }); + const testReturnedAnswer = "1|answer|DB10840|No"; await wrapper.setProps({ index: 0 }); + await nextTick(); //Act const result = wrapper.vm.getQuestionChainAnswerIfComplete(testReturnedAnswer); @@ -274,16 +276,52 @@ describe("Question Chain component", () => { //Assert expect(result).toMatchObject({ answerResult: "DB10840", - answeredQuestions: [ - { questionNum: 1, questionText: "Question here?", selectedAnswerText: "No" }, - { - questionNum: 1, - questionText: "Does only your center sliding piece need to be replaced?", - selectedAnswerText: "No", - }, - ], + problemQuestionId: 9531, 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); }); }); }); diff --git a/src/digital-components/question-chain/question-chain.vue b/src/digital-components/question-chain/question-chain.vue index 18b123aae..b1960eb51 100644 --- a/src/digital-components/question-chain/question-chain.vue +++ b/src/digital-components/question-chain/question-chain.vue @@ -60,6 +60,7 @@ export default { : q.questionSequence + "|answer|" + a.answerResult + "|" + a.answerText, nextQuestionSequence: a.nextQuestionSequence, answerResult: a.answerResult, + problemQuestionId: a.problemQuestionId, questionSequence: q.questionSequence, questionType: a.nextQuestionSequence ? "nextQuestion" : "answer", }; @@ -81,6 +82,17 @@ export default { } }, 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) { /* returnedAnswer example format: @@ -126,6 +138,10 @@ export default { selectedAnswer: q.answerSelected, selectedAnswerText: q.answerSelected.split("|")[3], questionNum: q.questionSequence, + problemQuestionId: this.getProblemQuestionIdFromSelectedAnswer( + q, + q.answerSelected + ), }); } }); @@ -144,10 +160,17 @@ export default { } else { // reset current question index (removes .current-question class) 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 { answerResult: questionAnswer, + problemQuestionId: this.getProblemQuestionIdFromSelectedAnswer( + answeredQuestion, + returnedAnswer + ), answeredQuestions: answeredQuestions, index: this.index, }; diff --git a/src/layouts/part-questions/part-questions.spec.js b/src/layouts/part-questions/part-questions.spec.js index 4dcbb669c..4e7cb53dd 100644 --- a/src/layouts/part-questions/part-questions.spec.js +++ b/src/layouts/part-questions/part-questions.spec.js @@ -320,6 +320,7 @@ describe("partQuestions.vue...", () => { glassName: "Single", isSuppressedPart: undefined, result: "FW04848", + problemQuestionId: null, }, ], false diff --git a/src/layouts/part-questions/part-questions.vue b/src/layouts/part-questions/part-questions.vue index 958a38095..1c342543f 100644 --- a/src/layouts/part-questions/part-questions.vue +++ b/src/layouts/part-questions/part-questions.vue @@ -176,6 +176,7 @@ export default { glassLocation: glass.glassLocation, glassName: glass.glassName, result: (glass.answerData && glass.answerData.answerResult) || "", + problemQuestionId: glass.answerData?.problemQuestionId ?? null, answeredQuestions: glass.answerData?.answeredQuestions, isSuppressedPart: glass.isSuppressedPart, }; diff --git a/src/mixins/vehicle-questions-mixin.js b/src/mixins/vehicle-questions-mixin.js index 2f64df7b0..1636f0c47 100644 --- a/src/mixins/vehicle-questions-mixin.js +++ b/src/mixins/vehicle-questions-mixin.js @@ -141,6 +141,7 @@ export default { // add answerData to current glass glass.answerData = { answerResult: answerResult, + problemQuestionId: answeredGlass.problemQuestionId ?? null, answeredQuestions: answeredGlass.answeredQuestions, }; } @@ -320,6 +321,7 @@ export default { questionText: question.questionText, selectedAnswerText: matchedAnswer.answerText, questionNum: question.questionSequence, + problemQuestionId: matchedAnswer.problemQuestionId ?? null, suppressThisQuestion: question.suppressThisQuestion, }; // set the answerData (used as indicator that it has been already answered) @@ -327,6 +329,7 @@ export default { answerResult: matchedAnswer.nextQuestionSequence ? matchedAnswer.nextQuestionSequence : matchedAnswer.answerResult, + problemQuestionId: matchedAnswer.problemQuestionId ?? null, answeredQuestions: [answeredQuestionObj], }; @@ -346,6 +349,7 @@ export default { // set final answer data for the current answered glass part self.questionsData[answer.index].answerData = { answerResult: answer.answerResult, + problemQuestionId: answer.problemQuestionId ?? null, answeredQuestions: answer.answeredQuestions, }; diff --git a/src/store/index.js b/src/store/index.js index 2fff59ee3..d913ddb06 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -4317,11 +4317,17 @@ function convertResultsForApi(resultsArray) { if (!resultsArray) return []; const converted = []; resultsArray.forEach((answer) => { - converted.push({ + const convertedAnswer = { location: answer.glassLocation, name: answer.glassName, result: answer.result, - }); + }; + + if (answer.problemQuestionId != null) { + convertedAnswer.problemQuestionId = answer.problemQuestionId; + } + + converted.push(convertedAnswer); }); return converted; } diff --git a/src/store/store.spec.js b/src/store/store.spec.js index 1753ed621..2fae32003 100644 --- a/src/store/store.spec.js +++ b/src/store/store.spec.js @@ -4470,4 +4470,115 @@ describe("isVinOptionalVehicle", () => { 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", + }, + ], + }), + }) + ); + }); }); From 06880203e8701f9ba63c8657577ee5f0b35a1fc7 Mon Sep 17 00:00:00 2001 From: Carl Nation Date: Thu, 9 Jul 2026 14:56:31 -0400 Subject: [PATCH 11/23] CASH-3022 save/load phone extension for C&C CASH-3022 save/load phone extension for C&C --- src/layouts/policy-info/policy-info.vue | 10 +++++----- src/store/index.js | 13 +++++++------ 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/layouts/policy-info/policy-info.vue b/src/layouts/policy-info/policy-info.vue index 57b9b4e5e..d35292bb1 100644 --- a/src/layouts/policy-info/policy-info.vue +++ b/src/layouts/policy-info/policy-info.vue @@ -511,13 +511,13 @@ export default { return store.getters.order.policy.policyNumber; }, getPolicyZipFromStore() { - return store.getters.order.policy.zipCode; + return store.getters.order.customer.address.zipCode; }, getPhoneNumberFromStore() { - return store.getters.order.policy.phoneNumber; + return store.getters.order.customer.phoneNumber; }, getPhoneExtensionFromStore() { - return store.getters.order.policy.phoneExtension; + return store.getters.order.customer.phoneExtension; }, getDateOfLossFromStore() { return store.getters.order.damage.dateOfLoss; @@ -539,10 +539,10 @@ export default { return match ?? value; }, getPolicyStateFromStore() { - return store.getters.order.policy.state; + return store.getters.order.customer.address.state; }, getCityFromStore() { - return store.getters.order.policy.city; + return store.getters.order.customer.address.city; }, getDateOfBirthFromStore() { return store.getters.order.policy.dateOfBirth; diff --git a/src/store/index.js b/src/store/index.js index 2fff59ee3..acf7b8346 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -112,6 +112,7 @@ const getDefaultState = () => { lastName: null, emailAddress: null, phoneNumber: null, + phoneExtension: null, isSmsOptIn: null, waitListRequested: null, address: { @@ -184,8 +185,6 @@ const getDefaultState = () => { additionalAuthFlag: null, isNoComp: false, insuranceCompanyName: null, - phoneNumber: null, - phoneExtension: null, dateOfBirth: null, lossTime: null, lossLocation: null, @@ -358,10 +357,10 @@ export const mutations = { state.order.policy.insuranceCompanyName = accountName; }, updatePhoneNumber(state, phoneNumber) { - state.order.policy.phoneNumber = phoneNumber; + state.order.customer.phoneNumber = phoneNumber; }, updatePhoneExtension(state, phoneExtension) { - state.order.policy.phoneExtension = phoneExtension; + state.order.customer.phoneExtension = phoneExtension; }, updateStreetAddress(state, streetAddress) { state.order.customer.address.streetAddress = streetAddress; @@ -580,9 +579,9 @@ export const mutations = { state.order.customer.address.city = insuranceDetails.city; state.order.customer.address.state = insuranceDetails.state; state.order.customer.address.zipCode = insuranceDetails.zipCode; + state.order.customer.phoneNumber = insuranceDetails.phoneNumber; + state.order.customer.phoneExtension = insuranceDetails.phoneExtension; state.order.payment.insuranceCoverage.claimNumber = insuranceDetails.claimNumber; - state.order.policy.phoneNumber = insuranceDetails.phoneNumber; - state.order.policy.phoneExtension = insuranceDetails.phoneExtension; state.order.policy.morePolicyQuestions = insuranceDetails.morePolicyQuestions; state.order.policy.dateOfBirth = insuranceDetails.dateOfBirth; state.order.policy.lossTime = insuranceDetails.lossTime; @@ -965,6 +964,7 @@ export const mutations = { state.order.customer.firstName = sessionInformation.order.customer.firstName; state.order.customer.lastName = sessionInformation.order.customer.lastName; state.order.customer.phoneNumber = sessionInformation.order.customer.homePhone; + state.order.customer.phoneExtension = sessionInformation.order.customer.phoneExt; state.order.customer.isSmsOptIn = sessionInformation.order.customer.isSmsOptIn; if (sessionInformation.order.customer.address) { @@ -2822,6 +2822,7 @@ export const actions = { lastName: order.customer.lastName, isSmsOptIn: order.customer.isSmsOptIn, phoneNumber: order.customer.phoneNumber, + phoneExt: order.customer.phoneExtension, address: { streetAddress: order.customer.address.streetAddress, streetAddress2: order.customer.address.streetAddress2, From df80ede6233343c5161a5e3c1cb703c3f6d3ce9a Mon Sep 17 00:00:00 2001 From: credelinghuys Date: Thu, 9 Jul 2026 15:13:12 -0400 Subject: [PATCH 12/23] CASH-3025: Consent management changes --- src/constants/error-messages.js | 2 +- .../save-progress-popup-question.spec.js | 43 +++++++++++++------ .../save-progress-popup-question.vue | 2 +- ...rogress-popup-sms-consent-question.spec.js | 11 ++--- ...ve-progress-popup-sms-consent-question.vue | 5 ++- 5 files changed, 38 insertions(+), 25 deletions(-) diff --git a/src/constants/error-messages.js b/src/constants/error-messages.js index 2f49e2f1b..380a5a98c 100644 --- a/src/constants/error-messages.js +++ b/src/constants/error-messages.js @@ -32,7 +32,7 @@ const errorMessages = { DATE_REQUIRED: "Please select a date", PHONE_REQUIRED: "Please enter your phone number", 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", MAKE_REQUIRED: "Please select your vehicle make", MODEL_REQUIRED: "Please select your vehicle model", diff --git a/src/fmg-components/save-progress-popup-question/save-progress-popup-question.spec.js b/src/fmg-components/save-progress-popup-question/save-progress-popup-question.spec.js index 9979a4162..3db98487a 100644 --- a/src/fmg-components/save-progress-popup-question/save-progress-popup-question.spec.js +++ b/src/fmg-components/save-progress-popup-question/save-progress-popup-question.spec.js @@ -45,14 +45,14 @@ describe("save-progress-popup-question ", () => { }); describe("contact method tabs", () => { - test("should default to the phone tab", () => { + test("should default to the email tab", () => { const { wrapper } = setupMocks({ props: { 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", () => { @@ -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(); - 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({ props: { modalWidgetName: "SaveProgressPopupWidget", }, }); + wrapper.vm.selectContactMethod("PhoneAnswer"); + await wrapper.vm.$nextTick(); + 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.isPhoneTabSelected).toBe(false); - expect(wrapper.vm.userInput).toBe(""); 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 () => { const { wrapper } = setupMocks({ props: { @@ -126,7 +140,7 @@ describe("save-progress-popup-question ", () => { wrapper.vm.smsConsent = { transactional: true, marketing: true }; wrapper.vm.showConsentErrors = true; - wrapper.vm.selectContactMethod("EmailAnswer"); + wrapper.vm.selectContactMethod("PhoneAnswer"); await wrapper.vm.$nextTick(); 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 }); 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 }); const resetSpy = jest.spyOn(wrapper.vm, "resetPhoneSendButtonStyle"); @@ -179,6 +195,7 @@ describe("save-progress-popup-question ", () => { await flushPromises(); wrapper.vm.dispatchStoreAction = dispatchStoreAction; + wrapper.vm.selectContactMethod("PhoneAnswer"); wrapper.vm.userInput = "555-123-4567"; wrapper.vm.smsConsent = { transactional: true, marketing: false }; wrapper.vm.modal.validate = jest.fn().mockResolvedValue({ valid: true }); @@ -203,6 +220,7 @@ describe("save-progress-popup-question ", () => { }); wrapper.vm.dispatchStoreAction = dispatchStoreAction; + wrapper.vm.selectContactMethod("PhoneAnswer"); wrapper.vm.userInput = "555-123-4567"; wrapper.vm.smsConsent = { transactional: true, marketing: false }; @@ -238,7 +256,6 @@ describe("save-progress-popup-question ", () => { }); wrapper.vm.dispatchStoreAction = dispatchStoreAction; - wrapper.vm.selectContactMethod("EmailAnswer"); wrapper.vm.userInput = "test@example.com"; await wrapper.vm.saveProgress(); @@ -267,6 +284,7 @@ describe("save-progress-popup-question ", () => { }); wrapper.vm.dispatchStoreAction = dispatchStoreAction; + wrapper.vm.selectContactMethod("PhoneAnswer"); wrapper.vm.userInput = "555-123-4567"; wrapper.vm.smsConsent = { transactional: true, marketing: false }; @@ -289,7 +307,6 @@ describe("save-progress-popup-question ", () => { }); wrapper.vm.dispatchStoreAction = dispatchStoreAction; - wrapper.vm.selectContactMethod("EmailAnswer"); wrapper.vm.userInput = "test@example.com"; await wrapper.vm.saveProgress(); diff --git a/src/fmg-components/save-progress-popup-question/save-progress-popup-question.vue b/src/fmg-components/save-progress-popup-question/save-progress-popup-question.vue index 95fb40273..2d46b03b9 100644 --- a/src/fmg-components/save-progress-popup-question/save-progress-popup-question.vue +++ b/src/fmg-components/save-progress-popup-question/save-progress-popup-question.vue @@ -118,7 +118,7 @@ export default { data() { return { userInput: "", - contactMethod: saveProgressPopupContactMethods.PHONE, + contactMethod: saveProgressPopupContactMethods.EMAIL, smsConsent: defaultSaveProgressSmsConsent(), smsConsentCopy: getSaveProgressSmsConsentFallbackCopy(), isProgressSaved: false, diff --git a/src/fmg-components/save-progress-popup-question/save-progress-popup-sms-consent-question.spec.js b/src/fmg-components/save-progress-popup-question/save-progress-popup-sms-consent-question.spec.js index 4a9fe13a1..fd6bea0ff 100644 --- a/src/fmg-components/save-progress-popup-question/save-progress-popup-sms-consent-question.spec.js +++ b/src/fmg-components/save-progress-popup-question/save-progress-popup-sms-consent-question.spec.js @@ -7,15 +7,14 @@ import { import { errorMessages } from "@/constants/error-messages"; 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 fallbackCopy = getSaveProgressSmsConsentFallbackCopy(); 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(1).props("labelText")).toBe(fallbackCopy.marketing); }); 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" }); + expect(checkboxes).toHaveLength(1); 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", () => { @@ -61,7 +60,6 @@ describe("save-progress-popup-sms-consent-question", () => { const checkboxes = wrapper.findAllComponents({ name: "checkboxQuestion" }); expect(checkboxes.at(0).props("isDisabled")).toBe(true); - expect(checkboxes.at(1).props("isDisabled")).toBe(true); expect(wrapper.find("fieldset").attributes("disabled")).toBe(""); }); @@ -76,7 +74,6 @@ describe("save-progress-popup-sms-consent-question", () => { const checkboxes = wrapper.findAllComponents({ name: "checkboxQuestion" }); 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); }); @@ -94,7 +91,6 @@ describe("save-progress-popup-sms-consent-question", () => { const checkboxes = wrapper.findAllComponents({ name: "checkboxQuestion" }); 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 () => { @@ -111,6 +107,5 @@ describe("save-progress-popup-sms-consent-question", () => { const checkboxes = wrapper.findAllComponents({ name: "checkboxQuestion" }); expect(checkboxes.at(0).props("hasError")).toBe(false); - expect(checkboxes.at(1).props("hasError")).toBe(false); }); }); diff --git a/src/fmg-components/save-progress-popup-question/save-progress-popup-sms-consent-question.vue b/src/fmg-components/save-progress-popup-question/save-progress-popup-sms-consent-question.vue index 55e18e623..683541895 100644 --- a/src/fmg-components/save-progress-popup-question/save-progress-popup-sms-consent-question.vue +++ b/src/fmg-components/save-progress-popup-question/save-progress-popup-sms-consent-question.vue @@ -9,12 +9,13 @@ :labelText="consentCopy.transactional" :hasError="showConsentError" :isDisabled="isDisabled" /> - + :isDisabled="isDisabled" /> --> Date: Fri, 10 Jul 2026 08:58:32 -0400 Subject: [PATCH 13/23] CASH-2934: Allow multiple success events. Added additional event for phone number. --- src/global-methods.js | 38 ++++++++++++++++++++++++++++---------- src/global-methods.spec.js | 27 +++++++++++++++++++++++++++ src/store/index.js | 4 +++- 3 files changed, 58 insertions(+), 11 deletions(-) diff --git a/src/global-methods.js b/src/global-methods.js index 240c80d82..5cdc2f3ea 100644 --- a/src/global-methods.js +++ b/src/global-methods.js @@ -87,18 +87,36 @@ export default { }).then( (response) => { if (logApiCall) { - let additionalEventData = ""; - if (additionalSuccessEventDataHandler) { - additionalEventData = "_" + additionalSuccessEventDataHandler(response); - } const endpointWithoutParams = analyticsMixIn.methods.removeParamsFromEndpoint(endpoint); - analyticsMixIn.methods.pushEventToGA( - GaCategories.API_RESPONSE, - `${pageNameToLog}_${endpointWithoutParams}`, - `${GaLabels.SUCCESS}${additionalEventData}`, - true - ); + const gaAction = `${pageNameToLog}_${endpointWithoutParams}`; + + if (additionalSuccessEventDataHandler) { + const handlerResult = additionalSuccessEventDataHandler(response); + 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); diff --git a/src/global-methods.spec.js b/src/global-methods.spec.js index c8c4d6da9..65bd675be 100644 --- a/src/global-methods.spec.js +++ b/src/global-methods.spec.js @@ -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", () => { //Arrange const endpoint = "https://mock.safelite.com"; diff --git a/src/store/index.js b/src/store/index.js index d913ddb06..95ce9a8d7 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -2940,8 +2940,10 @@ export const actions = { }, logApiCall: true, pageNameToLog: pageNameToLog, - additionalSuccessEventDataHandler: (response) => + additionalSuccessEventDataHandler: (response) => [ "Email provided: " + (order.customer.emailAddress ? "true" : "false"), + "Phone provided: " + (order.customer.phoneNumber ? "true" : "false"), + ], }); }, From b947eb5c5f8ba98101b406adde69795abf08f86c Mon Sep 17 00:00:00 2001 From: kpatel8hs4io <31411746+kpatel8hs4io@users.noreply.github.com> Date: Fri, 10 Jul 2026 13:09:23 -0400 Subject: [PATCH 14/23] minor changes for playwright tests --- playwright-tests/pages/BailoutSuccessPage.ts | 2 +- playwright-tests/pages/PolicyInfoPage.ts | 2 +- playwright-tests/tests/0000__M.test.ts | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/playwright-tests/pages/BailoutSuccessPage.ts b/playwright-tests/pages/BailoutSuccessPage.ts index 603172bd3..c0ed1eb43 100644 --- a/playwright-tests/pages/BailoutSuccessPage.ts +++ b/playwright-tests/pages/BailoutSuccessPage.ts @@ -20,7 +20,7 @@ export class BailoutSuccessPage extends BasePage { await expect(this.thankYouHeading).toBeVisible(); await expect(this.bodyText).toBeVisible(); 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(); } diff --git a/playwright-tests/pages/PolicyInfoPage.ts b/playwright-tests/pages/PolicyInfoPage.ts index 04f35c346..d65e0d7c1 100644 --- a/playwright-tests/pages/PolicyInfoPage.ts +++ b/playwright-tests/pages/PolicyInfoPage.ts @@ -12,7 +12,7 @@ export class PolicyInfoPage extends BasePage { } @step("PolicyInfoPage >> Continue") - async handlePolicyInfoPage(testData: Partial): Promise { + async handleNewPolicyInfoPage(testData: Partial): Promise { await expect(this.page).toHaveURL(/\/fmg\/policy-info/); throw new TestSuccessAlert('Policy info page validated successfully.'); diff --git a/playwright-tests/tests/0000__M.test.ts b/playwright-tests/tests/0000__M.test.ts index 88e4101f5..a0670130b 100644 --- a/playwright-tests/tests/0000__M.test.ts +++ b/playwright-tests/tests/0000__M.test.ts @@ -380,14 +380,12 @@ async function handleInsuranceFlow(testCase: TestCase) { await HeritageProblemGlassQuestionsPage.handleHeritageProblemGlassQuestionsPage(testCase.testData); } - // Liberty Mutual route directly to PolicyInfoPage instead of CCPolicyInfoPage if (isNewInsuranceFlow) { let policyInfoPage = testCase.pages.policyInfoPage; - await policyInfoPage.handlePolicyInfoPage(testCase.testData); - return; - } - - // Handle ccPolicyInfoPage + await policyInfoPage.handleNewPolicyInfoPage(testCase.testData); + } else { + + // Handle ccPolicyInfoPage let ccPolicyInfoPage = testCase.pages.ccPolicyInfoPage; await ccPolicyInfoPage.handleCCPolicyInfoPage(testCase.testData); @@ -431,6 +429,8 @@ async function handleInsuranceFlow(testCase: TestCase) { let coverageStatementPage = testCase.pages.coverageStatementPage; await coverageStatementPage.handleCoverageStatementPage(testCase.testData); + } + // 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 if (isCashInsuranceFlow) { From f51c3ab305c407e0006efdca586aa24658d423cc Mon Sep 17 00:00:00 2001 From: scottkiener-at-safelite Date: Mon, 13 Jul 2026 07:57:24 -0400 Subject: [PATCH 15/23] CASH-3029 | Afterpay copy change --- .../quote/afterpay-modal-banner/afterpay-modal-banner.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/layouts/quote/afterpay-modal-banner/afterpay-modal-banner.vue b/src/layouts/quote/afterpay-modal-banner/afterpay-modal-banner.vue index 98c6844b8..a1b92c90d 100644 --- a/src/layouts/quote/afterpay-modal-banner/afterpay-modal-banner.vue +++ b/src/layouts/quote/afterpay-modal-banner/afterpay-modal-banner.vue @@ -15,8 +15,8 @@ {{ getInlineAltText(token) }} -   + Date: Mon, 13 Jul 2026 07:58:40 -0400 Subject: [PATCH 16/23] Formatting --- .../quote/afterpay-modal-banner/afterpay-modal-banner.vue | 1 - 1 file changed, 1 deletion(-) diff --git a/src/layouts/quote/afterpay-modal-banner/afterpay-modal-banner.vue b/src/layouts/quote/afterpay-modal-banner/afterpay-modal-banner.vue index a1b92c90d..cc708dc6a 100644 --- a/src/layouts/quote/afterpay-modal-banner/afterpay-modal-banner.vue +++ b/src/layouts/quote/afterpay-modal-banner/afterpay-modal-banner.vue @@ -197,7 +197,6 @@ export default { } @include media-breakpoint-up(md) { padding-left: 1rem; - } } } From 0d6b42f4b4309827dad936c98ac2505694a4e1e3 Mon Sep 17 00:00:00 2001 From: credelinghuys Date: Mon, 13 Jul 2026 10:42:32 -0400 Subject: [PATCH 17/23] CASH-3027: Add consent management feature toggle --- src/constants/error-messages.js | 3 +- src/constants/experiments.js | 3 ++ .../save-progress-popup-question.vue | 10 +++-- ...rogress-popup-sms-consent-question.spec.js | 41 +++++++++++++++++-- ...ve-progress-popup-sms-consent-question.vue | 24 ++++++----- .../save-progress-sms-consent-helper.js | 4 +- .../save-progress-sms-consent-helper.spec.js | 12 ++++-- src/layouts/quote/quote.vue | 7 ++++ 8 files changed, 82 insertions(+), 22 deletions(-) diff --git a/src/constants/error-messages.js b/src/constants/error-messages.js index 380a5a98c..3356787a1 100644 --- a/src/constants/error-messages.js +++ b/src/constants/error-messages.js @@ -32,7 +32,8 @@ const errorMessages = { DATE_REQUIRED: "Please select a date", PHONE_REQUIRED: "Please enter your phone number", PHONE_FORMAT: "Phone number must be 10 digits", - SMS_CONSENT_REQUIRED: "Please select checkbox to receive text messages", + SMS_CONSENT_REQUIRED_1: "Please select checkbox to receive text messages", + SMS_CONSENT_REQUIRED_2: "Please select at least one consent option", YEAR_REQUIRED: "Please select your vehicle year", MAKE_REQUIRED: "Please select your vehicle make", MODEL_REQUIRED: "Please select your vehicle model", diff --git a/src/constants/experiments.js b/src/constants/experiments.js index 1cd1260a7..dcbaf1912 100644 --- a/src/constants/experiments.js +++ b/src/constants/experiments.js @@ -58,6 +58,9 @@ const experimentSettings = { TIER_ONE: "TierOne", TIER_TWO: "TierTwo", TIER_THREE: "TierThree", + + // Consent Management + SHOW_CONSENT_MANAGEMENT: "ShowConsentManagement", }; const experimentTriggers = { diff --git a/src/fmg-components/save-progress-popup-question/save-progress-popup-question.vue b/src/fmg-components/save-progress-popup-question/save-progress-popup-question.vue index 2d46b03b9..a226c0522 100644 --- a/src/fmg-components/save-progress-popup-question/save-progress-popup-question.vue +++ b/src/fmg-components/save-progress-popup-question/save-progress-popup-question.vue @@ -46,7 +46,8 @@ v-model="smsConsent" :consentCopy="smsConsentCopy" :isDisabled="isProgressSaved" - :showConsentErrors="showConsentErrors" /> + :showConsentErrors="showConsentErrors" + :showConsentManagement="showConsentManagement" />
{ expect(checkboxes.at(0).props("labelText")).toBe(fallbackCopy.transactional); }); + it("should render the marketing consent checkbox when consent management is enabled", () => { + const fallbackCopy = getSaveProgressSmsConsentFallbackCopy(); + const wrapper = shallowMount(saveProgressPopupSmsConsentQuestion, { + propsData: { + showConsentManagement: true, + }, + }); + + const checkboxes = wrapper.findAllComponents({ name: "checkboxQuestion" }); + + expect(checkboxes).toHaveLength(2); + expect(checkboxes.at(1).props("labelText")).toBe(fallbackCopy.marketing); + }); + it("should emit updated consent when a checkbox changes", async () => { const wrapper = shallowMount(saveProgressPopupSmsConsentQuestion, { propsData: { @@ -74,20 +88,41 @@ describe("save-progress-popup-sms-consent-question", () => { const checkboxes = wrapper.findAllComponents({ name: "checkboxQuestion" }); expect(checkboxes.at(0).props("hasError")).toBe(false); - expect(wrapper.text()).not.toContain(errorMessages.SMS_CONSENT_REQUIRED); + expect(wrapper.text()).not.toContain(errorMessages.SMS_CONSENT_REQUIRED_1); + expect(wrapper.text()).not.toContain(errorMessages.SMS_CONSENT_REQUIRED_2); }); - it("should show consent errors when showConsentErrors is true and nothing is selected", async () => { + it("should show the transactional consent error when consent management is disabled", async () => { const wrapper = shallowMount(saveProgressPopupSmsConsentQuestion, { propsData: { modelValue: defaultSaveProgressSmsConsent(), showConsentErrors: false, + showConsentManagement: false, }, }); await wrapper.setProps({ showConsentErrors: true }); - expect(wrapper.text()).toContain(errorMessages.SMS_CONSENT_REQUIRED); + expect(wrapper.text()).toContain(errorMessages.SMS_CONSENT_REQUIRED_1); + expect(wrapper.text()).not.toContain(errorMessages.SMS_CONSENT_REQUIRED_2); + + const checkboxes = wrapper.findAllComponents({ name: "checkboxQuestion" }); + expect(checkboxes.at(0).props("hasError")).toBe(true); + }); + + it("should show the multi-consent error when consent management is enabled", async () => { + const wrapper = shallowMount(saveProgressPopupSmsConsentQuestion, { + propsData: { + modelValue: defaultSaveProgressSmsConsent(), + showConsentErrors: false, + showConsentManagement: true, + }, + }); + + await wrapper.setProps({ showConsentErrors: true }); + + expect(wrapper.text()).toContain(errorMessages.SMS_CONSENT_REQUIRED_2); + expect(wrapper.text()).not.toContain(errorMessages.SMS_CONSENT_REQUIRED_1); const checkboxes = wrapper.findAllComponents({ name: "checkboxQuestion" }); expect(checkboxes.at(0).props("hasError")).toBe(true); diff --git a/src/fmg-components/save-progress-popup-question/save-progress-popup-sms-consent-question.vue b/src/fmg-components/save-progress-popup-question/save-progress-popup-sms-consent-question.vue index 683541895..e19e35171 100644 --- a/src/fmg-components/save-progress-popup-question/save-progress-popup-sms-consent-question.vue +++ b/src/fmg-components/save-progress-popup-question/save-progress-popup-sms-consent-question.vue @@ -9,19 +9,19 @@ :labelText="consentCopy.transactional" :hasError="showConsentError" :isDisabled="isDisabled" /> - + :isDisabled="isDisabled" /> - {{ errorMessages.SMS_CONSENT_REQUIRED }} + {{ consentErrorMessage }} @@ -37,11 +37,6 @@ import { export default { name: "save-progress-popup-sms-consent-question", - data() { - return { - errorMessages, - }; - }, props: { modelValue: { type: Object, @@ -53,6 +48,7 @@ export default { }, isDisabled: Boolean, showConsentErrors: Boolean, + showConsentManagement: Boolean, }, computed: { transactionalConsent: { @@ -72,7 +68,15 @@ export default { }, }, showConsentError() { - return this.showConsentErrors && !hasSaveProgressSmsConsentSelection(this.modelValue); + return ( + this.showConsentErrors && + !hasSaveProgressSmsConsentSelection(this.modelValue, this.showConsentManagement) + ); + }, + consentErrorMessage() { + return this.showConsentManagement + ? errorMessages.SMS_CONSENT_REQUIRED_2 + : errorMessages.SMS_CONSENT_REQUIRED_1; }, }, methods: { diff --git a/src/helpers/save-progress-sms-consent/save-progress-sms-consent-helper.js b/src/helpers/save-progress-sms-consent/save-progress-sms-consent-helper.js index 6c29495a9..2269c86ff 100644 --- a/src/helpers/save-progress-sms-consent/save-progress-sms-consent-helper.js +++ b/src/helpers/save-progress-sms-consent/save-progress-sms-consent-helper.js @@ -14,8 +14,8 @@ export function getSaveProgressSmsConsentFallbackCopy() { return { ...SAVE_PROGRESS_SMS_CONSENT_FALLBACK_COPY }; } -export function hasSaveProgressSmsConsentSelection(consent) { - return Boolean(consent?.transactional || consent?.marketing); +export function hasSaveProgressSmsConsentSelection(consent, showConsentManagement = false) { + return Boolean(consent?.transactional || (showConsentManagement && consent?.marketing)); } export async function getSaveProgressSmsConsentConfig() { diff --git a/src/helpers/save-progress-sms-consent/save-progress-sms-consent-helper.spec.js b/src/helpers/save-progress-sms-consent/save-progress-sms-consent-helper.spec.js index 7c05fa866..bcc9ae617 100644 --- a/src/helpers/save-progress-sms-consent/save-progress-sms-consent-helper.spec.js +++ b/src/helpers/save-progress-sms-consent/save-progress-sms-consent-helper.spec.js @@ -20,13 +20,19 @@ describe("save-progress-sms-consent-helper", () => { expect(hasSaveProgressSmsConsentSelection(defaultSaveProgressSmsConsent())).toBe(false); }); - it("should return true when either consent option is selected", () => { + it("should return true when either consent option is selected and consent management is enabled", () => { expect( - hasSaveProgressSmsConsentSelection({ transactional: true, marketing: false }) + hasSaveProgressSmsConsentSelection({ transactional: true, marketing: false }, true) ).toBe(true); expect( - hasSaveProgressSmsConsentSelection({ transactional: false, marketing: true }) + hasSaveProgressSmsConsentSelection({ transactional: false, marketing: true }, true) ).toBe(true); }); + + it("should ignore marketing consent when consent management is disabled", () => { + expect( + hasSaveProgressSmsConsentSelection({ transactional: false, marketing: true }, false) + ).toBe(false); + }); }); }); diff --git a/src/layouts/quote/quote.vue b/src/layouts/quote/quote.vue index cad68ef0f..50512bcbf 100644 --- a/src/layouts/quote/quote.vue +++ b/src/layouts/quote/quote.vue @@ -114,6 +114,7 @@ pageName="quote" v-if="showSaveProgressPopup" :showSaveProgressPopup="showSaveProgressPopup" + :showConsentManagement="showConsentManagement" @save-progress-saved="updateSaveProgressAsSaved" @close-save-progress-popup="closeSaveProgressPopup" /> @@ -693,6 +694,12 @@ export default { showRecalDisclaimer() { return this.isRecalibrationOnOrder && this.isRecalPriceRemove; }, + showConsentManagement() { + return experimentMixin.methods.hasSettingEqualTo( + experimentSettings.SHOW_CONSENT_MANAGEMENT, + "true" + ); + }, afterpayExtendedPayOptionThreshold() { let thresholdAmount = experimentMixin.methods.getSettingValue( experimentSettings.AFTERPAY_EXTENDED_PAY_OPTION_THRESHOLD From 70c1c6f4b97f84b00642b1c864add8a32dabf62c Mon Sep 17 00:00:00 2001 From: Carl Nation Date: Tue, 14 Jul 2026 06:35:56 -0400 Subject: [PATCH 18/23] CASH-3036 add additional policy fields CASH-3036 add additional policy fields --- src/constants/error-messages.js | 1 + src/constants/more-policy-questions.js | 54 +++++++++ .../more-policy-questions.spec.js | 88 +++++++++++++++ .../more-policy-questions.vue | 21 +++- src/layouts/policy-info/policy-info.spec.js | 76 +++++++++++++ src/layouts/policy-info/policy-info.vue | 33 +++++- src/store/index.js | 11 +- src/store/store.spec.js | 106 ++++++++++++++++++ 8 files changed, 384 insertions(+), 6 deletions(-) create mode 100644 src/constants/more-policy-questions.js create mode 100644 src/layouts/policy-info/more-policy-questions/more-policy-questions.spec.js diff --git a/src/constants/error-messages.js b/src/constants/error-messages.js index 380a5a98c..bf6e23c41 100644 --- a/src/constants/error-messages.js +++ b/src/constants/error-messages.js @@ -42,6 +42,7 @@ const errorMessages = { INSURANCE_COMPANY_NAME_REQUIRED: "Please enter your insurance company name", POLICY_NUMBER_REQUIRED: "Please enter your policy number", POLICY_NUMBER_FORMAT: "Please enter a valid policy number", + CLAIM_NUMBER_REQUIRED: "Please enter your claim number", CLAIM_NUMBER_VALID: "Please enter a valid claim number", CLAIM_NUMBER_FORMAT: "Claim number must be 6 to 8 digits", DATE_OF_LOSS_REQUIRED: "Please enter your date of damage", diff --git a/src/constants/more-policy-questions.js b/src/constants/more-policy-questions.js new file mode 100644 index 000000000..7f7e51c3d --- /dev/null +++ b/src/constants/more-policy-questions.js @@ -0,0 +1,54 @@ +// CMS answer `Name` values that can appear in MorePolicyQuestionsWidget / +// MorePolicyQuestionsOverrideWidget's Answers array (CMS content is a superset +// across both widget variants). +export const morePolicyQuestionAnswers = { + ADDITIONAL_DAMAGE: "AdditionalDamage", + RENTAL: "Rental", + OTHER_RESPONSIBLE_PARTY: "OtherResponsibleParty", + THIRD_PARTY_VEHICLE: "ThirdPartyVehicle", + INJURIES: "Injuries", +}; + +// Maps each possible CMS answer Name to the boolean flag persisted in the store +// and sent to the save-session API, regardless of which widget variant (and +// therefore which subset of answers) is shown to the user. +export const morePolicyQuestionAnswerToFlag = { + [morePolicyQuestionAnswers.ADDITIONAL_DAMAGE]: "additionalDamage", + [morePolicyQuestionAnswers.RENTAL]: "rental", + [morePolicyQuestionAnswers.OTHER_RESPONSIBLE_PARTY]: "otherResponsibleParty", + [morePolicyQuestionAnswers.THIRD_PARTY_VEHICLE]: "thirdPartyVehicle", + [morePolicyQuestionAnswers.INJURIES]: "injuries", +}; + +export function getDefaultMorePolicyQuestions() { + return Object.values(morePolicyQuestionAnswerToFlag).reduce((flags, flagName) => { + flags[flagName] = false; + return flags; + }, {}); +} + +// Converts the array of selected CMS answer Names (as emitted by the +// checkbox-based buttonQuestion component) into the boolean-flag object shape +// that is persisted to the store and sent to the backend. +export function morePolicyQuestionAnswerNamesToFlags(selectedAnswerNames) { + const flags = getDefaultMorePolicyQuestions(); + for (const answerName of Array.isArray(selectedAnswerNames) ? selectedAnswerNames : []) { + const flagName = morePolicyQuestionAnswerToFlag[answerName]; + if (flagName) { + flags[flagName] = true; + } + } + return flags; +} + +// Reverses morePolicyQuestionAnswerNamesToFlags, so the checkbox-based +// buttonQuestion component (which expects an array of selected values) can be +// pre-populated from previously persisted flags. +export function morePolicyQuestionFlagsToAnswerNames(flags) { + if (!flags) { + return []; + } + return Object.entries(morePolicyQuestionAnswerToFlag) + .filter(([, flagName]) => flags[flagName]) + .map(([answerName]) => answerName); +} diff --git a/src/layouts/policy-info/more-policy-questions/more-policy-questions.spec.js b/src/layouts/policy-info/more-policy-questions/more-policy-questions.spec.js new file mode 100644 index 000000000..a6e1175b5 --- /dev/null +++ b/src/layouts/policy-info/more-policy-questions/more-policy-questions.spec.js @@ -0,0 +1,88 @@ +import { shallowMount } from "@vue/test-utils"; +import morePolicyQuestions from "./more-policy-questions"; + +const answersFromCms = [ + { Name: "AdditionalDamage", Text: "Additional damage occurred" }, + { Name: "Rental", Text: "The vehicle was a rental" }, + { Name: "OtherResponsibleParty", Text: "Another party is responsible" }, +]; + +const mockMixin = { + methods: { + getCmsContent: jest.fn().mockImplementation((widgetName, property) => { + if (property === "Answers") { + return answersFromCms; + } + return "Please select all that apply"; + }), + }, +}; + +function mountComponent(modelValue) { + return shallowMount(morePolicyQuestions, { + props: { + modelValue, + groupName: "MorePolicyQuestionsQuestion", + cmsWidgetName: "MorePolicyQuestionsWidget", + }, + mixins: [mockMixin], + }); +} + +describe("more-policy-questions.vue", () => { + it("converts previously selected boolean flags into the array of CMS answer Names for the checkbox group", () => { + // Act + const wrapper = mountComponent({ + additionalDamage: true, + rental: false, + otherResponsibleParty: true, + thirdPartyVehicle: false, + injuries: false, + }); + + // Assert + expect(wrapper.vm.selectedValues).toEqual(["AdditionalDamage", "OtherResponsibleParty"]); + }); + + it("treats a null/undefined modelValue as no answers selected", () => { + // Act + const wrapper = mountComponent(undefined); + + // Assert + expect(wrapper.vm.selectedValues).toEqual([]); + }); + + it("emits a full set of boolean flags reflecting which CMS answer Names were checked", () => { + // Arrange + const wrapper = mountComponent({}); + + // Act + wrapper.vm.selectedValues = ["Rental", "Injuries"]; + + // Assert + expect(wrapper.emitted("update:modelValue")[0][0]).toEqual({ + additionalDamage: false, + rental: true, + otherResponsibleParty: false, + thirdPartyVehicle: false, + injuries: true, + }); + }); + + it("emits every flag as false when no answers are checked", () => { + // Arrange + const wrapper = mountComponent({ rental: true }); + + // Act + wrapper.vm.selectedValues = []; + + // Assert + expect(wrapper.emitted("update:modelValue")[0][0]).toEqual({ + additionalDamage: false, + rental: false, + otherResponsibleParty: false, + thirdPartyVehicle: false, + injuries: false, + }); + }); +}); diff --git a/src/layouts/policy-info/more-policy-questions/more-policy-questions.vue b/src/layouts/policy-info/more-policy-questions/more-policy-questions.vue index 44c9d5fb9..1523062ac 100644 --- a/src/layouts/policy-info/more-policy-questions/more-policy-questions.vue +++ b/src/layouts/policy-info/more-policy-questions/more-policy-questions.vue @@ -13,11 +13,18 @@