Merge branch 'develop' into feature/CSR-249
This commit is contained in:
commit
d0153869f8
7 changed files with 486 additions and 193 deletions
|
|
@ -20,10 +20,8 @@ module.exports = {
|
||||||
"!src/layouts/reveal/**/*.vue",
|
"!src/layouts/reveal/**/*.vue",
|
||||||
"!src/layouts/estimate/**/*.vue",
|
"!src/layouts/estimate/**/*.vue",
|
||||||
// TODO REMOVE THESE AFTER WRITING UNIT TESTS
|
// TODO REMOVE THESE AFTER WRITING UNIT TESTS
|
||||||
"!src/layouts/address-vehicles/address-vehicles-question/address-vehicles-question.vue",
|
|
||||||
"!src/ux-components/alert/alert.vue",
|
"!src/ux-components/alert/alert.vue",
|
||||||
"!src/helpers/validation-rules.js",
|
"!src/helpers/validation-rules.js",
|
||||||
"!src/common-components/question-chain/question-chain",
|
|
||||||
// END
|
// END
|
||||||
], // ! means exclude from coverage.
|
], // ! means exclude from coverage.
|
||||||
testMatch: ["**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"],
|
testMatch: ["**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"],
|
||||||
|
|
|
||||||
241
src/common-components/question-chain/question-chain.spec.js
Normal file
241
src/common-components/question-chain/question-chain.spec.js
Normal 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 };
|
||||||
|
}
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
:groupName="`${questionData.glassName}-${questionData.glassLocation}-${i}`"
|
:groupName="`${questionData.glassName}-${questionData.glassLocation}-${i}`"
|
||||||
textPosition="text-start"
|
textPosition="text-start"
|
||||||
v-model="selectedValue"
|
v-model="selectedValue"
|
||||||
isRequired=true
|
:isRequired=true
|
||||||
:validationRules="validationRules"
|
:validationRules="validationRules"
|
||||||
:clearOnUnmount=false
|
:clearOnUnmount=false
|
||||||
/>
|
/>
|
||||||
|
|
@ -30,12 +30,14 @@ export default {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
questionData: Array,
|
questionData: Object,
|
||||||
validationRules: String,
|
validationRules: String,
|
||||||
modelValue: String,
|
modelValue: Array,
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
|
||||||
questions() {
|
questions() {
|
||||||
|
console.log("answeredQuestions: ", this.answeredQuestions)
|
||||||
const questions = this.questionData.partQuestions.map((q, i) => {
|
const questions = this.questionData.partQuestions.map((q, i) => {
|
||||||
return {
|
return {
|
||||||
questionText: q.questionText,
|
questionText: q.questionText,
|
||||||
|
|
@ -52,7 +54,7 @@ export default {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// add an empty item to be array[0] since we start with 1
|
// add an empty item to be array[0] since we start with 1
|
||||||
questions.unshift({});
|
questions.unshift({ "DeliberatelyBlankObject": "This object has been added as a placeholder only for question #0"});
|
||||||
return questions;
|
return questions;
|
||||||
},
|
},
|
||||||
selectedValue: {
|
selectedValue: {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import { shallowMount } from "@vue/test-utils";
|
import { shallowMount } from "@vue/test-utils";
|
||||||
import addressVehiclesQuestion from "@/layouts/address-vehicles/address-vehicles-question/address-vehicles-question";
|
import addressVehiclesQuestion from "@/layouts/address-vehicles/address-vehicles-question/address-vehicles-question";
|
||||||
|
|
||||||
|
|
||||||
describe("addressVehiclesQuestion.vue", () => {
|
describe("addressVehiclesQuestion.vue", () => {
|
||||||
|
|
||||||
it("Should return content for differentVehicleAlertHeader", () => {
|
it("Should return content for differentVehicleAlertHeader", () => {
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,11 @@
|
||||||
:buttonID="`${glassLocation}-${glassName}-${name}`"
|
:buttonID="`${glassLocation}-${glassName}-${name}`"
|
||||||
:groupName="`${glassLocation}-${glassName}`"
|
:groupName="`${glassLocation}-${glassName}`"
|
||||||
@isCheckedChanged="ResetTintAndPartSelections()"
|
@isCheckedChanged="ResetTintAndPartSelections()"
|
||||||
|
:validationRules="validationRules"
|
||||||
/>
|
/>
|
||||||
|
<div class="row form-test-error mt-1">
|
||||||
|
<error-message :name="`${glassLocation}-${glassName}`" v-if="!suppressError"></error-message>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<transition name="fade" mode="out-in">
|
<transition name="fade" mode="out-in">
|
||||||
|
|
@ -53,6 +57,7 @@
|
||||||
:loaderEnabled="false"
|
:loaderEnabled="false"
|
||||||
isRequired
|
isRequired
|
||||||
:groupName="`${glassLocation}-${glassName}-${name}`"
|
:groupName="`${glassLocation}-${glassName}-${name}`"
|
||||||
|
:validationRules="validationRules"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -68,6 +73,7 @@ import buttonQuestion from "@/common-components/button-question/button-question"
|
||||||
// Supporting files
|
// Supporting files
|
||||||
import { getTintImage } from "@/constants/tint-mapper";
|
import { getTintImage } from "@/constants/tint-mapper";
|
||||||
import { getCustomTransformValue } from "@/constants/dynamictext-mapper";
|
import { getCustomTransformValue } from "@/constants/dynamictext-mapper";
|
||||||
|
import { ErrorMessage } from 'vee-validate';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "glass-part-question",
|
name: "glass-part-question",
|
||||||
|
|
@ -84,6 +90,7 @@ export default {
|
||||||
glassLocation: String,
|
glassLocation: String,
|
||||||
colorAnswers: Array,
|
colorAnswers: Array,
|
||||||
modelValue: Object,
|
modelValue: Object,
|
||||||
|
validationRules: String,
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.LoadPreselectedValues();
|
this.LoadPreselectedValues();
|
||||||
|
|
@ -91,6 +98,7 @@ export default {
|
||||||
components: {
|
components: {
|
||||||
listCard,
|
listCard,
|
||||||
buttonQuestion,
|
buttonQuestion,
|
||||||
|
ErrorMessage,
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
colorQuestionText() {
|
colorQuestionText() {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="page-container-grouped-styles vehicle-parts">
|
<Form
|
||||||
|
@submit="onSubmit"
|
||||||
|
@invalid-submit="onInvalidSubmit"
|
||||||
|
ref="theForm"
|
||||||
|
v-slot="{ meta }"
|
||||||
|
>
|
||||||
|
<div class="page-container-grouped-styles vehicle-parts">
|
||||||
<funnelHeader ref="funnelHeader" cmsWidgetName="FunnelHeaderWidget" />
|
<funnelHeader ref="funnelHeader" cmsWidgetName="FunnelHeaderWidget" />
|
||||||
<vehicleBanner ref="vehicleBanner" cmsWidgetName="VehicleBannerWidget" :displayGenericVehicleImage="false" />
|
<vehicleBanner ref="vehicleBanner" cmsWidgetName="VehicleBannerWidget" :displayGenericVehicleImage="false" />
|
||||||
<funnelSubHeader ref="funnelSubHeader" cmsWidgetName="FunnelSubHeaderWidget" />
|
<funnelSubHeader ref="funnelSubHeader" cmsWidgetName="FunnelSubHeaderWidget" />
|
||||||
|
|
@ -8,10 +14,10 @@
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<alert
|
<alert
|
||||||
class="rounded border-0 shadow-sm"
|
class="rounded border-0 shadow-sm"
|
||||||
alertClass="alert-warning"
|
alertClass="alert-warning"
|
||||||
cmsWidgetName="AlertWidget"
|
cmsWidgetName="AlertWidget"
|
||||||
:isDismissible="false"
|
:isDismissible="false"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -24,16 +30,24 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<glassPartQuestion
|
<glassPartQuestion
|
||||||
:ref="`${RefPrefix}-${item.glassLocation}-${item.glassName}`"
|
:ref="`${RefPrefix}-${item.glassLocation}-${item.glassName}`"
|
||||||
v-model="glassParts[item.glassLocation + '-' + item.glassName]"
|
v-model="glassParts[item.glassLocation + '-' + item.glassName]"
|
||||||
:glassLocation="item.glassLocation"
|
:glassLocation="item.glassLocation"
|
||||||
:glassName="item.glassName"
|
:glassName="item.glassName"
|
||||||
:colorAnswers="item.colorAnswers"
|
:colorAnswers="item.colorAnswers"
|
||||||
|
validationRules="replace-options-required"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<funnelFooter cmsWidgetName="FunnelFooterWidget" ref="funnelFooter" @back-clicked="backButtonAction" @ForwardClicked="forwardButtonAction" />
|
<funnelFooter
|
||||||
|
cmsWidgetName="FunnelFooterWidget"
|
||||||
|
ref="funnelFooter"
|
||||||
|
:isForwardActionDisabled="!meta.valid"
|
||||||
|
@back-clicked="backButtonAction"
|
||||||
|
@ForwardClicked="forwardButtonAction"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</Form>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
@ -50,170 +64,177 @@ import { settleAllPromises } from "@/helpers/layout-helper";
|
||||||
import { fmgPageValues } from "@/router/router-constants/fmgPage-values";
|
import { fmgPageValues } from "@/router/router-constants/fmgPage-values";
|
||||||
import { storeMutations } from "@/constants/store-mutations";
|
import { storeMutations } from "@/constants/store-mutations";
|
||||||
import store from "@/store";
|
import store from "@/store";
|
||||||
|
import { Form, defineRule } from "vee-validate";
|
||||||
|
import { required } from "@/helpers/validation-rules";
|
||||||
|
import { errorMessages } from "@/constants/error-messages";
|
||||||
|
|
||||||
|
// DEFINE VALIDATION RULES
|
||||||
|
defineRule("replace-options-required", required(errorMessages.OPTION_REQUIRED));
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "vehicle-parts",
|
name: "vehicle-parts",
|
||||||
async beforeRouteEnter(to, from, next) {
|
async beforeRouteEnter(to, from, next) {
|
||||||
// Call APIs
|
// Call APIs
|
||||||
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
|
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
|
||||||
// Settle promises and get results
|
// Settle promises and get results
|
||||||
const promiseResultMap = [{
|
const promiseResultMap = [{
|
||||||
resultKey: "cmsContent",
|
resultKey: "cmsContent",
|
||||||
promise: cmsContentPromise,
|
promise: cmsContentPromise,
|
||||||
}, ];
|
}, ];
|
||||||
const resultMap = await settleAllPromises(promiseResultMap);
|
const resultMap = await settleAllPromises(promiseResultMap);
|
||||||
// Call the "next" function to complete the transition to this page.
|
// Call the "next" function to complete the transition to this page.
|
||||||
next((vm) => {
|
next((vm) => {
|
||||||
vm.setCmsContent(resultMap.cmsContent);
|
vm.setCmsContent(resultMap.cmsContent);
|
||||||
|
|
||||||
// Glass Part Question dynamic component
|
// Glass Part Question dynamic component
|
||||||
Object.keys(vm.$refs)
|
Object.keys(vm.$refs)
|
||||||
.filter((r) => r.includes(vm.RefPrefix) && vm.$refs[r][0] !== undefined)
|
.filter((r) => r.includes(vm.RefPrefix) && vm.$refs[r][0] !== undefined)
|
||||||
.forEach((c) =>
|
.forEach((c) =>
|
||||||
vm.$refs[c][0].initializeComponent({
|
vm.$refs[c][0].initializeComponent({
|
||||||
ColorQuestionWidget: resultMap.cmsContent.ColorQuestionWidget,
|
ColorQuestionWidget: resultMap.cmsContent.ColorQuestionWidget,
|
||||||
FeatureQuestionWidget: resultMap.cmsContent.FeatureQuestionWidget,
|
FeatureQuestionWidget: resultMap.cmsContent.FeatureQuestionWidget,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
glassParts: {},
|
glassParts: {},
|
||||||
alertWidgetData: Object,
|
alertWidgetData: Object,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
glassPartQuestion,
|
Form,
|
||||||
funnelHeader,
|
glassPartQuestion,
|
||||||
vehicleBanner,
|
funnelHeader,
|
||||||
funnelSubHeader,
|
vehicleBanner,
|
||||||
funnelFooter,
|
funnelSubHeader,
|
||||||
alert,
|
funnelFooter,
|
||||||
},
|
alert,
|
||||||
computed: {
|
},
|
||||||
PartsForQuestions() {
|
computed: {
|
||||||
const partsData = this.PartsFromApi;
|
PartsForQuestions() {
|
||||||
|
const partsData = this.PartsFromApi;
|
||||||
|
|
||||||
// Map API result data, to vehicle-parts data structure
|
// Map API result data, to vehicle-parts data structure
|
||||||
const mappedData = partsData.partsOrQuestions.map((g) => {
|
const mappedData = partsData.partsOrQuestions.map((g) => {
|
||||||
return {
|
return {
|
||||||
glassName: g.glassName,
|
glassName: g.glassName,
|
||||||
glassLocation: g.glassLocation,
|
glassLocation: g.glassLocation,
|
||||||
colorAnswers: g.parts.reduce((arr, p) => {
|
colorAnswers: g.parts.reduce((arr, p) => {
|
||||||
arr.push({
|
arr.push({
|
||||||
ColorAnswerText: p.color,
|
ColorAnswerText: p.color,
|
||||||
FeatureAnswers: [
|
FeatureAnswers: [
|
||||||
{
|
{
|
||||||
FeatureAnswerText:
|
FeatureAnswerText:
|
||||||
p.description === "" ? p.color : p.description,
|
p.description === "" ? p.color : p.description,
|
||||||
PartNumber: p.partNumber,
|
PartNumber: p.partNumber,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
|
||||||
return arr;
|
|
||||||
}, []),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return mappedData;
|
|
||||||
},
|
|
||||||
|
|
||||||
PartsFromApi() {
|
|
||||||
return store.getters.pageData(fmgPageValues.VEHICLE_PARTS);
|
|
||||||
},
|
|
||||||
|
|
||||||
RefPrefix() {
|
|
||||||
return "partQuestion";
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
arePagePrerequisitesValid() {
|
|
||||||
// Check if isRepair is populated and if the pageData we need is here (Parts data)
|
|
||||||
if (
|
|
||||||
(store.getters.damage.isRepair != null) &&
|
|
||||||
Object.keys(store.getters.pageData(fmgPageValues.VEHICLE_PARTS))
|
|
||||||
.length !== 0
|
|
||||||
) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
backButtonAction() {
|
|
||||||
this.$router.navigate(this.navigationScenarios.CLICKED_BACK, this.$route);
|
|
||||||
},
|
|
||||||
forwardButtonAction() {
|
|
||||||
const selectedGlassPartNumbers = [];
|
|
||||||
const matchedParts = [];
|
|
||||||
|
|
||||||
// Compile all selected parts from the page.
|
|
||||||
for (let [key, value] of Object.entries(this.glassParts)) {
|
|
||||||
for (let [glassKey, glassValue] of Object.entries(value)) {
|
|
||||||
selectedGlassPartNumbers.push(glassValue[0]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Match them to the parts from the API.
|
|
||||||
for (let [key, value] of Object.entries(
|
|
||||||
this.PartsFromApi.partsOrQuestions
|
|
||||||
)) {
|
|
||||||
for (let [partKey, partValue] of Object.entries(value.parts)) {
|
|
||||||
const currentPart =
|
|
||||||
this.PartsFromApi.partsOrQuestions[key].parts[partKey];
|
|
||||||
const isMatched = selectedGlassPartNumbers.some(
|
|
||||||
(p) => p === currentPart.partNumber
|
|
||||||
);
|
|
||||||
|
|
||||||
if (isMatched) {
|
|
||||||
matchedParts.push(currentPart);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If no parts could be matched, throw an error.
|
|
||||||
if (matchedParts.length === 0) {
|
|
||||||
throw new Error("Could not match any parts to the selected parts");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save parts to the store.
|
|
||||||
store.commit(storeMutations.UPDATE_GLASS_PARTS, matchedParts);
|
|
||||||
|
|
||||||
// Navigate to the next page.
|
|
||||||
this.$router.navigateAfterSave(
|
|
||||||
this.navigationScenarios.SELECTED_PARTS,
|
|
||||||
this.$route
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
resetDependentState() {
|
|
||||||
// Nothing additional to reset here: The page save is already fully resetting all the line-items on the order
|
|
||||||
},
|
|
||||||
|
|
||||||
LoadInitialPartsData() {
|
|
||||||
const partsData = this.PartsFromApi;
|
|
||||||
const alreadyPopulatedPartsData =
|
|
||||||
this.$store.getters.lineItems.glassParts === null
|
|
||||||
? {}
|
|
||||||
: this.$store.getters.lineItems.glassParts;
|
|
||||||
|
|
||||||
partsData.partsOrQuestions.map((g) => {
|
|
||||||
// If the part is already populated, use the value from the store and populate the v-model.
|
|
||||||
Object.keys(alreadyPopulatedPartsData).forEach((key) => {
|
|
||||||
const partNumber = alreadyPopulatedPartsData[key].partNumber;
|
|
||||||
g.parts.forEach((p) => {
|
|
||||||
if (p.partNumber === partNumber) {
|
|
||||||
this.glassParts[g.glassLocation + "-" + g.glassName] = {
|
|
||||||
[g.glassLocation]: [partNumber],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
return arr;
|
||||||
|
}, []),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return mappedData;
|
||||||
|
},
|
||||||
|
|
||||||
|
PartsFromApi() {
|
||||||
|
return store.getters.pageData(fmgPageValues.VEHICLE_PARTS);
|
||||||
|
},
|
||||||
|
|
||||||
|
RefPrefix() {
|
||||||
|
return "partQuestion";
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
arePagePrerequisitesValid() {
|
||||||
|
// Check if isRepair is populated and if the pageData we need is here (Parts data)
|
||||||
|
if (
|
||||||
|
(store.getters.damage.isRepair != null) &&
|
||||||
|
Object.keys(store.getters.pageData(fmgPageValues.VEHICLE_PARTS))
|
||||||
|
.length !== 0
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
backButtonAction() {
|
||||||
|
this.$router.navigate(this.navigationScenarios.CLICKED_BACK, this.$route);
|
||||||
|
},
|
||||||
|
forwardButtonAction() {
|
||||||
|
const selectedGlassPartNumbers = [];
|
||||||
|
const matchedParts = [];
|
||||||
|
|
||||||
|
// Compile all selected parts from the page.
|
||||||
|
for (let [key, value] of Object.entries(this.glassParts)) {
|
||||||
|
for (let [glassKey, glassValue] of Object.entries(value)) {
|
||||||
|
selectedGlassPartNumbers.push(glassValue[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match them to the parts from the API.
|
||||||
|
for (let [key, value] of Object.entries(
|
||||||
|
this.PartsFromApi.partsOrQuestions
|
||||||
|
)) {
|
||||||
|
for (let [partKey, partValue] of Object.entries(value.parts)) {
|
||||||
|
const currentPart =
|
||||||
|
this.PartsFromApi.partsOrQuestions[key].parts[partKey];
|
||||||
|
const isMatched = selectedGlassPartNumbers.some(
|
||||||
|
(p) => p === currentPart.partNumber
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isMatched) {
|
||||||
|
matchedParts.push(currentPart);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no parts could be matched, throw an error.
|
||||||
|
if (matchedParts.length === 0) {
|
||||||
|
throw new Error("Could not match any parts to the selected parts");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save parts to the store.
|
||||||
|
store.commit(storeMutations.UPDATE_GLASS_PARTS, matchedParts);
|
||||||
|
|
||||||
|
// Navigate to the next page.
|
||||||
|
this.$router.navigateAfterSave(
|
||||||
|
this.navigationScenarios.SELECTED_PARTS,
|
||||||
|
this.$route
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
resetDependentState() {
|
||||||
|
// Nothing additional to reset here: The page save is already fully resetting all the line-items on the order
|
||||||
|
},
|
||||||
|
|
||||||
|
LoadInitialPartsData() {
|
||||||
|
const partsData = this.PartsFromApi;
|
||||||
|
const alreadyPopulatedPartsData =
|
||||||
|
this.$store.getters.lineItems.glassParts === null
|
||||||
|
? {}
|
||||||
|
: this.$store.getters.lineItems.glassParts;
|
||||||
|
|
||||||
|
partsData.partsOrQuestions.map((g) => {
|
||||||
|
// If the part is already populated, use the value from the store and populate the v-model.
|
||||||
|
Object.keys(alreadyPopulatedPartsData).forEach((key) => {
|
||||||
|
const partNumber = alreadyPopulatedPartsData[key].partNumber;
|
||||||
|
g.parts.forEach((p) => {
|
||||||
|
if (p.partNumber === partNumber) {
|
||||||
|
this.glassParts[g.glassLocation + "-" + g.glassName] = {
|
||||||
|
[g.glassLocation]: [partNumber],
|
||||||
|
};
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
});
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.LoadInitialPartsData();
|
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.LoadInitialPartsData();
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@
|
||||||
maxLength="17"
|
maxLength="17"
|
||||||
:mask="vinMask"
|
:mask="vinMask"
|
||||||
@focus="setVinTouched"
|
@focus="setVinTouched"
|
||||||
|
@maska="rawVinValue = $event.target.dataset.maskRawValue"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -188,6 +189,7 @@ export default {
|
||||||
vinPopulatedOnPageLoad: this.getVinFromStore()?.length > 0,
|
vinPopulatedOnPageLoad: this.getVinFromStore()?.length > 0,
|
||||||
initialVin: this.getVinFromStore(),
|
initialVin: this.getVinFromStore(),
|
||||||
vinTouched: false,
|
vinTouched: false,
|
||||||
|
rawVinValue: "",
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
|
@ -203,10 +205,14 @@ export default {
|
||||||
this.getCmsContent("FunnelFooterWidget", "ForwardButtonText")
|
this.getCmsContent("FunnelFooterWidget", "ForwardButtonText")
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
zip() {
|
||||||
|
this.noServiceZip = false;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
perfectMatchNewVinAlert() {
|
perfectMatchNewVinAlert() {
|
||||||
const isVinPerfectMatch = this.vinPopulatedOnPageLoad && this.vin === this.getVinFromStore();
|
const vinToCheck = this.vinTouched ? this.rawVinValue : this.initialVin;
|
||||||
|
const isVinPerfectMatch = this.vinPopulatedOnPageLoad && vinToCheck === this.getVinFromStore();
|
||||||
this.updateIsCarIdDifferent(isVinPerfectMatch);
|
this.updateIsCarIdDifferent(isVinPerfectMatch);
|
||||||
return isVinPerfectMatch;
|
return isVinPerfectMatch;
|
||||||
},
|
},
|
||||||
|
|
@ -305,38 +311,56 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async forwardButtonAction() {
|
async forwardButtonAction() {
|
||||||
|
let zipValidationResponse;
|
||||||
|
let vehicleLookupResponse;
|
||||||
|
|
||||||
const zipValidation = this.validateZip(this.zip);
|
const zipValidation = this.validateZip(this.zip);
|
||||||
const zipValidationResponse = await zipValidation;
|
|
||||||
|
|
||||||
if (!zipValidationResponse.data.isServiceable) {
|
|
||||||
this.customAlertData.zip = this.zip;
|
|
||||||
this.$refs.funnelFooter.removeLoader();
|
|
||||||
this.noServiceZip = true;
|
|
||||||
this.invalidZip = this.zip;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 VIN Lookup
|
|
||||||
let vehicleLookupResponse;
|
|
||||||
if (this.vinTouched && this.vin != this.initialVin) {
|
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 vinToLookup = this.vinTouched ? this.vin : this.initialVin;
|
||||||
const vehicleLookup = this.lookupVehicle(vinToLookup);
|
const vehicleLookup = this.lookupVehicle(vinToLookup);
|
||||||
|
|
||||||
|
zipValidationResponse = await zipValidation;
|
||||||
vehicleLookupResponse = await vehicleLookup.catch((response) => {
|
vehicleLookupResponse = await vehicleLookup.catch((response) => {
|
||||||
if (response.status == StatusCodes.NOT_FOUND) {
|
if (response.status == StatusCodes.NOT_FOUND) {
|
||||||
this.vinNotFound = true;
|
this.vinNotFound = true;
|
||||||
this.$refs.funnelFooter.removeLoader();
|
this.$refs.funnelFooter.removeLoader();
|
||||||
this.noServiceZip = false;
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!vehicleLookupResponse) {
|
// Check if Service Zip entered is servicable, if not display an alert
|
||||||
|
if (!zipValidationResponse.data.isServiceable) {
|
||||||
|
this.customAlertData.zip = this.zip;
|
||||||
|
this.noServiceZip = true;
|
||||||
|
this.invalidZip = this.zip;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!vehicleLookupResponse || !zipValidationResponse.data.isServiceable) {
|
||||||
|
this.$refs.funnelFooter.removeLoader();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.navigateForward();
|
const zipValidationResponse = await zipValidation;
|
||||||
|
|
||||||
|
// Check if Service Zip entered is serviceable, if not display an alert
|
||||||
|
if (!zipValidationResponse.data.isServiceable) {
|
||||||
|
this.customAlertData.zip = this.zip;
|
||||||
|
this.noServiceZip = true;
|
||||||
|
this.invalidZip = this.zip;
|
||||||
|
this.$refs.funnelFooter.removeLoader();
|
||||||
|
|
||||||
|
return;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
this.navigateForward();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!vehicleLookupResponse) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue