From 33692d3046b8602464d7ddc4ec5e9e76d4e1e441 Mon Sep 17 00:00:00 2001 From: Scott Kiener Date: Mon, 14 Nov 2022 12:26:42 -0500 Subject: [PATCH 1/2] csr-731 | Before large test rewrite --- .../button-question/button-question.spec.js | 25 +- .../service-package-question-test-helper.js | 279 ++++++++++++++++++ .../service-package-question.spec.js | 255 ++++++++++++++++ .../service-package-radio.spec.js | 128 ++++++++ 4 files changed, 686 insertions(+), 1 deletion(-) create mode 100644 src/layouts/quote/service-package-question/service-package-question-test-helper.js create mode 100644 src/layouts/quote/service-package-question/service-package-question.spec.js create mode 100644 src/layouts/quote/service-package-question/service-package-radio/service-package-radio.spec.js diff --git a/src/common-components/button-question/button-question.spec.js b/src/common-components/button-question/button-question.spec.js index 006cbcf2c..8a8696c43 100644 --- a/src/common-components/button-question/button-question.spec.js +++ b/src/common-components/button-question/button-question.spec.js @@ -1,4 +1,4 @@ -import { shallowMount } from "@vue/test-utils"; +import { shallowMount, mount } from "@vue/test-utils"; import buttonQuestion from "@/common-components/button-question/button-question"; import { getMountOptions } from "@/helpers/unit-test-helper.js"; @@ -115,6 +115,29 @@ describe("buttonQuestion.vue", () => { "2020", ]); }); + test("should accept an imported component for buttonTypeObject and successfully add it to components", async ()=> { + // Arrange + const mockComponent = { + name: 'mockComponent', + methods: { + mockComponentMethod() {return 'mockComponentMethod return value'} + } + }; + + const wrapper = await shallowMount( + buttonQuestion, + setupMocks({propsData: {'buttonTypeString': 'mockComponent', 'buttonTypeObject': mockComponent}}) + ); + + // Act + const componentList = wrapper.vm.$options.components; + const componentExists = !!componentList.mockComponent; + const componentNameMatches = componentList.mockComponent.name === mockComponent.name; + const componentMethodMatches = componentList.mockComponent.methods.mockComponentMethod() === mockComponent.methods.mockComponentMethod(); + + // Assert + expect(componentExists && componentNameMatches && componentMethodMatches).toBe(true); + }); }); describe("buttonsInfo", () => { diff --git a/src/layouts/quote/service-package-question/service-package-question-test-helper.js b/src/layouts/quote/service-package-question/service-package-question-test-helper.js new file mode 100644 index 000000000..3b18ed667 --- /dev/null +++ b/src/layouts/quote/service-package-question/service-package-question-test-helper.js @@ -0,0 +1,279 @@ +export const mockProcessedCmsContent = { + "05_01_CSR_Quote_Standard_Repair" : { + EconomyServicePackage: { + HeaderText: 'Economy service', + SubheaderText: '', + BodyText: '', + Image: '', + FooterText: '{if:custom:frontWipersApplicableForTierTwo}New wiper blades are not included with Economy service. Because chips can disrupt the glass surface, we recommend new wipers after a repair.{else}{if:custom:rearWiperApplicableForTierTwo}New wiper blades are not included with Economy service. Because chips can disrupt the glass surface, we recommend new wipers after a repair.{end}{end}' + }, + StandardServicePackage: { + HeaderText: 'Standard service', + SubheaderText: 'MOST POPULAR', + BodyText: '', + Image: '', + FooterText: '' + }, + PremiumServicePackage: { + HeaderText: 'Premium service', + SubheaderText: '', + BodyText: '', + Image: '', + FooterText: '' + } + }, + "05_01_CSR_Quote_Recal": { + EconomyServicePackage: { + HeaderText: 'Economy service', + SubheaderText: '', + BodyText: '', + Image: '', + FooterText: '{if:custom:frontWipersApplicableForTierTwo}New wiper blades are not included with Economy service. Because chips can disrupt the glass surface, we recommend new wipers after a repair.{else}{if:custom:rearWiperApplicableForTierTwo}New wiper blades are not included with Economy service. Because chips can disrupt the glass surface, we recommend new wipers after a repair.{end}{end}' + }, + StandardServicePackage: { + HeaderText: 'Standard service', + SubheaderText: 'MOST POPULAR', + BodyText: '', + Image: '', + FooterText: '' + }, + PremiumServicePackage: { + HeaderText: 'Premium service', + SubheaderText: '', + BodyText: '', + Image: '', + FooterText: '' + } + }, + "05_01_CSR_Quote_RearGlass": { + EconomyServicePackage: { + HeaderText: 'Economy service', + SubheaderText: '', + BodyText: '', + Image: '', + FooterText: '{if:custom:frontWipersApplicableForTierTwo}New wiper blades are not included with Economy service. Because chips can disrupt the glass surface, we recommend new wipers after a repair.{else}{if:custom:rearWiperApplicableForTierTwo}New wiper blades are not included with Economy service. Because chips can disrupt the glass surface, we recommend new wipers after a repair.{end}{end}' + }, + StandardServicePackage: { + HeaderText: 'Standard service', + SubheaderText: 'MOST POPULAR', + BodyText: '', + Image: '', + FooterText: '' + }, + PremiumServicePackage: { + HeaderText: 'Premium service', + SubheaderText: '', + BodyText: '', + Image: '', + FooterText: '' + } + } +}; + +export const inputQuestionWidgetAnswers = { + CashServicePackageQuestionWidget: { + "Answers": [ + { + "Name": "TierOne", + "SubWidgetName": "EconomyServicePackage" + }, + { + "Name": "TierTwo", + "SubWidgetName": "StandardServicePackage" + }, + { + "Name": "TierThree", + "SubWidgetName": "PremiumServicePackage" + } + ] + }, + InsuranceServicePackageQuestionWidget: { + "Answers": [ + { + "Name": "TierOne", + "SubWidgetName": "EconomyServicePackage" + }, + { + "Name": "TierTwo", + "SubWidgetName": "StandardServicePackage" + }, + { + "Name": "TierThree", + "SubWidgetName": "PremiumServicePackage" + } + ] + } +}; + + export const mockCmsContent = { + "Result": [ + { + "Name": "CashServicePackageQuestionWidget", + "Model": { + "QuestionText": "", + "Answers": [ + { + "Name": "TierOne", + "Text": "", + "SubText": "", + "AnswerImageUrl": "", + "SubWidgetName": "EconomyServicePackage" + }, + { + "Name": "TierTwo", + "Text": "", + "SubText": "", + "AnswerImageUrl": "", + "SubWidgetName": "StandardServicePackage" + }, + { + "Name": "TierThree", + "Text": "", + "SubText": "", + "AnswerImageUrl": "", + "SubWidgetName": "PremiumServicePackage" + } + ] + } + }, + { + "Name": "InsuranceServicePackageQuestionWidget", + "Model": { + "QuestionText": "", + "Answers": [ + { + "Name": "TierOne", + "Text": "", + "SubText": "", + "AnswerImageUrl": "", + "SubWidgetName": "EconomyServicePackage" + }, + { + "Name": "TierTwo", + "Text": "", + "SubText": "", + "AnswerImageUrl": "", + "SubWidgetName": "StandardServicePackage" + }, + { + "Name": "TierThree", + "Text": "", + "SubText": "", + "AnswerImageUrl": "", + "SubWidgetName": "PremiumServicePackage" + } + ] + } + }, + { + "Name": "EconomyServicePackage", + "Model": { + "HeaderText": "Economy service", + "SubheaderText": "", + "BodyText": "", + "Image": "", + "FooterText": "{if:custom:frontWipersApplicableForTierTwo}New wiper blades are not included with Economy service. Because chips can disrupt the glass surface, we recommend new wipers after a repair.{else}{if:custom:rearWiperApplicableForTierTwo}New wiper blades are not included with Economy service. Because chips can disrupt the glass surface, we recommend new wipers after a repair.{end}{end}" + } + }, + { + "Name": "StandardServicePackage", + "Model": { + "HeaderText": "Standard service", + "SubheaderText": "MOST POPULAR", + "BodyText": "", + "Image": "", + "FooterText": "" + } + }, + { + "Name": "PremiumServicePackage", + "Model": { + "HeaderText": "Premium service", + "SubheaderText": "", + "BodyText": "", + "Image": "", + "FooterText": "" + } + } +]}; + +export const expectedModifiedAnswers = { + "05_01_CSR_Quote_Standard_Repair" : { + tierOne: { + value: 'TierOne', + buttonLabel: 'Economy service', + buttonLabelSubCopy: '', + buttonBodyCopy: '', + buttonAuxillaryCopy: '$500.22', + buttonFooterCopy: 'New wiper blades are not included with Economy service. Because chips can disrupt the glass surface, we recommend new wipers after a repair.' + }, + tierTwo: { + value: 'TierTwo', + buttonLabel: 'Standard service', + buttonLabelSubCopy: 'MOST POPULAR', + buttonBodyCopy: '', + buttonAuxillaryCopy: '$585.90', + buttonFooterCopy: '' + }, + tierThree: { + value: 'TierThree', + buttonLabel: 'Premium service', + buttonLabelSubCopy: '', + buttonBodyCopy: '', + buttonAuxillaryCopy: '$621.40', + buttonFooterCopy: '' + } + }, + "05_01_CSR_Quote_Recal": { + tierOne: { + value: 'TierOne', + buttonLabel: 'Economy service', + buttonLabelSubCopy: '', + buttonBodyCopy: '', + buttonAuxillaryCopy: '$500.22', + buttonFooterCopy: 'New wiper blades are not included with Economy service. Because chips can disrupt the glass surface, we recommend new wipers after a repair.' + }, + tierTwo: { + value: 'TierTwo', + buttonLabel: 'Standard service', + buttonLabelSubCopy: 'MOST POPULAR', + buttonBodyCopy: '', + buttonAuxillaryCopy: '$585.90', + buttonFooterCopy: '' + }, + tierThree: { + value: 'TierThree', + buttonLabel: 'Premium service', + buttonLabelSubCopy: '', + buttonBodyCopy: '', + buttonAuxillaryCopy: '$621.40', + buttonFooterCopy: '' + } + }, + "05_01_CSR_Quote_RearGlass": { + tierOne: { + value: 'TierOne', + buttonLabel: 'Economy service', + buttonLabelSubCopy: '', + buttonBodyCopy: '', + buttonAuxillaryCopy: '$350.22', + buttonFooterCopy: 'New wiper blades are not included with Economy service. Because chips can disrupt the glass surface, we recommend new wipers after a repair.' + }, + tierTwo: { + value: 'TierTwo', + buttonLabel: 'Standard service', + buttonLabelSubCopy: 'MOST POPULAR', + buttonBodyCopy: '', + buttonAuxillaryCopy: '$374.70', + buttonFooterCopy: '' + }, + tierThree: { + value: 'TierThree', + buttonLabel: 'Premium service', + buttonLabelSubCopy: '', + buttonBodyCopy: '', + buttonAuxillaryCopy: '$460.38', + buttonFooterCopy: '' + } + }, +} \ No newline at end of file diff --git a/src/layouts/quote/service-package-question/service-package-question.spec.js b/src/layouts/quote/service-package-question/service-package-question.spec.js new file mode 100644 index 000000000..b1e0c7c2e --- /dev/null +++ b/src/layouts/quote/service-package-question/service-package-question.spec.js @@ -0,0 +1,255 @@ +import {shallowMount, mount} from "@vue/test-utils"; +import servicePackageQuestion from "./service-package-question"; +import { getMountOptions } from "@/helpers/unit-test-helper.js"; +import { fetchCmsContentForPage } from "@/helpers/cms-content-helper"; +import {dispatch} from "@/store"; +import store from "@/store"; +import { mockCmsContent, expectedModifiedAnswers, mockProcessedCmsContent, inputQuestionWidgetAnswers } from "./service-package-question-test-helper" + + +jest.mock("@/store", () => ({ + commit: jest.fn(), + dispatch: jest.fn(), + getters: { + order: { + damage: { + glassToReplace: [ + {glassLocation: "Rear"} + ] + }, + }, + hasAnyNonWindshieldGlassParts: true + }, + // order: { + // damage: { + // isRepair: false + // } + // }, + state: { // I think I can drop this when everything is done, but it's needed for generating the expected stuff from cms-content-helper + order: { + damage: { + isRepair: false, + }, + glassToReplace: [ + {glassLocation: "Rear"} + ] + } + } +})); + +// describe("service-package-question.vue", () => { +// it("should emit captured value", () => { +// // Arrange +// const wrapper = setupMocks({}); +// // Act +// wrapper.componentVM.selectedValues = "newValue"; + +// // Assert +// expect(wrapper.emitted()["update:modelValue"][0][0]).toEqual("newValue"); +// }); +// it("should return insurance answers from servicePackageAnswers when isInsuranceSelected = true", () => { +// // Arrange +// let modifiedProps = JSON.parse(JSON.stringify(mockProps)); // create a copy +// modifiedProps['isInsuranceSelected'] = true; +// const wrapper = setupMocks({props: modifiedProps}); +// //Arrange +// expect(wrapper.vm.servicePackageAnswers[0].buttonLabel).toBe(mockCmsContent.insuranceGroupContentWidget.HeaderText); +// }); +// it("should map cms content to a correctly customizied answers object", ()=> { +// // Arrange +// const expectedValue = mockCmsContent.cashAnswers.Answers[0].Name; +// // This round about way of referencing the mocked content mimics how it is referenced by the code +// // hopefully helpful for diagnosing why this test might be failing in the future +// const subWidgetName = mockCmsContent.cashAnswers.Answers[0].SubWidgetName; +// const cmsWidget = mockCmsContent[subWidgetName]; +// const expectedButtonLabel = cmsWidget.HeaderText; +// const expectedButtonLabelSubCopy = cmsWidget.SubheaderText +// const expectedButtonBodyCopy = cmsWidget.BodyText; +// const expectedButtonAuxillaryCopy = "500.22"; // combined price of windshield + recal +// const expectedButtonFooterCopy = cmsWidget.FooterText; +// const wrapper = setupMocks({}); +// // Assert +// const economyServicePackageAnswers = wrapper.vm.servicePackageAnswers[0]; +// expect(economyServicePackageAnswers.value).toBe(expectedValue); +// expect(economyServicePackageAnswers.buttonLabel).toBe(expectedButtonLabel); +// expect(economyServicePackageAnswers.buttonLabelSubCopy).toBe(expectedButtonLabelSubCopy); +// expect(economyServicePackageAnswers.buttonBodyCopy).toBe(expectedButtonBodyCopy); +// expect(economyServicePackageAnswers.buttonAuxillaryCopy.includes(expectedButtonAuxillaryCopy)).toBe(true); +// expect(economyServicePackageAnswers.buttonFooterCopy).toBe(expectedButtonFooterCopy); +// }); +// it("should not send a standard package answer if no VAPS are eligible for standard", () => { +// // Arrange +// let modifiedProps = JSON.parse(JSON.stringify(mockProps)); // create a copy +// modifiedProps['lineItems'].splice(1,3); +// const wrapper = setupMocks({props: modifiedProps}); + +// // Assert +// expect(wrapper.vm.servicePackageAnswers.length).toEqual(2); +// }); +// it("should not send a standard package answer if no VAPS are eligible for standard", () => { +// // Arrange +// let modifiedProps = JSON.parse(JSON.stringify(mockProps)); // create a copy +// modifiedProps['lineItems'].splice(1,3); +// const wrapper = setupMocks({props: modifiedProps}); + +// // Assert +// expect(wrapper.vm.servicePackageAnswers.length).toEqual(2); +// }); +// }); +describe("service-package-question.vue, matching business rules for package display", () => { + // mock scenarios in figma: + // https://www.figma.com/file/Spt9hBtGj8r8PFFGNIN3G5/New-Funnel?node-id=100%3A11383 + dispatch.mockImplementation(() => Promise.resolve({ data: mockCmsContent })); + beforeEach( async ()=> { + //processedCmsContent = await fetchCmsContentForPage('testPage'); + //console.log(processedCmsContent); + processedCmsContent = inputQuestionWidgetAnswers; + mockMixin = { + methods: { + getCmsContent: jest.fn((widgetName, cmsFieldName) => { + return processedCmsContent[widgetName][cmsFieldName]; + }) + }, + }; + mockProps = { + 'modelValue': 'initialValue', + 'isInsuranceSelected': false, + 'insuranceCmsWidgetName': 'InsuranceServicePackageQuestionWidget', + 'cashCmsWidgetName': 'CashServicePackageQuestionWidget', + 'availableLineItems': [ + { + partNumber : "001", + Description : "Windshield with Recal", + partType : "Windshield", + Quantity : "1", + BasePartNumber : "001", + Color : "Green", + CanSafeliteRecalibrate: true, + price : 350.22 + }, + { + "partNumber": "SBB16", + "description": "SAFELITE BEAM BLADE 16", + "partType": "FRONT WIPER", + "price": 32.64, + }, + { + "partNumber": "SBB26", + "description": "SAFELITE BEAM BLADE 26", + "partType": "FRONT WIPER", + "price": 53.04, + }, + { + "partNumber": "SBBR12A", + "description": "SAFELITE REAR BLADE 12A", + "partType": "REAR WIPER", + "price": 24.48, + }, + { + "partNumber": "RAIN DEFENSE", + "description": null, + "partType": "RAIN DEFENSE", + "price": 35.50, + }, + ] + }; + }) + it("should match the 05_01_CSR_Quote_Standard_Repair mock", () => { + // Arrange + const packageNameKey = "05_01_CSR_Quote_Standard_Repair"; + Object.assign(processedCmsContent, mockProcessedCmsContent[packageNameKey]); + const wrapper = setupMocks({}); + + // Assert + // economy answer + runPackageAnswerExpectStatements(wrapper.vm.servicePackageAnswers[0], expectedModifiedAnswers[packageNameKey].tierOne); + // standard answer + runPackageAnswerExpectStatements(wrapper.vm.servicePackageAnswers[1], expectedModifiedAnswers[packageNameKey].tierTwo); + // premium answer + runPackageAnswerExpectStatements(wrapper.vm.servicePackageAnswers[2], expectedModifiedAnswers[packageNameKey].tierThree); + }); + it.only("should match the 05_01_CSR_Quote_Recal mock", () => { + // Arrange + const packageNameKey = "05_01_CSR_Quote_Recal"; + mockProps.availableLineItems.push( + { + partNumber : "RECAL STATIC", + Description : "Recalibration", + partType : "recalibration", + Quantity : "1", + price : 150.00, + }, + ); + Object.assign(processedCmsContent, mockProcessedCmsContent[packageNameKey]); + const wrapper = setupMocks({ + customMountOptions: { + store: { + getters: { + order: { + damage: { + glassToReplace: [ + {glassLocation: "Windshield"} + ] + }, + }, + hasAnyNonWindshieldGlassParts: false + }, + }, + }, + }); + + // Assert + // economy answer + runPackageAnswerExpectStatements(wrapper.vm.servicePackageAnswers[0], expectedModifiedAnswers[packageNameKey].tierOne); + // standard answer + runPackageAnswerExpectStatements(wrapper.vm.servicePackageAnswers[1], expectedModifiedAnswers[packageNameKey].tierTwo); + // premium answer + runPackageAnswerExpectStatements(wrapper.vm.servicePackageAnswers[2], expectedModifiedAnswers[packageNameKey].tierThree); + }); + it("should match the 05_01_CSR_Quote_RearGlass mock", () => { + // Arrange + const packageNameKey = "05_01_CSR_Quote_RearGlass"; + Object.assign(processedCmsContent, mockProcessedCmsContent[packageNameKey]); + const wrapper = setupMocks({}); + //console.log(wrapper.vm.servicePackageAnswers); + + // Assert + // economy answer + runPackageAnswerExpectStatements(wrapper.vm.servicePackageAnswers[0], expectedModifiedAnswers[packageNameKey].tierOne); + // standard answer + runPackageAnswerExpectStatements(wrapper.vm.servicePackageAnswers[1], expectedModifiedAnswers[packageNameKey].tierTwo); + // premium answer + runPackageAnswerExpectStatements(wrapper.vm.servicePackageAnswers[2], expectedModifiedAnswers[packageNameKey].tierThree); + }); +}); + +function runPackageAnswerExpectStatements(servicePackageAnswer, expectedOutput) { + expect(servicePackageAnswer.buttonLabel).toBe(expectedOutput.buttonLabel); + expect(servicePackageAnswer.buttonLabelSubCopy).toBe(expectedOutput.buttonLabelSubCopy); + expect(servicePackageAnswer.buttonBodyCopy).toBe(expectedOutput.buttonBodyCopy); + expect(servicePackageAnswer.buttonFooterCopy).toBe(expectedOutput.buttonFooterCopy); +} + +function setupMocks({ customMountOptions, props = mockProps }) { + const mountOptions = getMountOptions({ + ...customMountOptions + }); + console.log(mountOptions.global); + + // Modify/augment default mount options + mountOptions.global.mocks["$store"] = store; + mountOptions.global.mixins = [mockMixin]; + mountOptions.propsData = props; + const wrapper = shallowMount(servicePackageQuestion, mountOptions); + + return wrapper; +} + /////////////// + // Constants // + /////////////// + +let processedCmsContent; + +let mockMixin; + +let mockProps; \ No newline at end of file diff --git a/src/layouts/quote/service-package-question/service-package-radio/service-package-radio.spec.js b/src/layouts/quote/service-package-question/service-package-radio/service-package-radio.spec.js new file mode 100644 index 000000000..4684e5212 --- /dev/null +++ b/src/layouts/quote/service-package-question/service-package-radio/service-package-radio.spec.js @@ -0,0 +1,128 @@ +import { mount } from "@vue/test-utils"; +import servicePackageRadio from "./service-package-radio"; +import inputButtonWrapperMixin from "@/mixins/input-button-wrapper-mixin"; + +describe("service-package-radio.vue", () => { + it("Should include buttonLabel in html", async () => { + // Arrange + let { wrapper } = setupMocks({ + mountOptionsMockData: { + propsData: mockProps, + }, + }); + + // Act + const outputHtml = wrapper.html(); + + // Assert + expect(outputHtml).toEqual(expect.stringContaining(mockProps['buttonLabel'])); + }); + + it("Should include buttonLabelAuxillaryCopy in html", async () => { + // Arrange + let { wrapper } = setupMocks({ + mountOptionsMockData: { + propsData: mockProps, + }, + }); + + // Act + const outputHtml = wrapper.html(); + + // Assert + expect(outputHtml).toEqual(expect.stringContaining(mockProps['buttonLabelAuxillaryCopy'])); + }); + + it("Should include buttonLabelSubCopy in html", async () => { + // Arrange + let { wrapper } = setupMocks({ + mountOptionsMockData: { + propsData: mockProps, + }, + }); + + // Act + const outputHtml = wrapper.html(); + + // Assert + expect(outputHtml).toEqual(expect.stringContaining(mockProps['buttonLabelSubCopy'])); + }); + it("Should include buttonFooterCopy in html", async () => { + // Arrange + let { wrapper } = setupMocks({ + mountOptionsMockData: { + propsData: mockProps, + }, + }); + + // Act + const outputHtml = wrapper.html(); + + // Assert + expect(outputHtml).toEqual(expect.stringContaining(mockProps['buttonFooterCopy'])); + }); + it("Should get 5 strings from getArrayOfListItemsFromRawCmsCopy when provided with a buttonBodyCopy", async () => { + // Arrange + let { wrapper } = setupMocks({ + mountOptionsMockData: { + propsData: mockProps, + }, + }); + // Act + const results = wrapper.vm.getArrayOfListItemsFromRawCmsCopy(wrapper.vm.buttonBodyCopy); + + // Assert + expect(results.length).toBe(5); + }); + it("Should get strings from getArrayOfListItemsFromRawCmsCopy without