diff --git a/src/constants/experiments.js b/src/constants/experiments.js index f89aad963..b702b11bb 100644 --- a/src/constants/experiments.js +++ b/src/constants/experiments.js @@ -6,6 +6,8 @@ const experimentSettings = { GOOGLE_CUSTOM_DIMENSION_INDEX: "Google Custom Dimension Index", SUPPRESS_VIN_CAPTURE: "SuppressVinCapture", DISPLAY_AVAILABILITY_INDICATORS: "DisplayAvailabilityIndicators", + PIA_EXPERIENCE: "PIA Experience", + SUBMIT_ORDER_ENABLE_PIA: "SubmitOrder_Enable_PIA", }; const experimentTriggers = { diff --git a/src/constants/payment-method-constants.js b/src/constants/payment-method-constants.js new file mode 100644 index 000000000..807e44c0e --- /dev/null +++ b/src/constants/payment-method-constants.js @@ -0,0 +1,13 @@ +export const paymentMethods = { + NONE: null, + LATER: "Later", + CREDIT_CARD: "CreditCard", + PAYPAL: "Paypal", + AFTERPAY: "Afterpay", +}; + +export const paymentTimes = { + NONE: null, + ADVANCE: "Advance", + LATER: "Later", +}; diff --git a/src/layouts/payment-method/payment-method-question/payment-method-list-button/payment-method-list-button.spec.js b/src/layouts/payment-method/payment-method-question/payment-method-list-button/payment-method-list-button.spec.js new file mode 100644 index 000000000..8cf6c55bb --- /dev/null +++ b/src/layouts/payment-method/payment-method-question/payment-method-list-button/payment-method-list-button.spec.js @@ -0,0 +1,104 @@ +import { shallowMount } from "@vue/test-utils"; +import { getMountOptions } from "@/helpers/unit-test-helper.js"; + +import paymentMethodListButton from "@/layouts/payment-method/payment-method-question/payment-method-list-button/payment-method-list-button"; + +const testConstants = { + images: { + A: "imageA", + B: "imageB", + NONE: null, + }, + names: { + A: "nameA", + B: "nameB", + }, + content: { + withInline: "Button text with {custom:inlineImage} inline.", + noInline: "Button text with no inline", + }, +}; + +let cmsContent; + +describe("Payment Method Question", () => { + beforeEach(() => { + cmsContent = {}; + }); + + describe("Side image", () => { + it("Renders side image if image is present and not inline", () => { + // Arrange + const props = generateDefaultProps(); + const { wrapper } = setupMocks({ + propsData: props, + }); + + // Act + const showSideImage = wrapper.vm.shouldDisplaySideImage; + + expect(showSideImage).toBe(true); + }); + + it("Does not render side image if no image is present", () => { + // Arrange + let props = generateDefaultProps(); + + props.buttonImage = testConstants.images.NONE; + + const { wrapper } = setupMocks({ + propsData: props, + }); + + // Act + const showSideImage = wrapper.vm.shouldDisplaySideImage; + + expect(showSideImage).toBe(false); + }); + + it("Does not render side image if inline", () => { + // Arrange + let props = generateDefaultProps(); + + props.buttonLabel = testConstants.content.withInline; + props.altText = testConstants.content.withInline; + + const { wrapper } = setupMocks({ + propsData: props, + }); + + // Act + const showSideImage = wrapper.vm.shouldDisplaySideImage; + + expect(showSideImage).toBe(false); + }); + }); +}); + +function generateDefaultProps() { + return { + buttonLabel: testConstants.noInline, + altText: testConstants.noInline, + groupName: "payment-method", + value: testConstants.names.A, + buttonImage: testConstants.images.A, + }; +} + +function setupMocks(customMountOptions) { + const mountOptions = getMountOptions(customMountOptions); + + const mockMixin = { + methods: { + getCmsContent: jest.fn((widgetName, cmsFieldName) => { + return cmsContent?.[widgetName]?.[cmsFieldName] ?? ""; + }), + }, + }; + + mountOptions.global.mixins = [mockMixin]; + + const wrapper = shallowMount(paymentMethodListButton, mountOptions); + wrapper.vm.setCmsContent = jest.fn(); + return { wrapper }; +} diff --git a/src/layouts/payment-method/payment-method-question/payment-method-list-button/payment-method-list-button.vue b/src/layouts/payment-method/payment-method-question/payment-method-list-button/payment-method-list-button.vue new file mode 100644 index 000000000..2763f1af9 --- /dev/null +++ b/src/layouts/payment-method/payment-method-question/payment-method-list-button/payment-method-list-button.vue @@ -0,0 +1,123 @@ + + + + + diff --git a/src/layouts/payment-method/payment-method-question/payment-method-question.spec.js b/src/layouts/payment-method/payment-method-question/payment-method-question.spec.js new file mode 100644 index 000000000..d3a486d57 --- /dev/null +++ b/src/layouts/payment-method/payment-method-question/payment-method-question.spec.js @@ -0,0 +1,130 @@ +import { shallowMount } from "@vue/test-utils"; +import { getMountOptions } from "@/helpers/unit-test-helper.js"; + +import paymentMethodQuestion from "@/layouts/payment-method/payment-method-question/payment-method-question"; + +const testConstants = { + cms: { + question: { + text: "Choose a payment option", + }, + answers: { + optionA: { + name: "NameA", + text: "TextA", + subText: "SubTextA", + imageId: "idA", + image: "imageA", + subWidget: "subWidgetA", + }, + optionB: { + name: "NameB", + text: "TextB", + subText: "SubTextB", + imageId: "idB", + image: "imageB", + subWidget: "subWidgetB", + }, + }, + }, +}; + +let cmsContent; + +describe("Payment Method Question", () => { + beforeEach(() => { + cmsContent = { + PaymentMethodWidget: { + QuestionText: testConstants.cms.question, + Answers: [ + { + Name: testConstants.cms.answers.optionA.name, + Text: testConstants.cms.answers.optionA.text, + SubText: testConstants.cms.answers.optionA.subText, + ImageId: testConstants.cms.answers.optionA.imageId, + AnswerImageUrl: testConstants.cms.answers.optionA.image, + SubWidgetName: testConstants.cms.answers.optionA.subWidget, + }, + { + Name: testConstants.cms.answers.optionB.name, + Text: testConstants.cms.answers.optionB.text, + SubText: testConstants.cms.answers.optionB.subText, + ImageId: testConstants.cms.answers.optionB.imageId, + AnswerImageUrl: testConstants.cms.answers.optionB.image, + SubWidgetName: testConstants.cms.answers.optionB.subWidget, + }, + ], + }, + }; + }); + + it("Should map cms content correctly", () => { + // Arrange + const props = generateDefaultProps(); + + const { wrapper } = setupMocks({ + propsData: props, + }); + + // Act + const mappedContent = wrapper.vm.paymentMethodAnswerData; + + // Assert + expect(mappedContent).toEqual([ + { + buttonLabel: testConstants.cms.answers.optionA.text, + altText: testConstants.cms.answers.optionA.text, + groupName: "payment-method", + value: testConstants.cms.answers.optionA.name, + buttonImage: testConstants.cms.answers.optionA.image, + }, + { + buttonLabel: testConstants.cms.answers.optionB.text, + altText: testConstants.cms.answers.optionB.text, + groupName: "payment-method", + value: testConstants.cms.answers.optionB.name, + buttonImage: testConstants.cms.answers.optionB.image, + }, + ]); + }); + + it("Handles null cms content gracefully", () => { + // Arrange + cmsContent = {}; + const props = generateDefaultProps(); + + const { wrapper } = setupMocks({ + propsData: props, + }); + + // Act + const mappedContent = wrapper.vm.paymentMethodAnswerData; + + // Assert + expect(mappedContent).toEqual([]); + }); +}); + +function generateDefaultProps() { + return { + modelValue: "modelValue", + }; +} + +function setupMocks(customMountOptions) { + const mountOptions = getMountOptions(customMountOptions); + + const mockMixin = { + methods: { + getCmsContent: jest.fn((widgetName, cmsFieldName) => { + return cmsContent?.[widgetName]?.[cmsFieldName] ?? ""; + }), + }, + }; + + mountOptions.global.mixins = [mockMixin]; + + const wrapper = shallowMount(paymentMethodQuestion, mountOptions); + wrapper.vm.setCmsContent = jest.fn(); + return { wrapper }; +} diff --git a/src/layouts/payment-method/payment-method-question/payment-method-question.vue b/src/layouts/payment-method/payment-method-question/payment-method-question.vue new file mode 100644 index 000000000..473e36114 --- /dev/null +++ b/src/layouts/payment-method/payment-method-question/payment-method-question.vue @@ -0,0 +1,80 @@ + + + + + diff --git a/src/layouts/payment-method/payment-method.spec.js b/src/layouts/payment-method/payment-method.spec.js index 130a2def4..803f10829 100644 --- a/src/layouts/payment-method/payment-method.spec.js +++ b/src/layouts/payment-method/payment-method.spec.js @@ -5,6 +5,7 @@ import customerDetails from "@/layouts/payment-method/payment-method.vue"; import { shallowMount } from "@vue/test-utils"; import { getMountOptions } from "@/helpers/unit-test-helper.js"; import store from "@/store"; +import { experimentSettings } from "@/constants/experiments"; // Mock our module for promises. jest.mock("@/helpers/layout-helper.js", () => ({ @@ -15,6 +16,8 @@ jest.mock("@/helpers/heritage-integration/order-helper.js", () => ({ submitWorkOrder: jest.fn(), })); +let piaDisabledFlag = false; + describe("payment-method.vue", () => { describe("navigation", () => { test("if the back button is clicked, navigate back", async () => { @@ -52,19 +55,37 @@ function setupMocks() { }, }; - const wrapper = shallowMount( - customerDetails, - getMountOptions({ - router: { - navigate: jest.fn(), - navigateWithSaving: jest.fn(), - navigateWithoutSaving: jest.fn(), - }, - store: { - getters: store.getters, - }, - }) - ); + const mountOptions = getMountOptions({ + router: { + navigate: jest.fn(), + navigateWithSaving: jest.fn(), + navigateWithoutSaving: jest.fn(), + }, + store: { + getters: store.getters, + }, + }); + + const mockMixin = { + methods: { + getSettingValue: jest.fn((settingName) => { + if (!piaDisabledFlag) { + if (settingName === experimentSettings.PIA_EXPERIENCE) { + return "PIA Optional"; + } + if (settingName === experimentSettings.SUBMIT_ORDER_ENABLE_PIA) { + return "true"; + } + } + + return "false"; + }), + }, + }; + + mountOptions.global.mixins = [mockMixin]; + + const wrapper = shallowMount(customerDetails, mountOptions); return { wrapper }; } diff --git a/src/layouts/payment-method/payment-method.vue b/src/layouts/payment-method/payment-method.vue index 28fe0fd31..eac00fe15 100644 --- a/src/layouts/payment-method/payment-method.vue +++ b/src/layouts/payment-method/payment-method.vue @@ -38,46 +38,13 @@ :rainDefenseItem="availableRainDefense" vapsTilesCmsName="VapsProductTiles" /> -
- - - - -
-
- -
-
- -
+ + diff --git a/src/store/index.js b/src/store/index.js index add9f355f..2c2eb8492 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -598,7 +598,10 @@ export const getters = { funnelServiceCity: state.order.serviceLocation.city, funnelServiceState: state.order.serviceLocation.state, funnelServiceZipCode: state.order.serviceLocation.zipCode, + funnelServiceZipCodeCtu: state.order.serviceLocation.zipCodeCtu, + funnelProviderNumber: state.order.serviceLocation.provider.providerNumber, funnelParentAccountNumber: state.order.payment.parentAccountNumber, + funnelReferralType: state.order.payment.isInsurance ? "INSURANCE" : "CASH QUOTE", funnelIsCoverageVerified: state.order.payment.insuranceCoverage.isVerified, funnelHasRecalibrationPart: getHasRecalibrationPart(state), funnelSelectedMultiGlass: state.order.damage.glassToReplace?.length > 1, diff --git a/src/store/store.spec.js b/src/store/store.spec.js index d55862e3e..9ae8065ae 100644 --- a/src/store/store.spec.js +++ b/src/store/store.spec.js @@ -2934,6 +2934,9 @@ describe("Getters", () => { funnelSupportingItems: null, funnelVaps: null, funnelGlassToReplace: null, + funnelProviderNumber: "1", + funnelReferralType: "CASH QUOTE", + funnelServiceZipCodeCtu: "11111", }; //Act @@ -2948,12 +2951,17 @@ describe("Getters", () => { city: mockStateValues.funnelServiceCity, state: mockStateValues.funnelServiceState, zipCode: mockStateValues.funnelServiceZipCode, + zipCodeCtu: mockStateValues.funnelServiceZipCodeCtu, + provider: { + providerNumber: mockStateValues.funnelProviderNumber, + }, }); mutations.updateParentAcctNumber(storeState, mockStateValues.funnelParentAccountNumber); mutations.updateInsuranceVerifiedStatus( storeState, mockStateValues.funnelIsCoverageVerified ); + mutations.updateIsInsurance(storeState, false); mutations.updateGlassParts(storeState, mockStateValues.funnelGlassParts); mutations.updateSupportingItems(storeState, mockStateValues.funnelSupportingItems); mutations.updateVaps(storeState, mockStateValues.funnelVaps); @@ -2981,6 +2989,9 @@ describe("Getters", () => { funnelSelectedBackGlass: false, funnelSelectedDriverSideGlass: false, funnelSelectedPassengerSideGlass: false, + funnelProviderNumber: mockStateValues.funnelProviderNumber, + funnelReferralType: mockStateValues.funnelReferralType, + funnelServiceZipCodeCtu: mockStateValues.funnelServiceZipCodeCtu, }); }); @@ -3003,6 +3014,9 @@ describe("Getters", () => { funnelGlassParts: [], funnelOtherParts: [], funnelGlassToReplace: [], + funnelProviderNumber: "1", + funnelReferralType: "CASH QUOTE", + funnelServiceZipCodeCtu: "11111", }; //Act @@ -3017,12 +3031,17 @@ describe("Getters", () => { city: mockStateValues.funnelServiceCity, state: mockStateValues.funnelServiceState, zipCode: mockStateValues.funnelServiceZipCode, + zipCodeCtu: mockStateValues.funnelServiceZipCodeCtu, + provider: { + providerNumber: mockStateValues.funnelProviderNumber, + }, }); mutations.updateParentAcctNumber(storeState, mockStateValues.funnelParentAccountNumber); mutations.updateInsuranceVerifiedStatus( storeState, mockStateValues.funnelIsCoverageVerified ); + mutations.updateIsInsurance(storeState, false); mutations.updateGlassParts(storeState, mockStateValues.funnelGlassParts); mutations.updateSupportingItems(storeState, mockStateValues.funnelSupportingItems); mutations.updateVaps(storeState, mockStateValues.funnelVaps); @@ -3050,6 +3069,9 @@ describe("Getters", () => { funnelSelectedBackGlass: false, funnelSelectedDriverSideGlass: false, funnelSelectedPassengerSideGlass: false, + funnelProviderNumber: mockStateValues.funnelProviderNumber, + funnelReferralType: mockStateValues.funnelReferralType, + funnelServiceZipCodeCtu: mockStateValues.funnelServiceZipCodeCtu, }); }); @@ -3085,6 +3107,9 @@ describe("Getters", () => { glassName: "Single", }, ], + funnelProviderNumber: "1", + funnelReferralType: "CASH QUOTE", + funnelServiceZipCodeCtu: "11111", }; //Act @@ -3099,9 +3124,14 @@ describe("Getters", () => { city: mockStateValues.serviceCity, state: mockStateValues.serviceState, zipCode: mockStateValues.serviceZipCode, + zipCodeCtu: mockStateValues.funnelServiceZipCodeCtu, + provider: { + providerNumber: mockStateValues.funnelProviderNumber, + }, }); mutations.updateParentAcctNumber(storeState, mockStateValues.parentAccountNumber); mutations.updateInsuranceVerifiedStatus(storeState, mockStateValues.isCoverageVerified); + mutations.updateIsInsurance(storeState, false); mutations.updateGlassParts(storeState, mockStateValues.glassParts); mutations.updateSupportingItems(storeState, mockStateValues.funnelSupportingItems); mutations.updateVaps(storeState, mockStateValues.funnelVaps); @@ -3129,6 +3159,9 @@ describe("Getters", () => { funnelSelectedBackGlass: false, funnelSelectedDriverSideGlass: false, funnelSelectedPassengerSideGlass: false, + funnelProviderNumber: mockStateValues.funnelProviderNumber, + funnelReferralType: mockStateValues.funnelReferralType, + funnelServiceZipCodeCtu: mockStateValues.funnelServiceZipCodeCtu, }); }); @@ -3190,6 +3223,9 @@ describe("Getters", () => { glassName: "Quarter", }, ], + funnelProviderNumber: "1", + funnelReferralType: "CASH QUOTE", + funnelServiceZipCodeCtu: "11111", }; //Act @@ -3204,9 +3240,14 @@ describe("Getters", () => { city: mockStateValues.serviceCity, state: mockStateValues.serviceState, zipCode: mockStateValues.serviceZipCode, + zipCodeCtu: mockStateValues.funnelServiceZipCodeCtu, + provider: { + providerNumber: mockStateValues.funnelProviderNumber, + }, }); mutations.updateParentAcctNumber(storeState, mockStateValues.parentAccountNumber); mutations.updateInsuranceVerifiedStatus(storeState, mockStateValues.isCoverageVerified); + mutations.updateIsInsurance(storeState, false); mutations.updateGlassParts(storeState, mockStateValues.glassParts); mutations.updateSupportingItems(storeState, mockStateValues.funnelSupportingItems); mutations.updateVaps(storeState, mockStateValues.funnelVaps); @@ -3234,6 +3275,9 @@ describe("Getters", () => { funnelSelectedBackGlass: true, funnelSelectedDriverSideGlass: true, funnelSelectedPassengerSideGlass: true, + funnelProviderNumber: mockStateValues.funnelProviderNumber, + funnelReferralType: mockStateValues.funnelReferralType, + funnelServiceZipCodeCtu: mockStateValues.funnelServiceZipCodeCtu, }); }); });