Merge branch 'develop' into feature/CSR-480

This commit is contained in:
Adam Caouette 2022-06-17 09:26:54 -04:00
commit 658a0e7c54
27 changed files with 1883 additions and 827 deletions

View file

@ -181,4 +181,22 @@ export default {
text-align: center;
}
}
.vehicle-parts {
.question-text {
span {
font-size: .875rem;
text-align: left;
margin: 0 0 .5rem 0;
}
}
.question-text {
margin: 0;
}
fieldset {
.ui-radio {
margin: 0;
}
}
}
</style>

View file

@ -1,35 +0,0 @@
import { shallowMount } from "@vue/test-utils";
import textInput from "./text-input";
describe("text-input.vue", () => {
it("Should render a text input", async () => {
// Act
const wrapper = shallowMount(textInput, {
propsData: {
name: "test",
label: "unit test label",
},
});
// Assert
const input = wrapper.find("input");
expect(input.exists()).toBe(true);
});
it("Should return aria-required state", async () => {
// Act
const wrapper = shallowMount(textInput, {
propsData: {
name: "test",
label: "unit test label",
isRequired: true,
},
});
// Assert
const input = wrapper.find("input");
expect(input.attributes()["aria-required"]).toEqual("true");
});
});

View file

@ -1,71 +0,0 @@
<!-- Simple implementation of an input field -->
<template>
<div class="d-flex w-50 mb-2" :class="{ 'has-error': !!errorMessage }">
<input
type="text"
:name="name"
:value="inputValue"
:id="name"
:aria-required="isRequired"
@input="handleChange"
@blur="handleBlur"
:data-focus-target="name"
/>
<label
:for="name"
:aria-labelledby="name"
class="d-flex justify-content-center py-3 px-4"
>
<span class="m-0">{{ label }}</span>
</label>
</div>
<div v-show="errorMessage" class="row px-3 form-test-error">
{{ errorMessage }}
</div>
</template>
<script>
import { useField } from "vee-validate";
export default {
name: "textInput",
props: {
type: {
type: String,
default: "text",
},
value: {
type: String,
default: "",
},
name: {
type: String,
required: true,
},
label: {
type: String,
required: true,
},
isRequired: Boolean,
},
setup(props) {
const {
value: inputValue,
errorMessage,
handleBlur,
handleChange,
meta,
} = useField(props.name, undefined, {
initialValue: props.value,
});
return {
handleChange,
handleBlur,
errorMessage,
inputValue,
meta,
};
},
};
</script>

View file

@ -167,7 +167,7 @@ describe("textboxQuestion.vue", () => {
});
it("Should call this.handleChange with new value when this.semiAggressiveValidation = true, the value is changed, and the new value is valid", async () => {
it("Should call this.handleChange with new value when the value is changed and the new value is valid", async () => {
// Arrange
const wrapper = shallowMount(textboxQuestion, {
global: {
@ -178,7 +178,6 @@ describe("textboxQuestion.vue", () => {
propsData: {
options: {},
modelValue: "foo",
semiAggressiveValidation: true,
},
mixins: [mockMixin]
});

View file

@ -20,6 +20,7 @@
@change="handleChange"
@blur="handleChange"
:maxlength="maxLength ? maxLength : '999'"
@focus="$emit('focus', $event.target.value)"
/>
<div v-show="errorMessage" class="row my-2 form-test-error">
<span class="d-inline-flex mt-0" role="alert">{{ errorMessage }}</span>
@ -54,7 +55,6 @@ export default {
default: "",
},
validationRules: String,
semiAggressiveValidation: Boolean,
cmsWidgetName: String,
maxLength: String,
},
@ -129,12 +129,10 @@ export default {
},
watch: {
async value(newValue) {
if (this.semiAggressiveValidation) {
const result = await validate(newValue, this.validationRules); // do a test validation check, without triggering full validation
if (result.valid) {
this.handleChange(newValue); // trigger full validation on this field only
}
}
const result = await validate(newValue, this.validationRules); // do a test validation check, without triggering full validation
if (result.valid) {
this.handleChange(newValue); // trigger full validation on this field only
}
},
},
};

View file

@ -14,7 +14,7 @@ const storeMutations = {
UPDATE_IS_REPAIR: "updateIsRepair",
UPDATE_NUMBER_OF_CHIPS: "updateNumberOfChips",
UPDATE_GLASS_TO_REPLACE: "updateGlassToReplace",
UPDATE_PARTS: "updateParts",
UPDATE_GLASS_PARTS: "updateGlassParts",
UPDATE_REGISTRATION_LICENSE_PLATE : "updateRegistrationLicensePlate",
UPDATE_REGISTRATION_ADDRESS: "updateRegistrationAddress",
UPDATE_REGISTRATION_CITY: "updateRegistrationCity",
@ -40,7 +40,7 @@ const storeMutations = {
RESET_VEHICLE_STATE: "resetVehicleState",
RESET_DAMAGE_STATE: "resetDamageState",
RESET_REGISTRATION_STATE: "resetRegistrationState",
RESET_PARTS_STATE: "resetPartsState",
RESET_GLASS_PARTS_STATE: "resetGlassPartsState",
RESET_STATE: "resetState",
// OTHER MUTATIONS

View file

@ -9,7 +9,7 @@ const tintMap = {
{ name: "brown tint, blue shade", src: "Glass-BlueShade-BrownTint.svg" },
{ name: "gray tint, blue shade", src: "Glass-BlueShade-GrayTint.svg" },
{ name: "green tint, blue shade", src: "Glass-BlueShade-GreenTint.svg" },
{ name: "blue shade", src: "Glass-BlueShade-NoTint.svg" },
{ name: "clear, blue shade", src: "Glass-BlueShade-NoTint.svg" },
// Brown Shade
{ name: "brown tint, brown shade", src: "Glass-BrownShade-BrownTint.svg" },
@ -26,13 +26,13 @@ const tintMap = {
{ name: "green tint, green shade", src: "Glass-GreenShade-GreenTint.svg" },
// Tints Only
{ name: "blue tint", src: "Glass-NoShade-BlueTint.svg" },
{ name: "brown tint", src: "Glass-NoShade-BrownTint.svg" },
{ name: "blue tint privacy", src: "Glass-NoShade-BlueTint.svg" },
{ name: "bronze tint", src: "Glass-NoShade-BrownTint.svg" },
{ name: "dark brown tint", src: "Glass-NoShade-DarkBrownTint.svg" },
{ name: "dark gray tint", src: "Glass-NoShade-DarkGrayTint.svg" },
{ name: "privacy, black frame", src: "Glass-NoShade-Privacy.svg" },
{ name: "gray tint", src: "Glass-NoShade-GrayTint.svg" },
{ name: "green tint", src: "Glass-NoShade-GreenTint.svg" },
{ name: "gray tint privacy", src: "Glass-NoShade-Privacy.svg" },
{ name: "gray tint privacy", src: "Glass-NoShade-GrayTint.svg" },
// No shade or tint
{ name: "clear", src: "Glass-NoShade-NoTint.svg" }
@ -41,13 +41,13 @@ const tintMap = {
windshield: [
// Blue Shade
{ name: "blue tint, blue shade", src: "Windshield-BlueShade-BlueTint.svg" },
{ name: "brown tint, blue shade", src: "Windshield-BlueShade-BrownTint.svg" },
{ name: "bronze tint, blue shade", src: "Windshield-BlueShade-BrownTint.svg" },
{ name: "gray tint, blue shade", src: "Windshield-BlueShade-GrayTint.svg" },
{ name: "green tint, blue shade", src: "Windshield-BlueShade-GreenTint.svg" },
{ name: "blue shade", src: "Windshield-BlueShade-NoTint.svg" },
{ name: "clear, blue shade", src: "Windshield-BlueShade-NoTint.svg" },
// Brown Shade
{ name: "brown tint, brown shade", src: "Windshield-BrownShade-BrownTint.svg" },
{ name: "bronze tint, bronze shade", src: "Windshield-BrownShade-BrownTint.svg" },
// Gray Shade
{ name: "blue tint, gray shade", src: "Windshield-GrayShade-BlueTint.svg" },
@ -62,12 +62,12 @@ const tintMap = {
// Tints Only
{ name: "blue tint", src: "Windshield-NoShade-BlueTint.svg" },
{ name: "brown tint", src: "Windshield-NoShade-BrownTint.svg" },
{ name: "bronze tint", src: "Windshield-NoShade-BrownTint.svg" },
{ name: "dark brown tint", src: "Windshield-NoShade-DarkBrownTint.svg" },
{ name: "dark gray tint", src: "Windshield-NoShade-DarkGrayTint.svg" },
{ name: "privacy, black frame", src: "Windshield-NoShade-Privacy.svg" },
{ name: "gray tint", src: "Windshield-NoShade-GrayTint.svg" },
{ name: "green tint", src: "Windshield-NoShade-GreenTint.svg" },
{ name: "gray tint privacy", src: "Windshield-NoShade-Privacy.svg" },
{ name: "gray tint privacy", src: "Windshield-NoShade-GrayTint.svg" },
// No shade or tint
{ name: "clear", src: "Windshield-NoShade-NoTint.svg" },

View file

@ -2,7 +2,6 @@
import addressLookup from "@/layouts/address-lookup/address-lookup.vue";
// Supporting Files
import baseMixin from "@/mixins/base-mixin";
import { shallowMount } from "@vue/test-utils";
import { getMountOptions } from "@/helpers/unit-test-helper.js";
import { storeActions } from "@/constants/store-actions";
@ -32,7 +31,7 @@ describe("address-lookup.vue", () => {
zipCode: "43215"
}
const { wrapper } = setupMocks(addressLookup, {
const { wrapper } = setupMocks({
isZipServiceable: false
});
@ -48,9 +47,9 @@ describe("address-lookup.vue", () => {
await wrapper.vm.forwardButtonAction();
// Assert
expect(wrapper.findComponent({ ref: "alertNonServiceableZip" }).isVisible()).toBe(true);
});
expect(wrapper.findComponent({ ref: "alertNonServiceableZip" }).isVisible()).toBe(true);
});
test("if the address matches a different vehicle display the Matched Different VehicleAlert", async () => {
// Arrange
const mockRegistrationAddress = {
@ -60,7 +59,7 @@ describe("address-lookup.vue", () => {
zipCode: "43215"
}
const { wrapper } = setupMocks(addressLookup, {
const { wrapper } = setupMocks({
isZipServiceable: true
});
@ -76,8 +75,8 @@ describe("address-lookup.vue", () => {
await wrapper.vm.forwardButtonAction();
// Assert
expect(wrapper.findComponent({ ref: "alertMatchedDifferentVehicle" }).isVisible()).toBe(true);
});
expect(wrapper.findComponent({ ref: "alertMatchedDifferentVehicle" }).isVisible()).toBe(true);
});
test("if the looking up VIN by address is not allowed in the state selected display the Vin Lookup By HomeAddress Not Allowed Alert", async () => {
// Arrange
@ -88,41 +87,27 @@ describe("address-lookup.vue", () => {
zipCode: "43215"
}
const { wrapper } = setupMocks(addressLookup, {
isZipServiceable: true
const { wrapper } = setupMocks({
isZipServiceable: true,
lookupVinbyAddressResponse: {
isStatePermissible: false,
vinVehicles: [{
vin: "TEST_VIN",
vehicle: {
carId: "CARID"
}
},
{
vin: "TEST_VIN2",
vehicle: {
carId: "CARID2"
}
}]
}
});
store.commit(storeMutations.UPDATE_CAR_ID, "CARID");
baseMixin.methods.dispatchStoreAction = jest.fn();
baseMixin.methods.dispatchStoreAction.mockImplementation((actionName, value) => {
let data = {};
if (actionName == storeActions.VALIDATE_ZIP) {
data = {
isServiceable: true
}
}
else if (actionName == storeActions.LOOKUP_VIN_BY_ADDRESS) {
data = {
isStatePermissible: false,
vinVehicles: [{
vin: "TEST_VIN",
vehicle: {
carId: "CARID"
}
},
{
vin: "TEST_VIN2",
vehicle: {
carId: "CARID2"
}
}]
}
}
return Promise.resolve({ data });
})
await wrapper.setData({
customerQuestions: {
addressQuestions: mockRegistrationAddress
@ -133,7 +118,7 @@ describe("address-lookup.vue", () => {
await wrapper.vm.forwardButtonAction();
// Assert
expect(wrapper.findComponent({ ref: "alertVinLookupsByHomeAddressNotAllowed" }).isVisible()).toBe(true);
expect(wrapper.findComponent({ ref: "alertVinLookupsByHomeAddressNotAllowed" }).isVisible()).toBe(true);
});
@ -146,31 +131,16 @@ describe("address-lookup.vue", () => {
zipCode: "43215"
}
const { wrapper } = setupMocks(addressLookup, {
isZipServiceable: true
const { wrapper } = setupMocks({
isZipServiceable: true,
lookupVinbyAddressResponse: {
isStatePermissible: true,
vinVehicles: [] // Return no vehicles
}
});
store.commit(storeMutations.UPDATE_CAR_ID, "CARID");
baseMixin.methods.dispatchStoreAction = jest.fn();
baseMixin.methods.dispatchStoreAction.mockImplementation((actionName, value) => {
let data = {};
if (actionName == storeActions.VALIDATE_ZIP) {
data = {
isServiceable: true
}
}
else if (actionName == storeActions.LOOKUP_VIN_BY_ADDRESS) {
data = {
isStatePermissible: true,
vinVehicles: [] // Return no vehicles
}
}
return Promise.resolve({ data });
})
await wrapper.setData({
customerQuestions: {
addressQuestions: mockRegistrationAddress
@ -183,21 +153,21 @@ describe("address-lookup.vue", () => {
await wrapper.vm.forwardButtonAction();
// Assert
expect(wrapper.findComponent({ ref: "alertVinNotFound" }).isVisible()).toBe(true);
expect(wrapper.findComponent({ ref: "alertVinNotFound" }).isVisible()).toBe(true);
});
});
});
describe("navigation", () => {
test("if the back button is clicked, navigate back", async () => {
// Arrange
const { wrapper } = setupMocks(addressLookup, {
const { wrapper } = setupMocks({
isZipServiceable: true
});
// Act
await wrapper.vm.backButtonAction();
await wrapper.vm.backButtonAction();
// Assert
expect(wrapper.vm.$router.navigate).toHaveBeenCalled();
@ -213,41 +183,27 @@ describe("address-lookup.vue", () => {
zipCode: "43215"
}
const { wrapper } = setupMocks(addressLookup, {
isZipServiceable: true
const { wrapper } = setupMocks({
isZipServiceable: true,
lookupVinbyAddressResponse: {
isStatePermissible: true,
vinVehicles: [{
vin: "TEST_VIN",
vehicle: {
carId: "CARID"
}
},
{
vin: "TEST_VIN2",
vehicle: {
carId: "CARID2"
}
}]
}
});
store.commit(storeMutations.UPDATE_CAR_ID, "CARID");
baseMixin.methods.dispatchStoreAction = jest.fn();
baseMixin.methods.dispatchStoreAction.mockImplementation((actionName, value) => {
let data = {};
if (actionName == storeActions.VALIDATE_ZIP) {
data = {
isServiceable: true
}
}
else if (actionName == storeActions.LOOKUP_VIN_BY_ADDRESS) {
data = {
isStatePermissible: true,
vinVehicles: [{
vin: "TEST_VIN",
vehicle: {
carId: "CARID"
}
},
{
vin: "TEST_VIN2",
vehicle: {
carId: "CARID2"
}
}]
}
}
return Promise.resolve({ data });
})
await wrapper.setData({
customerQuestions: {
addressQuestions: mockRegistrationAddress
@ -261,7 +217,7 @@ describe("address-lookup.vue", () => {
// Assert
expect(wrapper.vm.navigateForward).toHaveBeenCalled();
});
});
test("if the car entered matches one of multiple vehicles found, update vehicle info and navigate to the heritage funnel", async () => {
// Arrange
@ -272,42 +228,27 @@ describe("address-lookup.vue", () => {
zipCode: "43215"
}
const { wrapper } = setupMocks(addressLookup, {
isZipServiceable: true
const { wrapper } = setupMocks({
isZipServiceable: true,
lookupVinbyAddressResponse: {
isStatePermissible: true,
vinVehicles: [{
vin: "TEST_VIN",
vehicle: {
carId: "CARID"
}
},
{
vin: "TEST_VIN2",
vehicle: {
carId: "CARID2"
}
}]
}
});
store.commit(storeMutations.UPDATE_CAR_ID, "CARID");
baseMixin.methods.dispatchStoreAction = jest.fn();
baseMixin.methods.dispatchStoreAction.mockImplementation((actionName, value) => {
let data = {};
if (actionName == storeActions.VALIDATE_ZIP) {
data = {
isServiceable: true
}
}
else if (actionName == storeActions.LOOKUP_VIN_BY_ADDRESS) {
data = {
isStatePermissible: true,
vinVehicles: [{
vin: "TEST_VIN",
vehicle: {
carId: "CARID"
}
},
{
vin: "TEST_VIN2",
vehicle: {
carId: "CARID2"
}
}]
}
}
return Promise.resolve({ data });
})
await wrapper.setData({
customerQuestions: {
addressQuestions: mockRegistrationAddress
@ -322,8 +263,7 @@ describe("address-lookup.vue", () => {
// Assert
expect(wrapper.vm.updateVehicleInfo).toHaveBeenCalled();
expect(navigateAfterSaveToHeritageFunnel).toHaveBeenCalled();
});
});
test("if the car entered does not match any of the multiple vehicles found, navigate to address-vehicles page", async () => {
// Arrange
@ -334,54 +274,39 @@ describe("address-lookup.vue", () => {
zipCode: "43215"
}
const { wrapper } = setupMocks(addressLookup, {
isZipServiceable: true
const { wrapper } = setupMocks({
isZipServiceable: true,
lookupVinbyAddressResponse: {
isStatePermissible: true,
vinVehicles: [{
vin: "TEST_VIN",
vehicle: {
carId: "CARID"
}
},
{
vin: "TEST_VIN2",
vehicle: {
carId: "CARID2"
}
}]
}
});
store.commit(storeMutations.UPDATE_CAR_ID, "CARID_A");
baseMixin.methods.dispatchStoreAction = jest.fn();
baseMixin.methods.dispatchStoreAction.mockImplementation((actionName, value) => {
let data = {};
if (actionName == storeActions.VALIDATE_ZIP) {
data = {
isServiceable: true
}
}
else if (actionName == storeActions.LOOKUP_VIN_BY_ADDRESS) {
data = {
isStatePermissible: true,
vinVehicles: [{
vin: "TEST_VIN",
vehicle: {
carId: "CARID"
}
},
{
vin: "TEST_VIN2",
vehicle: {
carId: "CARID2"
}
}]
}
}
return Promise.resolve({ data });
})
const carsFound = [{
vin: "TEST_VIN",
vehicle: {
carId: "CARID"
}
},
{
vin: "TEST_VIN2",
vehicle: {
carId: "CARID2"
}
}]
vin: "TEST_VIN",
vehicle: {
carId: "CARID"
}
},
{
vin: "TEST_VIN2",
vehicle: {
carId: "CARID2"
}
}]
await wrapper.setData({
customerQuestions: {
@ -396,8 +321,7 @@ describe("address-lookup.vue", () => {
// Assert
expect(wrapper.vm.$router.navigateAfterSave).toHaveBeenCalledWith(navigationScenarios.CONTINUING_WITH_MULTIPLE_VEHICLES, undefined, {}, {}, carsFound);
});
});
test("if the car entered matches one of the vehicles found but the zip is NOT serviceable, do not navigate forward", async () => {
// Arrange
@ -408,42 +332,27 @@ describe("address-lookup.vue", () => {
zipCode: "43215"
}
const { wrapper } = setupMocks(addressLookup, {
isZipServiceable: true
const { wrapper } = setupMocks({
isZipServiceable: false,
lookupVinbyAddressResponse: {
isStatePermissible: true,
vinVehicles: [{
vin: "TEST_VIN",
vehicle: {
carId: "CARID"
}
},
{
vin: "TEST_VIN2",
vehicle: {
carId: "CARID2"
}
}]
}
});
store.commit(storeMutations.UPDATE_CAR_ID, "CARID");
baseMixin.methods.dispatchStoreAction = jest.fn();
baseMixin.methods.dispatchStoreAction.mockImplementation((actionName, value) => {
let data = {};
if (actionName == storeActions.VALIDATE_ZIP) {
data = {
isServiceable: false
}
}
else if (actionName == storeActions.LOOKUP_VIN_BY_ADDRESS) {
data = {
isStatePermissible: true,
vinVehicles: [{
vin: "TEST_VIN",
vehicle: {
carId: "CARID"
}
},
{
vin: "TEST_VIN2",
vehicle: {
carId: "CARID2"
}
}]
}
}
return Promise.resolve({ data });
})
await wrapper.setData({
customerQuestions: {
addressQuestions: mockRegistrationAddress
@ -458,7 +367,7 @@ describe("address-lookup.vue", () => {
// Assert
expect(wrapper.vm.navigateForward).toHaveBeenCalledTimes(0);
});
});
test("if a different vehicle is found than the one entered and the selected glass is not available for that vehicle, navigate back to vehicle-damage page", async () => {
// Arrange
@ -469,31 +378,21 @@ describe("address-lookup.vue", () => {
zipCode: "43215"
}
const { wrapper } = setupMocks(addressLookup, {
isZipServiceable: true
const { wrapper } = setupMocks({
isZipServiceable: true,
lookupVinbyAddressResponse: {
isStatePermissible: true,
vinVehicles: [{
vin: "TEST_VIN",
vehicle: {
carId: "CARID2"
}
}]
}
});
store.commit(storeMutations.UPDATE_CAR_ID, "CARID");
baseMixin.methods.dispatchStoreAction = jest.fn();
baseMixin.methods.dispatchStoreAction.mockImplementation((actionName, value) => {
let data = {};
if (actionName == storeActions.LOOKUP_VIN_BY_ADDRESS) {
data = {
isStatePermissible: true,
vinVehicles: [{
vin: "TEST_VIN",
vehicle: {
carId: "CARID2"
}
}]
}
}
return Promise.resolve({ data });
})
await wrapper.setData({
customerQuestions: {
addressQuestions: mockRegistrationAddress
@ -520,18 +419,79 @@ describe("address-lookup.vue", () => {
await wrapper.vm.navigateForward(carEntered, carsFound);
// Assert
expect(wrapper.vm.$router.navigateAfterSave).toHaveBeenCalledWith(navigationScenarios.CONTINUING_WITH_DIFFERENT_GLASS, undefined, {}, {"displayVehicleChangeAlert": true}, {});
expect(wrapper.vm.$router.navigateAfterSave).toHaveBeenCalledWith(navigationScenarios.SELECTED_VIN_HAS_MISMATCHED_GLASS, undefined, {}, { "displayVehicleChangeAlert": true }, {});
});
test("single car was found and matches entered vehicle => navigateForwardWithSingleCarMatch", async () => {
// Arrange
const carEntered = {
carId: "CARID2"
};
const carsFound = [
{
vin: "TEST_VIN_2",
vehicle: {
carId: "CARID2"
}
}
];
const { wrapper } = setupMocks({}, {});
wrapper.vm.navigateForwardWithSingleCarMatch = jest.fn();
// Act
wrapper.vm.navigateForward(carEntered, carsFound);
// Assert
expect(wrapper.vm.navigateForwardWithSingleCarMatch).toHaveBeenCalledTimes(1);
});
test("multiple cars were found and one matches entered vehicle => navigateForwardWithSingleCarMatch", async () => {
// Arrange
const carEntered = {
carId: "CARID2"
};
const carsFound = [
{
vin: "TEST_VIN_1",
vehicle: {
carId: "CARID1"
}
},
{
vin: "TEST_VIN_2",
vehicle: {
carId: "CARID2"
}
},
{
vin: "TEST_VIN_3",
vehicle: {
carId: "CARID3"
}
}
];
const { wrapper } = setupMocks({}, {});
wrapper.vm.navigateForwardWithSingleCarMatch = jest.fn();
// Act
wrapper.vm.navigateForward(carEntered, carsFound);
// Assert
expect(wrapper.vm.navigateForwardWithSingleCarMatch).toHaveBeenCalledTimes(1);
});
});
describe("reseting dependent state", () => {
describe("resetting dependent state", () => {
test("when reseting dependent state, license plate is set to null and parts state and dependencies are reset", async () => {
// Arrange
const commitSpy = jest.spyOn(store, "commit");
const dispatchSpy = jest.spyOn(store, "dispatch");
const { wrapper } = setupMocks(addressLookup, {
const { wrapper } = setupMocks({
isZipServiceable: true
});
@ -544,7 +504,7 @@ describe("address-lookup.vue", () => {
});
});
describe("registration and service zips", () => {
describe("if registration zip is serviceable", () => {
test("if registration address is provided => update service address on successful continue", async () => {
@ -556,7 +516,7 @@ describe("address-lookup.vue", () => {
zipCode: "43215"
}
const { wrapper } = setupMocks(addressLookup,{
const { wrapper } = setupMocks({
isZipServiceable: true
});
@ -572,7 +532,7 @@ describe("address-lookup.vue", () => {
await wrapper.vm.forwardButtonAction();
// Assert
expect(baseMixin.methods.dispatchStoreAction).toHaveBeenCalledWith(storeActions.UPDATE_SERVICE_LOCATION_WITH_VEHICLE_REGISTRATION);
expect(wrapper.vm.dispatchStoreAction).toHaveBeenCalledWith(storeActions.UPDATE_SERVICE_LOCATION_WITH_VEHICLE_REGISTRATION);
});
});
@ -586,7 +546,7 @@ describe("address-lookup.vue", () => {
zipCode: "43215"
}
const { wrapper } = setupMocks(addressLookup, {
const { wrapper } = setupMocks({
isZipServiceable: false
});
@ -618,9 +578,9 @@ describe("address-lookup.vue", () => {
zipCode: "43215"
}
const { wrapper } = setupMocks(addressLookup, {
isZipServiceable: false
}
const { wrapper } = setupMocks({
isZipServiceable: false
}
);
expect(wrapper.vm.showServiceZipField).toBeFalsy();
@ -650,9 +610,9 @@ describe("address-lookup.vue", () => {
zipCode: "43215"
}
const { wrapper } = setupMocks(addressLookup, {
isZipServiceable: false
}
const { wrapper } = setupMocks({
isZipServiceable: false
}
);
store.commit(storeMutations.UPDATE_CAR_ID, "CARID");
@ -667,7 +627,7 @@ describe("address-lookup.vue", () => {
await wrapper.vm.forwardButtonAction();
// Assert
expect(baseMixin.methods.dispatchStoreAction).not.toHaveBeenCalledWith(storeActions.UPDATE_SERVICE_LOCATION_WITH_VEHICLE_REGISTRATION);
expect(wrapper.vm.dispatchStoreAction).not.toHaveBeenCalledWith(storeActions.UPDATE_SERVICE_LOCATION_WITH_VEHICLE_REGISTRATION);
});
test("if registration address, service zip are provided, and user clicks continue => both zips are saved and are different", async () => {
@ -679,12 +639,13 @@ describe("address-lookup.vue", () => {
zipCode: "43215"
}
const { wrapper } = setupMocks(addressLookup, {});
const { wrapper } = setupMocks({});
wrapper.vm.navigateForwardWithSingleCarMatch = jest.fn();
store.commit(storeMutations.UPDATE_CAR_ID, "CARID");
baseMixin.methods.dispatchStoreAction = jest.fn();
baseMixin.methods.dispatchStoreAction.mockImplementation((actionName, value) => {
wrapper.vm.dispatchStoreAction = jest.fn();
wrapper.vm.dispatchStoreAction.mockImplementation((actionName, value) => {
let data = {};
if (actionName == storeActions.VALIDATE_ZIP) {
if (value == "43215") {
@ -718,15 +679,16 @@ describe("address-lookup.vue", () => {
addressQuestions: mockRegistrationAddress
}
})
await wrapper.vm.forwardButtonAction();
await wrapper.setData({
serviceZipCode: "12345"
})
// Act
// // Act
await wrapper.vm.forwardButtonAction();
// Assert
// // Assert
expect(store.getters.order.serviceLocation.zipCode).not.toEqual(store.getters.vehicle.registration.zipCode);
expect(store.getters.vehicle.registration.zipCode).toEqual("43215");
expect(store.getters.order.serviceLocation.zipCode).toEqual("12345");
@ -735,10 +697,9 @@ describe("address-lookup.vue", () => {
});
});
function setupMocks(mountOptions, { isZipServiceable = true, lookupVinbyAddressResponse }) {
function setupMocks({ isZipServiceable = true, lookupVinbyAddressResponse, partsOrQuestions = [] }) {
store.commit(storeMutations.RESET_STATE);
const wrapper = shallowMount(addressLookup, getMountOptions({
...mountOptions,
actionList: [
{
actionName: storeActions.VALIDATE_ZIP,
@ -757,7 +718,13 @@ function setupMocks(mountOptions, { isZipServiceable = true, lookupVinbyAddressR
}
}]
}
}
},
{
actionName: storeActions.GET_PARTS_OR_QUESTIONS,
data: {
partsOrQuestions: partsOrQuestions
}
},
],
router: {
navigate: jest.fn(),
@ -766,12 +733,11 @@ function setupMocks(mountOptions, { isZipServiceable = true, lookupVinbyAddressR
}));
wrapper.vm.getCmsContent = jest.fn().mockImplementation(() => "");
wrapper.vm.setCmsContent = baseMixin.methods.setCmsContent;
wrapper.vm.setCmsContent = jest.fn();
wrapper.vm.$refs.funnelFooter.updateButtonText = jest.fn();
wrapper.vm.$refs.funnelFooter.removeLoader = jest.fn();
wrapper.vm.$refs.loadingModal.showModal = jest.fn();
return { wrapper };
}

View file

@ -82,15 +82,15 @@ import { settleAllPromises } from "@/helpers/layout-helper";
import store from "@/store";
import { storeActions } from "@/constants/store-actions";
import { storeMutations } from "@/constants/store-mutations";
import baseMixin from "@/mixins/base-mixin";
import { navigateAfterSaveToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper";
import { getDamageString, isGlassAvailableForCarId } from "@/helpers/damage-helper";
import vinPagesMixin from "@/mixins/vin-pages-mixin";
defineRule("service-zip-required", required(errorMessages.SERVICE_ZIP_REQUIRED));
defineRule("service-zip-format", regex(/(^\d{5}$)|(^\d{5}-\d{4}$)/, errorMessages.SERVICE_ZIP_FORMAT));
export default {
name: "address-lookup",
mixins: [vinPagesMixin],
async beforeRouteEnter(to, from, next) {
// Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
@ -291,26 +291,23 @@ export default {
this.$router.navigateAfterSave(
// then navigate back to "vehicle-damage", and display vehicle changed alert
// on that page
this.navigationScenarios.CONTINUING_WITH_DIFFERENT_GLASS,
this.navigationScenarios.SELECTED_VIN_HAS_MISMATCHED_GLASS,
this.$route, {}, {
displayVehicleChangeAlert: true
}, {}
);
} else {
// otherwise
this.$refs.loadingModal.showModal();
navigateAfterSaveToHeritageFunnel(this.$route);
this.navigateForwardWithSingleCarMatch();
}
} else if (carsFound.length > 1) {
// if multiple cars were found
let matchingCars = carsFound.filter(car => car.vehicle.carId === carEntered.carId);
if (matchingCars.length === 1) {
this.$refs.loadingModal.showModal();
// and one and only of them matches the car id entered
const matchingCar = matchingCars[0];
this.updateVehicleInfo(matchingCar.vin, matchingCar.vehicle);
navigateAfterSaveToHeritageFunnel(this.$route);
this.navigateForwardWithSingleCarMatch();
} else {
// if there are no matches or there are multiple matches, navigate to "address-vehicles" page
this.$router.navigateAfterSave(this.navigationScenarios.CONTINUING_WITH_MULTIPLE_VEHICLES, this.$route, {}, {}, carsFound);
@ -319,18 +316,18 @@ export default {
},
validateZip(zip) {
return baseMixin.methods.dispatchStoreAction(
return this.dispatchStoreAction(
storeActions.VALIDATE_ZIP,
{ zip });
},
lookupVin(lastName, streetAddress, zip, state) {
return baseMixin.methods.dispatchStoreAction(
return this.dispatchStoreAction(
storeActions.LOOKUP_VIN_BY_ADDRESS,
{
licenseLastName: lastName,
licenseStreetAddress: streetAddress,
licenseZip: zip,
licenseState: state,
licenseZip: zip,
licenseState: state
}, false
);
},
@ -361,9 +358,7 @@ export default {
const serviceLocation = store.getters.order.serviceLocation;
if (!serviceLocation.address && serviceLocation.zipCode && serviceLocation.zipCode == store.getters.vehicle.registration.zipCode) {
baseMixin.methods.dispatchStoreAction(
storeActions.UPDATE_SERVICE_LOCATION_WITH_VEHICLE_REGISTRATION
);
this.dispatchStoreAction(storeActions.UPDATE_SERVICE_LOCATION_WITH_VEHICLE_REGISTRATION);
}
}
},

View file

@ -25,7 +25,6 @@
inputId="cbf28188fdf2436688fd735915f7ee56"
disableAutoFill
validationRules="city-required"
semiAggressiveValidation
/>
</div>
</div>
@ -52,7 +51,6 @@
mask="#####"
disableAutoFill
validationRules="zip-code-required|zip-code-format"
semiAggressiveValidation
/>
</div>
</div>

View file

@ -33,7 +33,6 @@
inputId="00450a91b8964a768ce3992e6feb890f"
disableAutoFill
validationRules="email-address-required|email-address-format"
semiAggressiveValidation
/>
</div>
</div>

View file

@ -5,9 +5,10 @@ import addressVehicles from "@/layouts/address-vehicles/address-vehicles";
import { shallowMount } from "@vue/test-utils";
import { getMountOptions } from "@/helpers/unit-test-helper.js";
import store from "@/store";
import { storeActions } from "@/constants/store-actions";
import { storeMutations } from "@/constants/store-mutations";
import * as navigateToHeritage from "@/helpers/heritage-integration/navigation-helper";
// Mock our module for promises.
jest.mock("@/helpers/damage-helper", () => ({
isGlassAvailableForCarId: () => {
@ -17,11 +18,12 @@ jest.mock("@/helpers/damage-helper", () => ({
describe("addressVehicles.vue", () => {
test("Should return true for valid page requisites if carId / zipCode / emailAddress / pageData exists", async () => {
// Arrange
const { wrapper } = setupMocks({});
wrapper.vm.$router.navigate = jest.fn();
store.commit(storeMutations.UPDATE_CAR_ID, "NOT NULL");
store.commit(storeMutations.UPDATE_SERVICE_LOCATION_ZIP_CODE, "12345");
store.commit(storeMutations.UPDATE_CUSTOMER_EMAIL_ADDRESS, "test@test.com");
// Act
const result = wrapper.vm.arePagePrerequisitesValid();
@ -35,10 +37,9 @@ describe("addressVehicles.vue", () => {
test("Should return false for valid page requisites if carId is missing", async () => {
// Arrange
const { wrapper } = setupMocks({});
wrapper.vm.$router.navigate = jest.fn();
// Act
wrapper.vm.$store.getters.order.vehicle.carId = null;
store.commit(storeMutations.UPDATE_CAR_ID, null);
const result = wrapper.vm.arePagePrerequisitesValid();
//Assert
@ -55,7 +56,7 @@ describe("addressVehicles.vue", () => {
wrapper.vm.$router.navigate = jest.fn();
// Act
wrapper.setData({
await wrapper.setData({
selectedVehicleVin: ['5NMS3CADXLH233004'],
});
wrapper.vm.backButtonAction();
@ -85,7 +86,7 @@ describe("addressVehicles.vue", () => {
wrapper.vm.navigateForward = jest.fn().mockImplementation(()=> {});
// Act
wrapper.setData({
await wrapper.setData({
selectedVehicleVin: ['5NMS3CADXLH233004'],
});
@ -115,7 +116,7 @@ describe("addressVehicles.vue", () => {
wrapper.vm.updateCustomerInfo = jest.fn().mockImplementation(()=> {});
// Act
wrapper.setData({
await wrapper.setData({
selectedVehicleVin: ['5NMS3CADXLH233004'],
});
await wrapper.vm.forwardButtonAction();
@ -142,7 +143,7 @@ describe("addressVehicles.vue", () => {
wrapper.vm.$router.navigateAfterSave = jest.fn();
// Act
wrapper.setData({
await wrapper.setData({
selectedVehicleVin: ['5NMS3CADXLH233004'],
isSelectedGlassAvailableForVehicle: false,
isCarIdDifferent: true,
@ -150,7 +151,7 @@ describe("addressVehicles.vue", () => {
await wrapper.vm.updateCustomerInfo(wrapper.vm.selectedVehicle.vin, wrapper.vm.selectedVehicle.vehicle);
//Assert
expect(store.dispatch).toBeCalledWith("resetDamageAndDependencies");
expect(wrapper.vm.dispatchStoreAction).toBeCalledWith("resetDamageAndDependencies");
wrapper.unmount();
});
@ -164,7 +165,7 @@ describe("addressVehicles.vue", () => {
await wrapper.vm.lookupVin('1234567890');
//Assert
expect(store.dispatch).toBeCalledWith("lookupVehicleByVin", {"vin": "1234567890"});
expect(wrapper.vm.dispatchStoreAction).toBeCalledWith("lookupVehicleByVin", {"vin": "1234567890"});
wrapper.unmount();
});
@ -172,10 +173,9 @@ describe("addressVehicles.vue", () => {
test("If selectedVehicleVin changes, then should update isCarIdDifferent", async () => {
// Arrange
const { wrapper } = setupMocks({});
wrapper.vm.$refs.funnelFooter.updateButtonText = jest.fn();
// Act
wrapper.setData({
await wrapper.setData({
selectedVehicleVin: ['5NMS3CADXLH233004'],
isCarIdDifferent: false,
});
@ -193,7 +193,7 @@ describe("addressVehicles.vue", () => {
wrapper.vm.$refs.funnelFooter.updateButtonText = jest.fn();
// Act
wrapper.setData({
await wrapper.setData({
selectedVehicleVin: ['5NMS3CADXLH233004'],
isCarIdDifferent: false,
});
@ -212,7 +212,7 @@ describe("addressVehicles.vue", () => {
wrapper.vm.$router.navigateAfterSave = jest.fn();
// Act
wrapper.setData({
await wrapper.setData({
selectedVehicleVin: ['5NMS3CADXLH233004'],
isSelectedGlassAvailableForVehicle: false,
isCarIdDifferent: true,
@ -225,7 +225,7 @@ describe("addressVehicles.vue", () => {
wrapper.unmount();
});
test("Should navigate to navigateAfterSaveToHeritageFunnel if carId is not different on navigateForward", async () => {
test("carId is not different on navigateForward (car was found) => Should handle navigating forward with car match", async () => {
// Arrange
const { wrapper } = setupMocks({});
wrapper.vm.$refs.funnelFooter.updateButtonText = jest.fn();
@ -233,79 +233,49 @@ describe("addressVehicles.vue", () => {
navigateToHeritage.navigateAfterSaveToHeritageFunnel = jest.fn();
// Act
wrapper.setData({
selectedVehicleVin: ['5NMS3CADXLH233004'],
isSelectedGlassAvailableForVehicle: true,
await wrapper.setData({
isCarIdDifferent: false,
});
await wrapper.vm.navigateForward();
//Assert
expect(navigateToHeritage.navigateAfterSaveToHeritageFunnel).toBeCalledTimes(1);
expect(wrapper.vm.navigateForwardWithSingleCarMatch).toBeCalledTimes(1);
wrapper.unmount();
});
});
function setupMocks({
// modelValueProp = "1900",
cmsQuestionText = "CMS text goes here",
// dataFromStoreApi = [],
}) {
function setupMocks({}) {
//Mock store
store.dispatch = jest.fn(() => {});
store.getters = {
pageData: jest.fn((pageName) => {
// console.log('pageName: ', pageName) // address-vehicles
return [
{
vehicle: {
"carId": "CR00069309",
"category": "SUV",
"year": 2020,
"make": "Hyundai",
"model": "Santa Fe",
"style": "4 door utility",
"imageUrl": "https://dbhdyzvm8lm25.cloudfront.net/color_0320_032/MY2020/13769/13769_cc0320_032_WW8.jpg",
"imageVifNumber": "13769",
"imageVifColor": "white"
},
vin: "5NMS3CADXLH233004"
},
];
}),
order: {
store.commit(storeMutations.RESET_STATE);
store.commit(storeMutations.UPDATE_PAGE_DATA, {
page: "address-vehicles",
data: [{
vehicle: {
carId: "123",
"carId": "CR00069309",
"category": "SUV",
"year": 2020,
"make": "Hyundai",
"model": "Santa Fe",
"style": "4 door utility",
"imageUrl": "https://dbhdyzvm8lm25.cloudfront.net/color_0320_032/MY2020/13769/13769_cc0320_032_WW8.jpg",
"imageVifNumber": "13769",
"imageVifColor": "white"
},
serviceLocation: {
zipCode: "12345"
},
customer: {
emailAddress: "qw@er.ty"
}
},
damage: {
glassToReplace: "Windshield"
},
vehicle: {
carId: "456",
}
};
// baseMixin.methods.dispatchStoreAction = jest.fn();
vin: "5NMS3CADXLH233004"
}],
})
const mountOptions = getMountOptions({
store: {
dispatch: store.dispatch,
getters: store.getters,
},
router: {
navigate: jest.fn(),
},
actionList: [
{
actionName: storeActions.LOOKUP_VEHICLE_BY_VIN,
data: {}
}
]
});
//Mock props
@ -320,16 +290,6 @@ function setupMocks({
}
return null;
}),
// dispatchStoreAction: jest.fn(() => {
// console.log("23424243")
// }),
// isGlassAvailableForCarId: () => {
// console.log("%%%%%%%%%%%%%%%")
// return Promise.resolve(true)
// }
// lookupVin: jest.fn(() => Promise.resolve(lookupVinResponse)),
},
computed: {
dynamicStrings() {
@ -337,18 +297,13 @@ function setupMocks({
}
}
}
// mountOptions.propsData = {
// modelValue: modelValueProp,
// };
mountOptions.mixins = [mockMixin];
const wrapper = shallowMount(addressVehicles, mountOptions);
//Mock CMS content
const cmsContent = {
QuestionText: cmsQuestionText,
};
wrapper.vm.$refs.funnelFooter.updateButtonText = jest.fn();
wrapper.vm.navigateForwardWithSingleCarMatch = jest.fn();
return { wrapper };
}

View file

@ -59,25 +59,25 @@ import loadingModal from '@/common-components/loading-modal/loading-modal.vue';
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import { settleAllPromises } from "@/helpers/layout-helper";
import store from "@/store";
import baseMixin from "@/mixins/base-mixin.js";
import { storeActions } from "@/constants/store-actions";
import { storeMutations } from "@/constants/store-mutations";
import { fmgPageValues } from "@/router/router-constants/fmgPage-values";
import { errorMessages } from "@/constants/error-messages";
import { required } from "@/helpers/validation-rules";
import { Form, defineRule } from "vee-validate";
import { navigateAfterSaveToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper";
import { isGlassAvailableForCarId } from "@/helpers/damage-helper";
import { doesCopyContainRouterLink,
splitCopyOnCMSPlaceHolder,
getRouterLinkRouteFromCopy,
getRouterLinkDisplayTextFromCopy, } from "@/helpers/cms-content-helper"
getRouterLinkDisplayTextFromCopy, } from "@/helpers/cms-content-helper";
import vinPagesMixin from "@/mixins/vin-pages-mixin";
// DEFINE VALIDATION RULES
defineRule("vehicle-required", required(errorMessages.VEHICLE_REQUIRED));
export default {
name: "address-vehicles",
mixins: [vinPagesMixin],
async beforeRouteEnter(to, from, next) {
// Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
@ -188,13 +188,12 @@ export default {
);
return;
} else {
this.$refs.loadingModal.showModal();
navigateAfterSaveToHeritageFunnel(this.$route);
this.navigateForwardWithSingleCarMatch();
return;
}
},
lookupVin(vin) {
return baseMixin.methods.dispatchStoreAction(
return this.dispatchStoreAction(
storeActions.LOOKUP_VEHICLE_BY_VIN,
{ vin }
);
@ -204,7 +203,7 @@ export default {
},
updateCustomerInfo(vin, vehicle) {
if (this.isCarIdDifferent && !this.isSelectedGlassAvailableForVehicle) {
store.dispatch(storeActions.RESET_DAMAGE_STATE_AND_DEPENDENCIES);
this.dispatchStoreAction(storeActions.RESET_DAMAGE_STATE_AND_DEPENDENCIES);
}
store.commit(storeMutations.UPDATE_VEHICLE_VIN, vin);
store.commit(storeMutations.UPDATE_YEAR, vehicle.year);

View file

@ -4,11 +4,11 @@ import licensePlateLookup from "@/layouts/license-plate-lookup/license-plate-loo
// Supporting Files
import { settleAllPromises } from "@/helpers/layout-helper.js";
import * as navigateToHeritage from "@/helpers/heritage-integration/navigation-helper";
import baseMixin from "@/mixins/base-mixin";
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import { shallowMount } from "@vue/test-utils";
import { getMountOptions } from "@/helpers/unit-test-helper.js";
import { nextTick } from "vue";
import { storeActions } from "@/constants/store-actions";
import { storeMutations } from "@/constants/store-mutations";
import store from "@/store";
@ -223,19 +223,22 @@ describe("license-plate-lookup.vue", () => {
});
describe("navigateForward", () => {
test("NavigateAfterSave should be called if isCarIdDifferent is true and isSelectedGlassAvailableForVehicle is false when navigateForward is called", async () => {
test("navigateAfterSave should be called if isCarIdDifferent is true and isSelectedGlassAvailableForVehicle is false when navigateForward is called", async () => {
// Arrange
const { wrapper } = setupMocks({});
//Act
wrapper.vm.isCarIdDifferent = true;
wrapper.vm.isSelectedGlassAvailableForVehicle = false;
await wrapper.setData({
isCarIdDifferent: true,
isSelectedGlassAvailableForVehicle: false
})
wrapper.vm.$router.navigateAfterSave = jest.fn();
wrapper.vm.getCmsContent = jest.fn().mockImplementation(() => {
return '';
});
store.dispatch = jest.fn();
wrapper.vm.dispatchStoreAction = jest.fn();
await wrapper.vm.navigateForward();
@ -249,7 +252,9 @@ describe("license-plate-lookup.vue", () => {
const { wrapper } = setupMocks({});
//Act
wrapper.vm.isCarIdDifferent = false;
await wrapper.setData({
isCarIdDifferent: false
})
wrapper.vm.getCmsContent = jest.fn().mockImplementation(() => {
return '';
});
@ -259,6 +264,38 @@ describe("license-plate-lookup.vue", () => {
//Assert
expect(navigateToHeritage.navigateAfterSaveToHeritageFunnel).toHaveBeenCalled();
});
test("carId matches returned vehicle => navigateForwardWithSingleCarMatch", async () => {
// Arrange
const { wrapper } = setupMocks({});
await wrapper.setData({
isCarIdDifferent: false
})
wrapper.vm.navigateForwardWithSingleCarMatch = jest.fn();
// Act
await wrapper.vm.navigateForward();
//Assert
expect(wrapper.vm.navigateForwardWithSingleCarMatch).toHaveBeenCalledTimes(1);
});
test("selected glass is available for returned vehicle => navigateForwardWithSingleCarMatch", async () => {
// Arrange
const { wrapper } = setupMocks({});
await wrapper.setData({
isSelectedGlassAvailableForVehicle: true
})
wrapper.vm.navigateForwardWithSingleCarMatch = jest.fn();
// Act
await wrapper.vm.navigateForward();
//Assert
expect(wrapper.vm.navigateForwardWithSingleCarMatch).toHaveBeenCalledTimes(1);
});
});
});
@ -269,7 +306,9 @@ describe("license-plate-lookup.vue", () => {
const { wrapper } = setupMocks({});
//Act
wrapper.vm.licensePlate = "NEWPLATE";
wrapper.setData({
licensePlate: "NEWPLATE"
})
wrapper.vm.getCmsContent = jest.fn();
await wrapper.vm.$nextTick();
@ -283,7 +322,9 @@ describe("license-plate-lookup.vue", () => {
const { wrapper } = setupMocks({});
//Act
wrapper.vm.registrationZip = "55555";
await wrapper.setData({
registrationZip: "55555"
})
wrapper.vm.getCmsContent = jest.fn();
await wrapper.vm.$nextTick();
@ -297,7 +338,9 @@ describe("license-plate-lookup.vue", () => {
const { wrapper } = setupMocks({});
//Act
wrapper.vm.serviceZip = "55555";
await wrapper.setData({
serviceZip: "55555"
})
wrapper.vm.getCmsContent = jest.fn();
await wrapper.vm.$nextTick();
@ -469,22 +512,23 @@ describe("license-plate-lookup.vue", () => {
const { wrapper } = setupMocks({});
//Act
wrapper.vm.isCarIdDifferent = true;
wrapper.vm.isSelectedGlassAvailableForVehicle = false;
await wrapper.setData({
isCarIdDifferent: true,
isSelectedGlassAvailableForVehicle: false
})
wrapper.vm.getCmsContent = jest.fn().mockImplementation(() => {
return '';
});
store.commit = jest.fn();
store.dispatch = jest.fn();
const vehicleInfo = { year: "2020", make: "honda", model: "civic", style: "2 door", carId: "TestId", category: "testCat", imageUrl: "image.jpg", imageVifNumber: "123", imageColor: "blue" }
await wrapper.vm.updateCustomerInfo('vin', vehicleInfo, 'registrationState');
//Assert
expect(store.dispatch).toHaveBeenCalled();
expect(wrapper.vm.dispatchStoreAction).toHaveBeenCalled();
})
test("dispatch non blocking store action called on validate zip", async () => {
test("dispatchStoreAction called on validate zip", async () => {
// Arrange
const { wrapper } = setupMocks({});
@ -494,10 +538,10 @@ describe("license-plate-lookup.vue", () => {
//Assert
expect(baseMixin.methods.dispatchStoreAction).toHaveBeenCalled();
expect(wrapper.vm.dispatchStoreAction).toHaveBeenCalled();
});
test("dispatch non blocking store action called on lookup vin", async () => {
test("dispatchStoreAction called on lookup vin", async () => {
// Arrange
const { wrapper } = setupMocks({});
@ -507,22 +551,18 @@ describe("license-plate-lookup.vue", () => {
//Assert
expect(baseMixin.methods.dispatchStoreAction).toHaveBeenCalled();
expect(wrapper.vm.dispatchStoreAction).toHaveBeenCalled();
});
})
});
function setupMocks({
pageHeaderWidgetHeaderText = {},
mountOptionsMockData = {
router: {
navigate: jest.fn(),
},
},
mountOptionsMockData = {},
partsOrQuestions = []
}) {
store.commit(storeMutations.RESET_STATE);
//Mock api responses
baseMixin.methods.dispatchStoreAction = jest.fn();
const apiResponses = {
cmsContent: {
FunnelSubHeaderWidget: pageHeaderWidgetHeaderText,
@ -537,6 +577,21 @@ function setupMocks({
},
};
mountOptionsMockData = {
...mountOptionsMockData,
router: {
navigate: jest.fn(),
},
actionList: [
{
actionName: storeActions.GET_PARTS_OR_QUESTIONS,
data: {
partsOrQuestions: partsOrQuestions
}
},
]
}
const apiPromise = Promise.resolve(apiResponses);
settleAllPromises.mockImplementation(() => apiPromise);
@ -547,7 +602,7 @@ function setupMocks({
const wrapper = shallowMount(licensePlateLookup, mountOptions);
wrapper.vm.setCmsContent = baseMixin.methods.setCmsContent;
wrapper.vm.setCmsContent = jest.fn();
wrapper.vm.getCmsContent = jest.fn().mockImplementation(() => "");
wrapper.vm.$refs.funnelFooter.updateButtonText = jest.fn();
wrapper.vm.$refs.funnelFooter.removeLoader = jest.fn();

View file

@ -35,7 +35,6 @@
v-model="email"
inputId="email"
validationRules="email-address-required|email-address-format"
semiAggressiveValidation
/>
</div>
</div>
@ -54,7 +53,6 @@
v-model="serviceZip"
inputId="serviceZip"
validationRules="zip-required|zip-format"
semiAggressiveValidation
/>
</div>
</div>
@ -94,14 +92,13 @@ import loadingModal from '@/common-components/loading-modal/loading-modal.vue';
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import { settleAllPromises } from "@/helpers/layout-helper";
import store from "@/store";
import baseMixin from "@/mixins/base-mixin.js";
import { storeActions } from "@/constants/store-actions";
import { storeMutations } from "@/constants/store-mutations";
import { errorMessages } from "@/constants/error-messages";
import { navigateAfterSaveToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper";
import { getDamageString, isGlassAvailableForCarId, } from "@/helpers/damage-helper";
import { required, regex, } from "@/helpers/validation-rules";
import { Form, defineRule, } from "vee-validate";
import vinPagesMixin from "@/mixins/vin-pages-mixin";
// DEFINE VALIDATION RULES
defineRule("license-plate-required", required(errorMessages.LICENSE_PLATE_REQUIRED));
@ -112,6 +109,7 @@ defineRule("email-address-format", regex(/^([a-zA-Z0-9_\-.+]+)@([a-zA-Z0-9_\-.]+
export default {
name: "license-plate-lookup",
mixins: [vinPagesMixin],
async beforeRouteEnter(to, from, next) {
// Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
@ -297,22 +295,21 @@ export default {
);
return;
} else {
this.$refs.loadingModal.showModal();
navigateAfterSaveToHeritageFunnel(this.$route);
this.navigateForwardWithSingleCarMatch();
return;
}
},
validateZip(zip) {
return baseMixin.methods.dispatchStoreAction(storeActions.VALIDATE_ZIP, {
return this.dispatchStoreAction(storeActions.VALIDATE_ZIP, {
zip,
});
},
lookupVin(plate, state) {
return baseMixin.methods.dispatchStoreAction(storeActions.LOOKUP_VIN_BY_PLATE,{ licensePlate: plate, licenseState: state }, false);
return this.dispatchStoreAction(storeActions.LOOKUP_VIN_BY_PLATE,{ licensePlate: plate, licenseState: state }, false);
},
updateCustomerInfo(vin, vehicleInfo, registrationState, serviceState) {
if (this.isCarIdDifferent && !this.isSelectedGlassAvailableForVehicle) {
store.dispatch(storeActions.RESET_DAMAGE_STATE_AND_DEPENDENCIES);
this.dispatchStoreAction(storeActions.RESET_DAMAGE_STATE_AND_DEPENDENCIES);
}
store.commit(storeMutations.UPDATE_VEHICLE_VIN, vin);
store.commit(storeMutations.UPDATE_YEAR, vehicleInfo.year);

View file

@ -5,9 +5,7 @@
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" />
<div class="fade-on-route-transition sub-container make-tall">
<h1>Part Questions Page Placeholder</h1>
<funnel-footer
cmsWidgetName="FunnelFooterWidget" @back-clicked="backButtonAction"
/>
<funnel-footer cmsWidgetName="FunnelFooterWidget" @back-clicked="backButtonAction" />
</div>
</div>
</template>
@ -65,14 +63,11 @@ export default {
);
},
arePagePrerequisitesValid() {
if (Object.keys(store.getters.pageData(fmgPageValues.PART_QUESTIONS)).length !== 0) {
return true;
}
return false;
return Object.keys(store.getters.pageData(fmgPageValues.PART_QUESTIONS)).length !== 0;
},
resetDependentState() {
// Set
store.commit(storeMutations.UPDATE_PARTS, null);
store.commit(storeMutations.UPDATE_GLASS_PARTS, null);
// Invokes
store.dispatch(storeActions.RESET_PARTS_AND_DEPS);

View file

@ -50,7 +50,7 @@ export default {
},
resetDependentState() {
// Set
store.commit(storeMutations.UPDATE_PARTS, null);
store.commit(storeMutations.UPDATE_GLASS_PARTS, null);
// Invokes
store.dispatch(storeActions.RESET_PARTS_AND_DEPS);

View file

@ -1,7 +1,7 @@
<template>
<div class="container">
<div class="row">
<p class="mb-0">{{ colorQuestionText }}</p>
<p class="mb-0 color-question-text">{{ colorQuestionText }}</p>
</div>
</div>
@ -29,7 +29,11 @@
:buttonID="`${glassLocation}-${glassName}-${name}`"
:groupName="`${glassLocation}-${glassName}`"
@isCheckedChanged="ResetTintAndPartSelections()"
:validationRules="validationRules"
/>
<div class="row form-test-error mt-1">
<error-message :name="`${glassLocation}-${glassName}`" v-if="!suppressError"></error-message>
</div>
</div>
</div>
<transition name="fade" mode="out-in">
@ -53,6 +57,7 @@
:loaderEnabled="false"
isRequired
:groupName="`${glassLocation}-${glassName}-${name}`"
:validationRules="validationRules"
/>
</div>
</div>
@ -68,6 +73,7 @@ import buttonQuestion from "@/common-components/button-question/button-question"
// Supporting files
import { getTintImage } from "@/constants/tint-mapper";
import { getCustomTransformValue } from "@/constants/dynamictext-mapper";
import { ErrorMessage } from 'vee-validate';
export default {
name: "glass-part-question",
@ -84,6 +90,7 @@ export default {
glassLocation: String,
colorAnswers: Array,
modelValue: Object,
validationRules: String,
},
mounted() {
this.LoadPreselectedValues();
@ -91,6 +98,7 @@ export default {
components: {
listCard,
buttonQuestion,
ErrorMessage,
},
computed: {
colorQuestionText() {
@ -168,23 +176,25 @@ export default {
// Check if only a single part is present for the tint and set the v-model if it is.
AutoSelectIfSinglePart() {
// Check if the selected tint only has a single feature
Object.keys(this.PartDataFromApi.partsOrQuestions).forEach((key) => {
const currentGlassSelection =
this.PartDataFromApi.partsOrQuestions[key];
Object.keys(this.PartDataFromApi.partsOrQuestions ?? {}).forEach(
(key) => {
const currentGlassSelection =
this.PartDataFromApi.partsOrQuestions[key];
if (
currentGlassSelection.glassName === this.glassName &&
currentGlassSelection.glassLocation === this.glassLocation
) {
if (currentGlassSelection.parts.length === 1) {
this.selectedPart = {
[currentGlassSelection.glassLocation]: [
currentGlassSelection.parts[0].partNumber,
],
};
if (
currentGlassSelection.glassName === this.glassName &&
currentGlassSelection.glassLocation === this.glassLocation
) {
if (currentGlassSelection.parts.length === 1) {
this.selectedPart = {
[currentGlassSelection.glassLocation]: [
currentGlassSelection.parts[0].partNumber,
],
};
}
}
}
});
);
},
// Loads the preselected values from the store.
@ -227,4 +237,9 @@ export default {
font-size: 0.875rem;
}
}
.color-question-text {
color: $black;
font-weight: $font-weight-bold;
}
</style>

View file

@ -1,5 +1,11 @@
<template>
<div class="page-container-grouped-styles">
<Form
@submit="onSubmit"
@invalid-submit="onInvalidSubmit"
ref="theForm"
v-slot="{ meta }"
>
<div class="page-container-grouped-styles vehicle-parts">
<funnelHeader ref="funnelHeader" cmsWidgetName="FunnelHeaderWidget" />
<vehicleBanner ref="vehicleBanner" cmsWidgetName="VehicleBannerWidget" :displayGenericVehicleImage="false" />
<funnelSubHeader ref="funnelSubHeader" cmsWidgetName="FunnelSubHeaderWidget" />
@ -8,10 +14,10 @@
<div class="row">
<div class="col">
<alert
class="rounded border-0 shadow-sm"
alertClass="alert-warning"
cmsWidgetName="AlertWidget"
:isDismissible="false"
class="rounded border-0 shadow-sm"
alertClass="alert-warning"
cmsWidgetName="AlertWidget"
:isDismissible="false"
/>
</div>
</div>
@ -24,16 +30,24 @@
</div>
<glassPartQuestion
:ref="`${RefPrefix}-${item.glassLocation}-${item.glassName}`"
v-model="glassParts[item.glassLocation + '-' + item.glassName]"
:glassLocation="item.glassLocation"
:glassName="item.glassName"
:colorAnswers="item.colorAnswers"
:ref="`${RefPrefix}-${item.glassLocation}-${item.glassName}`"
v-model="glassParts[item.glassLocation + '-' + item.glassName]"
:glassLocation="item.glassLocation"
:glassName="item.glassName"
:colorAnswers="item.colorAnswers"
validationRules="replace-options-required"
/>
</div>
<funnelFooter cmsWidgetName="FunnelFooterWidget" ref="funnelFooter" @back-clicked="backButtonAction" @ForwardClicked="forwardButtonAction" />
<funnelFooter
cmsWidgetName="FunnelFooterWidget"
ref="funnelFooter"
:isForwardActionDisabled="!meta.valid"
@back-clicked="backButtonAction"
@ForwardClicked="forwardButtonAction"
/>
</div>
</div>
</div>
</Form>
</template>
<script>
@ -50,170 +64,177 @@ import { settleAllPromises } from "@/helpers/layout-helper";
import { fmgPageValues } from "@/router/router-constants/fmgPage-values";
import { storeMutations } from "@/constants/store-mutations";
import store from "@/store";
import { Form, defineRule } from "vee-validate";
import { required } from "@/helpers/validation-rules";
import { errorMessages } from "@/constants/error-messages";
// DEFINE VALIDATION RULES
defineRule("replace-options-required", required(errorMessages.OPTION_REQUIRED));
export default {
name: "vehicle-parts",
async beforeRouteEnter(to, from, next) {
// Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
// Settle promises and get results
const promiseResultMap = [{
resultKey: "cmsContent",
promise: cmsContentPromise,
}, ];
const resultMap = await settleAllPromises(promiseResultMap);
// Call the "next" function to complete the transition to this page.
next((vm) => {
vm.setCmsContent(resultMap.cmsContent);
name: "vehicle-parts",
async beforeRouteEnter(to, from, next) {
// Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
// Settle promises and get results
const promiseResultMap = [{
resultKey: "cmsContent",
promise: cmsContentPromise,
}, ];
const resultMap = await settleAllPromises(promiseResultMap);
// Call the "next" function to complete the transition to this page.
next((vm) => {
vm.setCmsContent(resultMap.cmsContent);
// Glass Part Question dynamic component
Object.keys(vm.$refs)
.filter((r) => r.includes(vm.RefPrefix) && vm.$refs[r][0] !== undefined)
.forEach((c) =>
vm.$refs[c][0].initializeComponent({
ColorQuestionWidget: resultMap.cmsContent.ColorQuestionWidget,
FeatureQuestionWidget: resultMap.cmsContent.FeatureQuestionWidget,
})
);
});
},
data() {
return {
glassParts: {},
alertWidgetData: Object,
};
},
components: {
glassPartQuestion,
funnelHeader,
vehicleBanner,
funnelSubHeader,
funnelFooter,
alert,
},
computed: {
PartsForQuestions() {
const partsData = this.PartsFromApi;
// Glass Part Question dynamic component
Object.keys(vm.$refs)
.filter((r) => r.includes(vm.RefPrefix) && vm.$refs[r][0] !== undefined)
.forEach((c) =>
vm.$refs[c][0].initializeComponent({
ColorQuestionWidget: resultMap.cmsContent.ColorQuestionWidget,
FeatureQuestionWidget: resultMap.cmsContent.FeatureQuestionWidget,
})
);
});
},
data() {
return {
glassParts: {},
alertWidgetData: Object,
};
},
components: {
Form,
glassPartQuestion,
funnelHeader,
vehicleBanner,
funnelSubHeader,
funnelFooter,
alert,
},
computed: {
PartsForQuestions() {
const partsData = this.PartsFromApi;
// Map API result data, to vehicle-parts data structure
const mappedData = partsData.partsOrQuestions.map((g) => {
return {
glassName: g.glassName,
glassLocation: g.glassLocation,
colorAnswers: g.parts.reduce((arr, p) => {
arr.push({
ColorAnswerText: p.color,
FeatureAnswers: [
{
FeatureAnswerText:
p.description === "" ? p.color : p.description,
PartNumber: p.partNumber,
},
],
});
return arr;
}, []),
};
});
return mappedData;
},
PartsFromApi() {
return store.getters.pageData(fmgPageValues.VEHICLE_PARTS);
},
RefPrefix() {
return "partQuestion";
},
},
methods: {
arePagePrerequisitesValid() {
// Check if isRepair is populated and if the pageData we need is here (Parts data)
if (
(store.getters.damage.isRepair != null) &&
Object.keys(store.getters.pageData(fmgPageValues.VEHICLE_PARTS))
.length !== 0
) {
return true;
}
return false;
},
backButtonAction() {
this.$router.navigate(this.navigationScenarios.CLICKED_BACK, this.$route);
},
forwardButtonAction() {
const selectedGlassPartNumbers = [];
const matchedParts = [];
// Compile all selected parts from the page.
for (let [key, value] of Object.entries(this.glassParts)) {
for (let [glassKey, glassValue] of Object.entries(value)) {
selectedGlassPartNumbers.push(glassValue[0]);
}
}
// Match them to the parts from the API.
for (let [key, value] of Object.entries(
this.PartsFromApi.partsOrQuestions
)) {
for (let [partKey, partValue] of Object.entries(value.parts)) {
const currentPart =
this.PartsFromApi.partsOrQuestions[key].parts[partKey];
const isMatched = selectedGlassPartNumbers.some(
(p) => p === currentPart.partNumber
);
if (isMatched) {
matchedParts.push(currentPart);
}
}
}
// If no parts could be matched, throw an error.
if (matchedParts.length === 0) {
throw new Error("Could not match any parts to the selected parts");
}
// Save parts to the store.
store.commit(storeMutations.UPDATE_PARTS, matchedParts);
// Navigate to the next page.
this.$router.navigateAfterSave(
this.navigationScenarios.SELECTED_PARTS,
this.$route
);
},
resetDependentState() {
// Nothing additional to reset here: The page save is already fully resetting all the line-items on the order
},
LoadInitialPartsData() {
const partsData = this.PartsFromApi;
const alreadyPopulatedPartsData =
this.$store.getters.lineItems.glassParts === null
? {}
: this.$store.getters.lineItems.glassParts;
partsData.partsOrQuestions.map((g) => {
// If the part is already populated, use the value from the store and populate the v-model.
Object.keys(alreadyPopulatedPartsData).forEach((key) => {
const partNumber = alreadyPopulatedPartsData[key].partNumber;
g.parts.forEach((p) => {
if (p.partNumber === partNumber) {
this.glassParts[g.glassLocation + "-" + g.glassName] = {
[g.glassLocation]: [partNumber],
};
}
// Map API result data, to vehicle-parts data structure
const mappedData = partsData.partsOrQuestions.map((g) => {
return {
glassName: g.glassName,
glassLocation: g.glassLocation,
colorAnswers: g.parts.reduce((arr, p) => {
arr.push({
ColorAnswerText: p.color,
FeatureAnswers: [
{
FeatureAnswerText:
p.description === "" ? p.color : p.description,
PartNumber: p.partNumber,
},
],
});
return arr;
}, []),
};
});
return mappedData;
},
PartsFromApi() {
return store.getters.pageData(fmgPageValues.VEHICLE_PARTS);
},
RefPrefix() {
return "partQuestion";
},
},
methods: {
arePagePrerequisitesValid() {
// Check if isRepair is populated and if the pageData we need is here (Parts data)
if (
(store.getters.damage.isRepair != null) &&
Object.keys(store.getters.pageData(fmgPageValues.VEHICLE_PARTS))
.length !== 0
) {
return true;
}
return false;
},
backButtonAction() {
this.$router.navigate(this.navigationScenarios.CLICKED_BACK, this.$route);
},
forwardButtonAction() {
const selectedGlassPartNumbers = [];
const matchedParts = [];
// Compile all selected parts from the page.
for (let [key, value] of Object.entries(this.glassParts)) {
for (let [glassKey, glassValue] of Object.entries(value)) {
selectedGlassPartNumbers.push(glassValue[0]);
}
}
// Match them to the parts from the API.
for (let [key, value] of Object.entries(
this.PartsFromApi.partsOrQuestions
)) {
for (let [partKey, partValue] of Object.entries(value.parts)) {
const currentPart =
this.PartsFromApi.partsOrQuestions[key].parts[partKey];
const isMatched = selectedGlassPartNumbers.some(
(p) => p === currentPart.partNumber
);
if (isMatched) {
matchedParts.push(currentPart);
}
}
}
// If no parts could be matched, throw an error.
if (matchedParts.length === 0) {
throw new Error("Could not match any parts to the selected parts");
}
// Save parts to the store.
store.commit(storeMutations.UPDATE_GLASS_PARTS, matchedParts);
// Navigate to the next page.
this.$router.navigateAfterSave(
this.navigationScenarios.SELECTED_PARTS,
this.$route
);
},
resetDependentState() {
// Nothing additional to reset here: The page save is already fully resetting all the line-items on the order
},
LoadInitialPartsData() {
const partsData = this.PartsFromApi;
const alreadyPopulatedPartsData =
this.$store.getters.lineItems.glassParts === null
? {}
: this.$store.getters.lineItems.glassParts;
partsData.partsOrQuestions.map((g) => {
// If the part is already populated, use the value from the store and populate the v-model.
Object.keys(alreadyPopulatedPartsData).forEach((key) => {
const partNumber = alreadyPopulatedPartsData[key].partNumber;
g.parts.forEach((p) => {
if (p.partNumber === partNumber) {
this.glassParts[g.glassLocation + "-" + g.glassName] = {
[g.glassLocation]: [partNumber],
};
}
});
});
},
},
mounted() {
this.LoadInitialPartsData();
});
},
},
mounted() {
this.LoadInitialPartsData();
},
};
</script>

View file

@ -1,37 +1,38 @@
import { shallowMount } from "@vue/test-utils";
import { shallowMount } from "@vue/test-utils";
import vinLookup from "./vin-lookup.vue";
import { getMountOptions } from "@/helpers/unit-test-helper.js";
import { navigationScenarios } from "@/router/router-constants/navigation-scenarios.js";
import store from "@/store";
jest.mock("@/store", () => ({
commit: jest.fn(),
dispatch: jest.fn(),
getters: {
vehicle: {
year: 2019,
carId: 'initial carId'
},
order: {
serviceLocation: {
zipCode: "45253"
commit: jest.fn(),
dispatch: jest.fn(),
getters: {
vehicle: {
year: 2019,
carId: 'initial carId'
},
customer: {
emailAddress: "builddigitaltest@safelite.com"
order: {
serviceLocation: {
zipCode: "45253"
},
customer: {
emailAddress: "builddigitaltest@safelite.com"
}
},
payment: {
insuranceCoverage: {
isVerified: true
}
},
damage: {
glassToReplace: "windshield"
}
},
payment: {
insuranceCoverage: {
isVerified: true
}
},
damage: {
glassToReplace: "windshield"
}
},
}));
import { getDamageString, getIsWindshieldOnly,isGlassAvailableForCarId } from "@/helpers/damage-helper";
import { getDamageString, getIsWindshieldOnly, isGlassAvailableForCarId } from "@/helpers/damage-helper";
jest.mock("@/helpers/damage-helper", () => ({
isGlassAvailableForCarId: jest.fn(() => {
@ -45,21 +46,21 @@ jest.mock("@/helpers/damage-helper", () => ({
describe("vin-lookup.vue", () => {
it("Should update the funnel-footer forward button when VIN is changed", (done) => {
//Arrange
const { wrapper } = setupMocks({ });
const { wrapper } = setupMocks({});
//Act
wrapper.setData({vin: "newValue"});
wrapper.setData({ vin: "newValue" });
//Assert
wrapper.vm.$nextTick(() => {
expect(wrapper.vm.$refs.funnelFooter.updateButtonText).toBeCalled();
done();
});
});
it("Should call navigateForward() if the store carId matches the vin response carId and forward button is clicked", async () => {
// Arrange
const { wrapper } = setupMocks({ });
wrapper.vm.navigateForward = jest.fn();
const { wrapper } = setupMocks({});
wrapper.vm.navigateForward = jest.fn();
// Act
await wrapper.vm.forwardButtonAction();
@ -68,19 +69,48 @@ describe("vin-lookup.vue", () => {
expect(wrapper.vm.navigateForward).toHaveBeenCalled();
});
it("Should not call navigateForward() if the store carId does not match the vin response carId and forward button is clicked", async () => {
it("Should do a VIN lookup if the user has clicked on the VIN field and entered a new VIN or changed a previously matched VIN.", async () => {
// Arrange
const { wrapper } = setupMocks({ });
const { wrapper } = setupMocks({});
wrapper.vm.vinTouched = true;
wrapper.vm.vin = "foo";
wrapper.vm.initialVin = "!foo";
wrapper.vm.navigateForward = jest.fn();
const vehicleLookupApiResponse = {
data: {
carId: 'new carId' // does not match the store value
}
};
};
const vinPromise = Promise.resolve(vehicleLookupApiResponse);
wrapper.vm.lookupVehicle = jest.fn().mockImplementation(() => vinPromise);
wrapper.vm.navigateForward = jest.fn();
// Act
await wrapper.vm.forwardButtonAction();
//Assert
expect(wrapper.vm.lookupVehicle).toHaveBeenCalled();
});
it("Should not call navigateForward() if the store carId does not match the vin response carId and forward button is clicked", async () => {
// Arrange
const { wrapper } = setupMocks({});
// New lookup
wrapper.vm.vinTouched = true;
wrapper.vm.vin = "";
wrapper.vm.initialVin = "foo";
const vehicleLookupApiResponse = {
data: {
carId: 'new carId' // does not match the store value
}
};
const vinPromise = Promise.resolve(vehicleLookupApiResponse);
wrapper.vm.lookupVehicle = jest.fn().mockImplementation(() => vinPromise);
wrapper.vm.navigateForward = jest.fn();
// Act
await wrapper.vm.forwardButtonAction();
@ -91,16 +121,16 @@ describe("vin-lookup.vue", () => {
it("Should call navigateForward() if the store carId does not match the vin response carId but does match previously enterted carId and forward button is clicked", async () => {
// Arrange
const { wrapper } = setupMocks({ });
const { wrapper } = setupMocks({});
const vehicleLookupApiResponse = {
data: {
carId: 'new carId' // does not match the store value
}
};
};
const vinPromise = Promise.resolve(vehicleLookupApiResponse);
wrapper.vm.lookupVehicle = jest.fn().mockImplementation(() => vinPromise);
wrapper.vm.navigateForward = jest.fn();
wrapper.vm.navigateForward = jest.fn();
wrapper.vm.previouslyEnteredCarId = 'new carId';
@ -113,7 +143,7 @@ describe("vin-lookup.vue", () => {
it("Should not call navigateForward() if zip service returns a non-serviceable flag", async () => {
// Arrange
const { wrapper } = setupMocks({ });
const { wrapper } = setupMocks({});
const zipValidationApiResponse = {
data: {
isServiceable: false
@ -121,9 +151,9 @@ describe("vin-lookup.vue", () => {
};
const zipPromise = Promise.resolve(zipValidationApiResponse);
wrapper.vm.validateZip = jest.fn().mockImplementation(() => zipPromise);
wrapper.vm.navigateForward = jest.fn();
wrapper.vm.navigateForward = jest.fn();
wrapper.vm.previouslyEnteredCarId = 'new carId';
@ -136,16 +166,23 @@ describe("vin-lookup.vue", () => {
it("Should not call navigateForward() when forward button is clicked but lookupVehicle errors out.", async () => {
// Arrange
const { wrapper } = setupMocks({ });
const { wrapper } = setupMocks({});
wrapper.vm.vinTouched = true;
wrapper.vm.vin = "foo";
wrapper.vm.initialVin = "!foo";
const vehicleLookupApiResponse = {
data: {
status: {
carId: 'new carId' // does not match the store value
}
};
};
const vinPromise = Promise.reject(vehicleLookupApiResponse);
wrapper.vm.lookupVehicle = jest.fn().mockImplementation(() => vinPromise);
wrapper.vm.navigateForward = jest.fn();
const response = {
status: 404
};
wrapper.vm.lookupVehicle = jest.fn().mockImplementation((response) => vinPromise);
wrapper.vm.navigateForward = jest.fn();
wrapper.vm.previouslyEnteredCarId = 'new carId';
@ -155,19 +192,75 @@ describe("vin-lookup.vue", () => {
//Assert
expect(wrapper.vm.navigateForward).not.toHaveBeenCalled();
});
describe("navigateForward", () => {
test("carId is different from returned vehicle and selected glass isn't available => continue with different glass", async () => {
// Arrange
const { wrapper } = setupMocks({
customMountOptions: {
router: {
navigateAfterSave: jest.fn()
}
}
});
wrapper.vm.navigateForwardWithSingleCarMatch = jest.fn();
wrapper.setData({
isCarIdDifferent: true,
isSelectedGlassAvailableForVehicle: false
});
// Act
await wrapper.vm.navigateForward();
//Assert
expect(wrapper.vm.$router.navigateAfterSave).toBeCalledTimes(1);
expect(wrapper.vm.$router.navigateAfterSave).toHaveBeenCalledWith(navigationScenarios.SELECTED_VIN_HAS_MISMATCHED_GLASS, wrapper.vm.$route, expect.anything(), expect.anything(), expect.anything());
})
test("carId matches => navigateForwardWithSingleCarMatch", async () => {
// Arrange
const { wrapper } = setupMocks({});
wrapper.vm.navigateForwardWithSingleCarMatch = jest.fn();
wrapper.setData({
isCarIdDifferent: false,
});
// Act
await wrapper.vm.navigateForward();
//Assert
expect(wrapper.vm.navigateForwardWithSingleCarMatch).toBeCalledTimes(1);
})
test("selected glass is available for returned vehicle => navigateForwardWithSingleCarMatch", async () => {
// Arrange
const { wrapper } = setupMocks({});
wrapper.vm.navigateForwardWithSingleCarMatch = jest.fn();
wrapper.setData({
isSelectedGlassAvailableForVehicle: true,
});
// Act
await wrapper.vm.navigateForward();
//Assert
expect(wrapper.vm.navigateForwardWithSingleCarMatch).toBeCalledTimes(1);
})
})
});
function setupMocks({ customMountOptions }) {
const mountOptions = getMountOptions({});
const mountOptions = getMountOptions({
...customMountOptions
});
const finalMountOptions = Object.assign(mountOptions, customMountOptions);
// Modify/augment default mount options
finalMountOptions.global.mocks["$store"] = store;
finalMountOptions.global.mixins = [mockMixin];
finalMountOptions['attachTo'] = document.body; // append wrapper to document.body to test DOM methods
mountOptions.global.mocks["$store"] = store;
mountOptions.global.mixins = [mockMixin];
mountOptions['attachTo'] = document.body; // append wrapper to document.body to test DOM methods
const wrapper = shallowMount(vinLookup, finalMountOptions);
const wrapper = shallowMount(vinLookup, mountOptions);
mockOutPromises(wrapper);
mockOutStubFunctions(wrapper);
return { wrapper };
@ -183,8 +276,8 @@ function mockOutPromises(wrapper) {
data: {
carId: 'initial carId'
}
};
};
const zipPromise = Promise.resolve(zipValidationApiResponse);
const vinPromise = Promise.resolve(vehicleLookupApiResponse);
@ -197,8 +290,8 @@ function mockOutStubFunctions(wrapper) {
wrapper.vm.$refs.funnelFooter.removeLoader = jest.fn();
}
const mockMixin = {
const mockMixin = {
methods: {
getCmsContent: jest.fn(() => "placeholder CMS content"),
getCmsContent: jest.fn(() => "placeholder CMS content"),
}
}
}

View file

@ -8,7 +8,7 @@
<div class="page-container-grouped-styles">
<loadingModal ref="loadingModal"/>
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
<vehicleBanner
<vehicleBanner
cmsWidgetName="VehicleBannerWidget"
:displayGenericVehicleImage="false"
/>
@ -25,6 +25,9 @@
validationRules="vin-required|vin-format"
:isDisabled="isVinFieldReadOnly"
maxLength="17"
:mask="vinMask"
@focus="setVinTouched"
@maska="rawVinValue = $event.target.dataset.maskRawValue"
/>
</div>
</div>
@ -55,7 +58,6 @@
isRequired
disableAutoFill
validationRules="email-address-required|email-address-format"
semiAggressiveValidation
/>
</div>
</div>
@ -132,7 +134,6 @@ import loadingModal from '@/common-components/loading-modal/loading-modal.vue';
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import { settleAllPromises } from "@/helpers/layout-helper";
import store from "@/store";
import baseMixin from "@/mixins/base-mixin.js";
import { storeActions } from "@/constants/store-actions";
import { storeMutations } from "@/constants/store-mutations";
import { errorMessages } from "@/constants/error-messages";
@ -141,6 +142,8 @@ import { required, regex } from "@/helpers/validation-rules";
import { Form, defineRule } from "vee-validate";
import { navigateAfterSaveToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper";
import { getFunnelCookie } from "@/helpers/heritage-integration/cookie-helper";
import vinPagesMixin from "@/mixins/vin-pages-mixin";
import { StatusCodes } from 'http-status-codes';
// DEFINE VALIDATION RULES
defineRule("zip-required", required(errorMessages.SERVICE_ZIP_REQUIRED));
@ -152,6 +155,7 @@ defineRule("vin-format", regex(/^[a-hA-Hj-nJ-NpPr-zR-Z0-9]{17}$/, errorMessages.
export default {
name: "vin-lookup",
mixins: [vinPagesMixin],
async beforeRouteEnter(to, from, next) {
// Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
@ -183,10 +187,16 @@ export default {
previouslyEnteredCarId: '',
invalidZip: '',
vinPopulatedOnPageLoad: this.getVinFromStore()?.length > 0,
initialVin: this.getVinFromStore(),
vinTouched: false,
rawVinValue: "",
};
},
mounted() {
this.attachCustomEvents();
if (this.vinPopulatedOnPageLoad) {
this.setupVinMask();
}
},
watch: {
vin() {
@ -194,13 +204,17 @@ export default {
this.$refs.funnelFooter.updateButtonText(
this.getCmsContent("FunnelFooterWidget", "ForwardButtonText")
);
}
},
zip() {
this.noServiceZip = false;
},
},
computed: {
perfectMatchNewVinAlert() {
const isVinPerfectMatch = this.vinPopulatedOnPageLoad && this.vin === this.getVinFromStore();
this.updateIsCarIdDifferent(isVinPerfectMatch);
return isVinPerfectMatch;
const vinToCheck = this.vinTouched ? this.rawVinValue : this.initialVin;
const isVinPerfectMatch = this.vinPopulatedOnPageLoad && vinToCheck === this.getVinFromStore();
this.updateIsCarIdDifferent(isVinPerfectMatch);
return isVinPerfectMatch;
},
MatchedDifferentVehicleAlertHeader(){
const text = this.getCmsContent("MatchedDifferentVehicle",
@ -238,10 +252,22 @@ export default {
getIsWindshieldOnly())
},
isVinFieldReadOnly(){
return this.$store.getters.payment.insuranceCoverage.isVerified || getFunnelCookie().HasDelayedClaimRegistration;
return this.$store.getters.payment.insuranceCoverage.isVerified || getFunnelCookie().HasDelayedClaimRegistration;
},
},
methods: {
setVinTouched() {
if (!this.vinTouched) {
this.vinMask = "XXXXXXXXXXXXXXXXX";
this.vin = this.initialVin;
}
this.vinTouched = true;
},
setupVinMask() {
const lastSixChars = this.initialVin.substring(11, this.initialVin.length);
this.vinMask = `!X!X!X!X!X!X!X!X!X!X!X${lastSixChars}`;
},
arePagePrerequisitesValid() {
return store.getters.vehicle.carId !== null;
},
@ -285,25 +311,59 @@ export default {
}
},
async forwardButtonAction() {
let zipValidationResponse;
let vehicleLookupResponse;
const zipValidation = this.validateZip(this.zip);
const vehicleLookup = this.lookupVehicle(this.vin);
const zipValidationResponse = await zipValidation;
const vehicleLookupResponse = await vehicleLookup.catch(() => {
this.vinNotFound = true;
this.$refs.funnelFooter.removeLoader();
this.noServiceZip = false;
return false;
});
if (this.vinTouched && this.vin != this.initialVin) {
// If the user has clicked on the VIN field, either they are doing a new VIN lookup or changing the VIN previously matched.
// Therefore we need to do a Vehicle Lookup
const vinToLookup = this.vinTouched ? this.vin : this.initialVin;
const vehicleLookup = this.lookupVehicle(vinToLookup);
zipValidationResponse = await zipValidation;
vehicleLookupResponse = await vehicleLookup.catch((response) => {
if (response.status == StatusCodes.NOT_FOUND) {
this.vinNotFound = true;
this.$refs.funnelFooter.removeLoader();
return false;
}
});
// Check if Service Zip entered is servicable, if not display an alert
if (!zipValidationResponse.data.isServiceable) {
this.customAlertData.zip = this.zip;
this.noServiceZip = true;
this.invalidZip = this.zip;
}
if (!vehicleLookupResponse || !zipValidationResponse.data.isServiceable) {
this.$refs.funnelFooter.removeLoader();
return;
}
} else {
const zipValidationResponse = await zipValidation;
// Check if Service Zip entered is serviceable, if not display an alert
if (!zipValidationResponse.data.isServiceable) {
this.customAlertData.zip = this.zip;
this.noServiceZip = true;
this.invalidZip = this.zip;
this.$refs.funnelFooter.removeLoader();
return;
} else {
this.navigateForward();
return;
}
}
if (!vehicleLookupResponse) {
return;
}
if (!zipValidationResponse.data.isServiceable) {
this.customAlertData.zip = this.zip;
this.$refs.funnelFooter.removeLoader();
this.noServiceZip = true;
this.invalidZip = this.zip;
return;
}
this.isCarIdDifferent = vehicleLookupResponse.data.carId !== store.getters.vehicle.carId;
if (this.isCarIdDifferent && (vehicleLookupResponse.data.carId !== this.previouslyEnteredCarId)) {
@ -317,26 +377,27 @@ export default {
this.isCarIdDifferent = true;
return;
}
this.updateStore(vehicleLookupResponse.data, zipValidationResponse.data);
this.navigateForward();
},
navigateForward(){
if(this.isCarIdDifferent && !this.isSelectedGlassAvailableForVehicle){
this.$router.navigateAfterSave(this.navigationScenarios.CLICKED_FORWARD, this.$route, {}, { displayVehicleChangeAlert: true }, {});
if (this.isCarIdDifferent && !this.isSelectedGlassAvailableForVehicle) {
this.$router.navigateAfterSave(this.navigationScenarios.SELECTED_VIN_HAS_MISMATCHED_GLASS, this.$route, {}, { displayVehicleChangeAlert: true }, {});
return;
} else {
this.$refs.loadingModal.showModal();
navigateAfterSaveToHeritageFunnel(this.$route);
this.navigateForwardWithSingleCarMatch();
return;
}
},
validateZip(zip) {
return baseMixin.methods.dispatchStoreAction(storeActions.VALIDATE_ZIP, {
return this.dispatchStoreAction(storeActions.VALIDATE_ZIP, {
zip,
});
},
lookupVehicle(vin) {
return baseMixin.methods.dispatchStoreAction(
return this.dispatchStoreAction(
storeActions.LOOKUP_VEHICLE_BY_VIN,
{ vin }
);

View file

@ -0,0 +1,28 @@
import store from "@/store";
import { storeActions } from "@/constants/store-actions.js";
import { storeMutations } from "@/constants/store-mutations.js";
import { navigateAfterSaveToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper";
export default {
methods: {
async navigateForwardWithSingleCarMatch() {
const result = await this.dispatchStoreAction(storeActions.GET_PARTS_OR_QUESTIONS);
const partsOrQuestions = result.data.partsOrQuestions;
const hasPartsQuestions = partsOrQuestions.some(pq => pq.partQuestions?.length > 0);
const hasGlassLocationWithMultipleParts = partsOrQuestions.some(pq => pq.parts?.length > 1);
if (hasPartsQuestions) {
this.$router.navigateAfterSave(this.navigationScenarios.SELECTED_VIN_WITH_PART_QUESTIONS, this.$route, {}, {}, result.data);
}
else if (hasGlassLocationWithMultipleParts) {
this.$router.navigateAfterSave(this.navigationScenarios.SELECTED_VIN_WITH_MULTIPLE_PARTS, this.$route, {}, {}, result.data);
}
else {
store.commit(storeMutations.UPDATE_GLASS_PARTS, result.data);
this.$refs.loadingModal.showModal();
navigateAfterSaveToHeritageFunnel(this.$route);
}
}
}
}

View file

@ -0,0 +1,921 @@
import vinPagesMixin from "@/mixins/vin-pages-mixin";
import { shallowMount } from "@vue/test-utils";
import { setupMocksForJsFiles, getMountOptions } from "@/helpers/unit-test-helper.js";
import { storeActions } from "@/constants/store-actions";
import { storeMutations } from "@/constants/store-mutations";
import { navigationScenarios } from "@/router/router-constants/navigation-scenarios";
import store from "@/store";
import loadingModal from '@/common-components/loading-modal/loading-modal.vue';
import { navigateAfterSaveToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper";
jest.mock("@/helpers/heritage-integration/navigation-helper", () => ({
navigateAfterSaveToHeritageFunnel: jest.fn()
}));
describe("vin-pages-mixin", () => {
afterEach(() => {
jest.clearAllMocks();
})
describe("navigateForwardWithSingleCarMatch", () => {
describe("should go to parts-questions", () => {
test("single glass location has part question => go to parts-questions", async () => {
// Arrange
const partsOrQuestions = [
{
"glassName": "Single",
"glassLocation": "Windshield",
"parts": null,
"partQuestions": [
{
"questionSequence": 1,
"questionText": "Is there a line running across the bottom, driver's side then up the center of the Windshield?",
"answers": [
{
"answerText": "Yes",
"nextQuestionSequence": null,
"answerResult": "DW01144"
},
{
"answerText": "No",
"nextQuestionSequence": null,
"answerResult": "DW01143"
}
]
}
]
}
];
const { wrapper } = setupMocks({
partsOrQuestions: partsOrQuestions
});
// Act
await wrapper.vm.navigateForwardWithSingleCarMatch();
// Assert
expect(wrapper.vm.$router.navigateAfterSave).toHaveBeenCalledTimes(1);
expect(wrapper.vm.$router.navigateAfterSave).toHaveBeenCalledWith(navigationScenarios.SELECTED_VIN_WITH_PART_QUESTIONS, wrapper.vm.$route, {}, {}, { partsOrQuestions });
});
test("multiple glass locations have part questions => go to parts-questions", async () => {
// Arrange
const partsOrQuestions = [
{
"glassName": "Single",
"glassLocation": "Windshield",
"parts": null,
"partQuestions": [
{
"questionSequence": 1,
"questionText": "Is there a line running across the bottom, driver's side then up the center of the Windshield?",
"answers": [
{
"answerText": "Yes",
"nextQuestionSequence": null,
"answerResult": "DW01144"
},
{
"answerText": "No",
"nextQuestionSequence": null,
"answerResult": "DW01143"
}
]
}
]
},
{
"glassName": "Front",
"glassLocation": "Driver",
"parts": [
{
"partNumber": "DD08158GTYN",
"description": "driver side, front",
"color": "Green Tint",
"requiresRecalibration": false,
"requiresCapabilityQuestions": false,
"childParts": null
}
],
"partQuestions": null
},
{
"glassName": "Quarter",
"glassLocation": "Driver",
"parts": [
{
"partNumber": "DQ08162GTYN",
"description": "driver side, 1 hole",
"color": "Green Tint",
"requiresRecalibration": false,
"requiresCapabilityQuestions": false,
"childParts": null
}
],
"partQuestions": null
},
{
"glassName": "SideDoor",
"glassLocation": "Driver",
"parts": null,
"partQuestions": [
{
"questionSequence": 2,
"questionText": "Is this a super awesome question?",
"answers": [
{
"answerText": "Yes",
"nextQuestionSequence": null,
"answerResult": "DW01144000"
},
{
"answerText": "Super yes",
"nextQuestionSequence": null,
"answerResult": "DW01143001"
}
]
}
]
},
{
"glassName": "Stationary",
"glassLocation": "Rear",
"parts": [
{
"partNumber": "DB08165GTNN",
"description": "heated glass, stationary",
"color": "Green Tint",
"requiresRecalibration": false,
"requiresCapabilityQuestions": false,
"childParts": null
}
],
"partQuestions": null
}
];
const { wrapper } = setupMocks({
partsOrQuestions: partsOrQuestions
});
// Act
await wrapper.vm.navigateForwardWithSingleCarMatch();
// Assert
expect(wrapper.vm.$router.navigateAfterSave).toHaveBeenCalledTimes(1);
expect(wrapper.vm.$router.navigateAfterSave).toHaveBeenCalledWith(navigationScenarios.SELECTED_VIN_WITH_PART_QUESTIONS, wrapper.vm.$route, {}, {}, { partsOrQuestions });
});
test("multiple glass locations selected, one has part question => go to parts-questions", async () => {
// Arrange
const partsOrQuestions = [
{
"glassName": "Single",
"glassLocation": "Windshield",
"parts": null,
"partQuestions": [
{
"questionSequence": 1,
"questionText": "Is there a line running across the bottom, driver's side then up the center of the Windshield?",
"answers": [
{
"answerText": "Yes",
"nextQuestionSequence": null,
"answerResult": "DW01144"
},
{
"answerText": "No",
"nextQuestionSequence": null,
"answerResult": "DW01143"
}
]
}
]
},
{
"glassName": "Front",
"glassLocation": "Driver",
"parts": [
{
"partNumber": "DD08158GTYN",
"description": "driver side, front",
"color": "Green Tint",
"requiresRecalibration": false,
"requiresCapabilityQuestions": false,
"childParts": null
}
],
"partQuestions": null
},
{
"glassName": "Quarter",
"glassLocation": "Driver",
"parts": [
{
"partNumber": "DQ08162GTYN",
"description": "driver side, 1 hole",
"color": "Green Tint",
"requiresRecalibration": false,
"requiresCapabilityQuestions": false,
"childParts": null
}
],
"partQuestions": null
},
{
"glassName": "SideDoor",
"glassLocation": "Driver",
"parts": [
{
"partNumber": "DD08160GTYN",
"description": "driver side, body side, 1 hole",
"color": "Green Tint",
"requiresRecalibration": false,
"requiresCapabilityQuestions": false,
"childParts": null
}
],
"partQuestions": null
},
{
"glassName": "Stationary",
"glassLocation": "Rear",
"parts": [
{
"partNumber": "DB08165GTNN",
"description": "heated glass, stationary",
"color": "Green Tint",
"requiresRecalibration": false,
"requiresCapabilityQuestions": false,
"childParts": null
}
],
"partQuestions": null
}
];
const { wrapper } = setupMocks({
partsOrQuestions: partsOrQuestions
});
// Act
await wrapper.vm.navigateForwardWithSingleCarMatch();
// Assert
expect(wrapper.vm.$router.navigateAfterSave).toHaveBeenCalledTimes(1);
expect(wrapper.vm.$router.navigateAfterSave).toHaveBeenCalledWith(navigationScenarios.SELECTED_VIN_WITH_PART_QUESTIONS, wrapper.vm.$route, {}, {}, { partsOrQuestions });
});
test("a selected glass location has part questions and multiple parts => go to parts-questions", async () => {
// Arrange
const partsOrQuestions = [
{
"glassName": "Single",
"glassLocation": "Windshield",
"parts": null,
"partQuestions": [
{
"questionSequence": 1,
"questionText": "Is there a line running across the bottom, driver's side then up the center of the Windshield?",
"answers": [
{
"answerText": "Yes",
"nextQuestionSequence": null,
"answerResult": "DW01144"
},
{
"answerText": "No",
"nextQuestionSequence": null,
"answerResult": "DW01143"
}
]
}
]
},
{
"glassName": "Front",
"glassLocation": "Driver",
"parts": [
{
"partNumber": "DD08158GTYN",
"description": "driver side, front",
"color": "Green Tint",
"requiresRecalibration": false,
"requiresCapabilityQuestions": false,
"childParts": null
}
],
"partQuestions": null
},
{
"glassName": "Quarter",
"glassLocation": "Driver",
"parts": [
{
"partNumber": "DQ08162GTYN",
"description": "driver side, 1 hole",
"color": "Green Tint",
"requiresRecalibration": false,
"requiresCapabilityQuestions": false,
"childParts": null
},
{
"partNumber": "DQ08162YPYN",
"description": "driver side, 1 hole",
"color": "Gray Tint Privacy",
"requiresRecalibration": false,
"requiresCapabilityQuestions": false,
"childParts": null
}
],
"partQuestions": null
},
{
"glassName": "SideDoor",
"glassLocation": "Driver",
"parts": [
{
"partNumber": "DD08160GTYN",
"description": "driver side, body side, 1 hole",
"color": "Green Tint",
"requiresRecalibration": false,
"requiresCapabilityQuestions": false,
"childParts": null
},
{
"partNumber": "DD08160YPYN",
"description": "driver side, body side, 1 hole",
"color": "Gray Tint Privacy",
"requiresRecalibration": false,
"requiresCapabilityQuestions": false,
"childParts": null
}
],
"partQuestions": null
},
{
"glassName": "Stationary",
"glassLocation": "Rear",
"parts": [
{
"partNumber": "DB08165GTNN",
"description": "heated glass, stationary",
"color": "Green Tint",
"requiresRecalibration": false,
"requiresCapabilityQuestions": false,
"childParts": null
},
{
"partNumber": "DB08165YPNN",
"description": "heated glass, stationary",
"color": "Gray Tint Privacy",
"requiresRecalibration": false,
"requiresCapabilityQuestions": false,
"childParts": null
},
{
"partNumber": "DB08166GTNN",
"description": "stationary",
"color": "Green Tint",
"requiresRecalibration": false,
"requiresCapabilityQuestions": false,
"childParts": null
},
{
"partNumber": "DB08167GTNN",
"description": "heated glass, movable, 8 hole",
"color": "Green Tint",
"requiresRecalibration": false,
"requiresCapabilityQuestions": false,
"childParts": null
},
{
"partNumber": "DB08167YPNN",
"description": "heated glass, movable, 8 hole",
"color": "Gray Tint Privacy",
"requiresRecalibration": false,
"requiresCapabilityQuestions": false,
"childParts": null
}
],
"partQuestions": null
}
];
const { wrapper } = setupMocks({
partsOrQuestions: partsOrQuestions
});
// Act
await wrapper.vm.navigateForwardWithSingleCarMatch();
// Assert
expect(wrapper.vm.$router.navigateAfterSave).toHaveBeenCalledTimes(1);
expect(wrapper.vm.$router.navigateAfterSave).toHaveBeenCalledWith(navigationScenarios.SELECTED_VIN_WITH_PART_QUESTIONS, wrapper.vm.$route, {}, {}, { partsOrQuestions });
});
});
describe("should go to vehicle-parts", () => {
test("single glass location has multiple parts => go to vehicle-parts", async () => {
// Arrange
const partsOrQuestions = [
{
"glassName": "Stationary",
"glassLocation": "Rear",
"parts": [
{
"partNumber": "FB25724GTYN",
"description": "heated glass, solar, antenna",
"color": "Green Tint",
"requiresRecalibration": false,
"requiresCapabilityQuestions": false,
"childParts": null
},
{
"partNumber": "FB25759GTYN",
"description": "heated glass, solar, antenna, w/diversity antenna",
"color": "Green Tint",
"requiresRecalibration": false,
"requiresCapabilityQuestions": false,
"childParts": null
}
],
"partQuestions": null
}
];
const { wrapper } = setupMocks({
partsOrQuestions: partsOrQuestions
});
// Act
await wrapper.vm.navigateForwardWithSingleCarMatch();
// Assert
expect(wrapper.vm.$router.navigateAfterSave).toHaveBeenCalledTimes(1);
expect(wrapper.vm.$router.navigateAfterSave).toHaveBeenCalledWith(navigationScenarios.SELECTED_VIN_WITH_MULTIPLE_PARTS, wrapper.vm.$route, {}, {}, { partsOrQuestions });
});
test("multiple glass locations selected, one of them has multiple parts => go to vehicle parts", async () => {
// Arrange
const partsOrQuestions = [
{
"glassName": "Single",
"glassLocation": "Windshield",
"parts": [
{
"partNumber": "FW03647GTNN",
"description": "solar, 3rd visor band",
"color": "Green Tint",
"requiresRecalibration": false,
"requiresCapabilityQuestions": false,
"childParts": [
{
"partNumber": "MWF03647",
"partType": "MOULDING",
"description": "Upper "
}
]
}
],
"partQuestions": null
},
{
"glassName": "Back",
"glassLocation": "Driver",
"parts": [
{
"partNumber": "FD25747GTYN",
"description": "solar, driver side, rear, ex models and above",
"color": "Green Tint",
"requiresRecalibration": false,
"requiresCapabilityQuestions": false,
"childParts": null
}
],
"partQuestions": null
},
{
"glassName": "Front",
"glassLocation": "Driver",
"parts": [
{
"partNumber": "FD25719GTYN",
"description": "solar, driver side, front, ex models and above",
"color": "Green Tint",
"requiresRecalibration": false,
"requiresCapabilityQuestions": false,
"childParts": null
}
],
"partQuestions": null
},
{
"glassName": "Vent",
"glassLocation": "Driver",
"parts": [
{
"partNumber": "FV25749GTNN",
"description": "solar, driver side, rear",
"color": "Green Tint",
"requiresRecalibration": false,
"requiresCapabilityQuestions": false,
"childParts": null
}
],
"partQuestions": null
},
{
"glassName": "Stationary",
"glassLocation": "Rear",
"parts": [
{
"partNumber": "FB25724GTYN",
"description": "heated glass, solar, antenna",
"color": "Green Tint",
"requiresRecalibration": false,
"requiresCapabilityQuestions": false,
"childParts": null
},
{
"partNumber": "FB25759GTYN",
"description": "heated glass, solar, antenna, w/diversity antenna",
"color": "Green Tint",
"requiresRecalibration": false,
"requiresCapabilityQuestions": false,
"childParts": null
}
],
"partQuestions": null
}
];
const { wrapper } = setupMocks({
partsOrQuestions: partsOrQuestions
});
// Act
await wrapper.vm.navigateForwardWithSingleCarMatch();
// Assert
expect(wrapper.vm.$router.navigateAfterSave).toHaveBeenCalledTimes(1);
expect(wrapper.vm.$router.navigateAfterSave).toHaveBeenCalledWith(navigationScenarios.SELECTED_VIN_WITH_MULTIPLE_PARTS, wrapper.vm.$route, {}, {}, { partsOrQuestions });
});
test("multiple glass locations selected, multiple have multiple parts => go to vehicle-parts", async () => {
// Arrange
const partsOrQuestions = [
{
"glassName": "Single",
"glassLocation": "Windshield",
"parts": [
{
"partNumber": "DW02101GTYN",
"description": "solar",
"color": "Green Tint",
"requiresRecalibration": false,
"requiresCapabilityQuestions": false,
"childParts": null
}
],
"partQuestions": null
},
{
"glassName": "Back",
"glassLocation": "Driver",
"parts": [
{
"partNumber": "DD12202GTYN",
"description": "solar, driver side, rear",
"color": "Green Tint",
"requiresRecalibration": false,
"requiresCapabilityQuestions": false,
"childParts": null
},
{
"partNumber": "DD12202YPYN",
"description": "solar, driver side, rear",
"color": "Gray Tint Privacy",
"requiresRecalibration": false,
"requiresCapabilityQuestions": false,
"childParts": null
}
],
"partQuestions": null
},
{
"glassName": "Front",
"glassLocation": "Driver",
"parts": [
{
"partNumber": "DD12198GTYN",
"description": "solar, driver side, front",
"color": "Green Tint",
"requiresRecalibration": false,
"requiresCapabilityQuestions": false,
"childParts": null
},
{
"partNumber": "DD12200GTYN",
"description": "solar, driver side, front, laminated, soundproofing",
"color": "Green Tint",
"requiresRecalibration": false,
"requiresCapabilityQuestions": false,
"childParts": null
}
],
"partQuestions": null
},
{
"glassName": "Quarter",
"glassLocation": "Driver",
"parts": [
{
"partNumber": "DQ12204GTYNOEM",
"description": "solar, driver side, encap",
"color": "Green Tint",
"requiresRecalibration": false,
"requiresCapabilityQuestions": false,
"childParts": null
},
{
"partNumber": "DQ12204YPYNOEM",
"description": "solar, driver side, encap",
"color": "Gray Tint Privacy",
"requiresRecalibration": false,
"requiresCapabilityQuestions": false,
"childParts": null
},
{
"partNumber": "DQ12205GTYNOEM",
"description": "solar, antenna, driver side, encap",
"color": "Green Tint",
"requiresRecalibration": false,
"requiresCapabilityQuestions": false,
"childParts": null
},
{
"partNumber": "DQ12205YPYNOEM",
"description": "solar, antenna, driver side, encap",
"color": "Gray Tint Privacy",
"requiresRecalibration": false,
"requiresCapabilityQuestions": false,
"childParts": null
},
{
"partNumber": "DQ12207GTYN",
"description": "solar, driver side, encap, chrome molding",
"color": "Green Tint",
"requiresRecalibration": false,
"requiresCapabilityQuestions": false,
"childParts": null
},
{
"partNumber": "DQ12207YPYNOEM",
"description": "solar, driver side, encap, chrome molding",
"color": "Gray Tint Privacy",
"requiresRecalibration": false,
"requiresCapabilityQuestions": false,
"childParts": null
},
{
"partNumber": "DQ12208GTYNOEM",
"description": "solar, antenna, driver side, encap, chrome molding",
"color": "Green Tint",
"requiresRecalibration": false,
"requiresCapabilityQuestions": false,
"childParts": null
},
{
"partNumber": "DQ12208YPYNOEM",
"description": "solar, antenna, driver side, encap, chrome molding",
"color": "Gray Tint Privacy",
"requiresRecalibration": false,
"requiresCapabilityQuestions": false,
"childParts": null
}
],
"partQuestions": null
},
{
"glassName": "Stationary",
"glassLocation": "Rear",
"parts": [
{
"partNumber": "DB12209GTYN",
"description": "heated glass, solar, 1 hole",
"color": "Green Tint",
"requiresRecalibration": false,
"requiresCapabilityQuestions": false,
"childParts": null
},
{
"partNumber": "DB12209YPYN",
"description": "heated glass, solar, 1 hole",
"color": "Gray Tint Privacy",
"requiresRecalibration": false,
"requiresCapabilityQuestions": false,
"childParts": null
}
],
"partQuestions": null
}
];
const { wrapper } = setupMocks({
partsOrQuestions: partsOrQuestions
});
// Act
await wrapper.vm.navigateForwardWithSingleCarMatch();
// Assert
expect(wrapper.vm.$router.navigateAfterSave).toHaveBeenCalledTimes(1);
expect(wrapper.vm.$router.navigateAfterSave).toHaveBeenCalledWith(navigationScenarios.SELECTED_VIN_WITH_MULTIPLE_PARTS, wrapper.vm.$route, {}, {}, { partsOrQuestions });
});
});
describe("should go to heritage funnel", () => {
test("single glass location selected, has no part questions and has one part => go to heritage funnel", async () => {
// Arrange
const partsOrQuestions = [
{
"glassName": "Single",
"glassLocation": "Windshield",
"parts": [
{
"partNumber": "FW04186GTYN",
"description": "solar, soundproofing, lane keep assist",
"color": "Green Tint",
"requiresRecalibration": true,
"requiresCapabilityQuestions": false,
"childParts": [
{
"partNumber": "GGG 3563 KIT",
"partType": "MOULDING",
"description": "Kit, Top & Sides "
}
]
}
],
"partQuestions": null
}
]
const { wrapper } = setupMocks({
partsOrQuestions: partsOrQuestions
});
// Act
await wrapper.vm.navigateForwardWithSingleCarMatch();
// Assert
expect(navigateAfterSaveToHeritageFunnel).toHaveBeenCalledTimes(1);
});
test("multiple glass locations selected, each has one part and no part questions => go to heritage funnel", async () => {
// Arrange
const partsOrQuestions = [
{
"glassName": "Single",
"glassLocation": "Windshield",
"parts": [
{
"partNumber": "FW04186GTYN",
"description": "solar, soundproofing, lane keep assist",
"color": "Green Tint",
"requiresRecalibration": true,
"requiresCapabilityQuestions": false,
"childParts": [
{
"partNumber": "GGG 3563 KIT",
"partType": "MOULDING",
"description": "Kit, Top & Sides "
}
]
}
],
"partQuestions": null
},
{
"glassName": "Back",
"glassLocation": "Driver",
"parts": [
{
"partNumber": "FD25457GTYN",
"description": "solar, driver side, rear",
"color": "Green Tint",
"requiresRecalibration": false,
"requiresCapabilityQuestions": false,
"childParts": null
}
],
"partQuestions": null
},
{
"glassName": "Front",
"glassLocation": "Driver",
"parts": [
{
"partNumber": "FD27090GTYN",
"description": "solar, driver side, front",
"color": "Green Tint",
"requiresRecalibration": false,
"requiresCapabilityQuestions": false,
"childParts": null
}
],
"partQuestions": null
},
{
"glassName": "Vent",
"glassLocation": "Driver",
"parts": [
{
"partNumber": "FV25459GTNN",
"description": "solar, driver side, rear",
"color": "Green Tint",
"requiresRecalibration": false,
"requiresCapabilityQuestions": false,
"childParts": null
}
],
"partQuestions": null
},
{
"glassName": "Stationary",
"glassLocation": "Rear",
"parts": [
{
"partNumber": "FB25460GTYN",
"description": "heated glass, solar",
"color": "Green Tint",
"requiresRecalibration": false,
"requiresCapabilityQuestions": false,
"childParts": null
}
],
"partQuestions": null
}
];
const { wrapper } = setupMocks({
partsOrQuestions: partsOrQuestions
});
// wrapper.vm.navigateAfterSaveToHeritageFunnel = jest.fn();
store.commit = jest.fn();
// Act
await wrapper.vm.navigateForwardWithSingleCarMatch();
// Assert
expect(store.commit).toHaveBeenCalledTimes(1);
expect(store.commit).toHaveBeenCalledWith(storeMutations.UPDATE_GLASS_PARTS, { partsOrQuestions })
expect(wrapper.vm.$refs.loadingModal.showModal).toHaveBeenCalledTimes(1);
expect(navigateAfterSaveToHeritageFunnel).toHaveBeenCalledTimes(1);
});
});
});
});
function setupMocks({ partsOrQuestions = [] }) {
const baseMixin = setupMocksForJsFiles({
actionList: [
{
actionName: storeActions.GET_PARTS_OR_QUESTIONS,
data: {
partsOrQuestions: partsOrQuestions
}
},
],
});
const mocks = getMountOptions({
router: {
navigateAfterSave: jest.fn()
},
});
const mockVinComponent = {
components: { loadingModal },
template: '<loadingModal ref="loadingModal" />',
// render() {
// return '<div ref="loadingModal"></div>'
// },
mixins: [vinPagesMixin, baseMixin.baseMixin]
};
// const mockVinComponent = Vue.component("mockcomponent", {
// template: '<loadingModal ref="loadingModal" />',
// mixins: [vinPagesMixin, baseMixin.baseMixin]
// })
const wrapper = shallowMount(mockVinComponent, mocks);
wrapper.vm.$refs.loadingModal.showModal = jest.fn();
return { wrapper };
}

View file

@ -8,11 +8,11 @@ const navigationScenarios = {
CLICKED_FORWARD: "CLICKED_FORWARD",
CLICKED_FORWARD_WITH_VIN: "CLICKED_FORWARD_WITH_VIN",
SELECTED_PARTS: "SELECTED_PARTS",
CONTINUING_WITH_PARTS_QUESTION: "CONTINUING_WITH_PARTS_QUESTION",
CONTINUING_WITH_MULTIPLE_PARTS: "CONTINUING_WITH_MULTIPLE_PARTS",
SELECTED_VIN_WITH_PART_QUESTIONS: "SELECTED_VIN_WITH_PART_QUESTIONS",
SELECTED_VIN_WITH_MULTIPLE_PARTS: "SELECTED_VIN_WITH_MULTIPLE_PARTS",
CONTINUING_WITH_SINGLE_PART: "CONTINUING_WITH_SINGLE_PART",
CONTINUING_WITH_MULTIPLE_VEHICLES: "CONTINUING_WITH_MULTIPLE_VEHICLES",
CONTINUING_WITH_DIFFERENT_GLASS: "CONTINUING_WITH_DIFFERENT_GLASS",
SELECTED_VIN_HAS_MISMATCHED_GLASS: "SELECTED_VIN_HAS_MISMATCHED_GLASS",
CLICKED_FORWARD_WITHOUT_VIN: "CLICKED_FORWARD_WITHOUT_VIN",
SELECTED_MANUAL_VIN: "SELECTED_MANUAL_VIN",
SELECTED_LICENSE_PLATE: "SELECTED_LICENSE_PLATE",

View file

@ -72,7 +72,7 @@ const routingTable = [
maps: [
{
scenario: navigationScenarios.CLICKED_BACK,
destinationFmgPageValue: fmgPageValues.VEHICLE_DAMAGE,
destinationFmgPageValue: fmgPageValues.VIN_LOOKUP,
},
{
scenario: navigationScenarios.SELECTED_PARTS,
@ -105,12 +105,20 @@ const routingTable = [
destinationFmgPageValue: fmgPageValues.ESTIMATE,
},
{
scenario: navigationScenarios.CLICKED_FORWARD,
scenario: navigationScenarios.SELECTED_VIN_HAS_MISMATCHED_GLASS,
destinationFmgPageValue: fmgPageValues.VEHICLE_DAMAGE,
},
{
scenario: navigationScenarios.CLICKED_BACK_WITH_VIN,
destinationFmgPageValue: fmgPageValues.VEHICLE_DAMAGE,
},
{
scenario: navigationScenarios.SELECTED_VIN_WITH_PART_QUESTIONS,
destinationFmgPageValue: fmgPageValues.PART_QUESTIONS
},
{
scenario: navigationScenarios.SELECTED_VIN_WITH_MULTIPLE_PARTS,
destinationFmgPageValue: fmgPageValues.VEHICLE_PARTS
}
],
},
@ -125,6 +133,14 @@ const routingTable = [
scenario: navigationScenarios.CLICKED_FORWARD,
destinationFmgPageValue: fmgPageValues.VEHICLE_DAMAGE,
},
{
scenario: navigationScenarios.SELECTED_VIN_WITH_PART_QUESTIONS,
destinationFmgPageValue: fmgPageValues.PART_QUESTIONS
},
{
scenario: navigationScenarios.SELECTED_VIN_WITH_MULTIPLE_PARTS,
destinationFmgPageValue: fmgPageValues.VEHICLE_PARTS
}
],
},
{
@ -139,9 +155,17 @@ const routingTable = [
destinationFmgPageValue: fmgPageValues.ADDRESS_VEHICLES,
},
{
scenario: navigationScenarios.CONTINUING_WITH_DIFFERENT_GLASS,
scenario: navigationScenarios.SELECTED_VIN_HAS_MISMATCHED_GLASS,
destinationFmgPageValue: fmgPageValues.VEHICLE_DAMAGE,
},
{
scenario: navigationScenarios.SELECTED_VIN_WITH_PART_QUESTIONS,
destinationFmgPageValue: fmgPageValues.PART_QUESTIONS
},
{
scenario: navigationScenarios.SELECTED_VIN_WITH_MULTIPLE_PARTS,
destinationFmgPageValue: fmgPageValues.VEHICLE_PARTS
}
],
},
{
@ -154,7 +178,15 @@ const routingTable = [
{
scenario: navigationScenarios.CLICKED_FORWARD,
destinationFmgPageValue: fmgPageValues.VEHICLE_DAMAGE,
},
},
{
scenario: navigationScenarios.SELECTED_VIN_WITH_PART_QUESTIONS,
destinationFmgPageValue: fmgPageValues.PART_QUESTIONS
},
{
scenario: navigationScenarios.SELECTED_VIN_WITH_MULTIPLE_PARTS,
destinationFmgPageValue: fmgPageValues.VEHICLE_PARTS
}
],
},
{
@ -178,6 +210,15 @@ const routingTable = [
},
],
},
{
fmgPageValue: fmgPageValues.PART_QUESTIONS,
maps: [
{
scenario: navigationScenarios.CLICKED_BACK,
destinationFmgPageValue: fmgPageValues.VIN_LOOKUP
}
]
},
];
export { routingTable };

View file

@ -111,7 +111,7 @@ export const mutations = {
updateGlassToReplace(state, glassToReplace) {
state.order.damage.glassToReplace = glassToReplace;
},
updateParts(state, partsData) {
updateGlassParts(state, partsData) {
state.order.lineItems.glassParts = partsData;
},
updatePageData(state, pageData) {
@ -212,9 +212,8 @@ export const mutations = {
state.order.vehicle.registration.firstName = null;
state.order.vehicle.registration.lastName = null;
},
resetPartsState(state) {
resetGlassPartsState(state) {
state.order.lineItems.glassParts = null;
state.order.lineItems.otherParts = null;
},
resetState(state) {
Object.assign(state, getDefaultState());
@ -400,14 +399,14 @@ export const actions = {
},
resetDamageAndDependencies(context) {
context.commit(storeMutations.RESET_DAMAGE_STATE);
context.commit(storeMutations.RESET_PARTS_STATE);
context.commit(storeMutations.RESET_GLASS_PARTS_STATE);
},
resetRegistrationAndDependencies(context) {
context.commit(storeMutations.RESET_REGISTRATION_STATE);
context.commit(storeMutations.RESET_PARTS_STATE)
context.commit(storeMutations.RESET_GLASS_PARTS_STATE)
},
resetPartsAndDependencies(context) {
context.commit(storeMutations.RESET_PARTS_STATE);
context.commit(storeMutations.RESET_GLASS_PARTS_STATE);
},
resetState(context) {
context.commit(storeMutations.RESET_STATE);
@ -539,7 +538,16 @@ export const actions = {
// Parts API Actions
getPartsOrQuestions(context, { carId, glassArray, zipCode, vin = '' }) {
getPartsOrQuestions(context) {
const vehicle = context.getters.vehicle;
const damage = context.getters.damage;
const order = context.state.order;
const carId = vehicle.carId;
const glassArray = damage.glassToReplace;
const zipCode = order.serviceLocation.zipCode;
const vin = vehicle.vin;
return globalMethods.callHttpClient({
method: endpoints.GetPartsOrQuestions.method,
endpoint: endpoints.GetPartsOrQuestions.url,

View file

@ -183,7 +183,7 @@ describe("Mutations", () => {
const storeState = state;
// Act
mutations.updateParts(storeState, { 'Windshield-Single': 'PARTNUM101'});
mutations.updateGlassParts(storeState, { 'Windshield-Single': 'PARTNUM101'});
// Assert
expect(storeState.order.lineItems.glassParts).toEqual({ 'Windshield-Single': 'PARTNUM101'});
@ -446,7 +446,7 @@ describe("Actions", () => {
await actions.resetDamageAndDependencies(context)
expect(commit).toBeCalledWith(storeMutations.RESET_DAMAGE_STATE);
expect(commit).toBeCalledWith(storeMutations.RESET_PARTS_STATE);
expect(commit).toBeCalledWith(storeMutations.RESET_GLASS_PARTS_STATE);
});
@ -462,7 +462,7 @@ describe("Actions", () => {
await actions.resetRegistrationAndDependencies(context)
expect(commit).toBeCalledWith(storeMutations.RESET_REGISTRATION_STATE);
expect(commit).toBeCalledWith(storeMutations.RESET_PARTS_STATE);
expect(commit).toBeCalledWith(storeMutations.RESET_GLASS_PARTS_STATE);
});
@ -477,7 +477,7 @@ describe("Actions", () => {
// Act
await actions.resetPartsAndDependencies(context)
expect(commit).toBeCalledWith(storeMutations.RESET_PARTS_STATE);
expect(commit).toBeCalledWith(storeMutations.RESET_GLASS_PARTS_STATE);
});
@ -747,7 +747,7 @@ describe("Getters", () => {
const storeState = state;
// Act
mutations.updateParts(storeState, {"Rear-Stationary": 'PART101'});
mutations.updateGlassParts(storeState, {"Rear-Stationary": 'PART101'});
// Assert
expect(getters.lineItems(storeState).glassParts).toEqual({"Rear-Stationary": 'PART101'});