Merge branch 'develop' into feature/CSR-439

This commit is contained in:
DonielleAtSafelite 2022-06-24 14:43:47 -04:00 committed by GitHub
commit 159aadb2b4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
45 changed files with 2381 additions and 2313 deletions

View file

@ -11,19 +11,9 @@ module.exports = {
"!src/constants/*.js",
"!src/router/**/*.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/part-questions/**/*.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",
"!src/layouts/reveal/**/*.vue"
// END
], // ! means exclude from coverage.
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({}));
await wrapper.setProps({
answers: ["2022", "2021", "2020"],
isMultiSelect: false
isMultiSelect: false,
modelValue: []
});
const val = { checkValue: true, value: "2021", }
wrapper.vm.handleCheckedChanged(val);

View file

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

View file

@ -29,20 +29,22 @@ export default {
name: "FunnelSubHeader",
props: {
hasBackButton: Boolean,
backButtonAccessibleText: String,
cmsWidgetName: String,
},
components: {
buttonBack,
},
computed: {
text(){
text() {
return this.getCmsContent(this.cmsWidgetName, 'HeaderText');
},
subText(){
subText() {
return this.getCmsContent(this.cmsWidgetName, 'HeaderSubText');
},
headerColor(){
backButtonAccessibleText() {
return this.getCmsContent(this.cmsWidgetName, 'BackButtonAccessibleText');
},
headerColor() {
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
const wrapper = shallowMount(textboxQuestion, {
global: {
@ -178,7 +178,6 @@ describe("textboxQuestion.vue", () => {
propsData: {
options: {},
modelValue: "foo",
semiAggressiveValidation: true,
},
mixins: [mockMixin]
});

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -59,25 +59,25 @@ import loadingModal from '@/common-components/loading-modal/loading-modal.vue';
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import { settleAllPromises } from "@/helpers/layout-helper";
import store from "@/store";
import baseMixin from "@/mixins/base-mixin.js";
import { storeActions } from "@/constants/store-actions";
import { storeMutations } from "@/constants/store-mutations";
import { fmgPageValues } from "@/router/router-constants/fmgPage-values";
import { errorMessages } from "@/constants/error-messages";
import { required } from "@/helpers/validation-rules";
import { Form, defineRule } from "vee-validate";
import { navigateAfterSaveToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper";
import { isGlassAvailableForCarId } from "@/helpers/damage-helper";
import { doesCopyContainRouterLink,
splitCopyOnCMSPlaceHolder,
getRouterLinkRouteFromCopy,
getRouterLinkDisplayTextFromCopy, } from "@/helpers/cms-content-helper"
getRouterLinkDisplayTextFromCopy, } from "@/helpers/cms-content-helper";
import vinPagesMixin from "@/mixins/vin-pages-mixin";
// DEFINE VALIDATION RULES
defineRule("vehicle-required", required(errorMessages.VEHICLE_REQUIRED));
export default {
name: "address-vehicles",
mixins: [vinPagesMixin],
async beforeRouteEnter(to, from, next) {
// Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
@ -102,7 +102,7 @@ export default {
},
data() {
return {
selectedVehicleVin: null,
selectedVehicleVin: "",
isCarIdDifferent: false,
isSelectedGlassAvailableForVehicle: true,
};
@ -145,7 +145,7 @@ export default {
return store.getters.pageData(fmgPageValues.ADDRESS_VEHICLES);
},
selectedVehicle() {
return this.VehiclesForQuestions.find( ({ vin }) => vin === this.selectedVehicleVin[0] );
return this.VehiclesForQuestions.find( ({ vin }) => vin === this.selectedVehicleVin );
},
},
methods: {
@ -188,13 +188,12 @@ export default {
);
return;
} else {
this.$refs.loadingModal.showModal();
navigateAfterSaveToHeritageFunnel(this.$route);
this.navigateForwardWithSingleCarMatch();
return;
}
},
lookupVin(vin) {
return baseMixin.methods.dispatchStoreAction(
return this.dispatchStoreAction(
storeActions.LOOKUP_VEHICLE_BY_VIN,
{ vin }
);
@ -205,7 +204,7 @@ export default {
//this.$store.dispatch(storeActions.SAVE_VIN, vin,vehicle),
updateCustomerInfo(vin, vehicle) {
if (this.isCarIdDifferent && !this.isSelectedGlassAvailableForVehicle) {
store.dispatch(storeActions.RESET_DAMAGE_STATE_AND_DEPENDENCIES);
this.dispatchStoreAction(storeActions.RESET_DAMAGE_STATE_AND_DEPENDENCIES);
}
store.commit(storeMutations.UPDATE_VEHICLE_VIN, vin);
store.commit(storeMutations.UPDATE_YEAR, vehicle.year);
@ -221,10 +220,13 @@ export default {
},
watch: {
selectedVehicleVin() {
// does this vehicle match the previously selected carId?
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}`);
selectedVehicleVin: {
handler() {
// does this vehicle match the previously selected carId?
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
import { settleAllPromises } from "@/helpers/layout-helper.js";
import * as navigateToHeritage from "@/helpers/heritage-integration/navigation-helper";
import baseMixin from "@/mixins/base-mixin";
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import { shallowMount } from "@vue/test-utils";
import { getMountOptions } from "@/helpers/unit-test-helper.js";
import { nextTick } from "vue";
import { storeActions } from "@/constants/store-actions";
import { storeMutations } from "@/constants/store-mutations";
import store from "@/store";
@ -223,19 +223,22 @@ describe("license-plate-lookup.vue", () => {
});
describe("navigateForward", () => {
test("NavigateAfterSave should be called if isCarIdDifferent is true and isSelectedGlassAvailableForVehicle is false when navigateForward is called", async () => {
test("navigateAfterSave should be called if isCarIdDifferent is true and isSelectedGlassAvailableForVehicle is false when navigateForward is called", async () => {
// Arrange
const { wrapper } = setupMocks({});
//Act
wrapper.vm.isCarIdDifferent = true;
wrapper.vm.isSelectedGlassAvailableForVehicle = false;
await wrapper.setData({
isCarIdDifferent: true,
isSelectedGlassAvailableForVehicle: false
})
wrapper.vm.$router.navigateAfterSave = jest.fn();
wrapper.vm.getCmsContent = jest.fn().mockImplementation(() => {
return '';
});
store.dispatch = jest.fn();
wrapper.vm.dispatchStoreAction = jest.fn();
await wrapper.vm.navigateForward();
@ -249,7 +252,9 @@ describe("license-plate-lookup.vue", () => {
const { wrapper } = setupMocks({});
//Act
wrapper.vm.isCarIdDifferent = false;
await wrapper.setData({
isCarIdDifferent: false
})
wrapper.vm.getCmsContent = jest.fn().mockImplementation(() => {
return '';
});
@ -259,6 +264,38 @@ describe("license-plate-lookup.vue", () => {
//Assert
expect(navigateToHeritage.navigateAfterSaveToHeritageFunnel).toHaveBeenCalled();
});
test("carId matches returned vehicle => navigateForwardWithSingleCarMatch", async () => {
// Arrange
const { wrapper } = setupMocks({});
await wrapper.setData({
isCarIdDifferent: false
})
wrapper.vm.navigateForwardWithSingleCarMatch = jest.fn();
// Act
await wrapper.vm.navigateForward();
//Assert
expect(wrapper.vm.navigateForwardWithSingleCarMatch).toHaveBeenCalledTimes(1);
});
test("selected glass is available for returned vehicle => navigateForwardWithSingleCarMatch", async () => {
// Arrange
const { wrapper } = setupMocks({});
await wrapper.setData({
isSelectedGlassAvailableForVehicle: true
})
wrapper.vm.navigateForwardWithSingleCarMatch = jest.fn();
// Act
await wrapper.vm.navigateForward();
//Assert
expect(wrapper.vm.navigateForwardWithSingleCarMatch).toHaveBeenCalledTimes(1);
});
});
});
@ -269,7 +306,9 @@ describe("license-plate-lookup.vue", () => {
const { wrapper } = setupMocks({});
//Act
wrapper.vm.licensePlate = "NEWPLATE";
wrapper.setData({
licensePlate: "NEWPLATE"
})
wrapper.vm.getCmsContent = jest.fn();
await wrapper.vm.$nextTick();
@ -283,7 +322,9 @@ describe("license-plate-lookup.vue", () => {
const { wrapper } = setupMocks({});
//Act
wrapper.vm.registrationZip = "55555";
await wrapper.setData({
registrationZip: "55555"
})
wrapper.vm.getCmsContent = jest.fn();
await wrapper.vm.$nextTick();
@ -297,7 +338,9 @@ describe("license-plate-lookup.vue", () => {
const { wrapper } = setupMocks({});
//Act
wrapper.vm.serviceZip = "55555";
await wrapper.setData({
serviceZip: "55555"
})
wrapper.vm.getCmsContent = jest.fn();
await wrapper.vm.$nextTick();
@ -469,22 +512,23 @@ describe("license-plate-lookup.vue", () => {
const { wrapper } = setupMocks({});
//Act
wrapper.vm.isCarIdDifferent = true;
wrapper.vm.isSelectedGlassAvailableForVehicle = false;
await wrapper.setData({
isCarIdDifferent: true,
isSelectedGlassAvailableForVehicle: false
})
wrapper.vm.getCmsContent = jest.fn().mockImplementation(() => {
return '';
});
store.commit = jest.fn();
store.dispatch = jest.fn();
const vehicleInfo = { year: "2020", make: "honda", model: "civic", style: "2 door", carId: "TestId", category: "testCat", imageUrl: "image.jpg", imageVifNumber: "123", imageColor: "blue" }
await wrapper.vm.updateCustomerInfo('vin', vehicleInfo, 'registrationState');
//Assert
expect(store.dispatch).toHaveBeenCalled();
expect(wrapper.vm.dispatchStoreAction).toHaveBeenCalled();
})
test("dispatch non blocking store action called on validate zip", async () => {
test("dispatchStoreAction called on validate zip", async () => {
// Arrange
const { wrapper } = setupMocks({});
@ -494,10 +538,10 @@ describe("license-plate-lookup.vue", () => {
//Assert
expect(baseMixin.methods.dispatchStoreAction).toHaveBeenCalled();
expect(wrapper.vm.dispatchStoreAction).toHaveBeenCalled();
});
test("dispatch non blocking store action called on lookup vin", async () => {
test("dispatchStoreAction called on lookup vin", async () => {
// Arrange
const { wrapper } = setupMocks({});
@ -507,22 +551,18 @@ describe("license-plate-lookup.vue", () => {
//Assert
expect(baseMixin.methods.dispatchStoreAction).toHaveBeenCalled();
expect(wrapper.vm.dispatchStoreAction).toHaveBeenCalled();
});
})
});
function setupMocks({
pageHeaderWidgetHeaderText = {},
mountOptionsMockData = {
router: {
navigate: jest.fn(),
},
},
mountOptionsMockData = {},
partsOrQuestions = []
}) {
store.commit(storeMutations.RESET_STATE);
//Mock api responses
baseMixin.methods.dispatchStoreAction = jest.fn();
const apiResponses = {
cmsContent: {
FunnelSubHeaderWidget: pageHeaderWidgetHeaderText,
@ -537,6 +577,21 @@ function setupMocks({
},
};
mountOptionsMockData = {
...mountOptionsMockData,
router: {
navigate: jest.fn(),
},
actionList: [
{
actionName: storeActions.GET_PARTS_OR_QUESTIONS,
data: {
partsOrQuestions: partsOrQuestions
}
},
]
}
const apiPromise = Promise.resolve(apiResponses);
settleAllPromises.mockImplementation(() => apiPromise);
@ -547,7 +602,7 @@ function setupMocks({
const wrapper = shallowMount(licensePlateLookup, mountOptions);
wrapper.vm.setCmsContent = baseMixin.methods.setCmsContent;
wrapper.vm.setCmsContent = jest.fn();
wrapper.vm.getCmsContent = jest.fn().mockImplementation(() => "");
wrapper.vm.$refs.funnelFooter.updateButtonText = jest.fn();
wrapper.vm.$refs.funnelFooter.removeLoader = jest.fn();

View file

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

View file

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

View file

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

View file

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

View file

@ -314,25 +314,25 @@ export default {
const selectedGlassToReplace = [];
if (this.isWindshieldDamageLocation && !this.isWindshieldRepair){
this.selectedWindshieldOptions.selectedWindshieldReplaceOptions.forEach(wsItem => {
selectedGlassToReplace.push({ location: damageLocationsSelected.WINDSHIELD, name: wsItem});
selectedGlassToReplace.push({ glassLocation: damageLocationsSelected.WINDSHIELD, glassName: wsItem});
})
}
if (this.isDriverSideReplace){
this.sideDoorOptionsData.selectedDriverSideReplaceOptions.forEach(driverItem => {
selectedGlassToReplace.push({ location: damageLocationsSelected.DRIVER, name: driverItem});
selectedGlassToReplace.push({ glassLocation: damageLocationsSelected.DRIVER, glassName: driverItem});
})
}
if (this.isPassengerSideReplace){
this.sideDoorOptionsData.selectedPassengerSideReplaceOptions.forEach(passengerItem => {
selectedGlassToReplace.push({ location: damageLocationsSelected.PASSENGER, name: passengerItem});
selectedGlassToReplace.push({ glassLocation: damageLocationsSelected.PASSENGER, glassName: passengerItem});
})
}
if (this.isRearWindowDamageLocation) {
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
cmsWidgetName="FunnelSubHeaderWidget"
:hasBackButton="true"
backButtonAccessibleText="Change Vehicle Year"
@click-event="backButtonAction"
/>
<div class="fade-on-route-transition">

View file

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

View file

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

View file

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

View file

@ -7,7 +7,6 @@
<funnelSubHeader
cmsWidgetName="FunnelSubHeaderWidget"
:hasBackButton="true"
backButtonAccessibleText="Change Vehicle Model"
@click-event="backButtonAction"
/>
<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 { getMountOptions } from "@/helpers/unit-test-helper.js";
import { navigationScenarios } from "@/router/router-constants/navigation-scenarios.js";
import store from "@/store";
jest.mock("@/store", () => ({
commit: jest.fn(),
dispatch: jest.fn(),
getters: {
vehicle: {
year: 2019,
carId: 'initial carId'
},
order: {
serviceLocation: {
zipCode: "45253"
commit: jest.fn(),
dispatch: jest.fn(),
getters: {
vehicle: {
year: 2019,
carId: 'initial carId'
},
customer: {
emailAddress: "builddigitaltest@safelite.com"
order: {
serviceLocation: {
zipCode: "45253"
},
customer: {
emailAddress: "builddigitaltest@safelite.com"
}
},
payment: {
insuranceCoverage: {
isVerified: true
}
},
damage: {
glassToReplace: "windshield"
}
},
payment: {
insuranceCoverage: {
isVerified: true
}
},
damage: {
glassToReplace: "windshield"
}
},
}));
import { getDamageString, getIsWindshieldOnly,isGlassAvailableForCarId } from "@/helpers/damage-helper";
import { getDamageString, getIsWindshieldOnly, isGlassAvailableForCarId } from "@/helpers/damage-helper";
jest.mock("@/helpers/damage-helper", () => ({
isGlassAvailableForCarId: jest.fn(() => {
@ -45,41 +46,21 @@ jest.mock("@/helpers/damage-helper", () => ({
describe("vin-lookup.vue", () => {
it("Should update the funnel-footer forward button when VIN is changed", (done) => {
//Arrange
const { wrapper } = setupMocks({ });
wrapper.vm.$refs.funnelFooter.updateButtonText = jest.fn();
const { wrapper } = setupMocks({});
//Act
wrapper.setData({vin: "newValue"});
wrapper.setData({ vin: "newValue" });
//Assert
wrapper.vm.$nextTick(() => {
expect(wrapper.vm.$refs.funnelFooter.updateButtonText).toBeCalled();
done();
});
});
it("Should call navigateForward() if the store carId matches the vin response carId and forward button is clicked", async () => {
// Arrange
const { wrapper } = setupMocks({ });
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);
wrapper.vm.$refs.funnelFooter.updateButtonText = jest.fn();
wrapper.vm.$refs.funnelFooter.removeLoader = jest.fn();
wrapper.vm.navigateForward = jest.fn();
const { wrapper } = setupMocks({});
wrapper.vm.navigateForward = jest.fn();
// Act
await wrapper.vm.forwardButtonAction();
@ -87,26 +68,231 @@ describe("vin-lookup.vue", () => {
//Assert
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 }) {
const mountOptions = getMountOptions({
...customMountOptions
});
const mountOptions = getMountOptions({});
const finalMountOptions = Object.assign(mountOptions, customMountOptions);
// Modify/augment default mount options
finalMountOptions.global.mocks["$store"] = store;
finalMountOptions.global.mixins = [mockMixin];
finalMountOptions['attachTo'] = document.body; // append wrapper to document.body to test DOM methods
const wrapper = shallowMount(vinLookup, finalMountOptions);
return { wrapper };
}
mountOptions.global.mocks["$store"] = store;
mountOptions.global.mixins = [mockMixin];
mountOptions['attachTo'] = document.body; // append wrapper to document.body to test DOM methods
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: {
getCmsContent: jest.fn(() => "placeholder CMS content"),
getCmsContent: jest.fn(() => "placeholder CMS content"),
}
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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