diff --git a/jest.config.js b/jest.config.js
index a73aac7ea..a511fdc02 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -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)"],
diff --git a/src/common-components/button-question/button-question.spec.js b/src/common-components/button-question/button-question.spec.js
index b70304035..9cf75368e 100644
--- a/src/common-components/button-question/button-question.spec.js
+++ b/src/common-components/button-question/button-question.spec.js
@@ -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);
diff --git a/src/common-components/button-question/button-question.vue b/src/common-components/button-question/button-question.vue
index efc1cf051..f1c2ad98a 100644
--- a/src/common-components/button-question/button-question.vue
+++ b/src/common-components/button-question/button-question.vue
@@ -36,6 +36,7 @@
data-test="button"
:validationRules="validationRules"
:class="[suppressError ? 'alertError' : '']"
+ :clearOnUnmount="clearOnUnmount"
/>
@@ -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];
}
},
},
diff --git a/src/common-components/question-chain/question-chain.vue b/src/common-components/question-chain/question-chain.vue
new file mode 100644
index 000000000..d605d7fde
--- /dev/null
+++ b/src/common-components/question-chain/question-chain.vue
@@ -0,0 +1,119 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/constants/store-mutations.js b/src/constants/store-mutations.js
index e8349ef40..1924b0063 100644
--- a/src/constants/store-mutations.js
+++ b/src/constants/store-mutations.js
@@ -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
diff --git a/src/layouts/part-questions/part-questions.vue b/src/layouts/part-questions/part-questions.vue
index 36a7d6465..af202feed 100644
--- a/src/layouts/part-questions/part-questions.vue
+++ b/src/layouts/part-questions/part-questions.vue
@@ -67,7 +67,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);
diff --git a/src/layouts/reveal/reveal.vue b/src/layouts/reveal/reveal.vue
index 200fec263..7ca9d903f 100644
--- a/src/layouts/reveal/reveal.vue
+++ b/src/layouts/reveal/reveal.vue
@@ -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);
diff --git a/src/layouts/vehicle-parts/vehicle-parts.vue b/src/layouts/vehicle-parts/vehicle-parts.vue
index a16d0b0a8..9ab861f0f 100644
--- a/src/layouts/vehicle-parts/vehicle-parts.vue
+++ b/src/layouts/vehicle-parts/vehicle-parts.vue
@@ -198,7 +198,7 @@ methods: {
}
// Save parts to the store.
- store.commit(storeMutations.UPDATE_PARTS, matchedParts);
+ store.commit(storeMutations.UPDATE_GLASS_PARTS, matchedParts);
// Navigate to the next page.
this.$router.navigateAfterSave(
diff --git a/src/layouts/vin-lookup/vin-lookup.spec.js b/src/layouts/vin-lookup/vin-lookup.spec.js
index 1c580f4a6..e782f345e 100644
--- a/src/layouts/vin-lookup/vin-lookup.spec.js
+++ b/src/layouts/vin-lookup/vin-lookup.spec.js
@@ -69,9 +69,38 @@ describe("vin-lookup.vue", () => {
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
@@ -138,14 +167,21 @@ describe("vin-lookup.vue", () => {
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 = {
- data: {
+ status: {
carId: 'new carId' // does not match the store value
}
};
const vinPromise = Promise.reject(vehicleLookupApiResponse);
- wrapper.vm.lookupVehicle = jest.fn().mockImplementation(() => vinPromise);
+ const response = {
+ status: 404
+ };
+ wrapper.vm.lookupVehicle = jest.fn().mockImplementation((response) => vinPromise);
wrapper.vm.navigateForward = jest.fn();
wrapper.vm.previouslyEnteredCarId = 'new carId';
diff --git a/src/layouts/vin-lookup/vin-lookup.vue b/src/layouts/vin-lookup/vin-lookup.vue
index 33c192941..392cde7f1 100644
--- a/src/layouts/vin-lookup/vin-lookup.vue
+++ b/src/layouts/vin-lookup/vin-lookup.vue
@@ -27,6 +27,7 @@
maxLength="17"
:mask="vinMask"
@focus="setVinTouched"
+ @maska="rawVinValue = $event.target.dataset.maskRawValue"
/>
@@ -142,6 +143,7 @@ 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));
@@ -187,6 +189,7 @@ export default {
vinPopulatedOnPageLoad: this.getVinFromStore()?.length > 0,
initialVin: this.getVinFromStore(),
vinTouched: false,
+ rawVinValue: "",
};
},
mounted() {
@@ -202,10 +205,14 @@ export default {
this.getCmsContent("FunnelFooterWidget", "ForwardButtonText")
);
},
+ zip() {
+ this.noServiceZip = false;
+ },
},
computed: {
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);
return isVinPerfectMatch;
},
@@ -259,7 +266,7 @@ export default {
},
setupVinMask() {
const lastSixChars = this.initialVin.substring(11, this.initialVin.length);
- this.vinMask = `!X!X!X!X!X!X!X!X!X!X!X${lastSixChars}`;
+ this.vinMask = `!X!X!X!X!X!X!X!X!X!X!X${lastSixChars}`;
},
arePagePrerequisitesValid() {
return store.getters.vehicle.carId !== null;
@@ -304,34 +311,58 @@ export default {
}
},
async forwardButtonAction() {
- // If there is no change to the VIN entered then navigate forward without performing lookup.
- if (this.vinPopulatedOnPageLoad && this.vin == this.initialVin) {
- this.navigateForward();
- return;
- }
+ 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 false;
- });
+ 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((response) => {
+ if (response.status == StatusCodes.NOT_FOUND) {
+ 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.customAlertData.zip = this.zip;
+ this.noServiceZip = true;
+ this.invalidZip = 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.customAlertData.zip = this.zip;
+ this.noServiceZip = true;
+ this.invalidZip = this.zip;
+ this.$refs.funnelFooter.removeLoader();
+
+ return;
+
+ } else {
+ this.navigateForward();
+ return;
+ }
+ }
if (!vehicleLookupResponse) {
return;
}
-
- if (!zipValidationResponse.data.isServiceable) {
- this.customAlertData.zip = this.zip;
- this.$refs.funnelFooter.removeLoader();
- this.noServiceZip = true;
- this.invalidZip = this.zip;
- return;
- }
this.isCarIdDifferent = vehicleLookupResponse.data.carId !== store.getters.vehicle.carId;
diff --git a/src/mixins/vin-pages-mixin.js b/src/mixins/vin-pages-mixin.js
index 8f33fdf92..383bcf237 100644
--- a/src/mixins/vin-pages-mixin.js
+++ b/src/mixins/vin-pages-mixin.js
@@ -19,7 +19,7 @@ export default {
this.$router.navigateAfterSave(this.navigationScenarios.SELECTED_VIN_WITH_MULTIPLE_PARTS, this.$route, {}, {}, result.data);
}
else {
- store.commit(storeMutations.UPDATE_PARTS, result.data);
+ store.commit(storeMutations.UPDATE_GLASS_PARTS, result.data);
this.$refs.loadingModal.showModal();
navigateAfterSaveToHeritageFunnel(this.$route);
}
diff --git a/src/mixins/vin-pages-mixin.spec.js b/src/mixins/vin-pages-mixin.spec.js
index 747b62793..aef929d52 100644
--- a/src/mixins/vin-pages-mixin.spec.js
+++ b/src/mixins/vin-pages-mixin.spec.js
@@ -872,7 +872,7 @@ describe("vin-pages-mixin", () => {
// Assert
expect(store.commit).toHaveBeenCalledTimes(1);
- expect(store.commit).toHaveBeenCalledWith(storeMutations.UPDATE_PARTS, { partsOrQuestions })
+ expect(store.commit).toHaveBeenCalledWith(storeMutations.UPDATE_GLASS_PARTS, { partsOrQuestions })
expect(wrapper.vm.$refs.loadingModal.showModal).toHaveBeenCalledTimes(1);
expect(navigateAfterSaveToHeritageFunnel).toHaveBeenCalledTimes(1);
});
diff --git a/src/router/index.js b/src/router/index.js
index 74ee110de..2b2d09859 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -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);
}
diff --git a/src/store/index.js b/src/store/index.js
index c3e54d27b..5730adf53 100644
--- a/src/store/index.js
+++ b/src/store/index.js
@@ -111,7 +111,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) {
@@ -212,9 +212,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());
@@ -400,14 +399,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);
diff --git a/src/store/store.spec.js b/src/store/store.spec.js
index bdb49ece7..c70629296 100644
--- a/src/store/store.spec.js
+++ b/src/store/store.spec.js
@@ -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'});
diff --git a/src/ux-components/list-button-horizontal/list-button-horizontal.vue b/src/ux-components/list-button-horizontal/list-button-horizontal.vue
index eef4be443..dfd322cb3 100644
--- a/src/ux-components/list-button-horizontal/list-button-horizontal.vue
+++ b/src/ux-components/list-button-horizontal/list-button-horizontal.vue
@@ -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() {
diff --git a/src/ux-components/list-button/list-button.vue b/src/ux-components/list-button/list-button.vue
index f09384934..861619093 100644
--- a/src/ux-components/list-button/list-button.vue
+++ b/src/ux-components/list-button/list-button.vue
@@ -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() {
diff --git a/src/ux-components/list-card/list-card.vue b/src/ux-components/list-card/list-card.vue
index 264d7c299..88aad0b6a 100644
--- a/src/ux-components/list-card/list-card.vue
+++ b/src/ux-components/list-card/list-card.vue
@@ -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() {