diff --git a/src/constants/endpoints.js b/src/constants/endpoints.js index c55410da..6e2de5cb 100644 --- a/src/constants/endpoints.js +++ b/src/constants/endpoints.js @@ -125,6 +125,10 @@ const endpoints = Object.freeze({ url: `${PARTS_BASE_URL}/mobile-fee`, method: 'GET' }, + GetSV2GlassTypes: { + url: `${PARTS_BASE_URL}/map-to-sv2-glass-types`, + method: 'POST' + }, GetServiceabilityDetails: { url: `${LOCATION_BASE_URL}/serviceability-details`, method: 'GET' diff --git a/src/constants/part-type-strings.js b/src/constants/part-type-strings.js index 4876b999..040ca6ad 100644 --- a/src/constants/part-type-strings.js +++ b/src/constants/part-type-strings.js @@ -7,7 +7,8 @@ RECALIBRATION: 'RECALIBRATION', REPLACE_FEE: 'REPLACE FEE', MOBILE_FEE: 'MOBILE FEE', - REPAIR_FEE: 'REPAIR FEE' + REPAIR_FEE: 'REPAIR FEE', + WARRANTY: 'WARRANTY' }); export default partTypeStrings; diff --git a/src/helpers/damage-helper.js b/src/helpers/damage-helper.js index 5e886326..34bfc890 100644 --- a/src/helpers/damage-helper.js +++ b/src/helpers/damage-helper.js @@ -71,6 +71,26 @@ export async function isGlassAvailableForCarId(carId) { } } +export function getGlassList(glassPieces) { + let names = ''; + switch (glassPieces.length) + { + case 0: + break; + case 1: + names = glassPieces[0]; + break; + case 2: + names = glassPieces.join(' and '); + break; + default: + names = glassPieces.slice(0, -1).join(', ') + ', and ' + glassPieces.slice(-1); + break; + } + + return names.toLowerCase(); +} + /** * Commented code are copied directly from DigitalConsumer.FixMyGlass * and have not been adjusted for ISS. diff --git a/src/iss-components/confirmation-cart/confirmation-cart.spec.js b/src/iss-components/confirmation-cart/confirmation-cart.spec.js new file mode 100644 index 00000000..7391f3d9 --- /dev/null +++ b/src/iss-components/confirmation-cart/confirmation-cart.spec.js @@ -0,0 +1,1580 @@ +import { shallowMount } from '@vue/test-utils'; +import { createTestingPinia } from '@pinia/testing'; +import confirmationCart from '@/iss-components/confirmation-cart/confirmation-cart.vue'; + +// Supporting Files +import { getEnumName, getMountOptions } from '@/helpers/unit-test-helper.js'; +import { useMainStore, getDefaultState } from '@/store'; +import { formatAmountInDollars } from '@/helpers/text-helper.js'; +import partTypeStrings from '@/constants/part-type-strings'; +import partNumberStrings from '@/constants/part-number-strings'; +import { getHighestFullySatisfiedTier, getPackageContents } from '@/helpers/service-package-helper.js'; +import { getPriceOfLineItems } from '@/helpers/price-calculator.js'; +import coverageStatuses from '@/constants/coverage-statuses'; +import coverageType from '@/constants/coverage-type'; + +const VERIFYING_COVERAGE = 'Verifying coverage'; + +jest.mock('@/helpers/text-helper', () => ({ + formatAmountInDollars: jest.fn() +})); +jest.mock('@/helpers/price-calculator.js', () => ({ + getPriceOfLineItems: jest.fn(), + getTaxOfLineItems: jest.fn(), + getPriceOfLineItem: jest.fn() +})); + +jest.mock('@/helpers/service-package-helper', () => ({ + getHighestFullySatisfiedTier: jest.fn(), + getPackageContents: jest.fn() +})); + +function getMountedComponent(mainInitialState = {}, initialData = {}, propsData = {}) { + const mountOptions = getMountOptions({ + router: { + navigate: jest.fn() + } + }); + + const testingPinia = createTestingPinia({ + initialState: { + main: mainInitialState + } + }); + useMainStore(testingPinia); + + mountOptions.global.mixins[0].methods.getSettingValue = jest.fn(() => 'false'); + mountOptions.global.plugins = [testingPinia]; + mountOptions.data = () => (initialData); + mountOptions.propsData = propsData; + + const wrapper = shallowMount(confirmationCart, mountOptions); + return { wrapper }; +} + +beforeEach(() => { + const store = useMainStore(); + const defaultState = getDefaultState(); + Object.keys(defaultState).forEach((key) => { + store[key] = defaultState[key]; + }); +}); + +describe('confirmation-cart component', () => { + test('initial data rendered as expected', () => { + // Arrange + const { wrapper } = getMountedComponent({ + order: { + availableVaps: [] + } + }); + + // Assert + expect(wrapper.vm.$data).toMatchSnapshot(); + }); + describe('displays', () => { + test('cart dropdown head', () => { + // Arrange + const reference = '#cart-dropdown-head'; + const { wrapper } = getMountedComponent(confirmationCart); + + // Act + const head = wrapper.find(reference); + + // Assert + expect(head.exists()).toBeTruthy(); + }); + test('cart dropdown head is visible', () => { + // Arrange + const reference = '#cart-dropdown-head'; + const { wrapper } = getMountedComponent(confirmationCart, {}, { showDropdownHeader: true }); + + // Act + const head = wrapper.find(reference); + + // Assert + expect(head.exists()).toBeTruthy(); + expect(head.classes()).toContain('cart-toggle'); + }); + test('cart dropdown head is not visible', () => { + // Arrange + const reference = '#cart-dropdown-head'; + const { wrapper } = getMountedComponent(confirmationCart, {}, { showDropdownHeader: false }); + + // Act + const head = wrapper.find(reference); + + // Assert + expect(head.exists()).toBeTruthy(); + expect(head.classes()).not.toContain('cart-toggle'); + }); + test('cart table', () => { + // Arrange + const reference = '#cart-table'; + const isExpanded = true; + const initialData = { isExpanded }; + const { wrapper } = getMountedComponent({}, {}, initialData); + + // Act + const cartTable = wrapper.find(reference); + + // Assert + expect(cartTable.exists()).toBeTruthy(); + }); + test('cart deductible when showDeductibleLineItem is true', () => { + // Arrange + const reference = '#cart-deductible'; + const isExpanded = true; + const initialData = { isExpanded }; + const storeData = { + order: { + insuranceCoverage: { + coverageStatus: coverageStatuses.VERIFIED, + coverageType: coverageType.Deductible + } + } + }; + const { wrapper } = getMountedComponent(storeData, {}, initialData); + + // Act + const cartDeductible = wrapper.find(reference); + + // Assert + expect(cartDeductible.exists()).toBeTruthy(); + }); + test('cart base price when showDeductibleLineItem is false', () => { + // Arrange + const reference = '#cart-base-price'; + const isExpanded = true; + const initialData = { isExpanded }; + const storeData = { + order: { + currentDeductible: 0, + lineItems: { }, + insuranceCoverage: { + coverageStatus: coverageStatuses.VERIFIED, + coverageType: coverageType.ITAC + } + }, + issConfig: { + isClaimRegistrationRequired: true + } + }; + const { wrapper } = getMountedComponent(storeData, {}, initialData); + + // Act + const cartBasePrice = wrapper.find(reference); + + // Assert + expect(cartBasePrice.exists()).toBeTruthy(); + }); + test('service package', () => { + // Arrange + const reference = '#cart-service-package'; + const isExpanded = true; + const initialData = { isExpanded }; + const { wrapper } = getMountedComponent({}, {}, initialData); + + // Act + const servicePackage = wrapper.find(reference); + + // Assert + expect(servicePackage.exists()).toBeTruthy(); + }); + test('non service package cart items', () => { + // Arrange + const reference = '#non-packaged-cart-items'; + const isExpanded = true; + const initialData = { isExpanded }; + const { wrapper } = getMountedComponent({}, {}, initialData); + + // Act + const nonPackagedCartItems = wrapper.find(reference); + + // Assert + expect(nonPackagedCartItems.exists()).toBeTruthy(); + }); + test('recycle cart item label when isRecycle true for some cart item', () => { + // Arrange + const reference = '#recycle-fee-label'; + const isExpanded = true; + const initialPropsData = { isExpanded }; + const storeData = { + order: { + lineItems: { + feeItems: [ + { partNumber: partNumberStrings.RECYCLE_FEE } + ] + } + } + }; + const { wrapper } = getMountedComponent(storeData, {}, initialPropsData); + + // Act + const recycleFeeLabel = wrapper.find(reference); + + // Assert + expect(wrapper.vm.recycleFeeCartItem).not.toBeNull(); + expect(recycleFeeLabel.exists()).toBeTruthy(); + }); + test('cart footer', () => { + // Arrange + const reference = '#cart-footer'; + const isExpanded = true; + const initialData = { isExpanded }; + const { wrapper } = getMountedComponent({}, {}, initialData); + + // Act + const cartFooter = wrapper.find(reference); + + // Assert + expect(cartFooter.exists()).toBeTruthy(); + }); + test('subtotal', () => { + // Arrange + const reference = '#cart-subtotal'; + const isExpanded = true; + const initialData = { isExpanded }; + const { wrapper } = getMountedComponent({}, {}, initialData); + + // Act + const subtotal = wrapper.find(reference); + + // Assert + expect(subtotal.exists()).toBeTruthy(); + }); + test('tax', () => { + // Arrange + const reference = '#cart-sales-tax'; + const isExpanded = true; + const initialData = { isExpanded }; + const { wrapper } = getMountedComponent({}, {}, initialData); + + // Act + const salesTax = wrapper.find(reference); + + // Assert + expect(salesTax.exists()).toBeTruthy(); + }); + test('amount paid when Pay in Advance', () => { + // Arrange + const reference = '#cart-amount-paid'; + const isExpanded = true; + const showAsPaid = true; + const initialPropsData = { isExpanded, showAsPaid }; + const storeData = { + order: { + payment: { + isPayInAdvance: true + } + } + }; + const { wrapper } = getMountedComponent(storeData, {}, initialPropsData); + + // Act + const amountPaid = wrapper.find(reference); + + // Assert + expect(amountPaid.exists()).toBeTruthy(); + }); + test('bottom amount due', () => { + // Arrange + const reference = '#bottom-amount-due'; + const isExpanded = true; + const initialData = { isExpanded }; + const { wrapper } = getMountedComponent({}, {}, initialData); + + // Act + const bottomAmountDue = wrapper.find(reference); + + // Assert + expect(bottomAmountDue.exists()).toBeTruthy(); + }); + }); + describe('does not display', () => { + test('cart deductible when showDeductibleLineItem is false', () => { + // Arrange + const reference = '#cart-deductible'; + const isExpanded = true; + const initialData = { isExpanded }; + const storeData = { + order: { + currentDeductible: 0, + lineItems: { }, + insuranceCoverage: { + coverageStatus: coverageStatuses.VERIFIED, + coverageType: coverageType.ITAC + } + }, + issConfig: { + isClaimRegistrationRequired: true + } + }; + const { wrapper } = getMountedComponent(storeData, {}, initialData); + + // Act + const cartDeductible = wrapper.find(reference); + + // Assert + expect(cartDeductible.exists()).toBeFalsy(); + }); + test('cart base price when showDeductibleLineItem is true', () => { + // Arrange + const reference = '#cart-base-price'; + const isExpanded = true; + const initialData = { isExpanded }; + const storeData = { + order: { + insuranceCoverage: { + coverageStatus: coverageStatuses.VERIFIED, + coverageType: coverageType.Deductible + } + } + }; + const { wrapper } = getMountedComponent(storeData, {}, initialData); + + // Act + const cartBasePrice = wrapper.find(reference); + + // Assert + expect(cartBasePrice.exists()).toBeFalsy(); + }); + test('recycle cart item label when isRecycle false for all cart items', () => { + // Arrange + const reference = '#recycle-fee-label'; + const isExpanded = true; + const initialPropsData = { isExpanded }; + const storeData = { + order: { + lineItems: { + supportingItems: [] + } + } + }; + const { wrapper } = getMountedComponent(storeData, {}, initialPropsData); + + // Act + const recycleFeeLabel = wrapper.find(reference); + + // Assert + expect(wrapper.vm.recycleFeeCartItem).toBeNull(); + expect(recycleFeeLabel.exists()).toBeFalsy(); + }); + }); + describe('computed', () => { + describe('amountDue', () => { + test('returns 0 when showAsPaid is true', () => { + // Arrange + const propsData = { + showAsPaid: true + }; + const { wrapper } = getMountedComponent({}, {}, propsData); + + // Act + const result = wrapper.vm.amountDue; + + // Assert + expect(result).toBe(0); + }); + test('returns sum of subTotal and salesTax when showAsPaid is false', () => { + // TODO when subTotal and salesTax are finished + }); + }); + describe('availableLineItems', () => { + test('returns expected when all null', () => { + // Arrange + const propsData = { availableVaps: null }; + const { wrapper } = getMountedComponent({}, {}, propsData); + + // Act + const result = wrapper.vm.availableLineItems; + + // Assert + expect(result).toStrictEqual([]); + }); + test('returns expected when glassParts not null', async () => { + // Arrange + const glassParts = [{ partNumber: 'glass' }]; + const storeData = { + order: { + lineItems: { glassParts } + } + }; + const propsData = { availableVaps: null }; + const { wrapper } = getMountedComponent(storeData, {}, propsData); + const expected = glassParts; + + // Act + const result = wrapper.vm.availableLineItems; + + // Assert + expect(result).toStrictEqual(expected); + }); + test('returns expected when supportingItems not null', async () => { + // Arrange + const supportingItems = [{ partNumber: 'support' }]; + const storeData = { + order: { + lineItems: { supportingItems } + } + }; + const propsData = { availableVaps: null }; + const { wrapper } = getMountedComponent(storeData, {}, propsData); + const expected = supportingItems; + + // Act + const result = wrapper.vm.availableLineItems; + + // Assert + expect(result).toStrictEqual(expected); + }); + test('returns expected when otherParts not null', () => { + // Arrange + const otherParts = [{ partNumber: 'other' }]; + const storeData = { + order: { + lineItems: { otherParts } + } + }; + const propsData = { availableVaps: null }; + const { wrapper } = getMountedComponent(storeData, {}, propsData); + const expected = otherParts; + + // Act + const result = wrapper.vm.availableLineItems; + + // Assert + expect(result).toStrictEqual(expected); + }); + test('returns expected when mobileFee not null', () => { + // Arrange + const mobileFee = { partNumber: 'mobile' }; + const storeData = { + order: { + lineItems: { + feeItems: [mobileFee] + } + } + }; + const propsData = { availableVaps: null }; + const { wrapper } = getMountedComponent(storeData, {}, propsData); + const expected = [mobileFee]; + + // Act + const result = wrapper.vm.availableLineItems; + + // Assert + expect(result).toStrictEqual(expected); + }); + test('returns expected when availableVaps null', async () => { + // Arrange + const availableVaps = [{ partNumber: 'vaps1' }]; + const initialData = { availableVaps }; + const { wrapper } = getMountedComponent({}, initialData, {}); + const expected = availableVaps; + + // Act + const result = wrapper.vm.availableLineItems; + + // Assert + expect(result).toStrictEqual(expected); + }); + test('returns expected when glassParts, supportingItems, otherParts, mobileFee, and availableVaps have content', async () => { + // Arrange + const part1 = { partType: 'apple' }; + const part2 = { partType: 'banana' }; + const part3 = { partType: 'orange' }; + const part4 = { partType: 'grape' }; + const part5 = { partType: 'raspberry' }; + const mobileFee = { partType: 'mobile' }; + const storeData = { + order: { + lineItems: { + glassParts: [part1], + supportingItems: [part2, part3], + otherParts: [], + feeItems: [mobileFee] + } + } + }; + const initialData = { + availableVaps: [part4, part5] + }; + const { wrapper } = getMountedComponent(storeData, initialData, {}); + const sortByPartType = (a, b) => { + const typeA = a.partType.toUpperCase(); + const typeB = b.partType.toUpperCase(); + if (typeA < typeB) { + return -1; + } + if (typeA > typeB) { + return 1; + } + return 0; + }; + const expected = [part1, part2, part3, part4, part5, mobileFee]; + + // Act + const result = wrapper.vm.availableLineItems; + + // Assert + expect(result.sort(sortByPartType)).toStrictEqual(expected.sort(sortByPartType)); + }); + }); + describe('servicePackageCartItems', () => { + test.each([ + [undefined], [null], [[]] + ])('returns empty list when package contents %p', (packageContents) => { + // Arrange + getPackageContents.mockImplementationOnce(() => packageContents); + const { wrapper } = getMountedComponent(); + const expected = []; + + // Act + const result = wrapper.vm.servicePackageCartItems; + + // Assert + expect(result).toStrictEqual(expected); + }); + test('returns front wiper when front wiper included in types', () => { + // Arrange + const frontWiperPartType = partTypeStrings.FRONT_WIPER; + const packageContents = [frontWiperPartType]; + getPackageContents.mockImplementation(() => packageContents); + + const mountOptions = getMountOptions({ + router: { navigate: jest.fn() } + }); + const expectedName = 'some name'; + const vapsItemDescriptions = [ + { + Name: frontWiperPartType, + Text: expectedName + } + ]; + const widgetName = 'VapsItemDescriptions'; + const mockMixin = { + methods: { + getCmsContent: jest.fn((widget, _) => (widget === widgetName ? vapsItemDescriptions : [])) + } + }; + mountOptions.mixins = [mockMixin]; + const storeData = { + order: { + lineItems: { + vaps: [{ partType: frontWiperPartType }] + } + } + }; + const testingPinia = createTestingPinia({ + initialState: { + main: storeData + } + }); + useMainStore(testingPinia); + mountOptions.global.plugins = [testingPinia]; + + const wrapper = shallowMount(confirmationCart, mountOptions); + + // Act + const result = wrapper.vm.servicePackageCartItems; + + // Assert + expect(result.length).toBe(1); + expect(result[0].name).toBe(expectedName); + }); + test('returns rear wiper when rear wiper included in types', () => { + // Arrange + const rearWiperPartType = partTypeStrings.REAR_WIPER; + const packageContents = [rearWiperPartType]; + getPackageContents.mockImplementationOnce(() => packageContents); + + const mountOptions = getMountOptions({ + router: { navigate: jest.fn() } + }); + const expectedName = 'some other name'; + const vapsItemDescriptions = [ + { + Name: rearWiperPartType, + Text: expectedName + } + ]; + const mockMixin = { + methods: { + getCmsContent: jest.fn(() => vapsItemDescriptions) + } + }; + mountOptions.mixins = [mockMixin]; + const storeData = { + order: { + lineItems: { + vaps: [{ partType: rearWiperPartType }] + } + } + }; + const testingPinia = createTestingPinia({ + initialState: { + main: storeData + } + }); + useMainStore(testingPinia); + mountOptions.global.plugins = [testingPinia]; + + const wrapper = shallowMount(confirmationCart, mountOptions); + + // Act + const result = wrapper.vm.servicePackageCartItems; + + // Assert + expect(result.length).toBe(1); + expect(result[0].name).toBe(expectedName); + }); + test('returns rain defense when rain defense included in types', () => { + // Arrange + const rainDefensePartType = partTypeStrings.RAIN_DEFENSE; + const packageContents = [rainDefensePartType]; + getPackageContents.mockImplementationOnce(() => packageContents); + + const mountOptions = getMountOptions({ + router: { navigate: jest.fn() } + }); + const expectedName = 'different name'; + const vapsItemDescriptions = [ + { + Name: rainDefensePartType, + Text: expectedName + } + ]; + const mockMixin = { + methods: { + getCmsContent: jest.fn(() => vapsItemDescriptions) + } + }; + mountOptions.mixins = [mockMixin]; + const storeData = { + order: { + lineItems: { + vaps: [{ partType: rainDefensePartType }] + } + } + }; + const testingPinia = createTestingPinia({ + initialState: { + main: storeData + } + }); + useMainStore(testingPinia); + mountOptions.global.plugins = [testingPinia]; + + const wrapper = shallowMount(confirmationCart, mountOptions); + + // Act + const result = wrapper.vm.servicePackageCartItems; + + // Assert + expect(result.length).toBe(1); + expect(result[0].name).toBe(expectedName); + }); + test('returns expected when all included in types', () => { + // Arrange + const frontWiperPartType = partTypeStrings.FRONT_WIPER; + const rearWiperPartType = partTypeStrings.REAR_WIPER; + const rainDefensePartType = partTypeStrings.RAIN_DEFENSE; + const packageContents = [frontWiperPartType, rearWiperPartType, rainDefensePartType]; + getPackageContents.mockImplementationOnce(() => packageContents); + + const mountOptions = getMountOptions({ + router: { navigate: jest.fn() } + }); + const expectedFrontWiperName = 'front wiper'; + const expectedRearWiperName = 'rear wiper'; + const expectedRainDefenseName = 'rain repel'; + const vapsItemDescriptions = [ + { + Name: frontWiperPartType, + Text: expectedFrontWiperName + }, + { + Name: rearWiperPartType, + Text: expectedRearWiperName + }, + { + Name: rainDefensePartType, + Text: expectedRainDefenseName + } + ]; + const mockMixin = { + methods: { + getCmsContent: jest.fn(() => vapsItemDescriptions) + } + }; + mountOptions.mixins = [mockMixin]; + const storeData = { + order: { + lineItems: { + vaps: [ + { partType: frontWiperPartType }, + { partType: rearWiperPartType }, + { partType: rainDefensePartType } + ] + } + } + }; + const testingPinia = createTestingPinia({ + initialState: { + main: storeData + } + }); + useMainStore(testingPinia); + mountOptions.global.plugins = [testingPinia]; + + const wrapper = shallowMount(confirmationCart, mountOptions); + + // Act + const result = wrapper.vm.servicePackageCartItems; + + // Assert + expect(result.length).toBe(3); + expect(result).toContainEqual(expect.objectContaining({ + name: expectedFrontWiperName + })); + expect(result).toContainEqual(expect.objectContaining({ + name: expectedRearWiperName + })); + expect(result).toContainEqual(expect.objectContaining({ + name: expectedRainDefenseName + })); + }); + }); + describe('nonServicePackageCartItems', () => { + test('when recycleFee, includes recycle fee', () => { + // Arrange + const storeData = { + order: { + lineItems: { + feeItems: [ + { + partNumber: partNumberStrings.RECYCLE_FEE + } + ] + } + } + }; + const { wrapper } = getMountedComponent(storeData); + + // Act + const result = wrapper.vm.nonServicePackageCartItems; + + // Assert + expect(result).toContainEqual(expect.objectContaining({ + partType: partTypeStrings.REPLACE_FEE + })); + }); + test('when mobileFee, includes mobile fee', () => { + // Arrange + const storeData = { + order: { + lineItems: { + feeItems: [{ partType: partTypeStrings.MOBILE_FEE }] + } + } + }; + const { wrapper } = getMountedComponent(storeData); + + // Act + const result = wrapper.vm.nonServicePackageCartItems; + + // Assert + expect(result).toContainEqual(expect.objectContaining({ + partType: partTypeStrings.MOBILE_FEE + })); + }); + test('front wiper not included when front wiper in service package', () => { + // Arrange + getPriceOfLineItems.mockImplementation(() => 123); + getPackageContents.mockImplementationOnce(() => [partTypeStrings.FRONT_WIPER]); + const storeData = { + order: { + lineItems: { + vaps: [ + { + partType: partTypeStrings.FRONT_WIPER + } + ] + } + } + }; + const { wrapper } = getMountedComponent(storeData); + + // Act + const result = wrapper.vm.nonServicePackageCartItems; + + // Assert + expect(result).not.toContainEqual(expect.objectContaining({ + partType: partTypeStrings.FRONT_WIPER + })); + }); + test('rear wiper not included when rear wiper in service package', () => { + // Arrange + getPriceOfLineItems.mockImplementation(() => 123); + getPackageContents.mockImplementationOnce(() => [partTypeStrings.REAR_WIPER]); + const storeData = { + order: { + lineItems: { + vaps: [ + { + partType: partTypeStrings.REAR_WIPER + } + ] + } + } + }; + const { wrapper } = getMountedComponent(storeData); + + // Act + const result = wrapper.vm.nonServicePackageCartItems; + + // Assert + expect(result).not.toContainEqual(expect.objectContaining({ + partType: partTypeStrings.REAR_WIPER + })); + }); + test('rain defense not included when rain defense in service package', () => { + // Arrange + getPriceOfLineItems.mockImplementation(() => 123); + getPackageContents.mockImplementationOnce(() => [partTypeStrings.RAIN_DEFENSE]); + const storeData = { + order: { + lineItems: { + vaps: [ + { + partType: partTypeStrings.RAIN_DEFENSE + } + ] + } + } + }; + const { wrapper } = getMountedComponent(storeData); + + // Act + const result = wrapper.vm.nonServicePackageCartItems; + + // Assert + expect(result).not.toContainEqual(expect.objectContaining({ + partType: partTypeStrings.RAIN_DEFENSE + })); + }); + test('front wiper included when not in service package and in vaps', () => { + // Arrange + getPriceOfLineItems.mockImplementation(() => 123); + getPackageContents.mockImplementationOnce(() => []); + const storeData = { + order: { + lineItems: { + vaps: [ + { + partType: partTypeStrings.FRONT_WIPER + } + ] + } + } + }; + const { wrapper } = getMountedComponent(storeData); + + // Act + const result = wrapper.vm.nonServicePackageCartItems; + + // Assert + expect(result).toContainEqual(expect.objectContaining({ + partType: partTypeStrings.FRONT_WIPER + })); + }); + test('rear wiper included when not in service package and in vaps', () => { + // Arrange + getPriceOfLineItems.mockImplementation(() => 123); + getPackageContents.mockImplementationOnce(() => []); + const storeData = { + order: { + lineItems: { + vaps: [ + { + partType: partTypeStrings.REAR_WIPER + } + ] + } + } + }; + const { wrapper } = getMountedComponent(storeData); + + // Act + const result = wrapper.vm.nonServicePackageCartItems; + + // Assert + expect(result).toContainEqual(expect.objectContaining({ + partType: partTypeStrings.REAR_WIPER + })); + }); + test('rain defense included when not in service package and in vaps', () => { + // Arrange + getPriceOfLineItems.mockImplementation(() => 123); + getPackageContents.mockImplementationOnce(() => []); + const storeData = { + order: { + lineItems: { + vaps: [ + { + partType: partTypeStrings.RAIN_DEFENSE + } + ] + } + } + }; + const { wrapper } = getMountedComponent(storeData); + + // Act + const result = wrapper.vm.nonServicePackageCartItems; + + // Assert + expect(result).toContainEqual(expect.objectContaining({ + partType: partTypeStrings.RAIN_DEFENSE + })); + }); + test('item in vaps outside front wiper, rear wiper, and rain defense never included', () => { + // Arrange + getPriceOfLineItems.mockImplementation(() => 123); + getPackageContents.mockImplementationOnce(() => []); + const storeData = { + order: { + lineItems: { + vaps: [ + { + partType: partTypeStrings.RECALIBRATION + } + ] + } + } + }; + const { wrapper } = getMountedComponent(storeData); + + // Act + const result = wrapper.vm.nonServicePackageCartItems; + + // Assert + expect(result).not.toContainEqual(expect.objectContaining({ + partType: partTypeStrings.RECALIBRATION + })); + }); + test('returns expected when multiple variables apply', () => { + // Arrange + getPriceOfLineItems.mockImplementation(() => 123); + getPackageContents.mockImplementationOnce(() => [partTypeStrings.FRONT_WIPER]); + const storeData = { + order: { + lineItems: { + feeItems: [ + { partType: partTypeStrings.MOBILE_FEE }, + { partNumber: partNumberStrings.RECYCLE_FEE } + ], + vaps: [ + { partType: partTypeStrings.RECALIBRATION }, + { partType: partTypeStrings.FRONT_WIPER }, + { partType: partTypeStrings.REAR_WIPER } + ] + } + } + }; + const { wrapper } = getMountedComponent(storeData); + + // Act + const result = wrapper.vm.nonServicePackageCartItems; + + // Assert + expect(result.length).toBe(3); + expect(result).toContainEqual(expect.objectContaining({ + partType: partTypeStrings.REPLACE_FEE + })); + expect(result).toContainEqual(expect.objectContaining({ + partType: partTypeStrings.MOBILE_FEE + })); + expect(result).toContainEqual(expect.objectContaining({ + partType: partTypeStrings.REAR_WIPER + })); + }); + }); + describe('servicePackageLabelWidget', () => { + test.each([ + [null], + [undefined] + ])('returns "" when servicePackageNames %p', (servicePackageNames) => { + // Arrange + const mountOptions = getMountOptions({ + router: { navigate: jest.fn() } + }); + const mockMixin = { + methods: { + getCmsContent: jest.fn(() => servicePackageNames) + } + }; + mountOptions.mixins = [mockMixin]; + + const wrapper = shallowMount(confirmationCart, mountOptions); + + // Act + const result = wrapper.vm.servicePackageLabelWidget; + + // Assert + expect(result).toBe(''); + }); + test('returns "" when servicePackageTier not found in servicePackageNames', () => { + // Arrange + const mountOptions = getMountOptions({ + router: { navigate: jest.fn() } + }); + getHighestFullySatisfiedTier.mockImplementationOnce(() => 'some other tier'); + const servicePackageNames = [{ Name: 'some tier' }]; + const mockMixin = { + methods: { + getCmsContent: jest.fn(() => servicePackageNames) + } + }; + mountOptions.mixins = [mockMixin]; + const wrapper = shallowMount(confirmationCart, mountOptions); + + // Act + const result = wrapper.vm.servicePackageLabelWidget; + + // Assert + expect(result).toBe(''); + }); + test('returns "" when servicePackageTier has no SubWidgetName', () => { + // Arrange + const mountOptions = getMountOptions({ + router: { navigate: jest.fn() } + }); + const tier = 'tier1'; + getHighestFullySatisfiedTier.mockImplementationOnce(() => tier); + const servicePackageNames = [{ Name: tier }]; + const mockMixin = { + methods: { + getCmsContent: jest.fn(() => servicePackageNames) + } + }; + mountOptions.mixins = [mockMixin]; + const wrapper = shallowMount(confirmationCart, mountOptions); + + // Act + const result = wrapper.vm.servicePackageLabelWidget; + + // Assert + expect(result).toBe(''); + }); + test('returns expected when servicePackageTier has SubWidgetName', () => { + // Arrange + const mountOptions = getMountOptions({ + router: { navigate: jest.fn() } + }); + const tier = 'tier1'; + getHighestFullySatisfiedTier.mockImplementationOnce(() => tier); + const expected = 'name of sub widget'; + const servicePackageNames = [{ + Name: tier, + SubWidgetName: expected + }]; + const mockMixin = { + methods: { + getCmsContent: jest.fn(() => servicePackageNames) + } + }; + mountOptions.mixins = [mockMixin]; + const wrapper = shallowMount(confirmationCart, mountOptions); + + // Act + const result = wrapper.vm.servicePackageLabelWidget; + + // Assert + expect(result).toBe(expected); + }); + }); + describe('recycleFeeCartItem', () => { + test.each([ + [null], [undefined], [[]] + ])('returns null when supporting items %p', (supportingItems) => { + // Arrange + const storeData = { + order: { + lineItems: { supportingItems } + } + }; + const { wrapper } = getMountedComponent(storeData); + const expected = null; + + // Act + const result = wrapper.vm.recycleFeeCartItem; + + // Assert + expect(result).toStrictEqual(expected); + }); + test('returns null when recycle fee not in non empty supporting items', () => { + // Arrange + const storeData = { + order: { + lineItems: { + supportingItems: [{ partNumber: 'not recycle' }] + } + } + }; + const { wrapper } = getMountedComponent(storeData); + const expected = null; + + // Act + const result = wrapper.vm.recycleFeeCartItem; + + // Assert + expect(result).toStrictEqual(expected); + }); + test('returns expected when recycle fee in fee items', () => { + // Arrange + const mountOptions = getMountOptions({ + router: { navigate: jest.fn() } + }); + const expectedName = 'recycling fee'; + const widgetName = 'RecycleFeeWidget'; + const mockMixin = { + methods: { + getCmsContent: jest.fn((widget, _) => (widget === widgetName ? expectedName : [])) + } + }; + mountOptions.mixins = [mockMixin]; + const storeData = { + order: { + lineItems: { + feeItems: [{ partNumber: partNumberStrings.RECYCLE_FEE }] + } + } + }; + const testingPinia = createTestingPinia({ + initialState: { + main: storeData + } + }); + useMainStore(testingPinia); + mountOptions.global.plugins = [testingPinia]; + + const wrapper = shallowMount(confirmationCart, mountOptions); + + // Act + const result = wrapper.vm.recycleFeeCartItem; + + // Assert + expect(result.name).toBe(expectedName); + }); + }); + describe('mobileFeeCartItem', () => { + test.each([[null], [undefined]])('returns null when mobile fee %p', (mobileFee) => { + // Arrange + const storeData = { + order: { + lineItems: { mobileFee } + } + }; + const { wrapper } = getMountedComponent(storeData); + const expected = null; + + // Act + const result = wrapper.vm.mobileFeeCartItem; + + // Assert + expect(result).toStrictEqual(expected); + }); + test('returns expected when mobile fee in fee items', () => { + // Arrange + const mountOptions = getMountOptions({ + router: { navigate: jest.fn() } + }); + const expectedName = 'mobile fee'; + const widgetName = 'MobileServiceWidget'; + const mockMixin = { + methods: { + getCmsContent: jest.fn((widget, _) => (widget === widgetName ? expectedName : [])) + } + }; + mountOptions.mixins = [mockMixin]; + const storeData = { + order: { + lineItems: { + feeItems: [{ partType: partTypeStrings.MOBILE_FEE }] + } + } + }; + const testingPinia = createTestingPinia({ + initialState: { + main: storeData + } + }); + useMainStore(testingPinia); + mountOptions.global.plugins = [testingPinia]; + + const wrapper = shallowMount(confirmationCart, mountOptions); + + // Act + const result = wrapper.vm.mobileFeeCartItem; + + // Assert + expect(result.name).toBe(expectedName); + }); + }); + describe('packagePrice', () => { + test('returns 0 when servicePackageCartItems is empty', () => { + // Arrange + getPriceOfLineItems.mockImplementation(() => 123); + getPackageContents.mockImplementationOnce(() => []); + const storeData = { + order: { + lineItems: { + vaps: [ + { + partType: partTypeStrings.REAR_WIPER + } + ] + } + } + }; + const { wrapper } = getMountedComponent(storeData); + + // Act + const result = wrapper.vm.packagePrice; + + // Assert + expect(result).toBe(0); + }); + test('returns expected when servicePackageCartItems not empty', () => { + // Arrange + const rearPrice = 101; + const frontPrice = 9; + getPriceOfLineItems.mockImplementation((lineItems) => { + if (lineItems.some((item) => item?.partType === partTypeStrings.FRONT_WIPER)) { + return frontPrice; + } + if (lineItems.some((item) => item?.partType === partTypeStrings.REAR_WIPER)) { + return rearPrice; + } + return 1; + }); + getPackageContents.mockImplementationOnce(() => [partTypeStrings.FRONT_WIPER, partTypeStrings.REAR_WIPER]); + const storeData = { + order: { + lineItems: { + vaps: [ + { partType: partTypeStrings.FRONT_WIPER }, + { partType: partTypeStrings.REAR_WIPER } + ] + } + } + }; + const { wrapper } = getMountedComponent(storeData); + const expectedPrice = 110; + + // Act + const result = wrapper.vm.packagePrice; + + // Assert + expect(result).toBe(expectedPrice); + }); + test('returns expected when servicePackageCartItems has one item', () => { + // Arrange + const frontPrice = 9; + getPriceOfLineItems.mockImplementation((lineItems) => { + if (lineItems.some((item) => item?.partType === partTypeStrings.FRONT_WIPER)) { + return frontPrice; + } + return 1; + }); + getPackageContents.mockImplementationOnce(() => [partTypeStrings.FRONT_WIPER, partTypeStrings.REAR_WIPER]); + const storeData = { + order: { + lineItems: { + vaps: [ + { partType: partTypeStrings.FRONT_WIPER } + ] + } + } + }; + const { wrapper } = getMountedComponent(storeData); + + // Act + const result = wrapper.vm.packagePrice; + + // Assert + expect(result).toBe(frontPrice); + }); + }); + }); + describe('method', () => { + const dollarAmount = '$84.00'; + describe.each([ + [VERIFYING_COVERAGE, coverageStatuses.PENDING, coverageType.NONE], + [VERIFYING_COVERAGE, coverageStatuses.PENDING, coverageType.NO_COMP], + [VERIFYING_COVERAGE, coverageStatuses.PENDING, coverageType.ITAC], + [VERIFYING_COVERAGE, coverageStatuses.PENDING, coverageType.Deductible], + [dollarAmount, coverageStatuses.VERIFIED, coverageType.NO_COMP], + [dollarAmount, coverageStatuses.VERIFIED, coverageType.ITAC], + [dollarAmount, coverageStatuses.VERIFIED, coverageType.Deductible], + [VERIFYING_COVERAGE, coverageStatuses.NO_COVERAGE, coverageType.NONE], + [VERIFYING_COVERAGE, coverageStatuses.NO_COVERAGE, coverageType.NO_COMP], + [VERIFYING_COVERAGE, coverageStatuses.NO_COVERAGE, coverageType.ITAC], + [VERIFYING_COVERAGE, coverageStatuses.NO_COVERAGE, coverageType.Deductible] + ])('getDisplayed', (expected, status, type) => { + test(`returns ${expected} when coverage status ${getEnumName(coverageStatuses, status)} and coverageType is ${getEnumName(coverageType, type)}`, () => { + // Arrange + const storeData = { + order: { + insuranceCoverage: { + coverageStatus: status, + coverageType: type + }, + currentDeductible: 321 + } + }; + + const { wrapper } = getMountedComponent(storeData); + formatAmountInDollars.mockReturnValueOnce(dollarAmount); + + // Act + const result = wrapper.vm.getDisplayed(0); + + // Assert + expect(result).toBe(expected); + }); + }); + describe('getCartItemForVapsPart', () => { + test('returns item with no label when no descriptions returned', () => { + // Arrange + const mountOptions = getMountOptions({ + router: { navigate: jest.fn() } + }); + const descriptions = []; + const widgetName = 'VapsItemDescriptions'; + const mockMixin = { + methods: { + getCmsContent: jest.fn((widget, _) => (widget === widgetName ? descriptions : [])) + } + }; + mountOptions.mixins = [mockMixin]; + const part = 'some part'; + const storeData = { + order: { + lineItems: { + vaps: [{ partType: part }] + } + } + }; + const testingPinia = createTestingPinia({ + initialState: { + main: storeData + } + }); + useMainStore(testingPinia); + mountOptions.global.plugins = [testingPinia]; + const wrapper = shallowMount(confirmationCart, mountOptions); + + const expectedName = ''; + + // Act + const result = wrapper.vm.getCartItemForVapsPart(part); + + // Assert + expect(result.name).toBe(expectedName); + }); + test('returns item with no label when type not in descriptions', () => { + // Arrange + const mountOptions = getMountOptions({ + router: { navigate: jest.fn() } + }); + const descriptions = [{ + Name: 'not part name' + }]; + const widgetName = 'VapsItemDescriptions'; + const mockMixin = { + methods: { + getCmsContent: jest.fn((widget, _) => (widget === widgetName ? descriptions : [])) + } + }; + mountOptions.mixins = [mockMixin]; + const part = 'some part'; + const storeData = { + order: { + lineItems: { + vaps: [{ partType: part }] + } + } + }; + const testingPinia = createTestingPinia({ + initialState: { + main: storeData + } + }); + useMainStore(testingPinia); + mountOptions.global.plugins = [testingPinia]; + const wrapper = shallowMount(confirmationCart, mountOptions); + + const expectedName = ''; + + // Act + const result = wrapper.vm.getCartItemForVapsPart(part); + + // Assert + expect(result.name).toBe(expectedName); + }); + test('returns item with no label when description text not defined', () => { + // Arrange + const mountOptions = getMountOptions({ + router: { navigate: jest.fn() } + }); + const part = 'some part'; + const descriptions = [{ + Name: part + }]; + const widgetName = 'VapsItemDescriptions'; + const mockMixin = { + methods: { + getCmsContent: jest.fn((widget, _) => (widget === widgetName ? descriptions : [])) + } + }; + mountOptions.mixins = [mockMixin]; + const storeData = { + order: { + lineItems: { + vaps: [{ partType: part }] + } + } + }; + const testingPinia = createTestingPinia({ + initialState: { + main: storeData + } + }); + useMainStore(testingPinia); + mountOptions.global.plugins = [testingPinia]; + const wrapper = shallowMount(confirmationCart, mountOptions); + const expectedName = ''; + + // Act + const result = wrapper.vm.getCartItemForVapsPart(part); + + // Assert + expect(result.name).toBe(expectedName); + }); + test('returns expected label when vaps description text set', () => { + // Arrange + const mountOptions = getMountOptions({ + router: { navigate: jest.fn() } + }); + const part = 'some part'; + const expectedName = 'name to show up'; + const descriptions = [{ + Name: part, + Text: expectedName + }]; + const widgetName = 'VapsItemDescriptions'; + const mockMixin = { + methods: { + getCmsContent: jest.fn((widget, _) => (widget === widgetName ? descriptions : [])) + } + }; + mountOptions.mixins = [mockMixin]; + const storeData = { + order: { + lineItems: { + vaps: [{ partType: part }] + } + } + }; + const testingPinia = createTestingPinia({ + initialState: { + main: storeData + } + }); + useMainStore(testingPinia); + mountOptions.global.plugins = [testingPinia]; + const wrapper = shallowMount(confirmationCart, mountOptions); + + // Act + const result = wrapper.vm.getCartItemForVapsPart(part); + + // Assert + expect(result.name).toBe(expectedName); + }); + test.each([ + [undefined], [null] + ])('returns null when vapsInOrder %p', async (vapsInOrder) => { + // Arrange + const mountOptions = getMountOptions({ + router: { navigate: jest.fn() } + }); + const part = 'some part'; + const expectedName = 'name to show up'; + const descriptions = [{ + Name: part, + Text: expectedName + }]; + const widgetName = 'VapsItemDescriptions'; + const mockMixin = { + methods: { + getCmsContent: jest.fn((widget, _) => (widget === widgetName ? descriptions : [])) + } + }; + mountOptions.mixins = [mockMixin]; + const storeData = { + order: { + lineItems: { + vaps: vapsInOrder + } + } + }; + const testingPinia = createTestingPinia({ + initialState: { + main: storeData + } + }); + useMainStore(testingPinia); + mountOptions.global.plugins = [testingPinia]; + const wrapper = shallowMount(confirmationCart, mountOptions); + + // Act + const result = wrapper.vm.getCartItemForVapsPart(part); + + // Assert + expect(result).toBe(null); + }); + test('returns item with expected price when part type found in vapsInOrder', () => { + // Arrange + const mountOptions = getMountOptions({ + router: { navigate: jest.fn() } + }); + const part = 'some part'; + const vaps = [{ partType: part }]; + const storeData = { + order: { + lineItems: { vaps } + } + }; + const testingPinia = createTestingPinia({ + initialState: { + main: storeData + } + }); + useMainStore(testingPinia); + mountOptions.global.plugins = [testingPinia]; + const expectedSubtotal = 93; + getPriceOfLineItems.mockImplementation(() => expectedSubtotal); + const wrapper = shallowMount(confirmationCart, mountOptions); + + // Act + const result = wrapper.vm.getCartItemForVapsPart(part); + + // Assert + expect(result.subTotal).toBe(expectedSubtotal); + }); + }); + }); +}); diff --git a/src/iss-components/confirmation-cart/confirmation-cart.vue b/src/iss-components/confirmation-cart/confirmation-cart.vue new file mode 100644 index 00000000..f459cd2a --- /dev/null +++ b/src/iss-components/confirmation-cart/confirmation-cart.vue @@ -0,0 +1,478 @@ + + + + + diff --git a/src/layouts/order-confirmation/order-confirmation.vue b/src/layouts/order-confirmation/order-confirmation.vue index c3514995..fcd9dea5 100644 --- a/src/layouts/order-confirmation/order-confirmation.vue +++ b/src/layouts/order-confirmation/order-confirmation.vue @@ -8,21 +8,34 @@
-
-
-
+
+
+
-
-
-

{{ formatDate(schedule.date) }}

-

- {{ appointmentTimeText }} -

-
+
+ + +
+
+ {{ serviceDescriptionText }} +
+
+ {{ workOrderTitle }} + {{ workOrderNumber }} +
+
+ {{ appointmentWordingText }} + {{ formatDate(schedule.date) }} + {{ appointmentTimeText }} + {{ appointmentLocation }} -
-
-
-
- +
+ {{ wipersTitle }} + {{ description }} +
+
+ {{ rainRepelTitle }} + {{ rainRepelBody }}
-
+
+ {{ contactDetailsTitle }} + {{ contactDetailsBody }} +
+
+
+ {{ cartHeaderText }} +
+ +
@@ -81,7 +108,7 @@ import siteHeader from '@/iss-components/site-header/site-header.vue'; import siteFooter from '@/iss-components/site-footer/site-footer.vue'; import addToCalendar from '@/layouts/order-confirmation/add-to-calendar/add-to-calendar.vue'; -import cartDropdown from '@/iss-components/cart-dropdown/cart-dropdown.vue'; +import confirmationCart from '@/iss-components/confirmation-cart/confirmation-cart.vue'; // Supporting files import { @@ -103,6 +130,9 @@ import { toTitleCase } from '@/helpers/text-helper.js'; import { AppointmentTypeStrings } from '@/constants/schedule-constants'; import applicationConfig from '@/constants/application-config'; import widgetFields from '@/constants/cms-widget-fields.js'; +import { getGlassList } from '@/helpers/damage-helper'; +import partTypeStrings from '@/constants/part-type-strings'; +import coverageType from '@/constants/coverage-type'; export default { name: 'order-confirmation', @@ -112,18 +142,23 @@ export default { siteHeader, siteFooter, addToCalendar, - cartDropdown + confirmationCart }, mixins: [BaseFormMixin], async beforeRouteEnter(to, from, next) { // Call APIs const cmsContentPromise = fetchCmsContentForPage(to.query.issPage); + const glassTypesPromise = useMainStore().getSV2GlassTypes(useMainStore().getSubmittedOrder().damage.glassToReplace); // Settle promises and get results const promiseResultMap = [ { resultKey: 'cmsContent', promise: cmsContentPromise + }, + { + resultKey: 'glassTypes', + promise: glassTypesPromise } ]; // use resultMap to populate layout content. @@ -131,6 +166,9 @@ export default { next((vm) => { vm.setCmsContent(resultMap.cmsContent); + vm.setData({ + glassTypes: resultMap.glassTypes + }) }); }, setup() { @@ -144,18 +182,21 @@ export default { serviceLocation, schedule, customer, + contactInfo, payment, customerPortalLoginToken, damage, - referralNumber + referralNumber, + hasRecalibrationPart } = this.submittedOrder; - const { issConfig, hasRecalibrationPart } = this.mainStore; + const { issConfig } = this.mainStore; return { vehicle, customerEmail: customer?.emailAddress, payment, schedule, serviceLocation, + contactInfo, appointmentType: serviceLocation.appointmentType, providerAddress: serviceLocation?.provider?.address, customerPortalLoginToken, @@ -164,12 +205,24 @@ export default { isRepair: damage.isRepair, hasRecalibrationPart, referralNumber: referralNumber?.toString(), + glassTypes: [], + isNoComp: this.submittedOrder.insuranceCoverage.coverageType === coverageType.NO_COMP, widgets: { siteHeader: 'SiteHeaderWidget', emailConfirmation: 'EmailConfirmationWordingWidget', + emailConfirmationNoComp: 'EmailConfirmationNoCompWordingWidget', orderConfirmation: 'OrderConfirmationContent', + serviceDescription: 'ServiceDescriptionTextWidget', + workOrderNumberTitle: 'WorkOrderNumberTextWidget', mobile: 'MobileWordingWidget', dropOffAndInShop: 'DropOffAndInShopWordingWidget', + wipersText: 'WipersTextWidget', + rainRepel: 'RainRepelWidget', + smsUpdates: 'SmsUpdatesWidget', + contactDetails: 'ContactDetailsWidget', + vapsItemDescriptions: 'VapsItemDescriptions', + payAtAppointment: 'PayAtAppointmentTextWidget', + orderDetails: 'OrderDetailsTextWidget', siteFooter: 'SiteFooterWidget' } }; @@ -182,18 +235,29 @@ export default { vehicleYear: this.vehicle?.year, vehicleMake: this.vehicle?.make, vehicleModel: this.vehicle?.model, + serviceType: this.serviceTypeText, address: this.appointmentLocation, inShopDuration: this.inShopAppointmentDuration, email: this.customerEmail, + phone: this.contactInfo.servicePhone, + workOrderNumber: this.workOrderNumber, CUSTOMER_PORTAL_URL: applicationConfig.CUSTOMER_PORTAL_URL, CUSTOMER_PORTAL_LOGIN_TOKEN: this.customerPortalLoginToken }; }, - confirmationEmailText() { - const content = this.getCmsContentWithCustomValues( - this.widgets.emailConfirmation, - widgetFields.CONTENT_GROUP_WIDGET.BODY_TEXT - ); + orderConfirmationUpdateAppointmentText() { + let content = ''; + if(this.isNoComp) { + content = this.getCmsContentWithCustomValues( + this.widgets.emailConfirmationNoComp, + widgetFields.CONTENT_GROUP_WIDGET.BODY_TEXT + ); + } else { + content = this.getCmsContentWithCustomValues( + this.widgets.emailConfirmation, + widgetFields.CONTENT_GROUP_WIDGET.BODY_TEXT + ); + } return content ?.replaceAll('<', '<') ?.replaceAll('>', '>'); @@ -210,10 +274,50 @@ export default { widgetFields.CONTENT_GROUP_WIDGET.IMAGE ); }, + orderConfirmationSubheaderText() { + return this.getCmsContent( + this.widgets.orderConfirmation, + widgetFields.CONTENT_GROUP_WIDGET.SUBHEADER_TEXT + ); + }, + serviceDescriptionText() { + const content = this.getCmsContentWithCustomValues( + this.widgets.serviceDescription, + widgetFields.TEXT_BLOCK_WIDGET.TEXT + ); + return content; + }, + serviceTypeText() { + let glassList = getGlassList(this.glassTypes); + let workType = ''; + if (this.isRepair) { + workType = 'repair'; + } else { + workType = 'replacement'; + if (this.hasRecalibrationPart) { + if (glassList === 'windshield') { + workType = 'replacement and recalibration'; + } + else { + glassList = glassList.replace('windshield', 'windshield replacement, recalibration'); + } + } + } + return `${glassList} ${workType}`; + }, + workOrderTitle() { + return this.getCmsContent( + this.widgets.workOrderNumberTitle, + widgetFields.TEXT_BLOCK_WIDGET.TEXT + ); + }, + workOrderNumber() { + return this.submittedOrder.workOrderNumber; + }, mobileWordingText() { return this.getCmsContentWithCustomValues( this.widgets.mobile, - widgetFields.CONTENT_GROUP_WIDGET.BODY_TEXT + widgetFields.CONTENT_GROUP_WIDGET.HEADER_TEXT ); }, mobileWordingText2() { @@ -225,7 +329,7 @@ export default { nonMobileWordingText() { return this.getCmsContentWithCustomValues( this.widgets.dropOffAndInShop, - widgetFields.CONTENT_GROUP_WIDGET.BODY_TEXT + widgetFields.CONTENT_GROUP_WIDGET.HEADER_TEXT ); }, nonMobileWordingText2() { @@ -241,12 +345,12 @@ export default { } if (this.isInShopAppointment) { const formattedStartTime = get12HourTimeFormat(startTime); - return `at ${formattedStartTime}`; + return `Your appointment is at ${formattedStartTime}`; } if (this.isMobileAppointment) { const mobileStartTime = get12HourTimeMobileFormat(startTime); const mobileEndTime = get12HourTimeMobileFormat(endTime); - return `Between ${mobileStartTime} - ${mobileEndTime}`; + return `Your appointment is at ${mobileStartTime} - ${mobileEndTime}`; } return null; }, @@ -261,12 +365,12 @@ export default { }, serviceLocationFullAddress() { const { address, address2, city, state, zipCode } = this.serviceLocation; - return `${address ?? ''}, ${address2 ? `${address2},` : ''}
${city ?? ''}, ${state ?? ''} ${zipCode ?? ''}`; + return `${address ?? ''}, ${address2 ? `${address2},` : ''} ${city ?? ''}, ${state ?? ''} ${zipCode ?? ''}`; }, providerFullAddress() { const { streetAddress, city, state, zipCode } = this.providerAddress; return streetAddress - ? `${toTitleCase(streetAddress)},
${toTitleCase(city)}, ${state ?? ''} ${zipCode ?? ''}` + ? `${toTitleCase(streetAddress)}, ${toTitleCase(city)}, ${state ?? ''} ${zipCode ?? ''}` : ''; }, appointmentWordingText() { @@ -317,7 +421,85 @@ export default { // settleTenderAmount always shows 0 via localhost or dev. // Temporarily set return true to see cart in localhost or dev environment - return false; + return true; + }, + displayWipers() { + const hasWiperPart = this.submittedOrder.lineItems.vaps.some((part) => part.partType.toLowerCase().includes('wiper')); + return hasWiperPart; + }, + wipersTitle() { + return this.getCmsContent( + this.widgets.wipersText, + widgetFields.TEXT_BLOCK_WIDGET.TEXT + ); + }, + wipersBody() { + const wiperTypesOnOrder = []; + + const hasFrontWiper = this.submittedOrder.lineItems.vaps.some((part) => part.partType === partTypeStrings.FRONT_WIPER); + const hasRearWiper = this.submittedOrder.lineItems.vaps.some((part) => part.partType === partTypeStrings.REAR_WIPER); + + if (hasFrontWiper) { + wiperTypesOnOrder.push(partTypeStrings.FRONT_WIPER); + } + if (hasRearWiper) { + wiperTypesOnOrder.push(partTypeStrings.REAR_WIPER); + } + + const wiperDescriptions = wiperTypesOnOrder.map((type) => { + return this.getCmsContentForVapsType(type); + }); + + return wiperDescriptions; + }, + displayRainRepel() { + return this.submittedOrder.lineItems.vaps.some((part) => part.partType === partTypeStrings.RAIN_DEFENSE); + }, + rainRepelTitle() { + return this.getCmsContent( + this.widgets.rainRepel, + widgetFields.CONTENT_GROUP_WIDGET.HEADER_TEXT + ); + }, + rainRepelBody() { + return this.getCmsContent( + this.widgets.rainRepel, + widgetFields.CONTENT_GROUP_WIDGET.BODY_TEXT + ); + }, + displaySMSPhone() { + return this.contactInfo.requestTextUpdates && this.contactInfo.servicePhone; + }, + smsTitle() { + return this.getCmsContent( + this.widgets.smsUpdates, + widgetFields.TEXT_BLOCK_WIDGET.TEXT + ); + }, + contactDetailsTitle() { + return this.getCmsContent( + this.widgets.contactDetails, + widgetFields.CONTENT_GROUP_WIDGET.HEADER_TEXT + ); + }, + contactDetailsBody() { + return this.getCmsContentWithCustomValues( + this.widgets.contactDetails, + widgetFields.CONTENT_GROUP_WIDGET.BODY_TEXT + ); + }, + cartHeaderText() { + if (this.payment.isPayInAdvance) { + return this.getCmsContent( + this.widgets.orderDetails, + widgetFields.TEXT_BLOCK_WIDGET.TEXT + ); + } else { + return this.getCmsContent( + this.widgets.payAtAppointment, + widgetFields.TEXT_BLOCK_WIDGET.TEXT + ); + } } }, mounted() { @@ -411,68 +593,135 @@ export default { return dateObject.toLocaleDateString('en-us', { weekday: 'long', month: 'long', - day: 'numeric' + day: 'numeric', + year: 'numeric' }); + }, + setData({ glassTypes }) { + this.glassTypes = glassTypes; + }, + getCmsContentForVapsType(vapsPartType) { + const vapsItemDescriptions = this.getCmsContent( + this.widgets.vapsItemDescriptions, + widgetFields.INPUT_QUESTION_WIDGET.ANSWERS + ); + + if (!vapsItemDescriptions) { + return ''; + } + + const vapsTypeDescription = vapsItemDescriptions?.find((entry) => entry?.Name === vapsPartType); + return vapsTypeDescription?.Text ?? ''; } } }; diff --git a/src/layouts/payment-method/payment-method.vue b/src/layouts/payment-method/payment-method.vue index 195909ec..2ec3d29e 100644 --- a/src/layouts/payment-method/payment-method.vue +++ b/src/layouts/payment-method/payment-method.vue @@ -24,8 +24,7 @@
diff --git a/src/store/index.js b/src/store/index.js index d74067f7..05961894 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -2874,6 +2874,19 @@ export const useMainStore = defineStore({ }); }, + async getSV2GlassTypes(glassPieces) { + const glassArray = convertGlassPieceNamingForApi(glassPieces); + const response = await globalMethods.callHttpClient({ + method: endpoints.GetSV2GlassTypes.method, + endpoint: endpoints.GetSV2GlassTypes.url, + payload: { + glassPieces: glassArray, + formatWithSpaces: true + } + }); + return response.data; + }, + hasSubmittedOrder() { return window.sessionStorage.getItem(webStorageConstants.SUBMITTED_ORDER) !== null; }, @@ -2889,12 +2902,13 @@ export const useMainStore = defineStore({ await this.getCarrierAccountInfo(); const submittedOrder = this.order; const { experiments } = this.applicationUser; - const { issConfig } = this; + const { issConfig, hasRecalibrationPart } = this; submittedOrder.isUnverified = this.isUnverified; submittedOrder.isVerified = this.isVerified; submittedOrder.submitType = submitType; submittedOrder.payment.isPayInAdvance = this.isPayInAdvance; + submittedOrder.hasRecalibrationPart = hasRecalibrationPart; // set to local storage window.sessionStorage.setItem(webStorageConstants.SUBMITTED_ORDER, JSON.stringify(submittedOrder));