diff --git a/src/constants/save-progress-cms-widgets.js b/src/constants/save-progress-cms-widgets.js new file mode 100644 index 000000000..d80960ace --- /dev/null +++ b/src/constants/save-progress-cms-widgets.js @@ -0,0 +1,10 @@ +const saveProgressCmsWidgets = { + PHONE_SPECIFIC: "SaveProgressPopupWidget_PhoneSpecific", + EMAIL_SPECIFIC: "SaveProgressPopupWidget_EmailSpecific", + MODAL_PHONE_QUESTION: "SaveProgressPhoneQuestionWidget", + MODAL_EMAIL_QUESTION: "SaveProgressEmailQuestionWidget", + POPUP_PHONE_QUESTION: "SaveProgressPopupPhoneQuestionWidget", + POPUP_EMAIL_QUESTION: "SaveProgressPopupEmailQuestionWidget", +}; + +export { saveProgressCmsWidgets }; diff --git a/src/fmg-components/save-progress-modal-question/save-progress-modal-question.spec.js b/src/fmg-components/save-progress-modal-question/save-progress-modal-question.spec.js index 15fb1481d..0f27858de 100644 --- a/src/fmg-components/save-progress-modal-question/save-progress-modal-question.spec.js +++ b/src/fmg-components/save-progress-modal-question/save-progress-modal-question.spec.js @@ -1,43 +1,261 @@ -import { mount, shallowMount } from "@vue/test-utils"; +import { mount } from "@vue/test-utils"; import saveProgressModalQuestion from "./save-progress-modal-question"; import { getMountOptions } from "@/helpers/unit-test-helper.js"; +import { storeActions } from "@/constants/store-actions"; +import { saveProgressCmsWidgets } from "@/constants/save-progress-cms-widgets"; +import { saveQuote } from "@/helpers/heritage-integration/order-helper.js"; +import experimentMixin from "@/mixins/experiment-mixin.js"; +import { experimentSettings } from "@/constants/experiments"; + +jest.mock("@/helpers/heritage-integration/order-helper.js", () => ({ + saveQuote: jest.fn().mockResolvedValue(undefined), +})); + +jest.mock("@/helpers/save-progress-sms-consent/save-progress-sms-consent-helper", () => ({ + ...jest.requireActual("@/helpers/save-progress-sms-consent/save-progress-sms-consent-helper"), + getSaveProgressSmsConsentConfig: jest.fn().mockReturnValue({ + copy: { + transactional: "Sign me up for updates about my upcoming service.", + marketing: "Sign me up for promotional and product offers.", + }, + value: { transactional: false, marketing: false }, + }), +})); jest.mock("@/digital-components/modal/modal", () => ({ methods: { openModal: jest.fn(), + closeModal: jest.fn(), resetButtonStyle: jest.fn(), + resetForm: jest.fn(), + validate: jest.fn().mockResolvedValue({ valid: true }), }, })); -describe("save-progress-modal-question ", () => { - describe("when openModal is run ", () => { - test("the modal should open ", () => { - // Arrange +jest.mock("@/ux-components/alert/alert", () => ({ + name: "alert", + template: "
", +})); + +describe("save-progress-modal-question", () => { + beforeEach(() => { + jest.clearAllMocks(); + experimentMixin.methods.hasSettingEqualTo = jest.fn().mockReturnValue(false); + }); + + describe("when openModal is run", () => { + test("the modal should open", () => { const { wrapper } = setupMocks({ props: { - modelValue: "", modalWidgetName: "testModal", }, }); - // Act wrapper.vm.openModal(); - // Assert expect(wrapper.vm.modal).not.toBeNull(); }); }); + + describe("legacy email-only mode", () => { + test("should save email to the store", async () => { + const { wrapper, dispatchStoreAction } = setupMocks({ + props: { + modalWidgetName: "SaveProgressModalWidget", + pageName: "part-questions", + }, + }); + + wrapper.vm.userInput = "test@example.com"; + await wrapper.vm.saveProgress(); + + expect(dispatchStoreAction).toHaveBeenCalledWith( + storeActions.SAVE_EMAIL, + "test@example.com", + false + ); + expect(saveQuote).toHaveBeenCalledWith({ pageNameToLog: "part-questions" }); + expect(wrapper.vm.isProgressSaved).toBe(true); + }); + + test("should use the legacy disclaimer widget", () => { + const { wrapper } = setupMocks({ + props: { + modalWidgetName: "SaveProgressModalWidget", + }, + cmsContent: { + SaveProgressModalWidget: { FooterText2: "Legacy disclaimer" }, + }, + }); + + expect(wrapper.vm.modalDisclaimerText).toBe("Legacy disclaimer"); + }); + }); + + describe("consent management mode", () => { + beforeEach(() => { + experimentMixin.methods.hasSettingEqualTo = jest + .fn() + .mockImplementation( + (settingName, settingValue) => + settingName === experimentSettings.SHOW_CONSENT_MANAGEMENT && + settingValue === "true" + ); + }); + + test("should default to the phone tab when opened", () => { + const { wrapper } = setupMocks({ + props: { + modalWidgetName: "SaveProgressModalWidget", + }, + }); + + wrapper.vm.openModal(); + + expect(wrapper.vm.isPhoneTabSelected).toBe(true); + }); + + test("should use popup-specific tab labels from CMS widgets", () => { + const { wrapper } = setupMocks({ + props: { + modalWidgetName: "SaveProgressModalWidget", + }, + cmsContent: { + SaveProgressPopupWidget_PhoneSpecific: { HeadlineText: "PHONE" }, + SaveProgressPopupWidget_EmailSpecific: { HeadlineText: "EMAIL" }, + }, + }); + + expect(wrapper.vm.contactTabs).toEqual([ + { label: "PHONE", value: "PhoneAnswer" }, + { label: "EMAIL", value: "EmailAnswer" }, + ]); + }); + + test("should use the modal question CMS widgets for phone and email inputs", () => { + const { wrapper } = setupMocks({ + props: { + modalWidgetName: "SaveProgressModalWidget", + }, + }); + + expect(wrapper.vm.phoneQuestionWidgetName).toBe( + saveProgressCmsWidgets.MODAL_PHONE_QUESTION + ); + expect(wrapper.vm.emailQuestionWidgetName).toBe( + saveProgressCmsWidgets.MODAL_EMAIL_QUESTION + ); + }); + + test("should use the email-specific popup disclaimer on the email tab", async () => { + const { wrapper } = setupMocks({ + props: { + modalWidgetName: "SaveProgressModalWidget", + }, + cmsContent: { + SaveProgressPopupWidget_EmailSpecific: { BodyText: "Email disclaimer" }, + SaveProgressPopupWidget_PhoneSpecific: { BodyText: "Phone disclaimer" }, + }, + }); + + wrapper.vm.selectContactMethod("EmailAnswer"); + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.modalDisclaimerText).toBe("Email disclaimer"); + }); + + test("should use the phone-specific popup disclaimer on the phone tab", () => { + const { wrapper } = setupMocks({ + props: { + modalWidgetName: "SaveProgressModalWidget", + }, + cmsContent: { + SaveProgressPopupWidget_EmailSpecific: { BodyText: "Email disclaimer" }, + SaveProgressPopupWidget_PhoneSpecific: { BodyText: "Phone disclaimer" }, + }, + }); + + wrapper.vm.contactMethod = "PhoneAnswer"; + + expect(wrapper.vm.modalDisclaimerText).toBe("Phone disclaimer"); + }); + + test("should show consent errors when phone is valid but transactional consent is missing", async () => { + const { wrapper } = setupMocks({ + props: { + modalWidgetName: "SaveProgressModalWidget", + }, + }); + + wrapper.vm.contactMethod = "PhoneAnswer"; + wrapper.vm.userInput = "5551234567"; + wrapper.vm.smsConsent = { transactional: false, marketing: false }; + + await wrapper.vm.validatePhoneAndSave(); + + expect(wrapper.vm.showConsentErrors).toBe(true); + }); + + test("should save phone and sms consent when valid", async () => { + const { wrapper, dispatchStoreAction } = setupMocks({ + props: { + modalWidgetName: "SaveProgressModalWidget", + pageName: "vehicle-parts", + }, + }); + + wrapper.vm.contactMethod = "PhoneAnswer"; + wrapper.vm.userInput = "555-123-4567"; + wrapper.vm.smsConsent = { transactional: true, marketing: true }; + + await wrapper.vm.saveProgress(); + + expect(dispatchStoreAction).toHaveBeenCalledWith( + storeActions.SAVE_PHONE_NUMBER, + "5551234567", + false + ); + expect(dispatchStoreAction).toHaveBeenCalledWith( + storeActions.SAVE_IS_SMS_OPT_IN, + true, + false + ); + expect(dispatchStoreAction).toHaveBeenCalledWith( + storeActions.SAVE_IS_SMS_MARKETING_OPT_IN, + true, + false + ); + expect(saveQuote).toHaveBeenCalledWith({ pageNameToLog: "vehicle-parts" }); + }); + + test("should reset sms consent when switching tabs", async () => { + const { wrapper } = setupMocks({ + props: { + modalWidgetName: "SaveProgressModalWidget", + }, + }); + + wrapper.vm.contactMethod = "PhoneAnswer"; + wrapper.vm.smsConsent = { transactional: true, marketing: true }; + wrapper.vm.selectContactMethod("EmailAnswer"); + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.smsConsent).toEqual({ transactional: false, marketing: false }); + }); + }); }); -function setupMocks({ options, props }) { +function setupMocks({ options, props, cmsContent = {} }) { const mountOptions = getMountOptions({ ...options, }); + const dispatchStoreAction = jest.fn().mockResolvedValue(undefined); + const mockBaseMixin = { methods: { - getCmsContent: jest.fn(), - dispatchStoreAction: jest.fn(), + getCmsContent: jest.fn((widgetName, fieldName) => cmsContent[widgetName]?.[fieldName]), + dispatchStoreAction, }, }; @@ -47,5 +265,5 @@ function setupMocks({ options, props }) { const wrapper = mount(saveProgressModalQuestion, mountOptions); - return { wrapper }; + return { wrapper, dispatchStoreAction }; } diff --git a/src/fmg-components/save-progress-modal-question/save-progress-modal-question.vue b/src/fmg-components/save-progress-modal-question/save-progress-modal-question.vue index 3bcc1a7a4..361a31869 100644 --- a/src/fmg-components/save-progress-modal-question/save-progress-modal-question.vue +++ b/src/fmg-components/save-progress-modal-question/save-progress-modal-question.vue @@ -17,13 +17,66 @@ :onModalClosedCallback="onModalClosed" :footerButtonText="modalButtonText" :isFooterButtonPrimary="true" + :isFooterButtonSuppressed="isPhoneTabSelected && !isProgressSaved" @footer-button-event="saveProgress"> + + +