Merge branch 'develop' into feature/CSR-659

This commit is contained in:
Leah Schumann 2022-06-15 13:39:15 -04:00
commit 9e63cd0f9c
24 changed files with 410 additions and 466 deletions

View file

@ -23,6 +23,7 @@ module.exports = {
"!src/layouts/address-vehicles/address-vehicles-question/address-vehicles-question.vue",
"!src/ux-components/alert/alert.vue",
"!src/helpers/validation-rules.js",
"!src/common-components/question-chain/question-chain",
// 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

@ -0,0 +1,119 @@
<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 {
models: Array,
currentQuestion: 1,
answeredQuestions: [],
};
},
props: {
questionData: Array,
validationRules: String,
modelValue: String,
},
computed: {
questions() {
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({});
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

@ -55,7 +55,6 @@ export default {
default: "",
},
validationRules: String,
semiAggressiveValidation: Boolean,
cmsWidgetName: String,
maxLength: String,
},
@ -130,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

@ -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
});
@ -60,7 +59,7 @@ describe("address-lookup.vue", () => {
zipCode: "43215"
}
const { wrapper } = setupMocks(addressLookup, {
const { wrapper } = setupMocks({
isZipServiceable: true
});
@ -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
@ -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
@ -192,7 +162,7 @@ describe("address-lookup.vue", () => {
test("if the back button is clicked, navigate back", async () => {
// Arrange
const { wrapper } = setupMocks(addressLookup, {
const { wrapper } = setupMocks({
isZipServiceable: true
});
@ -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
@ -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
@ -333,42 +274,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_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: {
@ -406,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
@ -467,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
@ -590,7 +491,7 @@ describe("address-lookup.vue", () => {
// Arrange
const commitSpy = jest.spyOn(store, "commit");
const dispatchSpy = jest.spyOn(store, "dispatch");
const { wrapper } = setupMocks(addressLookup, {
const { wrapper } = setupMocks({
isZipServiceable: true
});
@ -615,7 +516,7 @@ describe("address-lookup.vue", () => {
zipCode: "43215"
}
const { wrapper } = setupMocks(addressLookup, {
const { wrapper } = setupMocks({
isZipServiceable: true
});
@ -645,7 +546,7 @@ describe("address-lookup.vue", () => {
zipCode: "43215"
}
const { wrapper } = setupMocks(addressLookup, {
const { wrapper } = setupMocks({
isZipServiceable: false
});
@ -677,7 +578,7 @@ describe("address-lookup.vue", () => {
zipCode: "43215"
}
const { wrapper } = setupMocks(addressLookup, {
const { wrapper } = setupMocks({
isZipServiceable: false
}
);
@ -709,7 +610,7 @@ describe("address-lookup.vue", () => {
zipCode: "43215"
}
const { wrapper } = setupMocks(addressLookup, {
const { wrapper } = setupMocks({
isZipServiceable: false
}
);
@ -726,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 () => {
@ -738,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") {
@ -777,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");
@ -794,10 +697,9 @@ describe("address-lookup.vue", () => {
});
});
function setupMocks(mountOptions, { isZipServiceable = true, lookupVinbyAddressResponse, partsOrQuestions = [] }) {
function setupMocks({ isZipServiceable = true, lookupVinbyAddressResponse, partsOrQuestions = [] }) {
store.commit(storeMutations.RESET_STATE);
const wrapper = shallowMount(addressLookup, getMountOptions({
...mountOptions,
actionList: [
{
actionName: storeActions.VALIDATE_ZIP,
@ -831,7 +733,7 @@ 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();

View file

@ -82,8 +82,6 @@ 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";
@ -318,12 +316,12 @@ 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,

View file

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

View file

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

View file

@ -5,6 +5,8 @@ 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.
@ -19,7 +21,9 @@ 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();
@ -33,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
@ -53,7 +56,7 @@ describe("addressVehicles.vue", () => {
wrapper.vm.$router.navigate = jest.fn();
// Act
wrapper.setData({
await wrapper.setData({
selectedVehicleVin: ['5NMS3CADXLH233004'],
});
wrapper.vm.backButtonAction();
@ -83,7 +86,7 @@ describe("addressVehicles.vue", () => {
wrapper.vm.navigateForward = jest.fn().mockImplementation(()=> {});
// Act
wrapper.setData({
await wrapper.setData({
selectedVehicleVin: ['5NMS3CADXLH233004'],
});
@ -113,7 +116,7 @@ describe("addressVehicles.vue", () => {
wrapper.vm.updateCustomerInfo = jest.fn().mockImplementation(()=> {});
// Act
wrapper.setData({
await wrapper.setData({
selectedVehicleVin: ['5NMS3CADXLH233004'],
});
await wrapper.vm.forwardButtonAction();
@ -140,7 +143,7 @@ describe("addressVehicles.vue", () => {
wrapper.vm.$router.navigateAfterSave = jest.fn();
// Act
wrapper.setData({
await wrapper.setData({
selectedVehicleVin: ['5NMS3CADXLH233004'],
isSelectedGlassAvailableForVehicle: false,
isCarIdDifferent: true,
@ -148,7 +151,7 @@ describe("addressVehicles.vue", () => {
await wrapper.vm.updateCustomerInfo(wrapper.vm.selectedVehicle.vin, wrapper.vm.selectedVehicle.vehicle);
//Assert
expect(store.dispatch).toBeCalledWith("resetDamageAndDependencies");
expect(wrapper.vm.dispatchStoreAction).toBeCalledWith("resetDamageAndDependencies");
wrapper.unmount();
});
@ -162,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();
});
@ -170,10 +173,9 @@ describe("addressVehicles.vue", () => {
test("If selectedVehicleVin changes, then should update isCarIdDifferent", async () => {
// Arrange
const { wrapper } = setupMocks({});
wrapper.vm.$refs.funnelFooter.updateButtonText = jest.fn();
// Act
wrapper.setData({
await wrapper.setData({
selectedVehicleVin: ['5NMS3CADXLH233004'],
isCarIdDifferent: false,
});
@ -191,7 +193,7 @@ describe("addressVehicles.vue", () => {
wrapper.vm.$refs.funnelFooter.updateButtonText = jest.fn();
// Act
wrapper.setData({
await wrapper.setData({
selectedVehicleVin: ['5NMS3CADXLH233004'],
isCarIdDifferent: false,
});
@ -210,7 +212,7 @@ describe("addressVehicles.vue", () => {
wrapper.vm.$router.navigateAfterSave = jest.fn();
// Act
wrapper.setData({
await wrapper.setData({
selectedVehicleVin: ['5NMS3CADXLH233004'],
isSelectedGlassAvailableForVehicle: false,
isCarIdDifferent: true,
@ -229,10 +231,9 @@ describe("addressVehicles.vue", () => {
wrapper.vm.$refs.funnelFooter.updateButtonText = jest.fn();
wrapper.vm.$refs.loadingModal.showModal = jest.fn();
navigateToHeritage.navigateAfterSaveToHeritageFunnel = jest.fn();
wrapper.vm.navigateForwardWithSingleCarMatch = jest.fn();
// Act
wrapper.setData({
await wrapper.setData({
isCarIdDifferent: false,
});
await wrapper.vm.navigateForward();
@ -246,53 +247,35 @@ describe("addressVehicles.vue", () => {
function setupMocks({}) {
//Mock store
store.dispatch = jest.fn(() => {});
store.getters = {
pageData: jest.fn((pageName) => {
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",
}
};
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
@ -319,5 +302,8 @@ function setupMocks({}) {
const wrapper = shallowMount(addressVehicles, mountOptions);
wrapper.vm.$refs.funnelFooter.updateButtonText = jest.fn();
wrapper.vm.navigateForwardWithSingleCarMatch = jest.fn();
return { wrapper };
}

View file

@ -59,14 +59,12 @@ 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,
@ -195,7 +193,7 @@ export default {
}
},
lookupVin(vin) {
return baseMixin.methods.dispatchStoreAction(
return this.dispatchStoreAction(
storeActions.LOOKUP_VEHICLE_BY_VIN,
{ vin }
);
@ -205,7 +203,7 @@ export default {
},
updateCustomerInfo(vin, vehicle) {
if (this.isCarIdDifferent && !this.isSelectedGlassAvailableForVehicle) {
store.dispatch(storeActions.RESET_DAMAGE_STATE_AND_DEPENDENCIES);
this.dispatchStoreAction(storeActions.RESET_DAMAGE_STATE_AND_DEPENDENCIES);
}
store.commit(storeMutations.UPDATE_VEHICLE_VIN, vin);
store.commit(storeMutations.UPDATE_YEAR, vehicle.year);

View file

@ -4,7 +4,6 @@ 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";
@ -230,13 +229,16 @@ 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.$router.navigateAfterSave = jest.fn();
wrapper.vm.getCmsContent = jest.fn().mockImplementation(() => {
return '';
});
store.dispatch = jest.fn();
wrapper.vm.dispatchStoreAction = jest.fn();
await wrapper.vm.navigateForward();
@ -250,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 '';
});
@ -302,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();
@ -316,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();
@ -330,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();
@ -502,19 +512,20 @@ 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("dispatchStoreAction called on validate zip", async () => {
@ -527,7 +538,7 @@ describe("license-plate-lookup.vue", () => {
//Assert
expect(baseMixin.methods.dispatchStoreAction).toHaveBeenCalled();
expect(wrapper.vm.dispatchStoreAction).toHaveBeenCalled();
});
test("dispatchStoreAction called on lookup vin", async () => {
@ -540,7 +551,7 @@ describe("license-plate-lookup.vue", () => {
//Assert
expect(baseMixin.methods.dispatchStoreAction).toHaveBeenCalled();
expect(wrapper.vm.dispatchStoreAction).toHaveBeenCalled();
});
})
});
@ -552,7 +563,6 @@ function setupMocks({
}) {
store.commit(storeMutations.RESET_STATE);
//Mock api responses
baseMixin.methods.dispatchStoreAction = jest.fn();
const apiResponses = {
cmsContent: {
FunnelSubHeaderWidget: pageHeaderWidgetHeaderText,
@ -592,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,7 +92,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";
@ -303,16 +300,16 @@ export default {
}
},
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

@ -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>
@ -168,23 +168,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 +229,9 @@ export default {
font-size: 0.875rem;
}
}
.color-question-text {
color: $black;
font-weight: $font-weight-bold;
}
</style>

View file

@ -1,5 +1,5 @@
<template>
<div class="page-container-grouped-styles">
<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" />

View file

@ -57,7 +57,6 @@
isRequired
disableAutoFill
validationRules="email-address-required|email-address-format"
semiAggressiveValidation
/>
</div>
</div>

View file

@ -46,6 +46,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

@ -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() {