Merge branch 'feature/CSR-439' of https://github.com/Safelite/DigitalConsumer.FixMyGlass into feature/CSR-439

This commit is contained in:
Donielle Austin 2022-06-24 15:58:37 -04:00
commit 7e8513cdd6
45 changed files with 2381 additions and 2313 deletions

View file

@ -11,19 +11,9 @@ module.exports = {
"!src/constants/*.js", "!src/constants/*.js",
"!src/router/**/*.js", "!src/router/**/*.js",
"!src/helpers/unit-test-helper.js", "!src/helpers/unit-test-helper.js",
"!src/layouts/component-test/component-test.vue",
"!src/layouts/form-test/form-test.vue",
"!src/layouts/vin-lookup/vin-lookup.vue",
"!src/layouts/vehicle-damage/windshield-damage-type-question/windshield-damage-type-question.vue",
"!src/layouts/vehicle-damage/windshield-options/windshield-options.vue", "!src/layouts/vehicle-damage/windshield-options/windshield-options.vue",
"!src/layouts/part-questions/**/*.vue", "!src/layouts/part-questions/**/*.vue",
"!src/layouts/reveal/**/*.vue", "!src/layouts/reveal/**/*.vue"
"!src/layouts/estimate/**/*.vue",
// TODO REMOVE THESE AFTER WRITING UNIT TESTS
"!src/layouts/address-vehicles/address-vehicles.vue",
"!src/layouts/address-vehicles/address-vehicles-question/address-vehicles-question.vue",
"!src/ux-components/alert\alert.vue",
"!src/helpers/validation-rules.js",
// END // END
], // ! means exclude from coverage. ], // ! means exclude from coverage.
testMatch: ["**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"], testMatch: ["**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"],

View file

@ -112,7 +112,8 @@ describe("buttonQuestion.vue", () => {
const wrapper = shallowMount(buttonQuestion, setupMocks({})); const wrapper = shallowMount(buttonQuestion, setupMocks({}));
await wrapper.setProps({ await wrapper.setProps({
answers: ["2022", "2021", "2020"], answers: ["2022", "2021", "2020"],
isMultiSelect: false isMultiSelect: false,
modelValue: []
}); });
const val = { checkValue: true, value: "2021", } const val = { checkValue: true, value: "2021", }
wrapper.vm.handleCheckedChanged(val); wrapper.vm.handleCheckedChanged(val);

View file

@ -36,6 +36,7 @@
data-test="button" data-test="button"
:validationRules="validationRules" :validationRules="validationRules"
:class="[suppressError ? 'alertError' : '']" :class="[suppressError ? 'alertError' : '']"
:clearOnUnmount="clearOnUnmount"
/> />
</div> </div>
</fieldset> </fieldset>
@ -84,6 +85,10 @@ export default {
validationRules: String, validationRules: String,
suppressError: Boolean, suppressError: Boolean,
useTextForValue: Boolean, useTextForValue: Boolean,
clearOnUnmount: {
type: Boolean,
default: true
}
}, },
computed: { computed: {
getFieldSetClasses() { getFieldSetClasses() {
@ -133,17 +138,14 @@ export default {
return answer.Name ? answer.Name : answer; return answer.Name ? answer.Name : answer;
}, },
handleCheckedChanged(val) { handleCheckedChanged(val) {
if(this.selectingInitiatesLoad) {
if(this.isMultiSelect && this.selectedValues) { this.selectedValues = [val.value];
// Add or remove item to array of data to emit } else {
const newSelectedValues = this.selectedValues;
if(Array.isArray(this.selectedValues)) { if(Array.isArray(this.selectedValues)) {
const newSelectedValues = this.selectedValues;
val.checkValue ? newSelectedValues.push(val.value) : newSelectedValues.splice(newSelectedValues.indexOf(val.value), 1); val.checkValue ? newSelectedValues.push(val.value) : newSelectedValues.splice(newSelectedValues.indexOf(val.value), 1);
this.selectedValues = newSelectedValues; this.selectedValues = newSelectedValues;
} }
} else {
this.selectedValues = [val.value];
} }
}, },
}, },
@ -179,4 +181,22 @@ export default {
text-align: center; 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> </style>

View file

@ -29,20 +29,22 @@ export default {
name: "FunnelSubHeader", name: "FunnelSubHeader",
props: { props: {
hasBackButton: Boolean, hasBackButton: Boolean,
backButtonAccessibleText: String,
cmsWidgetName: String, cmsWidgetName: String,
}, },
components: { components: {
buttonBack, buttonBack,
}, },
computed: { computed: {
text(){ text() {
return this.getCmsContent(this.cmsWidgetName, 'HeaderText'); return this.getCmsContent(this.cmsWidgetName, 'HeaderText');
}, },
subText(){ subText() {
return this.getCmsContent(this.cmsWidgetName, 'HeaderSubText'); return this.getCmsContent(this.cmsWidgetName, 'HeaderSubText');
}, },
headerColor(){ backButtonAccessibleText() {
return this.getCmsContent(this.cmsWidgetName, 'BackButtonAccessibleText');
},
headerColor() {
return this.subText ? 'dark-header' : 'light-header'; return this.subText ? 'dark-header' : 'light-header';
} }
}, },

View file

@ -0,0 +1,241 @@
import { shallowMount } from "@vue/test-utils";
import questionChain from "@/common-components/question-chain/question-chain.vue";
import { getMountOptions } from "@/helpers/unit-test-helper.js";
import store from "@/store";
jest.mock("@/store",()=>{return{};},{virtual:true});
describe("Question Chain component", () => {
it("Should not emit a modelValue change when setting selectedValue if isNewModelValueComplete is false", () => {
//Arrange
const { wrapper } = setupMocks({ modelValueProp: [] });
wrapper.vm.currentQuestion = 6;
const answerReturned = [wrapper.vm.questions[wrapper.vm.currentQuestion].answers[0].Name.toString()];
const localThis = {
$emit: jest.fn(),
getNewModelValue: jest.fn(() => { return false })
}
//Act
questionChain.computed.selectedValue.set.call(localThis, answerReturned);
//Assert
expect(localThis.$emit).not.toBeCalled();
});
it("Should emit a modelValue change when setting selectedValue if isNewModelValueComplete is true", () => {
//Arrange
const { wrapper } = setupMocks({ modelValueProp: [] });
wrapper.vm.currentQuestion = 6;
const answerReturned = [wrapper.vm.questions[wrapper.vm.currentQuestion].answers[0].Name.toString()];
const localThis = {
$emit: jest.fn(),
getNewModelValue: jest.fn(() => { return true })
}
//Act
questionChain.computed.selectedValue.set.call(localThis, answerReturned);
//Assert
expect(localThis.$emit).toBeCalledWith("update:modelValue", true);
});
it("should return false if no returned answer is given", () => {
//Arrange
const { wrapper } = setupMocks({ modelValueProp: [] });
wrapper.vm.currentQuestion = 1;
const answerReturned = null;
//Act
const result = wrapper.vm.getNewModelValue(answerReturned);
//Assert
expect(result).toEqual(false);
});
it("should return false if the user's answer on the current question leads to another question", () => {
//Arrange
const { wrapper } = setupMocks({ modelValueProp: [] });
wrapper.vm.currentQuestion = 1;
const answerReturned = [wrapper.vm.questions[wrapper.vm.currentQuestion].answers[0].Name.toString()];
//Act
const result = wrapper.vm.getNewModelValue(answerReturned);
//Assert
expect(result).toEqual(false);
});
it("should return an object with the final answer if the user's answer on the current question is a part number", () => {
//Arrange
const { wrapper } = setupMocks({ modelValueProp: [] });
wrapper.vm.currentQuestion = 6;
const answerReturned = [wrapper.vm.questions[wrapper.vm.currentQuestion].answers[0].Name.toString()];
//Act
const result = wrapper.vm.getNewModelValue(answerReturned);
//Assert
expect(result).toEqual(
{
answerResult: 'DW02102',
answeredQuestions: [
{
questionText: 'Is your vehicle equipped with heated seats?',
selectedAnswerText: 'Yes'
}
]
}
);
});
});
function setupMocks({
modelValueProp = "",
questionDataProp = {
"glassName": "Single",
"glassLocation": "Windshield",
"parts": null,
"partQuestions": [
{
"questionSequence": 1,
"questionText": "Is your Cherokee the Overland edition which can be identified by having a wood and leather wrapped steering wheel?",
"answers": [
{
"answerText": "Yes",
"nextQuestionSequence": 2,
"answerResult": ""
},
{
"answerText": "No",
"nextQuestionSequence": 3,
"answerResult": ""
}
]
},
{
"questionSequence": 2,
"questionText": "Is your vehicle equipped with rain sensing wipers that adjust their speed automatically when it rains?",
"answers": [
{
"answerText": "Yes",
"nextQuestionSequence": null,
"answerResult": "DW02270"
},
{
"answerText": "No",
"nextQuestionSequence": null,
"answerResult": "DW02264"
}
]
},
{
"questionSequence": 3,
"questionText": "Is your vehicle equipped with rain sensing wipers that adjust their speed automatically when it rains?",
"answers": [
{
"answerText": "Yes",
"nextQuestionSequence": null,
"answerResult": "DW02268"
},
{
"answerText": "No",
"nextQuestionSequence": 4,
"answerResult": ""
}
]
},
{
"questionSequence": 4,
"questionText": "Is your vehicle equipped with automatic climate control which will change the fan speed automatically in order to maintain a set temperature?",
"answers": [
{
"answerText": "Yes",
"nextQuestionSequence": 5,
"answerResult": ""
},
{
"answerText": "No",
"nextQuestionSequence": 6,
"answerResult": ""
}
]
},
{
"questionSequence": 5,
"questionText": "Is your vehicle equipped with heated seats?",
"answers": [
{
"answerText": "Yes",
"nextQuestionSequence": null,
"answerResult": "DW02104"
},
{
"answerText": "No",
"nextQuestionSequence": null,
"answerResult": "DW02103"
}
]
},
{
"questionSequence": 6,
"questionText": "Is your vehicle equipped with heated seats?",
"answers": [
{
"answerText": "Yes",
"nextQuestionSequence": null,
"answerResult": "DW02102"
},
{
"answerText": "No",
"nextQuestionSequence": null,
"answerResult": "DW02101"
}
]
}
],
},
methodsToMock = [],
}) {
//Mock store
store.dispatch = jest.fn(() => dataFromStoreApi);
store.getters = { vehicle: {year: 2019, make: 'honda', model: 'civc', style: '2 Door', category: 'CAR'} };
const mountOptions = getMountOptions({
store: {
dispatch: store.dispatch,
getters: store.getters,
},
});
//Mock props
const mockMixin = {
methods: {
getCmsContent: jest.fn()
}
}
mountOptions.propsData = {
modelValue: modelValueProp,
questionData: questionDataProp,
};
mountOptions.mixins = [mockMixin];
//Mock methods
methodsToMock.forEach((methodName) => {
questionChain.methods[methodName] = jest.fn();
});
const wrapper = shallowMount(questionChain, mountOptions);
//Mock CMS content
const cmsContent = {
};
return { wrapper, cmsContent };
}

View file

@ -0,0 +1,120 @@
<template>
<div v-for="(q, i) in questions" :key="i">
<transition appear name="fade" mode="out-in">
<buttonQuestion
v-if="q.questionSequence === currentQuestion"
class="radioQuestion"
:questionText="q.questionText"
:answers="q.answers"
:groupName="`${questionData.glassName}-${questionData.glassLocation}-${i}`"
textPosition="text-start"
v-model="selectedValue"
:isRequired=true
:validationRules="validationRules"
:clearOnUnmount=false
/>
</transition>
</div>
</template>
<script>
import buttonQuestion from "@/common-components/button-question/button-question";
export default {
name: "questionChain",
data() {
return {
currentQuestion: 1,
answeredQuestions: [],
};
},
props: {
questionData: Object,
validationRules: String,
modelValue: Array,
},
computed: {
questions() {
console.log("answeredQuestions: ", this.answeredQuestions)
const questions = this.questionData.partQuestions.map((q, i) => {
return {
questionText: q.questionText,
questionSequence: q.questionSequence,
answers: q.answers.map((a) => {
return {
Text: a.answerText,
// Name will either be nextQuestionSequence or answerResult
Name: a.nextQuestionSequence ? a.nextQuestionSequence : "answer-" + a.answerResult,
nextQuestionSequence: a.nextQuestionSequence,
answerResult: a.answerResult,
}
})
}
});
// add an empty item to be array[0] since we start with 1
questions.unshift({ "DeliberatelyBlankObject": "This object has been added as a placeholder only for question #0"});
return questions;
},
selectedValue: {
get: function() {
return this.modelValue;
},
set: function(returnedAnswer) {
const isNewModelValueComplete = this.getNewModelValue(returnedAnswer);
if (isNewModelValueComplete) {
this.$emit("update:modelValue", isNewModelValueComplete);
}
}
}
},
methods: {
getNewModelValue(returnedAnswer) {
if (!returnedAnswer || !Array.isArray(returnedAnswer)) { return false }
const lastAnswer = returnedAnswer[returnedAnswer.length - 1];
const currentQuestion = this.questions[this.currentQuestion];
if (lastAnswer.indexOf("answer-") === 0) {
// if it is an answerResult
const finalAnswer = lastAnswer.slice(7);
const currentQuestionSelectedAnswer = currentQuestion.answers.find(
({ answerResult }) => answerResult === finalAnswer
);
// add current item to list of answered questions
this.answeredQuestions.push(
{
questionText: currentQuestion.questionText,
selectedAnswerText: currentQuestionSelectedAnswer.Text,
}
);
return {
answerResult: finalAnswer,
answeredQuestions: this.answeredQuestions,
};
} else {
const currentQuestionSelectedAnswer = currentQuestion.answers.find(
({ nextQuestionSequence }) => nextQuestionSequence === parseInt(lastAnswer)
);
// add current item to list of answered questions
this.answeredQuestions.push(
{
questionText: currentQuestion.questionText,
selectedAnswerText: currentQuestionSelectedAnswer.Text,
}
);
this.currentQuestion = parseInt(lastAnswer); // update count to display next question
return false;
}
}
},
components: {
buttonQuestion,
},
};
</script>

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 // Arrange
const wrapper = shallowMount(textboxQuestion, { const wrapper = shallowMount(textboxQuestion, {
global: { global: {
@ -178,7 +178,6 @@ describe("textboxQuestion.vue", () => {
propsData: { propsData: {
options: {}, options: {},
modelValue: "foo", modelValue: "foo",
semiAggressiveValidation: true,
}, },
mixins: [mockMixin] mixins: [mockMixin]
}); });

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,7 +1,6 @@
import { shallowMount } from "@vue/test-utils"; import { shallowMount } from "@vue/test-utils";
import addressVehiclesQuestion from "@/layouts/address-vehicles/address-vehicles-question/address-vehicles-question"; import addressVehiclesQuestion from "@/layouts/address-vehicles/address-vehicles-question/address-vehicles-question";
describe("addressVehiclesQuestion.vue", () => { describe("addressVehiclesQuestion.vue", () => {
it("Should return content for differentVehicleAlertHeader", () => { it("Should return content for differentVehicleAlertHeader", () => {
@ -40,7 +39,7 @@ describe("addressVehiclesQuestion.vue", () => {
// Act // Act
const localThis = { $emit: jest.fn() } const localThis = { $emit: jest.fn() }
addressVehiclesQuestion.computed.selectedVehicleVin.set.call(localThis, 'newValue'); addressVehiclesQuestion.computed.selectedVehicleVinAsArray.set.call(localThis, ['newValue']);
// Assert // Assert
expect(localThis.$emit).toBeCalledWith("update:modelValue", "newValue"); expect(localThis.$emit).toBeCalledWith("update:modelValue", "newValue");

View file

@ -5,7 +5,7 @@
groupName="ChooseAddressVehicle" groupName="ChooseAddressVehicle"
:questionText="questionText" :questionText="questionText"
:answers="vehicles" :answers="vehicles"
v-model="selectedVehicleVin" v-model="selectedVehicleVinAsArray"
isRequired=true isRequired=true
:validation-rules="validationRules" :validation-rules="validationRules"
/> />
@ -62,16 +62,18 @@ export default {
questionText() { questionText() {
return this.getCmsContent("VehicleConfirmationQuestion", "QuestionText"); return this.getCmsContent("VehicleConfirmationQuestion", "QuestionText");
}, },
selectedVehicleVin: { selectedVehicleVinAsArray: {
get: function() { get: function() {
return this.modelValue; const modelValueAsArray = this.modelValue ? [this.modelValue] : [];
return modelValueAsArray;
}, },
set: function(newValue) { set: function(newValue) {
this.$emit("update:modelValue", newValue); const newValueAsScalar = newValue && newValue.length > 0 ? newValue[newValue.length-1] : null;
this.$emit("update:modelValue", newValueAsScalar);
} }
}, },
selectedVehicle() { selectedVehicle() { // this computed is only needed for the computed differentVehicleAlertBody text above
return this.vehicles.find( ({ vin }) => vin === this.selectedVehicleVin[0] ); return this.vehicles.find( ({ vin }) => vin === this.selectedVehicleVinAsArray[this.selectedVehicleVinAsArray.length-1] );
}, },
}, },
components: { components: {

View file

@ -5,9 +5,10 @@ import addressVehicles from "@/layouts/address-vehicles/address-vehicles";
import { shallowMount } from "@vue/test-utils"; import { shallowMount } from "@vue/test-utils";
import { getMountOptions } from "@/helpers/unit-test-helper.js"; import { getMountOptions } from "@/helpers/unit-test-helper.js";
import store from "@/store"; 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"; import * as navigateToHeritage from "@/helpers/heritage-integration/navigation-helper";
// Mock our module for promises. // Mock our module for promises.
jest.mock("@/helpers/damage-helper", () => ({ jest.mock("@/helpers/damage-helper", () => ({
isGlassAvailableForCarId: () => { isGlassAvailableForCarId: () => {
@ -17,11 +18,12 @@ jest.mock("@/helpers/damage-helper", () => ({
describe("addressVehicles.vue", () => { describe("addressVehicles.vue", () => {
test("Should return true for valid page requisites if carId / zipCode / emailAddress / pageData exists", async () => { test("Should return true for valid page requisites if carId / zipCode / emailAddress / pageData exists", async () => {
// Arrange // Arrange
const { wrapper } = setupMocks({}); 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 // Act
const result = wrapper.vm.arePagePrerequisitesValid(); 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 () => { test("Should return false for valid page requisites if carId is missing", async () => {
// Arrange // Arrange
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
wrapper.vm.$router.navigate = jest.fn();
// Act // Act
wrapper.vm.$store.getters.order.vehicle.carId = null; store.commit(storeMutations.UPDATE_CAR_ID, null);
const result = wrapper.vm.arePagePrerequisitesValid(); const result = wrapper.vm.arePagePrerequisitesValid();
//Assert //Assert
@ -55,8 +56,8 @@ describe("addressVehicles.vue", () => {
wrapper.vm.$router.navigate = jest.fn(); wrapper.vm.$router.navigate = jest.fn();
// Act // Act
wrapper.setData({ await wrapper.setData({
selectedVehicleVin: ['5NMS3CADXLH233004'], selectedVehicleVin: '5NMS3CADXLH233004',
}); });
wrapper.vm.backButtonAction(); wrapper.vm.backButtonAction();
@ -85,8 +86,8 @@ describe("addressVehicles.vue", () => {
wrapper.vm.navigateForward = jest.fn().mockImplementation(()=> {}); wrapper.vm.navigateForward = jest.fn().mockImplementation(()=> {});
// Act // Act
wrapper.setData({ await wrapper.setData({
selectedVehicleVin: ['5NMS3CADXLH233004'], selectedVehicleVin: '5NMS3CADXLH233004',
}); });
await wrapper.vm.forwardButtonAction(); await wrapper.vm.forwardButtonAction();
@ -115,8 +116,8 @@ describe("addressVehicles.vue", () => {
wrapper.vm.updateCustomerInfo = jest.fn().mockImplementation(()=> {}); wrapper.vm.updateCustomerInfo = jest.fn().mockImplementation(()=> {});
// Act // Act
wrapper.setData({ await wrapper.setData({
selectedVehicleVin: ['5NMS3CADXLH233004'], selectedVehicleVin: '5NMS3CADXLH233004',
}); });
await wrapper.vm.forwardButtonAction(); await wrapper.vm.forwardButtonAction();
wrapper.vm.$nextTick(); wrapper.vm.$nextTick();
@ -142,15 +143,15 @@ describe("addressVehicles.vue", () => {
wrapper.vm.$router.navigateAfterSave = jest.fn(); wrapper.vm.$router.navigateAfterSave = jest.fn();
// Act // Act
wrapper.setData({ await wrapper.setData({
selectedVehicleVin: ['5NMS3CADXLH233004'], selectedVehicleVin: '5NMS3CADXLH233004',
isSelectedGlassAvailableForVehicle: false, isSelectedGlassAvailableForVehicle: false,
isCarIdDifferent: true, isCarIdDifferent: true,
}); });
await wrapper.vm.updateCustomerInfo(wrapper.vm.selectedVehicle.vin, wrapper.vm.selectedVehicle.vehicle); await wrapper.vm.updateCustomerInfo(wrapper.vm.selectedVehicle.vin, wrapper.vm.selectedVehicle.vehicle);
//Assert //Assert
expect(store.dispatch).toBeCalledWith("resetDamageAndDependencies"); expect(wrapper.vm.dispatchStoreAction).toBeCalledWith("resetDamageAndDependencies");
wrapper.unmount(); wrapper.unmount();
}); });
@ -164,7 +165,7 @@ describe("addressVehicles.vue", () => {
await wrapper.vm.lookupVin('1234567890'); await wrapper.vm.lookupVin('1234567890');
//Assert //Assert
expect(store.dispatch).toBeCalledWith("lookupVehicleByVin", {"vin": "1234567890"}); expect(wrapper.vm.dispatchStoreAction).toBeCalledWith("lookupVehicleByVin", {"vin": "1234567890"});
wrapper.unmount(); wrapper.unmount();
}); });
@ -172,11 +173,10 @@ describe("addressVehicles.vue", () => {
test("If selectedVehicleVin changes, then should update isCarIdDifferent", async () => { test("If selectedVehicleVin changes, then should update isCarIdDifferent", async () => {
// Arrange // Arrange
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
wrapper.vm.$refs.funnelFooter.updateButtonText = jest.fn();
// Act // Act
wrapper.setData({ await wrapper.setData({
selectedVehicleVin: ['5NMS3CADXLH233004'], selectedVehicleVin: '5NMS3CADXLH233004',
isCarIdDifferent: false, isCarIdDifferent: false,
}); });
await wrapper.vm.resetDependentState(); await wrapper.vm.resetDependentState();
@ -193,8 +193,8 @@ describe("addressVehicles.vue", () => {
wrapper.vm.$refs.funnelFooter.updateButtonText = jest.fn(); wrapper.vm.$refs.funnelFooter.updateButtonText = jest.fn();
// Act // Act
wrapper.setData({ await wrapper.setData({
selectedVehicleVin: ['5NMS3CADXLH233004'], selectedVehicleVin: '5NMS3CADXLH233004',
isCarIdDifferent: false, isCarIdDifferent: false,
}); });
await wrapper.vm.resetDependentState(); await wrapper.vm.resetDependentState();
@ -212,8 +212,8 @@ describe("addressVehicles.vue", () => {
wrapper.vm.$router.navigateAfterSave = jest.fn(); wrapper.vm.$router.navigateAfterSave = jest.fn();
// Act // Act
wrapper.setData({ await wrapper.setData({
selectedVehicleVin: ['5NMS3CADXLH233004'], selectedVehicleVin: '5NMS3CADXLH233004',
isSelectedGlassAvailableForVehicle: false, isSelectedGlassAvailableForVehicle: false,
isCarIdDifferent: true, isCarIdDifferent: true,
}); });
@ -225,7 +225,7 @@ describe("addressVehicles.vue", () => {
wrapper.unmount(); 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 // Arrange
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
wrapper.vm.$refs.funnelFooter.updateButtonText = jest.fn(); wrapper.vm.$refs.funnelFooter.updateButtonText = jest.fn();
@ -233,79 +233,49 @@ describe("addressVehicles.vue", () => {
navigateToHeritage.navigateAfterSaveToHeritageFunnel = jest.fn(); navigateToHeritage.navigateAfterSaveToHeritageFunnel = jest.fn();
// Act // Act
wrapper.setData({ await wrapper.setData({
selectedVehicleVin: ['5NMS3CADXLH233004'],
isSelectedGlassAvailableForVehicle: true,
isCarIdDifferent: false, isCarIdDifferent: false,
}); });
await wrapper.vm.navigateForward(); await wrapper.vm.navigateForward();
//Assert //Assert
expect(navigateToHeritage.navigateAfterSaveToHeritageFunnel).toBeCalledTimes(1); expect(wrapper.vm.navigateForwardWithSingleCarMatch).toBeCalledTimes(1);
wrapper.unmount(); wrapper.unmount();
}); });
}); });
function setupMocks({}) {
function setupMocks({
// modelValueProp = "1900",
cmsQuestionText = "CMS text goes here",
// dataFromStoreApi = [],
}) {
//Mock store //Mock store
store.dispatch = jest.fn(() => {}); store.commit(storeMutations.RESET_STATE);
store.getters = { store.commit(storeMutations.UPDATE_PAGE_DATA, {
pageData: jest.fn((pageName) => { page: "address-vehicles",
// console.log('pageName: ', pageName) // address-vehicles data: [{
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: {
vehicle: { 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: { vin: "5NMS3CADXLH233004"
zipCode: "12345" }],
}, })
customer: {
emailAddress: "qw@er.ty"
}
},
damage: {
glassToReplace: "Windshield"
},
vehicle: {
carId: "456",
}
};
// baseMixin.methods.dispatchStoreAction = jest.fn();
const mountOptions = getMountOptions({ const mountOptions = getMountOptions({
store: {
dispatch: store.dispatch,
getters: store.getters,
},
router: { router: {
navigate: jest.fn(), navigate: jest.fn(),
}, },
actionList: [
{
actionName: storeActions.LOOKUP_VEHICLE_BY_VIN,
data: {}
}
]
}); });
//Mock props //Mock props
@ -320,16 +290,6 @@ function setupMocks({
} }
return null; return null;
}), }),
// dispatchStoreAction: jest.fn(() => {
// console.log("23424243")
// }),
// isGlassAvailableForCarId: () => {
// console.log("%%%%%%%%%%%%%%%")
// return Promise.resolve(true)
// }
// lookupVin: jest.fn(() => Promise.resolve(lookupVinResponse)),
}, },
computed: { computed: {
dynamicStrings() { dynamicStrings() {
@ -337,18 +297,13 @@ function setupMocks({
} }
} }
} }
// mountOptions.propsData = {
// modelValue: modelValueProp,
// };
mountOptions.mixins = [mockMixin]; mountOptions.mixins = [mockMixin];
const wrapper = shallowMount(addressVehicles, mountOptions); const wrapper = shallowMount(addressVehicles, mountOptions);
//Mock CMS content wrapper.vm.$refs.funnelFooter.updateButtonText = jest.fn();
const cmsContent = { wrapper.vm.navigateForwardWithSingleCarMatch = jest.fn();
QuestionText: cmsQuestionText,
};
return { wrapper }; 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 { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import { settleAllPromises } from "@/helpers/layout-helper"; import { settleAllPromises } from "@/helpers/layout-helper";
import store from "@/store"; import store from "@/store";
import baseMixin from "@/mixins/base-mixin.js";
import { storeActions } from "@/constants/store-actions"; import { storeActions } from "@/constants/store-actions";
import { storeMutations } from "@/constants/store-mutations"; import { storeMutations } from "@/constants/store-mutations";
import { fmgPageValues } from "@/router/router-constants/fmgPage-values"; import { fmgPageValues } from "@/router/router-constants/fmgPage-values";
import { errorMessages } from "@/constants/error-messages"; import { errorMessages } from "@/constants/error-messages";
import { required } from "@/helpers/validation-rules"; import { required } from "@/helpers/validation-rules";
import { Form, defineRule } from "vee-validate"; import { Form, defineRule } from "vee-validate";
import { navigateAfterSaveToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper";
import { isGlassAvailableForCarId } from "@/helpers/damage-helper"; import { isGlassAvailableForCarId } from "@/helpers/damage-helper";
import { doesCopyContainRouterLink, import { doesCopyContainRouterLink,
splitCopyOnCMSPlaceHolder, splitCopyOnCMSPlaceHolder,
getRouterLinkRouteFromCopy, getRouterLinkRouteFromCopy,
getRouterLinkDisplayTextFromCopy, } from "@/helpers/cms-content-helper" getRouterLinkDisplayTextFromCopy, } from "@/helpers/cms-content-helper";
import vinPagesMixin from "@/mixins/vin-pages-mixin";
// DEFINE VALIDATION RULES // DEFINE VALIDATION RULES
defineRule("vehicle-required", required(errorMessages.VEHICLE_REQUIRED)); defineRule("vehicle-required", required(errorMessages.VEHICLE_REQUIRED));
export default { export default {
name: "address-vehicles", name: "address-vehicles",
mixins: [vinPagesMixin],
async beforeRouteEnter(to, from, next) { async beforeRouteEnter(to, from, next) {
// Call APIs // Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage); const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
@ -102,7 +102,7 @@ export default {
}, },
data() { data() {
return { return {
selectedVehicleVin: null, selectedVehicleVin: "",
isCarIdDifferent: false, isCarIdDifferent: false,
isSelectedGlassAvailableForVehicle: true, isSelectedGlassAvailableForVehicle: true,
}; };
@ -145,7 +145,7 @@ export default {
return store.getters.pageData(fmgPageValues.ADDRESS_VEHICLES); return store.getters.pageData(fmgPageValues.ADDRESS_VEHICLES);
}, },
selectedVehicle() { selectedVehicle() {
return this.VehiclesForQuestions.find( ({ vin }) => vin === this.selectedVehicleVin[0] ); return this.VehiclesForQuestions.find( ({ vin }) => vin === this.selectedVehicleVin );
}, },
}, },
methods: { methods: {
@ -188,13 +188,12 @@ export default {
); );
return; return;
} else { } else {
this.$refs.loadingModal.showModal(); this.navigateForwardWithSingleCarMatch();
navigateAfterSaveToHeritageFunnel(this.$route);
return; return;
} }
}, },
lookupVin(vin) { lookupVin(vin) {
return baseMixin.methods.dispatchStoreAction( return this.dispatchStoreAction(
storeActions.LOOKUP_VEHICLE_BY_VIN, storeActions.LOOKUP_VEHICLE_BY_VIN,
{ vin } { vin }
); );
@ -205,7 +204,7 @@ export default {
//this.$store.dispatch(storeActions.SAVE_VIN, vin,vehicle), //this.$store.dispatch(storeActions.SAVE_VIN, vin,vehicle),
updateCustomerInfo(vin, vehicle) { updateCustomerInfo(vin, vehicle) {
if (this.isCarIdDifferent && !this.isSelectedGlassAvailableForVehicle) { 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_VEHICLE_VIN, vin);
store.commit(storeMutations.UPDATE_YEAR, vehicle.year); store.commit(storeMutations.UPDATE_YEAR, vehicle.year);
@ -221,10 +220,13 @@ export default {
}, },
watch: { watch: {
selectedVehicleVin() { selectedVehicleVin: {
// does this vehicle match the previously selected carId? handler() {
this.isCarIdDifferent = this.selectedVehicle.vehicle.carId !== store.getters.vehicle.carId; // does this vehicle match the previously selected carId?
this.$refs.funnelFooter.updateButtonText(`Continue with ${this.selectedVehicle.vehicle.year} ${this.selectedVehicle.vehicle.make} ${this.selectedVehicle.vehicle.model}`); this.isCarIdDifferent = this.selectedVehicle.vehicle.carId !== store.getters.vehicle.carId;
this.$refs.funnelFooter.updateButtonText(`Continue with ${this.selectedVehicle.vehicle.year} ${this.selectedVehicle.vehicle.make} ${this.selectedVehicle.vehicle.model}`);
},
deep: true
}, },
}, },

File diff suppressed because it is too large Load diff

View file

@ -1,244 +0,0 @@
<template>
<div class="container-fluid shadow rounded-3 p-2 position-relative make-tall">
<funnelHeader ref="funnelHeader" />
<vehicleBanner ref="vehicleBanner" :displayGenericVehicleImage=false />
<funnelSubHeader ref="funnelSubHeader" />
<Form
@submit="onSubmit"
@invalid-submit="onInvalidSubmit"
ref="theForm"
v-slot="{ values, meta, errors }"
>
<damageLocationQuestion
ref="damageLocation"
v-model="selectedDamageLocations"
groupName="DamageLocationQuestion"
/>
<windshieldOptions
ref="windshieldOptions"
v-model="selectedWindshieldOptions"
:hasRepairReplaceConflict="hasRepairReplaceConflict"
:selectedDamageLocations="selectedDamageLocations"
/>
<alert
class="my-3"
v-show="hasRepairReplaceConflict"
alertClass="alert-danger"
alertHeadline="You'll need to schedule separate appointments"
alertCopy="Vehicle service requiring both glass repair and replacement must be scheduled separately, as they're performed by different technicians. Continue scheduling your first service now, and then come back to schedule the second service."
:isDismissible="false"
/>
<sideDoorOptions
ref="sideDoorOptions"
groupName="SideDoorSideQuestion"
v-model="sideDoorOptionsData"
v-show="!hasRepairReplaceConflict"
:selectedDamageLocations="selectedDamageLocations"
/>
<replaceOptionsQuestion
ref="backGlassOptions"
:isAvailable="isRearWindowDamageLocation && !hasRepairReplaceConflict"
v-model="selectedRearReplaceOptions"
groupName="BackGlassReplaceOptionsQuestion"
validationRules="replace-options-required"
/>
<funnel-footer
ref="funnelFooter"
:isForwardActionDisabled="!meta.valid"
@back-clicked="backButtonAction"
@ForwardClicked="forwardButtonAction"
/>
<!-- KEEP TEMPORARILY FOR TROUBLESHOOTING VALIDATION -->
<div class="g-2 mt-6 mx-4 w-25 position-fixed fixed-top">
<p>Current Form, values:</p>
<pre>{{ values }}</pre>
<p>Errors:</p>
<pre>{{ errors }}</pre>
<p>Is Form Valid? (Meta.valid):</p>
<pre>{{ meta.valid }}</pre>
</div>
</Form>
</div>
</template>
<script>
// Components
import funnelHeader from "@/common-components/funnel-header/funnel-header";
import funnelFooter from "@/common-components/funnel-footer/funnel-footer";
import vehicleBanner from "@/common-components/vehicle-banner/vehicle-banner";
import funnelSubHeader from "@/common-components/funnel-sub-header/funnel-sub-header";
import replaceOptionsQuestion from "@/layouts/vehicle-damage/replace-options-question/replace-options-question";
import damageLocationQuestion from "@/layouts/vehicle-damage/damage-location-question/damage-location-question";
import sideDoorOptions from "@/layouts/vehicle-damage/side-door-options/side-door-options";
import windshieldOptions from "@/layouts/vehicle-damage/windshield-options/windshield-options";
import alert from "@/ux-components/alert/alert";
// Supporting files
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import { settleAllPromises } from "@/helpers/layout-helper";
import { storeActions } from "@/constants/store-actions";
import store from "@/store";
import baseMixin from "@/mixins/base-mixin";
import { Form, defineRule } from "vee-validate";
import { required } from "@/helpers/validation-rules";
import { errorMessages } from "@/constants/error-messages";
import { damageLocationsCms } from "@/constants/damage-locations-cms.js";
// DEFINE VALIDATION RULES
defineRule("replace-options-required", required(errorMessages.REPLACE_OPTIONS_REQUIRED));
export default {
name: "form-test",
async beforeRouteEnter(to, from, next) {
// Call APIs
const cmsContentPromise = fetchCmsContentForPage('vehicle-damage');
const damageOptionsPromise =
baseMixin.methods.dispatchStoreAction(
storeActions.GET_DAMAGE_OPTIONS,
{ carId: store.getters.vehicle.carId }
);
// Settle promises and get results
const promiseResultMap = [
{
resultKey: "cmsContent",
promise: cmsContentPromise,
},
{
resultKey: "damageOptions",
promise: damageOptionsPromise,
},
];
const resultMap = await settleAllPromises(promiseResultMap);
// Call the "next" function to complete the transition to this page.
next((vm) => {
vm.$refs.funnelHeader.initializeComponent(
resultMap.cmsContent.FunnelHeaderWidget
);
vm.$refs.vehicleBanner.initializeComponent(
resultMap.cmsContent.VehicleBannerWidget
);
vm.$refs.funnelSubHeader.initializeComponent(
resultMap.cmsContent.FunnelSubHeaderWidget
);
vm.$refs.damageLocation.initializeComponent(
resultMap.cmsContent.DamageLocationQuestion, resultMap.damageOptions
);
vm.$refs.sideDoorOptions.initializeComponent(
resultMap.cmsContent.SideDoorSideQuestion, resultMap.cmsContent.DriverSideReplaceOptionsQuestion, resultMap.cmsContent.PassengerSideReplaceOptionsQuestion, resultMap.damageOptions.driverSideOptions.availableReplacementOptions, resultMap.damageOptions.passengerSideOptions.availableReplacementOptions
);
vm.$refs.windshieldOptions.initializeComponent(
resultMap.cmsContent.WindshieldDamageTypeQuestion, resultMap.cmsContent.WindshieldChipCountQuestion,
resultMap.cmsContent.WindshieldReplaceOptionsQuestion, resultMap.damageOptions.windshieldOptions.availableReplacementOptions
);
vm.$refs.backGlassOptions.initializeComponent(
resultMap.cmsContent.RearReplaceOptionsQuestion, resultMap.damageOptions.backGlassOptions.availableReplacementOptions
);
vm.$refs.funnelFooter.initializeComponent(
resultMap.cmsContent.FunnelFooterWidget
);
});
},
data(){
return {
selectedDamageLocations: [],
sideDoorOptionsData: {
selectedDoorSides: [],
selectedDriverSideReplaceOptions: [],
selectedPassengerSideReplaceOptions: []
},
selectedWindshieldOptions: [],
selectedRearReplaceOptions: [],
}
},
methods: {
arePagePrerequisitesValid() {
return store.getters.vehicle.carId !== null;
},
resetDependentState() {
store.dispatch(storeActions.RESET_PARTS_STATE_AND_DEPENDENCIES);
},
onSubmit(values) {
window.alert('Success! Submitted values: ' + JSON.stringify(values, null ,2))
console.log('submitted: ', JSON.stringify(values, null ,2));
},
onInvalidSubmit({ values, errors, results }) {
// identify the first error field and put focus on it
console.log("values: ", values);
console.log("errors: ", errors);
console.log("results: ", results);
// get error names array
const errorNames = errors ? Object.keys(errors) : [];
const firstErrorEl = errorNames[0];
if (firstErrorEl) {
console.log('firstErrorEl: ', firstErrorEl);
const qsString = "[data-focus-target='" + firstErrorEl + "']";
document.querySelector(qsString).focus();
}
},
},
computed: {
isWindshieldDamageLocation() {
return this.selectedDamageLocations.some(selectedDamages =>
{
return selectedDamages.toUpperCase() === damageLocationsCms.WINDSHIELD;
});
},
isSideDoorDamageLocation() {
return this.selectedDamageLocations.some(selectedDamages =>
{
return selectedDamages.toUpperCase() === damageLocationsCms.SIDEDOOR;
});
},
isRearWindowDamageLocation() {
return this.selectedDamageLocations.some(selectedDamages =>
{
return selectedDamages.toUpperCase() === damageLocationsCms.REARWINDOW;
});
},
isWindshieldRepair() {
if (!this.isWindshieldDamageLocation) return false;
return this.selectedWindshieldOptions.selectedWindshieldDamageType && this.selectedWindshieldOptions.selectedWindshieldDamageType.some(selectedDamageType =>
{
return selectedDamageType.toUpperCase() === "REPAIR";
});
},
isDriverSideReplace() {
if (!this.isSideDoorDamageLocation) return false;
return this.sideDoorOptionsData.selectedDoorSides.some(selectedDriverSide =>
{
return selectedDriverSide.toUpperCase() === damageLocationsCms.DRIVERSIDE;
});
},
isPassengerSideReplace() {
if (!this.isSideDoorDamageLocation) return false;
return this.sideDoorOptionsData.selectedDoorSides.some(selectedPassengerSide =>
{
return selectedPassengerSide.toUpperCase() === damageLocationsCms.PASSENGERSIDE;
});
},
hasRepairReplaceConflict() {
return this.isWindshieldDamageLocation && this.selectedDamageLocations.length > 1 && this.isWindshieldRepair;
},
},
components: {
funnelHeader,
funnelFooter,
vehicleBanner,
funnelSubHeader,
sideDoorOptions,
damageLocationQuestion,
windshieldOptions,
replaceOptionsQuestion,
Form,
alert,
},
};
</script>

View file

@ -4,11 +4,11 @@ import licensePlateLookup from "@/layouts/license-plate-lookup/license-plate-loo
// Supporting Files // Supporting Files
import { settleAllPromises } from "@/helpers/layout-helper.js"; import { settleAllPromises } from "@/helpers/layout-helper.js";
import * as navigateToHeritage from "@/helpers/heritage-integration/navigation-helper"; import * as navigateToHeritage from "@/helpers/heritage-integration/navigation-helper";
import baseMixin from "@/mixins/base-mixin";
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper"; import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import { shallowMount } from "@vue/test-utils"; import { shallowMount } from "@vue/test-utils";
import { getMountOptions } from "@/helpers/unit-test-helper.js"; import { getMountOptions } from "@/helpers/unit-test-helper.js";
import { nextTick } from "vue"; import { nextTick } from "vue";
import { storeActions } from "@/constants/store-actions";
import { storeMutations } from "@/constants/store-mutations"; import { storeMutations } from "@/constants/store-mutations";
import store from "@/store"; import store from "@/store";
@ -223,19 +223,22 @@ describe("license-plate-lookup.vue", () => {
}); });
describe("navigateForward", () => { 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 // Arrange
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
//Act //Act
wrapper.vm.isCarIdDifferent = true; await wrapper.setData({
wrapper.vm.isSelectedGlassAvailableForVehicle = false; isCarIdDifferent: true,
isSelectedGlassAvailableForVehicle: false
})
wrapper.vm.$router.navigateAfterSave = jest.fn(); wrapper.vm.$router.navigateAfterSave = jest.fn();
wrapper.vm.getCmsContent = jest.fn().mockImplementation(() => { wrapper.vm.getCmsContent = jest.fn().mockImplementation(() => {
return ''; return '';
}); });
store.dispatch = jest.fn(); wrapper.vm.dispatchStoreAction = jest.fn();
await wrapper.vm.navigateForward(); await wrapper.vm.navigateForward();
@ -249,7 +252,9 @@ describe("license-plate-lookup.vue", () => {
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
//Act //Act
wrapper.vm.isCarIdDifferent = false; await wrapper.setData({
isCarIdDifferent: false
})
wrapper.vm.getCmsContent = jest.fn().mockImplementation(() => { wrapper.vm.getCmsContent = jest.fn().mockImplementation(() => {
return ''; return '';
}); });
@ -259,6 +264,38 @@ describe("license-plate-lookup.vue", () => {
//Assert //Assert
expect(navigateToHeritage.navigateAfterSaveToHeritageFunnel).toHaveBeenCalled(); 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({}); const { wrapper } = setupMocks({});
//Act //Act
wrapper.vm.licensePlate = "NEWPLATE"; wrapper.setData({
licensePlate: "NEWPLATE"
})
wrapper.vm.getCmsContent = jest.fn(); wrapper.vm.getCmsContent = jest.fn();
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
@ -283,7 +322,9 @@ describe("license-plate-lookup.vue", () => {
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
//Act //Act
wrapper.vm.registrationZip = "55555"; await wrapper.setData({
registrationZip: "55555"
})
wrapper.vm.getCmsContent = jest.fn(); wrapper.vm.getCmsContent = jest.fn();
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
@ -297,7 +338,9 @@ describe("license-plate-lookup.vue", () => {
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
//Act //Act
wrapper.vm.serviceZip = "55555"; await wrapper.setData({
serviceZip: "55555"
})
wrapper.vm.getCmsContent = jest.fn(); wrapper.vm.getCmsContent = jest.fn();
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
@ -469,22 +512,23 @@ describe("license-plate-lookup.vue", () => {
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
//Act //Act
wrapper.vm.isCarIdDifferent = true; await wrapper.setData({
wrapper.vm.isSelectedGlassAvailableForVehicle = false; isCarIdDifferent: true,
isSelectedGlassAvailableForVehicle: false
})
wrapper.vm.getCmsContent = jest.fn().mockImplementation(() => { wrapper.vm.getCmsContent = jest.fn().mockImplementation(() => {
return ''; return '';
}); });
store.commit = jest.fn(); 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" } 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'); await wrapper.vm.updateCustomerInfo('vin', vehicleInfo, 'registrationState');
//Assert //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 // Arrange
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
@ -494,10 +538,10 @@ describe("license-plate-lookup.vue", () => {
//Assert //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 // Arrange
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
@ -507,22 +551,18 @@ describe("license-plate-lookup.vue", () => {
//Assert //Assert
expect(baseMixin.methods.dispatchStoreAction).toHaveBeenCalled(); expect(wrapper.vm.dispatchStoreAction).toHaveBeenCalled();
}); });
}) })
}); });
function setupMocks({ function setupMocks({
pageHeaderWidgetHeaderText = {}, pageHeaderWidgetHeaderText = {},
mountOptionsMockData = { mountOptionsMockData = {},
router: { partsOrQuestions = []
navigate: jest.fn(),
},
},
}) { }) {
store.commit(storeMutations.RESET_STATE); store.commit(storeMutations.RESET_STATE);
//Mock api responses //Mock api responses
baseMixin.methods.dispatchStoreAction = jest.fn();
const apiResponses = { const apiResponses = {
cmsContent: { cmsContent: {
FunnelSubHeaderWidget: pageHeaderWidgetHeaderText, 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); const apiPromise = Promise.resolve(apiResponses);
settleAllPromises.mockImplementation(() => apiPromise); settleAllPromises.mockImplementation(() => apiPromise);
@ -547,7 +602,7 @@ function setupMocks({
const wrapper = shallowMount(licensePlateLookup, mountOptions); const wrapper = shallowMount(licensePlateLookup, mountOptions);
wrapper.vm.setCmsContent = baseMixin.methods.setCmsContent; wrapper.vm.setCmsContent = jest.fn();
wrapper.vm.getCmsContent = jest.fn().mockImplementation(() => ""); wrapper.vm.getCmsContent = jest.fn().mockImplementation(() => "");
wrapper.vm.$refs.funnelFooter.updateButtonText = jest.fn(); wrapper.vm.$refs.funnelFooter.updateButtonText = jest.fn();
wrapper.vm.$refs.funnelFooter.removeLoader = jest.fn(); wrapper.vm.$refs.funnelFooter.removeLoader = jest.fn();

View file

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

View file

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

View file

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

View file

@ -145,8 +145,8 @@ describe("vehicle-damage.vue", () => {
wrapper.vm.selectedRearReplaceOptions = ["Stationary"]; wrapper.vm.selectedRearReplaceOptions = ["Stationary"];
const expectedGlassToReplace = [{ location: "Windshield", name: "Single" }, { location: "Driver", name: "Back" }, const expectedGlassToReplace = [{ glassLocation: "Windshield", glassName: "Single" }, { glassLocation: "Driver", glassName: "Back" },
{ location: "Passenger", name: "Quarter" }, { location: "Rear", name: "Stationary" }]; { glassLocation: "Passenger", glassName: "Quarter" }, { glassLocation: "Rear", glassName: "Stationary" }];
//Act //Act
vehicleDamage.beforeRouteEnter.call( vehicleDamage.beforeRouteEnter.call(
@ -237,7 +237,7 @@ describe("vehicle-damage.vue", () => {
selectedWindshieldDamageType: ["Replace"] selectedWindshieldDamageType: ["Replace"]
}; };
const expectedGlassToReplace = [{ location: "Windshield", name: "Single" },]; const expectedGlassToReplace = [{ glassLocation: "Windshield", glassName: "Single" },];
//Act //Act
vehicleDamage.beforeRouteEnter.call( vehicleDamage.beforeRouteEnter.call(

View file

@ -314,25 +314,25 @@ export default {
const selectedGlassToReplace = []; const selectedGlassToReplace = [];
if (this.isWindshieldDamageLocation && !this.isWindshieldRepair){ if (this.isWindshieldDamageLocation && !this.isWindshieldRepair){
this.selectedWindshieldOptions.selectedWindshieldReplaceOptions.forEach(wsItem => { this.selectedWindshieldOptions.selectedWindshieldReplaceOptions.forEach(wsItem => {
selectedGlassToReplace.push({ location: damageLocationsSelected.WINDSHIELD, name: wsItem}); selectedGlassToReplace.push({ glassLocation: damageLocationsSelected.WINDSHIELD, glassName: wsItem});
}) })
} }
if (this.isDriverSideReplace){ if (this.isDriverSideReplace){
this.sideDoorOptionsData.selectedDriverSideReplaceOptions.forEach(driverItem => { this.sideDoorOptionsData.selectedDriverSideReplaceOptions.forEach(driverItem => {
selectedGlassToReplace.push({ location: damageLocationsSelected.DRIVER, name: driverItem}); selectedGlassToReplace.push({ glassLocation: damageLocationsSelected.DRIVER, glassName: driverItem});
}) })
} }
if (this.isPassengerSideReplace){ if (this.isPassengerSideReplace){
this.sideDoorOptionsData.selectedPassengerSideReplaceOptions.forEach(passengerItem => { this.sideDoorOptionsData.selectedPassengerSideReplaceOptions.forEach(passengerItem => {
selectedGlassToReplace.push({ location: damageLocationsSelected.PASSENGER, name: passengerItem}); selectedGlassToReplace.push({ glassLocation: damageLocationsSelected.PASSENGER, glassName: passengerItem});
}) })
} }
if (this.isRearWindowDamageLocation) { if (this.isRearWindowDamageLocation) {
this.selectedRearReplaceOptions.forEach(rearItem => { this.selectedRearReplaceOptions.forEach(rearItem => {
selectedGlassToReplace.push({ location: damageLocationsSelected.REAR, name: rearItem}); selectedGlassToReplace.push({ glassLocation: damageLocationsSelected.REAR, glassName: rearItem});
}) })
} }

View file

@ -7,7 +7,6 @@
<funnelSubHeader <funnelSubHeader
cmsWidgetName="FunnelSubHeaderWidget" cmsWidgetName="FunnelSubHeaderWidget"
:hasBackButton="true" :hasBackButton="true"
backButtonAccessibleText="Change Vehicle Year"
@click-event="backButtonAction" @click-event="backButtonAction"
/> />
<div class="fade-on-route-transition"> <div class="fade-on-route-transition">

View file

@ -7,7 +7,6 @@
<funnelSubHeader <funnelSubHeader
cmsWidgetName="FunnelSubHeaderWidget" cmsWidgetName="FunnelSubHeaderWidget"
:hasBackButton="true" :hasBackButton="true"
backButtonAccessibleText="Change Vehicle Make"
@click-event="backButtonAction" @click-event="backButtonAction"
/> />
<div class="fade-on-route-transition"> <div class="fade-on-route-transition">

View file

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

View file

@ -1,5 +1,11 @@
<template> <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" /> <funnelHeader ref="funnelHeader" cmsWidgetName="FunnelHeaderWidget" />
<vehicleBanner ref="vehicleBanner" cmsWidgetName="VehicleBannerWidget" :displayGenericVehicleImage="false" /> <vehicleBanner ref="vehicleBanner" cmsWidgetName="VehicleBannerWidget" :displayGenericVehicleImage="false" />
<funnelSubHeader ref="funnelSubHeader" cmsWidgetName="FunnelSubHeaderWidget" /> <funnelSubHeader ref="funnelSubHeader" cmsWidgetName="FunnelSubHeaderWidget" />
@ -8,10 +14,10 @@
<div class="row"> <div class="row">
<div class="col"> <div class="col">
<alert <alert
class="rounded border-0 shadow-sm" class="rounded border-0 shadow-sm"
alertClass="alert-warning" alertClass="alert-warning"
cmsWidgetName="AlertWidget" cmsWidgetName="AlertWidget"
:isDismissible="false" :isDismissible="false"
/> />
</div> </div>
</div> </div>
@ -24,16 +30,24 @@
</div> </div>
<glassPartQuestion <glassPartQuestion
:ref="`${RefPrefix}-${item.glassLocation}-${item.glassName}`" :ref="`${RefPrefix}-${item.glassLocation}-${item.glassName}`"
v-model="glassParts[item.glassLocation + '-' + item.glassName]" v-model="glassParts[item.glassLocation + '-' + item.glassName]"
:glassLocation="item.glassLocation" :glassLocation="item.glassLocation"
:glassName="item.glassName" :glassName="item.glassName"
:colorAnswers="item.colorAnswers" :colorAnswers="item.colorAnswers"
validationRules="replace-options-required"
/> />
</div> </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> </div>
</Form>
</template> </template>
<script> <script>
@ -50,132 +64,141 @@ import { settleAllPromises } from "@/helpers/layout-helper";
import { fmgPageValues } from "@/router/router-constants/fmgPage-values"; import { fmgPageValues } from "@/router/router-constants/fmgPage-values";
import { storeMutations } from "@/constants/store-mutations"; import { storeMutations } from "@/constants/store-mutations";
import store from "@/store"; 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 { export default {
name: "vehicle-parts", name: "vehicle-parts",
async beforeRouteEnter(to, from, next) { async beforeRouteEnter(to, from, next) {
// Call APIs // Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage); const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
// Settle promises and get results // Settle promises and get results
const promiseResultMap = [{ const promiseResultMap = [{
resultKey: "cmsContent", resultKey: "cmsContent",
promise: cmsContentPromise, promise: cmsContentPromise,
}, ]; }, ];
const resultMap = await settleAllPromises(promiseResultMap); const resultMap = await settleAllPromises(promiseResultMap);
// Call the "next" function to complete the transition to this page. // Call the "next" function to complete the transition to this page.
next((vm) => { next((vm) => {
vm.setCmsContent(resultMap.cmsContent); vm.setCmsContent(resultMap.cmsContent);
// Glass Part Question dynamic component // Glass Part Question dynamic component
Object.keys(vm.$refs) Object.keys(vm.$refs)
.filter((r) => r.includes(vm.RefPrefix) && vm.$refs[r][0] !== undefined) .filter((r) => r.includes(vm.RefPrefix) && vm.$refs[r][0] !== undefined)
.forEach((c) => .forEach((c) =>
vm.$refs[c][0].initializeComponent({ vm.$refs[c][0].initializeComponent({
ColorQuestionWidget: resultMap.cmsContent.ColorQuestionWidget, ColorQuestionWidget: resultMap.cmsContent.ColorQuestionWidget,
FeatureQuestionWidget: resultMap.cmsContent.FeatureQuestionWidget, 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;
}, []),
};
}); });
},
data() {
return {
glassParts: {},
alertWidgetData: Object,
};
},
components: {
glassPartQuestion,
funnelHeader,
vehicleBanner,
funnelSubHeader,
funnelFooter,
alert,
},
computed: {
PartsForQuestions() {
const partsData = this.PartsFromApi;
// Map API result data, to vehicle-parts data structure return mappedData;
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() { PartsFromApi() {
// Check if isRepair is populated and if the pageData we need is here (Parts data) return store.getters.pageData(fmgPageValues.VEHICLE_PARTS);
if ( },
(store.getters.damage.isRepair != null) &&
Object.keys(store.getters.pageData(fmgPageValues.VEHICLE_PARTS)) RefPrefix() {
.length !== 0 return "partQuestion";
) { },
return true; },
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]);
} }
}
return false; // Match them to the parts from the API.
}, for (let [key, value] of Object.entries(
backButtonAction() { this.PartsFromApi.partsOrQuestions
this.$router.navigate(this.navigationScenarios.CLICKED_BACK, this.$route); )) {
}, for (let [partKey, partValue] of Object.entries(value.parts)) {
forwardButtonAction() { const currentPart =
const selectedGlassPartNumbers = []; this.PartsFromApi.partsOrQuestions[key].parts[partKey];
const matchedParts = []; const isMatched = selectedGlassPartNumbers.some(
(p) => p === currentPart.partNumber
);
// Compile all selected parts from the page. if (isMatched) {
for (let [key, value] of Object.entries(this.glassParts)) { matchedParts.push(currentPart);
for (let [glassKey, glassValue] of Object.entries(value)) {
selectedGlassPartNumbers.push(glassValue[0]);
} }
} }
}
// Match them to the parts from the API. // If no parts could be matched, throw an error.
for (let [key, value] of Object.entries( if (matchedParts.length === 0) {
this.PartsFromApi.partsOrQuestions throw new Error("Could not match any parts to the selected parts");
)) { }
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);
// Save parts to the store. // Save parts to the store.
store.commit(storeMutations.UPDATE_PARTS, matchedParts); store.commit(storeMutations.UPDATE_PARTS, matchedParts);
@ -206,10 +229,11 @@ export default {
}); });
}); });
}); });
}, });
},
mounted() {
this.LoadInitialPartsData();
}, },
},
mounted() {
this.LoadInitialPartsData();
},
}; };
</script> </script>

View file

@ -7,7 +7,6 @@
<funnelSubHeader <funnelSubHeader
cmsWidgetName="FunnelSubHeaderWidget" cmsWidgetName="FunnelSubHeaderWidget"
:hasBackButton="true" :hasBackButton="true"
backButtonAccessibleText="Change Vehicle Model"
@click-event="backButtonAction" @click-event="backButtonAction"
/> />
<div class="fade-on-route-transition"> <div class="fade-on-route-transition">

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 vinLookup from "./vin-lookup.vue";
import { getMountOptions } from "@/helpers/unit-test-helper.js"; import { getMountOptions } from "@/helpers/unit-test-helper.js";
import { navigationScenarios } from "@/router/router-constants/navigation-scenarios.js";
import store from "@/store"; import store from "@/store";
jest.mock("@/store", () => ({ jest.mock("@/store", () => ({
commit: jest.fn(), commit: jest.fn(),
dispatch: jest.fn(), dispatch: jest.fn(),
getters: { getters: {
vehicle: { vehicle: {
year: 2019, year: 2019,
carId: 'initial carId' carId: 'initial carId'
},
order: {
serviceLocation: {
zipCode: "45253"
}, },
customer: { order: {
emailAddress: "builddigitaltest@safelite.com" 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", () => ({ jest.mock("@/helpers/damage-helper", () => ({
isGlassAvailableForCarId: jest.fn(() => { isGlassAvailableForCarId: jest.fn(() => {
@ -45,41 +46,21 @@ jest.mock("@/helpers/damage-helper", () => ({
describe("vin-lookup.vue", () => { describe("vin-lookup.vue", () => {
it("Should update the funnel-footer forward button when VIN is changed", (done) => { it("Should update the funnel-footer forward button when VIN is changed", (done) => {
//Arrange //Arrange
const { wrapper } = setupMocks({ }); const { wrapper } = setupMocks({});
wrapper.vm.$refs.funnelFooter.updateButtonText = jest.fn();
//Act //Act
wrapper.setData({vin: "newValue"}); wrapper.setData({ vin: "newValue" });
//Assert //Assert
wrapper.vm.$nextTick(() => { wrapper.vm.$nextTick(() => {
expect(wrapper.vm.$refs.funnelFooter.updateButtonText).toBeCalled(); expect(wrapper.vm.$refs.funnelFooter.updateButtonText).toBeCalled();
done(); done();
}); });
}); });
it("Should call navigateForward() if the store carId matches the vin response carId and forward button is clicked", async () => { it("Should call navigateForward() if the store carId matches the vin response carId and forward button is clicked", async () => {
// Arrange // Arrange
const { wrapper } = setupMocks({ }); const { wrapper } = setupMocks({});
const zipValidationApiResponse = { wrapper.vm.navigateForward = jest.fn();
data: {
isServiceable: true
}
};
const vehicleLookupApiResponse = {
data: {
carId: 'initial carId'
}
};
const zipPromise = Promise.resolve(zipValidationApiResponse);
const vinPromise = Promise.resolve(vehicleLookupApiResponse);
wrapper.vm.validateZip = jest.fn().mockImplementation(() => zipPromise);
wrapper.vm.lookupVehicle = jest.fn().mockImplementation(() => vinPromise);
wrapper.vm.$refs.funnelFooter.updateButtonText = jest.fn();
wrapper.vm.$refs.funnelFooter.removeLoader = jest.fn();
wrapper.vm.navigateForward = jest.fn();
// Act // Act
await wrapper.vm.forwardButtonAction(); await wrapper.vm.forwardButtonAction();
@ -87,26 +68,231 @@ describe("vin-lookup.vue", () => {
//Assert //Assert
expect(wrapper.vm.navigateForward).toHaveBeenCalled(); expect(wrapper.vm.navigateForward).toHaveBeenCalled();
}); });
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({});
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);
// 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();
//Assert
expect(wrapper.vm.navigateForward).not.toHaveBeenCalled();
});
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 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.previouslyEnteredCarId = 'new carId';
// Act
await wrapper.vm.forwardButtonAction();
//Assert
expect(wrapper.vm.navigateForward).toHaveBeenCalled();
});
it("Should not call navigateForward() if zip service returns a non-serviceable flag", async () => {
// Arrange
const { wrapper } = setupMocks({});
const zipValidationApiResponse = {
data: {
isServiceable: false
}
};
const zipPromise = Promise.resolve(zipValidationApiResponse);
wrapper.vm.validateZip = jest.fn().mockImplementation(() => zipPromise);
wrapper.vm.setupUiForNonServiceableZip = jest.fn();
wrapper.vm.navigateForward = jest.fn();
wrapper.vm.previouslyEnteredCarId = 'new carId';
// Act
await wrapper.vm.forwardButtonAction();
//Assert
expect(wrapper.vm.navigateForward).not.toHaveBeenCalled();
});
it("Should not call navigateForward() when forward button is clicked but lookupVehicle errors out.", async () => {
// Arrange
const { wrapper } = setupMocks({});
wrapper.vm.vinTouched = true;
wrapper.vm.vin = "foo";
wrapper.vm.initialVin = "!foo";
const vehicleLookupApiResponse = {
status: {
carId: 'new carId' // does not match the store value
}
};
const vinPromise = Promise.reject(vehicleLookupApiResponse);
const response = {
status: 404
};
wrapper.vm.lookupVehicle = jest.fn().mockImplementation((response) => vinPromise);
wrapper.vm.navigateForward = jest.fn();
wrapper.vm.previouslyEnteredCarId = 'new carId';
// Act
await wrapper.vm.forwardButtonAction();
//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 }) { function setupMocks({ customMountOptions }) {
const mountOptions = getMountOptions({
...customMountOptions
});
const mountOptions = getMountOptions({});
const finalMountOptions = Object.assign(mountOptions, customMountOptions);
// Modify/augment default mount options // Modify/augment default mount options
finalMountOptions.global.mocks["$store"] = store; mountOptions.global.mocks["$store"] = store;
finalMountOptions.global.mixins = [mockMixin]; mountOptions.global.mixins = [mockMixin];
finalMountOptions['attachTo'] = document.body; // append wrapper to document.body to test DOM methods mountOptions['attachTo'] = document.body; // append wrapper to document.body to test DOM methods
const wrapper = shallowMount(vinLookup, finalMountOptions);
return { wrapper };
}
const mockMixin = { const wrapper = shallowMount(vinLookup, mountOptions);
mockOutPromises(wrapper);
mockOutStubFunctions(wrapper);
return { wrapper };
}
function mockOutPromises(wrapper) {
const zipValidationApiResponse = {
data: {
isServiceable: true
}
};
const vehicleLookupApiResponse = {
data: {
carId: 'initial carId'
}
};
const zipPromise = Promise.resolve(zipValidationApiResponse);
const vinPromise = Promise.resolve(vehicleLookupApiResponse);
wrapper.vm.validateZip = jest.fn().mockImplementation(() => zipPromise);
wrapper.vm.lookupVehicle = jest.fn().mockImplementation(() => vinPromise);
}
function mockOutStubFunctions(wrapper) {
wrapper.vm.$refs.funnelFooter.updateButtonText = jest.fn();
wrapper.vm.$refs.funnelFooter.removeLoader = jest.fn();
}
const mockMixin = {
methods: { 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"> <div class="page-container-grouped-styles">
<loadingModal ref="loadingModal"/> <loadingModal ref="loadingModal"/>
<funnelHeader cmsWidgetName="FunnelHeaderWidget" /> <funnelHeader cmsWidgetName="FunnelHeaderWidget" />
<vehicleBanner <vehicleBanner
cmsWidgetName="VehicleBannerWidget" cmsWidgetName="VehicleBannerWidget"
:displayGenericVehicleImage="false" :displayGenericVehicleImage="false"
/> />
@ -25,6 +25,9 @@
validationRules="vin-required|vin-format" validationRules="vin-required|vin-format"
:isDisabled="isVinFieldReadOnly" :isDisabled="isVinFieldReadOnly"
maxLength="17" maxLength="17"
:mask="vinMask"
@focus="setVinTouched"
@maska="rawVinValue = $event.target.dataset.maskRawValue"
/> />
</div> </div>
</div> </div>
@ -55,7 +58,6 @@
isRequired isRequired
disableAutoFill disableAutoFill
validationRules="email-address-required|email-address-format" validationRules="email-address-required|email-address-format"
semiAggressiveValidation
/> />
</div> </div>
</div> </div>
@ -132,7 +134,6 @@ import loadingModal from '@/common-components/loading-modal/loading-modal.vue';
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper"; import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import { settleAllPromises } from "@/helpers/layout-helper"; import { settleAllPromises } from "@/helpers/layout-helper";
import store from "@/store"; import store from "@/store";
import baseMixin from "@/mixins/base-mixin.js";
import { storeActions } from "@/constants/store-actions"; import { storeActions } from "@/constants/store-actions";
import { storeMutations } from "@/constants/store-mutations"; import { storeMutations } from "@/constants/store-mutations";
import { errorMessages } from "@/constants/error-messages"; import { errorMessages } from "@/constants/error-messages";
@ -141,6 +142,8 @@ import { required, regex } from "@/helpers/validation-rules";
import { Form, defineRule } from "vee-validate"; import { Form, defineRule } from "vee-validate";
import { navigateAfterSaveToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper"; import { navigateAfterSaveToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper";
import { getFunnelCookie } from "@/helpers/heritage-integration/cookie-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 // DEFINE VALIDATION RULES
defineRule("zip-required", required(errorMessages.SERVICE_ZIP_REQUIRED)); 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 { export default {
name: "vin-lookup", name: "vin-lookup",
mixins: [vinPagesMixin],
async beforeRouteEnter(to, from, next) { async beforeRouteEnter(to, from, next) {
// Call APIs // Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage); const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
@ -183,10 +187,16 @@ export default {
previouslyEnteredCarId: '', previouslyEnteredCarId: '',
invalidZip: '', invalidZip: '',
vinPopulatedOnPageLoad: this.getVinFromStore()?.length > 0, vinPopulatedOnPageLoad: this.getVinFromStore()?.length > 0,
initialVin: this.getVinFromStore(),
vinTouched: false,
rawVinValue: "",
}; };
}, },
mounted() { mounted() {
this.attachCustomEvents(); this.attachCustomEvents();
if (this.vinPopulatedOnPageLoad) {
this.setupVinMask();
}
}, },
watch: { watch: {
vin() { vin() {
@ -194,13 +204,17 @@ export default {
this.$refs.funnelFooter.updateButtonText( this.$refs.funnelFooter.updateButtonText(
this.getCmsContent("FunnelFooterWidget", "ForwardButtonText") this.getCmsContent("FunnelFooterWidget", "ForwardButtonText")
); );
} },
zip() {
this.noServiceZip = false;
},
}, },
computed: { computed: {
perfectMatchNewVinAlert() { perfectMatchNewVinAlert() {
const isVinPerfectMatch = this.vinPopulatedOnPageLoad && this.vin === this.getVinFromStore(); const vinToCheck = this.vinTouched ? this.rawVinValue : this.initialVin;
this.updateIsCarIdDifferent(isVinPerfectMatch); const isVinPerfectMatch = this.vinPopulatedOnPageLoad && vinToCheck === this.getVinFromStore();
return isVinPerfectMatch; this.updateIsCarIdDifferent(isVinPerfectMatch);
return isVinPerfectMatch;
}, },
MatchedDifferentVehicleAlertHeader(){ MatchedDifferentVehicleAlertHeader(){
const text = this.getCmsContent("MatchedDifferentVehicle", const text = this.getCmsContent("MatchedDifferentVehicle",
@ -238,10 +252,22 @@ export default {
getIsWindshieldOnly()) getIsWindshieldOnly())
}, },
isVinFieldReadOnly(){ isVinFieldReadOnly(){
return this.$store.getters.payment.insuranceCoverage.isVerified || getFunnelCookie().HasDelayedClaimRegistration; return this.$store.getters.payment.insuranceCoverage.isVerified || getFunnelCookie().HasDelayedClaimRegistration;
}, },
}, },
methods: { 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() { arePagePrerequisitesValid() {
return store.getters.vehicle.carId !== null; return store.getters.vehicle.carId !== null;
}, },
@ -285,22 +311,51 @@ export default {
} }
}, },
async forwardButtonAction() { async forwardButtonAction() {
let zipValidationResponse;
let vehicleLookupResponse;
const zipValidation = this.validateZip(this.zip); const zipValidation = this.validateZip(this.zip);
const vehicleLookup = this.lookupVehicle(this.vin);
const zipValidationResponse = await zipValidation; if (this.vinTouched && this.vin != this.initialVin) {
const vehicleLookupResponse = await vehicleLookup.catch(() => { // If the user has clicked on the VIN field, either they are doing a new VIN lookup or changing the VIN previously matched.
this.vinNotFound = true; // Therefore we need to do a Vehicle Lookup
this.$refs.funnelFooter.removeLoader(); const vinToLookup = this.vinTouched ? this.vin : this.initialVin;
this.noServiceZip = false; const vehicleLookup = this.lookupVehicle(vinToLookup);
return;
}); zipValidationResponse = await zipValidation;
if (!zipValidationResponse.data.isServiceable) { vehicleLookupResponse = await vehicleLookup.catch(() => {
this.customAlertData.zip = this.zip; this.vinNotFound = true;
this.$refs.funnelFooter.removeLoader(); this.$refs.funnelFooter.removeLoader();
this.noServiceZip = true; return false;
this.invalidZip = this.zip; });
// Check if Service Zip entered is servicable, if not display an alert
if (!zipValidationResponse.data.isServiceable) {
this.setupUiForNonServiceableZip(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.setupUiForNonServiceableZip(this.zip);
this.$refs.funnelFooter.removeLoader();
return;
} else {
this.navigateForward();
return;
}
}
if (!vehicleLookupResponse) {
return; return;
} }
this.isCarIdDifferent = vehicleLookupResponse.data.carId !== store.getters.vehicle.carId; this.isCarIdDifferent = vehicleLookupResponse.data.carId !== store.getters.vehicle.carId;
if (this.isCarIdDifferent && (vehicleLookupResponse.data.carId !== this.previouslyEnteredCarId)) { if (this.isCarIdDifferent && (vehicleLookupResponse.data.carId !== this.previouslyEnteredCarId)) {
@ -314,26 +369,27 @@ export default {
this.isCarIdDifferent = true; this.isCarIdDifferent = true;
return; return;
} }
this.updateStore(vehicleLookupResponse.data, zipValidationResponse.data); this.updateStore(vehicleLookupResponse.data, zipValidationResponse.data);
this.navigateForward(); this.navigateForward();
}, },
navigateForward(){ navigateForward(){
if(this.isCarIdDifferent && !this.isSelectedGlassAvailableForVehicle){ if (this.isCarIdDifferent && !this.isSelectedGlassAvailableForVehicle) {
this.$router.navigate(this.navigationScenarios.CLICKED_FORWARD, this.$route, {}, { displayVehicleChangeAlert: true }, {}); this.$router.navigateAfterSave(this.navigationScenarios.SELECTED_VIN_HAS_MISMATCHED_GLASS, this.$route, {}, { displayVehicleChangeAlert: true }, {});
return; return;
} else { } else {
this.$refs.loadingModal.showModal(); this.navigateForwardWithSingleCarMatch();
navigateAfterSaveToHeritageFunnel(this.$route);
return; return;
} }
}, },
validateZip(zip) { validateZip(zip) {
return baseMixin.methods.dispatchStoreAction(storeActions.VALIDATE_ZIP, { return this.dispatchStoreAction(storeActions.VALIDATE_ZIP, {
zip, zip,
}); });
}, },
lookupVehicle(vin) { lookupVehicle(vin) {
return baseMixin.methods.dispatchStoreAction( return this.dispatchStoreAction(
storeActions.LOOKUP_VEHICLE_BY_VIN, storeActions.LOOKUP_VEHICLE_BY_VIN,
{ vin } { vin }
); );
@ -356,6 +412,11 @@ export default {
store.commit(storeMutations.UPDATE_SERVICE_LOCATION_STATE, zipInfo.state); store.commit(storeMutations.UPDATE_SERVICE_LOCATION_STATE, zipInfo.state);
store.commit(storeMutations.UPDATE_CUSTOMER_EMAIL_ADDRESS, this.email); store.commit(storeMutations.UPDATE_CUSTOMER_EMAIL_ADDRESS, this.email);
}, },
setupUiForNonServiceableZip(zip) {
this.customAlertData.zip = zip;
this.invalidZip = zip;
this.noServiceZip = true;
},
}, },
components: { components: {
Form, Form,

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

@ -18,22 +18,7 @@ import eventBus from "@/helpers/event-bus/event-bus";
import store from "@/store"; import store from "@/store";
import analyticsMixin from "@/mixins/analytics-mixin"; import analyticsMixin from "@/mixins/analytics-mixin";
// Components
import ComponentTest from "@/layouts/component-test/component-test.vue";
import FormTest from "@/layouts/form-test/form-test.vue";
const routes = [ const routes = [
{
path: "/component-test", // This is a temporary route for testing.
name: "ComponentTest",
component: ComponentTest,
},
{
path: "/form-test", // This is a temporary route for testing.
name: "FormTest",
component: FormTest,
},
{ {
path: "/", path: "/",
name: "root", name: "root",
@ -46,6 +31,7 @@ const routes = [
// If the saved session has timed out, clear the session, execute 404 logic. // If the saved session has timed out, clear the session, execute 404 logic.
if (getFunnelCookie() !== null && !isSavedSessionStillActive()) { if (getFunnelCookie() !== null && !isSavedSessionStillActive()) {
await baseMixin.methods.dispatchStoreAction(storeActions.RESET_STATE);
await GoToFunnelStartOn404(next); await GoToFunnelStartOn404(next);
} }

View file

@ -8,11 +8,11 @@ const navigationScenarios = {
CLICKED_FORWARD: "CLICKED_FORWARD", CLICKED_FORWARD: "CLICKED_FORWARD",
CLICKED_FORWARD_WITH_VIN: "CLICKED_FORWARD_WITH_VIN", CLICKED_FORWARD_WITH_VIN: "CLICKED_FORWARD_WITH_VIN",
SELECTED_PARTS: "SELECTED_PARTS", SELECTED_PARTS: "SELECTED_PARTS",
CONTINUING_WITH_PARTS_QUESTION: "CONTINUING_WITH_PARTS_QUESTION", SELECTED_VIN_WITH_PART_QUESTIONS: "SELECTED_VIN_WITH_PART_QUESTIONS",
CONTINUING_WITH_MULTIPLE_PARTS: "CONTINUING_WITH_MULTIPLE_PARTS", SELECTED_VIN_WITH_MULTIPLE_PARTS: "SELECTED_VIN_WITH_MULTIPLE_PARTS",
CONTINUING_WITH_SINGLE_PART: "CONTINUING_WITH_SINGLE_PART", CONTINUING_WITH_SINGLE_PART: "CONTINUING_WITH_SINGLE_PART",
CONTINUING_WITH_MULTIPLE_VEHICLES: "CONTINUING_WITH_MULTIPLE_VEHICLES", 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", CLICKED_FORWARD_WITHOUT_VIN: "CLICKED_FORWARD_WITHOUT_VIN",
SELECTED_MANUAL_VIN: "SELECTED_MANUAL_VIN", SELECTED_MANUAL_VIN: "SELECTED_MANUAL_VIN",
SELECTED_LICENSE_PLATE: "SELECTED_LICENSE_PLATE", SELECTED_LICENSE_PLATE: "SELECTED_LICENSE_PLATE",

View file

@ -72,7 +72,7 @@ const routingTable = [
maps: [ maps: [
{ {
scenario: navigationScenarios.CLICKED_BACK, scenario: navigationScenarios.CLICKED_BACK,
destinationFmgPageValue: fmgPageValues.VEHICLE_DAMAGE, destinationFmgPageValue: fmgPageValues.VIN_LOOKUP,
}, },
{ {
scenario: navigationScenarios.SELECTED_PARTS, scenario: navigationScenarios.SELECTED_PARTS,
@ -105,12 +105,20 @@ const routingTable = [
destinationFmgPageValue: fmgPageValues.ESTIMATE, destinationFmgPageValue: fmgPageValues.ESTIMATE,
}, },
{ {
scenario: navigationScenarios.CLICKED_FORWARD, scenario: navigationScenarios.SELECTED_VIN_HAS_MISMATCHED_GLASS,
destinationFmgPageValue: fmgPageValues.VEHICLE_DAMAGE, destinationFmgPageValue: fmgPageValues.VEHICLE_DAMAGE,
}, },
{ {
scenario: navigationScenarios.CLICKED_BACK_WITH_VIN, scenario: navigationScenarios.CLICKED_BACK_WITH_VIN,
destinationFmgPageValue: fmgPageValues.VEHICLE_DAMAGE, 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, scenario: navigationScenarios.CLICKED_FORWARD,
destinationFmgPageValue: fmgPageValues.VEHICLE_DAMAGE, 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, destinationFmgPageValue: fmgPageValues.ADDRESS_VEHICLES,
}, },
{ {
scenario: navigationScenarios.CONTINUING_WITH_DIFFERENT_GLASS, scenario: navigationScenarios.SELECTED_VIN_HAS_MISMATCHED_GLASS,
destinationFmgPageValue: fmgPageValues.VEHICLE_DAMAGE, 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, scenario: navigationScenarios.CLICKED_FORWARD,
destinationFmgPageValue: fmgPageValues.VEHICLE_DAMAGE, 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 }; export { routingTable };

View file

@ -115,7 +115,7 @@ export const mutations = {
updateGlassToReplace(state, glassToReplace) { updateGlassToReplace(state, glassToReplace) {
state.order.damage.glassToReplace = glassToReplace; state.order.damage.glassToReplace = glassToReplace;
}, },
updateParts(state, partsData) { updateGlassParts(state, partsData) {
state.order.lineItems.glassParts = partsData; state.order.lineItems.glassParts = partsData;
}, },
updatePageData(state, pageData) { updatePageData(state, pageData) {
@ -216,9 +216,8 @@ export const mutations = {
state.order.vehicle.registration.firstName = null; state.order.vehicle.registration.firstName = null;
state.order.vehicle.registration.lastName = null; state.order.vehicle.registration.lastName = null;
}, },
resetPartsState(state) { resetGlassPartsState(state) {
state.order.lineItems.glassParts = null; state.order.lineItems.glassParts = null;
state.order.lineItems.otherParts = null;
}, },
resetState(state) { resetState(state) {
Object.assign(state, getDefaultState()); Object.assign(state, getDefaultState());
@ -404,14 +403,14 @@ export const actions = {
}, },
resetDamageAndDependencies(context) { resetDamageAndDependencies(context) {
context.commit(storeMutations.RESET_DAMAGE_STATE); context.commit(storeMutations.RESET_DAMAGE_STATE);
context.commit(storeMutations.RESET_PARTS_STATE); context.commit(storeMutations.RESET_GLASS_PARTS_STATE);
}, },
resetRegistrationAndDependencies(context) { resetRegistrationAndDependencies(context) {
context.commit(storeMutations.RESET_REGISTRATION_STATE); context.commit(storeMutations.RESET_REGISTRATION_STATE);
context.commit(storeMutations.RESET_PARTS_STATE) context.commit(storeMutations.RESET_GLASS_PARTS_STATE)
}, },
resetPartsAndDependencies(context) { resetPartsAndDependencies(context) {
context.commit(storeMutations.RESET_PARTS_STATE); context.commit(storeMutations.RESET_GLASS_PARTS_STATE);
}, },
resetState(context) { resetState(context) {
context.commit(storeMutations.RESET_STATE); context.commit(storeMutations.RESET_STATE);
@ -543,7 +542,16 @@ export const actions = {
// Parts API 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({ return globalMethods.callHttpClient({
method: endpoints.GetPartsOrQuestions.method, method: endpoints.GetPartsOrQuestions.method,
endpoint: endpoints.GetPartsOrQuestions.url, endpoint: endpoints.GetPartsOrQuestions.url,

View file

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

View file

@ -78,6 +78,10 @@ export default {
validationRules: String, validationRules: String,
selectedValues: [Array, String], selectedValues: [Array, String],
hasError: Boolean, hasError: Boolean,
clearOnUnmount: {
type: Boolean,
default: true
}
}, },
data() { data() {
return { return {
@ -92,9 +96,11 @@ export default {
: this.selectedValues[0]; : this.selectedValues[0];
} }
}, },
unmounted() { // needed to clear this button's selectedValues if it is removed unmounted() { // needed to clear this button's selectedValues if it is removed to keep validation in sync
this.checkValue = false; if (this.clearOnUnmount) {
this.handleCheckChange(); this.checkValue = false;
this.handleCheckChange();
}
}, },
methods: { methods: {
displayLoader() { displayLoader() {

View file

@ -78,6 +78,10 @@ export default {
validationRules: String, validationRules: String,
selectedValues: [Array, String], selectedValues: [Array, String],
hasError: Boolean, hasError: Boolean,
clearOnUnmount: {
type: Boolean,
default: true
}
}, },
data() { data() {
return { return {
@ -92,9 +96,11 @@ export default {
: this.selectedValues[0]; : this.selectedValues[0];
} }
}, },
unmounted() { // needed to clear this button's selectedValues if it is removed unmounted() { // needed to clear this button's selectedValues if it is removed to keep validation in sync
this.checkValue = false; if (this.clearOnUnmount) {
this.handleCheckChange(); this.checkValue = false;
this.handleCheckChange();
}
}, },
methods: { methods: {
displayLoader() { displayLoader() {

View file

@ -86,6 +86,10 @@ export default {
selectedValues: [Array, String], selectedValues: [Array, String],
modelValue: Object, modelValue: Object,
hasError: Boolean, hasError: Boolean,
clearOnUnmount: {
type: Boolean,
default: true
}
}, },
data() { data() {
return { return {
@ -99,9 +103,11 @@ export default {
: this.selectedValues[0]; : this.selectedValues[0];
} }
}, },
unmounted() { // needed to clear this button's selectedValues if it is removed unmounted() { // needed to clear this button's selectedValues if it is removed to keep validation in sync
this.checkValue = false; if (this.clearOnUnmount) {
this.handleCheckChange(); this.checkValue = false;
this.handleCheckChange();
}
}, },
computed: { computed: {
getLabelClasses() { getLabelClasses() {