Merge branch 'feature/CSR-1088' into feature/CSR-1012
This commit is contained in:
commit
165249af2f
17 changed files with 713 additions and 175 deletions
|
|
@ -54,6 +54,10 @@ const endpoints = {
|
||||||
url: "/vehicle/api/v1/vehicle/lookup-vin-by-address",
|
url: "/vehicle/api/v1/vehicle/lookup-vin-by-address",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
},
|
},
|
||||||
|
IsVinByAddressPermissible: {
|
||||||
|
url: "/vehicle/api/v1/vehicle/is-vin-by-address-permissible",
|
||||||
|
method: "GET",
|
||||||
|
},
|
||||||
GetPartsOrQuestions: {
|
GetPartsOrQuestions: {
|
||||||
url: "/parts/api/v1/parts/parts-or-questions",
|
url: "/parts/api/v1/parts/parts-or-questions",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
const queryStrings = {
|
const queryStrings = {
|
||||||
FMG_PAGE: "fmgPage",
|
FMG_PAGE: "fmgPage",
|
||||||
START_TYPE: "start_type",
|
START_TYPE: "start_type",
|
||||||
ZIP_CODE: "zipCode",
|
ZIP_CODE: "zipcode",
|
||||||
};
|
};
|
||||||
|
|
||||||
export { queryStrings };
|
export { queryStrings };
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ const storeActions = {
|
||||||
GET_DAMAGE_OPTIONS: "getDamageOptions",
|
GET_DAMAGE_OPTIONS: "getDamageOptions",
|
||||||
GET_EVOX_IMAGE: "getEvoxImage",
|
GET_EVOX_IMAGE: "getEvoxImage",
|
||||||
IS_VIN_OPTIONAL_VEHICLE: "isVinOptionalVehicle",
|
IS_VIN_OPTIONAL_VEHICLE: "isVinOptionalVehicle",
|
||||||
|
IS_VIN_BY_ADDRESS_PERMISSIBLE: "isVinByAddressPermissible",
|
||||||
|
|
||||||
// Lookup Actions
|
// Lookup Actions
|
||||||
LOOKUP_VEHICLE_BY_YMMS: "lookupVehicleByYmms",
|
LOOKUP_VEHICLE_BY_YMMS: "lookupVehicleByYmms",
|
||||||
|
|
|
||||||
27
src/helpers/object-cloning-helper.js
Normal file
27
src/helpers/object-cloning-helper.js
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
// For nested objects, spread operator only creates new references to the top level fields,
|
||||||
|
// the remaining nested fields actually reference the original object which can introduce problems.
|
||||||
|
|
||||||
|
// The purpose of this method is to deep clone the data in an object recursively, this is useful
|
||||||
|
// for cloning modelValues to internal models when regular two-way binding is not an option.
|
||||||
|
// See: mobile-location-modal-questions.vue
|
||||||
|
|
||||||
|
// Creates a deep clone of an object. Clones primitives, arrays and objects, excluding class instances.
|
||||||
|
// https://www.30secondsofcode.org/js/s/deep-clone
|
||||||
|
export function deepClone(object) {
|
||||||
|
if (object === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let clone = Object.assign({}, object);
|
||||||
|
Object.keys(clone).forEach(
|
||||||
|
(key) =>
|
||||||
|
(clone[key] = typeof object[key] === "object" ? deepClone(object[key]) : object[key])
|
||||||
|
);
|
||||||
|
|
||||||
|
if (Array.isArray(object)) {
|
||||||
|
clone.length = object.length;
|
||||||
|
return Array.from(clone);
|
||||||
|
}
|
||||||
|
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
48
src/helpers/object-cloning-helper.spec.js
Normal file
48
src/helpers/object-cloning-helper.spec.js
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
import { deepClone } from "./object-cloning-helper";
|
||||||
|
|
||||||
|
describe("object-cloning-helper.js", () => {
|
||||||
|
it("Should return null if no object is passed in", async () => {
|
||||||
|
// Arrange
|
||||||
|
const expected = null;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const result = deepClone(null);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(result).toEqual(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should return a deep copy of the object", async () => {
|
||||||
|
// Arrange
|
||||||
|
|
||||||
|
const object = {
|
||||||
|
addressQuestions: {
|
||||||
|
streetAddress: "555 Some St",
|
||||||
|
apartmentNumberOrBusinessName: "Apt 1",
|
||||||
|
city: "Funkytown",
|
||||||
|
state: "OH",
|
||||||
|
zipCode: "55555",
|
||||||
|
},
|
||||||
|
isVehicleProtected: true,
|
||||||
|
serviceZipCode: "55555",
|
||||||
|
};
|
||||||
|
|
||||||
|
const expected = {
|
||||||
|
addressQuestions: {
|
||||||
|
streetAddress: "555 Some St",
|
||||||
|
apartmentNumberOrBusinessName: "Apt 1",
|
||||||
|
city: "Funkytown",
|
||||||
|
state: "OH",
|
||||||
|
zipCode: "55555",
|
||||||
|
},
|
||||||
|
isVehicleProtected: true,
|
||||||
|
serviceZipCode: "55555",
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const result = deepClone(object);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(result).toStrictEqual(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import { storeActions } from "@/constants/store-actions";
|
import { storeActions } from "@/constants/store-actions";
|
||||||
import baseMixin from "@/mixins/base-mixin.js";
|
import baseMixin from "@/mixins/base-mixin.js";
|
||||||
|
|
||||||
export async function getMobileFee(serviceZipCode) {
|
export async function getPricedMobileFeePart(serviceZipCode) {
|
||||||
if (!serviceZipCode) {
|
if (!serviceZipCode) {
|
||||||
return 0;
|
return Promise.resolve(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
const zipCodeData = await baseMixin.methods.getZipCodeData(serviceZipCode);
|
const zipCodeData = await baseMixin.methods.getZipCodeData(serviceZipCode);
|
||||||
|
|
@ -26,5 +26,5 @@ export async function getMobileFee(serviceZipCode) {
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
|
|
||||||
return Promise.resolve(baseMixin.methods.getTotalLineItemPrice(pricingResults[0]));
|
return Promise.resolve(pricingResults[0]);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1 +1,78 @@
|
||||||
test.todo("some test to be written in the future");
|
import { getPricedMobileFeePart } from "./service-location-helper";
|
||||||
|
import { storeActions } from "@/constants/store-actions";
|
||||||
|
|
||||||
|
const mockStoreActionGetMobileFeePart = storeActions.GET_MOBILE_FEE_PART;
|
||||||
|
const mockStoreActionPriceOrderItemsAndSaveServerData =
|
||||||
|
storeActions.PRICE_ORDER_ITEMS_AND_SAVE_SERVER_DATA;
|
||||||
|
|
||||||
|
jest.mock("@/mixins/base-mixin.js", () => ({
|
||||||
|
methods: {
|
||||||
|
getZipCodeData: jest.fn().mockImplementation(() => {
|
||||||
|
return Promise.resolve({
|
||||||
|
containsMilitaryBase: false,
|
||||||
|
isServiceable: true,
|
||||||
|
isValid: true,
|
||||||
|
state: "OH",
|
||||||
|
zipCodeCtu: "01820",
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
|
||||||
|
dispatchStoreAction: jest.fn().mockImplementation((actionName) => {
|
||||||
|
if (actionName === mockStoreActionGetMobileFeePart) {
|
||||||
|
return Promise.resolve({
|
||||||
|
data: {
|
||||||
|
partNumber: "MOBILE FEE",
|
||||||
|
description: "MOBILE FEE",
|
||||||
|
partType: "FEE",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (actionName === mockStoreActionPriceOrderItemsAndSaveServerData) {
|
||||||
|
return Promise.resolve([
|
||||||
|
{
|
||||||
|
partNumber: "MOBILE FEE",
|
||||||
|
description: "MOBILE FEE",
|
||||||
|
partType: "FEE",
|
||||||
|
laborAmount: 49.99,
|
||||||
|
sellingPrice: 0,
|
||||||
|
kitPrice: 0,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe("service-location-helper.js", () => {
|
||||||
|
it("Should return null if no service zip code is passed in", async () => {
|
||||||
|
// Arrange
|
||||||
|
const serviceZipCode = null;
|
||||||
|
const expected = null;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const result = await getPricedMobileFeePart(serviceZipCode);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(result).toEqual(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should return the priced mobile fee part", async () => {
|
||||||
|
// Arrange
|
||||||
|
const serviceZipCode = "43235";
|
||||||
|
const expected = {
|
||||||
|
partNumber: "MOBILE FEE",
|
||||||
|
description: "MOBILE FEE",
|
||||||
|
partType: "FEE",
|
||||||
|
laborAmount: 49.99,
|
||||||
|
sellingPrice: 0,
|
||||||
|
kitPrice: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const result = await getPricedMobileFeePart(serviceZipCode);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(result).toEqual(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -110,6 +110,8 @@ import { experimentSettings } from "@/constants/experiments";
|
||||||
import vinPagesMixin from "@/mixins/vin-pages-mixin";
|
import vinPagesMixin from "@/mixins/vin-pages-mixin";
|
||||||
import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper";
|
import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper";
|
||||||
import { saveSession } from "@/helpers/heritage-integration/order-helper.js";
|
import { saveSession } from "@/helpers/heritage-integration/order-helper.js";
|
||||||
|
import baseMixin from "@/mixins/base-mixin.js";
|
||||||
|
import { queryStrings } from "@/constants/query-strings";
|
||||||
|
|
||||||
// Define Validation Rules
|
// Define Validation Rules
|
||||||
defineRule("zip-required", required(errorMessages.SERVICE_ZIP_REQUIRED));
|
defineRule("zip-required", required(errorMessages.SERVICE_ZIP_REQUIRED));
|
||||||
|
|
@ -130,7 +132,7 @@ export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
selectedVinLookupMethod: null,
|
selectedVinLookupMethod: null,
|
||||||
serviceZipCode: this.getZipFromStore() ?? this.$route.query.zipCode,
|
serviceZipCode: this.getZipFromStore() ?? this.$route.query.zipcode,
|
||||||
emailAddress: this.getEmailFromStore(),
|
emailAddress: this.getEmailFromStore(),
|
||||||
displayInvalidZipAlert: false,
|
displayInvalidZipAlert: false,
|
||||||
displayNonServiceableZipAlert: false,
|
displayNonServiceableZipAlert: false,
|
||||||
|
|
@ -143,12 +145,31 @@ export default {
|
||||||
//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 hasZip = urlParams.has(queryStrings.ZIP_CODE);
|
||||||
|
const zip = urlParams.get(queryStrings.ZIP_CODE);
|
||||||
|
|
||||||
|
var vinByAddressPromise;
|
||||||
|
if (hasZip) {
|
||||||
|
vinByAddressPromise = baseMixin.methods.dispatchStoreAction(
|
||||||
|
storeActions.IS_VIN_BY_ADDRESS_PERMISSIBLE,
|
||||||
|
zip,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
//Settle promises and get results
|
//Settle promises and get results
|
||||||
const promiseResultMap = [
|
const promiseResultMap = [
|
||||||
{
|
{
|
||||||
resultKey: "cmsContent",
|
resultKey: "cmsContent",
|
||||||
promise: cmsContentPromise,
|
promise: cmsContentPromise,
|
||||||
},
|
},
|
||||||
|
zip != null &&
|
||||||
|
zip != undefined && {
|
||||||
|
resultKey: "vinByAddress",
|
||||||
|
promise: vinByAddressPromise,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const resultMap = await settleAllPromises(promiseResultMap);
|
const resultMap = await settleAllPromises(promiseResultMap);
|
||||||
|
|
@ -170,6 +191,14 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (zip && resultMap.vinByAddress === false) {
|
||||||
|
var indexToRemove = resultMap.cmsContent.VinLookupMethod.Answers.findIndex(
|
||||||
|
(answer) => answer.Name === "HomeAddress"
|
||||||
|
);
|
||||||
|
if (indexToRemove) {
|
||||||
|
resultMap.cmsContent.VinLookupMethod.Answers.splice(indexToRemove, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
vm.setCmsContent(resultMap.cmsContent);
|
vm.setCmsContent(resultMap.cmsContent);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -152,7 +152,7 @@ export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
licensePlate: this.getLicensePlateFromStore(),
|
licensePlate: this.getLicensePlateFromStore(),
|
||||||
registrationZipCode: this.getRegistrationZipFromStore() ?? this.$route.query.zipCode,
|
registrationZipCode: this.getRegistrationZipFromStore() ?? this.$route.query.zipcode,
|
||||||
email: this.getEmailFromStore(),
|
email: this.getEmailFromStore(),
|
||||||
serviceZipCode: this.getServiceZipFromStore(),
|
serviceZipCode: this.getServiceZipFromStore(),
|
||||||
displayNonServiceableZipAlert: false,
|
displayNonServiceableZipAlert: false,
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import mobileLocationModalQuestions from "./mobile-location-modal-questions";
|
||||||
import { mount, shallowMount } from "@vue/test-utils";
|
import { mount, shallowMount } from "@vue/test-utils";
|
||||||
import { storeActions } from "@/constants/store-actions";
|
import { storeActions } from "@/constants/store-actions";
|
||||||
import { getMountOptions } from "@/helpers/unit-test-helper.js";
|
import { getMountOptions } from "@/helpers/unit-test-helper.js";
|
||||||
|
import modal from "@/digital-components/modal/modal";
|
||||||
|
|
||||||
import crypto from "crypto";
|
import crypto from "crypto";
|
||||||
|
|
||||||
|
|
@ -42,7 +43,7 @@ const mockMixin = {
|
||||||
}),
|
}),
|
||||||
|
|
||||||
getZipCodeData: jest.fn((zip) => {
|
getZipCodeData: jest.fn((zip) => {
|
||||||
if (zip === "43235") {
|
if (zip === "43235" || zip === "55555") {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
containsMilitaryBase: false,
|
containsMilitaryBase: false,
|
||||||
isServiceable: true,
|
isServiceable: true,
|
||||||
|
|
@ -128,6 +129,213 @@ describe("mobile-location-modal-questions.vue", () => {
|
||||||
expect(wrapper.vm.internalModel.isVehicleProtected).toEqual(true);
|
expect(wrapper.vm.internalModel.isVehicleProtected).toEqual(true);
|
||||||
expect(wrapper.vm.internalModel.serviceZipCode).toEqual("55555");
|
expect(wrapper.vm.internalModel.serviceZipCode).toEqual("55555");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("Should open the modal when the 'Enter your service address' link is clicked", async () => {
|
||||||
|
// Arrange
|
||||||
|
const mobileLocationQuestions = {
|
||||||
|
addressQuestions: {
|
||||||
|
streetAddress: "555 Some St",
|
||||||
|
apartmentNumberOrBusinessName: "Apt 1",
|
||||||
|
city: "Funkytown",
|
||||||
|
state: "OH",
|
||||||
|
zipCode: "55555",
|
||||||
|
},
|
||||||
|
isVehicleProtected: true,
|
||||||
|
serviceZipCode: "55555",
|
||||||
|
};
|
||||||
|
|
||||||
|
const { wrapper } = setupMocks({
|
||||||
|
mixins: [mockMixin],
|
||||||
|
props: {
|
||||||
|
modelValue: mobileLocationQuestions,
|
||||||
|
},
|
||||||
|
mountOptions: {
|
||||||
|
attachTo: document.body,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
wrapper.vm.$refs.MobileLocationModalWidget.openModal = jest.fn();
|
||||||
|
const mobileLocationLink = wrapper.findComponent({ ref: "mobileLocationLink" });
|
||||||
|
|
||||||
|
// // Act
|
||||||
|
await mobileLocationLink.trigger("click-event");
|
||||||
|
|
||||||
|
// // Assert
|
||||||
|
expect(wrapper.vm.$refs.MobileLocationModalWidget.openModal).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should emit update:modelValue on setMobileLocation for a valid address and vehicle protection answer", async () => {
|
||||||
|
// Arrange
|
||||||
|
const mobileLocationQuestions = {
|
||||||
|
addressQuestions: {
|
||||||
|
streetAddress: "",
|
||||||
|
apartmentNumberOrBusinessName: "",
|
||||||
|
city: "",
|
||||||
|
state: "",
|
||||||
|
zipCode: "",
|
||||||
|
},
|
||||||
|
isVehicleProtected: true,
|
||||||
|
serviceZipCode: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
const newMobileLocationQuestions = {
|
||||||
|
addressQuestions: {
|
||||||
|
streetAddress: "555 Some St",
|
||||||
|
apartmentNumberOrBusinessName: "Apt 1",
|
||||||
|
city: "Funkytown",
|
||||||
|
state: "OH",
|
||||||
|
zipCode: "55555",
|
||||||
|
},
|
||||||
|
isVehicleProtected: true,
|
||||||
|
serviceZipCode: "55555",
|
||||||
|
};
|
||||||
|
|
||||||
|
const wrapper = shallowMount(mobileLocationModalQuestions, {
|
||||||
|
mixins: [mockMixin],
|
||||||
|
props: {
|
||||||
|
modelValue: mobileLocationQuestions,
|
||||||
|
linkWidgetName: linkWidgetName,
|
||||||
|
modalWidgetName: modalWidgetName,
|
||||||
|
},
|
||||||
|
attachTo: document.body,
|
||||||
|
});
|
||||||
|
wrapper.vm.$refs.MobileLocationModalWidget.closeModal = jest.fn();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
wrapper.vm.internalModel = newMobileLocationQuestions;
|
||||||
|
await wrapper.vm.setMobileLocation();
|
||||||
|
|
||||||
|
let expectedEmit = [[newMobileLocationQuestions]];
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(wrapper.emitted("update:modelValue")).toEqual(expectedEmit);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should not emit update:modelValue on setMobileLocation for an invalid address zip code", async () => {
|
||||||
|
// Arrange
|
||||||
|
const mobileLocationQuestions = {
|
||||||
|
addressQuestions: {
|
||||||
|
streetAddress: "",
|
||||||
|
apartmentNumberOrBusinessName: "",
|
||||||
|
city: "",
|
||||||
|
state: "",
|
||||||
|
zipCode: "",
|
||||||
|
},
|
||||||
|
isVehicleProtected: true,
|
||||||
|
serviceZipCode: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
const newMobileLocationQuestions = {
|
||||||
|
addressQuestions: {
|
||||||
|
streetAddress: "555 Some St",
|
||||||
|
apartmentNumberOrBusinessName: "Apt 1",
|
||||||
|
city: "Funkytown",
|
||||||
|
state: "OH",
|
||||||
|
zipCode: "61000",
|
||||||
|
},
|
||||||
|
isVehicleProtected: true,
|
||||||
|
serviceZipCode: "61000",
|
||||||
|
};
|
||||||
|
|
||||||
|
const wrapper = shallowMount(mobileLocationModalQuestions, {
|
||||||
|
mixins: [mockMixin],
|
||||||
|
props: {
|
||||||
|
modelValue: mobileLocationQuestions,
|
||||||
|
linkWidgetName: linkWidgetName,
|
||||||
|
modalWidgetName: modalWidgetName,
|
||||||
|
},
|
||||||
|
attachTo: document.body,
|
||||||
|
});
|
||||||
|
wrapper.vm.$refs.MobileLocationModalWidget.closeModal = jest.fn();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
wrapper.vm.internalModel = newMobileLocationQuestions;
|
||||||
|
await wrapper.vm.setMobileLocation();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(wrapper.emitted("update:modelValue")).not.toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should display invalid zip alert for invalid address zip inputs", async () => {
|
||||||
|
// Arrange
|
||||||
|
const mobileLocationQuestions = {
|
||||||
|
addressQuestions: {
|
||||||
|
streetAddress: "",
|
||||||
|
apartmentNumberOrBusinessName: "",
|
||||||
|
city: "",
|
||||||
|
state: "",
|
||||||
|
zipCode: "",
|
||||||
|
},
|
||||||
|
isVehicleProtected: true,
|
||||||
|
serviceZipCode: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
const newMobileLocationQuestions = {
|
||||||
|
addressQuestions: {
|
||||||
|
streetAddress: "555 Some St",
|
||||||
|
apartmentNumberOrBusinessName: "Apt 1",
|
||||||
|
city: "Funkytown",
|
||||||
|
state: "OH",
|
||||||
|
zipCode: "11111",
|
||||||
|
},
|
||||||
|
isVehicleProtected: true,
|
||||||
|
serviceZipCode: "11111",
|
||||||
|
};
|
||||||
|
|
||||||
|
const wrapper = shallowMount(mobileLocationModalQuestions, {
|
||||||
|
mixins: [mockMixin],
|
||||||
|
props: {
|
||||||
|
modelValue: mobileLocationQuestions,
|
||||||
|
linkWidgetName: linkWidgetName,
|
||||||
|
modalWidgetName: modalWidgetName,
|
||||||
|
},
|
||||||
|
attachTo: document.body,
|
||||||
|
});
|
||||||
|
|
||||||
|
wrapper.vm.internalModel = newMobileLocationQuestions;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await wrapper.vm.setMobileLocation();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(wrapper.vm.displayInvalidZipAlert).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should clear the internal model when resetModel is called", async () => {
|
||||||
|
// Arrange
|
||||||
|
const mobileLocationQuestions = {
|
||||||
|
addressQuestions: {
|
||||||
|
streetAddress: "555 Some St",
|
||||||
|
apartmentNumberOrBusinessName: "Apt 1",
|
||||||
|
city: "Funkytown",
|
||||||
|
state: "OH",
|
||||||
|
zipCode: "55555",
|
||||||
|
},
|
||||||
|
isVehicleProtected: true,
|
||||||
|
serviceZipCode: "55555",
|
||||||
|
};
|
||||||
|
|
||||||
|
const { wrapper } = setupMocks({
|
||||||
|
mixins: [mockMixin],
|
||||||
|
props: {
|
||||||
|
modelValue: mobileLocationQuestions,
|
||||||
|
},
|
||||||
|
mountOptions: {
|
||||||
|
attachTo: document.body,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
wrapper.vm.resetModel();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(wrapper.vm.internalModel.addressQuestions.streetAddress).toEqual("");
|
||||||
|
expect(wrapper.vm.internalModel.addressQuestions.apartmentNumberOrBusinessName).toEqual("");
|
||||||
|
expect(wrapper.vm.internalModel.addressQuestions.city).toEqual("");
|
||||||
|
expect(wrapper.vm.internalModel.addressQuestions.state).toEqual("");
|
||||||
|
expect(wrapper.vm.internalModel.addressQuestions.zipCode).toEqual("");
|
||||||
|
expect(wrapper.vm.internalModel.isVehicleProtected).toEqual(null);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function setupMocks({ mountOptions, mixins, props, isShallowMount = true }) {
|
function setupMocks({ mountOptions, mixins, props, isShallowMount = true }) {
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,15 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<label
|
|
||||||
for="mobileLocationLinkPromptId"
|
|
||||||
:aria-label="mobileLocationLinkPromptText"
|
|
||||||
class="form-label fw-bold w-100 ps-4 pe-4 pt-4"
|
|
||||||
v-html="mobileLocationLinkPromptText"></label>
|
|
||||||
<div class="update-mobile-location-text-link">
|
<div class="update-mobile-location-text-link">
|
||||||
<textLink
|
<textLink
|
||||||
|
ref="mobileLocationLink"
|
||||||
id="mobileLocationLinkPromptId"
|
id="mobileLocationLinkPromptId"
|
||||||
linkType="text"
|
linkType="text"
|
||||||
:text="this.mobileLocationLinkText"
|
:text="mobileLocationLinkText"
|
||||||
href="#!"
|
href="#!"
|
||||||
@click-event="openModal"
|
@click-event="openModal"
|
||||||
aria-label="Modal window" />
|
aria-label="Modal window" />
|
||||||
</div>
|
</div>
|
||||||
<textBlock
|
|
||||||
:customText="mobileFeeText"
|
|
||||||
cmsWidgetName="MobileFeeDisclaimerWidget"
|
|
||||||
typeStyle="caption" />
|
|
||||||
</div>
|
</div>
|
||||||
<modal
|
<modal
|
||||||
:ref="modalName"
|
:ref="modalName"
|
||||||
|
|
@ -46,18 +38,21 @@
|
||||||
<script>
|
<script>
|
||||||
// Components
|
// Components
|
||||||
import textLink from "@/ux-components/text-link/text-link";
|
import textLink from "@/ux-components/text-link/text-link";
|
||||||
|
import textBlock from "@/digital-components/text-block/text-block";
|
||||||
import modal from "@/digital-components/modal/modal";
|
import modal from "@/digital-components/modal/modal";
|
||||||
import alert from "@/ux-components/alert/alert";
|
import alert from "@/ux-components/alert/alert";
|
||||||
import addressQuestions from "@/layouts/address-lookup/customer-questions/address-questions/address-questions";
|
import addressQuestions from "@/layouts/address-lookup/customer-questions/address-questions/address-questions";
|
||||||
import vehicleProtectedQuestion from "@/layouts/service-location/mobile-location-modal-questions/vehicle-protected-question/vehicle-protected-question";
|
import vehicleProtectedQuestion from "@/layouts/service-location/mobile-location-modal-questions/vehicle-protected-question/vehicle-protected-question";
|
||||||
import textBlock from "@/digital-components/text-block/text-block";
|
|
||||||
|
// Helpers
|
||||||
|
import { deepClone } from "@/helpers/object-cloning-helper";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "mobile-location-modal-questions",
|
name: "mobile-location-modal-questions",
|
||||||
emits: ["update:modelValue"], // The component emits an event
|
emits: ["update:modelValue", "updated-zip-code"], // The component emits an event
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
internalModel: this.copyModel(this.modelValue),
|
internalModel: deepClone(this.modelValue),
|
||||||
displayInvalidZipAlert: false,
|
displayInvalidZipAlert: false,
|
||||||
modalName: "MobileLocationModalWidget",
|
modalName: "MobileLocationModalWidget",
|
||||||
};
|
};
|
||||||
|
|
@ -75,7 +70,7 @@ export default {
|
||||||
},
|
},
|
||||||
isVehicleProtected: null,
|
isVehicleProtected: null,
|
||||||
serviceZipCode: "",
|
serviceZipCode: "",
|
||||||
mobileFee: 0,
|
mobileFeePart: null,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
isZipServiceableMobile: Boolean,
|
isZipServiceableMobile: Boolean,
|
||||||
|
|
@ -86,9 +81,6 @@ export default {
|
||||||
alertInvalidZipWidgetName: String,
|
alertInvalidZipWidgetName: String,
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
mobileLocationLinkPromptText() {
|
|
||||||
return this.getCmsContent(this.linkWidgetName, "HeaderText");
|
|
||||||
},
|
|
||||||
mobileLocationLinkText() {
|
mobileLocationLinkText() {
|
||||||
if (
|
if (
|
||||||
this.addressModel.streetAddress !== null &&
|
this.addressModel.streetAddress !== null &&
|
||||||
|
|
@ -107,13 +99,6 @@ export default {
|
||||||
modalHeaderText() {
|
modalHeaderText() {
|
||||||
return this.getCmsContent(this.modalWidgetName, "HeaderText");
|
return this.getCmsContent(this.modalWidgetName, "HeaderText");
|
||||||
},
|
},
|
||||||
mobileFeeText() {
|
|
||||||
const cmsContentText = this.getCmsContent("MobileFeeDisclaimerWidget", "Text");
|
|
||||||
if (this.modelValue.mobileFee === undefined) {
|
|
||||||
return cmsContentText.replaceAll("{custom:mobileFee}", 0);
|
|
||||||
}
|
|
||||||
return cmsContentText.replaceAll("{custom:mobileFee}", this.modelValue.mobileFee);
|
|
||||||
},
|
|
||||||
modalFooterText() {
|
modalFooterText() {
|
||||||
return this.getCmsContent(this.modalWidgetName, "FooterText");
|
return this.getCmsContent(this.modalWidgetName, "FooterText");
|
||||||
},
|
},
|
||||||
|
|
@ -124,22 +109,12 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
copyModel(modelToCopy) {
|
|
||||||
return { ...modelToCopy };
|
|
||||||
},
|
|
||||||
|
|
||||||
setServiceZipCode(serviceZipCode) {
|
|
||||||
this.internalModel.serviceZipCode = serviceZipCode;
|
|
||||||
},
|
|
||||||
|
|
||||||
openModal() {
|
openModal() {
|
||||||
this.$refs[this.modalName].openModal();
|
this.$refs[this.modalName].openModal();
|
||||||
},
|
},
|
||||||
|
|
||||||
closeModal() {
|
closeModal() {
|
||||||
this.$refs[this.modalName].closeModal();
|
this.$refs[this.modalName].closeModal();
|
||||||
},
|
},
|
||||||
|
|
||||||
resetComponent() {
|
resetComponent() {
|
||||||
this.resetModel();
|
this.resetModel();
|
||||||
|
|
||||||
|
|
@ -149,19 +124,15 @@ export default {
|
||||||
// Reinitialize the Address Auto Complete
|
// Reinitialize the Address Auto Complete
|
||||||
this.$refs.addressQuestions.setupAddressLookup();
|
this.$refs.addressQuestions.setupAddressLookup();
|
||||||
},
|
},
|
||||||
|
|
||||||
resetModel() {
|
resetModel() {
|
||||||
// Address
|
// Address
|
||||||
this.internalModel.addressQuestions.streetAddress = "";
|
this.internalModel.addressQuestions.streetAddress = "";
|
||||||
this.internalModel.addressQuestions.apartmentNumberOrBusinessName = "";
|
this.internalModel.addressQuestions.apartmentNumberOrBusinessName = "";
|
||||||
this.internalModel.addressQuestions.city = "";
|
this.internalModel.addressQuestions.city = "";
|
||||||
this.internalModel.addressQuestions.state = "";
|
|
||||||
this.internalModel.addressQuestions.zipCode = "";
|
|
||||||
|
|
||||||
// Is Vehicle Protected
|
// Is Vehicle Protected
|
||||||
this.internalModel.isVehicleProtected = null;
|
this.internalModel.isVehicleProtected = null;
|
||||||
},
|
},
|
||||||
|
|
||||||
async setMobileLocation() {
|
async setMobileLocation() {
|
||||||
// Validate the Zip Code
|
// Validate the Zip Code
|
||||||
const zipCodeData = await this.getZipCodeData(
|
const zipCodeData = await this.getZipCodeData(
|
||||||
|
|
@ -172,23 +143,17 @@ export default {
|
||||||
this.displayInvalidZipAlert = true;
|
this.displayInvalidZipAlert = true;
|
||||||
} else {
|
} else {
|
||||||
// Update the page level model
|
// Update the page level model
|
||||||
this.internalModel.serviceZipCode = this.internalModel.addressQuestions.zipCode;
|
|
||||||
this.$emit("update:modelValue", this.internalModel);
|
this.$emit("update:modelValue", this.internalModel);
|
||||||
|
|
||||||
// Emit the update service zip code information to the parent to update the service zipcode details
|
|
||||||
this.$emit("updated-zip-code", {
|
|
||||||
zipCode: this.internalModel.addressQuestions.zipCode,
|
|
||||||
state: zipCodeData.state,
|
|
||||||
isServiceable: zipCodeData.isServiceable,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.closeModal();
|
this.closeModal();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
modelValue(newValue) {
|
modelValue: {
|
||||||
this.internalModel = this.copyModel(newValue);
|
handler(newValue) {
|
||||||
|
this.internalModel = deepClone(newValue);
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
|
|
|
||||||
|
|
@ -4,24 +4,38 @@ import serviceLocation from "@/layouts/service-location/service-location.vue";
|
||||||
// Supporting files
|
// Supporting files
|
||||||
import { shallowMount } from "@vue/test-utils";
|
import { shallowMount } from "@vue/test-utils";
|
||||||
import { getMountOptions } from "@/helpers/unit-test-helper";
|
import { getMountOptions } from "@/helpers/unit-test-helper";
|
||||||
import baseMixin from "@/mixins/base-mixin";
|
|
||||||
import { nextTick } from "vue";
|
|
||||||
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
|
||||||
import { settleAllPromises } from "@/helpers/layout-helper.js";
|
|
||||||
import { getMobileFee } from "@/helpers/service-location-helper";
|
|
||||||
import store from "@/store";
|
import store from "@/store";
|
||||||
|
import baseMixin from "@/mixins/base-mixin";
|
||||||
|
|
||||||
// Define Mocks
|
// Define Mocks
|
||||||
jest.mock("@/helpers/layout-helper.js", () => ({
|
jest.mock("@/helpers/cms-content-helper", () => ({
|
||||||
settleAllPromises: jest.fn(),
|
fetchCmsContentForPage: jest.fn(() => {
|
||||||
|
return Promise.resolve("content");
|
||||||
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock("@/helpers/cms-content-helper", () => ({
|
const mockGetPricedMobileFeePart = (mockServiceZipCode) => {
|
||||||
fetchCmsContentForPage: jest.fn(),
|
let mobileFeePart = {};
|
||||||
}));
|
|
||||||
|
if (mockServiceZipCode === "43235") {
|
||||||
|
mobileFeePart = {
|
||||||
|
partNumber: "MOBILE FEE",
|
||||||
|
description: "MOBILE FEE",
|
||||||
|
partType: "FEE",
|
||||||
|
laborAmount: 49.99,
|
||||||
|
sellingPrice: 0,
|
||||||
|
kitPrice: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.resolve(mobileFeePart);
|
||||||
|
};
|
||||||
|
|
||||||
jest.mock("@/helpers/service-location-helper", () => ({
|
jest.mock("@/helpers/service-location-helper", () => ({
|
||||||
getMobileFee: jest.fn(),
|
getPricedMobileFeePart: jest.fn((mockServiceZipCode) => {
|
||||||
|
return mockGetPricedMobileFeePart(mockServiceZipCode);
|
||||||
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock("@/store", () => ({
|
jest.mock("@/store", () => ({
|
||||||
|
|
@ -55,16 +69,43 @@ beforeEach(() => {
|
||||||
},
|
},
|
||||||
vehicle: {
|
vehicle: {
|
||||||
registration: {
|
registration: {
|
||||||
address: undefined,
|
address: "5555 Sulgrave Dr",
|
||||||
city: undefined,
|
city: "New Albany",
|
||||||
state: undefined,
|
state: "OH",
|
||||||
zipCode: undefined,
|
zipCode: "43054",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("service-location.vue", () => {
|
describe("service-location.vue", () => {
|
||||||
|
describe("beforeRouteEnter", () => {
|
||||||
|
test("on load sets the mobile fee part when an service zip code has already been provided", async () => {
|
||||||
|
// Arrange
|
||||||
|
const { wrapper } = setupMocks({});
|
||||||
|
|
||||||
|
const mobileFeePart = {
|
||||||
|
partNumber: "MOBILE FEE",
|
||||||
|
description: "MOBILE FEE",
|
||||||
|
partType: "FEE",
|
||||||
|
laborAmount: 49.99,
|
||||||
|
sellingPrice: 0,
|
||||||
|
kitPrice: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await serviceLocation.beforeRouteEnter.call(
|
||||||
|
wrapper.vm,
|
||||||
|
{ query: { fmgPage: "serviceLocation" } },
|
||||||
|
undefined,
|
||||||
|
(c) => c(wrapper.vm)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(wrapper.vm.mobileLocationQuestions.mobileFeePart).toStrictEqual(mobileFeePart);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("arePagePrerequisitesValid", () => {
|
describe("arePagePrerequisitesValid", () => {
|
||||||
test("No prerequisites set: Should return false.", () => {
|
test("No prerequisites set: Should return false.", () => {
|
||||||
const { wrapper } = setupMocks({});
|
const { wrapper } = setupMocks({});
|
||||||
|
|
@ -108,55 +149,147 @@ describe("service-location.vue", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("updating service zip", () => {
|
describe("updating service zip", () => {
|
||||||
test("resets mobile location when service zip code is updated", () => {
|
test("updates the page model after providing the service zip code", () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const { wrapper } = setupMocks({});
|
const { wrapper } = setupMocks({});
|
||||||
const newZip = "61606";
|
|
||||||
|
|
||||||
// Act
|
|
||||||
wrapper.vm.$refs.mobileLocationModalQuestions.resetComponent = jest.fn();
|
wrapper.vm.$refs.mobileLocationModalQuestions.resetComponent = jest.fn();
|
||||||
wrapper.vm.resetMobileLocation(newZip);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect(wrapper.vm.mobileLocationQuestions.serviceZipCode).toBe(newZip);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("updates service zip code serviceZipCodeQuestion when resetServiceZipCode is called", () => {
|
|
||||||
// Arrange
|
|
||||||
const { wrapper } = setupMocks({});
|
|
||||||
const newServiceZipCodeQuestion = {
|
const newServiceZipCodeQuestion = {
|
||||||
zipCode: "61606",
|
zipCode: "61606",
|
||||||
state: "IL",
|
state: "IL",
|
||||||
isServiceable: true,
|
isServiceable: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const serviceZipCodeComponent = wrapper.findComponent({
|
||||||
|
ref: "serviceZipCodeQuestion",
|
||||||
|
});
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
wrapper.vm.$refs.mobileLocationModalQuestions.resetComponent = jest.fn();
|
serviceZipCodeComponent.vm.$emit("update:modelValue", newServiceZipCodeQuestion);
|
||||||
wrapper.vm.resetServiceZipCode(newServiceZipCodeQuestion);
|
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
expect(wrapper.vm.serviceZipCodeQuestion).toStrictEqual(newServiceZipCodeQuestion);
|
expect(wrapper.vm.serviceZipCodeQuestion).toStrictEqual(newServiceZipCodeQuestion);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("resets mobile location in watch for serviceZipCodeQuestion", async () => {
|
test("resets mobile location when service zip code is updated", () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const { wrapper } = setupMocks({});
|
const { wrapper } = setupMocks({});
|
||||||
const newServiceZipCodeQuestion = {
|
wrapper.vm.$refs.mobileLocationModalQuestions.resetComponent = jest.fn();
|
||||||
zipCode: "61606",
|
wrapper.vm.$refs.serviceZipCodeQuestion.resetMobileFeePart = jest.fn();
|
||||||
state: "IL",
|
|
||||||
|
const mobileLocationQuestions = {
|
||||||
|
addressQuestions: {
|
||||||
|
streetAddress: "5555 Sulgrave Dr",
|
||||||
|
apartmentNumberOrBusinessName: "Apt 1",
|
||||||
|
city: "New Albany",
|
||||||
|
state: "OH",
|
||||||
|
zipCode: "43054",
|
||||||
|
},
|
||||||
|
isVehicleProtected: true,
|
||||||
|
serviceZipCode: "43054",
|
||||||
|
mobileFee: 0,
|
||||||
|
};
|
||||||
|
wrapper.vm.mobileLocationQuestions = mobileLocationQuestions;
|
||||||
|
|
||||||
|
const newMobileLocationQuestions = {
|
||||||
|
addressQuestions: {
|
||||||
|
streetAddress: "",
|
||||||
|
apartmentNumberOrBusinessName: "",
|
||||||
|
city: "",
|
||||||
|
state: "",
|
||||||
|
zipCode: "",
|
||||||
|
},
|
||||||
|
isVehicleProtected: null,
|
||||||
|
serviceZipCode: "43081",
|
||||||
|
};
|
||||||
|
|
||||||
|
const serviceZipCodeComponent = wrapper.findComponent({
|
||||||
|
ref: "serviceZipCodeQuestion",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
serviceZipCodeComponent.vm.$emit("updated-service-zip-code", "43081");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(wrapper.vm.mobileLocationQuestions).toStrictEqual(newMobileLocationQuestions);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("updating mobile location", () => {
|
||||||
|
test("updates the page model after providing the mobile location", () => {
|
||||||
|
// Arrange
|
||||||
|
const { wrapper } = setupMocks({});
|
||||||
|
wrapper.vm.$refs.mobileLocationModalQuestions.resetComponent = jest.fn();
|
||||||
|
|
||||||
|
const mobileLocationQuestions = {
|
||||||
|
addressQuestions: {
|
||||||
|
streetAddress: "",
|
||||||
|
apartmentNumberOrBusinessName: "",
|
||||||
|
city: "",
|
||||||
|
state: "",
|
||||||
|
zipCode: "",
|
||||||
|
},
|
||||||
|
isVehicleProtected: null,
|
||||||
|
serviceZipCode: "43081",
|
||||||
|
};
|
||||||
|
wrapper.vm.mobileLocationQuestions = mobileLocationQuestions;
|
||||||
|
|
||||||
|
const newMobileLocationQuestions = {
|
||||||
|
addressQuestions: {
|
||||||
|
streetAddress: "5555 Sulgrave Dr",
|
||||||
|
apartmentNumberOrBusinessName: "Apt 1",
|
||||||
|
city: "New Albany",
|
||||||
|
state: "OH",
|
||||||
|
zipCode: "43054",
|
||||||
|
},
|
||||||
|
isVehicleProtected: true,
|
||||||
|
serviceZipCode: "43054",
|
||||||
|
mobileFee: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
const mobileLocationComponent = wrapper.findComponent({
|
||||||
|
ref: "mobileLocationModalQuestions",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
mobileLocationComponent.vm.$emit("update:modelValue", newMobileLocationQuestions);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(wrapper.vm.mobileLocationQuestions).toStrictEqual(newMobileLocationQuestions);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("resets service zip code when mobile location is updated", async () => {
|
||||||
|
// Arrange
|
||||||
|
const { wrapper } = setupMocks({});
|
||||||
|
wrapper.vm.$refs.mobileLocationModalQuestions.resetComponent = jest.fn();
|
||||||
|
wrapper.vm.$refs.serviceZipCodeQuestion.resetMobileFeePart = jest.fn();
|
||||||
|
|
||||||
|
const serviceZipCodeInfo = {
|
||||||
|
state: "OH",
|
||||||
|
zipCode: "43054",
|
||||||
isServiceable: true,
|
isServiceable: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Act
|
const newServiceZipCodeInfo = {
|
||||||
wrapper.vm.$refs.mobileLocationModalQuestions.resetComponent = jest.fn();
|
state: "OH",
|
||||||
wrapper.vm.serviceZipCodeQuestion = newServiceZipCodeQuestion;
|
zipCode: "43081",
|
||||||
|
isServiceable: true,
|
||||||
|
};
|
||||||
|
wrapper.vm.serviceZipCodeQuestion = serviceZipCodeInfo;
|
||||||
|
|
||||||
await wrapper.vm.$nextTick();
|
const mobileLocationComponent = wrapper.findComponent({
|
||||||
|
ref: "mobileLocationModalQuestions",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
mobileLocationComponent.vm.$emit("updated-zip-code", {
|
||||||
|
state: "OH",
|
||||||
|
zipCode: "43081",
|
||||||
|
isServiceable: true,
|
||||||
|
});
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
expect(wrapper.vm.mobileLocationQuestions.serviceZipCode).toStrictEqual(
|
expect(wrapper.vm.serviceZipCodeQuestion).toStrictEqual(newServiceZipCodeInfo);
|
||||||
newServiceZipCodeQuestion.zipCode
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -166,10 +299,6 @@ function setupMocks({ mountOptionsMockData = {} }) {
|
||||||
|
|
||||||
const apiPromise = Promise.resolve(apiResponses);
|
const apiPromise = Promise.resolve(apiResponses);
|
||||||
|
|
||||||
settleAllPromises.mockImplementation(() => apiPromise);
|
|
||||||
fetchCmsContentForPage.mockImplementation(() => Promise.resolve());
|
|
||||||
getMobileFee.mockImplementation(() => Promise.resolve());
|
|
||||||
|
|
||||||
const mountOptions = getMountOptions(mountOptionsMockData);
|
const mountOptions = getMountOptions(mountOptionsMockData);
|
||||||
const wrapper = shallowMount(serviceLocation, mountOptions);
|
const wrapper = shallowMount(serviceLocation, mountOptions);
|
||||||
wrapper.vm.setCmsContent = baseMixin.methods.setCmsContent;
|
wrapper.vm.setCmsContent = baseMixin.methods.setCmsContent;
|
||||||
|
|
|
||||||
|
|
@ -6,16 +6,25 @@
|
||||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" />
|
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" />
|
||||||
<serviceZipModalQuestion
|
<serviceZipModalQuestion
|
||||||
v-model="serviceZipCodeQuestion"
|
v-model="serviceZipCodeQuestion"
|
||||||
@updated-service-zip-code="recalculateMobileFee"
|
@updated-service-zip-code="resetMobileLocation"
|
||||||
ref="serviceZipCodeQuestion"
|
ref="serviceZipCodeQuestion"
|
||||||
linkWidgetName="ServiceZipLinkWidget"
|
linkWidgetName="ServiceZipLinkWidget"
|
||||||
modalWidgetName="ServiceZipModalWidget" />
|
modalWidgetName="ServiceZipModalWidget" />
|
||||||
|
<div class="text-center">
|
||||||
|
<label
|
||||||
|
for="mobileLocationLinkPromptId"
|
||||||
|
:aria-label="mobileLocationLinkPromptText"
|
||||||
|
class="form-label fw-bold w-100 ps-4 pe-4 pt-4"
|
||||||
|
v-html="mobileLocationLinkPromptText"></label>
|
||||||
|
</div>
|
||||||
<mobileLocationModalQuestions
|
<mobileLocationModalQuestions
|
||||||
v-model="mobileLocationQuestions"
|
v-model="mobileLocationQuestions"
|
||||||
@updated-zip-code="resetServiceZipCode"
|
|
||||||
ref="mobileLocationModalQuestions"
|
ref="mobileLocationModalQuestions"
|
||||||
linkWidgetName="MobileLocationLinkWidget"
|
linkWidgetName="MobileLocationLinkWidget"
|
||||||
modalWidgetName="MobileLocationModalWidget" />
|
modalWidgetName="MobileLocationModalWidget" />
|
||||||
|
<div class="text-center">
|
||||||
|
<textBlock :customText="mobileFeeDisclaimerText" typeStyle="caption" />
|
||||||
|
</div>
|
||||||
<funnel-footer
|
<funnel-footer
|
||||||
cmsWidgetName="FunnelFooterWidget"
|
cmsWidgetName="FunnelFooterWidget"
|
||||||
ref="funnelFooter"
|
ref="funnelFooter"
|
||||||
|
|
@ -38,32 +47,23 @@ import { Form } from "vee-validate";
|
||||||
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";
|
||||||
import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper";
|
import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper";
|
||||||
import { getMobileFee } from "@/helpers/service-location-helper";
|
import { getPricedMobileFeePart } from "@/helpers/service-location-helper";
|
||||||
import store from "@/store";
|
import store from "@/store";
|
||||||
|
import textBlock from "@/digital-components/text-block/text-block";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "service-location",
|
name: "service-location",
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
streetAddress: this.getServiceAddressFromStore(),
|
||||||
|
apartmentNumberOrBusinessName: "",
|
||||||
|
city: this.getServiceCityFromStore(),
|
||||||
|
state: this.getServiceStateFromStore(),
|
||||||
|
zipCode: this.getServiceZipCodeFromStore(),
|
||||||
|
isServiceable: false,
|
||||||
isZipServiceableMobile: null,
|
isZipServiceableMobile: null,
|
||||||
isZipServiceableInShop: null,
|
isZipServiceableInShop: null,
|
||||||
serviceZipCodeQuestion: {
|
mobileFeePart: null,
|
||||||
state: this.getServiceStateFromStore(),
|
|
||||||
zipCode: this.getServiceZipCodeFromStore(),
|
|
||||||
isServiceable: this.getIsServiceableFromStore(),
|
|
||||||
},
|
|
||||||
mobileLocationQuestions: {
|
|
||||||
addressQuestions: {
|
|
||||||
streetAddress: this.getRegistrationAddressFromStore(),
|
|
||||||
apartmentNumberOrBusinessName: "",
|
|
||||||
city: this.getRegistrationCityFromStore(),
|
|
||||||
state: this.getRegistrationStateFromStore(),
|
|
||||||
zipCode: this.getRegistrationZipFromStore(),
|
|
||||||
},
|
|
||||||
isVehicleProtected: null,
|
|
||||||
serviceZipCode: this.getServiceZipCodeFromStore(),
|
|
||||||
mobileFee: 0,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
async beforeRouteEnter(to, from, next) {
|
async beforeRouteEnter(to, from, next) {
|
||||||
|
|
@ -71,7 +71,7 @@ export default {
|
||||||
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
|
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
|
||||||
|
|
||||||
const serviceZipCode = store.getters.order.serviceLocation.zipCode;
|
const serviceZipCode = store.getters.order.serviceLocation.zipCode;
|
||||||
const mobileFeePromise = getMobileFee(serviceZipCode);
|
const mobileFeePartPromise = getPricedMobileFeePart(serviceZipCode);
|
||||||
|
|
||||||
// Settle promises and get results
|
// Settle promises and get results
|
||||||
const promiseResultMap = [
|
const promiseResultMap = [
|
||||||
|
|
@ -80,8 +80,8 @@ export default {
|
||||||
promise: cmsContentPromise,
|
promise: cmsContentPromise,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
resultKey: "mobileFee",
|
resultKey: "mobileFeePart",
|
||||||
promise: mobileFeePromise,
|
promise: mobileFeePartPromise,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
@ -90,9 +90,71 @@ 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.setData(resultMap.mobileFee);
|
vm.setData(resultMap.mobileFeePart);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
serviceZipCodeQuestion: {
|
||||||
|
get: function () {
|
||||||
|
return {
|
||||||
|
state: this.state,
|
||||||
|
zipCode: this.zipCode,
|
||||||
|
isServiceable: this.isServiceable,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
if (newValue.zipCode !== this.zipCode) {
|
||||||
|
this.resetMobileLocation(this.zipCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.state = newValue.state;
|
||||||
|
this.zipCode = newValue.zipCode;
|
||||||
|
this.isServiceable = newValue.isServiceable;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mobileLocationQuestions: {
|
||||||
|
get: function () {
|
||||||
|
return {
|
||||||
|
addressQuestions: {
|
||||||
|
streetAddress: this.streetAddress,
|
||||||
|
apartmentNumberOrBusinessName: this.apartmentNumberOrBusinessName,
|
||||||
|
city: this.city,
|
||||||
|
state: this.state,
|
||||||
|
zipCode: this.zipCode,
|
||||||
|
},
|
||||||
|
isVehicleProtected: this.isVehicleProtected,
|
||||||
|
mobileFeePart: this.mobileFeePart,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
this.streetAddress = newValue.addressQuestions.streetAddress;
|
||||||
|
this.apartmentNumberOrBusinessName =
|
||||||
|
newValue.addressQuestions.apartmentNumberOrBusinessName;
|
||||||
|
this.city = newValue.addressQuestions.city;
|
||||||
|
this.state = newValue.addressQuestions.state;
|
||||||
|
this.zipCode = newValue.addressQuestions.zipCode;
|
||||||
|
this.isVehicleProtected = newValue.isVehicleProtected;
|
||||||
|
this.mobileFeePart = newValue.mobileFeePart;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mobileLocationLinkPromptText() {
|
||||||
|
return this.getCmsContent("MobileLocationLinkWidget", "HeaderText");
|
||||||
|
},
|
||||||
|
mobileFeeDisclaimerText() {
|
||||||
|
const cmsContentText = this.getCmsContent("MobileFeeDisclaimerWidget", "Text");
|
||||||
|
return cmsContentText.replaceAll("{custom:mobileFee}", this.mobileFee);
|
||||||
|
},
|
||||||
|
mobileFee() {
|
||||||
|
if (!this.mobileFeePart) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
this.mobileFeePart.laborAmount +
|
||||||
|
this.mobileFeePart.sellingPrice +
|
||||||
|
this.mobileFeePart.kitPrice
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
arePagePrerequisitesValid() {
|
arePagePrerequisitesValid() {
|
||||||
return (
|
return (
|
||||||
|
|
@ -101,58 +163,40 @@ export default {
|
||||||
store.getters.payment.isInsurance !== null
|
store.getters.payment.isInsurance !== null
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
setData(mobileFee) {
|
setData(mobileFeePart) {
|
||||||
if (mobileFee) {
|
if (mobileFeePart) {
|
||||||
this.mobileLocationQuestions.mobileFee = mobileFee;
|
this.mobileFeePart = mobileFeePart;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getRegistrationAddressFromStore() {
|
getServiceAddressFromStore() {
|
||||||
return store.getters.vehicle.registration.address;
|
return store.getters.order.serviceLocation.address;
|
||||||
},
|
},
|
||||||
getRegistrationCityFromStore() {
|
getServiceCityFromStore() {
|
||||||
return store.getters.vehicle.registration.city;
|
return store.getters.order.serviceLocation.city;
|
||||||
},
|
|
||||||
getRegistrationStateFromStore() {
|
|
||||||
return store.getters.vehicle.registration.state;
|
|
||||||
},
|
|
||||||
getRegistrationZipFromStore() {
|
|
||||||
return store.getters.vehicle.registration.zipCode;
|
|
||||||
},
|
|
||||||
getServiceZipCodeFromStore() {
|
|
||||||
return store.getters.order.serviceLocation.zipCode;
|
|
||||||
},
|
},
|
||||||
getServiceStateFromStore() {
|
getServiceStateFromStore() {
|
||||||
return store.getters.order.serviceLocation.state;
|
return store.getters.order.serviceLocation.state;
|
||||||
},
|
},
|
||||||
|
getServiceZipCodeFromStore() {
|
||||||
|
return store.getters.order.serviceLocation.zipCode;
|
||||||
|
},
|
||||||
getIsServiceableFromStore() {
|
getIsServiceableFromStore() {
|
||||||
return store.getters.order.serviceLocation.isServiceable;
|
return store.getters.order.serviceLocation.isServiceable;
|
||||||
},
|
},
|
||||||
recalculateMobileFee(serviceZipCode) {
|
resetMobileFeePart(serviceZipCode) {
|
||||||
getMobileFee(serviceZipCode).then((mobileFee) => {
|
getPricedMobileFeePart(serviceZipCode).then((pricedMobileFeePart) => {
|
||||||
this.mobileLocationQuestions.mobileFee = mobileFee;
|
this.mobileFeePart = pricedMobileFeePart;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
resetMobileLocation(updatedServiceZipCode) {
|
resetMobileLocation(updatedServiceZipCode) {
|
||||||
this.mobileLocationQuestions = {
|
this.streetAddress = "";
|
||||||
addressQuestions: {
|
this.apartmentNumberOrBusinessName = "";
|
||||||
streetAddress: "",
|
this.city = "";
|
||||||
apartmentNumberOrBusinessName: "",
|
this.isVehicleProtected = null;
|
||||||
city: "",
|
|
||||||
state: "",
|
|
||||||
zipCode: "",
|
|
||||||
},
|
|
||||||
isVehicleProtected: null,
|
|
||||||
serviceZipCode: updatedServiceZipCode,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.$refs.mobileLocationModalQuestions.resetComponent();
|
this.$refs.mobileLocationModalQuestions.resetComponent();
|
||||||
},
|
|
||||||
resetServiceZipCode(newValue) {
|
|
||||||
this.serviceZipCodeQuestion.state = newValue.state;
|
|
||||||
this.serviceZipCodeQuestion.zipCode = newValue.zipCode;
|
|
||||||
this.serviceZipCodeQuestion.isServiceable = newValue.isServiceable;
|
|
||||||
|
|
||||||
this.recalculateMobileFee(this.serviceZipCodeQuestion.zipCode);
|
this.resetMobileFeePart(updatedServiceZipCode);
|
||||||
},
|
},
|
||||||
backButtonAction() {
|
backButtonAction() {
|
||||||
this.$router.navigateWithoutSaving(this.navigationScenarios.CLICKED_BACK, this.$route);
|
this.$router.navigateWithoutSaving(this.navigationScenarios.CLICKED_BACK, this.$route);
|
||||||
|
|
@ -161,15 +205,6 @@ export default {
|
||||||
navigateToHeritageFunnel({ loadingModal: this.$refs.loadingModal });
|
navigateToHeritageFunnel({ loadingModal: this.$refs.loadingModal });
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
|
||||||
serviceZipCodeQuestion: {
|
|
||||||
handler(newValue, oldValue) {
|
|
||||||
if (newValue !== oldValue) {
|
|
||||||
this.resetMobileLocation(newValue.zipCode);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
components: {
|
components: {
|
||||||
serviceZipModalQuestion,
|
serviceZipModalQuestion,
|
||||||
mobileLocationModalQuestions,
|
mobileLocationModalQuestions,
|
||||||
|
|
@ -178,6 +213,7 @@ export default {
|
||||||
funnelSubHeader,
|
funnelSubHeader,
|
||||||
Form,
|
Form,
|
||||||
loadingModal,
|
loadingModal,
|
||||||
|
textBlock,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -142,8 +142,6 @@ export default {
|
||||||
this.internalModel.state = zipCodeData.state;
|
this.internalModel.state = zipCodeData.state;
|
||||||
this.internalModel.isServiceable = zipCodeData.isServiceable;
|
this.internalModel.isServiceable = zipCodeData.isServiceable;
|
||||||
|
|
||||||
this.$emit("updated-service-zip-code", this.internalModel.zipCode);
|
|
||||||
|
|
||||||
// Update the page level model
|
// Update the page level model
|
||||||
this.$emit("update:modelValue", this.internalModel);
|
this.$emit("update:modelValue", this.internalModel);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -173,7 +173,7 @@ export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
vin: this.getVinFromStore(),
|
vin: this.getVinFromStore(),
|
||||||
serviceZipCode: this.getZipFromStore() ?? this.$route.query.zipCode,
|
serviceZipCode: this.getZipFromStore() ?? this.$route.query.zipcode,
|
||||||
emailAddress: this.getEmailFromStore(),
|
emailAddress: this.getEmailFromStore(),
|
||||||
isCarIdDifferent: false,
|
isCarIdDifferent: false,
|
||||||
customAlertData: {},
|
customAlertData: {},
|
||||||
|
|
|
||||||
|
|
@ -265,14 +265,23 @@ async function navigate(
|
||||||
fmgPage: destinationFmgPageValue,
|
fmgPage: destinationFmgPageValue,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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 hasZip = lowerCaseParams.has(queryStrings.ZIP_CODE);
|
||||||
|
const zip = lowerCaseParams.get(queryStrings.ZIP_CODE);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
Object.hasOwn(currentRoute.query, queryStrings.ZIP_CODE) &&
|
hasZip &&
|
||||||
(store.getters.order.serviceLocation.zipCode === undefined ||
|
(store.getters.order.serviceLocation.zipCode === undefined ||
|
||||||
store.getters.order.serviceLocation.zipCode == null) &&
|
store.getters.order.serviceLocation.zipCode == null) &&
|
||||||
(store.getters.order.vehicle.registration.zipCode === undefined ||
|
(store.getters.order.vehicle.registration.zipCode === undefined ||
|
||||||
store.getters.order.vehicle.registration.zipCode == null)
|
store.getters.order.vehicle.registration.zipCode == null)
|
||||||
) {
|
) {
|
||||||
queryStringsObject[queryStrings.ZIP_CODE] = currentRoute.query.zipCode;
|
queryStringsObject[queryStrings.ZIP_CODE] = zip;
|
||||||
}
|
}
|
||||||
|
|
||||||
router.push({
|
router.push({
|
||||||
|
|
|
||||||
|
|
@ -530,6 +530,13 @@ export const actions = {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
isVinByAddressPermissible(context, zip) {
|
||||||
|
return globalMethods.callHttpClient({
|
||||||
|
method: endpoints.IsVinByAddressPermissible.method,
|
||||||
|
endpoint: `${endpoints.IsVinByAddressPermissible.url}?zipcode=${zip}`,
|
||||||
|
payload: {},
|
||||||
|
});
|
||||||
|
},
|
||||||
getVehicleMakes(context, { year }) {
|
getVehicleMakes(context, { year }) {
|
||||||
return globalMethods.callHttpClient({
|
return globalMethods.callHttpClient({
|
||||||
method: endpoints.GetVehicleMakes.method,
|
method: endpoints.GetVehicleMakes.method,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue