Merge branch 'develop' into feature/CSR-1422

This commit is contained in:
Leah Schumann 2023-08-02 09:39:02 -04:00
commit abcabbe1d6
33 changed files with 2069 additions and 246 deletions

View file

@ -26,8 +26,7 @@ module.exports = {
testMatch: ["**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"], testMatch: ["**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"],
coverageThreshold: { coverageThreshold: {
global: { global: {
statements: 75, statements: 80,
// Got the go ahead from Mark to temporarily lower this. Taking out initialize component made the year,make,model and style coverage drop a bit. Once unit tests for license plate lookup, vin lookup and address lookup are in the coverage should go back up to 90
}, },
}, },
// Uncomment this to avoid the massive amount of warnings we are getting for onSubmit and onInvalidSubmit // Uncomment this to avoid the massive amount of warnings we are getting for onSubmit and onInvalidSubmit

View file

@ -12,6 +12,7 @@ const GaEvents = {
const GaCategories = { const GaCategories = {
API_RESPONSE: "Api_Response", API_RESPONSE: "Api_Response",
EVOX: "Evox", EVOX: "Evox",
FUNNEL_ENTRY: "funnel_entry",
}; };
const GaActions = { const GaActions = {
@ -20,6 +21,7 @@ const GaActions = {
VIF: "vif", VIF: "vif",
SUBMITTED: "Submitted", SUBMITTED: "Submitted",
DISPLAYED: "Displayed", DISPLAYED: "Displayed",
ZIP_CODE_PROVIDED: "zip_code_provided",
}; };
const GaLabels = { const GaLabels = {

View file

@ -62,6 +62,7 @@ const storeActions = {
SAVE_VEHICLE_MAKE: "saveVehicleMake", SAVE_VEHICLE_MAKE: "saveVehicleMake",
SAVE_VEHICLE_MODEL: "saveVehicleModel", SAVE_VEHICLE_MODEL: "saveVehicleModel",
SAVE_VEHICLE_STYLE: "saveVehicleStyle", SAVE_VEHICLE_STYLE: "saveVehicleStyle",
SAVE_VEHICLE: "saveVehicle",
SAVE_VEHICLE_DAMAGE: "saveVehicleDamage", SAVE_VEHICLE_DAMAGE: "saveVehicleDamage",
SAVE_VIN_LOOKUP: "saveVinLookup", SAVE_VIN_LOOKUP: "saveVinLookup",
SAVE_SERVICE_ZIP_CODE_INFO: "saveServiceZipCodeInfo", SAVE_SERVICE_ZIP_CODE_INFO: "saveServiceZipCodeInfo",

View file

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

View file

@ -44,7 +44,8 @@
type="radio" type="radio"
name="day-of-month" name="day-of-month"
v-model="selectedDate" v-model="selectedDate"
@click="fireDateClickedEvent" @click="fireDateSelectedEvent"
@keypress.enter="fireDateSelectedEvent"
:value="date.inputValue" :value="date.inputValue"
:id="`${month.monthLabel}-${date.dateNum.toString()}`" /> :id="`${month.monthLabel}-${date.dateNum.toString()}`" />
<label :for="`${month.monthLabel}-${date.dateNum.toString()}`"> <label :for="`${month.monthLabel}-${date.dateNum.toString()}`">
@ -56,7 +57,7 @@
:class="[!isLoading ? 'date-picker-hidden' : '']" :class="[!isLoading ? 'date-picker-hidden' : '']"
loaderColor="blue" loaderColor="blue"
loaderPosition="center" /> loaderPosition="center" />
<div class="row form-test-error"> <div class="row form-test-error" id="date-of-month-error">
<ErrorMessage name="date-of-month" class="small mt-1"></ErrorMessage> <ErrorMessage name="date-of-month" class="small mt-1"></ErrorMessage>
</div> </div>
<button <button
@ -190,7 +191,11 @@ export default {
initializeComponent(initialData) { initializeComponent(initialData) {
this.setCalendarData(initialData); this.setCalendarData(initialData);
}, },
fireDateClickedEvent() { fireDateSelectedEvent(event) {
// Ignore if arrow key selected radioButton
if (event.screenX === 0 && event.screenY === 0) {
return;
}
this.$emit("date-clicked"); this.$emit("date-clicked");
}, },
getWeekStartDate(dateString) { getWeekStartDate(dateString) {
@ -685,6 +690,12 @@ export default {
value: newValue, value: newValue,
}); });
}, },
errorMessage(newValue, oldValue) {
// Check for changing from no error message to some error message.
if (newValue && !oldValue) {
this.scrollToElement("date-of-month-error");
}
},
}, },
components: { components: {
loader, loader,
@ -700,7 +711,7 @@ export default {
} }
.date-picker { .date-picker {
position: relative; position: relative;
flex-grow: 1; flex-grow: 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View file

@ -16,14 +16,18 @@
:disabled="isDisabled" :disabled="isDisabled"
:aria-required="isRequired" :aria-required="isRequired"
:validationRules="validationRules" :validationRules="validationRules"
:placeHolderText="placeHolderText"> :placeHolderText="placeHolderText"
:valueAsKey="valueAsKey">
<option v-if="placeHolderText" value="" selected>{{ placeHolderText }}</option> <option v-if="placeHolderText" value="" selected>{{ placeHolderText }}</option>
<option v-for="(value, name, index) in options" :value="name" :key="index"> <option
v-for="(value, name, index) in options"
:value="valueAsKey == true ? value : name"
:key="valueAsKey == true ? value : index">
{{ value }} {{ value }}
</option> </option>
</select> </select>
<div v-show="errorMessage" class="row mt-2 form-test-error"> <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>
</div> </div>
</template> </template>
@ -47,6 +51,11 @@ export default {
placeHolderText: String, placeHolderText: String,
cmsWidgetName: String, cmsWidgetName: String,
hasError: Boolean, hasError: Boolean,
valueAsKey: {
type: Boolean,
required: false,
default: false,
},
}, },
setup(props) { setup(props) {
const uuid = uuidv4(); const uuid = uuidv4();

View file

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

View file

@ -19,15 +19,13 @@
tabindex="-1" tabindex="-1"
aria-labelledby="footerModalLabel" aria-labelledby="footerModalLabel"
aria-hidden="true" aria-hidden="true"
v-on="{ 'show.bs.modal': show, 'hide.bs.modal': hide }" v-on="{ 'show.bs.modal': show, 'hide.bs.modal': hide }">
:style="`height: calc(100% - ${currentFooterAndHeaderHeight}px);`">
<div class="menu-modal-container"> <div class="menu-modal-container">
<button <button
class="menu-button" class="menu-button"
type="button" type="button"
:class="[isActive ? 'active' : '']" :class="[isActive ? 'active' : '']"
data-bs-toggle="modal" @click="toggleModal"
data-bs-target="#footerModal"
aria-label="Hamburger Menu (modal window)"> aria-label="Hamburger Menu (modal window)">
<div class="bar1"></div> <div class="bar1"></div>
<div class="bar2"></div> <div class="bar2"></div>
@ -86,7 +84,6 @@ export default {
data() { data() {
return { return {
isActive: false, isActive: false,
currentFooterAndHeaderHeight: 0,
}; };
}, },
methods: { methods: {
@ -104,7 +101,6 @@ export default {
Modal.getInstance(document.getElementById("footerModal")).hide(); Modal.getInstance(document.getElementById("footerModal")).hide();
}, },
show() { show() {
this.currentFooterAndHeaderHeight = this.getFooterInfoBoxHeight() + 72;
this.isActive = true; this.isActive = true;
this.scrollToPageTop(); this.scrollToPageTop();
}, },

View file

@ -338,12 +338,10 @@ export default {
false false
); );
await this.dispatchStoreAction( 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, state: resultMap.serviceZipValidationResponse.state,
zipCode: this.serviceZipCode,
zipCodeCtu: resultMap.serviceZipValidationResponse.zipCodeCtu, zipCodeCtu: resultMap.serviceZipValidationResponse.zipCodeCtu,
}, },
false false

View file

@ -4,11 +4,6 @@ import customerDetails from "@/layouts/customer-details/customer-details.vue";
// Supporting Files // Supporting Files
import { shallowMount } from "@vue/test-utils"; import { shallowMount } from "@vue/test-utils";
import { getMountOptions } from "@/helpers/unit-test-helper.js"; import { getMountOptions } from "@/helpers/unit-test-helper.js";
import * as navigateToHeritage from "@/helpers/heritage-integration/navigation-helper";
jest.mock("@/helpers/heritage-integration/navigation-helper", () => ({
navigateToHeritageFunnel: jest.fn(),
}));
// Mock our module for promises. // Mock our module for promises.
jest.mock("@/helpers/layout-helper.js", () => ({ jest.mock("@/helpers/layout-helper.js", () => ({
@ -36,7 +31,7 @@ describe("customer-details.vue", () => {
await wrapper.vm.forwardButtonAction(); await wrapper.vm.forwardButtonAction();
// Assert // Assert
expect(wrapper.vm.$router.navigateWithoutSaving).toHaveBeenCalled(); expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalled();
}); });
}); });
}); });

View file

@ -150,7 +150,7 @@ export default {
(store.getters.order.damage.isRepair || (store.getters.order.damage.isRepair ||
(store.getters.order.lineItems?.glassParts != null && (store.getters.order.lineItems?.glassParts != null &&
store.getters.order.lineItems.glassParts.length > 0)) && store.getters.order.lineItems.glassParts.length > 0)) &&
!store.getters.payment.insuranceCoverage.isVerified store.getters.order.referralNumber?.length !== 6
); );
}, },
getDefaultIsInsuranceSelectedValue(availableLineItems) { getDefaultIsInsuranceSelectedValue(availableLineItems) {

View file

@ -4,7 +4,7 @@
<textBlock <textBlock
:cmsWidgetName="headerCmsWidgetName" :cmsWidgetName="headerCmsWidgetName"
typeStyle="body small bold dark" typeStyle="body small bold dark"
margin="mt-0" /> marginTopSizeOverride="0" />
<textLink <textLink
linkType="textSmall" linkType="textSmall"
text="Edit" text="Edit"
@ -12,9 +12,12 @@
@click-event="linkClicked" @click-event="linkClicked"
href="javascript:void(0)" /> href="javascript:void(0)" />
</div> </div>
<div class="small" v-for="item in content" :key="item" data-test="contentLine"> <div
{{ item }} class="small review-block-content"
</div> v-for="item in content"
:key="item"
data-test="contentLine"
v-html="item"></div>
</div> </div>
</template> </template>
@ -44,3 +47,9 @@ export default {
}, },
}; };
</script> </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 { shallowMount } from "@vue/test-utils";
import { getMountOptions } from "@/helpers/unit-test-helper.js"; 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", () => ({ import damageReview from "@/layouts/review/review-sections/damage-review/damage-review";
fetchCmsContentForPage: () => Promise.resolve("content"), 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", () => { 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", () => { describe("Correctly assembles damage info into a display string", () => {
test("Basic Replace", async () => { test("Shows windshield copy when windshield damage is included", async () => {
// Arrange // Arrange
const { wrapper } = setupMocks({ const { wrapper } = setupMocks({
propsData: { propsData: {
cmsWidgetName: testConstants.cmsConstants.widgetNames.header,
damageLocationsWidgetName: testConstants.cmsConstants.widgetNames.locations,
damage: { damage: {
isRepair: false, isRepair: false,
glassToReplace: [testConstants.glassItems.windshield],
}, },
}, },
}); });
@ -22,16 +261,21 @@ describe("Damage Review Block", () => {
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
// Assert // 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 // Arrange
const { wrapper } = setupMocks({ const { wrapper } = setupMocks({
propsData: { propsData: {
cmsWidgetName: testConstants.cmsConstants.widgetNames.header,
damageLocationsWidgetName: testConstants.cmsConstants.widgetNames.locations,
damage: { damage: {
isRepair: true, isRepair: true,
numberOfChips: 2, numberOfChips: 2,
glassToReplace: [],
}, },
}, },
}); });
@ -40,16 +284,21 @@ describe("Damage Review Block", () => {
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
// Assert // 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 // Arrange
const { wrapper } = setupMocks({ const { wrapper } = setupMocks({
propsData: { propsData: {
cmsWidgetName: testConstants.cmsConstants.widgetNames.header,
damageLocationsWidgetName: testConstants.cmsConstants.widgetNames.locations,
damage: { damage: {
isRepair: true, isRepair: false,
numberOfChips: 1, numberOfChips: null,
glassToReplace: [testConstants.glassItems.rear],
}, },
}, },
}); });
@ -58,7 +307,116 @@ describe("Damage Review Block", () => {
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
// Assert // 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) { function setupMocks(customMountOptions) {
const mountOptions = getMountOptions(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); const wrapper = shallowMount(damageReview, mountOptions);
wrapper.vm.setCmsContent = jest.fn(); wrapper.vm.setCmsContent = jest.fn();
return { wrapper }; return { wrapper };

View file

@ -7,11 +7,13 @@
<script> <script>
import reviewBlock from "@/layouts/review/review-block/review-block"; import reviewBlock from "@/layouts/review/review-block/review-block";
import { damageLocationsSelected } from "@/constants/damage-locations-selected.js";
export default { export default {
name: "damage-review", name: "damage-review",
props: { props: {
cmsWidgetName: String, cmsWidgetName: String,
damageLocationsWidgetName: String,
damage: Object, damage: Object,
}, },
data() { data() {
@ -21,17 +23,115 @@ export default {
editClicked() { editClicked() {
this.$emit("edit-clicked"); 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: { computed: {
displayContent() { displayContent() {
return [ return [
this.damage.isRepair ...this.windshieldCopy,
? `Windshield repair - ${this.damage.numberOfChips} chip${ ...this.driverSideCopy,
this.damage.numberOfChips !== 1 ? "s" : "" ...this.passengerSideCopy,
}` ...this.rearCopy,
: "Windshield crack",
]; ];
}, },
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: { components: {
reviewBlock, 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", name: "05_01_CSR_Quote_Cash",
params: { params: {
availableVaps: [ wiperResponse: [testConstants.parts.frontWiperPart, testConstants.parts.rearWiperPart],
testConstants.parts.frontWiperPart, rainDefenseResponse: testConstants.parts.rainDefensePart,
testConstants.parts.rearWiperPart,
testConstants.parts.rainDefensePart,
],
damage: { damage: {
isRepair: false, isRepair: false,
glassToReplace: [testConstants.damages.frontWindshield], glassToReplace: [testConstants.damages.frontWindshield],
@ -142,11 +139,8 @@ const figmaScenarios = [
{ {
name: "05_01_CSR_Quote_Standard_Repair", name: "05_01_CSR_Quote_Standard_Repair",
params: { params: {
availableVaps: [ wiperResponse: [testConstants.parts.frontWiperPart, testConstants.parts.rearWiperPart],
testConstants.parts.frontWiperPart, rainDefenseResponse: testConstants.parts.rainDefensePart,
testConstants.parts.rearWiperPart,
testConstants.parts.rainDefensePart,
],
damage: { damage: {
isRepair: true, isRepair: true,
glassToReplace: [], glassToReplace: [],
@ -206,11 +200,8 @@ const figmaScenarios = [
{ {
name: "05_01_CSR_Quote_Recal", name: "05_01_CSR_Quote_Recal",
params: { params: {
availableVaps: [ wiperResponse: [testConstants.parts.frontWiperPart, testConstants.parts.rearWiperPart],
testConstants.parts.frontWiperPart, rainDefenseResponse: testConstants.parts.rainDefensePart,
testConstants.parts.rearWiperPart,
testConstants.parts.rainDefensePart,
],
damage: { damage: {
isRepair: false, isRepair: false,
glassToReplace: [testConstants.damages.frontWindshield], glassToReplace: [testConstants.damages.frontWindshield],
@ -271,11 +262,8 @@ const figmaScenarios = [
{ {
name: "05_01_CSR_Quote_RearGlass+NonWindshield", name: "05_01_CSR_Quote_RearGlass+NonWindshield",
params: { params: {
availableVaps: [ wiperResponse: [testConstants.parts.frontWiperPart, testConstants.parts.rearWiperPart],
testConstants.parts.frontWiperPart, rainDefenseResponse: testConstants.parts.rainDefensePart,
testConstants.parts.rearWiperPart,
testConstants.parts.rainDefensePart,
],
damage: { damage: {
isRepair: false, isRepair: false,
glassToReplace: [testConstants.damages.rearWindshield], glassToReplace: [testConstants.damages.rearWindshield],
@ -343,11 +331,8 @@ const figmaScenarios = [
{ {
name: "05_01_CSR_Quote_RearGlass+Windshield", name: "05_01_CSR_Quote_RearGlass+Windshield",
params: { params: {
availableVaps: [ wiperResponse: [testConstants.parts.frontWiperPart, testConstants.parts.rearWiperPart],
testConstants.parts.frontWiperPart, rainDefenseResponse: testConstants.parts.rainDefensePart,
testConstants.parts.rearWiperPart,
testConstants.parts.rainDefensePart,
],
damage: { damage: {
isRepair: false, isRepair: false,
glassToReplace: [ glassToReplace: [
@ -461,7 +446,8 @@ const figmaScenarios = [
{ {
name: "05_01_CSR_Quote_RearGlassNoFrontFit", name: "05_01_CSR_Quote_RearGlassNoFrontFit",
params: { params: {
availableVaps: [testConstants.parts.rearWiperPart, testConstants.parts.rainDefensePart], wiperResponse: [testConstants.parts.rearWiperPart],
rainDefenseResponse: testConstants.parts.rainDefensePart,
damage: { damage: {
isRepair: false, isRepair: false,
glassToReplace: [testConstants.damages.rearWindshield], glassToReplace: [testConstants.damages.rearWindshield],
@ -518,11 +504,8 @@ const figmaScenarios = [
{ {
name: "05_01_CSR_Quote_Windshield+SideGlass", name: "05_01_CSR_Quote_Windshield+SideGlass",
params: { params: {
availableVaps: [ wiperResponse: [testConstants.parts.frontWiperPart, testConstants.parts.rearWiperPart],
testConstants.parts.frontWiperPart, rainDefenseResponse: testConstants.parts.rainDefensePart,
testConstants.parts.rearWiperPart,
testConstants.parts.rainDefensePart,
],
damage: { damage: {
isRepair: false, isRepair: false,
glassToReplace: [ glassToReplace: [
@ -586,11 +569,8 @@ const figmaScenarios = [
{ {
name: "05_01_CSR_Quote_SideGlass", name: "05_01_CSR_Quote_SideGlass",
params: { params: {
availableVaps: [ wiperResponse: [testConstants.parts.frontWiperPart, testConstants.parts.rearWiperPart],
testConstants.parts.frontWiperPart, rainDefenseResponse: testConstants.parts.rainDefensePart,
testConstants.parts.rearWiperPart,
testConstants.parts.rainDefensePart,
],
damage: { damage: {
isRepair: false, isRepair: false,
glassToReplace: [testConstants.damages.sideGlass], glassToReplace: [testConstants.damages.sideGlass],
@ -678,7 +658,8 @@ const figmaScenarios = [
{ {
name: "05_01_CSR_Quote_NoWiperFit", name: "05_01_CSR_Quote_NoWiperFit",
params: { params: {
availableVaps: [testConstants.parts.rainDefensePart], wiperResponse: [],
rainDefenseResponse: testConstants.parts.rainDefensePart,
damage: { damage: {
isRepair: false, isRepair: false,
glassToReplace: [testConstants.damages.frontWindshield], glassToReplace: [testConstants.damages.frontWindshield],
@ -821,6 +802,8 @@ describe("Service Package Review Block", () => {
propsData: props, propsData: props,
}); });
initializeWithDefault(wrapper);
// Act // Act
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
@ -851,6 +834,8 @@ describe("Service Package Review Block", () => {
propsData: props, propsData: props,
}); });
initializeWithDefault(wrapper);
// Act // Act
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
@ -874,6 +859,8 @@ describe("Service Package Review Block", () => {
// Act // Act
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
initializeWithDefault(wrapper);
// Assert // Assert
const includesFrontWiperCopy = wrapper.vm.displayContent.includes( const includesFrontWiperCopy = wrapper.vm.displayContent.includes(
testConstants.vapsCopy.frontWiperCopy testConstants.vapsCopy.frontWiperCopy
@ -893,6 +880,8 @@ describe("Service Package Review Block", () => {
propsData: generateDefaultProps(), propsData: generateDefaultProps(),
}); });
initializeWithDefault(wrapper);
// Act // Act
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
@ -908,7 +897,6 @@ describe("Service Package Review Block", () => {
it(`Should match figma scenario "${scenario.name}", iteration "${iteration.name}"`, async () => { it(`Should match figma scenario "${scenario.name}", iteration "${iteration.name}"`, async () => {
// Arrange // Arrange
let props = generateDefaultProps(); let props = generateDefaultProps();
props.availableVaps = scenario.params.availableVaps;
props.damage = scenario.params.damage; props.damage = scenario.params.damage;
props.glassParts = scenario.params.glassParts; props.glassParts = scenario.params.glassParts;
props.lineItems.supportingItems = scenario.params.supportingItems; props.lineItems.supportingItems = scenario.params.supportingItems;
@ -918,6 +906,11 @@ describe("Service Package Review Block", () => {
propsData: props, propsData: props,
}); });
wrapper.vm.initializeComponent({
wipers: scenario.params.wiperResponse,
rainDefense: scenario.params.rainDefenseResponse,
});
// Act // Act
await wrapper.vm.$nextTick(); 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() { function generateDefaultProps() {
return { return {
servicePackageOptionsCmsName: testConstants.cmsPropValues.servicePackageOptionsCmsName, servicePackageOptionsCmsName: testConstants.cmsPropValues.servicePackageOptionsCmsName,
defaultPackageItemsCmsName: testConstants.cmsPropValues.defaultPackageItemsCmsName, defaultPackageItemsCmsName: testConstants.cmsPropValues.defaultPackageItemsCmsName,
vapsItemsCmsName: testConstants.cmsPropValues.vapsItemsCmsName, vapsItemsCmsName: testConstants.cmsPropValues.vapsItemsCmsName,
availableVaps: [testConstants.parts.frontWiperPart, testConstants.parts.rainDefensePart],
lineItems: { lineItems: {
glassParts: [], glassParts: [],
supportingItems: [], supportingItems: [],

View file

@ -11,6 +11,9 @@ import {
getHighestFullySatisfiedTier, getHighestFullySatisfiedTier,
containsLineItemWithPartType, containsLineItemWithPartType,
} from "@/helpers/service-package-helper"; } 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 { export default {
name: "service-package-review", name: "service-package-review",
@ -18,14 +21,39 @@ export default {
servicePackageOptionsCmsName: String, servicePackageOptionsCmsName: String,
defaultPackageItemsCmsName: String, defaultPackageItemsCmsName: String,
vapsItemsCmsName: String, vapsItemsCmsName: String,
availableVaps: Array,
lineItems: Object, lineItems: Object,
damage: Object, damage: Object,
}, },
data() { data() {
return {}; return {
availableVaps: [],
};
}, },
methods: { 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() { editClicked() {
this.$emit("edit-clicked"); this.$emit("edit-clicked");
}, },

View file

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

View file

@ -0,0 +1,228 @@
import { shallowMount } from "@vue/test-utils";
import { getMountOptions } from "@/helpers/unit-test-helper.js";
import { applicationConfig } from "@/constants/application-config";
import { storeActions } from "@/constants/store-actions";
import schedule from "@/layouts/schedule/schedule.vue";
import store from "@/store";
import * as navigateToHeritage from "@/helpers/heritage-integration/navigation-helper";
import { nextTick } from "vue";
jest.mock("@/store", () => ({
commit: jest.fn(),
dispatch: jest.fn(),
}));
// Mock fetchCmsContentForPage
jest.mock("@/helpers/cms-content-helper", () => ({
fetchCmsContentForPage: () => Promise.resolve("content"),
splitCopyOnCMSPlaceHolder: jest.fn(() => ["A", "B"]),
}));
jest.mock(
"@/store",
() => {
return {};
},
{ virtual: true }
);
store.getters = {
order: {
schedule: {
date: "2020-01-01",
},
lineItems: {
glassParts: [],
supportingItems: [],
},
serviceLocation: {
appointmentType: "Inshop",
},
},
lineItems: {
glassParts: [],
supportingItems: [],
},
};
describe("schedule.vue", () => {
test("Should navigateWithoutSaving", async () => {
//Arrange
const { wrapper } = setupMocks({
customMountOptions: {
router: {
navigateWithoutSaving: jest.fn(),
},
route: { schedule },
},
});
wrapper.vm.dispatchStoreAction = jest.fn(() => {
return {
data: [],
};
});
//Act
await wrapper.vm.forwardButtonAction();
//Assert
expect(wrapper.vm.$router.navigateWithoutSaving).toHaveBeenCalled();
});
test("should pass arePagePrerequisitesValid with a mobile order and no providerNumber", () => {
//Arrange
const { wrapper } = setupMocks({});
store.getters = {
order: {
schedule: {
date: "2020-01-01",
},
serviceLocation: {
zipCode: "12345",
zipCodeCtu: "value",
appointmentType: "Mobile",
},
damage: {
isRepair: true,
},
referralNumber: "1234567",
},
payment: {
isInsurance: true,
},
lineItems: {
supportingItems: [],
},
};
let arePagePrerequisitesValid = wrapper.vm.arePagePrerequisitesValid();
expect(arePagePrerequisitesValid).toBe(true);
});
test("should pass arePagePrerequisitesValid with a inshop order and providerNumber", () => {
//Arrange
const { wrapper } = setupMocks({});
store.getters = {
order: {
schedule: {
date: "2020-01-01",
},
serviceLocation: {
zipCode: "12345",
zipCodeCtu: "value",
appointmentType: "Inshop",
provider: {
providerNumber: "5",
},
},
damage: {
isRepair: true,
},
referralNumber: "1234567",
},
payment: {
isInsurance: true,
},
lineItems: {
supportingItems: [],
},
};
let arePagePrerequisitesValid = wrapper.vm.arePagePrerequisitesValid();
expect(arePagePrerequisitesValid).toBe(true);
});
test("should fail arePagePrerequisitesValid with a replace with no glass parts", () => {
//Arrange
const { wrapper } = setupMocks({});
store.getters = {
order: {
schedule: {
date: "2020-01-01",
},
serviceLocation: {
zipCode: "12345",
zipCodeCtu: "value",
appointmentType: "Inshop",
provider: {
providerNumber: "5",
},
},
damage: {
isRepair: false,
},
referralNumber: "1234567",
},
payment: {
isInsurance: true,
},
lineItems: {
supportingItems: [],
glassParts: [],
},
};
let arePagePrerequisitesValid = wrapper.vm.arePagePrerequisitesValid();
expect(arePagePrerequisitesValid).toBe(false);
});
test("should fail arePagePrerequisitesValid without isInsurance", () => {
//Arrange
const { wrapper } = setupMocks({});
store.getters = {
order: {
schedule: {
date: "2020-01-01",
},
serviceLocation: {
zipCode: "12345",
zipCodeCtu: "value",
appointmentType: "Inshop",
provider: {
providerNumber: "5",
},
},
damage: {
isRepair: true,
},
referralNumber: "1234567",
},
payment: {
isInsurance: null,
},
lineItems: {
supportingItems: [],
glassParts: [],
},
};
let arePagePrerequisitesValid = wrapper.vm.arePagePrerequisitesValid();
expect(arePagePrerequisitesValid).toBe(false);
});
});
const mockCmsContent = {};
function setupMocks({ customMountOptions }) {
const mountOptions = getMountOptions({
...customMountOptions,
});
mountOptions.global.mocks["$store"] = store;
mountOptions["attachTo"] = document.body;
mountOptions.mixins = [
{
methods: {
getCmsContent: jest.fn().mockImplementation((widgetName, fieldName) => {
if (mockCmsContent[widgetName] && mockCmsContent[widgetName][fieldName])
return mockCmsContent[widgetName][fieldName];
}),
},
},
];
const wrapper = shallowMount(schedule, mountOptions);
wrapper.vm.setCmsContent = jest.fn();
return { wrapper };
}

View file

@ -0,0 +1,712 @@
import { shallowMount, flushPromises } from "@vue/test-utils";
import { getMountOptions } from "@/helpers/unit-test-helper.js";
import timeSlotModalQuestion from "@/layouts/schedule/time-slot-modal-question/time-slot-modal-question.vue";
import store from "@/store";
import {
RouteCodeFlags,
PREMIUM_TIME_SLOT_ID_FLAG,
PREMIUM_FEE_PART_TYPE,
} from "@/constants/schedule-constants";
describe("time-slot-modal-list-button-question.vue", () => {
test("When selected time slot is changed, a correctly formatted selectedTimeSlot should be emitted", async () => {
//Arrange
const date = "2023-07-01";
const timeSlotId = "testRouteCodeId";
const startTime = "8:00 AM";
const endTime = "8:30 AM";
const jobMaxMinutes = 100;
const expectedEmit = [
[
{
date: date,
routeCode: timeSlotId,
startTime: startTime,
endTime: endTime,
jobMaxMinutes: jobMaxMinutes.toString(),
},
],
];
const { wrapper } = setupMocks({
customMountOptions: {
propsData: {
estimatedServiceMinutesMaximum: jobMaxMinutes,
dateAndTimeSlotData: {
date: date,
timeSlots: [
{
id: timeSlotId,
startTime: startTime,
endTime: endTime,
},
],
},
},
},
});
wrapper.vm.$refs.timeSlots.closeModal = jest.fn();
wrapper.vm.selectedTimeSlotId = timeSlotId;
//Act
wrapper.vm.setSelectedTimeSlot();
//Assert
expect(wrapper.emitted()["update:modelValue"]).toEqual(expectedEmit);
});
});
describe("time-slot-modal-list-button-question.vue Supplemental information", () => {
test("The correct supplemental information should be obtained from CMS - Mobile - Not Premium ", async () => {
//Arrange
const mobileCmsWidgetName = "MobileNotPremium";
const { wrapper } = setupMocks({
customMountOptions: {
propsData: {
modelValue: { routeCode: "test" },
appointmentType: "Mobile",
mobileCmsWidgetName: mobileCmsWidgetName,
},
},
});
//Assert
expect(wrapper.vm.supplementalInformationBlock).toEqual(
mockCmsContent[mobileCmsWidgetName]["BodyText"]
);
});
test("The correct supplemental information should be obtained from CMS - Mobile - Premium", async () => {
//Arrange
const mobileCmsWidgetName = "MobilePremium";
const { wrapper } = setupMocks({
customMountOptions: {
propsData: {
modelValue: { routeCode: "test" + PREMIUM_TIME_SLOT_ID_FLAG },
appointmentType: "Mobile",
mobileCmsWidgetName: mobileCmsWidgetName,
},
},
});
//Assert
expect(wrapper.vm.supplementalInformationBlock).toEqual(
mockCmsContent[mobileCmsWidgetName]["BodyText"]
);
});
test("No supplemental information should be obtained from CMS - Inshop", async () => {
//Arrange
const inshopWidgetName = "Inshop";
const { wrapper } = setupMocks({
customMountOptions: {
propsData: {
modelValue: { routeCode: "test" },
appointmentType: "Inshop",
cmsWidgetName: inshopWidgetName,
},
},
});
//Assert
expect(wrapper.vm.supplementalInformationBlock).toBeNull();
});
test("No supplemental information should be obtained from CMS - Drop off, nothing selected", async () => {
//Arrange
const dropOffWidgetName = "Dropoff";
const { wrapper } = setupMocks({
customMountOptions: {
propsData: {
modelValue: null,
appointmentType: "Dropoff",
cmsWidgetName: dropOffWidgetName,
},
},
});
//Assert
expect(wrapper.vm.supplementalInformationBlock).toBeNull();
});
test("Correct supplemental information should be retrieved, Dropoff regular", async () => {
//Arrange
const dropOffWidgetName = "Dropoff";
const { wrapper } = setupMocks({
customMountOptions: {
propsData: {
modelValue: { routeCode: "Test" + RouteCodeFlags.ALL_DAY_DROP_OFF },
appointmentType: "Dropoff",
dropoffCmsWidgetName: dropOffWidgetName,
},
},
});
//Assert
expect(wrapper.vm.supplementalInformationBlock).toEqual(
mockCmsContent[dropOffWidgetName]["BodyText"]
);
});
test("Correct supplemental information should be retrieved, Dropoff Overnight", async () => {
//Arrange
const overnightDropOffCmsWidgetName = "Overnight";
const { wrapper } = setupMocks({
customMountOptions: {
propsData: {
modelValue: { routeCode: "Test" + RouteCodeFlags.OVERNIGHT_DROP_OFF },
appointmentType: "Dropoff",
overnightDropOffCmsWidgetName: overnightDropOffCmsWidgetName,
},
},
});
//Assert
expect(wrapper.vm.supplementalInformationBlock).toEqual(
mockCmsContent[overnightDropOffCmsWidgetName]["BodyText"]
);
});
test("Correct supplemental information should be retrieved, Dropoff Sameday", async () => {
//Arrange
const sameDayDropOffCmsWidgetName = "Sameday";
const date = new Date().toISOString().split("T")[0];
const timeSlotId = "testRouteCodeId";
const startTime = "8:00 AM";
const endTime = "8:30 AM";
const jobMaxMinutes = 100;
const { wrapper } = setupMocks({
customMountOptions: {
propsData: {
estimatedServiceMinutesMaximum: jobMaxMinutes,
dateAndTimeSlotData: {
date: date,
timeSlots: [
{
id: timeSlotId,
startTime: startTime,
endTime: endTime,
},
],
},
modelValue: { routeCode: "Test" + RouteCodeFlags.ALL_DAY_DROP_OFF },
appointmentType: "Dropoff",
sameDayDropOffCmsWidgetName: sameDayDropOffCmsWidgetName,
},
},
});
//Assert
expect(wrapper.vm.supplementalInformationBlock).toEqual(
mockCmsContent[sameDayDropOffCmsWidgetName]["BodyText"]
);
});
});
describe("time-slot-modal-list-button-question.vue Disclaimer", () => {
test("The correct disclaimer information should be obtained from CMS - Dropoff - Sameday ", async () => {
//Arrange
const sameDayDropOffCmsWidgetName = "Sameday";
const date = new Date().toISOString().split("T")[0];
const timeSlotId = "testRouteCodeId";
const startTime = "8:00 AM";
const endTime = "8:30 AM";
const jobMaxMinutes = 100;
const { wrapper } = setupMocks({
customMountOptions: {
propsData: {
estimatedServiceMinutesMaximum: jobMaxMinutes,
dateAndTimeSlotData: {
date: date,
timeSlots: [
{
id: timeSlotId,
startTime: startTime,
endTime: endTime,
},
],
},
modelValue: { routeCode: "Test" + RouteCodeFlags.ALL_DAY_DROP_OFF },
appointmentType: "Dropoff",
sameDayDropOffCmsWidgetName: sameDayDropOffCmsWidgetName,
},
},
});
//Assert
expect(wrapper.vm.disclaimerTextBlockCopy).toEqual(
mockCmsContent[sameDayDropOffCmsWidgetName]["FooterText"]
);
});
test("Correct disclaimer information should be retrieved, Dropoff regular", async () => {
//Arrange
const dropOffWidgetName = "Dropoff";
const { wrapper } = setupMocks({
customMountOptions: {
propsData: {
modelValue: { routeCode: "Test" + RouteCodeFlags.ALL_DAY_DROP_OFF },
appointmentType: "Dropoff",
dropoffCmsWidgetName: dropOffWidgetName,
},
},
});
//Assert
expect(wrapper.vm.disclaimerTextBlockCopy).toEqual(
mockCmsContent[dropOffWidgetName]["FooterText"]
);
});
test("Correct disclaimer information should be retrieved, Dropoff overnight", async () => {
//Arrange
const overnightDropOffCmsWidgetName = "Overnight";
const { wrapper } = setupMocks({
customMountOptions: {
propsData: {
modelValue: { routeCode: "Test" + RouteCodeFlags.OVERNIGHT_DROP_OFF },
appointmentType: "Dropoff",
overnightDropOffCmsWidgetName: overnightDropOffCmsWidgetName,
},
},
});
//Assert
expect(wrapper.vm.disclaimerTextBlockCopy).toEqual(
mockCmsContent[overnightDropOffCmsWidgetName]["FooterText"]
);
});
test("No disclaimer information should be retrieved, non-dropoff", async () => {
//Arrange
const inshopWidgetName = "Inshop";
const { wrapper } = setupMocks({
customMountOptions: {
propsData: {
modelValue: { routeCode: "test" },
appointmentType: "Inshop",
cmsWidgetName: inshopWidgetName,
},
},
});
//Assert
expect(wrapper.vm.disclaimerTextBlockCopy).toBeNull();
});
});
describe("time-slot-modal-list-button-question.vue Duration", () => {
test("The correct duration information should be displayed - Dropoff - Sameday ", async () => {
//Arrange
const sameDayDropOffCmsWidgetName = "Sameday";
const date = new Date().toISOString().split("T")[0];
const timeSlotId = "testRouteCodeId";
const startTime = "8:00 AM";
const endTime = "8:30 AM";
const jobMaxMinutes = 100;
const { wrapper } = setupMocks({
customMountOptions: {
propsData: {
estimatedServiceMinutesMaximum: jobMaxMinutes,
dateAndTimeSlotData: {
date: date,
timeSlots: [
{
id: timeSlotId,
startTime: startTime,
endTime: endTime,
},
],
},
modelValue: { routeCode: "Test" + RouteCodeFlags.ALL_DAY_DROP_OFF },
appointmentType: "Dropoff",
sameDayDropOffCmsWidgetName: sameDayDropOffCmsWidgetName,
},
},
});
//Assert
expect(wrapper.vm.durationTextBlockCopy).toEqual(
mockCmsContent[sameDayDropOffCmsWidgetName]["SubheaderText"]
);
});
test("Correct duration information should be retrieved, Dropoff overnight", async () => {
//Arrange
const overnightDropOffCmsWidgetName = "Overnight";
const { wrapper } = setupMocks({
customMountOptions: {
propsData: {
modelValue: { routeCode: "Test" + RouteCodeFlags.OVERNIGHT_DROP_OFF },
appointmentType: "Dropoff",
overnightDropOffCmsWidgetName: overnightDropOffCmsWidgetName,
},
},
});
//Assert
expect(wrapper.vm.durationTextBlockCopy).toEqual(
mockCmsContent[overnightDropOffCmsWidgetName]["SubheaderText"]
);
});
test("Correct duration information should be retrieved, Dropoff", async () => {
//Arrange
const dropoffCmsWidgetName = "Dropoff";
const { wrapper } = setupMocks({
customMountOptions: {
propsData: {
modelValue: { routeCode: "Test" + RouteCodeFlags.ALL_DAY_DROP_OFF },
appointmentType: "Dropoff",
dropoffCmsWidgetName: dropoffCmsWidgetName,
},
},
});
//Assert
expect(wrapper.vm.durationTextBlockCopy).toEqual(
mockCmsContent[dropoffCmsWidgetName]["SubheaderText"]
);
});
test("Correct duration information should be retrieved, Inshop - convert to hours", async () => {
//Arrange
const cmsWidgetName = "Inshop";
const { wrapper } = setupMocks({
customMountOptions: {
propsData: {
estimatedServiceMinutesMaximum: 180,
estimatedServiceMinutesMinimum: 120,
modelValue: { routeCode: "Test" },
appointmentType: "Inshop",
cmsWidgetName: cmsWidgetName,
},
},
});
//Assert
expect(wrapper.vm.durationTextBlockCopy).toEqual(
mockCmsContent[cmsWidgetName]["SubheaderText"] + " 2 - 3 hours"
);
});
test("Correct duration information should be retrieved, Inshop - convert to minutes", async () => {
//Arrange
const cmsWidgetName = "Inshop";
const { wrapper } = setupMocks({
customMountOptions: {
propsData: {
estimatedServiceMinutesMaximum: 90,
estimatedServiceMinutesMinimum: 60,
modelValue: { routeCode: "Test" },
appointmentType: "Inshop",
cmsWidgetName: cmsWidgetName,
},
},
});
//Assert
expect(wrapper.vm.durationTextBlockCopy).toEqual(
mockCmsContent[cmsWidgetName]["SubheaderText"] + " 60 - 90 minutes"
);
});
test("Correct duration information should be retrieved, Inshop - min = maximum time", async () => {
//Arrange
const cmsWidgetName = "Inshop";
const { wrapper } = setupMocks({
customMountOptions: {
propsData: {
estimatedServiceMinutesMaximum: 240,
estimatedServiceMinutesMinimum: 240,
modelValue: { routeCode: "Test" },
appointmentType: "Inshop",
cmsWidgetName: cmsWidgetName,
},
},
});
//Assert
expect(wrapper.vm.durationTextBlockCopy).toEqual(
mockCmsContent[cmsWidgetName]["SubheaderText"] + " 4 hours"
);
});
test("No duration information should be displayed, Mobile", async () => {
//Arrange
const mobileCmsWidgetName = "Mobile";
const { wrapper } = setupMocks({
customMountOptions: {
propsData: {
estimatedServiceMinutesMaximum: 240,
estimatedServiceMinutesMinimum: 240,
modelValue: { routeCode: "Test" },
appointmentType: "Mobile",
mobileCmsWidgetName: mobileCmsWidgetName,
},
},
});
//Assert
expect(wrapper.vm.durationTextBlockCopy).toBeNull();
});
});
describe("time-slot-modal-list-button-question.vue Available Timeslots", () => {
test("Available Timeslots should be formatted correctly - Dropoff - Sameday ", async () => {
//Arrange
const sameDayDropOffCmsWidgetName = "Sameday";
const date = new Date().toISOString().split("T")[0];
const timeSlotId = "testRouteCodeId";
const startTime = "8:00 AM";
const endTime = "8:30 AM";
const jobMaxMinutes = 100;
const { wrapper } = setupMocks({
customMountOptions: {
propsData: {
estimatedServiceMinutesMaximum: jobMaxMinutes,
dateAndTimeSlotData: {
date: date,
timeSlots: [
{
id: timeSlotId,
startTime: startTime,
endTime: endTime,
},
],
},
appointmentType: "Dropoff",
sameDayDropOffCmsWidgetName: sameDayDropOffCmsWidgetName,
},
},
});
//Assert
const expectedTimeSlotObject = {
buttonLabel: mockCmsContent[sameDayDropOffCmsWidgetName]["HeaderText"],
value: timeSlotId,
};
expect(wrapper.vm.availableTimeSlots[0]).toEqual(expectedTimeSlotObject);
});
test("Available Timeslots should be formatted correctly - Dropoff - Overnight ", async () => {
//Arrange
const overnightDropOffCmsWidgetName = "Overnight";
const date = new Date().toISOString().split("T")[0];
const timeSlotId = "testRouteCodeId" + RouteCodeFlags.OVERNIGHT_DROP_OFF;
const startTime = "8:00 AM";
const endTime = "8:30 AM";
const jobMaxMinutes = 100;
const { wrapper } = setupMocks({
customMountOptions: {
propsData: {
estimatedServiceMinutesMaximum: jobMaxMinutes,
dateAndTimeSlotData: {
date: date,
timeSlots: [
{
id: timeSlotId,
startTime: startTime,
endTime: endTime,
},
],
},
appointmentType: "Dropoff",
overnightDropOffCmsWidgetName: overnightDropOffCmsWidgetName,
},
},
});
//Assert
const expectedTimeSlotObject = {
buttonLabel: mockCmsContent[overnightDropOffCmsWidgetName]["HeaderText"],
value: timeSlotId,
};
expect(wrapper.vm.availableTimeSlots[0]).toEqual(expectedTimeSlotObject);
});
test("Available Timeslots should be formatted correctly - Dropoff", async () => {
//Arrange
const dropoffCmsWidgetName = "Dropoff";
const date = "2020-01-01";
const timeSlotId = "testRouteCodeId" + RouteCodeFlags.ALL_DAY_DROP_OFF;
const startTime = "8:00 AM";
const endTime = "8:30 AM";
const jobMaxMinutes = 100;
const { wrapper } = setupMocks({
customMountOptions: {
propsData: {
estimatedServiceMinutesMaximum: jobMaxMinutes,
dateAndTimeSlotData: {
date: date,
timeSlots: [
{
id: timeSlotId,
startTime: startTime,
endTime: endTime,
},
],
},
appointmentType: "Dropoff",
dropoffCmsWidgetName: dropoffCmsWidgetName,
},
},
});
//Assert
const expectedTimeSlotObject = {
buttonLabel: mockCmsContent[dropoffCmsWidgetName]["HeaderText"],
value: timeSlotId,
};
expect(wrapper.vm.availableTimeSlots[0]).toEqual(expectedTimeSlotObject);
});
test("Available Timeslots should be formatted correctly - Mobile not premium", async () => {
//Arrange
const mobileCmsWidgetName = "MobileNotPremium";
const date = "2020-01-01";
const timeSlotId = "testRouteCodeId";
const startTime = "8:00";
const endTime = "8:30";
const jobMaxMinutes = 100;
const { wrapper } = setupMocks({
customMountOptions: {
propsData: {
estimatedServiceMinutesMaximum: jobMaxMinutes,
dateAndTimeSlotData: {
date: date,
timeSlots: [
{
id: timeSlotId,
startTime: startTime,
endTime: endTime,
},
],
},
appointmentType: "Mobile",
mobileCmsWidgetName: mobileCmsWidgetName,
},
},
});
//Assert
const expectedTimeSlotObject = {
buttonLabel: startTime + " AM - " + endTime + " AM",
value: timeSlotId,
};
expect(wrapper.vm.availableTimeSlots[0]).toEqual(expectedTimeSlotObject);
});
test("Available Timeslots should be formatted correctly - Mobile with premium", async () => {
//Arrange
const mobilePremiumCmsWidgetName = "MobilePremium";
const date = "2020-01-01";
const timeSlotId = "testRouteCodeId";
const startTime = "8:00";
const endTime = "8:30";
const jobMaxMinutes = 100;
const { wrapper } = setupMocks({
customMountOptions: {
propsData: {
estimatedServiceMinutesMaximum: jobMaxMinutes,
premiumAppointmentFee: {
partType: PREMIUM_FEE_PART_TYPE,
},
dateAndTimeSlotData: {
date: date,
timeSlots: [
{
id: timeSlotId,
startTime: startTime,
endTime: endTime,
offerPremium: true,
},
],
},
appointmentType: "Mobile",
mobilePremiumCmsWidgetName: mobilePremiumCmsWidgetName,
},
},
});
//Assert
const expectedTimeSlotObject = {
additionalButtonData: {
isPremiumAppointment: true,
},
buttonLabel: mockCmsContent[mobilePremiumCmsWidgetName]["HeaderText"],
buttonLabelSubCopy: "+$15.99",
value: timeSlotId + PREMIUM_TIME_SLOT_ID_FLAG,
};
expect(wrapper.vm.availableTimeSlots[0]).toEqual(expectedTimeSlotObject);
});
test("Available Timeslots should be formatted correctly - Inshop", async () => {
//Arrange
const cmsWidgetName = "Inshop";
const date = "2020-01-01";
const timeSlotId = "testRouteCodeId";
const startTime = "8:00";
const endTime = "8:30";
const jobMaxMinutes = 100;
const { wrapper } = setupMocks({
customMountOptions: {
propsData: {
estimatedServiceMinutesMaximum: jobMaxMinutes,
dateAndTimeSlotData: {
date: date,
timeSlots: [
{
id: timeSlotId,
startTime: startTime,
endTime: endTime,
},
],
},
appointmentType: "Inshop",
cmsWidgetName: cmsWidgetName,
},
},
});
//Assert
const expectedTimeSlotObject = { buttonLabel: startTime + " AM", value: timeSlotId };
expect(wrapper.vm.availableTimeSlots[0]).toEqual(expectedTimeSlotObject);
});
});
const mockCmsContent = {
MobileNotPremium: {
BodyText: "Mobile, not premium BodyText",
FooterText: "Mobile, not premium FooterText",
SubheaderText: "Mobile, not premium SubheaderText",
HeaderText: "Mobile, not premium HeaderText",
},
MobilePremium: {
"BodyText:": "Mobile, with premium BodyText",
FooterText: "Mobile, with premium FooterText",
SubheaderText: "Mobile, with premium SubheaderText",
HeaderText: "Mobile, with premium HeaderText",
},
Inshop: {
BodyText: "Inshop BodyText",
FooterText: "Inshop FooterText",
SubheaderText: "Inshop SubheaderText",
HeaderText: "Inshop HeaderText",
},
Dropoff: {
BodyText: "Dropoff BodyText",
FooterText: "Dropoff FooterText",
SubheaderText: "Dropoff SubheaderText",
HeaderText: "Dropoff HeaderText",
},
Overnight: {
BodyText: "Dropoff Overnight",
FooterText: "Dropoff, Overnight FooterText",
SubheaderText: "Dropoff, Overnight SubheaderText",
HeaderText: "Dropoff, Overnight HeaderText",
},
Sameday: {
BodyText: "Dropoff Sameday",
FooterText: "Dropoff, Sameday FooterText",
SubheaderText: "Dropoff, Sameday SubheaderText",
HeaderText: "Dropoff, Sameday HeaderText",
},
};
function setupMocks({ customMountOptions }) {
const mountOptions = getMountOptions({
...customMountOptions,
});
mountOptions.global.mocks["$store"] = store;
mountOptions["attachTo"] = document.body;
mountOptions.mixins = [
{
methods: {
getCmsContent: jest.fn().mockImplementation((widgetName, fieldName) => {
if (mockCmsContent[widgetName] && mockCmsContent[widgetName][fieldName])
return mockCmsContent[widgetName][fieldName];
}),
getTotalLineItemPrice: jest.fn().mockImplementation(() => 15.99),
},
},
];
const wrapper = shallowMount(timeSlotModalQuestion, mountOptions);
wrapper.vm.setCmsContent = jest.fn();
return { wrapper };
}

View file

@ -150,7 +150,7 @@ export default {
return this.modelValue?.routeCode; return this.modelValue?.routeCode;
}, },
set: function (newValue) { set: function (newValue) {
// Button Question only supports Number, or String data types so we must get the full object to emit // Button Question only supports Number, or String data types so we must convert to the full object before emitting
this.selectedValue = this.getSelectedTimeSlotObject(newValue); this.selectedValue = this.getSelectedTimeSlotObject(newValue);
}, },
}, },
@ -326,7 +326,7 @@ export default {
} }
}, },
modal() { modal() {
return this.$refs["timeSlots"]; return this.$refs[this.modalName];
}, },
}, },
methods: { methods: {

View file

@ -17,7 +17,10 @@
@click-event="openModal" /> @click-event="openModal" />
</div> </div>
<div v-show="errorMessage" class="row my-1 form-test-error"> <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 }} {{ errorMessage }}
</span> </span>
</div> </div>

View file

@ -52,6 +52,7 @@ jest.mock("@/store", () => ({
damage: { damage: {
glassToReplace: [], glassToReplace: [],
}, },
order: { serviceLocation: { zipCode: "11111" } },
}, },
})); }));
@ -133,6 +134,7 @@ describe("vehicle-damage.vue", () => {
getters: { getters: {
vehicle: {}, vehicle: {},
payment: { insuranceCoverage: { isVerified: false } }, payment: { insuranceCoverage: { isVerified: false } },
order: { serviceLocation: { zipCode: "11111" } },
}, },
}, },
}, },
@ -175,7 +177,7 @@ describe("vehicle-damage.vue", () => {
expect(wrapper.vm.selectedGlassToReplace()).toEqual(expectedGlassToReplace); expect(wrapper.vm.selectedGlassToReplace()).toEqual(expectedGlassToReplace);
expect(baseMixin.methods.dispatchStoreAction).toBeCalledWith( expect(baseMixin.methods.dispatchStoreAction).toBeCalledWith(
storeActions.GET_DAMAGE_OPTIONS, storeActions.GET_DAMAGE_OPTIONS,
{ carId: "C00000000" } { carId: "C00000000", zipCode: "11111" }
); );
}); });
@ -241,6 +243,7 @@ describe("vehicle-damage.vue", () => {
getters: { getters: {
vehicle: {}, vehicle: {},
payment: { insuranceCoverage: { isVerified: false } }, payment: { insuranceCoverage: { isVerified: false } },
order: { serviceLocation: { zipCode: "11111" } },
}, },
}, },
}, },
@ -270,11 +273,10 @@ describe("vehicle-damage.vue", () => {
expect(wrapper.vm.selectedGlassToReplace()).toEqual(expectedGlassToReplace); expect(wrapper.vm.selectedGlassToReplace()).toEqual(expectedGlassToReplace);
expect(baseMixin.methods.dispatchStoreAction).toBeCalledWith( expect(baseMixin.methods.dispatchStoreAction).toBeCalledWith(
storeActions.GET_DAMAGE_OPTIONS, storeActions.GET_DAMAGE_OPTIONS,
{ carId: "C00000000" } { carId: "C00000000", zipCode: "11111" }
); );
}); });
}); });
describe("alert", () => { describe("alert", () => {
test("when displayVehicleChangeAlert router params is true, the alert: 'vehicleChangeAlert' should be rendered", () => { test("when displayVehicleChangeAlert router params is true, the alert: 'vehicleChangeAlert' should be rendered", () => {
// Arrange & Act // Arrange & Act
@ -321,7 +323,6 @@ describe("vehicle-damage.vue", () => {
expect(wrapper.findComponent({ ref: "vehicleChangeAlert" }).exists()).toBe(false); expect(wrapper.findComponent({ ref: "vehicleChangeAlert" }).exists()).toBe(false);
}); });
}); });
describe("glass selections and corresponding variables", () => { describe("glass selections and corresponding variables", () => {
test("isWindshieldDamageLocation is true when windshield is selected", async () => { test("isWindshieldDamageLocation is true when windshield is selected", async () => {
//Arrange //Arrange
@ -453,7 +454,6 @@ describe("vehicle-damage.vue", () => {
expect(wrapper.vm.isPassengerSideReplace).toEqual(true); expect(wrapper.vm.isPassengerSideReplace).toEqual(true);
}); });
}); });
describe("state validations", () => { describe("state validations", () => {
test("CarId set, arePagePrerequisitesValid should be true ", async () => { test("CarId set, arePagePrerequisitesValid should be true ", async () => {
//Arrange //Arrange
@ -474,7 +474,6 @@ describe("vehicle-damage.vue", () => {
expect(arePagePrerequisitesValid).toBe(true); expect(arePagePrerequisitesValid).toBe(true);
}); });
}); });
describe("input validations", () => { describe("input validations", () => {
// THE FOLLOWING TEST IS NOT NECESSARILY REQUIRED FOR COVERAGE // THE FOLLOWING TEST IS NOT NECESSARILY REQUIRED FOR COVERAGE
// BUT KEEP FOR AN EXAMPLE OF A VALIDATION TEST // BUT KEEP FOR AN EXAMPLE OF A VALIDATION TEST
@ -501,7 +500,6 @@ describe("vehicle-damage.vue", () => {
}); });
}); });
}); });
describe("get glass options from store", () => { describe("get glass options from store", () => {
const damageLocations = [ const damageLocations = [
["Windshield", [damageLocationsSelected.WINDSHIELD]], ["Windshield", [damageLocationsSelected.WINDSHIELD]],
@ -528,6 +526,7 @@ describe("vehicle-damage.vue", () => {
eventBusItem: jest.fn(), eventBusItem: jest.fn(),
damage: { glassToReplace: [{ glassLocation: damageLocation }] }, damage: { glassToReplace: [{ glassLocation: damageLocation }] },
isRepair: true, isRepair: true,
order: { serviceLocation: { zipCode: "11111" } },
}; };
var glassSelections = wrapper.vm.getDamageLocationsFromStore(); var glassSelections = wrapper.vm.getDamageLocationsFromStore();
@ -605,6 +604,7 @@ describe("vehicle-damage.vue", () => {
isRepair: isRepair, isRepair: isRepair,
numberOfChips: 2, numberOfChips: 2,
}, },
order: { serviceLocation: { zipCode: "11111" } },
}; };
var windshieldSelections = wrapper.vm.getWindshieldOptionsFromStore(); var windshieldSelections = wrapper.vm.getWindshieldOptionsFromStore();
@ -641,6 +641,7 @@ describe("vehicle-damage.vue", () => {
glassToReplace: [{ glassLocation: damageLocation, glassName: damageName }], glassToReplace: [{ glassLocation: damageLocation, glassName: damageName }],
}, },
isRepair: true, isRepair: true,
order: { serviceLocation: { zipCode: "11111" } },
}; };
var glassSelections = wrapper.vm.getDriverSideReplaceOptionsFromStore(); var glassSelections = wrapper.vm.getDriverSideReplaceOptionsFromStore();
@ -677,6 +678,7 @@ describe("vehicle-damage.vue", () => {
glassToReplace: [{ glassLocation: damageLocation, glassName: damageName }], glassToReplace: [{ glassLocation: damageLocation, glassName: damageName }],
}, },
isRepair: true, isRepair: true,
order: { serviceLocation: { zipCode: "11111" } },
}; };
var glassSelections = wrapper.vm.getPassengerSideReplaceOptionsFromStore(); var glassSelections = wrapper.vm.getPassengerSideReplaceOptionsFromStore();
@ -711,6 +713,7 @@ describe("vehicle-damage.vue", () => {
glassToReplace: [{ glassLocation: damageLocation, glassName: damageName }], glassToReplace: [{ glassLocation: damageLocation, glassName: damageName }],
}, },
isRepair: true, isRepair: true,
order: { serviceLocation: { zipCode: "11111" } },
}; };
var glassSelections = wrapper.vm.getRearReplaceOptionsFromStore(); var glassSelections = wrapper.vm.getRearReplaceOptionsFromStore();
@ -720,7 +723,6 @@ describe("vehicle-damage.vue", () => {
} }
); );
}); });
describe("hide back button", () => { describe("hide back button", () => {
test("if claim registration is delayed => should hide back button", () => { test("if claim registration is delayed => should hide back button", () => {
// Arrange // Arrange
@ -744,6 +746,7 @@ describe("vehicle-damage.vue", () => {
getters: { getters: {
vehicle: {}, vehicle: {},
payment: { insuranceCoverage: { isVerified: true } }, payment: { insuranceCoverage: { isVerified: true } },
order: { serviceLocation: { zipCode: "11111" } },
}, },
}, },
}, },
@ -784,6 +787,7 @@ function setupMocks({ pageHeaderWidgetHeaderText, mountOptionsMockData, funnelCo
isVerified: false, isVerified: false,
}, },
}, },
order: { serviceLocation: { zipCode: "11111" } },
}, },
}, },
}; };

View file

@ -81,7 +81,7 @@ import { getFunnelCookie } from "@/helpers/heritage-integration/cookie-helper";
import store from "@/store"; import store from "@/store";
import baseMixin from "@/mixins/base-mixin"; import baseMixin from "@/mixins/base-mixin";
import { queryStrings } from "@/constants/query-strings";
// DEFINE VALIDATION RULES // DEFINE VALIDATION RULES
defineRule("replace-options-required", required(errorMessages.REPLACE_OPTIONS_REQUIRED)); defineRule("replace-options-required", required(errorMessages.REPLACE_OPTIONS_REQUIRED));
@ -90,9 +90,20 @@ export default {
async beforeRouteEnter(to, from, next) { async beforeRouteEnter(to, from, next) {
// Call APIs // Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage); const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString);
const lowerCaseParams = new URLSearchParams();
for (const [name, value] of urlParams) {
lowerCaseParams.append(name.toLowerCase(), value);
}
const zip = lowerCaseParams.get(queryStrings.ZIP_CODE)
? lowerCaseParams.get(queryStrings.ZIP_CODE)
: store.getters.order.serviceLocation.zipCode;
const damageOptionsPromise = baseMixin.methods.dispatchStoreAction( const damageOptionsPromise = baseMixin.methods.dispatchStoreAction(
storeActions.GET_DAMAGE_OPTIONS, storeActions.GET_DAMAGE_OPTIONS,
{ carId: store.getters.vehicle.carId } { carId: store.getters.vehicle.carId, zipCode: zip }
); );
// Settle promises and get results // Settle promises and get results
@ -135,6 +146,7 @@ export default {
}, },
selectedWindshieldOptions: this.getWindshieldOptionsFromStore(), selectedWindshieldOptions: this.getWindshieldOptionsFromStore(),
selectedRearReplaceOptions: this.getRearReplaceOptionsFromStore(), selectedRearReplaceOptions: this.getRearReplaceOptionsFromStore(),
serviceZipCode: this.getZipFromStore() ?? this.$route.query.zipcode,
}; };
}, },
mounted() { mounted() {
@ -157,6 +169,18 @@ export default {
true true
); );
} }
if (this.serviceZipCode) {
this.pushEventToGA(
this.GaCategories.FUNNEL_ENTRY,
this.GaActions.ZIP_CODE_PROVIDED,
this.serviceZipCode,
true
);
}
},
getZipFromStore() {
return this.$store.getters.order.serviceLocation.zipCode;
}, },
backButtonAction() { backButtonAction() {

View file

@ -1,9 +1,5 @@
<template> <template>
<dropdownQuestion <dropdownQuestion disableAutoFill v-model="selectedValue" :valueAsKey="true" />
:options="values"
disableAutoFill
v-model="selectedValue"
:isDisabled="!values.length" />
</template> </template>
<script> <script>
@ -12,16 +8,8 @@ import dropdownQuestion from "@/digital-components/dropdown-question/dropdown-qu
export default { export default {
name: "vehicle-question", name: "vehicle-question",
data() {
return {
values: [],
selectedIndex: null,
};
},
props: { props: {
modelValue: String, modelValue: String,
updateValues: Function,
}, },
components: { components: {
@ -30,33 +18,13 @@ export default {
computed: { computed: {
selectedValue: { selectedValue: {
get() { get: function () {
return this.selectedIndex?.toString(); return this.modelValue?.toString();
}, },
set(newValue) { set: function (newValue) {
this.selectedIndex = newValue;
newValue = newValue != null && newValue > -1 ? this.values[newValue] : null;
this.$emit("update:modelValue", newValue); this.$emit("update:modelValue", newValue);
}, },
}, },
}, },
methods: {
async getNewValues(selectedYMMS) {
const results = await this.updateValues();
this.values = results?.data;
if (this.values.length == 1) {
this.selectedValue = 0;
} else {
this.selectedValue =
selectedYMMS != null ? this.values.indexOf(selectedYMMS) : null;
}
},
clearValues() {
this.values = [];
this.selectedValue = null;
},
},
}; };
</script> </script>

View file

@ -15,8 +15,9 @@
ref="vehicleYearQuestion" ref="vehicleYearQuestion"
class="mb-2 mt-4" class="mb-2 mt-4"
v-model="selectedYear" v-model="selectedYear"
:isDisabled="!yearOptions.length"
:options="yearOptions"
cmsWidgetName="VehicleYearQuestion" cmsWidgetName="VehicleYearQuestion"
:updateValues="updateYearValues"
validationRules="year-required" validationRules="year-required"
placeHolderText="Select year" placeHolderText="Select year"
inputId="yearQuestionField" /> inputId="yearQuestionField" />
@ -25,8 +26,9 @@
ref="vehicleMakeQuestion" ref="vehicleMakeQuestion"
class="mb-2 mt-4" class="mb-2 mt-4"
v-model="selectedMake" v-model="selectedMake"
:isDisabled="!makeOptions.length"
:options="makeOptions"
cmsWidgetName="VehicleMakeQuestion" cmsWidgetName="VehicleMakeQuestion"
:updateValues="updateMakeValues"
validationRules="make-required" validationRules="make-required"
placeHolderText="Select make" placeHolderText="Select make"
inputId="makeQuestionField" /> inputId="makeQuestionField" />
@ -36,7 +38,8 @@
class="mb-2 mt-4" class="mb-2 mt-4"
v-model="selectedModel" v-model="selectedModel"
cmsWidgetName="VehicleModelQuestion" cmsWidgetName="VehicleModelQuestion"
:updateValues="updateModelValues" :isDisabled="!modelOptions.length"
:options="modelOptions"
validationRules="model-required" validationRules="model-required"
placeHolderText="Select model" placeHolderText="Select model"
inputId="modelQuestionField" /> inputId="modelQuestionField" />
@ -46,7 +49,8 @@
class="mb-2 mt-4" class="mb-2 mt-4"
v-model="selectedStyle" v-model="selectedStyle"
cmsWidgetName="VehicleStyleQuestion" cmsWidgetName="VehicleStyleQuestion"
:updateValues="updateStyleValues" :isDisabled="!styleOptions.length"
:options="styleOptions"
validationRules="style-required" validationRules="style-required"
placeHolderText="Select style" placeHolderText="Select style"
inputId="styleQuestionField" /> inputId="styleQuestionField" />
@ -101,10 +105,14 @@ export default {
name: "vehicle", name: "vehicle",
data() { data() {
return { return {
selectedYear: null, selectedYear: this.selectedYearfromStore(),
selectedMake: null, selectedMake: this.selectedMakefromStore(),
selectedModel: null, selectedModel: this.selectedModelfromStore(),
selectedStyle: null, selectedStyle: this.selectedStylefromStore(),
yearOptions: [],
makeOptions: [],
modelOptions: [],
styleOptions: [],
}; };
}, },
@ -113,10 +121,6 @@ export default {
validationRules: String, validationRules: String,
}, },
mounted() {
this.$refs["vehicleYearQuestion"].getNewValues(this.selectedYearfromStore);
},
async beforeRouteEnter(to, from, next) { async beforeRouteEnter(to, from, next) {
// Call APIs // Call APIs
@ -124,6 +128,43 @@ export default {
const experimentForLogging = store.getters.applicationUser.experiments.find( const experimentForLogging = store.getters.applicationUser.experiments.find(
(e) => e.universeName === experimentUniverses.CONCEPT_FUNNEL (e) => e.universeName === experimentUniverses.CONCEPT_FUNNEL
); );
const yearQuestionInitialDataPromise = baseMixin.methods.dispatchStoreAction(
storeActions.GET_VEHICLE_YEARS,
{}
);
var makeQuestionInitialDataPromise = null;
var modelQuestionInitialDataPromise = null;
var styleQuestionInitialDataPromise = null;
if (
store.getters.order.vehicle.make &&
store.getters.order.vehicle.model &&
store.getters.order.vehicle.style
) {
makeQuestionInitialDataPromise = baseMixin.methods.dispatchStoreAction(
storeActions.GET_VEHICLE_MAKES,
{
year: store.getters.order.vehicle.year,
}
);
modelQuestionInitialDataPromise = baseMixin.methods.dispatchStoreAction(
storeActions.GET_VEHICLE_MODELS,
{
year: store.getters.order.vehicle.year,
make: store.getters.order.vehicle.make,
}
);
styleQuestionInitialDataPromise = baseMixin.methods.dispatchStoreAction(
storeActions.GET_VEHICLE_STYLES,
{
year: store.getters.order.vehicle.year,
make: store.getters.order.vehicle.make,
model: store.getters.order.vehicle.model,
}
);
}
// If the concept funnel experiment is found, as it should be when coming from safelite.com, then log the experiment exposure. // If the concept funnel experiment is found, as it should be when coming from safelite.com, then log the experiment exposure.
if (experimentForLogging !== undefined) { if (experimentForLogging !== undefined) {
@ -146,6 +187,22 @@ export default {
resultKey: "cmsContent", resultKey: "cmsContent",
promise: cmsContentPromise, promise: cmsContentPromise,
}, },
{
resultKey: "yearQuestionInitialData",
promise: yearQuestionInitialDataPromise,
},
{
resultKey: "makeQuestionInitialData",
promise: makeQuestionInitialDataPromise,
},
{
resultKey: "modelQuestionInitialData",
promise: modelQuestionInitialDataPromise,
},
{
resultKey: "styleQuestionInitialData",
promise: styleQuestionInitialDataPromise,
},
]; ];
let resultMap = await settleAllPromises(promiseResultMap); let resultMap = await settleAllPromises(promiseResultMap);
@ -153,48 +210,74 @@ export default {
// Call the "next" function to complete the transition to this page. // Call the "next" function to complete the transition to this page.
next((vm) => { next((vm) => {
vm.setCmsContent(resultMap.cmsContent); vm.setCmsContent(resultMap.cmsContent);
vm.initializeYearComponent(resultMap.yearQuestionInitialData);
if (
makeQuestionInitialDataPromise &&
modelQuestionInitialDataPromise &&
styleQuestionInitialDataPromise
)
vm.initializeMMSComponent(
resultMap.makeQuestionInitialData,
resultMap.modelQuestionInitialData,
resultMap.styleQuestionInitialData
);
}); });
}, },
watch: { watch: {
selectedYear(year) { async selectedYear(year) {
const parsedYear = parseInt(year);
this.dispatchStoreAction(storeActions.SAVE_VEHICLE_YEAR, parsedYear);
if (year) { if (year) {
this.$refs["vehicleMakeQuestion"].getNewValues(this.selectedMakefromStore); const result = await this.getMakeOptions(year);
this.makeOptions = result?.data;
if (this.selectedYearfromStore() !== year) {
this.selectedMake = null;
this.selectedModel = null;
this.selectedStyle = null;
}
} else { } else {
this.$refs["vehicleMakeQuestion"].clearValues(); this.makeOptions = [];
} }
}, },
selectedMake(make) { async selectedMake(make) {
this.dispatchStoreAction(storeActions.SAVE_VEHICLE_MAKE, make, false);
if (make) { if (make) {
this.$refs["vehicleModelQuestion"].getNewValues(this.selectedModelfromStore); const result = await this.getModelOptions(this.selectedYear, make);
this.modelOptions = result?.data;
if (this.selectedMakefromStore() !== make) {
this.selectedModel = null;
this.selectedStyle = null;
}
} else { } else {
this.$refs["vehicleModelQuestion"].clearValues(); this.modelOptions = [];
} }
}, },
selectedModel(model) { async selectedModel(model) {
this.dispatchStoreAction(storeActions.SAVE_VEHICLE_MODEL, model, false);
if (model) { if (model) {
this.$refs["vehicleStyleQuestion"].getNewValues(this.selectedStylefromStore); const result = await this.getStyleOptions(
this.selectedYear,
this.selectedMake,
model
);
this.styleOptions = result?.data;
if (this.selectedModelfromStore() !== model) {
this.selectedStyle = null;
}
} else { } else {
this.$refs["vehicleStyleQuestion"].clearValues(); this.styleOptions = [];
} }
}, },
selectedStyle(style) { selectedStyle(style) {
this.dispatchStoreAction(storeActions.SAVE_VEHICLE_STYLE, style, false); this.setVehicle(this.selectedYear, this.selectedMake, this.selectedModel, style);
this.setVehicle();
}, },
}, },
methods: { methods: {
setVehicle() { setVehicle(year, make, model, style) {
return this.dispatchStoreAction(this.storeActions.SET_VEHICLE, { return this.dispatchStoreAction(this.storeActions.SET_VEHICLE, {
year: this.$store.getters.vehicle.year, year: year,
make: this.$store.getters.vehicle.make, make: make,
model: this.$store.getters.vehicle.model, model: model,
style: this.$store.getters.vehicle.style, style: style,
}); });
}, },
@ -207,42 +290,29 @@ export default {
}, },
navigateForward() { navigateForward() {
this.dispatchStoreAction(
storeActions.SAVE_VEHICLE,
{
year: this.selectedYear,
make: this.selectedMake,
model: this.selectedModel,
style: this.selectedStyle,
},
false
);
this.$router.navigateWithSaving(this.navigationScenarios.CLICKED_FORWARD, this.$route); this.$router.navigateWithSaving(this.navigationScenarios.CLICKED_FORWARD, this.$route);
}, },
async updateYearValues() { initializeYearComponent(initialData) {
return await baseMixin.methods.dispatchStoreAction(storeActions.GET_VEHICLE_YEARS, {}); this.yearOptions = initialData;
}, },
initializeMMSComponent(makeOptions, modelOptions, styleOptions) {
async updateMakeValues() { this.makeOptions = makeOptions;
return await baseMixin.methods.dispatchStoreAction(storeActions.GET_VEHICLE_MAKES, { this.modelOptions = modelOptions;
year: store.getters.vehicle.year, this.styleOptions = styleOptions;
});
},
async updateModelValues() {
return await baseMixin.methods.dispatchStoreAction(storeActions.GET_VEHICLE_MODELS, {
year: store.getters.vehicle.year,
make: store.getters.vehicle.make,
});
},
async updateStyleValues() {
return baseMixin.methods.dispatchStoreAction(storeActions.GET_VEHICLE_STYLES, {
year: store.getters.vehicle.year,
make: store.getters.vehicle.make,
model: store.getters.vehicle.model,
});
},
},
computed: {
displayGeneric() {
return !this.selectedStyle;
}, },
selectedYearfromStore() { selectedYearfromStore() {
return store.getters.vehicle.year; return store.getters.vehicle.year?.toString();
}, },
selectedMakefromStore() { selectedMakefromStore() {
return store.getters.vehicle.make; return store.getters.vehicle.make;
@ -253,6 +323,32 @@ export default {
selectedStylefromStore() { selectedStylefromStore() {
return store.getters.vehicle.style; return store.getters.vehicle.style;
}, },
async getMakeOptions(year) {
return await baseMixin.methods.dispatchStoreAction(storeActions.GET_VEHICLE_MAKES, {
year: year,
});
},
async getModelOptions(year, make) {
return await baseMixin.methods.dispatchStoreAction(storeActions.GET_VEHICLE_MODELS, {
year: year,
make: make,
});
},
async getStyleOptions(year, make, model) {
return await baseMixin.methods.dispatchStoreAction(storeActions.GET_VEHICLE_STYLES, {
year: year,
make: make,
model: model,
});
},
},
computed: {
displayGeneric() {
return !this.selectedStyle;
},
}, },
components: { components: {

View file

@ -15,7 +15,9 @@
v-model="vin" v-model="vin"
customInputId="vin" customInputId="vin"
isRequired isRequired
validationRules="vin-required|vin-format" :validationRules="
!vinPopulatedOnPageLoad ? 'vin-required|vin-format' : ''
"
:isDisabled="vinPopulatedOnPageLoad" :isDisabled="vinPopulatedOnPageLoad"
maxLength="17" maxLength="17"
:mask="vinMask" :mask="vinMask"
@ -348,37 +350,22 @@ export default {
// If a VIN has already been found. Validate the Service Zip (in case of changes) // If a VIN has already been found. Validate the Service Zip (in case of changes)
const zipCodeData = await this.getZipCodeData(this.serviceZipCode); 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) { 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 ( if (
this.$store.getters.order.serviceLocation.zipCode != this.serviceZipCode || this.$store.getters.order.serviceLocation.zipCode != this.serviceZipCode ||
!this.$store.getters.order.serviceLocation.zipCodeCtu !this.$store.getters.order.serviceLocation.zipCodeCtu
) { ) {
const vehicleRegistrationInfo = this.$store.getters.vehicle.registration; await this.dispatchStoreAction(
if (vehicleRegistrationInfo.zipCode == this.serviceZipCode) { storeActions.SAVE_SERVICE_ZIP_CODE_INFO,
await this.dispatchStoreAction( {
storeActions.SAVE_SERVICE_LOCATION, state: zipCodeData.state,
{ zipCode: this.serviceZipCode,
address: vehicleRegistrationInfo.address, zipCodeCtu: zipCodeData.zipCodeCtu,
city: vehicleRegistrationInfo.city, },
state: vehicleRegistrationInfo.state, false
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_EMAIL, this.emailAddress, false); await this.dispatchStoreAction(storeActions.SAVE_EMAIL, this.emailAddress, false);

View file

@ -446,7 +446,9 @@ export default {
const payment = store.getters.payment; const payment = store.getters.payment;
if (payment.isInsurance && payment.insuranceCoverage.isVerified) { if (store.getters.order.referralNumber?.length === 6) {
navigateToHeritageFunnel({ loadingModal: self.$refs.loadingModal });
} else if (payment.isInsurance && payment.insuranceCoverage.isVerified) {
navigateToHeritageFunnel({ loadingModal: self.$refs.loadingModal }); navigateToHeritageFunnel({ loadingModal: self.$refs.loadingModal });
} else { } else {
self.$router.navigateWithSaving( self.$router.navigateWithSaving(

View file

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

View file

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

View file

@ -535,6 +535,9 @@ export const getters = {
}, },
eventBus: (state) => state.applicationUser.eventBus, eventBus: (state) => state.applicationUser.eventBus,
damage: (state) => state.order.damage, damage: (state) => state.order.damage,
hasExactlyOneChip: (state) => {
return state.order.damage?.numberOfChips === 1;
},
hasAnyNonWindshieldGlassParts: (state) => { hasAnyNonWindshieldGlassParts: (state) => {
const nonWindshieldItems = state.order.damage.glassToReplace?.filter( const nonWindshieldItems = state.order.damage.glassToReplace?.filter(
(glassToReplace) => glassToReplace.glassLocation != "Windshield" (glassToReplace) => glassToReplace.glassLocation != "Windshield"
@ -766,11 +769,12 @@ export const actions = {
}); });
}, },
getDamageOptions(context, { carId }) { getDamageOptions(context, { carId, zipCode }) {
return globalMethods.callHttpClient({ return globalMethods.callHttpClient({
methods: endpoints.GetDamageOptions.method, methods: endpoints.GetDamageOptions.method,
endpoint: `${endpoints.GetDamageOptions.url}/${carId}`, endpoint: `${endpoints.GetDamageOptions.url}/${carId}`,
payload: {}, payload: {},
additionalSuccessEventDataHandler: (response) => "QueryStringZip: " + zipCode,
}); });
}, },
@ -1622,6 +1626,25 @@ export const actions = {
} }
}, },
saveVehicle(context, { year, make, model, style }) {
context.dispatch(storeActions.RESET_DAMAGE_STATE_AND_DEPENDENCIES);
context.dispatch(storeActions.RESET_REGISTRATION_STATE_AND_DEPENDENCIES);
context.dispatch(storeActions.RESET_PARTS_STATE_AND_DEPENDENCIES);
if (context.state.order.vehicle.year !== year) {
context.commit(storeMutations.UPDATE_YEAR, year);
}
if (context.state.order.vehicle.make !== make) {
context.commit(storeMutations.UPDATE_MAKE, make);
}
if (context.state.order.vehicle.model !== model) {
context.commit(storeMutations.UPDATE_MODEL, model);
}
if (context.state.order.vehicle.style !== style) {
context.commit(storeMutations.UPDATE_STYLE, style);
}
},
saveVehicleDamage( saveVehicleDamage(
context, context,
{ isWindshieldRepair, selectedGlassToReplace, selectedWindshieldChipCount } { isWindshieldRepair, selectedGlassToReplace, selectedWindshieldChipCount }

View file

@ -1702,6 +1702,59 @@ describe("Actions", () => {
expect(commit).toBeCalledWith(storeMutations.UPDATE_VEHICLE_IMAGE_COLOR, null); expect(commit).toBeCalledWith(storeMutations.UPDATE_VEHICLE_IMAGE_COLOR, null);
}); });
it("saveVehicle, should save vehicle info", () => {
// Arrange
const context = state;
context.state = {
order: {
vehicle: {
year: "2016",
make: "Toyota",
model: "Accord",
style: "SUV",
},
},
};
const commit = jest.fn();
const dispatch = jest.fn();
context.commit = commit;
context.dispatch = dispatch;
//Act
const payload = {
year: "2015",
make: "Honda",
model: "Civic",
style: "Sedan",
};
actions.saveVehicle(context, payload);
//Assert
expect(dispatch).toHaveBeenNthCalledWith(
1,
storeActions.RESET_DAMAGE_STATE_AND_DEPENDENCIES
);
expect(dispatch).toHaveBeenNthCalledWith(
2,
storeActions.RESET_REGISTRATION_STATE_AND_DEPENDENCIES
);
if (context.state.order.vehicle.year !== payload.year) {
expect(commit).toBeCalledWith(storeMutations.UPDATE_YEAR, payload.year);
}
if (context.state.order.vehicle.make !== payload.make) {
expect(commit).toBeCalledWith(storeMutations.UPDATE_MAKE, payload.make);
}
if (context.state.order.vehicle.model !== payload.model) {
expect(commit).toBeCalledWith(storeMutations.UPDATE_MODEL, payload.model);
}
if (context.state.order.vehicle.style !== payload.style) {
expect(commit).toBeCalledWith(storeMutations.UPDATE_STYLE, payload.style);
}
});
it("saveVehicleDamage, should wipe out damage if different", () => { it("saveVehicleDamage, should wipe out damage if different", () => {
// Arrange // Arrange
const context = state; const context = state;