Merge remote-tracking branch 'origin/develop' into feature/CASH-2815
This commit is contained in:
commit
352b1c71e7
44 changed files with 1384 additions and 107 deletions
|
|
@ -15,6 +15,7 @@ export interface ITestData extends base {
|
|||
isMSRZip?: boolean,
|
||||
isPIAEnabled?: boolean,
|
||||
isDualRecal?: boolean,
|
||||
isNewInsuranceFlow?: boolean,
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
import { BailoutPage } from '../pages/BailoutPage';
|
||||
import { BailoutSuccessPage } from '../pages/BailoutSuccessPage';
|
||||
|
||||
|
|
@ -53,6 +54,7 @@ export interface ITestPages {
|
|||
orderConfirmationPage: OrderConfirmationPage,
|
||||
partQuestionsPage: PartQuestionsPage,
|
||||
paymentMethodPage: PaymentMethodPage,
|
||||
policyInfoPage: PolicyInfoPage,
|
||||
policyInfoSubmittedPage: PolicyInfoSubmittedPage,
|
||||
policyVehiclesPage: PolicyVehiclesPage,
|
||||
heritageProblemGlassQuestionsPage: HeritageProblemGlassQuestionsPage,
|
||||
|
|
@ -92,6 +94,7 @@ export const createTestPages: TestPagesFactory<ITestPages> = (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),
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
20
playwright-tests/pages/PolicyInfoPage.ts
Normal file
20
playwright-tests/pages/PolicyInfoPage.ts
Normal 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.');
|
||||
}
|
||||
}
|
||||
|
|
@ -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';
|
||||
|
|
@ -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.paymentMethod !== PaymentMethod.Insurance || 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);
|
||||
|
|
|
|||
|
|
@ -41,12 +41,15 @@ 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";
|
||||
import insuranceMeemicNearSchoolVerifiedTests from "./InsuranceMeemicNearSchoolVerified";
|
||||
import insuranceLibertyMutualTests from "./InsuranceLibertyMutual";
|
||||
import cashReplaceDualMobileMSRTests from "./CashReplaceDualMobileMSR";
|
||||
import cashReplaceDualNonMSRInshopTests from "./CashReplaceDualNonMSRInshop";
|
||||
import cashBigTruckTests from "./CashBigTruck";
|
||||
|
||||
|
||||
const test = getTestObject();
|
||||
|
|
@ -84,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 },
|
||||
|
|
@ -97,12 +101,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: "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
|
||||
|
|
@ -357,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;
|
||||
|
|
@ -373,6 +380,11 @@ async function handleInsuranceFlow(testCase: TestCase) {
|
|||
await HeritageProblemGlassQuestionsPage.handleHeritageProblemGlassQuestionsPage(testCase.testData);
|
||||
}
|
||||
|
||||
if (isNewInsuranceFlow) {
|
||||
let policyInfoPage = testCase.pages.policyInfoPage;
|
||||
await policyInfoPage.handleNewPolicyInfoPage(testCase.testData);
|
||||
} else {
|
||||
|
||||
// Handle ccPolicyInfoPage
|
||||
let ccPolicyInfoPage = testCase.pages.ccPolicyInfoPage;
|
||||
await ccPolicyInfoPage.handleCCPolicyInfoPage(testCase.testData);
|
||||
|
|
@ -417,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) {
|
||||
|
|
|
|||
64
playwright-tests/tests/CashBigTruck.ts
Normal file
64
playwright-tests/tests/CashBigTruck.ts
Normal 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;
|
||||
|
|
@ -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;
|
||||
54
playwright-tests/tests/InsuranceLibertyMutual.ts
Normal file
54
playwright-tests/tests/InsuranceLibertyMutual.ts
Normal 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;
|
||||
|
|
@ -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 at least one consent option",
|
||||
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",
|
||||
|
|
@ -42,6 +43,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",
|
||||
|
|
|
|||
|
|
@ -52,6 +52,18 @@ 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",
|
||||
|
||||
// Consent Management
|
||||
SHOW_CONSENT_MANAGEMENT: "ShowConsentManagement",
|
||||
|
||||
// YMM
|
||||
BAILOUT_VIN_REQUIRED_VEHICLES: "BailoutVINRequiredVehicles",
|
||||
};
|
||||
|
||||
const experimentTriggers = {
|
||||
|
|
|
|||
54
src/constants/more-policy-questions.js
Normal file
54
src/constants/more-policy-questions.js
Normal file
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
|
||||
function setupMocks({ options, props }) {
|
||||
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, 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];
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -34,7 +34,9 @@
|
|||
|
||||
<!-- Service Type -->
|
||||
<div class="service-type">
|
||||
<textBlock :cmsWidgetName="servicePackageTitleWidget" />
|
||||
<textBlock
|
||||
:cmsWidgetName="servicePackageTitleWidget"
|
||||
:customText="servicePackageTitleText" />
|
||||
<span>
|
||||
{{ getLineItemAmount(packagePrice) }}
|
||||
</span>
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
|
||||
|
|
@ -210,7 +228,7 @@ describe("save-progress-popup-question ", () => {
|
|||
|
||||
expect(dispatchStoreAction).toHaveBeenCalledWith(
|
||||
storeActions.SAVE_PHONE_NUMBER,
|
||||
"555-123-4567",
|
||||
"5551234567",
|
||||
false
|
||||
);
|
||||
expect(dispatchStoreAction).not.toHaveBeenCalledWith(
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -46,7 +46,8 @@
|
|||
v-model="smsConsent"
|
||||
:consentCopy="smsConsentCopy"
|
||||
:isDisabled="isProgressSaved"
|
||||
:showConsentErrors="showConsentErrors" />
|
||||
:showConsentErrors="showConsentErrors"
|
||||
:showConsentManagement="showConsentManagement" />
|
||||
</div>
|
||||
<saveProgressQuestion
|
||||
v-else
|
||||
|
|
@ -118,7 +119,7 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
userInput: "",
|
||||
contactMethod: saveProgressPopupContactMethods.PHONE,
|
||||
contactMethod: saveProgressPopupContactMethods.EMAIL,
|
||||
smsConsent: defaultSaveProgressSmsConsent(),
|
||||
smsConsentCopy: getSaveProgressSmsConsentFallbackCopy(),
|
||||
isProgressSaved: false,
|
||||
|
|
@ -129,7 +130,7 @@ export default {
|
|||
watch: {
|
||||
smsConsent: {
|
||||
handler(consent) {
|
||||
if (hasSaveProgressSmsConsentSelection(consent)) {
|
||||
if (hasSaveProgressSmsConsentSelection(consent, this.showConsentManagement)) {
|
||||
this.showConsentErrors = false;
|
||||
}
|
||||
},
|
||||
|
|
@ -146,6 +147,7 @@ export default {
|
|||
modalWidgetName: String,
|
||||
pageName: String,
|
||||
showSaveProgressPopup: Boolean,
|
||||
showConsentManagement: Boolean,
|
||||
},
|
||||
computed: {
|
||||
buttonText() {
|
||||
|
|
@ -234,7 +236,9 @@ export default {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!hasSaveProgressSmsConsentSelection(this.smsConsent)) {
|
||||
if (
|
||||
!hasSaveProgressSmsConsentSelection(this.smsConsent, this.showConsentManagement)
|
||||
) {
|
||||
this.showConsentErrors = true;
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,14 +7,27 @@ 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);
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
|
|
@ -47,8 +60,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 +74,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,25 +88,44 @@ 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);
|
||||
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);
|
||||
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 +142,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);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
:hasError="showConsentError"
|
||||
:isDisabled="isDisabled" />
|
||||
<checkboxQuestion
|
||||
v-if="showConsentManagement"
|
||||
v-model="marketingConsent"
|
||||
checkboxName="saveProgressMarketingConsent"
|
||||
:labelText="consentCopy.marketing"
|
||||
|
|
@ -20,7 +21,7 @@
|
|||
class="save-progress-popup-sms-consent__error d-inline-flex small mt-1"
|
||||
role="alert"
|
||||
aria-live="polite">
|
||||
{{ errorMessages.SMS_CONSENT_REQUIRED }}
|
||||
{{ consentErrorMessage }}
|
||||
</span>
|
||||
</fieldset>
|
||||
</template>
|
||||
|
|
@ -36,11 +37,6 @@ import {
|
|||
|
||||
export default {
|
||||
name: "save-progress-popup-sms-consent-question",
|
||||
data() {
|
||||
return {
|
||||
errorMessages,
|
||||
};
|
||||
},
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Object,
|
||||
|
|
@ -52,6 +48,7 @@ export default {
|
|||
},
|
||||
isDisabled: Boolean,
|
||||
showConsentErrors: Boolean,
|
||||
showConsentManagement: Boolean,
|
||||
},
|
||||
computed: {
|
||||
transactionalConsent: {
|
||||
|
|
@ -71,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: {
|
||||
|
|
|
|||
|
|
@ -87,18 +87,36 @@ export default {
|
|||
}).then(
|
||||
(response) => {
|
||||
if (logApiCall) {
|
||||
let additionalEventData = "";
|
||||
if (additionalSuccessEventDataHandler) {
|
||||
additionalEventData = "_" + additionalSuccessEventDataHandler(response);
|
||||
}
|
||||
const endpointWithoutParams =
|
||||
analyticsMixIn.methods.removeParamsFromEndpoint(endpoint);
|
||||
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,
|
||||
`${pageNameToLog}_${endpointWithoutParams}`,
|
||||
`${GaLabels.SUCCESS}${additionalEventData}`,
|
||||
gaAction,
|
||||
`${GaLabels.SUCCESS}_${entry}`,
|
||||
true
|
||||
);
|
||||
});
|
||||
} else {
|
||||
analyticsMixIn.methods.pushEventToGA(
|
||||
GaCategories.API_RESPONSE,
|
||||
gaAction,
|
||||
GaLabels.SUCCESS,
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return resolve(response);
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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) ?? {};
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -320,6 +320,7 @@ describe("partQuestions.vue...", () => {
|
|||
glassName: "Single",
|
||||
isSuppressedPart: undefined,
|
||||
result: "FW04848",
|
||||
problemQuestionId: null,
|
||||
},
|
||||
],
|
||||
false
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -13,11 +13,18 @@
|
|||
|
||||
<script>
|
||||
import buttonQuestion from "@/digital-components/button-question/button-question";
|
||||
import {
|
||||
morePolicyQuestionAnswerNamesToFlags,
|
||||
morePolicyQuestionFlagsToAnswerNames,
|
||||
} from "@/constants/more-policy-questions";
|
||||
|
||||
export default {
|
||||
name: "morePolicyQuestions",
|
||||
props: {
|
||||
modelValue: Array,
|
||||
// Boolean flags keyed by the camelCase name of each possible policy
|
||||
// question (e.g. { additionalDamage, rental, otherResponsibleParty,
|
||||
// thirdPartyVehicle, injuries }), see @/constants/more-policy-questions.
|
||||
modelValue: Object,
|
||||
groupName: String,
|
||||
cmsWidgetName: String,
|
||||
},
|
||||
|
|
@ -33,12 +40,18 @@ export default {
|
|||
if (!Array.isArray(this.answersFromCms)) return [];
|
||||
return this.answersFromCms;
|
||||
},
|
||||
// The checkbox-based buttonQuestion component works with an array of
|
||||
// selected CMS answer Names; translate to/from the boolean-flag object
|
||||
// that this component's own v-model exposes to its parent.
|
||||
selectedValues: {
|
||||
get() {
|
||||
return this.modelValue;
|
||||
return morePolicyQuestionFlagsToAnswerNames(this.modelValue);
|
||||
},
|
||||
set(newValue) {
|
||||
this.$emit("update:modelValue", newValue);
|
||||
set(newSelectedAnswerNames) {
|
||||
this.$emit(
|
||||
"update:modelValue",
|
||||
morePolicyQuestionAnswerNamesToFlags(newSelectedAnswerNames)
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -47,6 +47,82 @@ describe("policy-info.vue", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("displayClaimNumber", () => {
|
||||
test("is false when no morePolicyQuestions flags are set", async () => {
|
||||
const { wrapper } = setupMocks();
|
||||
wrapper.vm.morePolicyQuestions = {
|
||||
additionalDamage: false,
|
||||
rental: false,
|
||||
otherResponsibleParty: false,
|
||||
thirdPartyVehicle: false,
|
||||
injuries: false,
|
||||
};
|
||||
|
||||
expect(wrapper.vm.displayClaimNumber).toBe(false);
|
||||
});
|
||||
|
||||
test("is true when additionalDamage is checked", async () => {
|
||||
const { wrapper } = setupMocks();
|
||||
wrapper.vm.morePolicyQuestions = {
|
||||
additionalDamage: true,
|
||||
rental: false,
|
||||
otherResponsibleParty: false,
|
||||
thirdPartyVehicle: false,
|
||||
injuries: false,
|
||||
};
|
||||
|
||||
expect(wrapper.vm.displayClaimNumber).toBe(true);
|
||||
});
|
||||
|
||||
test("is true when rental is checked", async () => {
|
||||
const { wrapper } = setupMocks();
|
||||
wrapper.vm.morePolicyQuestions = {
|
||||
additionalDamage: false,
|
||||
rental: true,
|
||||
otherResponsibleParty: false,
|
||||
thirdPartyVehicle: false,
|
||||
injuries: false,
|
||||
};
|
||||
|
||||
expect(wrapper.vm.displayClaimNumber).toBe(true);
|
||||
});
|
||||
|
||||
test("is false when only unrelated flags (e.g. otherResponsibleParty, thirdPartyVehicle, injuries) are checked", async () => {
|
||||
const { wrapper } = setupMocks();
|
||||
wrapper.vm.morePolicyQuestions = {
|
||||
additionalDamage: false,
|
||||
rental: false,
|
||||
otherResponsibleParty: true,
|
||||
thirdPartyVehicle: true,
|
||||
injuries: true,
|
||||
};
|
||||
|
||||
expect(wrapper.vm.displayClaimNumber).toBe(false);
|
||||
});
|
||||
|
||||
test("is false when morePolicyQuestions is not yet populated", async () => {
|
||||
const { wrapper } = setupMocks();
|
||||
wrapper.vm.morePolicyQuestions = null;
|
||||
|
||||
expect(wrapper.vm.displayClaimNumber).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("claim-number-required validation rule", () => {
|
||||
test("fails when value is empty", async () => {
|
||||
const result = await validate("", "claim-number-required");
|
||||
|
||||
expect(result.valid).toBe(false);
|
||||
expect(result.errors).toContain(errorMessages.CLAIM_NUMBER_REQUIRED);
|
||||
});
|
||||
|
||||
test("passes when value is provided", async () => {
|
||||
const result = await validate("12345678", "claim-number-required");
|
||||
|
||||
expect(result.valid).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("forwardButtonAction", () => {
|
||||
// Regression coverage: clicking Continue must explicitly (re)validate the form
|
||||
// rather than assuming meta.valid/isForwardActionDisabled already caught everything,
|
||||
|
|
|
|||
|
|
@ -180,10 +180,20 @@
|
|||
|
||||
<morePolicyQuestions
|
||||
ref="morePolicyQuestions"
|
||||
class="mb-4"
|
||||
:cmsWidgetName="morePolicyQuestionsWidgetName"
|
||||
v-model="morePolicyQuestions"
|
||||
groupName="MorePolicyQuestionsQuestion" />
|
||||
|
||||
<textboxQuestion
|
||||
v-if="displayClaimNumber"
|
||||
isRequired
|
||||
class="mb-4"
|
||||
cmsWidgetName="ClaimNumberWidget"
|
||||
v-model="claimNumber"
|
||||
inputId="claimNumber"
|
||||
validationRules="claim-number-required" />
|
||||
|
||||
<insuranceNavBar
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
ref="navbar"
|
||||
|
|
@ -212,6 +222,10 @@ import dropdownQuestion from "@/digital-components/dropdown-question/dropdown-qu
|
|||
import morePolicyQuestions from "@/layouts/policy-info/more-policy-questions/more-policy-questions";
|
||||
import buttonQuestion from "@/digital-components/button-question/button-question";
|
||||
import { stateOptions } from "@/constants/state-options";
|
||||
import {
|
||||
morePolicyQuestionAnswers,
|
||||
morePolicyQuestionAnswerToFlag,
|
||||
} from "@/constants/more-policy-questions";
|
||||
import { Form, defineRule } from "vee-validate";
|
||||
import { required } from "@/helpers/validation-rules";
|
||||
import { errorMessages } from "@/constants/error-messages";
|
||||
|
|
@ -317,6 +331,7 @@ defineRule("street-address-required", required(errorMessages.STREET_ADDRESS_REQU
|
|||
defineRule("loss-time-required", required(errorMessages.LOSS_TIME_REQUIRED));
|
||||
defineRule("loss-location-required", required(errorMessages.LOSS_LOCATION_REQUIRED));
|
||||
defineRule("subrogation-required", required(errorMessages.SUBROGATION_REQUIRED));
|
||||
defineRule("claim-number-required", required(errorMessages.CLAIM_NUMBER_REQUIRED));
|
||||
|
||||
export default {
|
||||
name: "policy-info",
|
||||
|
|
@ -339,6 +354,7 @@ export default {
|
|||
morePolicyQuestions: this.getMorePolicyQuestionsFromStore(),
|
||||
subrogationSelectedValue: this.getSubrogationSelectedValueFromStore(),
|
||||
lossLocation: this.getLossLocationFromStore(),
|
||||
claimNumber: this.getClaimNumberFromStore(),
|
||||
};
|
||||
},
|
||||
components: {
|
||||
|
|
@ -471,6 +487,17 @@ export default {
|
|||
? "MorePolicyQuestionsOverrideWidget"
|
||||
: "MorePolicyQuestionsWidget";
|
||||
},
|
||||
// Only ask for a claim number if the user indicated additional damage
|
||||
// occurred or the vehicle was a rental, per the morePolicyQuestions answers.
|
||||
displayClaimNumber() {
|
||||
const additionalDamageFlag =
|
||||
morePolicyQuestionAnswerToFlag[morePolicyQuestionAnswers.ADDITIONAL_DAMAGE];
|
||||
const rentalFlag = morePolicyQuestionAnswerToFlag[morePolicyQuestionAnswers.RENTAL];
|
||||
return Boolean(
|
||||
this.morePolicyQuestions?.[additionalDamageFlag] ||
|
||||
this.morePolicyQuestions?.[rentalFlag]
|
||||
);
|
||||
},
|
||||
},
|
||||
// Ensure CMS content is fetched during route navigation without depending on `to`/`from`
|
||||
async beforeRouteEnter(to, from, next) {
|
||||
|
|
@ -511,13 +538,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 +566,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;
|
||||
|
|
@ -560,11 +587,14 @@ export default {
|
|||
return store.getters.order.policy.subrogation;
|
||||
},
|
||||
getMorePolicyQuestionsFromStore() {
|
||||
return store.getters.order.policy.morePolicyQuestions ?? [];
|
||||
return store.getters.order.policy.morePolicyQuestions;
|
||||
},
|
||||
getLossLocationFromStore() {
|
||||
return store.getters.order.policy.lossLocation;
|
||||
},
|
||||
getClaimNumberFromStore() {
|
||||
return store.getters.order.payment.insuranceCoverage.claimNumber;
|
||||
},
|
||||
cancelButtonAction() {
|
||||
this.$router.navigateWithoutSaving(
|
||||
this.navigationScenarios.CLICKED_CANCEL_VERIFICATION,
|
||||
|
|
@ -599,6 +629,7 @@ export default {
|
|||
dateOfBirth: this.dateOfBirth,
|
||||
subrogation: this.subrogationSelectedValue,
|
||||
streetAddress: this.streetAddress,
|
||||
claimNumber: this.claimNumber,
|
||||
},
|
||||
false
|
||||
);
|
||||
|
|
|
|||
|
|
@ -15,8 +15,8 @@
|
|||
<span v-else>{{ getInlineAltText(token) }}</span>
|
||||
</span>
|
||||
<span v-else v-html="token"></span>
|
||||
<span class="nbsp"> </span>
|
||||
</span>
|
||||
<span class="nbsp"> </span>
|
||||
<textLink
|
||||
linkType="text"
|
||||
:text="bannerCta"
|
||||
|
|
@ -197,7 +197,6 @@ export default {
|
|||
}
|
||||
@include media-breakpoint-up(md) {
|
||||
padding-left: 1rem;
|
||||
max-width: 36rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -114,6 +114,7 @@
|
|||
pageName="quote"
|
||||
v-if="showSaveProgressPopup"
|
||||
:showSaveProgressPopup="showSaveProgressPopup"
|
||||
:showConsentManagement="showConsentManagement"
|
||||
@save-progress-saved="updateSaveProgressAsSaved"
|
||||
@close-save-progress-popup="closeSaveProgressPopup" />
|
||||
</div>
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
},
|
||||
|
|
|
|||
|
|
@ -168,6 +168,23 @@ describe("vin-lookup.vue", () => {
|
|||
expect(wrapper.vm.navigateForward).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("Should keep the continue button loader spinning for vinRequired vehicles when VIN lookup fails", async () => {
|
||||
// Arrange
|
||||
store.getters.vehicle.vinRequired = true;
|
||||
const { wrapper } = setupMocks({});
|
||||
mockOutPromises({ vehicleLookupFailed: true });
|
||||
wrapper.vm.continueWithFailedVin = jest.fn().mockResolvedValue();
|
||||
wrapper.setData({ vin: "1HGCM82633A123456", vinPopulatedOnPageLoad: false });
|
||||
|
||||
// Act
|
||||
await wrapper.vm.forwardButtonAction();
|
||||
|
||||
// Assert
|
||||
expect(wrapper.vm.continueWithFailedVin).toHaveBeenCalled();
|
||||
expect(wrapper.vm.$refs.navbar.removeLoader).not.toHaveBeenCalled();
|
||||
store.getters.vehicle.vinRequired = false;
|
||||
});
|
||||
|
||||
describe("navigateForward", () => {
|
||||
test("carId is different from returned vehicle and selected glass isn't available => continue with different glass", async () => {
|
||||
// Arrange
|
||||
|
|
@ -394,11 +411,14 @@ function setupMocks({ customMountOptions }) {
|
|||
return { wrapper };
|
||||
}
|
||||
|
||||
function mockOutPromises({ carId, isZipValid = true, isZipServiceable = true }) {
|
||||
function mockOutPromises({
|
||||
carId,
|
||||
isZipValid = true,
|
||||
isZipServiceable = true,
|
||||
vehicleLookupFailed = false,
|
||||
}) {
|
||||
const apiResponses = {
|
||||
vehicleLookupResponse: {
|
||||
carId: carId,
|
||||
},
|
||||
vehicleLookupResponse: vehicleLookupFailed ? undefined : { carId: carId },
|
||||
zipCodeData: {
|
||||
isValid: isZipValid,
|
||||
isServiceable: isZipServiceable,
|
||||
|
|
|
|||
|
|
@ -295,7 +295,14 @@ export default {
|
|||
const isVinLookupRequired = this.$store.getters.vehicle.vinRequired;
|
||||
if (isVinLookupRequired) {
|
||||
this.requiredVinNotFound = true;
|
||||
this.continueWithFailedVin(this.vin, resultMap.zipCodeData);
|
||||
|
||||
// Check if Service Zip entered is serviceable, if not display an alert
|
||||
if (!resultMap.zipCodeData.isServiceable) {
|
||||
this.displayNonServiceableZipAlert = true;
|
||||
}
|
||||
|
||||
await this.continueWithFailedVin(this.vin, resultMap.zipCodeData);
|
||||
return;
|
||||
} else {
|
||||
this.displayVinNotFoundAlert = true;
|
||||
}
|
||||
|
|
@ -521,7 +528,7 @@ export default {
|
|||
},
|
||||
false
|
||||
);
|
||||
this.navigateForwardWithSingleCarMatch();
|
||||
await this.navigateForwardWithSingleCarMatch();
|
||||
},
|
||||
getRequiredVinNotFound() {
|
||||
// Prevents success alert from showing
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { saveSession } from "@/helpers/heritage-integration/order-helper.js";
|
|||
import { experimentSettings } from "@/constants/experiments";
|
||||
import { vinLookupMethodSelections } from "@/constants/vin-lookup-method-selections.js";
|
||||
import { bailoutCodes } from "@/constants/bailout-codes";
|
||||
import experimentMixin from "@/mixins/experiment-mixin.js";
|
||||
|
||||
export default {
|
||||
computed: {
|
||||
|
|
@ -37,7 +38,7 @@ export default {
|
|||
pageNameToLog: pageName,
|
||||
});
|
||||
|
||||
if (result.PartNotFound) {
|
||||
if (result.PartNotFound || this.bailoutVinRequiredVehicles()) {
|
||||
return bailoutMixin.methods.navigateToBailoutPage(
|
||||
this,
|
||||
bailoutCodes.PART_NOT_FOUND
|
||||
|
|
@ -75,5 +76,15 @@ export default {
|
|||
const phoneRegex = /^\(?(\d{3})\)?[-. ]?(\d{3})[-. ]?(\d{4})$/;
|
||||
return phoneRegex.test(input);
|
||||
},
|
||||
bailoutVinRequiredVehicles() {
|
||||
var isVinRequiredVehicle = store.getters.order.vehicle?.vinRequired;
|
||||
if (!isVinRequiredVehicle) {
|
||||
return false;
|
||||
}
|
||||
return experimentMixin.methods.hasSettingEqualTo(
|
||||
experimentSettings.BAILOUT_VIN_REQUIRED_VEHICLES,
|
||||
"true"
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -59,7 +59,6 @@ import {
|
|||
import { externalParameterStatus } from "@/constants/external-parameters";
|
||||
import { experimentSettings } from "@/constants/experiments";
|
||||
import { addPricesToLineItems, getAmountDue } from "@/helpers/pricing-helper";
|
||||
|
||||
// Export State
|
||||
const getDefaultState = () => {
|
||||
return {
|
||||
|
|
@ -112,6 +111,7 @@ const getDefaultState = () => {
|
|||
lastName: null,
|
||||
emailAddress: null,
|
||||
phoneNumber: null,
|
||||
phoneExtension: null,
|
||||
isSmsOptIn: null,
|
||||
waitListRequested: null,
|
||||
address: {
|
||||
|
|
@ -184,13 +184,18 @@ const getDefaultState = () => {
|
|||
additionalAuthFlag: null,
|
||||
isNoComp: false,
|
||||
insuranceCompanyName: null,
|
||||
phoneNumber: null,
|
||||
phoneExtension: null,
|
||||
dateOfBirth: null,
|
||||
lossTime: null,
|
||||
lossLocation: null,
|
||||
lastName: null,
|
||||
subrogation: null,
|
||||
morePolicyQuestions: {
|
||||
additionalDamage: false,
|
||||
rental: false,
|
||||
otherResponsibleParty: false,
|
||||
thirdPartyVehicle: false,
|
||||
injuries: false,
|
||||
},
|
||||
},
|
||||
schedule: {
|
||||
date: null,
|
||||
|
|
@ -358,10 +363,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 +585,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 +970,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) {
|
||||
|
|
@ -1009,6 +1015,9 @@ export const mutations = {
|
|||
state.order.policy.insuranceCompanyName =
|
||||
sessionInformation.order.policy?.insuranceCompanyName;
|
||||
|
||||
state.order.policy.morePolicyQuestions =
|
||||
sessionInformation.order.policy?.morePolicyQuestions;
|
||||
|
||||
state.order.policy.isNoComp =
|
||||
coverageTypeValue(sessionInformation?.order.insuranceCoverage.coverageType) ===
|
||||
coverageType.NOCOMP
|
||||
|
|
@ -2822,6 +2831,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,
|
||||
|
|
@ -2894,6 +2904,7 @@ export const actions = {
|
|||
currentDeductible: order.policy?.currentDeductible,
|
||||
originalDeductible: order.policy?.originalDeductible,
|
||||
policyNumber: order.policy?.policyNumber,
|
||||
morePolicyQuestions: order.policy?.morePolicyQuestions,
|
||||
},
|
||||
serviceLocation: {
|
||||
streetAddress: order.serviceLocation.address,
|
||||
|
|
@ -2940,8 +2951,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"),
|
||||
],
|
||||
});
|
||||
},
|
||||
|
||||
|
|
@ -4317,11 +4330,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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -795,6 +795,112 @@ describe("Actions", () => {
|
|||
expect(response.data).toEqual({ referralNumber: 123 });
|
||||
});
|
||||
|
||||
it("saveSession action, includes the selected morePolicyQuestions answers in the policy payload", async () => {
|
||||
// Arrange
|
||||
const context = state;
|
||||
|
||||
context.getters = {
|
||||
vehicle: {
|
||||
registration: {},
|
||||
},
|
||||
order: {
|
||||
damage: {
|
||||
numberOfChips: "2",
|
||||
partQuestionAnswers: {},
|
||||
moldingQuestionAnswers: {},
|
||||
capabilityQuestionAnswers: {},
|
||||
},
|
||||
},
|
||||
damage: {},
|
||||
applicationUser: {
|
||||
lastPageVisited: "test-page",
|
||||
crmCustomerId: "xxx-xxx-xxx",
|
||||
savedSessionId: "xxx-xxx-xxx",
|
||||
},
|
||||
};
|
||||
context.state = {
|
||||
order: {
|
||||
damage: {
|
||||
numberOfChips: "2",
|
||||
partQuestionAnswers: {},
|
||||
moldingQuestionAnswers: {},
|
||||
capabilityQuestionAnswers: {},
|
||||
},
|
||||
payment: {
|
||||
insuranceCoverage: {
|
||||
isVerified: false,
|
||||
},
|
||||
isInsurance: false,
|
||||
ccToken: {
|
||||
subscriptionId: null,
|
||||
expMonth: null,
|
||||
expYear: null,
|
||||
cardType: null,
|
||||
billToPostalCode: null,
|
||||
billToFirstName: null,
|
||||
billToLastName: null,
|
||||
referenceNumber: null,
|
||||
authCode: null,
|
||||
transactionId: null,
|
||||
transReferenceNumber: null,
|
||||
lastFour: null,
|
||||
},
|
||||
},
|
||||
policy: {
|
||||
morePolicyQuestions: {
|
||||
additionalDamage: true,
|
||||
rental: true,
|
||||
otherResponsibleParty: false,
|
||||
thirdPartyVehicle: false,
|
||||
injuries: false,
|
||||
},
|
||||
},
|
||||
serviceLocation: {},
|
||||
customer: {
|
||||
emailAddress: "test@safelite.com",
|
||||
firstName: "John",
|
||||
lastName: "Doe",
|
||||
isSmsOptIn: true,
|
||||
phoneNumber: "1234567890",
|
||||
address: {
|
||||
streetAddress: "123 Main St",
|
||||
streetAddress2: "Apt 1",
|
||||
city: "Anytown",
|
||||
state: "OH",
|
||||
zipCode: "12345",
|
||||
},
|
||||
},
|
||||
lineItems: {},
|
||||
},
|
||||
};
|
||||
|
||||
globalMethods.callHttpClient.mockImplementation(() => {
|
||||
return Promise.resolve({ data: { referralNumber: 123 } });
|
||||
});
|
||||
|
||||
// Act
|
||||
await actions.saveSession(context, { pageNameToLog: "test" });
|
||||
|
||||
// Assert
|
||||
expect(globalMethods.callHttpClient).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
payload: expect.objectContaining({
|
||||
order: expect.objectContaining({
|
||||
policy: expect.objectContaining({
|
||||
morePolicyQuestions: {
|
||||
additionalDamage: true,
|
||||
rental: true,
|
||||
otherResponsibleParty: false,
|
||||
thirdPartyVehicle: false,
|
||||
injuries: false,
|
||||
},
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("loadSession action, returns order information, calls mutation", async () => {
|
||||
// Arrange
|
||||
const context = state;
|
||||
|
|
@ -4470,4 +4576,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",
|
||||
},
|
||||
],
|
||||
}),
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue