CSR-319: move validation rules and errors into centralized files; fix and set up validation unit tests

This commit is contained in:
Adam Caouette 2022-03-04 13:03:08 -05:00
parent ca0b266efa
commit 312ba851bf
12 changed files with 167 additions and 96 deletions

View file

@ -0,0 +1,12 @@
const errorMessages = {
DAMAGE_LOCATION_REQUIRED: "Please select damage location",
DAMAGE_SIDE_REQUIRED: "Please select vehicle side",
DRIVER_SIDE_OPTIONS_REQUIRED: "Please select window",
PASSENGER_SIDE_OPTIONS_REQUIRED: "Please select window",
WINDSHIELD_DAMAGE_TYPE_REQUIRED: "Please select windshield damage",
WINDSHIELD_CHIP_COUNT_REQUIRED: "Please select chip(s)",
WINSHIELD_REPLACE_OPTIONS_REQUIRED: "Please select windshield part",
REPLACE_OPTIONS_REQUIRED: "Please select rear window type",
};
export default { errorMessages };

View file

@ -0,0 +1,8 @@
export function required(errorMessage) {
return (value) => {
if (!value || value.length < 1) {
return errorMessage;
}
return true;
};
}

View file

@ -0,0 +1,29 @@
import { required } from "@/helpers/validation-rules";
describe("validation-rules.vue", () => {
test("required rules should return error if value missing", () => {
//Arrange
const testFn = required("an error");
//Act
const testResponse = testFn();
//Assert
expect(testResponse).toBe("an error");
});
});
describe("validation-rules.vue", () => {
test("required rules should return true if value present", () => {
//Arrange
const testFn = required("an error");
//Act
const testResponse = testFn('some value');
//Assert
expect(testResponse).toBe(true);
});
});

View file

@ -16,14 +16,11 @@
import buttonQuestion from "@/common-components/button-question/button-question";
import store from "@/store";
import { defineRule } from "vee-validate";
import { required } from "@/helpers/validation-rules";
import errorMessages from "@/constants/error-messages";
// DEFINE VALIDATION RULES
defineRule("damage-location-required", (value) => {
if (!value || value.length < 1) {
return "Please select damage location";
}
return true;
});
defineRule("damage-location-required", required(errorMessages.DAMAGE_LOCATION_REQUIRED));
export default ({
name: "damageLocationQuestion",

View file

@ -1,7 +1,6 @@
<template>
<transition name="fade" mode="out-in">
<div v-if="isAvailable && this.answersToDisplay.length > 0" class="replace-options-question" :class="this.answersToDisplay.length < 2 ? 'd-noneXXXXconsole.logXXXXXXXXXXXXX' : ''" aria-live="polite">
answersToDisplay.length: {{answersToDisplay.length}}
<buttonQuestion
isWide
:questionText="questionText"

View file

@ -39,28 +39,13 @@ import buttonQuestion from "@/common-components/button-question/button-question"
import replaceOptionsQuestion from "@/layouts/vehicle-damage/replace-options-question/replace-options-question";
import store from "@/store";
import { defineRule } from "vee-validate";
import { required } from "@/helpers/validation-rules";
import errorMessages from "@/constants/error-messages";
// DEFINE VALIDATION RULES
defineRule("damage-side-required", (value) => {
if (!value || value.length < 1) {
return "Please select vehicle side";
}
return true;
});
defineRule("driver-side-options-required", (value) => {
if (!value || value.length < 1) {
return "Please select window";
}
return true;
});
defineRule("passenger-side-options-required", (value) => {
if (!value || value.length < 1) {
return "Please select window";
}
return true;
});
defineRule("damage-side-required", required(errorMessages.DAMAGE_SIDE_REQUIRED));
defineRule("driver-side-options-required", required(errorMessages.DRIVER_SIDE_OPTIONS_REQUIRED));
defineRule("passenger-side-options-required", required(errorMessages.PASSENGER_SIDE_OPTIONS_REQUIRED));
export default ({
name: "sideDoorOptions",

View file

@ -13,11 +13,12 @@ import replaceOptionsQuestion from "@/layouts/vehicle-damage/replace-options-que
import { settleAllPromises } from "@/helpers/layout-helper.js";
import baseMixin from "@/mixins/base-mixin";
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import { shallowMount } from "@vue/test-utils";
import { mount, flushPromises } from "@vue/test-utils";
import { getMountOptions } from "@/helpers/unit-test-helper.js";
import { nextTick } from "vue";
import { storeActions } from "@/constants/store-actions";
import store from "@/store";
import { validate } from "vee-validate";
// Mock our module for promises.
jest.mock("@/helpers/layout-helper.js", () => ({
@ -36,7 +37,9 @@ jest.mock("@/store", () => ({
getters: {
vehicle: {
carId: "C00000000",
image: "test.jpg",
},
eventBusItem: jest.fn(),
},
}));
@ -211,12 +214,84 @@ describe("vehicle-damage.vue", () => {
});
});
describe("vehicle-damage.vue", () => {
test("when onInvalidSubmit is triggered with errors focus will be put on the first element with an error", async () => {
//Arrange
const { wrapper } = setupMocks({});
const mockedValidationPayload = {
values: {},
errors: {
driverSideOptions: 'Please select window',
passengerSideOptions: 'Please select window'
},
results: {},
}
const newObj = document.createElement('input');
newObj.setAttribute("id", "testInput");
newObj.setAttribute("data-focus-target", "driverSideOptions");
document.body.appendChild(newObj);
const testInputElement = document.getElementById("testInput");
//Act
vehicleDamage.beforeRouteEnter.call(
wrapper.vm,
{ query: { fmgPage: "vehicle-damage" } },
undefined,
(c) => c(wrapper.vm)
);
wrapper.vm.onInvalidSubmit(mockedValidationPayload);
await nextTick();
const focusedEl = document.activeElement;
//Assert
expect(testInputElement).toBe(focusedEl);
});
});
// THE FOLLOWING TEST IS NOT NECESSARILY REQUIRED FOR COVERAGE
// BUT KEEP FOR AN EXAMPLE OF A VALIDATION TEST
//
describe("vehicle-damage.vue", () => {
test("when validation rules are set they should validate correctly", async () => {
//Arrange
const { wrapper } = setupMocks({});
//Act
vehicleDamage.beforeRouteEnter.call(
wrapper.vm,
{ query: { fmgPage: "vehicle-damage" } },
undefined,
(c) => c(wrapper.vm)
);
const testNull = validate( "", "replace-options-required");
const testString = validate( "sldfj", "replace-options-required");
await flushPromises();
//Assert
testNull.then(function(data) {
expect(data.valid).toEqual(false);
});
testString.then(function(data) {
expect(data.valid).toEqual(true);
});
});
});
function setupMocks({
pageHeaderWidgetHeaderText = {},
mountOptionsMockData = {
router: {
navigate: jest.fn(),
},
store: {
getters: {
vehicle: {},
},
},
},
}) {
//Mock api responses
@ -281,6 +356,7 @@ function setupMocks({
replaceOptionsQuestion.methods = {
initializeComponent: jest.fn(),
updateSelectedValues: jest.fn(),
};
funnelFooter.methods = {
@ -288,7 +364,9 @@ function setupMocks({
}
const mountOptions = getMountOptions(mountOptionsMockData);
const wrapper = shallowMount(vehicleDamage, mountOptions);
mountOptions['attachTo'] = document.body; // append wrapper to document.body to test DOM methods
const wrapper = mount(vehicleDamage, mountOptions);
const funnelHeaderWrapper = wrapper.findComponent({ name: "funnelHeader" });
funnelHeaderWrapper.vm.initializeComponent =
@ -300,38 +378,27 @@ function setupMocks({
const sideDoorOptionsWrapper = wrapper.findComponent({ name: "sideDoorOptions" });
sideDoorOptionsWrapper.vm.initializeComponent =
sideDoorOptions.methods.initializeComponent;
sideDoorOptions.methods.initializeComponent;
const windshieldOptionsWrapper = wrapper.findComponent({
name: "windshieldOptions",
});
const windshieldOptionsWrapper = wrapper.findComponent({ name: "windshieldOptions" });
windshieldOptionsWrapper.vm.initializeComponent =
windshieldOptions.methods.initializeComponent;
windshieldOptions.methods.initializeComponent;
const funnelSubHeaderWrapper = wrapper.findComponent({
name: "funnelSubHeader",
});
const funnelSubHeaderWrapper = wrapper.findComponent({ name: "funnelSubHeader" });
funnelSubHeaderWrapper.vm.initializeComponent =
funnelSubHeader.methods.initializeComponent;
const backGlassOptionsWrapper = wrapper.findComponent({
name: "replaceOptionsQuestion",
});
const backGlassOptionsWrapper = wrapper.findComponent({ name: "replaceOptionsQuestion" });
backGlassOptionsWrapper.vm.initializeComponent =
replaceOptionsQuestion.methods.initializeComponent;
replaceOptionsQuestion.methods.initializeComponent;
const damageLocationQuestionWrapper = wrapper.findComponent({
name: "damageLocationQuestion",
});
const damageLocationQuestionWrapper = wrapper.findComponent({ name: "damageLocationQuestion" });
damageLocationQuestionWrapper.vm.initializeComponent =
damageLocationQuestion.methods.initializeComponent;
damageLocationQuestion.methods.initializeComponent;
const funnelFooterWrapper = wrapper.findComponent({
name: "funnelFooter",
});
funnelFooterWrapper.vm.initializeComponent = funnelFooter.methods.initializeComponent;
const funnelFooterWrapper = wrapper.findComponent({ name: "funnelFooter" });
funnelFooterWrapper.vm.initializeComponent =
funnelFooter.methods.initializeComponent;
return { wrapper, apiPromise };
}

View file

@ -3,6 +3,8 @@
<funnelHeader ref="funnelHeader" />
<vehicleBanner ref="vehicleBanner" :displayGenericVehicleImage=false />
<funnelSubHeader ref="funnelSubHeader" />
selectedDamageLocations {{selectedDamageLocations}}
isRearWindowDamageLocation {{ isRearWindowDamageLocation }}
<Form
@submit="onSubmit"
@invalid-submit="onInvalidSubmit"
@ -80,15 +82,11 @@ 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";
// DEFINE VALIDATION RULES
defineRule("replace-options-required", (value) => {
if (!value || value.length < 1) {
return "Please select rear window type";
}
return true;
});
defineRule("replace-options-required", required(errorMessages.REPLACE_OPTIONS_REQUIRED));
export default {
name: "vehicle-damage",
@ -180,21 +178,20 @@ export default {
);
},
onSubmit(values) {
window.alert('Success! Submitted values: ' + JSON.stringify(values, null ,2))
console.log('submitted: ', JSON.stringify(values, null ,2));
// window.alert('Success! Submitted values: ' + JSON.stringify(values, null ,2))
// TODO - ADD IN SAVING OF DATA
// TODO - ADD IN ADVANCING TO NEXT PAGE
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();
const el = document.querySelector(qsString);
el && el.focus();
}
},
},

View file

@ -1,6 +1,5 @@
<template>
<div class="windshield-options">
selectedDamageLocations: {{selectedDamageLocations}}
<windshieldDamageTypeQuestion ref="windshieldDamageTypeQuestion"
:isAvailable=isWindshieldDamageLocation
:suppressError="suppressError"
@ -29,15 +28,14 @@ import windshieldDamageTypeQuestion from"@/layouts/vehicle-damage/windshield-opt
import windshieldChipCountQuestion from"@/layouts/vehicle-damage/windshield-options/windshield-chip-count-question/windshield-chip-count-question";
import replaceOptionsQuestion from"@/layouts/vehicle-damage/replace-options-question/replace-options-question";
import { defineRule } from "vee-validate";
import { required } from "@/helpers/validation-rules";
import errorMessages from "@/constants/error-messages";
// DEFINE VALIDATION RULES
defineRule("windshield-damage-type-required", (value) => {
console.log('windshield-damage-type-required, value: ', value);
if (!value || value.length < 1) {
return "Please select windshield damage";
}
return true;
});
defineRule("windshield-damage-type-required", required(errorMessages.WINDSHIELD_DAMAGE_TYPE_REQUIRED));
defineRule("windshield-chip-count-required", required(errorMessages.WINDSHIELD_CHIP_COUNT_REQUIRED));
defineRule("windshield-replace-options-required", required(errorMessages.WINSHIELD_REPLACE_OPTIONS_REQUIRED));
defineRule("checkForRepairAndReplace", (value, [other]) => {
console.log('checkForRepairAndReplace, value: ', value, ' / other: ', other);
if (value.toString().toUpperCase().includes("REPAIR") && other.toString().toUpperCase().includes("WINDSHIELD") && other.length > 1) {
@ -45,18 +43,6 @@ defineRule("checkForRepairAndReplace", (value, [other]) => {
}
return true;
});
defineRule("windshield-chip-count-required", (value) => {
if (!value || value.length < 1) {
return "Please select chip(s)";
}
return true;
});
defineRule("windshield-replace-options-required", (value) => {
if (!value || value.length < 1) {
return "Please select windshield part";
}
return true;
});
export default ({
name: "windshieldOptions",

View file

@ -186,12 +186,7 @@ describe("vehicle-style.vue", () => {
store: {
commit: jest.fn(),
getters: {
vehicle: {
year: 2020,
make: "Honda",
model: "Civic",
style: "2 Door",
},
vehicle: {},
},
},
actionList: [

View file

@ -208,7 +208,7 @@ describe("list-card.vue", () => {
});
wrapper.vm.handleCheckChange();
// Assert
expect(wrapper.emitted()["isCheckedChanged"][0]).toEqual([{value: "List Card Checkbox", checkValue: Boolean}]);
expect(wrapper.emitted()["isCheckedChanged"][0]).toEqual([{value: "List Card Checkbox", checkValue: [Boolean, String]}]);
});
it("Should set checkValue data if selectedButtonIDs has value(s)", async () => {

View file

@ -83,7 +83,6 @@ export default {
created(){
if(Array.isArray(this.selectedValues)){
this.checkValue = this.isMultiSelect ? this.selectedValues.includes(this.value) : this.selectedValues[0];
console.log('=================== this.checkValue: ', this.checkValue)
}
},
computed: {
@ -101,9 +100,6 @@ export default {
},
methods: {
handleCheckChange(newValue, oldValue){
// console.log('LC handleCheckChange... oldValue: ', oldValue);
// console.log('LC handleCheckChange... newValue: ', newValue);
const isInitialization = typeof(oldValue) === 'function';
if (!isInitialization) {
this.$emit('isCheckedChanged', { checkValue: this.checkValue, value: this.value.toString() });