Merge branch 'release/2024.02.15' into feature/CSR-1952-ajc

This commit is contained in:
Adam Caouette 2024-02-09 12:58:17 -05:00
commit d6433cd84b
19 changed files with 419 additions and 65 deletions

View file

@ -8,6 +8,7 @@ const experimentSettings = {
DISPLAY_AVAILABILITY_INDICATORS: "DisplayAvailabilityIndicators", DISPLAY_AVAILABILITY_INDICATORS: "DisplayAvailabilityIndicators",
PIA_EXPERIENCE: "PIA Experience", PIA_EXPERIENCE: "PIA Experience",
SUBMIT_ORDER_ENABLE_PIA: "SubmitOrder_Enable_PIA", SUBMIT_ORDER_ENABLE_PIA: "SubmitOrder_Enable_PIA",
IS_EMAIL_OPTIONAL: "isEmailOptional",
}; };
const experimentTriggers = { const experimentTriggers = {

View file

@ -121,6 +121,10 @@ export default {
hideInput: Boolean, hideInput: Boolean,
centerErrorMessage: Boolean, centerErrorMessage: Boolean,
keyDownHandler: Function, keyDownHandler: Function,
isEmailOptional: {
type: Boolean,
default: false,
},
}, },
setup(props) { setup(props) {
const uuid = uuidv4(); const uuid = uuidv4();
@ -193,7 +197,9 @@ export default {
}, },
computed: { computed: {
questionText() { questionText() {
return this.getCmsContent(this.cmsWidgetName, "QuestionText"); return this.isEmailOptional
? this.getCmsContent(this.cmsWidgetName, "QuestionText") + " (optional)"
: this.getCmsContent(this.cmsWidgetName, "QuestionText");
}, },
value: { value: {
get: function () { get: function () {

View file

@ -129,11 +129,7 @@ async function getLatestPageForRedirection() {
} else if (!estimateComponent.methods.arePagePrerequisitesValid()) { } else if (!estimateComponent.methods.arePagePrerequisitesValid()) {
return fmgPageValues.VEHICLE_DAMAGE; return fmgPageValues.VEHICLE_DAMAGE;
} else { } else {
if (scheduleComponent.methods.arePagePrerequisitesValid()) { if (quoteComponent.methods.arePagePrerequisitesValid()) {
return fmgPageValues.SCHEDULE;
} else if (serviceLocationComponent.methods.arePagePrerequisitesValid()) {
return fmgPageValues.SERVICE_LOCATION;
} else if (quoteComponent.methods.arePagePrerequisitesValid()) {
return fmgPageValues.QUOTE; return fmgPageValues.QUOTE;
} else if (capabilityQuestionsComponent.methods.arePagePrerequisitesValid()) { } else if (capabilityQuestionsComponent.methods.arePagePrerequisitesValid()) {
return fmgPageValues.CAPABILITY_QUESTIONS; return fmgPageValues.CAPABILITY_QUESTIONS;

View file

@ -10,6 +10,7 @@ import { storeMutations } from "@/constants/store-mutations";
import { navigationScenarios } from "@/router/router-constants/navigation-scenarios"; import { navigationScenarios } from "@/router/router-constants/navigation-scenarios";
import store from "@/store"; import store from "@/store";
import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper"; import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper";
import { experimentSettings } from "@/constants/experiments";
jest.mock("@/helpers/damage-helper", () => ({ jest.mock("@/helpers/damage-helper", () => ({
isGlassAvailableForCarId: jest.fn().mockImplementation(() => true), isGlassAvailableForCarId: jest.fn().mockImplementation(() => true),
@ -641,6 +642,17 @@ describe("address-lookup.vue", () => {
}); });
}); });
const mockMixin = {
methods: {
getSettingValue: jest.fn((settingName) => {
if (settingName === experimentSettings.IS_EMAIL_OPTIONAL) {
return "true";
}
return "false";
}),
},
};
function setupMocks({ function setupMocks({
isZipValid = true, isZipValid = true,
isZipServiceable = true, isZipServiceable = true,
@ -709,6 +721,7 @@ function setupMocks({
}, },
}, },
}, },
mixins: [mockMixin],
}) })
); );

View file

@ -20,7 +20,11 @@
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" ref="funnelSubHeader" /> <funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" ref="funnelSubHeader" />
<customerQuestions ref="customerQuestions" v-model="customerQuestions" /> <customerQuestions
ref="customerQuestions"
v-model="customerQuestions"
:validationRules="EmailValidationRules"
:isEmailOptional="IsEmailOptional" />
<alert <alert
ref="alertVinNotFound" ref="alertVinNotFound"

View file

@ -27,7 +27,9 @@
v-model="customerModel.emailAddress" v-model="customerModel.emailAddress"
ref="emailAddress" ref="emailAddress"
customInputId="emailAddress" customInputId="emailAddress"
validationRules="email-address-required|email-address-format" /> :isRequired="!isEmailOptional"
:validationRules="validationRules"
:isEmailOptional="isEmailOptional" />
</div> </div>
</div> </div>
<div class="row mb-0"> <div class="row mb-0">
@ -79,6 +81,7 @@ export default {
}), }),
}, },
validationRules: String, validationRules: String,
isEmailOptional: Boolean,
}, },
computed: { computed: {
customerModel: { customerModel: {

View file

@ -11,6 +11,7 @@ import { settleAllPromises } from "@/helpers/layout-helper.js";
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper"; import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import baseMixin from "../../mixins/base-mixin"; import baseMixin from "../../mixins/base-mixin";
import { vinLookupMethodSelections } from "@/constants/vin-lookup-method-selections.js"; import { vinLookupMethodSelections } from "@/constants/vin-lookup-method-selections.js";
import { experimentSettings } from "@/constants/experiments";
// Mock our module for promises. // Mock our module for promises.
jest.mock("@/helpers/layout-helper.js", () => ({ jest.mock("@/helpers/layout-helper.js", () => ({
@ -273,12 +274,24 @@ function setupMocks({
Answers: cmsAnswers, Answers: cmsAnswers,
FunnelFooterWidget: FunnelFooterWidget, FunnelFooterWidget: FunnelFooterWidget,
}; };
const mockMixin = {
methods: {
getSettingValue: jest.fn((settingName) => {
if (settingName === experimentSettings.IS_EMAIL_OPTIONAL) {
return "true";
}
return "false";
}),
},
};
const apiPromise = Promise.resolve({ cmsContent }); const apiPromise = Promise.resolve({ cmsContent });
settleAllPromises.mockImplementation(() => apiPromise); settleAllPromises.mockImplementation(() => apiPromise);
fetchCmsContentForPage.mockImplementation(() => Promise.resolve()); fetchCmsContentForPage.mockImplementation(() => Promise.resolve());
const mountOptions = getMountOptions({ ...mountOptionsMockData, mixins: [baseMixin] }); const mountOptions = getMountOptions({
...mountOptionsMockData,
mixins: [baseMixin, mockMixin],
});
mountOptions["attachTo"] = document.body; mountOptions["attachTo"] = document.body;
const wrapper = shallowMount(estimate, mountOptions); const wrapper = shallowMount(estimate, mountOptions);

View file

@ -52,9 +52,10 @@
cmsWidgetName="EmailAddressQuestionWidget" cmsWidgetName="EmailAddressQuestionWidget"
v-model="emailAddress" v-model="emailAddress"
inputId="emailAddress" inputId="emailAddress"
isRequired :isRequired="!IsEmailOptional"
disableAutoFill disableAutoFill
validationRules="email-address-required|email-address-format" /> :validationRules="EmailValidationRules"
:isEmailOptional="IsEmailOptional" />
<textBlock cmsWidgetName="QuoteEmailTextBlockWidget" typeStyle="caption" /> <textBlock cmsWidgetName="QuoteEmailTextBlockWidget" typeStyle="caption" />
<alert <alert

View file

@ -11,6 +11,7 @@ import { nextTick } from "vue";
import { storeActions } from "@/constants/store-actions"; import { storeActions } from "@/constants/store-actions";
import { storeMutations } from "@/constants/store-mutations"; import { storeMutations } from "@/constants/store-mutations";
import store from "@/store"; import store from "@/store";
import { experimentSettings } from "@/constants/experiments";
jest.mock("@/assets/img/loader.gif", () => "loader.gif"); jest.mock("@/assets/img/loader.gif", () => "loader.gif");
jest.mock("@/assets/img/windshield.png", () => "windshield.png"); jest.mock("@/assets/img/windshield.png", () => "windshield.png");
@ -692,11 +693,20 @@ function setupMocks({
}; };
const apiPromise = Promise.resolve(apiResponses); const apiPromise = Promise.resolve(apiResponses);
const mockMixin = {
methods: {
getSettingValue: jest.fn((settingName) => {
if (settingName === experimentSettings.IS_EMAIL_OPTIONAL) {
return "true";
}
return "false";
}),
},
};
settleAllPromises.mockImplementation(() => apiPromise); settleAllPromises.mockImplementation(() => apiPromise);
fetchCmsContentForPage.mockImplementation(() => Promise.resolve()); fetchCmsContentForPage.mockImplementation(() => Promise.resolve());
const mountOptions = getMountOptions(mountOptionsMockData); const mountOptions = getMountOptions({ ...mountOptionsMockData, mixins: [mockMixin] });
mountOptions["attachTo"] = document.body; // append wrapper to document.body to test DOM methods mountOptions["attachTo"] = document.body; // append wrapper to document.body to test DOM methods
const wrapper = shallowMount(licensePlateLookup, mountOptions); const wrapper = shallowMount(licensePlateLookup, mountOptions);

View file

@ -34,7 +34,9 @@
cmsWidgetName="EmailAddressQuestionWidget" cmsWidgetName="EmailAddressQuestionWidget"
v-model="email" v-model="email"
customInputId="email" customInputId="email"
validationRules="email-address-required|email-address-format" /> :isRequired="!IsEmailOptional"
:validationRules="EmailValidationRules"
:isEmailOptional="IsEmailOptional" />
<textBlock cmsWidgetName="QuoteEmailTextBlockWidget" typeStyle="caption" /> <textBlock cmsWidgetName="QuoteEmailTextBlockWidget" typeStyle="caption" />

View file

@ -1,5 +1,25 @@
import { mount, shallowMount } from "@vue/test-utils"; import { mount, shallowMount } from "@vue/test-utils";
import promoModalQuestion from "./promo-modal-question"; import promoModalQuestion from "./promo-modal-question";
import { storeActions } from "@/constants/store-actions";
import baseMixin from "@/mixins/base-mixin.js";
let mockReturnsForStoreActions = {};
jest.mock("@/mixins/base-mixin", () => ({
...jest.requireActual("@/mixins/base-mixin"),
methods: {
dispatchStoreActionWithLogging: jest.fn(
(action, { promoCode, addableVaps }, pageNameToLog, someBool) => {
return mockReturnsForStoreActions[action];
}
),
},
}));
afterEach(() => {
// reset store action returns
mockReturnsForStoreActions = {};
});
jest.mock("@/digital-components/textbox-question/textbox-question", () => ({ jest.mock("@/digital-components/textbox-question/textbox-question", () => ({
getCmsContent: jest.fn((widgetName, cmsFieldName) => { getCmsContent: jest.fn((widgetName, cmsFieldName) => {
@ -79,4 +99,127 @@ describe("promo-modal-question.vue", () => {
// Assert // Assert
expect(wrapper.emitted("update:modelValue")).toEqual([[lineItems]]); expect(wrapper.emitted("update:modelValue")).toEqual([[lineItems]]);
}); });
it("focuses on the input element when focusOnPromoInput is called", async () => {
// Arrange
const lineItems = {
glassParts: [],
supportingItems: [],
vaps: [],
promos: [],
};
const wrapper = mount(promoModalQuestion, {
mixins: [mockMixin],
props: {
modelValue: lineItems,
modalWidgetName: modalWidgetName,
},
attachTo: document.body,
});
const focusMock = jest.fn();
const inputMock = { focus: focusMock };
// Mock document.getElementById to return the inputMock
jest.spyOn(document, "getElementById").mockReturnValue(inputMock);
//Act
wrapper.vm.focusOnPromoInput();
await wrapper.vm.$nextTick();
//Assert
expect(focusMock).toHaveBeenCalled();
});
it("returns a validate response on applying promo", async () => {
// Arrange
const newPromo = "testPromo";
const pageNameToLog = "testPage";
const validateResponse = { orderPromos: [] };
const addableVaps = [];
mockReturnsForStoreActions[storeActions.VALIDATE_ORDER_PROMO_AND_SAVE_SERVER_DATA] =
validateResponse;
const lineItems = {
glassParts: [],
supportingItems: [],
vaps: [],
promos: [],
};
const wrapper = mount(promoModalQuestion, {
mixins: [mockMixin],
props: {
modelValue: lineItems,
modalWidgetName: modalWidgetName,
},
attachTo: document.body,
});
// Act
wrapper.vm.getPromoCodeData();
const promoValidationResponse = await baseMixin.methods.dispatchStoreActionWithLogging(
storeActions.VALIDATE_ORDER_PROMO_AND_SAVE_SERVER_DATA,
{
newPromo,
lineItems,
addableVaps,
},
pageNameToLog,
false
);
// Assert
expect(promoValidationResponse).toEqual(validateResponse);
});
test("If promocode is valid return taxed lineItems", async () => {
//Arrange
const taxedlineItems = {};
mockReturnsForStoreActions[storeActions.TAX_ORDER_ITEMS_AND_SAVE_SERVER_DATA] =
taxedlineItems;
const lineItems = {
glassParts: [],
supportingItems: [],
vaps: [],
promos: [],
};
let promoCode = "1wiper0";
const pricedLineItemsToTax = [];
pricedLineItemsToTax.push(promoCode);
const wrapper = mount(promoModalQuestion, {
mixins: [mockMixin],
props: {
modelValue: lineItems,
modalWidgetName: modalWidgetName,
},
attachTo: document.body,
});
wrapper.vm.addPromoCode();
const taxedLineItems = await baseMixin.methods.dispatchStoreActionWithLogging(
storeActions.TAX_ORDER_ITEMS_AND_SAVE_SERVER_DATA,
{
billToAccountNumber: "87291",
providerNumber: 2,
appointmentType: "IN_SHOP",
serviceLocationCity: "city",
serviceLocationState: "state",
serviceLocationZipCode: "12345",
pricedLineItems: pricedLineItemsToTax,
},
"payment-method",
false
);
// Assert
expect(taxedLineItems).toEqual(taxedlineItems);
});
}); });

View file

@ -100,6 +100,12 @@ export default {
modelValue: Object, modelValue: Object,
modalWidgetName: String, modalWidgetName: String,
availableVaps: Object, availableVaps: Object,
pageName: String,
taxPromos: {
type: Boolean,
required: false,
default: true,
},
}, },
computed: { computed: {
promoLinkText() { promoLinkText() {
@ -253,44 +259,52 @@ export default {
this.promoCode, this.promoCode,
this.lineItems, this.lineItems,
this.availableVaps, this.availableVaps,
"payment-method" this.pageName
); );
if (promoCodeData.isValid) { if (promoCodeData.isValid) {
const pricedLineItemsToTax = []; if (this.taxPromos) {
pricedLineItemsToTax.push(...promoCodeData.promoCode); const pricedLineItemsToTax = [];
const taxedLineItems = await baseMixin.methods.dispatchStoreActionWithLogging( pricedLineItemsToTax.push(...promoCodeData.promoCode);
storeActions.TAX_ORDER_ITEMS_AND_SAVE_SERVER_DATA, const taxedLineItems =
{ await baseMixin.methods.dispatchStoreActionWithLogging(
billToAccountNumber: "87291", storeActions.TAX_ORDER_ITEMS_AND_SAVE_SERVER_DATA,
providerNumber: {
store.getters.order.serviceLocation.provider.providerNumber, billToAccountNumber: "87291",
appointmentType: store.getters.order.serviceLocation.appointmentType, providerNumber:
serviceLocationCity: store.getters.order.serviceLocation.city, store.getters.order.serviceLocation.provider.providerNumber,
serviceLocationState: store.getters.order.serviceLocation.state, appointmentType:
serviceLocationZipCode: store.getters.order.serviceLocation.zipCode, store.getters.order.serviceLocation.appointmentType,
pricedLineItems: pricedLineItemsToTax, serviceLocationCity: store.getters.order.serviceLocation.city,
}, serviceLocationState: store.getters.order.serviceLocation.state,
"payment-method", serviceLocationZipCode:
false store.getters.order.serviceLocation.zipCode,
); pricedLineItems: pricedLineItemsToTax,
},
"payment-method",
false
);
// Match all line items to the line items as they are in the store // Match all line items to the line items as they are in the store
// and rebuild the original structure. // and rebuild the original structure.
this.lineItems = mapTaxedLineItemsToStoreFormat(taxedLineItems, this.lineItems); this.lineItems = mapTaxedLineItemsToStoreFormat(
const taxedVaps = mapTaxedLineItemsToStoreFormat( taxedLineItems,
taxedLineItems, this.lineItems
this.availableVaps );
); const taxedVaps = mapTaxedLineItemsToStoreFormat(
taxedLineItems,
this.availableVaps
);
const getVaps = getVapsThatNeedToBeAddedToSatisfyPromos( const getVaps = getVapsThatNeedToBeAddedToSatisfyPromos(
promoCodeData.promoCode, promoCodeData.promoCode,
taxedVaps, taxedVaps,
this.lineItems this.lineItems
); );
this.lineItems.vaps?.push(...getVaps);
}
this.lineItems?.promos.push(...promoCodeData.promoCode); this.lineItems?.promos.push(...promoCodeData.promoCode);
this.lineItems.vaps?.push(...getVaps);
this.closeModal(); this.closeModal();
} else { } else {
this.getErrorMessage(promoCodeData.errorCode, promoCodeData.additionalInfo); this.getErrorMessage(promoCodeData.errorCode, promoCodeData.additionalInfo);

View file

@ -133,8 +133,6 @@ describe("quote.vue", () => {
}; };
}); });
wrapper.vm.pricedGlassParts = [];
//Act //Act
await wrapper.vm.forwardButtonAction(); await wrapper.vm.forwardButtonAction();
@ -172,8 +170,6 @@ describe("quote.vue", () => {
}; };
}); });
wrapper.vm.pricedGlassParts = [];
//Act //Act
await wrapper.vm.forwardButtonAction(); await wrapper.vm.forwardButtonAction();
@ -288,8 +284,8 @@ describe("quote.vue", () => {
); );
//Assert //Assert
expect(wrapper.vm.pricedGlassParts !== null).toBe(true); expect(wrapper.vm.lineItems !== null).toBe(true);
expect(wrapper.vm.supportingItems !== null).toBe(true); expect(wrapper.vm.availableVaps !== null).toBe(true);
expect(wrapper.vm.availableLineItems !== null).toBe(true); expect(wrapper.vm.availableLineItems !== null).toBe(true);
// This should have its own test // This should have its own test
//expect(vm.isInsuranceSelected !== null).toBe(true); //expect(vm.isInsuranceSelected !== null).toBe(true);
@ -548,6 +544,31 @@ describe("quote.vue", () => {
//Assert //Assert
expect(wrapper.vm.isInsuranceSelected).toBe(true); expect(wrapper.vm.isInsuranceSelected).toBe(true);
}); });
test("On forward button action save promos", async () => {
//Arrange
store.getters.payment = {
insuranceCoverage: {},
isInsurance: false,
};
store.getters.order = {
lineItems: [],
payment: {
parentAccountNumber: 167132,
},
};
const { wrapper } = setupMocks({
customMountOptions: {
router: {
navigateWithSaving: jest.fn(),
},
route: { quote },
},
});
wrapper.vm.forwardButtonAction();
expect(wrapper.vm.lineItems.promos !== null).toBe(true);
});
}); });
function setupMocks({ customMountOptions }) { function setupMocks({ customMountOptions }) {

View file

@ -39,6 +39,15 @@
cmsWidgetName="AfterpayModalWidget" cmsWidgetName="AfterpayModalWidget"
:lineItems="availableLineItems" /> :lineItems="availableLineItems" />
<promoModalQuestion
class="small mt-4"
v-model="lineItems"
:availableVaps="availableVaps"
:availableLineItems="availableLineItems"
pageName="quote"
:taxPromos="false"
modalWidgetName="PromoModalWidget" />
<textBlock <textBlock
cmsWidgetName="quoteDisclaimer" cmsWidgetName="quoteDisclaimer"
justifyText="left" justifyText="left"
@ -89,9 +98,11 @@ import { payWithInsuranceStates } from "@/constants/pay-with-insurance-states";
import { import {
revalidatePromosAndValidateQueryStringPromo, revalidatePromosAndValidateQueryStringPromo,
buildToastMessagesFromRevalidateOrValidatePromoResponse, buildToastMessagesFromRevalidateOrValidatePromoResponse,
createPromoSuccessAlert,
} from "@/helpers/promotions-helper"; } from "@/helpers/promotions-helper";
import { queryStrings } from "@/constants/query-strings"; import { queryStrings } from "@/constants/query-strings";
import { getQuerystringParameter } from "@/helpers/querystring-helper"; import { getQuerystringParameter } from "@/helpers/querystring-helper";
import promoModalQuestion from "@/layouts/payment-method/promo-modal-question/promo-modal-question";
defineRule("option-required", required(errorMessages.OPTION_REQUIRED)); defineRule("option-required", required(errorMessages.OPTION_REQUIRED));
@ -139,7 +150,8 @@ export default {
]; ];
const resultMap = await settleAllPromises(promiseResultMap); const resultMap = await settleAllPromises(promiseResultMap);
const nullSafeGlassParts = store.getters.order.lineItems.glassParts ?? []; const lineItems = store.getters.order.lineItems;
const nullSafeGlassParts = lineItems.glassParts ?? [];
const availableLineItems = [ const availableLineItems = [
resultMap.rainDefense, resultMap.rainDefense,
...resultMap.supportingItems, ...resultMap.supportingItems,
@ -164,6 +176,7 @@ export default {
// Promo logic // Promo logic
// Populate the previous state of promos for toast message usage in "next()" // Populate the previous state of promos for toast message usage in "next()"
const availableVaps = [...resultMap.wipers, resultMap.rainDefense];
const oldActivePromos = store.getters.lineItems.promos?.slice(0); const oldActivePromos = store.getters.lineItems.promos?.slice(0);
const oldInactivePromos = store.getters.order.payment.inactivePromos?.slice(0); const oldInactivePromos = store.getters.order.payment.inactivePromos?.slice(0);
@ -175,13 +188,16 @@ export default {
pricingResults, pricingResults,
"quote" "quote"
); );
lineItems.promos = lineItems.promos ?? [];
lineItems.vaps = lineItems.vaps ?? [];
// End of promo logic // End of promo logic
// Call the "next" function to complete the transition to this page. // Call the "next" function to complete the transition to this page.
next((vm) => { next((vm) => {
vm.setCmsContent(resultMap.cmsContent); vm.setCmsContent(resultMap.cmsContent);
vm.pricedGlassParts = nullSafeGlassParts; vm.availableVaps = availableVaps;
vm.supportingItems = resultMap.supportingItems; vm.lineItems = lineItems;
vm.availableLineItems = pricingResults; vm.availableLineItems = pricingResults;
vm.isInsuranceSelected = vm.getDefaultIsInsuranceSelectedValue(vm.availableLineItems); vm.isInsuranceSelected = vm.getDefaultIsInsuranceSelectedValue(vm.availableLineItems);
@ -208,15 +224,17 @@ export default {
data() { data() {
return { return {
isInsuranceSelected: null, isInsuranceSelected: null,
selectedVaps: null,
availableLineItems: null, availableLineItems: null,
supportingItems: null, lineItems: [],
pricedGlassParts: null, availableVaps: [],
}; };
}, },
computed: { computed: {
allActivePromos() { allActivePromos() {
return this.$store.getters.order.lineItems.promos ?? []; return this.lineItems.promos ?? [];
},
lineItemsCloneForWatcher() {
return Object.assign({}, this.lineItems);
}, },
}, },
methods: { methods: {
@ -258,7 +276,7 @@ export default {
} }
}, },
vapsItemsSelectedAction(vapsItemsSelected) { vapsItemsSelectedAction(vapsItemsSelected) {
this.selectedVaps = vapsItemsSelected; this.lineItems.vaps = vapsItemsSelected;
}, },
backButtonAction() { backButtonAction() {
vehicleQuestionsMixin.methods.navigateBack(this); vehicleQuestionsMixin.methods.navigateBack(this);
@ -285,15 +303,23 @@ export default {
this.supportingItems = this.filterOutFees(this.supportingItems); this.supportingItems = this.filterOutFees(this.supportingItems);
} }
if (this.pricedGlassParts.length > 0) { if (this.lineItems.glassParts?.length > 0) {
this.dispatchStoreAction( this.dispatchStoreAction(
this.storeActions.SAVE_GLASS_PARTS_SUPPRESSING_STATE_RESETTING, this.storeActions.SAVE_GLASS_PARTS_SUPPRESSING_STATE_RESETTING,
this.pricedGlassParts, this.lineItems.glassParts,
false false
); );
} }
this.dispatchStoreAction(this.storeActions.SAVE_VAPS, this.selectedVaps, false); this.dispatchStoreAction(this.storeActions.SAVE_VAPS, this.lineItems.vaps, false);
this.dispatchStoreAction(
storeActions.SAVE_ACTIVE_AND_OR_INACTIVE_PROMOS,
{
activePromos: this.lineItems.promos,
inactivePromos: this.$store.getters.order.payment.inactivePromos,
},
false
);
const payment = this.$store.getters.payment; const payment = this.$store.getters.payment;
if (payment.isInsurance) { if (payment.isInsurance) {
@ -310,6 +336,32 @@ export default {
} }
}, },
}, },
watch: {
lineItemsCloneForWatcher: {
handler(newValue, oldValue) {
if (
!oldValue ||
oldValue.length == 0 ||
!oldValue.vaps ||
!newValue ||
newValue.length == 0
) {
return;
}
if (oldValue.promos.length < newValue.promos.length) {
const oldPromoCodes = oldValue.promos.map(
(promoObject) => promoObject.promoCode
);
const newlyActivatedPromoCodes = newValue.promos.filter(
(newPromo) => !oldPromoCodes.includes(newPromo.promoCode)
);
const alert = createPromoSuccessAlert(newlyActivatedPromoCodes[0].promoCode);
this.$refs.funnelHeader.pushGlobalAlert(alert, alert.shouldAutoFade);
}
},
deep: true,
},
},
components: { components: {
funnelHeader, funnelHeader,
navbar, navbar,
@ -322,6 +374,7 @@ export default {
contentGroupModal, contentGroupModal,
loadingModal, loadingModal,
afterpayModalBanner, afterpayModalBanner,
promoModalQuestion,
}, },
}; };
</script> </script>

View file

@ -256,6 +256,57 @@ describe("service-package-question.vue", () => {
// Assert // Assert
expect(wrapper.vm.selectedPackageName).toBe("TierThree"); expect(wrapper.vm.selectedPackageName).toBe("TierThree");
}); });
it("should select default package if promos are added", async () => {
//Arrange
mockProps.activePromos != null;
const wrapper = setupMocks({
mountOptionsMockData: {
store: {
getters: {
order: {
damage: {
isRepair: false,
glassToReplace: [{ glassLocation: "Windshield" }],
},
},
lineItems: {
vaps: [],
},
hasAnyNonWindshieldGlassParts: false,
payment: {
isInsurance: false,
},
},
},
},
});
//Act
wrapper.setProps({
activePromos: [
{
discountedLineItemIds: [
{
0: "428ec73c-38e4-4e14-8703-a987b0391898",
1: "79db94d7-163c-4408-a1ec-f83e58c11992",
},
],
partType: "PROMO_DISCOUNT",
promoCode: "1WIPER0",
partNumber: "WIPER DISCOUNT",
laborAmount: 0,
sellingPrice: -10,
kitPrice: 0,
salesTax: null,
},
],
});
const selectDefaultPackageMock = jest.spyOn(wrapper.vm, "selectDefaultPackage");
await nextTick();
//Assert
expect(selectDefaultPackageMock).toHaveBeenCalled();
});
}); });
describe("service-package-question.vue, matching business rules for package display", () => { describe("service-package-question.vue, matching business rules for package display", () => {
// mock scenarios in figma: // mock scenarios in figma:

View file

@ -63,6 +63,9 @@ export default {
this.selectDefaultPackage(); this.selectDefaultPackage();
} }
}, },
activePromos() {
this.selectDefaultPackage();
},
selectedPackageName(newValue) { selectedPackageName(newValue) {
const VapsProductsInSelectedPackage = this.getVapsLineItemsForSelectedPackage(newValue); const VapsProductsInSelectedPackage = this.getVapsLineItemsForSelectedPackage(newValue);
this.$emit("vapsItemsSelected", VapsProductsInSelectedPackage); this.$emit("vapsItemsSelected", VapsProductsInSelectedPackage);

View file

@ -3,6 +3,7 @@ import vinLookup from "./vin-lookup.vue";
import { getMountOptions } from "@/helpers/unit-test-helper.js"; import { getMountOptions } from "@/helpers/unit-test-helper.js";
import { navigationScenarios } from "@/router/router-constants/navigation-scenarios.js"; import { navigationScenarios } from "@/router/router-constants/navigation-scenarios.js";
import { settleAllPromises } from "@/helpers/layout-helper.js"; import { settleAllPromises } from "@/helpers/layout-helper.js";
import { experimentSettings } from "@/constants/experiments";
import store from "@/store"; import store from "@/store";
@ -378,5 +379,11 @@ function mockOutStubFunctions(wrapper) {
const mockMixin = { const mockMixin = {
methods: { methods: {
getCmsContent: jest.fn(() => "placeholder CMS content"), getCmsContent: jest.fn(() => "placeholder CMS content"),
getSettingValue: jest.fn((settingName) => {
if (settingName === experimentSettings.IS_EMAIL_OPTIONAL) {
return "true";
}
return "false";
}),
}, },
}; };

View file

@ -53,8 +53,9 @@
cmsWidgetName="EmailAddressQuestionWidget" cmsWidgetName="EmailAddressQuestionWidget"
v-model="emailAddress" v-model="emailAddress"
customInputId="emailAddress" customInputId="emailAddress"
isRequired :isRequired="!IsEmailOptional"
validationRules="email-address-required|email-address-format" /> :validationRules="EmailValidationRules"
:isEmailOptional="IsEmailOptional" />
<textBlock cmsWidgetName="QuoteEmailTextBlockWidget" typeStyle="caption" /> <textBlock cmsWidgetName="QuoteEmailTextBlockWidget" typeStyle="caption" />

View file

@ -2,8 +2,20 @@ import { storeActions } from "@/constants/store-actions.js";
import store from "@/store"; import store from "@/store";
import vehicleQuestionsMixin from "@/mixins/vehicle-questions-mixin"; import vehicleQuestionsMixin from "@/mixins/vehicle-questions-mixin";
import { saveSession } from "@/helpers/heritage-integration/order-helper.js"; import { saveSession } from "@/helpers/heritage-integration/order-helper.js";
import { experimentSettings } from "@/constants/experiments";
export default { export default {
computed: {
IsEmailOptional() {
const emailOptional = this.getSettingValue(experimentSettings.IS_EMAIL_OPTIONAL);
return emailOptional === "true";
},
EmailValidationRules() {
return this.IsEmailOptional
? "email-address-format"
: "email-address-required|email-address-format";
},
},
methods: { methods: {
async navigateForwardWithSingleCarMatch() { async navigateForwardWithSingleCarMatch() {
const pageName = this.$options?.name; const pageName = this.$options?.name;