Merge remote-tracking branch 'origin/develop' into feature/time-slot-unit-tests

This commit is contained in:
Scott Kiener 2023-07-28 13:52:54 -04:00
commit 9be6c5d688
18 changed files with 811 additions and 112 deletions

View file

@ -74,6 +74,9 @@ https://safelite.atlassian.net/wiki/spaces/DC/pages/76644418/Button+Question+Com
</div>
<div class="row form-test-error">
<error-message
role="comment"
aria-atomic="true"
aria-live="polite"
class="small mt-1"
:name="formatString(groupName)"
v-if="!suppressError"></error-message>

View file

@ -711,7 +711,7 @@ export default {
}
.date-picker {
position: relative;
flex-grow: 1;
flex-grow: 0;
display: flex;
flex-direction: column;

View file

@ -23,7 +23,7 @@
</option>
</select>
<div v-show="errorMessage" class="row mt-2 form-test-error">
<span role="alert">{{ errorMessage }}</span>
<span aria-atomic="true" aria-live="polite">{{ errorMessage }}</span>
</div>
</div>
</template>

View file

@ -68,7 +68,8 @@
<div v-show="errorMessage" class="row form-test-error">
<span
class="d-inline-flex small my-1"
role="alert"
aria-atomic="true"
aria-live="polite"
:class="[centerErrorMessage ? 'center-error-message' : '']"
>{{ errorMessage }}</span
>

View file

@ -338,12 +338,10 @@ export default {
false
);
await this.dispatchStoreAction(
storeActions.SAVE_SERVICE_LOCATION,
storeActions.SAVE_SERVICE_ZIP_CODE_INFO,
{
address: this.customerQuestions.addressQuestions.streetAddress,
city: this.customerQuestions.addressQuestions.city,
zipCode: this.serviceZipCode,
state: resultMap.serviceZipValidationResponse.state,
zipCode: this.serviceZipCode,
zipCodeCtu: resultMap.serviceZipValidationResponse.zipCodeCtu,
},
false

View file

@ -4,7 +4,7 @@
<textBlock
:cmsWidgetName="headerCmsWidgetName"
typeStyle="body small bold dark"
margin="mt-0" />
marginTopSizeOverride="0" />
<textLink
linkType="textSmall"
text="Edit"
@ -12,9 +12,12 @@
@click-event="linkClicked"
href="javascript:void(0)" />
</div>
<div class="small" v-for="item in content" :key="item" data-test="contentLine">
{{ item }}
</div>
<div
class="small review-block-content"
v-for="item in content"
:key="item"
data-test="contentLine"
v-html="item"></div>
</div>
</template>
@ -44,3 +47,9 @@ export default {
},
};
</script>
<style lang="scss" scoped>
:deep(.review-block-content > ul) {
margin-bottom: 0rem;
}
</style>

View file

@ -1,19 +1,258 @@
import { shallowMount } from "@vue/test-utils";
import { getMountOptions } from "@/helpers/unit-test-helper.js";
import damageReview from "@/layouts/review/review-sections/damage-review/damage-review";
jest.mock("@/helpers/cms-content-helper", () => ({
fetchCmsContentForPage: () => Promise.resolve("content"),
}));
import damageReview from "@/layouts/review/review-sections/damage-review/damage-review";
import { damageLocationsSelected as glassConstants } from "@/constants/damage-locations-selected";
const testConstants = {
cmsConstants: {
widgetNames: {
header: "DamageReviewWidget",
locations: "DamageLocationsWidget",
driverDamages: "DriverDamagesWidget",
passengerDamages: "PassengerDamagesWidget",
},
header: {
text: "Damage",
},
damageLocations: {
windshield: glassConstants.WINDSHIELD,
driver: glassConstants.DRIVER,
passenger: glassConstants.PASSENGER,
rear: glassConstants.REAR,
},
damageNames: {
vent: glassConstants.VENT,
front: glassConstants.FRONT,
back: glassConstants.BACK,
quarter: glassConstants.QUARTER,
side: glassConstants.SIDEDOOR,
},
locationCopy: {
windshield: "Windshield copy",
driver: "Driver copy",
passenger: "Passenger copy",
rear: "Rear copy",
},
damageCopy: {
vent: "Vent copy",
front: "Front copy",
back: "Back copy",
quarter: "Quarter copy",
side: "Side copy",
},
imageId: "00000000-0000-0000-0000-000000000000",
},
makeBulletedList: (items) => {
let list = "<ul>";
items.forEach((item) => {
list += `<li>${item}</li>`;
});
list += "</ul>";
return list;
},
glassItems: {
windshield: {
glassLocation: glassConstants.WINDSHIELD,
glassName: glassConstants.SINGLE,
},
rear: {
glassLocation: glassConstants.REAR,
glassName: glassConstants.STATIONARY,
},
passengerItems: {
vent: {
glassLocation: glassConstants.PASSENGER,
glassName: glassConstants.VENT,
},
front: {
glassLocation: glassConstants.PASSENGER,
glassName: glassConstants.FRONT,
},
back: {
glassLocation: glassConstants.PASSENGER,
glassName: glassConstants.BACK,
},
quarter: {
glassLocation: glassConstants.PASSENGER,
glassName: glassConstants.QUARTER,
},
side: {
glassLocation: glassConstants.PASSENGER,
glassName: glassConstants.SIDEDOOR,
},
},
driverItems: {
vent: {
glassLocation: glassConstants.DRIVER,
glassName: glassConstants.VENT,
},
front: {
glassLocation: glassConstants.DRIVER,
glassName: glassConstants.FRONT,
},
back: {
glassLocation: glassConstants.DRIVER,
glassName: glassConstants.BACK,
},
quarter: {
glassLocation: glassConstants.DRIVER,
glassName: glassConstants.QUARTER,
},
side: {
glassLocation: glassConstants.DRIVER,
glassName: glassConstants.SIDEDOOR,
},
},
},
};
let cmsContent;
describe("Damage Review Block", () => {
beforeEach(() => {
cmsContent = {
DamageReviewWidget: {
Text: testConstants.cmsConstants.header.text,
},
DamageLocationsWidget: {
Answers: [
{
Name: testConstants.cmsConstants.damageLocations.windshield,
Text: testConstants.cmsConstants.locationCopy.windshield,
SubText: "",
ImageId: testConstants.cmsConstants.imageId,
Image: "",
SubWidgetName: "",
},
{
Name: testConstants.cmsConstants.damageLocations.driver,
Text: testConstants.cmsConstants.locationCopy.driver,
SubText: "",
ImageId: testConstants.cmsConstants.imageId,
Image: "",
SubWidgetName: testConstants.cmsConstants.widgetNames.driverDamages,
},
{
Name: testConstants.cmsConstants.damageLocations.passenger,
Text: testConstants.cmsConstants.locationCopy.passenger,
SubText: "",
ImageId: testConstants.cmsConstants.imageId,
Image: "",
SubWidgetName: testConstants.cmsConstants.widgetNames.passengerDamages,
},
{
Name: testConstants.cmsConstants.damageLocations.rear,
Text: testConstants.cmsConstants.locationCopy.rear,
SubText: "",
ImageId: testConstants.cmsConstants.imageId,
Image: "",
SubWidgetName: "",
},
],
},
DriverDamagesWidget: {
Answers: [
{
Name: testConstants.cmsConstants.damageNames.vent,
Text: testConstants.cmsConstants.damageCopy.vent,
SubText: "",
ImageId: testConstants.cmsConstants.imageId,
Image: "",
SubWidgetName: "",
},
{
Name: testConstants.cmsConstants.damageNames.front,
Text: testConstants.cmsConstants.damageCopy.front,
SubText: "",
ImageId: testConstants.cmsConstants.imageId,
Image: "",
SubWidgetName: "",
},
{
Name: testConstants.cmsConstants.damageNames.back,
Text: testConstants.cmsConstants.damageCopy.back,
SubText: "",
ImageId: testConstants.cmsConstants.imageId,
Image: "",
SubWidgetName: "",
},
{
Name: testConstants.cmsConstants.damageNames.quarter,
Text: testConstants.cmsConstants.damageCopy.quarter,
SubText: "",
ImageId: testConstants.cmsConstants.imageId,
Image: "",
SubWidgetName: "",
},
{
Name: testConstants.cmsConstants.damageNames.side,
Text: testConstants.cmsConstants.damageCopy.side,
SubText: "",
ImageId: testConstants.cmsConstants.imageId,
Image: "",
SubWidgetName: "",
},
],
},
PassengerDamagesWidget: {
Answers: [
{
Name: testConstants.cmsConstants.damageNames.vent,
Text: testConstants.cmsConstants.damageCopy.vent,
SubText: "",
ImageId: testConstants.cmsConstants.imageId,
Image: "",
SubWidgetName: "",
},
{
Name: testConstants.cmsConstants.damageNames.front,
Text: testConstants.cmsConstants.damageCopy.front,
SubText: "",
ImageId: testConstants.cmsConstants.imageId,
Image: "",
SubWidgetName: "",
},
{
Name: testConstants.cmsConstants.damageNames.back,
Text: testConstants.cmsConstants.damageCopy.back,
SubText: "",
ImageId: testConstants.cmsConstants.imageId,
Image: "",
SubWidgetName: "",
},
{
Name: testConstants.cmsConstants.damageNames.quarter,
Text: testConstants.cmsConstants.damageCopy.quarter,
SubText: "",
ImageId: testConstants.cmsConstants.imageId,
Image: "",
SubWidgetName: "",
},
{
Name: testConstants.cmsConstants.damageNames.side,
Text: testConstants.cmsConstants.damageCopy.side,
SubText: "",
ImageId: testConstants.cmsConstants.imageId,
Image: "",
SubWidgetName: "",
},
],
},
};
});
describe("Correctly assembles damage info into a display string", () => {
test("Basic Replace", async () => {
test("Shows windshield copy when windshield damage is included", async () => {
// Arrange
const { wrapper } = setupMocks({
propsData: {
cmsWidgetName: testConstants.cmsConstants.widgetNames.header,
damageLocationsWidgetName: testConstants.cmsConstants.widgetNames.locations,
damage: {
isRepair: false,
glassToReplace: [testConstants.glassItems.windshield],
},
},
});
@ -22,16 +261,21 @@ describe("Damage Review Block", () => {
await wrapper.vm.$nextTick();
// Assert
expect(wrapper.vm.displayContent).toStrictEqual(["Windshield crack"]);
expect(wrapper.vm.displayContent).toStrictEqual([
testConstants.cmsConstants.locationCopy.windshield,
]);
});
test("Basic Repair", async () => {
test("Windshield copy is shown when order is a repair", async () => {
// Arrange
const { wrapper } = setupMocks({
propsData: {
cmsWidgetName: testConstants.cmsConstants.widgetNames.header,
damageLocationsWidgetName: testConstants.cmsConstants.widgetNames.locations,
damage: {
isRepair: true,
numberOfChips: 2,
glassToReplace: [],
},
},
});
@ -40,16 +284,21 @@ describe("Damage Review Block", () => {
await wrapper.vm.$nextTick();
// Assert
expect(wrapper.vm.displayContent).toStrictEqual(["Windshield repair - 2 chips"]);
expect(wrapper.vm.displayContent).toStrictEqual([
testConstants.cmsConstants.locationCopy.windshield,
]);
});
test("Basic Repair - no s with 1 chip", async () => {
test("Rear windshield copy shows when rear damage is present", async () => {
// Arrange
const { wrapper } = setupMocks({
propsData: {
cmsWidgetName: testConstants.cmsConstants.widgetNames.header,
damageLocationsWidgetName: testConstants.cmsConstants.widgetNames.locations,
damage: {
isRepair: true,
numberOfChips: 1,
isRepair: false,
numberOfChips: null,
glassToReplace: [testConstants.glassItems.rear],
},
},
});
@ -58,7 +307,116 @@ describe("Damage Review Block", () => {
await wrapper.vm.$nextTick();
// Assert
expect(wrapper.vm.displayContent).toStrictEqual(["Windshield repair - 1 chip"]);
expect(wrapper.vm.displayContent).toStrictEqual([
testConstants.cmsConstants.locationCopy.rear,
]);
});
test("Driver side copy and items are shown when driver side damage is present", async () => {
// Arrange
const { wrapper } = setupMocks({
propsData: {
cmsWidgetName: testConstants.cmsConstants.widgetNames.header,
damageLocationsWidgetName: testConstants.cmsConstants.widgetNames.locations,
damage: {
isRepair: false,
numberOfChips: null,
glassToReplace: [
testConstants.glassItems.driverItems.back,
testConstants.glassItems.driverItems.front,
],
},
},
});
// Act
await wrapper.vm.$nextTick();
// Assert
expect(wrapper.vm.displayContent).toStrictEqual([
testConstants.cmsConstants.locationCopy.driver,
testConstants.makeBulletedList([
testConstants.cmsConstants.damageCopy.front,
testConstants.cmsConstants.damageCopy.back,
]),
]);
});
test("Passenger side copy and items are shown when passenger side damage is present", async () => {
// Arrange
const { wrapper } = setupMocks({
propsData: {
cmsWidgetName: testConstants.cmsConstants.widgetNames.header,
damageLocationsWidgetName: testConstants.cmsConstants.widgetNames.locations,
damage: {
isRepair: false,
numberOfChips: null,
glassToReplace: [
testConstants.glassItems.passengerItems.quarter,
testConstants.glassItems.passengerItems.vent,
testConstants.glassItems.passengerItems.side,
],
},
},
});
// Act
await wrapper.vm.$nextTick();
// Assert
expect(wrapper.vm.displayContent).toStrictEqual([
testConstants.cmsConstants.locationCopy.passenger,
testConstants.makeBulletedList([
testConstants.cmsConstants.damageCopy.vent,
testConstants.cmsConstants.damageCopy.quarter,
testConstants.cmsConstants.damageCopy.side,
]),
]);
});
test("All relevant sections are shown in order in multiglass scenario", async () => {
// Arrange
const { wrapper } = setupMocks({
propsData: {
cmsWidgetName: testConstants.cmsConstants.widgetNames.header,
damageLocationsWidgetName: testConstants.cmsConstants.widgetNames.locations,
damage: {
isRepair: false,
numberOfChips: null,
glassToReplace: [
testConstants.glassItems.windshield,
testConstants.glassItems.rear,
testConstants.glassItems.driverItems.vent,
testConstants.glassItems.driverItems.front,
testConstants.glassItems.driverItems.back,
testConstants.glassItems.passengerItems.quarter,
testConstants.glassItems.passengerItems.back,
testConstants.glassItems.passengerItems.side,
],
},
},
});
// Act
await wrapper.vm.$nextTick();
// Assert
expect(wrapper.vm.displayContent).toStrictEqual([
testConstants.cmsConstants.locationCopy.windshield,
testConstants.cmsConstants.locationCopy.driver,
testConstants.makeBulletedList([
testConstants.cmsConstants.damageCopy.vent,
testConstants.cmsConstants.damageCopy.front,
testConstants.cmsConstants.damageCopy.back,
]),
testConstants.cmsConstants.locationCopy.passenger,
testConstants.makeBulletedList([
testConstants.cmsConstants.damageCopy.back,
testConstants.cmsConstants.damageCopy.quarter,
testConstants.cmsConstants.damageCopy.side,
]),
testConstants.cmsConstants.locationCopy.rear,
]);
});
});
});
@ -66,6 +424,16 @@ describe("Damage Review Block", () => {
function setupMocks(customMountOptions) {
const mountOptions = getMountOptions(customMountOptions);
const mockMixin = {
methods: {
getCmsContent: jest.fn((widgetName, cmsFieldName) => {
return cmsContent?.[widgetName]?.[cmsFieldName] ?? "";
}),
},
};
mountOptions.global.mixins = [mockMixin];
const wrapper = shallowMount(damageReview, mountOptions);
wrapper.vm.setCmsContent = jest.fn();
return { wrapper };

View file

@ -7,11 +7,13 @@
<script>
import reviewBlock from "@/layouts/review/review-block/review-block";
import { damageLocationsSelected } from "@/constants/damage-locations-selected.js";
export default {
name: "damage-review",
props: {
cmsWidgetName: String,
damageLocationsWidgetName: String,
damage: Object,
},
data() {
@ -21,17 +23,115 @@ export default {
editClicked() {
this.$emit("edit-clicked");
},
getAnswersNullSafe(widgetName) {
const rawAnswers = this.getCmsContent(widgetName, "Answers");
return rawAnswers ? rawAnswers : [];
},
getLocationAnswer(damageLocation) {
return this.locationAnswers?.find((answer) => answer.Name === damageLocation);
},
getGlassPieces(damageLocation) {
return this.damage?.glassToReplace?.filter(
(item) => item.glassLocation === damageLocation
);
},
generateBulletedListFromAnswers(answers) {
let list = "<ul>";
answers.forEach((answer) => {
list += `<li>${answer.Text}</li>`;
});
list += "</ul>";
return list;
},
},
computed: {
displayContent() {
return [
this.damage.isRepair
? `Windshield repair - ${this.damage.numberOfChips} chip${
this.damage.numberOfChips !== 1 ? "s" : ""
}`
: "Windshield crack",
...this.windshieldCopy,
...this.driverSideCopy,
...this.passengerSideCopy,
...this.rearCopy,
];
},
hasWindshieldDamage() {
const windshieldPieces = this.getGlassPieces(damageLocationsSelected.WINDSHIELD);
return this.damage.isRepair || !!windshieldPieces?.length;
},
hasDriverSideDamage() {
const driverPieces = this.getGlassPieces(damageLocationsSelected.DRIVER);
return !!driverPieces?.length;
},
hasPassengerSideDamage() {
const passengerPieces = this.getGlassPieces(damageLocationsSelected.PASSENGER);
return !!passengerPieces?.length;
},
hasRearDamage() {
const rearPieces = this.getGlassPieces(damageLocationsSelected.REAR);
return !!rearPieces?.length;
},
windshieldCopy() {
const answerContent = this.getLocationAnswer(damageLocationsSelected.WINDSHIELD);
if (this.hasWindshieldDamage) {
return [answerContent?.Text];
} else {
return [];
}
},
driverSideCopy() {
const answerContent = this.getLocationAnswer(damageLocationsSelected.DRIVER);
const damageAnswers = this.getAnswersNullSafe(answerContent?.SubWidgetName);
const driverSideItems = this.getGlassPieces(damageLocationsSelected.DRIVER);
const damageAnswersOnOrder = damageAnswers?.filter((answer) =>
driverSideItems?.some((glassPiece) => answer.Name === glassPiece.glassName)
);
if (this.hasDriverSideDamage) {
return [
answerContent?.Text,
this.generateBulletedListFromAnswers(damageAnswersOnOrder),
];
} else {
return [];
}
},
passengerSideCopy() {
const answerContent = this.getLocationAnswer(damageLocationsSelected.PASSENGER);
const damageAnswers = this.getAnswersNullSafe(answerContent?.SubWidgetName);
const passengerSideItems = this.getGlassPieces(damageLocationsSelected.PASSENGER);
const damageAnswersOnOrder = damageAnswers?.filter((answer) =>
passengerSideItems?.some((glassPiece) => answer.Name === glassPiece.glassName)
);
if (this.hasPassengerSideDamage) {
return [
answerContent?.Text,
this.generateBulletedListFromAnswers(damageAnswersOnOrder),
];
} else {
return [];
}
},
rearCopy() {
const answerContent = this.getLocationAnswer(damageLocationsSelected.REAR);
if (this.hasRearDamage) {
return [answerContent?.Text];
} else {
return [];
}
},
locationAnswers() {
return this.getAnswersNullSafe(this.damageLocationsWidgetName);
},
},
components: {
reviewBlock,

View file

@ -0,0 +1,129 @@
import { shallowMount } from "@vue/test-utils";
import { getMountOptions } from "@/helpers/unit-test-helper.js";
import serviceLocationReview from "@/layouts/review/review-sections/service-location-review/service-location-review";
import { AppointmentTypeStrings } from "@/constants/schedule-constants";
const cmsContent = {
ServiceLocationTitleWidget: {
Text: "Title Text",
},
};
describe("Service Location Review Block", () => {
it("Should render mobile address if mobile appointment", async () => {
// Arrange
const props = generateDefaultProps();
const { wrapper } = setupMocks({
propsData: props,
});
// Act
await wrapper.vm.$nextTick();
// Assert
expect(wrapper.vm.displayContent).toEqual([
"Mobile Address 1, Mobile Address 2, Mobile City, MO 11111",
]);
});
it("Should render service location address if inshop appointment.", async () => {
// Arrange
let props = generateDefaultProps();
props.serviceLocation.appointmentType = AppointmentTypeStrings.IN_SHOP;
const { wrapper } = setupMocks({
propsData: props,
});
// Act
await wrapper.vm.$nextTick();
// Assert
expect(wrapper.vm.displayContent).toEqual([
"Service Location Address, Service Location City, SL 22222",
]);
});
it("Should render service location address if drop-off appointment", async () => {
// Arrange
let props = generateDefaultProps();
props.serviceLocation.appointmentType = AppointmentTypeStrings.DROP_OFF;
const { wrapper } = setupMocks({
propsData: props,
});
// Act
await wrapper.vm.$nextTick();
// Assert
expect(wrapper.vm.displayContent).toEqual([
"Service Location Address, Service Location City, SL 22222",
]);
});
it("Should not add comma or any text if address2 is null", async () => {
// Arrange
let props = generateDefaultProps();
props.serviceLocation.address2 = null;
const { wrapper } = setupMocks({
propsData: props,
});
// Act
await wrapper.vm.$nextTick();
// Assert
expect(wrapper.vm.displayContent).toEqual(["Mobile Address 1, Mobile City, MO 11111"]);
});
});
function generateDefaultProps() {
return {
cmsWidgetName: "ServiceLocationTitleWidget",
serviceLocation: {
address: "Mobile Address 1",
address2: "Mobile Address 2",
city: "Mobile City",
state: "MO",
zipCode: "11111",
zipCodeCtu: "",
appointmentType: AppointmentTypeStrings.MOBILE,
isVehicleProtected: false,
provider: {
providerNumber: "",
address: {
streetAddress: "Service Location Address",
city: "Service Location City",
state: "SL",
zipCode: "22222",
zipCodeCtu: "",
},
},
},
};
}
function setupMocks(customMountOptions) {
const mountOptions = getMountOptions(customMountOptions);
const mockMixin = {
methods: {
getCmsContent: jest.fn((widgetName, cmsFieldName) => {
return cmsContent?.[widgetName]?.[cmsFieldName] ?? "";
}),
},
};
mountOptions.global.mixins = [mockMixin];
const wrapper = shallowMount(serviceLocationReview, mountOptions);
wrapper.vm.setCmsContent = jest.fn();
return { wrapper };
}

View file

@ -0,0 +1,58 @@
<template>
<reviewBlock
:headerCmsWidgetName="cmsWidgetName"
:content="displayContent"
@edit-clicked="editClicked" />
</template>
<script>
import reviewBlock from "@/layouts/review/review-block/review-block";
import { AppointmentTypeStrings } from "@/constants/schedule-constants";
export default {
name: "service-package-review",
props: {
cmsWidgetName: String,
serviceLocation: Object,
},
data() {
return {};
},
methods: {
editClicked() {
this.$emit("edit-clicked");
},
},
computed: {
displayContent() {
return [
`${this.addressInfo.address}${this.addressInfo.address2 ? ", " : ""}${
this.addressInfo.address2
}, ${this.addressInfo.city}, ${this.addressInfo.state} ${this.addressInfo.zipCode}`,
];
},
addressInfo() {
if (this.serviceLocation?.appointmentType === AppointmentTypeStrings.MOBILE) {
return {
address: this.serviceLocation?.address,
address2: this.serviceLocation?.address2 ?? "",
city: this.serviceLocation?.city,
state: this.serviceLocation?.state,
zipCode: this.serviceLocation?.zipCode,
};
} else {
return {
address: this.serviceLocation?.provider?.address?.streetAddress,
address2: "",
city: this.serviceLocation?.provider?.address?.city,
state: this.serviceLocation?.provider?.address?.state,
zipCode: this.serviceLocation?.provider?.address?.zipCode,
};
}
},
},
components: {
reviewBlock,
},
};
</script>

View file

@ -77,11 +77,8 @@ const figmaScenarios = [
{
name: "05_01_CSR_Quote_Cash",
params: {
availableVaps: [
testConstants.parts.frontWiperPart,
testConstants.parts.rearWiperPart,
testConstants.parts.rainDefensePart,
],
wiperResponse: [testConstants.parts.frontWiperPart, testConstants.parts.rearWiperPart],
rainDefenseResponse: testConstants.parts.rainDefensePart,
damage: {
isRepair: false,
glassToReplace: [testConstants.damages.frontWindshield],
@ -142,11 +139,8 @@ const figmaScenarios = [
{
name: "05_01_CSR_Quote_Standard_Repair",
params: {
availableVaps: [
testConstants.parts.frontWiperPart,
testConstants.parts.rearWiperPart,
testConstants.parts.rainDefensePart,
],
wiperResponse: [testConstants.parts.frontWiperPart, testConstants.parts.rearWiperPart],
rainDefenseResponse: testConstants.parts.rainDefensePart,
damage: {
isRepair: true,
glassToReplace: [],
@ -206,11 +200,8 @@ const figmaScenarios = [
{
name: "05_01_CSR_Quote_Recal",
params: {
availableVaps: [
testConstants.parts.frontWiperPart,
testConstants.parts.rearWiperPart,
testConstants.parts.rainDefensePart,
],
wiperResponse: [testConstants.parts.frontWiperPart, testConstants.parts.rearWiperPart],
rainDefenseResponse: testConstants.parts.rainDefensePart,
damage: {
isRepair: false,
glassToReplace: [testConstants.damages.frontWindshield],
@ -271,11 +262,8 @@ const figmaScenarios = [
{
name: "05_01_CSR_Quote_RearGlass+NonWindshield",
params: {
availableVaps: [
testConstants.parts.frontWiperPart,
testConstants.parts.rearWiperPart,
testConstants.parts.rainDefensePart,
],
wiperResponse: [testConstants.parts.frontWiperPart, testConstants.parts.rearWiperPart],
rainDefenseResponse: testConstants.parts.rainDefensePart,
damage: {
isRepair: false,
glassToReplace: [testConstants.damages.rearWindshield],
@ -343,11 +331,8 @@ const figmaScenarios = [
{
name: "05_01_CSR_Quote_RearGlass+Windshield",
params: {
availableVaps: [
testConstants.parts.frontWiperPart,
testConstants.parts.rearWiperPart,
testConstants.parts.rainDefensePart,
],
wiperResponse: [testConstants.parts.frontWiperPart, testConstants.parts.rearWiperPart],
rainDefenseResponse: testConstants.parts.rainDefensePart,
damage: {
isRepair: false,
glassToReplace: [
@ -461,7 +446,8 @@ const figmaScenarios = [
{
name: "05_01_CSR_Quote_RearGlassNoFrontFit",
params: {
availableVaps: [testConstants.parts.rearWiperPart, testConstants.parts.rainDefensePart],
wiperResponse: [testConstants.parts.rearWiperPart],
rainDefenseResponse: testConstants.parts.rainDefensePart,
damage: {
isRepair: false,
glassToReplace: [testConstants.damages.rearWindshield],
@ -518,11 +504,8 @@ const figmaScenarios = [
{
name: "05_01_CSR_Quote_Windshield+SideGlass",
params: {
availableVaps: [
testConstants.parts.frontWiperPart,
testConstants.parts.rearWiperPart,
testConstants.parts.rainDefensePart,
],
wiperResponse: [testConstants.parts.frontWiperPart, testConstants.parts.rearWiperPart],
rainDefenseResponse: testConstants.parts.rainDefensePart,
damage: {
isRepair: false,
glassToReplace: [
@ -586,11 +569,8 @@ const figmaScenarios = [
{
name: "05_01_CSR_Quote_SideGlass",
params: {
availableVaps: [
testConstants.parts.frontWiperPart,
testConstants.parts.rearWiperPart,
testConstants.parts.rainDefensePart,
],
wiperResponse: [testConstants.parts.frontWiperPart, testConstants.parts.rearWiperPart],
rainDefenseResponse: testConstants.parts.rainDefensePart,
damage: {
isRepair: false,
glassToReplace: [testConstants.damages.sideGlass],
@ -678,7 +658,8 @@ const figmaScenarios = [
{
name: "05_01_CSR_Quote_NoWiperFit",
params: {
availableVaps: [testConstants.parts.rainDefensePart],
wiperResponse: [],
rainDefenseResponse: testConstants.parts.rainDefensePart,
damage: {
isRepair: false,
glassToReplace: [testConstants.damages.frontWindshield],
@ -821,6 +802,8 @@ describe("Service Package Review Block", () => {
propsData: props,
});
initializeWithDefault(wrapper);
// Act
await wrapper.vm.$nextTick();
@ -851,6 +834,8 @@ describe("Service Package Review Block", () => {
propsData: props,
});
initializeWithDefault(wrapper);
// Act
await wrapper.vm.$nextTick();
@ -874,6 +859,8 @@ describe("Service Package Review Block", () => {
// Act
await wrapper.vm.$nextTick();
initializeWithDefault(wrapper);
// Assert
const includesFrontWiperCopy = wrapper.vm.displayContent.includes(
testConstants.vapsCopy.frontWiperCopy
@ -893,6 +880,8 @@ describe("Service Package Review Block", () => {
propsData: generateDefaultProps(),
});
initializeWithDefault(wrapper);
// Act
await wrapper.vm.$nextTick();
@ -908,7 +897,6 @@ describe("Service Package Review Block", () => {
it(`Should match figma scenario "${scenario.name}", iteration "${iteration.name}"`, async () => {
// Arrange
let props = generateDefaultProps();
props.availableVaps = scenario.params.availableVaps;
props.damage = scenario.params.damage;
props.glassParts = scenario.params.glassParts;
props.lineItems.supportingItems = scenario.params.supportingItems;
@ -918,6 +906,11 @@ describe("Service Package Review Block", () => {
propsData: props,
});
wrapper.vm.initializeComponent({
wipers: scenario.params.wiperResponse,
rainDefense: scenario.params.rainDefenseResponse,
});
// Act
await wrapper.vm.$nextTick();
@ -932,12 +925,18 @@ describe("Service Package Review Block", () => {
});
});
function initializeWithDefault(wrapper) {
wrapper.vm.initializeComponent({
wipers: [testConstants.parts.frontWiperPart],
rainDefense: testConstants.parts.rainDefensePart,
});
}
function generateDefaultProps() {
return {
servicePackageOptionsCmsName: testConstants.cmsPropValues.servicePackageOptionsCmsName,
defaultPackageItemsCmsName: testConstants.cmsPropValues.defaultPackageItemsCmsName,
vapsItemsCmsName: testConstants.cmsPropValues.vapsItemsCmsName,
availableVaps: [testConstants.parts.frontWiperPart, testConstants.parts.rainDefensePart],
lineItems: {
glassParts: [],
supportingItems: [],

View file

@ -11,6 +11,9 @@ import {
getHighestFullySatisfiedTier,
containsLineItemWithPartType,
} from "@/helpers/service-package-helper";
import { settleAllPromises } from "@/helpers/layout-helper";
import baseMixin from "@/mixins/base-mixin.js";
import { storeActions } from "@/constants/store-actions";
export default {
name: "service-package-review",
@ -18,14 +21,39 @@ export default {
servicePackageOptionsCmsName: String,
defaultPackageItemsCmsName: String,
vapsItemsCmsName: String,
availableVaps: Array,
lineItems: Object,
damage: Object,
},
data() {
return {};
return {
availableVaps: [],
};
},
methods: {
loadInitialData() {
const wipersPromise = baseMixin.methods.dispatchStoreAction(storeActions.GET_WIPERS);
const rainDefensePromise = baseMixin.methods.dispatchStoreAction(
storeActions.GET_RAIN_DEFENSE
);
const promiseResultMap = [
{
resultKey: "wipers",
promise: wipersPromise,
},
{
resultKey: "rainDefense",
promise: rainDefensePromise,
},
];
return settleAllPromises(promiseResultMap);
},
initializeComponent(apiResponses) {
const vapsFromApi = [...apiResponses.wipers, apiResponses.rainDefense];
this.availableVaps = vapsFromApi;
},
editClicked() {
this.$emit("edit-clicked");
},

View file

@ -48,19 +48,27 @@
<damageReview
cmsWidgetName="DamageReviewWidget"
damageLocationsWidgetName="DamageLocationsWidget"
:damage="damageInfo"
@edit-clicked="editDamage" />
<hr class="my-0" />
<servicePackageReview
ref="servicePackageReview"
servicePackageOptionsCmsName="ServicePackageTitle"
defaultPackageItemsCmsName="DefaultPackageItemDescriptions"
vapsItemsCmsName="VapsItemDescriptions"
:damage="damageInfo"
:availableVaps="availableVaps"
:lineItems="lineItems"
@edit-clicked="editServicePackage" />
<hr class="my-0" />
<serviceLocationReview
cmsWidgetName="ServiceLocationTitleWidget"
:serviceLocation="serviceLocationInfo"
@edit-clicked="editServiceLocation" />
</div>
<div>
@ -85,6 +93,7 @@ import textBlock from "@/digital-components/text-block/text-block";
import vehicleReview from "@/layouts/review/review-sections/vehicle-review/vehicle-review";
import damageReview from "@/layouts/review/review-sections/damage-review/damage-review";
import servicePackageReview from "@/layouts/review/review-sections/service-package-review/service-package-review";
import serviceLocationReview from "@/layouts/review/review-sections/service-location-review/service-location-review";
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import { settleAllPromises } from "@/helpers/layout-helper";
@ -102,6 +111,8 @@ export default {
storeActions.GET_RAIN_DEFENSE
);
const servicePackageInitialDataPromise = servicePackageReview.methods.loadInitialData();
// Settle promises and get results
const promiseResultMap = [
{
@ -109,12 +120,8 @@ export default {
promise: cmsContentPromise,
},
{
resultKey: "wipers",
promise: wipersPromise,
},
{
resultKey: "rainDefense",
promise: rainDefensePromise,
resultKey: "servicePackageData",
promise: servicePackageInitialDataPromise,
},
];
@ -123,15 +130,11 @@ export default {
next((vm) => {
vm.setCmsContent(resultMap.cmsContent);
vm.availableWipers = resultMap.wipers;
vm.availableRainDefense = [resultMap.rainDefense];
vm.$refs.servicePackageReview.initializeComponent(resultMap.servicePackageData);
});
},
data() {
return {
availableWipers: null,
availableRainDefense: null,
};
return {};
},
methods: {
arePagePrerequisitesValid() {
@ -157,6 +160,12 @@ export default {
this.$route
);
},
editServiceLocation() {
this.$router.navigateWithoutSaving(
this.navigationScenarios.CLICKED_SERVICE_LOCATION_EDIT,
this.$route
);
},
},
computed: {
subHeaderTitle() {
@ -174,12 +183,12 @@ export default {
damageInfo() {
return this.$store.getters.damage;
},
availableVaps() {
return [...(this.availableWipers ?? []), ...(this.availableRainDefense ?? [])];
},
lineItems() {
return this.$store.getters.lineItems;
},
serviceLocationInfo() {
return this.$store.getters.order.serviceLocation;
},
},
components: {
funnelHeader,
@ -190,6 +199,7 @@ export default {
vehicleReview,
damageReview,
servicePackageReview,
serviceLocationReview,
},
};
</script>

View file

@ -17,7 +17,10 @@
@click-event="openModal" />
</div>
<div v-show="errorMessage" class="row my-1 form-test-error">
<span class="d-inline-flex small mt-0 center-error-message" role="alert">
<span
class="d-inline-flex small mt-0 center-error-message"
aria-atomic="true"
aria-live="polite">
{{ errorMessage }}
</span>
</div>

View file

@ -350,37 +350,22 @@ export default {
// If a VIN has already been found. Validate the Service Zip (in case of changes)
const zipCodeData = await this.getZipCodeData(this.serviceZipCode);
// Check if Service Zip entered is serviceable then save the ZIP info and email address
// Check if Service Zip entered is serviceable then save the ZIP info
if (zipCodeData.isServiceable) {
//Only save the service location if the zip changed or we lack zipCodeCtu
//Only save the zipCode, state, and zipCodeCtu if the zip changed or we lack zipCodeCtu
if (
this.$store.getters.order.serviceLocation.zipCode != this.serviceZipCode ||
!this.$store.getters.order.serviceLocation.zipCodeCtu
) {
const vehicleRegistrationInfo = this.$store.getters.vehicle.registration;
if (vehicleRegistrationInfo.zipCode == this.serviceZipCode) {
await this.dispatchStoreAction(
storeActions.SAVE_SERVICE_LOCATION,
{
address: vehicleRegistrationInfo.address,
city: vehicleRegistrationInfo.city,
state: vehicleRegistrationInfo.state,
zipCode: vehicleRegistrationInfo.zipCode,
zipCodeCtu: zipCodeData.zipCodeCtu,
},
false
);
} else {
await this.dispatchStoreAction(
storeActions.SAVE_SERVICE_ZIP_CODE_INFO,
{
state: zipCodeData.state,
zipCode: this.serviceZipCode,
zipCodeCtu: zipCodeData.zipCodeCtu,
},
false
);
}
await this.dispatchStoreAction(
storeActions.SAVE_SERVICE_ZIP_CODE_INFO,
{
state: zipCodeData.state,
zipCode: this.serviceZipCode,
zipCodeCtu: zipCodeData.zipCodeCtu,
},
false
);
}
await this.dispatchStoreAction(storeActions.SAVE_EMAIL, this.emailAddress, false);

View file

@ -50,6 +50,7 @@ const navigationScenarios = {
CLICKED_VEHICLE_EDIT: "CLICKED_VEHICLE_EDIT",
CLICKED_DAMAGE_EDIT: "CLICKED_DAMAGE_EDIT",
CLICKED_SERVICE_PACKAGE_EDIT: "CLICKED_SERVICE_PACKAGE_EDIT",
CLICKED_SERVICE_LOCATION_EDIT: "CLICKED_SERVICE_LOCATION_EDIT",
};
export { navigationScenarios };

View file

@ -480,6 +480,10 @@ const routingTable = function (store) {
scenario: navigationScenarios.CLICKED_BACK,
destinationFmgPageValue: fmgPageValues.CUSTOMER_DETAILS,
},
{
scenario: navigationScenarios.CLICKED_SERVICE_LOCATION_EDIT,
destinationFmgPageValue: fmgPageValues.SERVICE_LOCATION,
},
],
},
];

View file

@ -528,6 +528,9 @@ export const getters = {
},
eventBus: (state) => state.applicationUser.eventBus,
damage: (state) => state.order.damage,
hasExactlyOneChip: (state) => {
return state.order.damage?.numberOfChips === 1;
},
hasAnyNonWindshieldGlassParts: (state) => {
const nonWindshieldItems = state.order.damage.glassToReplace?.filter(
(glassToReplace) => glassToReplace.glassLocation != "Windshield"