diff --git a/.eslintrc.js b/.eslintrc.js index 440245f9..33b69824 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -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': { diff --git a/src/constants/endpoints.js b/src/constants/endpoints.js index 8f21cdd9..770048e3 100644 --- a/src/constants/endpoints.js +++ b/src/constants/endpoints.js @@ -113,7 +113,7 @@ const endpoints = Object.freeze({ method: 'GET' }, GetAccountInfo: { - url: `${ACCOUNT_BASE_URL}/`, + url: `${ACCOUNT_BASE_URL}/account-details/`, method: 'GET' }, TaxOrderItems: { diff --git a/src/iss-components/cart-dropdown/__snapshots__/cart-dropdown.spec.js.snap b/src/iss-components/cart-dropdown/__snapshots__/cart-dropdown.spec.js.snap index ae4e42e9..5baa028a 100644 --- a/src/iss-components/cart-dropdown/__snapshots__/cart-dropdown.spec.js.snap +++ b/src/iss-components/cart-dropdown/__snapshots__/cart-dropdown.spec.js.snap @@ -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", diff --git a/src/iss-components/cart-dropdown/cart-dropdown.spec.js b/src/iss-components/cart-dropdown/cart-dropdown.spec.js index ab749535..b782d1b5 100644 --- a/src/iss-components/cart-dropdown/cart-dropdown.spec.js +++ b/src/iss-components/cart-dropdown/cart-dropdown.spec.js @@ -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([]); - }); - }); }); }); diff --git a/src/iss-components/cart-dropdown/cart-dropdown.vue b/src/iss-components/cart-dropdown/cart-dropdown.vue index 945c8f35..c14a047a 100644 --- a/src/iss-components/cart-dropdown/cart-dropdown.vue +++ b/src/iss-components/cart-dropdown/cart-dropdown.vue @@ -23,7 +23,7 @@ id="cart-deductible-or-base-price" class="cart-item cart-gray">
{{ deductibleLabel }} @@ -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 }, diff --git a/src/layouts/payment-method/payment-method.spec.js b/src/layouts/payment-method/payment-method.spec.js index 01e06a1e..476c9f59 100644 --- a/src/layouts/payment-method/payment-method.spec.js +++ b/src/layouts/payment-method/payment-method.spec.js @@ -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(); + }); }); }); diff --git a/src/layouts/payment-method/payment-method.vue b/src/layouts/payment-method/payment-method.vue index 34e1ed67..43c56607 100644 --- a/src/layouts/payment-method/payment-method.vue +++ b/src/layouts/payment-method/payment-method.vue @@ -26,7 +26,13 @@ servicePackageTitleWidgetName="ServicePackageTitle" :availableVaps="availableVaps" />
-
Pia Alert Placeholder
+ ({ +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;