From 548f1124798ba7f722b8482a112b621251832d9c Mon Sep 17 00:00:00 2001 From: Leah Schumann Date: Thu, 16 Jun 2022 08:29:09 -0400 Subject: [PATCH 1/6] Fixed bug where the Perfect Match New Vin Alert wasn't appearing unless you clicked on masked Vin --- src/layouts/vin-lookup/vin-lookup.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/layouts/vin-lookup/vin-lookup.vue b/src/layouts/vin-lookup/vin-lookup.vue index 29aeda5e4..f7e7ab56e 100644 --- a/src/layouts/vin-lookup/vin-lookup.vue +++ b/src/layouts/vin-lookup/vin-lookup.vue @@ -206,7 +206,7 @@ export default { }, computed: { perfectMatchNewVinAlert() { - const isVinPerfectMatch = this.vinPopulatedOnPageLoad && this.vin === this.getVinFromStore(); + const isVinPerfectMatch = this.vinPopulatedOnPageLoad && this.initialVin === this.getVinFromStore(); this.updateIsCarIdDifferent(isVinPerfectMatch); return isVinPerfectMatch; }, From 5e58722d459926b00f80541504ff04e7fb348c16 Mon Sep 17 00:00:00 2001 From: Leah Schumann Date: Thu, 16 Jun 2022 13:16:15 -0400 Subject: [PATCH 2/6] Ensured that both VIN Lookup and Zip Servicability alerts can be displayed at the same time --- src/layouts/vin-lookup/vin-lookup.vue | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/src/layouts/vin-lookup/vin-lookup.vue b/src/layouts/vin-lookup/vin-lookup.vue index f7e7ab56e..23cce0f67 100644 --- a/src/layouts/vin-lookup/vin-lookup.vue +++ b/src/layouts/vin-lookup/vin-lookup.vue @@ -203,6 +203,9 @@ export default { this.getCmsContent("FunnelFooterWidget", "ForwardButtonText") ); }, + zip() { + this.noServiceZip = false; + }, }, computed: { perfectMatchNewVinAlert() { @@ -309,33 +312,39 @@ export default { const zipValidation = this.validateZip(this.zip); const zipValidationResponse = await zipValidation; + // Check if Service Zip entered is servicable, if not display an alert if (!zipValidationResponse.data.isServiceable) { - this.customAlertData.zip = this.zip; - this.$refs.funnelFooter.removeLoader(); + this.customAlertData.zip = this.zip; 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 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 const vinToLookup = this.vinTouched ? this.vin : this.initialVin; const vehicleLookup = this.lookupVehicle(vinToLookup); vehicleLookupResponse = await vehicleLookup.catch((response) => { if (response.status == StatusCodes.NOT_FOUND) { this.vinNotFound = true; this.$refs.funnelFooter.removeLoader(); - this.noServiceZip = false; return false; } }); - if (!vehicleLookupResponse) { - return; - } + // if the ZIP is not serviceable or the Vin Lookup didn't return anything then don't complete the process and navigate forward. + if (!zipValidationResponse.data.isServiceable || !vehicleLookupResponse) { + this.$refs.funnelFooter.removeLoader(); + return; + } } else { + // if the ZIP is not serviceable then don't navigate forward. + if (!zipValidationResponse.data.isServiceable) { + this.$refs.funnelFooter.removeLoader(); + return; + } + this.navigateForward(); return; } From 597c67da93c133e0e51811b6211eebaced213b19 Mon Sep 17 00:00:00 2001 From: Leah Schumann Date: Thu, 16 Jun 2022 14:29:58 -0400 Subject: [PATCH 3/6] Reestablished simultaneous calls to VIN Lookup and Zip Validation --- src/layouts/vin-lookup/vin-lookup.vue | 59 +++++++++++++++++---------- 1 file changed, 37 insertions(+), 22 deletions(-) diff --git a/src/layouts/vin-lookup/vin-lookup.vue b/src/layouts/vin-lookup/vin-lookup.vue index 23cce0f67..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" /> @@ -188,6 +189,7 @@ export default { vinPopulatedOnPageLoad: this.getVinFromStore()?.length > 0, initialVin: this.getVinFromStore(), vinTouched: false, + rawVinValue: "", }; }, mounted() { @@ -209,7 +211,8 @@ export default { }, computed: { perfectMatchNewVinAlert() { - const isVinPerfectMatch = this.vinPopulatedOnPageLoad && this.initialVin === this.getVinFromStore(); + const vinToCheck = this.vinTouched ? this.rawVinValue : this.initialVin; + const isVinPerfectMatch = this.vinPopulatedOnPageLoad && vinToCheck === this.getVinFromStore(); this.updateIsCarIdDifferent(isVinPerfectMatch); return isVinPerfectMatch; }, @@ -308,23 +311,18 @@ export default { } }, async forwardButtonAction() { + let zipValidationResponse; + let vehicleLookupResponse; const zipValidation = this.validateZip(this.zip); - const zipValidationResponse = await zipValidation; - - // 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; - } - 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 VIN Lookup + // 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; @@ -333,19 +331,36 @@ export default { } }); - // if the ZIP is not serviceable or the Vin Lookup didn't return anything then don't complete the process and navigate forward. - if (!zipValidationResponse.data.isServiceable || !vehicleLookupResponse) { - this.$refs.funnelFooter.removeLoader(); - return; - } - } else { - // if the ZIP is not serviceable then don't navigate forward. + // Check if Service Zip entered is servicable, if not display an alert if (!zipValidationResponse.data.isServiceable) { - this.$refs.funnelFooter.removeLoader(); - return; + 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(); - this.navigateForward(); + return; + + } else { + this.navigateForward(); + return; + } + } + + if (!vehicleLookupResponse) { return; } From 67ba6a1e0cbb6c94a0e7c2cb70099c5101d4f6a5 Mon Sep 17 00:00:00 2001 From: Bryan Mauger Date: Thu, 16 Jun 2022 15:21:37 -0400 Subject: [PATCH 4/6] CSR-465 Add validation to parts page. --- .../glass-part-question.vue | 8 + src/layouts/vehicle-parts/vehicle-parts.vue | 359 +++++++++--------- 2 files changed, 198 insertions(+), 169 deletions(-) diff --git a/src/layouts/vehicle-parts/glass-part-question/glass-part-question.vue b/src/layouts/vehicle-parts/glass-part-question/glass-part-question.vue index 7d936c5bf..d0def364d 100644 --- a/src/layouts/vehicle-parts/glass-part-question/glass-part-question.vue +++ b/src/layouts/vehicle-parts/glass-part-question/glass-part-question.vue @@ -29,7 +29,11 @@ :buttonID="`${glassLocation}-${glassName}-${name}`" :groupName="`${glassLocation}-${glassName}`" @isCheckedChanged="ResetTintAndPartSelections()" + :validationRules="validationRules" /> +
+ +
@@ -53,6 +57,7 @@ :loaderEnabled="false" isRequired :groupName="`${glassLocation}-${glassName}-${name}`" + :validationRules="validationRules" /> @@ -68,6 +73,7 @@ import buttonQuestion from "@/common-components/button-question/button-question" // Supporting files import { getTintImage } from "@/constants/tint-mapper"; import { getCustomTransformValue } from "@/constants/dynamictext-mapper"; +import { ErrorMessage } from 'vee-validate'; export default { name: "glass-part-question", @@ -84,6 +90,7 @@ export default { glassLocation: String, colorAnswers: Array, modelValue: Object, + validationRules: String, }, mounted() { this.LoadPreselectedValues(); @@ -91,6 +98,7 @@ export default { components: { listCard, buttonQuestion, + ErrorMessage, }, computed: { colorQuestionText() { diff --git a/src/layouts/vehicle-parts/vehicle-parts.vue b/src/layouts/vehicle-parts/vehicle-parts.vue index d876f132f..a16d0b0a8 100644 --- a/src/layouts/vehicle-parts/vehicle-parts.vue +++ b/src/layouts/vehicle-parts/vehicle-parts.vue @@ -1,5 +1,11 @@ From e9af0c64a511753306a82e30737491a9d10999d0 Mon Sep 17 00:00:00 2001 From: Adam Caouette Date: Fri, 17 Jun 2022 09:24:52 -0400 Subject: [PATCH 5/6] CSR-480: add unit tests --- jest.config.js | 3 - .../question-chain/question-chain.spec.js | 241 ++++++++++++++++++ .../question-chain/question-chain.vue | 10 +- .../address-vehicles-question.spec.js | 1 - 4 files changed, 247 insertions(+), 8 deletions(-) create mode 100644 src/common-components/question-chain/question-chain.spec.js diff --git a/jest.config.js b/jest.config.js index 017648108..b66199733 100644 --- a/jest.config.js +++ b/jest.config.js @@ -20,11 +20,8 @@ module.exports = { "!src/layouts/reveal/**/*.vue", "!src/layouts/estimate/**/*.vue", // TODO REMOVE THESE AFTER WRITING UNIT TESTS - "!src/layouts/address-vehicles/address-vehicles.vue", - "!src/layouts/address-vehicles/address-vehicles-question/address-vehicles-question.vue", "!src/ux-components/alert\alert.vue", "!src/helpers/validation-rules.js", - "!src/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/question-chain/question-chain.spec.js b/src/common-components/question-chain/question-chain.spec.js new file mode 100644 index 000000000..84795fac0 --- /dev/null +++ b/src/common-components/question-chain/question-chain.spec.js @@ -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 }; +} \ No newline at end of file diff --git a/src/common-components/question-chain/question-chain.vue b/src/common-components/question-chain/question-chain.vue index d605d7fde..ef56c4041 100644 --- a/src/common-components/question-chain/question-chain.vue +++ b/src/common-components/question-chain/question-chain.vue @@ -9,7 +9,7 @@ :groupName="`${questionData.glassName}-${questionData.glassLocation}-${i}`" textPosition="text-start" v-model="selectedValue" - isRequired=true + :isRequired=true :validationRules="validationRules" :clearOnUnmount=false /> @@ -30,12 +30,14 @@ export default { }; }, props: { - questionData: Array, + questionData: Object, validationRules: String, - modelValue: String, + modelValue: Array, }, computed: { + questions() { + console.log("answeredQuestions: ", this.answeredQuestions) const questions = this.questionData.partQuestions.map((q, i) => { return { questionText: q.questionText, @@ -52,7 +54,7 @@ export default { } }); // 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; }, selectedValue: { diff --git a/src/layouts/address-vehicles/address-vehicles-question/address-vehicles-question.spec.js b/src/layouts/address-vehicles/address-vehicles-question/address-vehicles-question.spec.js index eaa24c9d4..d030c7c76 100644 --- a/src/layouts/address-vehicles/address-vehicles-question/address-vehicles-question.spec.js +++ b/src/layouts/address-vehicles/address-vehicles-question/address-vehicles-question.spec.js @@ -1,7 +1,6 @@ import { shallowMount } from "@vue/test-utils"; import addressVehiclesQuestion from "@/layouts/address-vehicles/address-vehicles-question/address-vehicles-question"; - describe("addressVehiclesQuestion.vue", () => { it("Should return content for differentVehicleAlertHeader", () => { From 0e5f01a6411a207f3fe271690da7a24656d59d4b Mon Sep 17 00:00:00 2001 From: Adam Caouette Date: Fri, 17 Jun 2022 10:23:04 -0400 Subject: [PATCH 6/6] CSR-480: fix typo --- jest.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jest.config.js b/jest.config.js index b66199733..fe6eeb04d 100644 --- a/jest.config.js +++ b/jest.config.js @@ -20,7 +20,7 @@ module.exports = { "!src/layouts/reveal/**/*.vue", "!src/layouts/estimate/**/*.vue", // TODO REMOVE THESE AFTER WRITING UNIT TESTS - "!src/ux-components/alert\alert.vue", + "!src/ux-components/alert/alert.vue", "!src/helpers/validation-rules.js", // END ], // ! means exclude from coverage.