CASH-2934: Add phone option and consent checks to quote page modal
This commit is contained in:
parent
4a96e363c2
commit
04c5ce1cfc
16 changed files with 1009 additions and 21 deletions
|
|
@ -32,6 +32,7 @@ const errorMessages = {
|
||||||
DATE_REQUIRED: "Please select a date",
|
DATE_REQUIRED: "Please select a date",
|
||||||
PHONE_REQUIRED: "Please enter your phone number",
|
PHONE_REQUIRED: "Please enter your phone number",
|
||||||
PHONE_FORMAT: "Phone number must be 10 digits",
|
PHONE_FORMAT: "Phone number must be 10 digits",
|
||||||
|
SMS_CONSENT_REQUIRED: "Please select at least one consent option",
|
||||||
YEAR_REQUIRED: "Please select your vehicle year",
|
YEAR_REQUIRED: "Please select your vehicle year",
|
||||||
MAKE_REQUIRED: "Please select your vehicle make",
|
MAKE_REQUIRED: "Please select your vehicle make",
|
||||||
MODEL_REQUIRED: "Please select your vehicle model",
|
MODEL_REQUIRED: "Please select your vehicle model",
|
||||||
|
|
|
||||||
|
|
@ -89,4 +89,15 @@ describe("checkbox-question.vue", () => {
|
||||||
|
|
||||||
expect(paragraph.text()).toEqual("screenreader text");
|
expect(paragraph.text()).toEqual("screenreader text");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("Should prefer labelText prop over CMS content", () => {
|
||||||
|
const wrapper = shallowMount(checkboxQuestion, {
|
||||||
|
propsData: {
|
||||||
|
labelText: "Hardcoded label",
|
||||||
|
},
|
||||||
|
mixins: [mockMixin],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(wrapper.vm.checkboxLabelCopy).toBe("Hardcoded label");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
:validationRules="validationRules"
|
:validationRules="validationRules"
|
||||||
:id="inputId"
|
:id="inputId"
|
||||||
:tabindex="tabIndex"
|
:tabindex="tabIndex"
|
||||||
|
:disabled="isDisabled"
|
||||||
:aria-required="isRequired" />
|
:aria-required="isRequired" />
|
||||||
<textBlock
|
<textBlock
|
||||||
class="m-0"
|
class="m-0"
|
||||||
|
|
@ -34,7 +35,7 @@ export default {
|
||||||
name: "checkboxQuestion",
|
name: "checkboxQuestion",
|
||||||
computed: {
|
computed: {
|
||||||
checkboxLabelCopy() {
|
checkboxLabelCopy() {
|
||||||
return this.getCmsContent(this.cmsWidgetName, "QuestionText");
|
return this.labelText || this.getCmsContent(this.cmsWidgetName, "QuestionText");
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
|
|
@ -72,11 +73,13 @@ export default {
|
||||||
customInputId: String,
|
customInputId: String,
|
||||||
validationRules: String,
|
validationRules: String,
|
||||||
cmsWidgetName: String,
|
cmsWidgetName: String,
|
||||||
|
labelText: String,
|
||||||
checkboxName: String,
|
checkboxName: String,
|
||||||
buttonID: String,
|
buttonID: String,
|
||||||
tabIndex: Number,
|
tabIndex: Number,
|
||||||
screenReaderOnlyText: String,
|
screenReaderOnlyText: String,
|
||||||
isRequired: Boolean,
|
isRequired: Boolean,
|
||||||
|
isDisabled: Boolean,
|
||||||
hasError: Boolean,
|
hasError: Boolean,
|
||||||
modelValue: {
|
modelValue: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
|
|
|
||||||
|
|
@ -61,4 +61,24 @@ describe("phone-number-question.vue", () => {
|
||||||
"outsideValidation|phone-number-format"
|
"outsideValidation|phone-number-format"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("Should pass placeholderText through to textbox-question", async () => {
|
||||||
|
const wrapper = shallowMount(phoneNumberQuestion, {
|
||||||
|
propsData: {
|
||||||
|
placeholderText: "",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const phoneNumber = wrapper.findComponent({ ref: "phoneNumber" });
|
||||||
|
|
||||||
|
expect(phoneNumber.props("placeholderText")).toBe("");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should default placeholderText to masked phone format", async () => {
|
||||||
|
const wrapper = shallowMount(phoneNumberQuestion, {});
|
||||||
|
|
||||||
|
const phoneNumber = wrapper.findComponent({ ref: "phoneNumber" });
|
||||||
|
|
||||||
|
expect(phoneNumber.props("placeholderText")).toBe("###-###-####");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,9 @@
|
||||||
:max-length="12"
|
:max-length="12"
|
||||||
v-model="selectedValue"
|
v-model="selectedValue"
|
||||||
:mask="mask"
|
:mask="mask"
|
||||||
placeholderText="###-###-####"
|
:placeholderText="placeholderText"
|
||||||
:isRequired="isRequired"
|
:isRequired="isRequired"
|
||||||
|
:isDisabled="isDisabled"
|
||||||
:validationRules="validationRulesForTextBoxQuestion"
|
:validationRules="validationRulesForTextBoxQuestion"
|
||||||
:cmsWidgetName="cmsWidgetName" />
|
:cmsWidgetName="cmsWidgetName" />
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -32,10 +33,15 @@ export default {
|
||||||
props: {
|
props: {
|
||||||
cmsWidgetName: String,
|
cmsWidgetName: String,
|
||||||
isRequired: Boolean,
|
isRequired: Boolean,
|
||||||
|
isDisabled: Boolean,
|
||||||
validationRules: String,
|
validationRules: String,
|
||||||
hasError: Boolean,
|
hasError: Boolean,
|
||||||
centerErrorMessage: Boolean,
|
centerErrorMessage: Boolean,
|
||||||
modelValue: String,
|
modelValue: String,
|
||||||
|
placeholderText: {
|
||||||
|
type: String,
|
||||||
|
default: "###-###-####",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
questionAlignment="center"
|
questionAlignment="center"
|
||||||
cornerStyle="rounded"
|
cornerStyle="rounded"
|
||||||
:displayQuestionText="true"
|
:displayQuestionText="true"
|
||||||
|
:isDisabled="isDisabled"
|
||||||
isRequired
|
isRequired
|
||||||
@input="validateEmail"
|
@input="validateEmail"
|
||||||
validationRules="email-address-required|email-address-format" />
|
validationRules="email-address-required|email-address-format" />
|
||||||
|
|
@ -37,6 +38,7 @@ export default {
|
||||||
props: {
|
props: {
|
||||||
modelValue: String,
|
modelValue: String,
|
||||||
cmsWidgetName: String,
|
cmsWidgetName: String,
|
||||||
|
isDisabled: Boolean,
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
value: {
|
value: {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,23 @@
|
||||||
import { mount, shallowMount } from "@vue/test-utils";
|
import { mount, flushPromises } from "@vue/test-utils";
|
||||||
import saveProgressPopupQuestion from "./save-progress-popup-question";
|
import saveProgressPopupQuestion from "./save-progress-popup-question";
|
||||||
import { getMountOptions } from "@/helpers/unit-test-helper.js";
|
import { getMountOptions } from "@/helpers/unit-test-helper.js";
|
||||||
|
import { storeActions } from "@/constants/store-actions";
|
||||||
|
import { saveQuote } from "@/helpers/heritage-integration/order-helper.js";
|
||||||
|
|
||||||
|
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().mockResolvedValue({
|
||||||
|
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", () => ({
|
jest.mock("@/digital-components/modal/modal", () => ({
|
||||||
methods: {
|
methods: {
|
||||||
|
|
@ -15,7 +32,6 @@ describe("save-progress-popup-question ", () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const { wrapper } = setupMocks({
|
const { wrapper } = setupMocks({
|
||||||
props: {
|
props: {
|
||||||
modelValue: "",
|
|
||||||
modalWidgetName: "testModal",
|
modalWidgetName: "testModal",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
@ -27,16 +43,276 @@ describe("save-progress-popup-question ", () => {
|
||||||
expect(wrapper.vm.modal).not.toBeNull();
|
expect(wrapper.vm.modal).not.toBeNull();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("contact method tabs", () => {
|
||||||
|
test("should default to the phone tab", () => {
|
||||||
|
const { wrapper } = setupMocks({
|
||||||
|
props: {
|
||||||
|
modalWidgetName: "SaveProgressPopupWidget",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(wrapper.vm.isPhoneTabSelected).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should use tab labels from phone and email specific CMS widgets", () => {
|
||||||
|
const { wrapper } = setupMocks({
|
||||||
|
props: {
|
||||||
|
modalWidgetName: "SaveProgressPopupWidget",
|
||||||
|
},
|
||||||
|
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 disclaimer BodyText from the selected tab widget", async () => {
|
||||||
|
const { wrapper } = setupMocks({
|
||||||
|
props: {
|
||||||
|
modalWidgetName: "SaveProgressPopupWidget",
|
||||||
|
},
|
||||||
|
cmsContent: {
|
||||||
|
SaveProgressPopupWidget_PhoneSpecific: { BodyText: "Phone disclaimer" },
|
||||||
|
SaveProgressPopupWidget_EmailSpecific: { BodyText: "Email disclaimer" },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(wrapper.vm.modalDisclaimerText).toBe("Phone disclaimer");
|
||||||
|
|
||||||
|
wrapper.vm.selectContactMethod("EmailAnswer");
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
|
||||||
|
expect(wrapper.vm.modalDisclaimerText).toBe("Email disclaimer");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should use phone question CMS widget on the phone tab", () => {
|
||||||
|
const { wrapper } = setupMocks({
|
||||||
|
props: {
|
||||||
|
modalWidgetName: "SaveProgressPopupWidget",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(wrapper.vm.phoneQuestionWidgetName).toBe("SaveProgressPopupPhoneQuestionWidget");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should show email content when the email tab is selected", async () => {
|
||||||
|
const { wrapper } = setupMocks({
|
||||||
|
props: {
|
||||||
|
modalWidgetName: "SaveProgressPopupWidget",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
wrapper.vm.selectContactMethod("EmailAnswer");
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
|
||||||
|
expect(wrapper.vm.contactMethod).toBe("EmailAnswer");
|
||||||
|
expect(wrapper.vm.isPhoneTabSelected).toBe(false);
|
||||||
|
expect(wrapper.vm.userInput).toBe("");
|
||||||
|
expect(wrapper.vm.emailQuestionWidgetName).toBe("SaveProgressPopupEmailQuestionWidget");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should reset sms consent when switching tabs", async () => {
|
||||||
|
const { wrapper } = setupMocks({
|
||||||
|
props: {
|
||||||
|
modalWidgetName: "SaveProgressPopupWidget",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
wrapper.vm.smsConsent = { transactional: true, marketing: true };
|
||||||
|
wrapper.vm.showConsentErrors = true;
|
||||||
|
wrapper.vm.selectContactMethod("EmailAnswer");
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
|
||||||
|
expect(wrapper.vm.smsConsent).toEqual({ transactional: false, marketing: false });
|
||||||
|
expect(wrapper.vm.showConsentErrors).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("validatePhoneAndSave", () => {
|
||||||
|
test("should show consent errors when phone is valid but no consent is selected", async () => {
|
||||||
|
const { wrapper } = setupMocks({
|
||||||
|
props: {
|
||||||
|
modalWidgetName: "SaveProgressPopupWidget",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
wrapper.vm.modal.validate = jest.fn().mockResolvedValue({ valid: true });
|
||||||
|
const resetSpy = jest.spyOn(wrapper.vm, "resetPhoneSendButtonStyle");
|
||||||
|
|
||||||
|
await wrapper.vm.validatePhoneAndSave();
|
||||||
|
|
||||||
|
expect(wrapper.vm.showConsentErrors).toBe(true);
|
||||||
|
expect(resetSpy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should reset the send button loader when phone validation fails", async () => {
|
||||||
|
const { wrapper } = setupMocks({
|
||||||
|
props: {
|
||||||
|
modalWidgetName: "SaveProgressPopupWidget",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
wrapper.vm.modal.validate = jest.fn().mockResolvedValue({ valid: false });
|
||||||
|
const resetSpy = jest.spyOn(wrapper.vm, "resetPhoneSendButtonStyle");
|
||||||
|
|
||||||
|
await wrapper.vm.validatePhoneAndSave();
|
||||||
|
|
||||||
|
expect(wrapper.vm.showConsentErrors).toBe(false);
|
||||||
|
expect(resetSpy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should save when phone and consent are valid", async () => {
|
||||||
|
const dispatchStoreAction = jest.fn().mockResolvedValue(undefined);
|
||||||
|
const { wrapper } = setupMocks({
|
||||||
|
props: {
|
||||||
|
modalWidgetName: "SaveProgressPopupWidget",
|
||||||
|
pageName: "quote",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
|
wrapper.vm.dispatchStoreAction = dispatchStoreAction;
|
||||||
|
wrapper.vm.userInput = "555-123-4567";
|
||||||
|
wrapper.vm.smsConsent = { transactional: true, marketing: false };
|
||||||
|
wrapper.vm.modal.validate = jest.fn().mockResolvedValue({ valid: true });
|
||||||
|
const resetSpy = jest.spyOn(wrapper.vm, "resetPhoneSendButtonStyle");
|
||||||
|
|
||||||
|
await wrapper.vm.validatePhoneAndSave();
|
||||||
|
|
||||||
|
expect(wrapper.vm.showConsentErrors).toBe(false);
|
||||||
|
expect(wrapper.vm.isProgressSaved).toBe(true);
|
||||||
|
expect(resetSpy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("saveProgress", () => {
|
||||||
|
test("should save phone number and sms consent when phone tab is selected", async () => {
|
||||||
|
const dispatchStoreAction = jest.fn().mockResolvedValue(undefined);
|
||||||
|
const { wrapper } = setupMocks({
|
||||||
|
props: {
|
||||||
|
modalWidgetName: "SaveProgressPopupWidget",
|
||||||
|
pageName: "quote",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
wrapper.vm.dispatchStoreAction = dispatchStoreAction;
|
||||||
|
wrapper.vm.userInput = "555-123-4567";
|
||||||
|
wrapper.vm.smsConsent = { transactional: true, marketing: false };
|
||||||
|
|
||||||
|
await wrapper.vm.saveProgress();
|
||||||
|
|
||||||
|
expect(dispatchStoreAction).toHaveBeenCalledWith(
|
||||||
|
storeActions.SAVE_PHONE_NUMBER,
|
||||||
|
"555-123-4567",
|
||||||
|
false
|
||||||
|
);
|
||||||
|
expect(dispatchStoreAction).not.toHaveBeenCalledWith(
|
||||||
|
storeActions.SAVE_EMAIL,
|
||||||
|
expect.anything(),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
expect(dispatchStoreAction).toHaveBeenCalledWith(
|
||||||
|
storeActions.SAVE_IS_SMS_OPT_IN,
|
||||||
|
true,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
expect(saveQuote).toHaveBeenCalledWith({ pageNameToLog: "quote" });
|
||||||
|
expect(wrapper.vm.isProgressSaved).toBe(true);
|
||||||
|
expect(wrapper.vm.savedContactMethod).toBe("PhoneAnswer");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should save email when email tab is selected", async () => {
|
||||||
|
const dispatchStoreAction = jest.fn().mockResolvedValue(undefined);
|
||||||
|
const { wrapper } = setupMocks({
|
||||||
|
props: {
|
||||||
|
modalWidgetName: "SaveProgressPopupWidget",
|
||||||
|
pageName: "quote",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
wrapper.vm.dispatchStoreAction = dispatchStoreAction;
|
||||||
|
wrapper.vm.selectContactMethod("EmailAnswer");
|
||||||
|
wrapper.vm.userInput = "test@example.com";
|
||||||
|
|
||||||
|
await wrapper.vm.saveProgress();
|
||||||
|
|
||||||
|
expect(dispatchStoreAction).toHaveBeenCalledWith(
|
||||||
|
storeActions.SAVE_EMAIL,
|
||||||
|
"test@example.com",
|
||||||
|
false
|
||||||
|
);
|
||||||
|
expect(dispatchStoreAction).not.toHaveBeenCalledWith(
|
||||||
|
storeActions.SAVE_PHONE_NUMBER,
|
||||||
|
expect.anything(),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
expect(saveQuote).toHaveBeenCalledWith({ pageNameToLog: "quote" });
|
||||||
|
expect(wrapper.vm.savedContactMethod).toBe("EmailAnswer");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should not allow switching tabs after a successful phone save", async () => {
|
||||||
|
const dispatchStoreAction = jest.fn().mockResolvedValue(undefined);
|
||||||
|
const { wrapper } = setupMocks({
|
||||||
|
props: {
|
||||||
|
modalWidgetName: "SaveProgressPopupWidget",
|
||||||
|
pageName: "quote",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
wrapper.vm.dispatchStoreAction = dispatchStoreAction;
|
||||||
|
wrapper.vm.userInput = "555-123-4567";
|
||||||
|
wrapper.vm.smsConsent = { transactional: true, marketing: false };
|
||||||
|
|
||||||
|
await wrapper.vm.saveProgress();
|
||||||
|
wrapper.vm.selectContactMethod("EmailAnswer");
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
|
||||||
|
expect(wrapper.vm.contactMethod).toBe("PhoneAnswer");
|
||||||
|
expect(wrapper.vm.isContactMethodTabDisabled("EmailAnswer")).toBe(true);
|
||||||
|
expect(wrapper.vm.isContactMethodTabDisabled("PhoneAnswer")).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should not allow switching tabs after a successful email save", async () => {
|
||||||
|
const dispatchStoreAction = jest.fn().mockResolvedValue(undefined);
|
||||||
|
const { wrapper } = setupMocks({
|
||||||
|
props: {
|
||||||
|
modalWidgetName: "SaveProgressPopupWidget",
|
||||||
|
pageName: "quote",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
wrapper.vm.dispatchStoreAction = dispatchStoreAction;
|
||||||
|
wrapper.vm.selectContactMethod("EmailAnswer");
|
||||||
|
wrapper.vm.userInput = "test@example.com";
|
||||||
|
|
||||||
|
await wrapper.vm.saveProgress();
|
||||||
|
wrapper.vm.selectContactMethod("PhoneAnswer");
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
|
||||||
|
expect(wrapper.vm.contactMethod).toBe("EmailAnswer");
|
||||||
|
expect(wrapper.vm.isContactMethodTabDisabled("PhoneAnswer")).toBe(true);
|
||||||
|
expect(wrapper.vm.isContactMethodTabDisabled("EmailAnswer")).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function setupMocks({ options, props }) {
|
function setupMocks({ options, props, cmsContent = {} }) {
|
||||||
const mountOptions = getMountOptions({
|
const mountOptions = getMountOptions({
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
|
|
||||||
const mockBaseMixin = {
|
const mockBaseMixin = {
|
||||||
methods: {
|
methods: {
|
||||||
getCmsContent: jest.fn(),
|
getCmsContent: jest.fn(
|
||||||
|
(widgetName, fieldName) => cmsContent[widgetName]?.[fieldName] ?? ""
|
||||||
|
),
|
||||||
dispatchStoreAction: jest.fn(),
|
dispatchStoreAction: jest.fn(),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,53 @@
|
||||||
:onModalClosedCallback="onModalClosed"
|
:onModalClosedCallback="onModalClosed"
|
||||||
:footerButtonText="modalButtonText"
|
:footerButtonText="modalButtonText"
|
||||||
:isFooterButtonPrimary="true"
|
:isFooterButtonPrimary="true"
|
||||||
|
:isFooterButtonSuppressed="isPhoneTabSelected && !isProgressSaved"
|
||||||
@footer-button-event="saveProgress">
|
@footer-button-event="saveProgress">
|
||||||
<p class="modal-body-inner">{{ modalBodyText }}</p>
|
<p class="modal-body-inner">{{ modalBodyText }}</p>
|
||||||
|
<fieldset class="save-progress-popup-question__tabs">
|
||||||
|
<div class="save-progress-popup-question__tab-list" role="radiogroup">
|
||||||
|
<label
|
||||||
|
v-for="tab in contactTabs"
|
||||||
|
:key="tab.value"
|
||||||
|
class="save-progress-popup-question__tab"
|
||||||
|
:class="{
|
||||||
|
'save-progress-popup-question__tab--active':
|
||||||
|
contactMethod === tab.value,
|
||||||
|
'save-progress-popup-question__tab--disabled':
|
||||||
|
isContactMethodTabDisabled(tab.value),
|
||||||
|
}">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
class="save-progress-popup-question__tab-input"
|
||||||
|
name="saveProgressPopupContactMethod"
|
||||||
|
:value="tab.value"
|
||||||
|
:checked="contactMethod === tab.value"
|
||||||
|
:disabled="isContactMethodTabDisabled(tab.value)"
|
||||||
|
@change="selectContactMethod(tab.value)" />
|
||||||
|
<span class="save-progress-popup-question__tab-label">{{ tab.label }}</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
<div v-if="isPhoneTabSelected" class="save-progress-popup-question__phone-fields">
|
||||||
|
<phoneNumberQuestion
|
||||||
|
v-model="userInput"
|
||||||
|
:cmsWidgetName="phoneQuestionWidgetName"
|
||||||
|
placeholderText=""
|
||||||
|
:isDisabled="isProgressSaved"
|
||||||
|
isRequired
|
||||||
|
validationRules="phone-number-required" />
|
||||||
|
<saveProgressPopupSmsConsentQuestion
|
||||||
|
v-model="smsConsent"
|
||||||
|
:consentCopy="smsConsentCopy"
|
||||||
|
:isDisabled="isProgressSaved"
|
||||||
|
:showConsentErrors="showConsentErrors" />
|
||||||
|
</div>
|
||||||
<saveProgressQuestion
|
<saveProgressQuestion
|
||||||
|
v-else
|
||||||
ref="saveProgressQuestion"
|
ref="saveProgressQuestion"
|
||||||
v-model="userInput"
|
v-model="userInput"
|
||||||
cmsWidgetName="SaveProgressPopupQuestionWidget"
|
:cmsWidgetName="emailQuestionWidgetName"
|
||||||
|
:isDisabled="isProgressSaved"
|
||||||
class="save-progress-popup-question" />
|
class="save-progress-popup-question" />
|
||||||
<template v-slot:modal-footer-slot>
|
<template v-slot:modal-footer-slot>
|
||||||
<buttonMain
|
<buttonMain
|
||||||
|
|
@ -23,6 +64,13 @@
|
||||||
:suppressLoader="true"
|
:suppressLoader="true"
|
||||||
class="skip-button"
|
class="skip-button"
|
||||||
@click-event="closeModal" />
|
@click-event="closeModal" />
|
||||||
|
<modalButtonMain
|
||||||
|
v-if="isPhoneTabSelected && !isProgressSaved"
|
||||||
|
ref="phoneSendButton"
|
||||||
|
:isPrimary="true"
|
||||||
|
class="w-100 modal-footer-button"
|
||||||
|
:buttonText="modalButtonText"
|
||||||
|
@click-event="validatePhoneAndSave" />
|
||||||
<p class="modal-disclaimer" v-html="modalDisclaimerText"></p>
|
<p class="modal-disclaimer" v-html="modalDisclaimerText"></p>
|
||||||
</template>
|
</template>
|
||||||
<alert
|
<alert
|
||||||
|
|
@ -36,11 +84,33 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import saveProgressQuestion from "@/fmg-components/save-progress-modal-question/save-progress-question/save-progress-question";
|
import saveProgressQuestion from "@/fmg-components/save-progress-modal-question/save-progress-question/save-progress-question";
|
||||||
|
import saveProgressPopupSmsConsentQuestion from "./save-progress-popup-sms-consent-question";
|
||||||
|
import phoneNumberQuestion from "@/digital-components/phone-number-question/phone-number-question";
|
||||||
import modal from "@/digital-components/modal/modal";
|
import modal from "@/digital-components/modal/modal";
|
||||||
import { storeActions } from "@/constants/store-actions.js";
|
import modalButtonMain from "@/digital-components/modal/ux-components/modal-button-main/modal-button-main";
|
||||||
import buttonMain from "@/ux-components/button-main/button-main";
|
import buttonMain from "@/ux-components/button-main/button-main";
|
||||||
import alert from "@/ux-components/alert/alert";
|
import alert from "@/ux-components/alert/alert";
|
||||||
import { saveQuote } from "@/helpers/heritage-integration/order-helper.js";
|
import { saveQuote } from "@/helpers/heritage-integration/order-helper.js";
|
||||||
|
import {
|
||||||
|
saveProgressPopupContactToStore,
|
||||||
|
saveProgressPopupContactMethods,
|
||||||
|
} from "@/helpers/save-progress-popup-contact-helper";
|
||||||
|
import {
|
||||||
|
defaultSaveProgressSmsConsent,
|
||||||
|
getSaveProgressSmsConsentFallbackCopy,
|
||||||
|
getSaveProgressSmsConsentConfig,
|
||||||
|
hasSaveProgressSmsConsentSelection,
|
||||||
|
} from "@/helpers/save-progress-sms-consent/save-progress-sms-consent-helper";
|
||||||
|
import { defineRule } from "vee-validate";
|
||||||
|
import { required } from "@/helpers/validation-rules";
|
||||||
|
import { errorMessages } from "@/constants/error-messages";
|
||||||
|
|
||||||
|
const PHONE_SPECIFIC_WIDGET = "SaveProgressPopupWidget_PhoneSpecific";
|
||||||
|
const EMAIL_SPECIFIC_WIDGET = "SaveProgressPopupWidget_EmailSpecific";
|
||||||
|
const PHONE_QUESTION_WIDGET = "SaveProgressPopupPhoneQuestionWidget";
|
||||||
|
const EMAIL_QUESTION_WIDGET = "SaveProgressPopupEmailQuestionWidget";
|
||||||
|
|
||||||
|
defineRule("phone-number-required", required(errorMessages.PHONE_REQUIRED));
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "save-progress-popup-question",
|
name: "save-progress-popup-question",
|
||||||
|
|
@ -48,15 +118,31 @@ export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
userInput: "",
|
userInput: "",
|
||||||
|
contactMethod: saveProgressPopupContactMethods.PHONE,
|
||||||
|
smsConsent: defaultSaveProgressSmsConsent(),
|
||||||
|
smsConsentCopy: getSaveProgressSmsConsentFallbackCopy(),
|
||||||
isProgressSaved: false,
|
isProgressSaved: false,
|
||||||
|
savedContactMethod: null,
|
||||||
|
showConsentErrors: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted() {
|
watch: {
|
||||||
|
smsConsent: {
|
||||||
|
handler(consent) {
|
||||||
|
if (hasSaveProgressSmsConsentSelection(consent)) {
|
||||||
|
this.showConsentErrors = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
async mounted() {
|
||||||
|
await this.loadSmsConsentConfig();
|
||||||
|
|
||||||
if (this.showSaveProgressPopup) this.modal.openModal();
|
if (this.showSaveProgressPopup) this.modal.openModal();
|
||||||
},
|
},
|
||||||
emits: ["close-save-progress-popup", "save-progress"], // <--- should remove oodles of warnings in dev tools
|
emits: ["close-save-progress-popup", "save-progress-saved"],
|
||||||
props: {
|
props: {
|
||||||
modelValue: Object,
|
|
||||||
modalWidgetName: String,
|
modalWidgetName: String,
|
||||||
pageName: String,
|
pageName: String,
|
||||||
showSaveProgressPopup: Boolean,
|
showSaveProgressPopup: Boolean,
|
||||||
|
|
@ -77,7 +163,11 @@ export default {
|
||||||
return this.getCmsContent(this.modalWidgetName, "FooterText");
|
return this.getCmsContent(this.modalWidgetName, "FooterText");
|
||||||
},
|
},
|
||||||
modalDisclaimerText() {
|
modalDisclaimerText() {
|
||||||
return this.getCmsContent(this.modalWidgetName, "FooterText2");
|
const disclaimerWidgetName = this.isPhoneTabSelected
|
||||||
|
? PHONE_SPECIFIC_WIDGET
|
||||||
|
: EMAIL_SPECIFIC_WIDGET;
|
||||||
|
|
||||||
|
return this.getCmsContent(disclaimerWidgetName, "BodyText");
|
||||||
},
|
},
|
||||||
modalName() {
|
modalName() {
|
||||||
return this.modalWidgetName;
|
return this.modalWidgetName;
|
||||||
|
|
@ -85,23 +175,92 @@ export default {
|
||||||
modal() {
|
modal() {
|
||||||
return this.$refs[this.modalName];
|
return this.$refs[this.modalName];
|
||||||
},
|
},
|
||||||
|
contactTabs() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: this.getCmsContent(PHONE_SPECIFIC_WIDGET, "HeadlineText") || "Phone",
|
||||||
|
value: saveProgressPopupContactMethods.PHONE,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: this.getCmsContent(EMAIL_SPECIFIC_WIDGET, "HeadlineText") || "Email",
|
||||||
|
value: saveProgressPopupContactMethods.EMAIL,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
},
|
||||||
|
isPhoneTabSelected() {
|
||||||
|
return this.contactMethod === saveProgressPopupContactMethods.PHONE;
|
||||||
|
},
|
||||||
|
phoneQuestionWidgetName() {
|
||||||
|
return PHONE_QUESTION_WIDGET;
|
||||||
|
},
|
||||||
|
emailQuestionWidgetName() {
|
||||||
|
return EMAIL_QUESTION_WIDGET;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
async loadSmsConsentConfig() {
|
||||||
|
const config = await getSaveProgressSmsConsentConfig();
|
||||||
|
this.smsConsentCopy = config.copy;
|
||||||
|
|
||||||
|
if (!this.isProgressSaved) {
|
||||||
|
this.smsConsent = config.value;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selectContactMethod(contactMethod) {
|
||||||
|
if (this.isProgressSaved || this.contactMethod === contactMethod) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.contactMethod = contactMethod;
|
||||||
|
this.userInput = "";
|
||||||
|
this.smsConsent = defaultSaveProgressSmsConsent();
|
||||||
|
this.showConsentErrors = false;
|
||||||
|
},
|
||||||
|
isContactMethodTabDisabled(contactMethod) {
|
||||||
|
return this.isProgressSaved && contactMethod !== this.savedContactMethod;
|
||||||
|
},
|
||||||
closeModal() {
|
closeModal() {
|
||||||
this.modal.closeModal();
|
this.modal.closeModal();
|
||||||
},
|
},
|
||||||
onModalClosed() {
|
onModalClosed() {
|
||||||
// send call to close popup
|
this.showConsentErrors = false;
|
||||||
this.$emit("close-save-progress-popup");
|
this.$emit("close-save-progress-popup");
|
||||||
},
|
},
|
||||||
async saveProgress() {
|
async validatePhoneAndSave() {
|
||||||
// save email address to store
|
try {
|
||||||
await this.dispatchStoreAction(storeActions.SAVE_EMAIL, this.userInput, false);
|
const validationResult = await this.modal.validate();
|
||||||
|
|
||||||
// send store call to send to new API (that triggers an email send)
|
if (!validationResult.valid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasSaveProgressSmsConsentSelection(this.smsConsent)) {
|
||||||
|
this.showConsentErrors = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.showConsentErrors = false;
|
||||||
|
await this.saveProgress();
|
||||||
|
} finally {
|
||||||
|
this.resetPhoneSendButtonStyle();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
resetPhoneSendButtonStyle() {
|
||||||
|
this.$refs.phoneSendButton?.resetButtonStyle();
|
||||||
|
},
|
||||||
|
async saveProgress() {
|
||||||
|
await saveProgressPopupContactToStore(this.dispatchStoreAction, {
|
||||||
|
contactMethod: this.contactMethod,
|
||||||
|
userInput: this.userInput,
|
||||||
|
smsConsent: this.smsConsent,
|
||||||
|
pageName: this.pageName,
|
||||||
|
});
|
||||||
|
|
||||||
|
// send store call to send to new API (that triggers an email/SMS send)
|
||||||
await saveQuote({ pageNameToLog: this.pageName });
|
await saveQuote({ pageNameToLog: this.pageName });
|
||||||
|
|
||||||
// hide save progress button; show success message alert
|
// hide save progress button; show success message alert
|
||||||
|
this.savedContactMethod = this.contactMethod;
|
||||||
this.isProgressSaved = true;
|
this.isProgressSaved = true;
|
||||||
|
|
||||||
// communicate to parent the new status
|
// communicate to parent the new status
|
||||||
|
|
@ -111,8 +270,11 @@ export default {
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
modal,
|
modal,
|
||||||
|
modalButtonMain,
|
||||||
buttonMain,
|
buttonMain,
|
||||||
saveProgressQuestion,
|
saveProgressQuestion,
|
||||||
|
phoneNumberQuestion,
|
||||||
|
saveProgressPopupSmsConsentQuestion,
|
||||||
alert,
|
alert,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
@ -227,8 +389,91 @@ export default {
|
||||||
}
|
}
|
||||||
.modal-body-inner {
|
.modal-body-inner {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-bottom: 1.5rem;
|
margin-bottom: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.save-progress-popup-question__tabs {
|
||||||
|
border: 0;
|
||||||
|
margin: 0 0 1rem 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-progress-popup-question__tab-list {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-progress-popup-question__tab {
|
||||||
|
flex: 1 1 50%;
|
||||||
|
display: grid;
|
||||||
|
min-height: 3.25rem;
|
||||||
|
margin: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-progress-popup-question__tab-input {
|
||||||
|
grid-area: 1 / 1;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
opacity: 0;
|
||||||
|
cursor: inherit;
|
||||||
|
|
||||||
|
&:focus-visible + .save-progress-popup-question__tab-label {
|
||||||
|
outline: 2px solid $blue;
|
||||||
|
outline-offset: 2px;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-progress-popup-question__tab-label {
|
||||||
|
grid-area: 1 / 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.875rem 0.5rem;
|
||||||
|
text-align: center;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.25rem;
|
||||||
|
color: $gray-600;
|
||||||
|
border-bottom: 3px solid transparent;
|
||||||
|
transition:
|
||||||
|
color 150ms linear,
|
||||||
|
border-color 150ms linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-progress-popup-question__tab--active .save-progress-popup-question__tab-label {
|
||||||
|
color: $red;
|
||||||
|
border-bottom-color: $red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-progress-popup-question__tab--disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
|
||||||
|
.save-progress-popup-question__tab-label {
|
||||||
|
color: $gray-400;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-progress-popup-question__phone-fields {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin: 0 0 1.5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.phone-number-question {
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
label {
|
||||||
|
text-align: left;
|
||||||
|
font-weight: 900;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.modal-footer {
|
.modal-footer {
|
||||||
padding: 0 1.5rem;
|
padding: 0 1.5rem;
|
||||||
margin: 0 0 1.5rem 0;
|
margin: 0 0 1.5rem 0;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,116 @@
|
||||||
|
import { shallowMount } from "@vue/test-utils";
|
||||||
|
import saveProgressPopupSmsConsentQuestion from "./save-progress-popup-sms-consent-question";
|
||||||
|
import {
|
||||||
|
defaultSaveProgressSmsConsent,
|
||||||
|
getSaveProgressSmsConsentFallbackCopy,
|
||||||
|
} from "@/helpers/save-progress-sms-consent/save-progress-sms-consent-helper";
|
||||||
|
import { errorMessages } from "@/constants/error-messages";
|
||||||
|
|
||||||
|
describe("save-progress-popup-sms-consent-question", () => {
|
||||||
|
it("should render two consent checkboxes with default copy", () => {
|
||||||
|
const wrapper = shallowMount(saveProgressPopupSmsConsentQuestion);
|
||||||
|
const fallbackCopy = getSaveProgressSmsConsentFallbackCopy();
|
||||||
|
|
||||||
|
const checkboxes = wrapper.findAllComponents({ name: "checkboxQuestion" });
|
||||||
|
|
||||||
|
expect(checkboxes).toHaveLength(2);
|
||||||
|
expect(checkboxes.at(0).props("labelText")).toBe(fallbackCopy.transactional);
|
||||||
|
expect(checkboxes.at(1).props("labelText")).toBe(fallbackCopy.marketing);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should emit updated consent when a checkbox changes", async () => {
|
||||||
|
const wrapper = shallowMount(saveProgressPopupSmsConsentQuestion, {
|
||||||
|
propsData: {
|
||||||
|
modelValue: defaultSaveProgressSmsConsent(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
wrapper.vm.transactionalConsent = true;
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
|
||||||
|
expect(wrapper.emitted("update:modelValue")).toEqual([
|
||||||
|
[{ transactional: true, marketing: false }],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should accept API-ready consent copy via prop", () => {
|
||||||
|
const apiCopy = {
|
||||||
|
transactional: "API transactional copy",
|
||||||
|
marketing: "API marketing copy",
|
||||||
|
};
|
||||||
|
|
||||||
|
const wrapper = shallowMount(saveProgressPopupSmsConsentQuestion, {
|
||||||
|
propsData: {
|
||||||
|
consentCopy: apiCopy,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const checkboxes = wrapper.findAllComponents({ name: "checkboxQuestion" });
|
||||||
|
|
||||||
|
expect(checkboxes.at(0).props("labelText")).toBe("API transactional copy");
|
||||||
|
expect(checkboxes.at(1).props("labelText")).toBe("API marketing copy");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should disable consent checkboxes when isDisabled is true", () => {
|
||||||
|
const wrapper = shallowMount(saveProgressPopupSmsConsentQuestion, {
|
||||||
|
propsData: {
|
||||||
|
isDisabled: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const checkboxes = wrapper.findAllComponents({ name: "checkboxQuestion" });
|
||||||
|
|
||||||
|
expect(checkboxes.at(0).props("isDisabled")).toBe(true);
|
||||||
|
expect(checkboxes.at(1).props("isDisabled")).toBe(true);
|
||||||
|
expect(wrapper.find("fieldset").attributes("disabled")).toBe("");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not show consent errors before showConsentErrors is true", () => {
|
||||||
|
const wrapper = shallowMount(saveProgressPopupSmsConsentQuestion, {
|
||||||
|
propsData: {
|
||||||
|
modelValue: defaultSaveProgressSmsConsent(),
|
||||||
|
showConsentErrors: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const checkboxes = wrapper.findAllComponents({ name: "checkboxQuestion" });
|
||||||
|
|
||||||
|
expect(checkboxes.at(0).props("hasError")).toBe(false);
|
||||||
|
expect(checkboxes.at(1).props("hasError")).toBe(false);
|
||||||
|
expect(wrapper.text()).not.toContain(errorMessages.SMS_CONSENT_REQUIRED);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should show consent errors when showConsentErrors is true and nothing is selected", async () => {
|
||||||
|
const wrapper = shallowMount(saveProgressPopupSmsConsentQuestion, {
|
||||||
|
propsData: {
|
||||||
|
modelValue: defaultSaveProgressSmsConsent(),
|
||||||
|
showConsentErrors: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await wrapper.setProps({ showConsentErrors: true });
|
||||||
|
|
||||||
|
expect(wrapper.text()).toContain(errorMessages.SMS_CONSENT_REQUIRED);
|
||||||
|
|
||||||
|
const checkboxes = wrapper.findAllComponents({ name: "checkboxQuestion" });
|
||||||
|
expect(checkboxes.at(0).props("hasError")).toBe(true);
|
||||||
|
expect(checkboxes.at(1).props("hasError")).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should clear consent errors when a selection is made while showConsentErrors is true", async () => {
|
||||||
|
const wrapper = shallowMount(saveProgressPopupSmsConsentQuestion, {
|
||||||
|
propsData: {
|
||||||
|
modelValue: defaultSaveProgressSmsConsent(),
|
||||||
|
showConsentErrors: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await wrapper.setProps({
|
||||||
|
modelValue: { transactional: true, marketing: false },
|
||||||
|
});
|
||||||
|
|
||||||
|
const checkboxes = wrapper.findAllComponents({ name: "checkboxQuestion" });
|
||||||
|
expect(checkboxes.at(0).props("hasError")).toBe(false);
|
||||||
|
expect(checkboxes.at(1).props("hasError")).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,104 @@
|
||||||
|
<template>
|
||||||
|
<fieldset
|
||||||
|
class="save-progress-popup-sms-consent"
|
||||||
|
:class="{ 'has-error': showConsentError }"
|
||||||
|
:disabled="isDisabled">
|
||||||
|
<checkboxQuestion
|
||||||
|
v-model="transactionalConsent"
|
||||||
|
checkboxName="saveProgressTransactionalConsent"
|
||||||
|
:labelText="consentCopy.transactional"
|
||||||
|
:hasError="showConsentError"
|
||||||
|
:isDisabled="isDisabled" />
|
||||||
|
<checkboxQuestion
|
||||||
|
v-model="marketingConsent"
|
||||||
|
checkboxName="saveProgressMarketingConsent"
|
||||||
|
:labelText="consentCopy.marketing"
|
||||||
|
:hasError="showConsentError"
|
||||||
|
:isDisabled="isDisabled" />
|
||||||
|
<span
|
||||||
|
v-if="showConsentError"
|
||||||
|
class="save-progress-popup-sms-consent__error d-inline-flex small mt-1"
|
||||||
|
role="alert"
|
||||||
|
aria-live="polite">
|
||||||
|
{{ errorMessages.SMS_CONSENT_REQUIRED }}
|
||||||
|
</span>
|
||||||
|
</fieldset>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import checkboxQuestion from "@/digital-components/checkbox-question/checkbox-question";
|
||||||
|
import { errorMessages } from "@/constants/error-messages";
|
||||||
|
import {
|
||||||
|
defaultSaveProgressSmsConsent,
|
||||||
|
getSaveProgressSmsConsentFallbackCopy,
|
||||||
|
hasSaveProgressSmsConsentSelection,
|
||||||
|
} from "@/helpers/save-progress-sms-consent/save-progress-sms-consent-helper";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "save-progress-popup-sms-consent-question",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
errorMessages,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
modelValue: {
|
||||||
|
type: Object,
|
||||||
|
default: defaultSaveProgressSmsConsent,
|
||||||
|
},
|
||||||
|
consentCopy: {
|
||||||
|
type: Object,
|
||||||
|
default: getSaveProgressSmsConsentFallbackCopy,
|
||||||
|
},
|
||||||
|
isDisabled: Boolean,
|
||||||
|
showConsentErrors: Boolean,
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
transactionalConsent: {
|
||||||
|
get() {
|
||||||
|
return this.modelValue.transactional;
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
this.emitConsentUpdate({ transactional: value });
|
||||||
|
},
|
||||||
|
},
|
||||||
|
marketingConsent: {
|
||||||
|
get() {
|
||||||
|
return this.modelValue.marketing;
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
this.emitConsentUpdate({ marketing: value });
|
||||||
|
},
|
||||||
|
},
|
||||||
|
showConsentError() {
|
||||||
|
return this.showConsentErrors && !hasSaveProgressSmsConsentSelection(this.modelValue);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
emitConsentUpdate(partialConsent) {
|
||||||
|
this.$emit("update:modelValue", {
|
||||||
|
...this.modelValue,
|
||||||
|
...partialConsent,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
checkboxQuestion,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.save-progress-popup-sms-consent {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
border: 0;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
&__error {
|
||||||
|
color: $red;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
38
src/helpers/save-progress-popup-contact-helper.js
Normal file
38
src/helpers/save-progress-popup-contact-helper.js
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
import { storeActions } from "@/constants/store-actions";
|
||||||
|
import store from "@/store";
|
||||||
|
|
||||||
|
export const saveProgressPopupContactMethods = {
|
||||||
|
PHONE: "PhoneAnswer",
|
||||||
|
EMAIL: "EmailAnswer",
|
||||||
|
};
|
||||||
|
|
||||||
|
export function isPhoneContactMethod(contactMethod) {
|
||||||
|
return contactMethod === saveProgressPopupContactMethods.PHONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function saveProgressPopupContactToStore(
|
||||||
|
dispatchStoreAction,
|
||||||
|
{ contactMethod, userInput, smsConsent, pageName }
|
||||||
|
) {
|
||||||
|
if (isPhoneContactMethod(contactMethod)) {
|
||||||
|
await dispatchStoreAction(storeActions.SAVE_PHONE_NUMBER, userInput, false);
|
||||||
|
await dispatchStoreAction(storeActions.SAVE_IS_SMS_OPT_IN, smsConsent.transactional, false);
|
||||||
|
|
||||||
|
const existingPageData = store.getters.pageData(pageName) ?? {};
|
||||||
|
await dispatchStoreAction(
|
||||||
|
storeActions.SAVE_PAGE_DATA,
|
||||||
|
{
|
||||||
|
page: pageName,
|
||||||
|
data: {
|
||||||
|
...existingPageData,
|
||||||
|
saveProgressSmsConsent: { ...smsConsent },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await dispatchStoreAction(storeActions.SAVE_EMAIL, userInput, false);
|
||||||
|
}
|
||||||
103
src/helpers/save-progress-popup-contact-helper.spec.js
Normal file
103
src/helpers/save-progress-popup-contact-helper.spec.js
Normal file
|
|
@ -0,0 +1,103 @@
|
||||||
|
import { storeActions } from "@/constants/store-actions";
|
||||||
|
import store from "@/store";
|
||||||
|
import {
|
||||||
|
isPhoneContactMethod,
|
||||||
|
saveProgressPopupContactMethods,
|
||||||
|
saveProgressPopupContactToStore,
|
||||||
|
} from "./save-progress-popup-contact-helper";
|
||||||
|
|
||||||
|
jest.mock("@/store", () => ({
|
||||||
|
getters: {
|
||||||
|
pageData: jest.fn(),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe("save-progress-popup-contact-helper", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
store.getters.pageData.mockReturnValue({ servicePackageSelected: "premium" });
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("isPhoneContactMethod", () => {
|
||||||
|
it("should return true for the phone contact method", () => {
|
||||||
|
expect(isPhoneContactMethod(saveProgressPopupContactMethods.PHONE)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return false for the email contact method", () => {
|
||||||
|
expect(isPhoneContactMethod(saveProgressPopupContactMethods.EMAIL)).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("saveProgressPopupContactToStore", () => {
|
||||||
|
it("should save phone number and persist sms consent on phone tab", async () => {
|
||||||
|
const dispatchStoreAction = jest.fn().mockResolvedValue(undefined);
|
||||||
|
const smsConsent = { transactional: true, marketing: false };
|
||||||
|
|
||||||
|
await saveProgressPopupContactToStore(dispatchStoreAction, {
|
||||||
|
contactMethod: saveProgressPopupContactMethods.PHONE,
|
||||||
|
userInput: "555-123-4567",
|
||||||
|
smsConsent,
|
||||||
|
pageName: "quote",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(dispatchStoreAction).toHaveBeenCalledWith(
|
||||||
|
storeActions.SAVE_PHONE_NUMBER,
|
||||||
|
"555-123-4567",
|
||||||
|
false
|
||||||
|
);
|
||||||
|
expect(dispatchStoreAction).not.toHaveBeenCalledWith(
|
||||||
|
storeActions.SAVE_EMAIL,
|
||||||
|
expect.anything(),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
expect(dispatchStoreAction).toHaveBeenCalledWith(
|
||||||
|
storeActions.SAVE_IS_SMS_OPT_IN,
|
||||||
|
true,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
expect(dispatchStoreAction).toHaveBeenCalledWith(
|
||||||
|
storeActions.SAVE_PAGE_DATA,
|
||||||
|
{
|
||||||
|
page: "quote",
|
||||||
|
data: {
|
||||||
|
servicePackageSelected: "premium",
|
||||||
|
saveProgressSmsConsent: smsConsent,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
false
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should save email on email tab without updating phone or sms opt in", async () => {
|
||||||
|
const dispatchStoreAction = jest.fn().mockResolvedValue(undefined);
|
||||||
|
|
||||||
|
await saveProgressPopupContactToStore(dispatchStoreAction, {
|
||||||
|
contactMethod: saveProgressPopupContactMethods.EMAIL,
|
||||||
|
userInput: "test@example.com",
|
||||||
|
smsConsent: { transactional: false, marketing: false },
|
||||||
|
pageName: "quote",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(dispatchStoreAction).toHaveBeenCalledWith(
|
||||||
|
storeActions.SAVE_EMAIL,
|
||||||
|
"test@example.com",
|
||||||
|
false
|
||||||
|
);
|
||||||
|
expect(dispatchStoreAction).not.toHaveBeenCalledWith(
|
||||||
|
storeActions.SAVE_PHONE_NUMBER,
|
||||||
|
expect.anything(),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
expect(dispatchStoreAction).not.toHaveBeenCalledWith(
|
||||||
|
storeActions.SAVE_IS_SMS_OPT_IN,
|
||||||
|
expect.anything(),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
expect(dispatchStoreAction).not.toHaveBeenCalledWith(
|
||||||
|
storeActions.SAVE_PAGE_DATA,
|
||||||
|
expect.anything(),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
const SAVE_PROGRESS_SMS_CONSENT_FALLBACK_COPY = {
|
||||||
|
transactional: "Sign me up for updates about my upcoming service.",
|
||||||
|
marketing: "Sign me up for promotional and product offers.",
|
||||||
|
};
|
||||||
|
|
||||||
|
export function defaultSaveProgressSmsConsent() {
|
||||||
|
return {
|
||||||
|
transactional: false,
|
||||||
|
marketing: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getSaveProgressSmsConsentFallbackCopy() {
|
||||||
|
return { ...SAVE_PROGRESS_SMS_CONSENT_FALLBACK_COPY };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hasSaveProgressSmsConsentSelection(consent) {
|
||||||
|
return Boolean(consent?.transactional || consent?.marketing);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getSaveProgressSmsConsentConfig() {
|
||||||
|
// Replace with Consent Management API lookup (CASH-1911).
|
||||||
|
return {
|
||||||
|
copy: getSaveProgressSmsConsentFallbackCopy(),
|
||||||
|
value: defaultSaveProgressSmsConsent(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
import {
|
||||||
|
defaultSaveProgressSmsConsent,
|
||||||
|
getSaveProgressSmsConsentFallbackCopy,
|
||||||
|
getSaveProgressSmsConsentConfig,
|
||||||
|
hasSaveProgressSmsConsentSelection,
|
||||||
|
} from "./save-progress-sms-consent-helper";
|
||||||
|
|
||||||
|
describe("save-progress-sms-consent-helper", () => {
|
||||||
|
describe("getSaveProgressSmsConsentConfig", () => {
|
||||||
|
it("should return fallback copy and default consent values", async () => {
|
||||||
|
const config = await getSaveProgressSmsConsentConfig();
|
||||||
|
|
||||||
|
expect(config.copy).toEqual(getSaveProgressSmsConsentFallbackCopy());
|
||||||
|
expect(config.value).toEqual(defaultSaveProgressSmsConsent());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("hasSaveProgressSmsConsentSelection", () => {
|
||||||
|
it("should return false when no consent is selected", () => {
|
||||||
|
expect(hasSaveProgressSmsConsentSelection(defaultSaveProgressSmsConsent())).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return true when either consent option is selected", () => {
|
||||||
|
expect(
|
||||||
|
hasSaveProgressSmsConsentSelection({ transactional: true, marketing: false })
|
||||||
|
).toBe(true);
|
||||||
|
expect(
|
||||||
|
hasSaveProgressSmsConsentSelection({ transactional: false, marketing: true })
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -324,12 +324,15 @@ export default {
|
||||||
|
|
||||||
const resultMap = await settleAllPromises(promiseResultMap);
|
const resultMap = await settleAllPromises(promiseResultMap);
|
||||||
const emailFromStore = store.getters.order.customer.emailAddress;
|
const emailFromStore = store.getters.order.customer.emailAddress;
|
||||||
|
const phoneFromStore = store.getters.order.customer.phoneNumber;
|
||||||
const isEmailInStoreOnPageLoad = emailFromStore?.length > 0;
|
const isEmailInStoreOnPageLoad = emailFromStore?.length > 0;
|
||||||
|
const isPhoneInStoreOnPageLoad = phoneFromStore?.length > 0;
|
||||||
|
const hasSaveProgressContactInStore = isEmailInStoreOnPageLoad || isPhoneInStoreOnPageLoad;
|
||||||
const isPopupSkipped =
|
const isPopupSkipped =
|
||||||
store.getters.pageData(routeData.QUOTE.name)?.saveProgressPopupSkipped === true;
|
store.getters.pageData(routeData.QUOTE.name)?.saveProgressPopupSkipped === true;
|
||||||
// "popup" is the automatic opening centered popup on load
|
// "popup" is the automatic opening centered popup on load
|
||||||
const showSaveProgressPopup = !(isEmailInStoreOnPageLoad || isPopupSkipped);
|
const showSaveProgressPopup = !(hasSaveProgressContactInStore || isPopupSkipped);
|
||||||
const showSaveProgressModal = isEmailInStoreOnPageLoad ? false : true;
|
const showSaveProgressModal = !hasSaveProgressContactInStore;
|
||||||
const shouldSkipToInsurance =
|
const shouldSkipToInsurance =
|
||||||
getBoolFromString(
|
getBoolFromString(
|
||||||
experimentMixin.methods.getSettingValue(experimentSettings.SKIP_TO_INSURANCE)
|
experimentMixin.methods.getSettingValue(experimentSettings.SKIP_TO_INSURANCE)
|
||||||
|
|
|
||||||
|
|
@ -2579,7 +2579,8 @@ export const actions = {
|
||||||
referralCorrelationId: order.referralCorrelationId,
|
referralCorrelationId: order.referralCorrelationId,
|
||||||
referralNumber: order.referralNumber?.toString(),
|
referralNumber: order.referralNumber?.toString(),
|
||||||
referralSequenceNumber: order.referralSequenceNumber,
|
referralSequenceNumber: order.referralSequenceNumber,
|
||||||
emailAddress: order.customer.emailAddress,
|
emailAddress: order.customer.emailAddress || undefined,
|
||||||
|
phoneNumber: order.customer.phoneNumber || undefined,
|
||||||
lastPage: pageNameToLog,
|
lastPage: pageNameToLog,
|
||||||
isRepair: damage.isRepair,
|
isRepair: damage.isRepair,
|
||||||
firstName: order.customer.firstName,
|
firstName: order.customer.firstName,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue