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

This commit is contained in:
Adam Caouette 2024-02-13 09:25:55 -05:00
commit 05b0105e98
12 changed files with 267 additions and 132 deletions

View file

@ -121,9 +121,10 @@ export default {
hideInput: Boolean,
centerErrorMessage: Boolean,
keyDownHandler: Function,
isEmailOptional: {
addOptionalText: {
type: Boolean,
default: false,
isRequired: false,
},
},
setup(props) {
@ -197,7 +198,7 @@ export default {
},
computed: {
questionText() {
return this.isEmailOptional
return this.addOptionalText
? this.getCmsContent(this.cmsWidgetName, "QuestionText") + " (optional)"
: this.getCmsContent(this.cmsWidgetName, "QuestionText");
},

View file

@ -13,101 +13,78 @@
<span class="label amount-due">{{ getFormattedAmount("", amountDue) }}</span>
</a>
</div>
<div class="cart-panel">
<div class="price-table">
<div class="service-type">
<textBlock :cmsWidgetName="servicePackageTitleWidget" />
<span>
{{ getFormattedAmount("", packagePrice) }}
</span>
</div>
<!-- Packaged Cart Items -->
<div
v-for="(cartItem, i) in packageCartItems"
:key="i"
class="packaged-cart-item">
<span
><span v-if="cartItem.category === 'promos'">&lt;</span
>{{ cartItem.name
}}<span v-if="cartItem.category === 'promos'">&gt;</span></span
>
<textLink
v-if="allowItemRemoval"
ref="removeLink"
linkType="text"
:text="removeLinkText"
href="javascript:void(0)"
@click-event="removeItem(cartItem.cartItemType, cartItem.category)">
<template v-slot:after-text>
<span class="sr-only">{{ cartItem.name }}</span>
</template>
</textLink>
</div>
<!-- Nonpackaged Cart Items -->
<div
v-for="(cartItem, i) in nonpackageCartItems"
:key="i"
class="non-packaged-cart-item"
:class="[
i % 2 == 0 ? 'even' : 'odd',
cartItem.isRemovable ? 'removable-cart-item' : '',
]">
<span v-if="cartItem != recycleFeeCartItem">{{ cartItem.name }}</span>
<textLink
v-if="allowItemRemoval && cartItem.isRemovable"
ref="removeLink"
linkType="text"
:text="removeLinkText"
href="javascript:void(0)"
@click-event="removeItem(cartItem.cartItemType, cartItem.category)" />
<textLink
v-else-if="
cartItem == recycleFeeCartItem && recyclingModalCmsWidgetName
"
linkType="text"
:text="recycleFeeCartItem.name"
href="javascript:void(0)"
@click-event="openModal(recyclingModalCmsWidgetName)" />
<span v-else-if="cartItem == recycleFeeCartItem">
{{ recycleFeeCartItem.name }}
</span>
<span>{{ getFormattedAmount(cartItem.category, cartItem.subTotal) }}</span>
</div>
<!-- Sub total, sales tax, total columns -->
<div class="sub-total">
<span>{{ subtotalText }}</span
><span>{{ getFormattedAmount("", subTotal) }}</span>
</div>
<div class="sales-tax">
<span>{{ salesTaxText }}</span
><span>{{ getFormattedAmount("", salesTax) }}</span>
</div>
<div v-if="showAsPaid" class="amount-paid">
<span>{{ amountPaidText }}</span
><span>{{ getFormattedAmount("", amountPaid) }}</span>
</div>
<div class="amount-due">
<span>{{ amountDueText }}</span
><span>{{ getFormattedAmount("", amountDue) }}</span>
</div>
<div class="price-table">
<div class="service-type">
<textBlock :cmsWidgetName="servicePackageTitleWidget" />
<span>
{{ getFormattedAmount("", packagePrice) }}
</span>
</div>
<!-- Packaged Cart Items -->
<div v-for="(cartItem, i) in packageCartItems" :key="i" class="packaged-cart-item">
<span>{{ cartItem.name }}</span>
<textLink
v-if="allowItemRemoval"
ref="removeLink"
linkType="text"
:text="removeLinkText"
href="javascript:void(0)"
@click-event="removeItem(cartItem.cartItemType, cartItem.category)">
<template v-slot:after-text>
<span class="sr-only">{{ cartItem.name }}</span>
</template>
</textLink>
</div>
<!-- Nonpackaged Cart Items -->
<div
v-for="(cartItem, i) in nonpackageCartItems"
:key="i"
class="non-packaged-cart-item"
:class="[
i % 2 == 0 ? 'even' : 'odd',
cartItem.isRemovable ? 'removable-cart-item' : '',
]">
<span v-if="cartItem != recycleFeeCartItem">{{ cartItem.name }}</span>
<textLink
v-if="allowItemRemoval && cartItem.isRemovable"
ref="removeLink"
linkType="text"
:text="removeLinkText"
href="javascript:void(0)"
@click-event="removeItem(cartItem.cartItemType, cartItem.category)" />
<textLink
v-else-if="cartItem == recycleFeeCartItem && recyclingModalCmsWidgetName"
linkType="text"
:text="recycleFeeCartItem.name"
href="javascript:void(0)"
@click-event="openModal(recyclingModalCmsWidgetName)" />
<span v-else-if="cartItem == recycleFeeCartItem">
{{ recycleFeeCartItem.name }}
</span>
<span>{{ getFormattedAmount(cartItem.category, cartItem.subTotal) }}</span>
</div>
<!-- Sub total, sales tax, total columns -->
<div class="sub-total">
<span>{{ subtotalText }}</span
><span>{{ getFormattedAmount("", subTotal) }}</span>
</div>
<div class="sales-tax">
<span>{{ salesTaxText }}</span
><span>{{ getFormattedAmount("", salesTax) }}</span>
</div>
<div v-if="showAsPaid" class="amount-paid">
<span>{{ amountPaidText }}</span
><span>{{ getFormattedAmount("", amountPaid) }}</span>
</div>
<div class="amount-due">
<span>{{ amountDueText }}</span
><span>{{ getFormattedAmount("", amountDue) }}</span>
</div>
<promoModalQuestion
ref="promoModalQuestion"
class="small mt-4"
v-model="lineItems"
:availableVaps="availableVaps"
modalWidgetName="PromoModalWidget" />
</div>
</div>
<div
class="my-2 lh-1 applied-promo"
v-for="(promoCode, i) in this.getPromoCodeList()"
:key="i">
<span class="caption">Promo code &lt;{{ promoCode }}&gt; applied </span>
</div>
<contentGroupModal ref="RecycleModal" cmsWidgetName="RecycleModal">
<textBlock cmsWidgetName="RecycleTextBlock" />
</contentGroupModal>
@ -119,7 +96,6 @@
import textLink from "@/ux-components/text-link/text-link";
import textBlock from "@/digital-components/text-block/text-block";
import contentGroupModal from "@/fmg-components/content-group-modal/content-group-modal";
import promoModalQuestion from "@/layouts/payment-method/promo-modal-question/promo-modal-question";
// Mixins
import baseMixin from "@/mixins/base-mixin.js";
@ -222,12 +198,6 @@ export default {
this.$emit("update:modelValue", this.lineItems);
},
getPromoCodeList() {
if (this.$refs["promoModalQuestion"]) {
return this.$refs["promoModalQuestion"].getPromoCodeList();
}
return [];
},
},
computed: {
screenReaderTotalAmountDueText() {
@ -826,23 +796,16 @@ export default {
},
deep: true,
},
lineItems: {
handler(newValue) {
this.$emit("update:modelValue", this.lineItems);
},
deep: true,
},
},
components: {
textBlock,
textLink,
contentGroupModal,
promoModalQuestion,
},
};
</script>
<style lang="scss" scoped>
<style lang="scss">
.cart {
margin-bottom: 1rem;
@ -867,15 +830,12 @@ export default {
.amount-due {
color: $green;
}
.cart-panel {
.price-table {
max-height: 0;
transition: all 350ms ease-in;
overflow: hidden;
visibility: hidden;
color: $gray-600;
}
.price-table {
div {
display: flex;
justify-content: space-between;
@ -980,26 +940,17 @@ export default {
&.expanded:after {
transform: rotate(180deg);
}
&.expanded + .cart-panel {
&.expanded + .price-table {
max-height: 650px;
transition: all 150ms ease-in;
overflow: hidden;
padding: 1rem 0 0.5rem;
padding-top: 1rem;
visibility: visible;
}
a {
text-decoration: none;
}
}
.applied-promo {
span {
color: $green-700;
font-weight: 500;
padding: 2px 8px;
background-color: $green-100;
border-radius: 12px;
}
}
}
.payment-method-question {
.question-text {

View file

@ -2,6 +2,12 @@ import { mount, shallowMount } from "@vue/test-utils";
import promoModalQuestion from "./promo-modal-question";
import { storeActions } from "@/constants/store-actions";
import baseMixin from "@/mixins/base-mixin.js";
import {
promoErrorCodes,
getPromoCodesFromPromoObjectsWithoutDuplicates,
getPromoCodeWithoutBundleIdentifier,
} from "@/helpers/promotions-helper";
import { cartItemCategories } from "@/constants/cart-item-categories";
let mockReturnsForStoreActions = {};
@ -222,4 +228,173 @@ describe("promo-modal-question.vue", () => {
expect(taxedLineItems).toEqual(taxedlineItems);
});
test("Get promoCode list will return the applied promos", async () => {
// Arrange
const promos = [
{ promoCode: "duplicatePromo" },
{ promoCode: "duplicatePromo" },
{ promoCode: "bundlePromo/400" },
{ promoCode: "bundlePromo/401" },
];
const appliedPromos = ["duplicatePromo", "bundlePromo"];
const lineItems = {
glassParts: [],
supportingItems: [],
vaps: [],
promos: [],
};
const wrapper = mount(promoModalQuestion, {
mixins: [mockMixin],
props: {
modelValue: lineItems,
modalWidgetName: modalWidgetName,
},
attachTo: document.body,
});
// Act
wrapper.vm.getPromoCodeList();
const promoCodeToDisplay = getPromoCodesFromPromoObjectsWithoutDuplicates(promos);
// Assert
expect(promoCodeToDisplay).toEqual(appliedPromos);
});
test("On clicking remove the promo gets removed from the lineItems", async () => {
//Arrange
const lineItems = {
glassParts: [],
supportingItems: [],
vaps: [],
promos: [{ promoCode: "duplicatePromo" }, { promoCode: "duplicatePromo" }],
};
const wrapper = mount(promoModalQuestion, {
mixins: [mockMixin],
props: {
modelValue: lineItems,
modalWidgetName: modalWidgetName,
},
attachTo: document.body,
});
const promo = ["bundlePromo"];
//Act
wrapper.vm.removeItem(promo);
const existingPromo = (lineItems[cartItemCategories.PROMOS] = lineItems[
cartItemCategories.PROMOS
].filter(
(lineItemsToKeep) =>
getPromoCodeWithoutBundleIdentifier(lineItemsToKeep.promoCode) != promo
));
//Assert
expect(existingPromo).toEqual(lineItems.promos);
});
test("Getting stacking alert on stack promo error code", async () => {
// Arrange
const lineItems = {
glassParts: [],
supportingItems: [],
vaps: [],
promos: [],
};
const wrapper = mount(promoModalQuestion, {
mixins: [mockMixin],
props: {
modelValue: lineItems,
modalWidgetName: modalWidgetName,
},
attachTo: document.body,
});
const additionalInfo = ["testAdditionalInfo"];
const stackingErrorCode = promoErrorCodes.PROMO_STACKING_NOT_ALLOWED;
// Act
wrapper.vm.getErrorMessage(stackingErrorCode, additionalInfo);
// Assert
expect(wrapper.vm.displayStackingPromoAlert).toBe(true);
});
test("Getting invalid promo alert on invalid promoCode", async () => {
// Arrange
const lineItems = {
glassParts: [],
supportingItems: [],
vaps: [],
promos: [],
};
const wrapper = mount(promoModalQuestion, {
mixins: [mockMixin],
props: {
modelValue: lineItems,
modalWidgetName: modalWidgetName,
},
attachTo: document.body,
});
const additionalInfo = ["testAdditionalInfo"];
const invalidErrorCode = promoErrorCodes.INVALID_PROMO_ON_ORDER;
// Act
wrapper.vm.getErrorMessage(invalidErrorCode, additionalInfo);
// Assert
expect(wrapper.vm.displayInvalidPromoAlert).toBe(true);
});
test("Getting invalid promo alert on invalid promoCode", async () => {
// Arrange
const lineItems = {
glassParts: [],
supportingItems: [],
vaps: [],
promos: [],
};
const wrapper = mount(promoModalQuestion, {
mixins: [mockMixin],
props: {
modelValue: lineItems,
modalWidgetName: modalWidgetName,
},
attachTo: document.body,
});
const additionalInfo = ["APPOINTMENT_TYPE"];
const invalidErrorCode = promoErrorCodes.INVALID_PROMO_ON_ORDER;
// Act
wrapper.vm.getErrorMessage(invalidErrorCode, additionalInfo);
// Assert
expect(wrapper.vm.displayInShopPromoAlert).toBe(true);
});
test("Get conflicting promoCodes on applying more than one promo", async () => {
// Arrange
const lineItems = {
glassParts: [],
supportingItems: [],
vaps: [],
promos: [],
};
const wrapper = mount(promoModalQuestion, {
mixins: [mockMixin],
props: {
modelValue: lineItems,
modalWidgetName: modalWidgetName,
},
attachTo: document.body,
});
const additionalInfo = ["1wiper0", "Glass30"];
const conflictingCodes = ["1wiper0", "Glass30"];
//Act
wrapper.vm.getConflictingPromoCode(additionalInfo);
//Assert
expect(conflictingCodes).toEqual(additionalInfo);
});
});

View file

@ -64,7 +64,7 @@
</template>
<script>
import textLink from "@/ux-components/text-link/text-link";
import promoQuestion from "@/layouts/payment-method/promo-modal-question/promo-question/promo-question";
import promoQuestion from "@/fmg-components/promo-modal-question/promo-question/promo-question";
import modal from "@/digital-components/modal/modal";
import alert from "@/ux-components/alert/alert";
import { storeActions } from "@/constants/store-actions.js";

View file

@ -29,7 +29,7 @@
customInputId="emailAddress"
:isRequired="!isEmailOptional"
:validationRules="validationRules"
:isEmailOptional="isEmailOptional" />
:addOptionalText="isEmailOptional" />
</div>
</div>
<div class="row mb-0">

View file

@ -55,7 +55,7 @@
:isRequired="!IsEmailOptional"
disableAutoFill
:validationRules="EmailValidationRules"
:isEmailOptional="IsEmailOptional" />
:addOptionalText="IsEmailOptional" />
<textBlock cmsWidgetName="QuoteEmailTextBlockWidget" typeStyle="caption" />
<alert

View file

@ -36,7 +36,7 @@
customInputId="email"
:isRequired="!IsEmailOptional"
:validationRules="EmailValidationRules"
:isEmailOptional="IsEmailOptional" />
:addOptionalText="IsEmailOptional" />
<textBlock cmsWidgetName="QuoteEmailTextBlockWidget" typeStyle="caption" />

View file

@ -29,7 +29,13 @@
servicePackageOptionsCmsName="ServicePackageTitle"
recyclingModalCmsWidgetName="RecycleModal" />
<hr class="mb-5" />
<promoModalQuestion
class="small mt-4"
v-model="lineItems"
:availableVaps="availableVaps"
modalWidgetName="PromoModalWidget" />
<hr class="my-5" />
<div>
<alert
@ -76,6 +82,7 @@ import paymentMethodQuestion from "@/layouts/payment-method/payment-method-quest
import loadingModal from "@/fmg-components/loading-modal/loading-modal.vue";
import cart from "@/fmg-components/cart/cart";
import reviewDropdown from "@/layouts/payment-method/review-dropdown/review-dropdown";
import promoModalQuestion from "@/fmg-components/promo-modal-question/promo-modal-question";
import { submitWorkOrder } from "@/helpers/heritage-integration/order-helper.js";
import alert from "@/ux-components/alert/alert";
@ -589,6 +596,7 @@ export default {
cart,
alert,
paymentMethodQuestion,
promoModalQuestion,
reviewDropdown,
},
};

View file

@ -102,7 +102,7 @@ import {
} from "@/helpers/promotions-helper";
import { queryStrings } from "@/constants/query-strings";
import { getQuerystringParameter } from "@/helpers/querystring-helper";
import promoModalQuestion from "@/layouts/payment-method/promo-modal-question/promo-modal-question";
import promoModalQuestion from "@/fmg-components/promo-modal-question/promo-modal-question";
defineRule("option-required", required(errorMessages.OPTION_REQUIRED));
@ -188,7 +188,7 @@ export default {
pricingResults,
"quote"
);
lineItems.glassParts = nullSafeGlassParts;
lineItems.promos = lineItems.promos ?? [];
lineItems.vaps = lineItems.vaps ?? [];
// End of promo logic

View file

@ -55,7 +55,7 @@
customInputId="emailAddress"
:isRequired="!IsEmailOptional"
:validationRules="EmailValidationRules"
:isEmailOptional="IsEmailOptional" />
:addOptionalText="IsEmailOptional" />
<textBlock cmsWidgetName="QuoteEmailTextBlockWidget" typeStyle="caption" />