This commit is contained in:
Michaela Brydon 2024-04-02 11:38:50 -04:00
commit 8328a1f14e
8 changed files with 369 additions and 148 deletions

View file

@ -44,7 +44,8 @@ module.exports = {
math: 'always'
}],
'import/extensions': ['error', 'always', { js: 'ignorePackages' }],
'no-param-reassign': ['error', { props: true, ignorePropertyModificationsFor: ['item'] }]
'no-param-reassign': ['error', { props: true, ignorePropertyModificationsFor: ['item'] }],
'no-restricted-syntax': ['off', 'ForOfStatement']
},
settings: {
'import/resolver': {

View file

@ -113,7 +113,7 @@ const endpoints = Object.freeze({
method: 'GET'
},
GetAccountInfo: {
url: `${ACCOUNT_BASE_URL}/`,
url: `${ACCOUNT_BASE_URL}/account-details/`,
method: 'GET'
},
TaxOrderItems: {

View file

@ -2,12 +2,7 @@
exports[`cart-dropdown component initial data rendered as expected 1`] = `
Object {
"baseServiceLineItems": Array [],
"deductible": null,
"glassToReplace": Array [],
"isExpanded": false,
"isRepair": null,
"vapsInOrder": Array [],
"widget": Object {
"amountDue": "AmountDueTextWidget",
"basePrice": "BasePriceWidget",

View file

@ -4,7 +4,7 @@ import cartDropdown from '@/iss-components/cart-dropdown/cart-dropdown.vue';
// Supporting Files
import { getMountOptions } from '@/helpers/unit-test-helper.js';
import { useMainStore } from '@/store';
import { useMainStore, getDefaultState } from '@/store';
import coverageStatuses from '@/constants/coverage-statuses';
import { formatAmountInDollars } from '@/helpers/text-helper.js';
import partTypeStrings from '@/constants/part-type-strings';
@ -599,7 +599,198 @@ describe('cart-dropdown component', () => {
expect(result).toBe(expectedPrice);
});
});
describe.skip('salesTax', () => {});
describe('salesTax', () => {
test('salesTax is treated as 0 when line items are null', () => {
// Arrange
const storeData = getDefaultState();
storeData.order.lineItems.glassParts = null;
storeData.order.lineItems.otherParts = null;
storeData.order.lineItems.supportingItems = null;
storeData.order.lineItems.vaps = null;
const { wrapper } = getMountedComponent(storeData);
// Act
const result = wrapper.vm.salesTax;
// Assert
expect(result).toBe(0);
});
test("salesTax is treated as 0 when null or undefined is set as the line item's sales tax.", () => {
// Arrange
const storeData = getDefaultState();
storeData.order.lineItems.glassParts = [
{ partType: 'mock', salesTax: null },
{ partType: 'mock', salesTax: undefined }
];
storeData.order.lineItems.supportingItems = [
{ partType: 'mock', salesTax: null },
{ partType: 'mock', salesTax: undefined }
];
storeData.order.lineItems.vaps = [
{ partType: 'mock', salesTax: null },
{ partType: 'mock', salesTax: undefined }
];
const { wrapper } = getMountedComponent(storeData);
// Act
const result = wrapper.vm.salesTax;
// Assert
expect(result).toBe(0);
});
test('Returns 0 as sales tax when coverage is unverified and order does not contain vaps.', () => {
// Arrange
const storeData = getDefaultState();
storeData.order.lineItems.glassParts = [
{ partType: 'mock', salesTax: 10 }
];
storeData.order.lineItems.supportingItems = [
{ partNumber: partNumberStrings.RECYCLE_FEE, partType: 'mock', salesTax: 10, sellingPrice: 39.99 }
];
storeData.order.lineItems.vaps = [];
storeData.order.payment.insuranceCoverage.isVerified = false;
const { wrapper } = getMountedComponent(storeData);
// Act
const result = wrapper.vm.salesTax;
// Assert
expect(result).toBe(0);
});
test('Returns sum of vaps sales tax when coverage is unverified and order has vaps.', () => {
// Arrange
const storeData = getDefaultState();
storeData.order.lineItems.glassParts = [
{ partType: 'mock', salesTax: 10 }
];
storeData.order.lineItems.vaps = [
{ partType: 'mock', salesTax: 1 },
{ partType: 'mock', salesTax: 2 }
];
storeData.order.payment.insuranceCoverage.isVerified = false;
const { wrapper } = getMountedComponent(storeData);
// Act
const result = wrapper.vm.salesTax;
// Assert
expect(result).toBe(3);
});
test('Returns Recycle Fee tax when coverage is Verified-Deductible, replace service, and no vaps.', () => {
// Arrange
const storeData = getDefaultState();
storeData.order.currentDeductible = 250;
storeData.order.lineItems.glassParts = [
{ partType: 'mock', salesTax: 10, sellingPrice: 150 }
];
storeData.order.lineItems.supportingItems = [
{ partNumber: partNumberStrings.RECYCLE_FEE, partType: 'mock', salesTax: 10, sellingPrice: 39.99 }
];
storeData.order.lineItems.vaps = null;
storeData.order.payment.insuranceCoverage.isVerified = true;
const { wrapper } = getMountedComponent(storeData);
// Act
const result = wrapper.vm.salesTax;
// Assert
expect(result).toBe(10);
});
test('Returns sum of vaps + recycle fee sales tax when Verified-Deductible, replace service, and has vaps.', () => {
// Arrange
const storeData = getDefaultState();
storeData.order.currentDeductible = 250;
storeData.order.lineItems.glassParts = [
{ partType: 'mock', salesTax: 10, sellingPrice: 100 }
];
storeData.order.lineItems.otherParts = [
{ partType: 'mock', salesTax: 10, kitPrice: 100 }
];
storeData.order.lineItems.supportingItems = [
{ partNumber: partNumberStrings.RECYCLE_FEE, partType: 'mock', salesTax: 10, sellingPrice: 39.99 },
{ partType: 'mock', salesTax: 10, kitPrice: 100 }
];
storeData.order.lineItems.vaps = [
{ partType: 'mock', salesTax: 2 },
{ partType: 'mock', salesTax: 3 }
];
storeData.order.payment.insuranceCoverage.isVerified = true;
const { wrapper } = getMountedComponent(storeData);
// Act
const result = wrapper.vm.salesTax;
// Assert
expect(result).toBe(15);
});
test('Returns sum of sales tax when Verified-ITAC.', () => {
// Arrange
const storeData = getDefaultState();
storeData.order.currentDeductible = 0;
storeData.order.lineItems.glassParts = [
{ partType: 'mock', salesTax: 10, sellingPrice: 100 }
];
storeData.order.lineItems.otherParts = [
{ partType: 'mock', salesTax: 10, kitPrice: 100 }
];
storeData.order.lineItems.supportingItems = [
{ partType: 'mock', salesTax: 10, kitPrice: 100 }
];
storeData.order.lineItems.vaps = [
{ partType: 'mock', salesTax: 5 }
];
storeData.order.payment.insuranceCoverage.isVerified = true;
storeData.order.policy.isITAC = true;
const { wrapper } = getMountedComponent(storeData);
// Act
const result = wrapper.vm.salesTax;
// Assert
expect(result).toBe(35);
});
test('Returns sum of sales tax when Verified-NoComp.', () => {
// Arrange
const storeData = getDefaultState();
storeData.order.currentDeductible = 0;
storeData.order.lineItems.glassParts = [
{ partType: 'mock', salesTax: 10, sellingPrice: 100 }
];
storeData.order.lineItems.otherParts = [
{ partType: 'mock', salesTax: 10, kitPrice: 100 }
];
storeData.order.lineItems.supportingItems = [
{ partType: 'mock', salesTax: 10, kitPrice: 100 }
];
storeData.order.lineItems.vaps = [
{ partType: 'mock', salesTax: 5 }
];
storeData.order.payment.insuranceCoverage.isVerified = true;
storeData.order.policy.noCoverage = true;
const { wrapper } = getMountedComponent(storeData);
// Act
const result = wrapper.vm.salesTax;
// Assert
expect(result).toBe(35);
});
});
describe('availableLineItems', () => {
test('returns expected when glassParts null', async () => {
// Arrange
@ -1292,7 +1483,7 @@ describe('cart-dropdown component', () => {
const storeData = {
order: {
lineItems: {
supportingItems: [{ partType: 'not recycle' }]
supportingItems: [{ partNumber: 'not recycle' }]
}
}
};
@ -1486,15 +1677,21 @@ describe('cart-dropdown component', () => {
describe('getDisplayed', () => {
test('returns "Verifying coverage" when not no comp, not itac, and deductible null', () => {
// Arrange
const storeData = {
order: {
policy: {
noCoverage: false,
isITAC: false
},
currentDeductible: null
}
};
// const storeData = {
// order: {
// policy: {
// noCoverage: false,
// isITAC: false
// },
// currentDeductible: null
// }
// };
const storeData = getDefaultState();
// storeData.order.payment.insuranceCoverage.coverageStatus = coverageStatuses.VERIFIED;
storeData.order.policy.noCoverage = false;
storeData.order.policy.isITAC = false;
storeData.order.currentDeductible = null;
const { wrapper } = getMountedComponent(storeData);
const amount = 123;
@ -1506,15 +1703,21 @@ describe('cart-dropdown component', () => {
});
test('returns "Verifying coverage" when not no comp, not itac, and coverage status PENDING', () => {
// Arrange
const storeData = {
order: {
policy: {
noCoverage: false,
isITAC: false
},
currentDeductible: 321
}
};
// const storeData = {
// order: {
// policy: {
// noCoverage: false,
// isITAC: false
// },
// currentDeductible: 321
// }
// };
const storeData = getDefaultState();
// storeData.order.payment.insuranceCoverage.coverageStatus = coverageStatuses.PENDING;
storeData.order.policy.noCoverage = false;
storeData.order.policy.isITAC = false;
storeData.order.currentDeductible = 321;
const { wrapper } = getMountedComponent(storeData);
const amount = 123;
@ -1829,75 +2032,5 @@ describe('cart-dropdown component', () => {
expect(result.subTotal).toBe(expectedSubtotal);
});
});
describe('removeItem', () => {
test('when isVap false, updateVaps not called', () => {
// Arrange
const storeData = {
order: {
lineItems: {
mobileFee: { partType: partTypeStrings.MOBILE_FEE },
vaps: [
{ partType: partTypeStrings.FRONT_WIPER },
{ partType: partTypeStrings.RAIN_DEFENSE }
]
}
}
};
const { wrapper } = getMountedComponent(storeData);
const initialVapsInOrder = wrapper.vm.vapsInOrder;
const partType = partTypeStrings.RAIN_DEFENSE;
const isVap = false;
// Act
wrapper.vm.removeItem(partType, isVap);
// Assert
expect(useMainStore().updateVaps).not.toHaveBeenCalled();
expect(wrapper.vm.vapsInOrder).toStrictEqual(initialVapsInOrder);
});
test('when isVap true, vapsInOrder is expected and updateVaps called', async () => {
// Arrange
const storeData = {
order: {
lineItems: {
mobileFee: { partType: partTypeStrings.MOBILE_FEE },
vaps: [
{ partType: partTypeStrings.FRONT_WIPER },
{ partType: partTypeStrings.REAR_WIPER }
]
}
}
};
const { wrapper } = getMountedComponent(storeData);
const partType = partTypeStrings.FRONT_WIPER;
const isVap = true;
const expectedVaps = [{ partType: partTypeStrings.REAR_WIPER }];
// Act
wrapper.vm.removeItem(partType, isVap);
await wrapper.vm.$nextTick();
// Assert
expect(useMainStore().updateVaps).toHaveBeenCalled();
expect(wrapper.vm.vapsInOrder).toStrictEqual(expectedVaps);
});
test('when isVap true and vapsInOrder null, vapsInOrder becomes []', () => {
// Arrange
const computedData = {
vapsInOrder: null
};
const { wrapper } = getMountedComponent({}, computedData);
const partType = partTypeStrings.FRONT_WIPER;
const isVap = true;
// Act
wrapper.vm.removeItem(partType, isVap);
// Assert
expect(useMainStore().updateVaps).toHaveBeenCalled();
expect(wrapper.vm.vapsInOrder).toStrictEqual([]);
});
});
});
});

View file

@ -23,7 +23,7 @@
id="cart-deductible-or-base-price"
class="cart-item cart-gray">
<div
v-if="showDeductibleLineItem"
v-if="showDeductibleCartItem"
id="cart-deductible"
class="d-flex justify-content-between align-items-center">
<span id="deductible-label">{{ deductibleLabel }}</span>
@ -171,20 +171,7 @@ export default {
availableVaps: Array
},
data() {
const { currentDeductible } = useMainStore().order;
const { glassToReplace, isRepair } = useMainStore().damage;
const { supportingItems, glassParts, otherParts, vaps } = useMainStore().lineItems;
return {
isRepair,
glassToReplace: glassToReplace ?? [],
vapsInOrder: vaps ?? [],
baseServiceLineItems: [
...(supportingItems ?? []),
...(glassParts ?? []),
...(otherParts ?? [])
],
deductible: currentDeductible,
isExpanded: false,
widget: {
amountDue: 'AmountDueTextWidget',
@ -200,9 +187,20 @@ export default {
};
},
computed: {
showDeductibleLineItem() {
showDeductibleCartItem() {
return !useMainStore().isNoComp && !useMainStore().policy.isITAC;
},
deductible() {
// Note: added as computed so it can be used in the template.
return useMainStore().order.currentDeductible;
},
baseServiceLineItems() {
return [
...(useMainStore().lineItems.supportingItems ?? []),
...(useMainStore().lineItems.glassParts ?? []),
...(useMainStore().lineItems.otherParts ?? [])
];
},
baseServicePrice() {
return getPriceOfLineItems(this.baseServiceLineItems) ?? 0;
},
@ -221,7 +219,27 @@ export default {
return basePrice + nonPackageCartItemsPrice + this.packagePrice;
},
salesTax() {
return 0; // TODO
function sumTax(lineItems) {
return lineItems?.reduce((accumulator, lineItem) => accumulator + (lineItem.salesTax ?? 0), 0) ?? 0;
}
let result = 0;
if (useMainStore().payment.insuranceCoverage.isVerified) {
if (useMainStore().isITAC || useMainStore().isNoComp) {
result += sumTax(this.baseServiceLineItems);
} else {
// deductible-case need to show tax for Recycle Fee
const recycleFeeLineItem = useMainStore().lineItems.supportingItems
?.find((lineItem) => lineItem.partNumber === partNumberStrings.RECYCLE_FEE);
result += recycleFeeLineItem != null ? recycleFeeLineItem.salesTax : 0;
}
}
result += sumTax(useMainStore().lineItems.vaps ?? []);
return result;
},
amountDue() {
return this.showAsPaid
@ -238,20 +256,28 @@ export default {
},
servicePackageTier() {
return getHighestFullySatisfiedTier(
this.glassToReplace,
useMainStore().damage.glassToReplace ?? [],
this.availableLineItems,
this.isRepair,
this.vapsInOrder
useMainStore().damage.isRepair,
useMainStore().lineItems.vaps ?? []
);
},
partTypesInServicePackage() {
return getPackageContents(
this.glassToReplace,
useMainStore().damage.glassToReplace ?? [],
this.availableLineItems,
this.isRepair,
useMainStore().damage.isRepair,
this.servicePackageTier
) ?? [];
},
// servicePackageCartItems() {
// const packageContentTypes = getPackageContents(
// useMainStore().damage.glassToReplace ?? [],
// this.availableLineItems,
// useMainStore().damage.isRepair,
// this.servicePackageTier
// ) ?? [];
// },
servicePackageCartItems() {
const items = [];
if (this.partTypesInServicePackage.includes(partTypeStrings.FRONT_WIPER) ?? this.frontWipersCartItem) {
@ -266,7 +292,7 @@ export default {
return items;
},
nonServicePackageCartItems() {
const partTypesInOrder = this.vapsInOrder?.reduce(
const partTypesInOrder = useMainStore().lineItems.vaps?.reduce(
(accumulator, vap) => {
if (!accumulator.includes(vap.partType)) {
accumulator.push(vap.partType);
@ -409,14 +435,15 @@ export default {
},
getCartItemForVapsPart(partType) {
const label = this.getCmsContentForVapsType(partType);
const lineItems = this.vapsInOrder
const lineItems = useMainStore().lineItems.vaps
?.filter((vapsLineItem) => vapsLineItem.partType === partType) ?? [];
return this.getCartItem(label, lineItems, true, partType, true, false);
},
removeItem(partType, isVap) {
if (isVap) {
this.vapsInOrder = this.vapsInOrder?.filter((vap) => vap?.partType !== partType) ?? [];
useMainStore().updateVaps(this.vapsInOrder);
const newVaps = useMainStore().lineItems.vaps?.filter((vap) => vap?.partType !== partType) ?? [];
console.log(`new vaps: ${JSON.stringify(newVaps)}`);
useMainStore().updateVaps(newVaps);
}
// NOTE Currently only vaps can be removed
},

View file

@ -5,39 +5,70 @@ import paymentMethod from '@/layouts/payment-method/payment-method.vue';
import { shallowMount } from '@vue/test-utils';
import { getMountOptions } from '@/helpers/unit-test-helper.js';
import { useMainStore } from '@/store';
import issPageValues from '@/router/router-constants/issPage-values';
import { paymentMethods } from '@/constants/payment-method-constants';
import queryStrings from '@/constants/query-strings';
function setupMocks() {
const mountOptions = getMountOptions();
function setupMocks({ customMountOptions = {}, queryString }) {
const mountOptions = getMountOptions({
...customMountOptions,
route: { query: { issPage: issPageValues.PAYMENT_METHOD, ...queryString }, params: {} }
});
const wrapper = shallowMount(paymentMethod, mountOptions);
return { wrapper };
return wrapper;
}
describe('payment-method.vue', () => {
test('getting payment method when method is pay later', async () => {
describe('Payment Method Type', () => {
test('getting payment method when method is pay later', async () => {
// Arrange
const { wrapper } = setupMocks();
const payLaterPaymentMethod = paymentMethods.PAY_AT_TIME_OF_SERVICE;
useMainStore().savePaymentMethodChoice(payLaterPaymentMethod);
const wrapper = setupMocks({});
const payLaterPaymentMethod = paymentMethods.PAY_AT_TIME_OF_SERVICE;
useMainStore().savePaymentMethodChoice(payLaterPaymentMethod);
// Act
const paymethod = wrapper.vm.getPaymentMethodFromStore();
// Act
const paymethod = wrapper.vm.getPaymentMethodFromStore();
// Assert
expect(paymethod).toBe(payLaterPaymentMethod);
// Assert
expect(paymethod).toBe(payLaterPaymentMethod);
});
test('getting payment method when method is pay in advance', async () => {
// Arrange
const wrapper = setupMocks({});
const payInAdvancePaymentMethod = paymentMethods.CREDIT_CARD;
useMainStore().savePaymentMethodChoice(payInAdvancePaymentMethod);
// Act
const paymethod = wrapper.vm.getPaymentMethodFromStore();
// Assert
expect(paymethod).not.toBe(payInAdvancePaymentMethod);
});
});
test('getting payment method when method is pay in advance', async () => {
// Arrange
const { wrapper } = setupMocks();
const payInAdvancePaymentMethod = paymentMethods.CREDIT_CARD;
useMainStore().savePaymentMethodChoice(payInAdvancePaymentMethod);
// Act
const paymethod = wrapper.vm.getPaymentMethodFromStore();
describe('Pay In Advance Error Alert', () => {
test('show pay in advance error alert when contained in querystring', () => {
// Arrange
const queryString = {
[queryStrings.DISPLAY_PAY_IN_ADVANCE_ALERT]: paymentMethods.AFTERPAY
};
const wrapper = setupMocks({ queryString });
// Assert
expect(paymethod).not.toBe(payInAdvancePaymentMethod);
// Assert
const payInAdvanceErrorAlert = wrapper.find('[name="payInAdvanceErrorAlert"]');
expect(payInAdvanceErrorAlert.exists()).toBeTruthy();
expect(payInAdvanceErrorAlert.isVisible()).toBeTruthy();
});
test('hide pay in advance error alert when not contained in querystring', () => {
// Arrange
const wrapper = setupMocks({});
// Assert
const payInAdvanceErrorAlert = wrapper.find('[name="payInAdvanceErrorAlert"]');
expect(payInAdvanceErrorAlert.exists()).toBeFalsy();
});
});
});

View file

@ -26,7 +26,13 @@
servicePackageTitleWidgetName="ServicePackageTitle"
:availableVaps="availableVaps" />
<hr class="mt-0 mb-5" />
<div>Pia Alert Placeholder</div>
<alert
v-if="displayPayInAdvanceAlert"
name="payInAdvanceErrorAlert"
class="my-4"
cmsWidgetName="PayInAdvanceErrorAlertWidget"
alertClass="alert-danger"
:isDismissible="false" />
<paymentMethodQuestion
v-model="paymentMethodInternalModel"
cmsWidgetName="PaymentMethodWidget"
@ -53,12 +59,14 @@ import siteSubHeader from '@/iss-components/site-sub-header/site-sub-header.vue'
import reviewDropdown from '@/layouts/payment-method/review-dropdown/review-dropdown.vue';
import cartDropdown from '@/iss-components/cart-dropdown/cart-dropdown.vue';
import paymentMethodQuestion from '@/layouts/payment-method/payment-method-question/payment-method-question.vue';
import alert from '@/ux-components/alert/alert.vue';
// Supporting Items
import settleAllPromises from '@/helpers/layout-helper';
import { useMainStore } from '@/store';
import { fetchCmsContentForPage } from '@/helpers/cms-content-helper';
import { paymentMethods } from '@/constants/payment-method-constants';
import queryStrings from '@/constants/query-strings';
import globalRules from '@/constants/global-rules';
import { Form } from 'vee-validate';
import issPageValues from '@/router/router-constants/issPage-values';
@ -75,7 +83,8 @@ export default {
siteFooter,
reviewDropdown,
cartDropdown,
paymentMethodQuestion
paymentMethodQuestion,
alert
},
mixins: [baseFormMixin],
async beforeRouteEnter(to, from, next) {
@ -106,6 +115,8 @@ export default {
const resultMap = await settleAllPromises(promiseResultMap);
await useMainStore().setPriceAndSalesTaxForOrderLineItems();
const unpricedVaps = [...resultMap.wipers, resultMap.rainDefense];
let hasBailedOut = false;
@ -155,6 +166,11 @@ export default {
},
paymentMethod() {
return this.paymentMethodInternalModel;
},
displayPayInAdvanceAlert() {
return (
this.$route.query[queryStrings.DISPLAY_PAY_IN_ADVANCE_ALERT]
);
}
},
watch: {

View file

@ -54,7 +54,7 @@ function getTimeSlotsAdditionalEventData(
return `FirstAvailableAppointment:${numberOfDays},Zip:${zipCode},ProvisionalTriggers:${provisionalTriggers.join(',')}`;
}
const getDefaultState = () => ({
export const getDefaultState = () => ({
order: {
vehicle: {
policyVehicleId: null,
@ -1550,17 +1550,35 @@ export const useMainStore = defineStore({
updateVaps(partsData) {
this.order.lineItems.vaps = partsData;
console.log(`store part data: ${JSON.stringify(partsData)}`);
},
updateMobileFee(mobileFee) {
this.order.lineItems.mobileFee = mobileFee;
},
async setPriceAndSalesTaxForOrderLineItems() {
const lineItemsToTax = [
...(this.order.lineItems.supportingItems ?? []),
...(this.order.lineItems.glassParts ?? []),
...(this.order.lineItems.otherParts ?? []),
...(this.order.lineItems.vaps ?? [])
];
if (this.order.lineItems.mobileFee != null) {
lineItemsToTax.push(this.order.lineItems.mobileFee);
}
const pricedLineItemsToTax = await this.getPriceOrderItems(lineItemsToTax);
this.taxOrderItemsAndSaveServerData(pricedLineItemsToTax);
},
updateVehicle(vehicle) {
// Assuming that the method caller pass all the properties.
// otherwise need to check for undefined for every property.
this.vehicle.policyVehicleId = vehicle.policyVehicleId;
this.vehicle.carId = vehicle.carId;
this.order.vehicle.policyVehicleId = vehicle.policyVehicleId;
this.order.vehicle.carId = vehicle.carId;
this.order.vehicle.category = vehicle.category;
this.order.vehicle.year = vehicle.year;
this.order.vehicle.make = vehicle.make;