diff --git a/.eslintrc.js b/.eslintrc.js index 3ced6a6d..65c1caa3 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -56,4 +56,4 @@ module.exports = { } } } -}; +}; \ No newline at end of file diff --git a/azure-pipelines.yml b/azure-pipelines.yml index bcb4ec5f..0ad8e535 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -18,7 +18,7 @@ pr: resources: containers: - container: node - image: ghcr.io/safelite/node-build:18 + image: ghcr.io/safelite/node-build:20 endpoint: GitHub Docker - container: awscli image: ghcr.io/safelite/awscli2-build:3.9 diff --git a/package-lock.json b/package-lock.json index a062bda3..a027d0d3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,6 @@ "name": "digitalconsumer.iss", "version": "0.1.0", "dependencies": { - "axios": "^1.12.0", "axios-retry": "^3.5.0", "bootstrap": "^5.3", "iframe-resizer": "^4.3.9", @@ -42,7 +41,7 @@ "@vue/test-utils": "^2.4.1", "@vue/vue3-jest": "^27.0.0-alpha.1", "axe-core": "^4.10.2", - "axios": "^1.12.0", + "axios": "^1.13.5", "axios-mock-adapter": "^1.21.5", "babel-jest": "^27.0.6", "concurrently": "^9.1.2", @@ -6684,14 +6683,14 @@ } }, "node_modules/axios": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.0.tgz", - "integrity": "sha512-oXTDccv8PcfjZmPGlWsPSwtOJCZ/b6W5jAMCNcfwJbCzDckwG0jrYJFaWH1yvivfCXjVzV/SPDEhMB3Q+DSurg==", + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz", + "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==", "dev": true, "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", "proxy-from-env": "^1.1.0" } }, @@ -10591,9 +10590,9 @@ "license": "ISC" }, "node_modules/follow-redirects": { - "version": "1.15.6", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", - "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", "dev": true, "funding": [ { @@ -10601,6 +10600,7 @@ "url": "https://github.com/sponsors/RubenVerborgh" } ], + "license": "MIT", "engines": { "node": ">=4.0" }, @@ -10707,9 +10707,9 @@ } }, "node_modules/form-data": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", - "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 56687b08..e29b27cb 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "test:playwright": "playwright test --config=playwright-tests/playwright.config.ts" }, "dependencies": { - "axios": "^1.12.0", + "axios": "^1.13.5", "axios-retry": "^3.5.0", "bootstrap": "^5.3", "iframe-resizer": "^4.3.9", @@ -51,7 +51,7 @@ "@vue/test-utils": "^2.4.1", "@vue/vue3-jest": "^27.0.0-alpha.1", "axe-core": "^4.10.2", - "axios": "^1.12.0", + "axios": "^1.13.5", "axios-mock-adapter": "^1.21.5", "babel-jest": "^27.0.6", "concurrently": "^9.1.2", diff --git a/src/constants/cart-item-type.js b/src/constants/cart-item-type.js index b3e1ecf4..017e2951 100644 --- a/src/constants/cart-item-type.js +++ b/src/constants/cart-item-type.js @@ -1,7 +1,8 @@ const cartItemType = Object.freeze({ RECYCLE_FEE: 'RECYCLE FEE', MOBILE_FEE: 'MOBILE FEE', - VAP: 'VAP' + VAP: 'VAP', + WARRANTY: 'WARRANTY' }); export default cartItemType; diff --git a/src/constants/endpoints.js b/src/constants/endpoints.js index c55410da..45bceb0a 100644 --- a/src/constants/endpoints.js +++ b/src/constants/endpoints.js @@ -81,7 +81,16 @@ const endpoints = Object.freeze({ method: 'POST' }, GetProviders: { - url: `${LOCATION_BASE_URL}/providers`, + url: ( + zipCode, + damageType, + radius, + parentAccountNumber, + safeliteOnly, + carId, + recalPartNumber, + isBigTruck + ) => `${LOCATION_BASE_URL}/providers/${zipCode}/${damageType}/${radius}/${parentAccountNumber}/${safeliteOnly}/${carId}/${recalPartNumber != null ? `${recalPartNumber}/` : ''}${isBigTruck}`, method: 'GET' }, GetCapabilityQuestions: { diff --git a/src/constants/part-type-strings.js b/src/constants/part-type-strings.js index 4876b999..babee9a8 100644 --- a/src/constants/part-type-strings.js +++ b/src/constants/part-type-strings.js @@ -7,7 +7,9 @@ RECALIBRATION: 'RECALIBRATION', REPLACE_FEE: 'REPLACE FEE', MOBILE_FEE: 'MOBILE FEE', - REPAIR_FEE: 'REPAIR FEE' + REPAIR_FEE: 'REPAIR FEE', + WARRANTY: 'WARRANTY', + MOLDING: 'MOULDING' }); export default partTypeStrings; diff --git a/src/digital-components/button-question/button-question.vue b/src/digital-components/button-question/button-question.vue index 0812922d..7ab57e82 100644 --- a/src/digital-components/button-question/button-question.vue +++ b/src/digital-components/button-question/button-question.vue @@ -17,7 +17,7 @@ class="w-100" :class="questionTextClasses">{{ questionText }} -
+
{ expect(wrapper.vm.$data).toMatchSnapshot(); }); describe('displays', () => { - test('cart dropdown head', () => { - // Arrange - const reference = '#cart-dropdown-head'; - const { wrapper } = getMountedComponent(cartDropdown); - - // 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(cartDropdown, {}, { 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(cartDropdown, {}, { 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); + const reference = '.cart-table'; + const { wrapper } = getMountedComponent({}, {}, {}); // Act const cartTable = wrapper.find(reference); @@ -121,11 +84,9 @@ describe('cart-dropdown component', () => { // Assert expect(cartTable.exists()).toBeTruthy(); }); - test('cart deductible when showDeductibleLineItem is true', () => { + test('cart deductible when showDeductibleCartItem is true', () => { // Arrange - const reference = '#cart-deductible'; - const isExpanded = true; - const initialData = { isExpanded }; + const reference = '#deductible-label'; const storeData = { order: { insuranceCoverage: { @@ -134,7 +95,7 @@ describe('cart-dropdown component', () => { } } }; - const { wrapper } = getMountedComponent(storeData, {}, initialData); + const { wrapper } = getMountedComponent(storeData, {}, {}); // Act const cartDeductible = wrapper.find(reference); @@ -144,9 +105,7 @@ describe('cart-dropdown component', () => { }); test('cart base price when showDeductibleLineItem is false', () => { // Arrange - const reference = '#cart-base-price'; - const isExpanded = true; - const initialData = { isExpanded }; + const reference = '#base-price-label'; const storeData = { order: { currentDeductible: 0, @@ -160,7 +119,7 @@ describe('cart-dropdown component', () => { isClaimRegistrationRequired: true } }; - const { wrapper } = getMountedComponent(storeData, {}, initialData); + const { wrapper } = getMountedComponent(storeData, {}, {}); // Act const cartBasePrice = wrapper.find(reference); @@ -168,25 +127,32 @@ describe('cart-dropdown component', () => { // 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); + const reference = '.non-packaged.cart-item'; + const part1 = { partType: 'apple' }; + const part2 = { partType: 'banana' }; + const part3 = { partType: 'orange' }; + const mobileFee = { partType: 'mobile' }; + const storeData = { + order: { + currentDeductible: 0, + lineItems: { + glassParts: [part1], + supportingItems: [part2, part3], + otherParts: [], + feeItems: [mobileFee] + }, + insuranceCoverage: { + coverageStatus: coverageStatuses.VERIFIED, + coverageType: coverageType.ITAC + } + }, + issConfig: { + isClaimRegistrationRequired: true + } + }; + const { wrapper } = getMountedComponent(storeData, {}, {}); // Act const nonPackagedCartItems = wrapper.find(reference); @@ -220,9 +186,7 @@ describe('cart-dropdown component', () => { test('cart footer', () => { // Arrange const reference = '#cart-footer'; - const isExpanded = true; - const initialData = { isExpanded }; - const { wrapper } = getMountedComponent({}, {}, initialData); + const { wrapper } = getMountedComponent({}, {}, {}); // Act const cartFooter = wrapper.find(reference); @@ -233,9 +197,7 @@ describe('cart-dropdown component', () => { test('subtotal', () => { // Arrange const reference = '#cart-subtotal'; - const isExpanded = true; - const initialData = { isExpanded }; - const { wrapper } = getMountedComponent({}, {}, initialData); + const { wrapper } = getMountedComponent({}, {}, {}); // Act const subtotal = wrapper.find(reference); @@ -246,9 +208,7 @@ describe('cart-dropdown component', () => { test('tax', () => { // Arrange const reference = '#cart-sales-tax'; - const isExpanded = true; - const initialData = { isExpanded }; - const { wrapper } = getMountedComponent({}, {}, initialData); + const { wrapper } = getMountedComponent({}, {}, {}); // Act const salesTax = wrapper.find(reference); @@ -280,9 +240,7 @@ describe('cart-dropdown component', () => { test('bottom amount due', () => { // Arrange const reference = '#bottom-amount-due'; - const isExpanded = true; - const initialData = { isExpanded }; - const { wrapper } = getMountedComponent({}, {}, initialData); + const { wrapper } = getMountedComponent({}, {}, {}); // Act const bottomAmountDue = wrapper.find(reference); @@ -295,8 +253,6 @@ describe('cart-dropdown component', () => { test('cart deductible when showDeductibleLineItem is false', () => { // Arrange const reference = '#cart-deductible'; - const isExpanded = true; - const initialData = { isExpanded }; const storeData = { order: { currentDeductible: 0, @@ -310,7 +266,7 @@ describe('cart-dropdown component', () => { isClaimRegistrationRequired: true } }; - const { wrapper } = getMountedComponent(storeData, {}, initialData); + const { wrapper } = getMountedComponent(storeData, {}, {}); // Act const cartDeductible = wrapper.find(reference); @@ -321,8 +277,6 @@ describe('cart-dropdown component', () => { test('cart base price when showDeductibleLineItem is true', () => { // Arrange const reference = '#cart-base-price'; - const isExpanded = true; - const initialData = { isExpanded }; const storeData = { order: { insuranceCoverage: { @@ -331,7 +285,7 @@ describe('cart-dropdown component', () => { } } }; - const { wrapper } = getMountedComponent(storeData, {}, initialData); + const { wrapper } = getMountedComponent(storeData, {}, {}); // Act const cartBasePrice = wrapper.find(reference); @@ -1223,91 +1177,6 @@ describe('cart-dropdown component', () => { 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'; diff --git a/src/iss-components/cart-dropdown/cart-dropdown.vue b/src/iss-components/cart-dropdown/cart-dropdown.vue index 34788563..da404697 100644 --- a/src/iss-components/cart-dropdown/cart-dropdown.vue +++ b/src/iss-components/cart-dropdown/cart-dropdown.vue @@ -1,156 +1,113 @@ @@ -176,28 +133,26 @@ import { isOrderUnverified } from '@/helpers/cart-helper'; import { getPriceOfLineItems, getTaxOfLineItems } from '@/helpers/price-calculator'; +import { processIfStatements } from '@/helpers/cms-content-helper'; const VERIFYING_COVERAGE = 'Verifying coverage'; const RECYCLING_MODAL_REF_NAME = 'RecycleModal'; export default { - name: 'cart-dropdown', + name: 'confirmation-cart', components: { contentGroupModal, textBlock, textLink }, props: { - showAsPaid: Boolean, readOnly: Boolean, + showAsPaid: Boolean, recyclingModalCmsWidgetName: String, - showDropdownHeader: Boolean, - isInitiallyExpanded: Boolean, submittedOrder: Object }, data() { return { - isExpanded: this.isInitiallyExpanded, widget: { amountDue: 'AmountDueTextWidget', amountPaid: 'AmountPaidTextWidget', @@ -208,7 +163,9 @@ export default { recycleFee: 'RecycleFeeWidget', mobileFee: 'MobileServiceWidget', servicePackage: 'ServicePackageTitle', - vapsItemDescriptions: 'VapsItemDescriptions' + vapsItemDescriptions: 'VapsItemDescriptions', + warrantyText: 'WarrantyCartItemTextWidget', + guaranteeText: 'GuaranteeCartItemTextWidget' }, cartItemType, RECYCLING_MODAL_REF_NAME @@ -273,7 +230,7 @@ export default { return result; }, vehicleDamage() { - return this.submittedOrder ? this.submittedOrder.damage : useMainStore().damage; + return this.cartOrder.damage; }, servicePackageTier() { const { glassToReplace, isRepair } = this.vehicleDamage; @@ -333,6 +290,9 @@ export default { if (this.mobileFeeCartItem && !isMobileFeeHidden) { items.push(this.mobileFeeCartItem); } + if (this.isITAC || this.isNoComp) { + items.push(this.warrantyCartItem); + } return items; }, frontWipersCartItem() { @@ -350,12 +310,6 @@ export default { ...this.nonServicePackageCartItems ]; }, - packagePrice() { - return this.servicePackageCartItems?.reduce( - (accumulator, cartItem) => accumulator + (cartItem?.subTotal ?? 0), - 0 - ) ?? 0; - }, amountDueLabel() { return this.getCmsContent(this.widget.amountDue, widgetFields.TEXT_BLOCK_WIDGET.TEXT); }, @@ -366,7 +320,12 @@ export default { return this.getCmsContent(this.widget.deductible, widgetFields.TEXT_BLOCK_WIDGET.TEXT); }, basePriceLabel() { - return this.getCmsContent(this.widget.basePrice, widgetFields.TEXT_BLOCK_WIDGET.TEXT); + const content = this.getCmsContent(this.widget.basePrice, widgetFields.TEXT_BLOCK_WIDGET.TEXT); + return processIfStatements( + content, + 'custom', + this.getCustomValueFromString + ); }, subtotalLabel() { return this.getCmsContent(this.widget.subtotal, widgetFields.TEXT_BLOCK_WIDGET.TEXT); @@ -409,15 +368,29 @@ export default { partTypeStrings.MOBILE_FEE ) : null; + }, + warrantyCartItem() { + const label = this.cartOrder.damage.glassToReplace?.length > 0 + ? this.getCmsContent(this.widget.warrantyText, widgetFields.TEXT_BLOCK_WIDGET.TEXT) + : this.getCmsContent(this.widget.guaranteeText, widgetFields.TEXT_BLOCK_WIDGET.TEXT); + return { + name: label, + cartItemType: cartItemType.WARRANTY, + partType: partTypeStrings.WARRANTY, + subTotal: 0, + salesTax: 0 + } + }, + hasMoldingPart() { + return this.cartOrder.lineItems.glassParts?.some((glassPart) => { + return glassPart.childParts?.some((childPart) => childPart.partType === partTypeStrings.MOLDING); + }); } }, methods: { openModal(modalName) { this.$refs[modalName].openModal(); }, - toggleIsExpanded() { - this.isExpanded = !this.isExpanded; - }, getDisplayed(amount) { return this.isUnverified ? VERIFYING_COVERAGE @@ -461,6 +434,16 @@ export default { const vapsTypeDescription = vapsItemDescriptions?.find((entry) => entry?.Name === vapsPartType); return vapsTypeDescription?.Text ?? ''; }, + getCustomValueFromString(str) { + switch (str) { + case 'isRepair': + return this.cartOrder.damage.isRepair; + case 'hasMoldingPart': + return this.hasMoldingPart; + default: + return null; + } + }, formatAmountInDollars } }; @@ -471,45 +454,38 @@ export default { $RECYCLING_MODAL_REF_NAME: 'RecycleModal'; -.force-no-top-margin { - margin-top: 0 !important; -} - -.color-black { - color: $black; -} - -.color-green { - color: $green; -} - -.color-darker-gray { - color: $darker-gray; -} - .cart-table { - max-height: 0; - transition: all 350ms ease-in; - overflow: hidden; - visibility: hidden; - margin-top: 1rem; - - :deep(a) { - padding-bottom: 0 !important; - line-height: 1.625rem !important; - text-underline-offset: auto !important; - } - - .non-packaged-cart-item:nth-child(odd){ - background-color: $gray-100; - } + max-height: 50rem; + transition: all 150ms ease-in; + min-width: fit-content; .cart-item { - padding: 0.25rem 1.5rem; - } + display: flex; + flex-direction: column; + padding: .5rem 0rem; + width: 100%; + min-width: fit-content; + &:nth-child(odd) { + background-color: #f4f4f4; + } + + &:last-child { + border-bottom: 1px solid #d4d6d8; + } - .cart-gray { - background-color: $gray-100; + .price-row { + display: flex; + justify-content: space-between; + } + + span, a { + white-space: nowrap; + padding: 0rem 1rem; + } + + a:hover { + color: $heritage-blue-secondary; + } } .cart-footer { @@ -520,13 +496,16 @@ $RECYCLING_MODAL_REF_NAME: 'RecycleModal'; background-color: $green-150; font-weight: $font-weight-bold; color: $black; - padding: 0.25rem 1.5rem; + >div { + padding: .5rem 1rem; + } } .bottom-amount-due { color: $white; + font-weight: 600; background-color: $green; - padding: 0.5rem 1.5rem; + padding: .5rem 1rem; } } @@ -544,39 +523,6 @@ $RECYCLING_MODAL_REF_NAME: 'RecycleModal'; text-align: center; } -.cart-header { - &.cart-toggle { - font-weight: $font-weight-bold; - - &:after { - content: ""; - transition: all 0.5s ease; - background-image: url($svg-payment-method-review-toggle); - background-repeat: no-repeat; - background-position: right center; - width: 1rem; - height: 0.5625rem; - display: inline-flex; - position: relative; - right: 0.75rem; - margin: 0.5rem 0 0.5rem 1rem; - cursor: pointer; - } - &.expanded:after { - transform: rotate(180deg); - } - a { - text-decoration: none; - } - } - &.expanded + .cart-table { - max-height: 50rem; - transition: all 150ms ease-in; - overflow: hidden; - visibility: visible; - } -} - :deep(#recycle-fee-label a){ line-height: initial; } diff --git a/src/iss-components/questions-page-layout/questions-page-layout.vue b/src/iss-components/questions-page-layout/questions-page-layout.vue index 97d06a51..43ce4251 100644 --- a/src/iss-components/questions-page-layout/questions-page-layout.vue +++ b/src/iss-components/questions-page-layout/questions-page-layout.vue @@ -19,7 +19,8 @@ class="mt-5" />
+ :key="questionsDatum.key" + class="question-chain-wrapper">
-
+
{{ buttonLabelSubCopy }} -
+
diff --git a/src/iss-components/site-header/site-header.vue b/src/iss-components/site-header/site-header.vue index 22ced000..5534d143 100644 --- a/src/iss-components/site-header/site-header.vue +++ b/src/iss-components/site-header/site-header.vue @@ -14,15 +14,15 @@
-
+ diff --git a/src/layouts/order-confirmation/add-to-calendar/add-to-calendar.spec.js b/src/layouts/order-confirmation/add-to-calendar/add-to-calendar.spec.js index 92914c3e..285cf934 100644 --- a/src/layouts/order-confirmation/add-to-calendar/add-to-calendar.spec.js +++ b/src/layouts/order-confirmation/add-to-calendar/add-to-calendar.spec.js @@ -2,11 +2,10 @@ import calendarOptions from '@/constants/calendar-options'; // Supporting Files -import { shallowMount, mount } from '@vue/test-utils'; +import { shallowMount } from '@vue/test-utils'; import { getMountOptions } from '@/helpers/unit-test-helper.js'; import { useMainStore } from '@/store'; import addToCalendar from '@/layouts/order-confirmation/add-to-calendar/add-to-calendar.vue'; -import { createTestingPinia } from '@pinia/testing'; const appointmentText = 'Appointment content'; function setupMocks({ customMountOptions }) { @@ -21,49 +20,6 @@ function setupMocks({ customMountOptions }) { } ]; const wrapper = shallowMount(addToCalendar, mountOptions); - wrapper.vm.$refs.calendarModalQuestion.openModal = jest.fn(); - return { wrapper }; -} - -const calendarModalQuestionStub = { - render: () => {} -}; - -const mockMixin = { - methods: { - getCmsContent: jest.fn().mockImplementation(() => appointmentText) - } -}; - -function getMountedComponent(mainInitialState = {}, initialData = {}, methodToRun = () => {}, mixin = mockMixin) { - const mountOptions = getMountOptions({ - router: { - navigate: jest.fn(), - navigateToExternalUrl: jest.fn() - } - }); - - mountOptions.global.stubs = { - calendarModalQuestion: calendarModalQuestionStub - }; - - const testingPinia = createTestingPinia({ - initialState: { - main: mainInitialState - } - }); - useMainStore(testingPinia); - methodToRun(); - - mountOptions.global.mixins[0].methods.getSettingValue = jest.fn(() => 'false'); - mountOptions.global.plugins = [testingPinia]; - mountOptions.mixins = [mixin]; - mountOptions.data = () => ( - initialData - ); - - const wrapper = shallowMount(addToCalendar, mountOptions); - wrapper.vm.$refs.calendarModalQuestion.openModal = jest.fn(); return { wrapper }; } @@ -119,51 +75,8 @@ afterEach(() => { jest.clearAllMocks(); }); -const sessionStorage = { - schedule: { - date: '2019-01-01', - startTime: '09:00', - endTime: '10:00', - routeCode: '000' - }, - lineItems: { - glassParts: [ - { - partNumber: 'ABC123' - } - ], - supportingItems: [] - }, - serviceLocation: { - address: '', - address2: null, - city: '', - state: 'AZ', - appointmentType: 'Inshop', - zipCode: '12345', - zipCodeCtu: '01234', - provider: { - providerNumber: '123', - address: { - streetAddress: 'test1', - city: 'test', - state: 'AZ', - zipCode: '12345', - zipCodeCtu: '01234' - } - } - }, - damage: { - isRepair: false - }, - referralNumber: '1234567', - payment: { - isInsurance: true - } -}; - describe('Add-to-calendar methods...', () => { - test('Add-to-calendar should trigger openModal method', () => { + test('Add-to-calendar should trigger open options', async () => { // Arrange const { wrapper } = setupMocks({ customMountOptions: { @@ -183,10 +96,11 @@ describe('Add-to-calendar methods...', () => { }); // Act - wrapper.vm.openCalendarModal(); + wrapper.vm.openCalendarOptions(); + await wrapper.vm.$nextTick(); // Assert - expect(wrapper.vm.$refs.calendarModalQuestion.openModal).toBeCalled(); + expect(wrapper.vm.$refs.calendarOptions.open).toEqual(true); }); test('getCalendarData should return expected model value.', () => { diff --git a/src/layouts/order-confirmation/add-to-calendar/add-to-calendar.vue b/src/layouts/order-confirmation/add-to-calendar/add-to-calendar.vue index 502337a6..284a34e6 100644 --- a/src/layouts/order-confirmation/add-to-calendar/add-to-calendar.vue +++ b/src/layouts/order-confirmation/add-to-calendar/add-to-calendar.vue @@ -1,7 +1,7 @@ - diff --git a/src/layouts/order-confirmation/add-to-calendar/calendar-modal-question/calendar-modal-question.spec.js b/src/layouts/order-confirmation/add-to-calendar/calendar-modal-question/calendar-modal-question.spec.js deleted file mode 100644 index 51b43508..00000000 --- a/src/layouts/order-confirmation/add-to-calendar/calendar-modal-question/calendar-modal-question.spec.js +++ /dev/null @@ -1,41 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import calendarModalQuestion from '@/layouts/order-confirmation/add-to-calendar/calendar-modal-question/calendar-modal-question.vue'; - -describe('calendar-modal-question.vue', () => { - test('When selected calendar option is changed, a correctly selectedCalendarOption should be emitted', async () => { - // Arrange - const url = 'iCal url'; - const name = 'iCal'; - const icon = 'iCal.svg'; - const expectedEmit = [ - [ - { - url, - name, - icon - } - ] - ]; - - const wrapper = shallowMount(calendarModalQuestion, { - propsData: { - calendarOptionsData: [ - { - url, - name, - icon - } - ] - } - }); - - wrapper.vm.$refs.calendarOptions.closeModal = jest.fn(); - wrapper.vm.selectedCalendarOption = name; - - // Act - wrapper.vm.setSelectedCalendarOption(); - - // Assert - expect(wrapper.emitted()['update:modelValue']).toEqual(expectedEmit); - }); -}); diff --git a/src/layouts/order-confirmation/add-to-calendar/calendar-modal-question/calendar-modal-question.vue b/src/layouts/order-confirmation/add-to-calendar/calendar-modal-question/calendar-modal-question.vue deleted file mode 100644 index 702abf1b..00000000 --- a/src/layouts/order-confirmation/add-to-calendar/calendar-modal-question/calendar-modal-question.vue +++ /dev/null @@ -1,180 +0,0 @@ - - - diff --git a/src/layouts/order-confirmation/add-to-calendar/calendar-option-selection/calendar-option-button/calendar-option-button.vue b/src/layouts/order-confirmation/add-to-calendar/calendar-option-selection/calendar-option-button/calendar-option-button.vue new file mode 100644 index 00000000..760e3a7a --- /dev/null +++ b/src/layouts/order-confirmation/add-to-calendar/calendar-option-selection/calendar-option-button/calendar-option-button.vue @@ -0,0 +1,41 @@ + + + diff --git a/src/layouts/order-confirmation/add-to-calendar/calendar-option-selection/calendar-option-selection.spec.js b/src/layouts/order-confirmation/add-to-calendar/calendar-option-selection/calendar-option-selection.spec.js new file mode 100644 index 00000000..a833b369 --- /dev/null +++ b/src/layouts/order-confirmation/add-to-calendar/calendar-option-selection/calendar-option-selection.spec.js @@ -0,0 +1,42 @@ +import { shallowMount } from '@vue/test-utils'; +import calendarOptionSelection from '@/layouts/order-confirmation/add-to-calendar/calendar-option-selection/calendar-option-selection.vue'; + +describe('calendar-option-selection.vue', () => { + test('When selected calendar option is changed, a correctly selectedCalendarOption should be emitted', async () => { + // Arrange + const url = 'iCal url'; + const name = 'iCal'; + const icon = 'iCal.svg'; + const url2 = 'Google url'; + const name2 = 'Google'; + const icon2 = 'Google.svg'; + const expectedEmit = [ + [ + 1 + ] + ]; + + const wrapper = shallowMount(calendarOptionSelection, { + propsData: { + calendarOptionsData: [ + { + url, + name, + icon + }, + { + url: url2, + name: name2, + icon: icon2 + } + ] + } + }); + + // Act + wrapper.vm.setSelectedCalendarOption(1); + + // Assert + expect(wrapper.emitted()['calendar-option-selected']).toEqual(expectedEmit); + }); +}); diff --git a/src/layouts/order-confirmation/add-to-calendar/calendar-option-selection/calendar-option-selection.vue b/src/layouts/order-confirmation/add-to-calendar/calendar-option-selection/calendar-option-selection.vue new file mode 100644 index 00000000..1c95c555 --- /dev/null +++ b/src/layouts/order-confirmation/add-to-calendar/calendar-option-selection/calendar-option-selection.vue @@ -0,0 +1,53 @@ + + + + diff --git a/src/layouts/order-confirmation/order-confirmation.spec.js b/src/layouts/order-confirmation/order-confirmation.spec.js index 6a06885a..be64fdc8 100644 --- a/src/layouts/order-confirmation/order-confirmation.spec.js +++ b/src/layouts/order-confirmation/order-confirmation.spec.js @@ -206,6 +206,7 @@ describe('OrderConfirmation.vue', () => { useMainStore().getSubmittedOrder = jest.fn().mockImplementation(() => sessionStorage); }; const { wrapper } = getMountedComponent(initialStore, {}, mockStoreActions); + console.log(wrapper.vm.$refs); // Act const arePagePrerequisitesValid = wrapper.vm.arePagePrerequisitesValid(); @@ -363,7 +364,7 @@ describe('OrderConfirmation.vue', () => { expect(actualEmail).toBe(emailAddress); }); }); - describe('confirmationEmailText', () => { + describe('orderConfirmationUpdateAppointmentText', () => { const greaterId = '>'; const lessId = '<'; test.each([ @@ -383,7 +384,7 @@ describe('OrderConfirmation.vue', () => { const { wrapper } = getMountedComponent(initialStore, {}, mockStoreActions); // Act - const text = wrapper.vm.confirmationEmailText; + const text = wrapper.vm.orderConfirmationUpdateAppointmentText; // Assert expect(text).toBe(expected); @@ -391,13 +392,13 @@ describe('OrderConfirmation.vue', () => { }); describe('appointmentTimeText', () => { test.each([ - ['Between 9 AM - 10 AM', '09:00', '10:00'], - ['Between 1 PM - 5 PM', '13:00', '17:00'], - ['Between 8:30 AM - 3 PM', '08:30', '15:00'], - ['Between 1 AM - 2 AM', '01:00', '02:00'], - ['Between 12 AM - 2 AM', '00:00', '02:00'], - ['Between 12 PM - 2 AM', '12:00', '02:00'], - ['Between 11 PM - 12 AM', '23:00', '00:00'] + ['Your appointment is at 9 AM - 10 AM', '09:00', '10:00'], + ['Your appointment is at 1 PM - 5 PM', '13:00', '17:00'], + ['Your appointment is at 8:30 AM - 3 PM', '08:30', '15:00'], + ['Your appointment is at 1 AM - 2 AM', '01:00', '02:00'], + ['Your appointment is at 12 AM - 2 AM', '00:00', '02:00'], + ['Your appointment is at 12 PM - 2 AM', '12:00', '02:00'], + ['Your appointment is at 11 PM - 12 AM', '23:00', '00:00'] ])('should return Mobile time in expected format "%p" when start time "%p" and end time "%p"', (expected, startTime, endTime) => { // Arrange const order = deepClone(sessionStorage); @@ -438,12 +439,12 @@ describe('OrderConfirmation.vue', () => { expect(testValue).toEqual('Drop off before 9:30 AM'); }); test.each([ - ['at 9:00 AM', '09:00', '10:00'], - ['at 1:00 PM', '13:00', '17:00'], - ['at 8:30 AM', '08:30', '15:00'], - ['at 1:00 AM', '01:00', '02:00'], - ['at 12:00 AM', '00:00', '02:00'], - ['at 12:00 PM', '12:00', '02:00'] + ['Your appointment is at 9:00 AM', '09:00', '10:00'], + ['Your appointment is at 1:00 PM', '13:00', '17:00'], + ['Your appointment is at 8:30 AM', '08:30', '15:00'], + ['Your appointment is at 1:00 AM', '01:00', '02:00'], + ['Your appointment is at 12:00 AM', '00:00', '02:00'], + ['Your appointment is at 12:00 PM', '12:00', '02:00'] ])('should return In Shop time in expected format "%p" when start time "%p"', (expected, startTime, endTime) => { // Arrange const order = deepClone(sessionStorage); @@ -479,8 +480,8 @@ describe('OrderConfirmation.vue', () => { } } }; - const expectedServiceLocation = '123 Service Rd., Box 4,
Columbus, OH 12345'; - const expectedShopLocation = '304 Provider Ln.,
Pittsburg, PA 16001'; + const expectedServiceLocation = '123 Service Rd., Box 4, Columbus, OH 12345'; + const expectedShopLocation = '304 Provider Ln., Pittsburg, PA 16001'; test.each([ AppointmentTypeStrings.MOBILE, AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP @@ -554,7 +555,7 @@ describe('OrderConfirmation.vue', () => { useMainStore().getSubmittedOrder = jest.fn().mockImplementation(() => order); }; const { wrapper } = getMountedComponent({}, {}, mockStoreActions); - const expected = '123 Service Rd., Box 4,
Columbus, OH 12345'; + const expected = '123 Service Rd., Box 4, Columbus, OH 12345'; // Act const text = wrapper.vm.serviceLocationFullAddress; @@ -568,7 +569,7 @@ describe('OrderConfirmation.vue', () => { useMainStore().getSubmittedOrder = jest.fn().mockImplementation(() => order); }; const { wrapper } = getMountedComponent({}, {}, mockStoreActions); - const expected = ', Box 4,
Columbus, OH 12345'; + const expected = ', Box 4, Columbus, OH 12345'; // Act const text = wrapper.vm.serviceLocationFullAddress; @@ -582,7 +583,7 @@ describe('OrderConfirmation.vue', () => { useMainStore().getSubmittedOrder = jest.fn().mockImplementation(() => order); }; const { wrapper } = getMountedComponent({}, {}, mockStoreActions); - const expected = '123 Service Rd.,
Columbus, OH 12345'; + const expected = '123 Service Rd., Columbus, OH 12345'; // Act const text = wrapper.vm.serviceLocationFullAddress; @@ -596,7 +597,7 @@ describe('OrderConfirmation.vue', () => { useMainStore().getSubmittedOrder = jest.fn().mockImplementation(() => order); }; const { wrapper } = getMountedComponent({}, {}, mockStoreActions); - const expected = '123 Service Rd., Box 4,
, OH 12345'; + const expected = '123 Service Rd., Box 4, , OH 12345'; // Act const text = wrapper.vm.serviceLocationFullAddress; @@ -610,7 +611,7 @@ describe('OrderConfirmation.vue', () => { useMainStore().getSubmittedOrder = jest.fn().mockImplementation(() => order); }; const { wrapper } = getMountedComponent({}, {}, mockStoreActions); - const expected = '123 Service Rd., Box 4,
Columbus, 12345'; + const expected = '123 Service Rd., Box 4, Columbus, 12345'; // Act const text = wrapper.vm.serviceLocationFullAddress; @@ -624,7 +625,7 @@ describe('OrderConfirmation.vue', () => { useMainStore().getSubmittedOrder = jest.fn().mockImplementation(() => order); }; const { wrapper } = getMountedComponent({}, {}, mockStoreActions); - const expected = '123 Service Rd., Box 4,
Columbus, OH '; + const expected = '123 Service Rd., Box 4, Columbus, OH '; // Act const text = wrapper.vm.serviceLocationFullAddress; @@ -642,7 +643,7 @@ describe('OrderConfirmation.vue', () => { useMainStore().getSubmittedOrder = jest.fn().mockImplementation(() => order); }; const { wrapper } = getMountedComponent({}, {}, mockStoreActions); - const expected = ',
, '; + const expected = ', , '; // Act const text = wrapper.vm.serviceLocationFullAddress; @@ -675,7 +676,7 @@ describe('OrderConfirmation.vue', () => { useMainStore().getSubmittedOrder = jest.fn().mockImplementation(() => order); }; const { wrapper } = getMountedComponent({}, {}, mockStoreActions); - const expected = '304 Provider Ln.,
Pittsburg, PA 16001'; + const expected = '304 Provider Ln., Pittsburg, PA 16001'; // Act const text = wrapper.vm.providerFullAddress; @@ -703,7 +704,7 @@ describe('OrderConfirmation.vue', () => { useMainStore().getSubmittedOrder = jest.fn().mockImplementation(() => order); }; const { wrapper } = getMountedComponent({}, {}, mockStoreActions); - const expected = '304 Provider Ln.,
, PA 16001'; + const expected = '304 Provider Ln., , PA 16001'; // Act const text = wrapper.vm.providerFullAddress; @@ -717,7 +718,7 @@ describe('OrderConfirmation.vue', () => { useMainStore().getSubmittedOrder = jest.fn().mockImplementation(() => order); }; const { wrapper } = getMountedComponent({}, {}, mockStoreActions); - const expected = '304 Provider Ln.,
Pittsburg, 16001'; + const expected = '304 Provider Ln., Pittsburg, 16001'; // Act const text = wrapper.vm.providerFullAddress; @@ -731,7 +732,7 @@ describe('OrderConfirmation.vue', () => { useMainStore().getSubmittedOrder = jest.fn().mockImplementation(() => order); }; const { wrapper } = getMountedComponent({}, {}, mockStoreActions); - const expected = '304 Provider Ln.,
Pittsburg, PA '; + const expected = '304 Provider Ln., Pittsburg, PA '; // Act const text = wrapper.vm.providerFullAddress; @@ -956,9 +957,9 @@ describe('OrderConfirmation.vue', () => { describe('Methods', () => { describe('formatDate', () => { test.each([ - ['Wednesday, April 22', '2020-04-22'], - ['Thursday, February 1', '2018-02-01'], - ['Wednesday, December 30', '2026-12-30'] + ['Wednesday, April 22, 2020', '2020-04-22'], + ['Thursday, February 1, 2018', '2018-02-01'], + ['Wednesday, December 30, 2026', '2026-12-30'] ])('returns %p given %p', (expected, date) => { // Arrange const order = deepClone(sessionStorage); diff --git a/src/layouts/order-confirmation/order-confirmation.vue b/src/layouts/order-confirmation/order-confirmation.vue index c3514995..63bd6cda 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 }} +
+ +
@@ -103,6 +131,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', @@ -144,18 +175,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 +198,23 @@ export default { isRepair: damage.isRepair, hasRecalibrationPart, referralNumber: referralNumber?.toString(), + 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 +227,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 +266,51 @@ 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() { + const glassPieces = this.submittedOrder.lineItems.glassParts?.map((part) => part.partType.toLowerCase()) ?? []; + let glassList = getGlassList(glassPieces); + 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 +322,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 +338,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 +358,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() { @@ -318,6 +415,84 @@ export default { // settleTenderAmount always shows 0 via localhost or dev. // Temporarily set return true to see cart in localhost or dev environment return false; + }, + 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 +586,138 @@ export default { return dateObject.toLocaleDateString('en-us', { weekday: 'long', month: 'long', - day: 'numeric' + day: 'numeric', + year: 'numeric' }); + }, + 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..aaaa32e5 100644 --- a/src/layouts/payment-method/payment-method.vue +++ b/src/layouts/payment-method/payment-method.vue @@ -24,8 +24,6 @@
diff --git a/src/layouts/payment-page/payment-page.vue b/src/layouts/payment-page/payment-page.vue index 7dd6b673..f4c86f11 100644 --- a/src/layouts/payment-page/payment-page.vue +++ b/src/layouts/payment-page/payment-page.vue @@ -42,8 +42,7 @@
diff --git a/src/layouts/schedule-page/schedule-page.spec.js b/src/layouts/schedule-page/schedule-page.spec.js index 4a756184..a418f35e 100644 --- a/src/layouts/schedule-page/schedule-page.spec.js +++ b/src/layouts/schedule-page/schedule-page.spec.js @@ -167,7 +167,7 @@ describe('schedule-page.vue', () => { days: [] }; const store = useMainStore(); - store.getShopTimeSlots.mockImplementation(() => ({ + store.getShopTimeSlots.mockImplementation(() => Promise.resolve({ data: { estimatedServiceMinutesMinimum: 90, estimatedServiceMinutesMaximum: 120, @@ -219,7 +219,7 @@ describe('schedule-page.vue', () => { days: [] }; const store = useMainStore(); - store.getShopTimeSlots.mockImplementation(() => ({ + store.getShopTimeSlots.mockImplementation(() => Promise.resolve({ data: { estimatedServiceMinutesMinimum: 90, estimatedServiceMinutesMaximum: 120, @@ -233,7 +233,8 @@ describe('schedule-page.vue', () => { '2023-01-01', '2023-03-31', 'Inshop', - '123' + '123', + '43228' ); // Assert diff --git a/src/layouts/schedule-page/schedule-page.vue b/src/layouts/schedule-page/schedule-page.vue index 0cd0f480..45382ced 100644 --- a/src/layouts/schedule-page/schedule-page.vue +++ b/src/layouts/schedule-page/schedule-page.vue @@ -176,15 +176,18 @@ const getAvailableDates = async ( storeAction.payload.endDate, storeAction.payload.shopAppointmentType, storeAction.payload.providerNumber - ); + ).catch(() => { + // eslint-disable-next-line no-console + console.warn('Error fetching shop time slots...'); + }); } else if (storeAction.payload?.zipCodeOverride) { timeSlotsResponse = await useMainStore().getMobileTimeSlots( storeAction.payload.startDate, storeAction.payload.endDate, storeAction.payload.zipCodeOverride - ).catch((error) => { + ).catch(() => { // eslint-disable-next-line no-console - console.warn('Error fetching mobile time slots:', error); + console.warn('Error fetching mobile time slots...'); }); } @@ -234,7 +237,7 @@ export default { const mobileFeePartPromise = getPricedMobileFeePart(serviceZipCode); const serviceabilityDetailsPromise = getServiceabilityDetails(serviceZipCode); const getGlassFeesPromise = useMainStore().getGlassFees(); - const providersPromise = useMainStore().getProviders(serviceZipCode); + const providersPromise = useMainStore().getSafeliteProviders(serviceZipCode); const premiumFeePromise = useMainStore().getMobilePremiumFee(); const premiumFeeWithPricePromise = premiumFeePromise.then((result) => { diff --git a/src/layouts/schedule-page/service-location/service-location.spec.js b/src/layouts/schedule-page/service-location/service-location.spec.js index 20e0596b..6f8d960d 100644 --- a/src/layouts/schedule-page/service-location/service-location.spec.js +++ b/src/layouts/schedule-page/service-location/service-location.spec.js @@ -147,7 +147,7 @@ function setupMocks({ appointmentType = AppointmentTypeStrings.IN_SHOP }) { } } })]; - useMainStore().getProviders = jest.fn().mockImplementation(() => mockProviders()); + useMainStore().getSafeliteProviders = jest.fn().mockImplementation(() => mockProviders()); const wrapper = mount(serviceLocation, mountOptions); wrapper.vm.$router.navigateWithSpinner = jest.fn(); diff --git a/src/layouts/schedule-page/service-location/service-location.vue b/src/layouts/schedule-page/service-location/service-location.vue index e48dddcc..75a5aa40 100644 --- a/src/layouts/schedule-page/service-location/service-location.vue +++ b/src/layouts/schedule-page/service-location/service-location.vue @@ -243,6 +243,10 @@ export default { return this.isGlassServiceableInshop; }, isServiceableMobile() { + if (this.isBigTruck) { + return false; + } + if (this.isRecalibrationServiceableMobile !== null) { return ( this.isGlassServiceableMobile @@ -553,7 +557,7 @@ export default { } }); - const providersPromise = useMainStore().getProviders(this.mobileZipCode); + const providersPromise = useMainStore().getSafeliteProviders(this.mobileZipCode); await providersPromise.then((result) => { const providers = result.data; if (providers) { diff --git a/src/layouts/schedule-page/service-location/shop-address/shop-address.vue b/src/layouts/schedule-page/service-location/shop-address/shop-address.vue index 54b9dc0d..934bd75f 100644 --- a/src/layouts/schedule-page/service-location/shop-address/shop-address.vue +++ b/src/layouts/schedule-page/service-location/shop-address/shop-address.vue @@ -195,7 +195,7 @@ export default { const formattedEndDate = endDate.toISOString().split('T')[0]; return { - displayAvailabilityIndicators: true, + displayAvailabilityIndicators: false, availabilityRatingCallback: getAvailabilityRating, startDate: formattedStartDate, endDate: formattedEndDate, @@ -314,7 +314,7 @@ export default { } }, async getNearbyShops(radiusInMiles) { - const result = await useMainStore().getProviders(this.internalZipcode, radiusInMiles); + const result = await useMainStore().getSafeliteProviders(this.internalZipcode, radiusInMiles); return result.data.shopProviders; }, getFullProviderAddress(provider) { diff --git a/src/layouts/vin-lookup/vin-lookup-alerts/vin-lookup-alerts.vue b/src/layouts/vin-lookup/vin-lookup-alerts/vin-lookup-alerts.vue index f9be7ede..495881b9 100644 --- a/src/layouts/vin-lookup/vin-lookup-alerts/vin-lookup-alerts.vue +++ b/src/layouts/vin-lookup/vin-lookup-alerts/vin-lookup-alerts.vue @@ -3,15 +3,20 @@ id="vin-lookup-alerts-wrapper" :class="wrapperCssClass"> + v-if="isVehicleNotFoundVisible" + class="mt-5" /> + v-if="isTwoIdenticalYMMVehicleFoundVisible" + class="mt-5" /> + v-else-if="isVehicleNotMatchedVisible" + class="mt-5" /> + v-else-if="isPerfectMatchVisible" + class="mt-5" /> + v-else-if="isNoServiceVisible" + class="mt-5" />
+ + diff --git a/src/layouts/vin-lookup/vin-lookup.vue b/src/layouts/vin-lookup/vin-lookup.vue index 6585d865..e4b5ea85 100644 --- a/src/layouts/vin-lookup/vin-lookup.vue +++ b/src/layouts/vin-lookup/vin-lookup.vue @@ -13,13 +13,12 @@
+ class="mt-4" /> @@ -48,6 +47,7 @@ import { isGlassAvailableForCarId } from '@/helpers/damage-helper'; import settleAllPromises from '@/helpers/layout-helper'; import routerParams from '@/router/router-constants/router-params'; import { useMainStore } from '@/store'; +import showIssLoadingModal from '@/helpers/loading-modal-helper.js'; // Import Component import baseFormMixin from '@/mixins/base-form-mixin'; @@ -140,7 +140,9 @@ export default { // TODO: Side effects in computed. if (this.vinPopulatedOnPageLoad) { // TODO: Modify to remove side effects in computed + // eslint-disable-next-line vue/no-side-effects-in-computed-properties this.activeVehicleLookupAlertType = vehicleLookupAlertTypes.PERFECT_MATCH; + // eslint-disable-next-line vue/no-side-effects-in-computed-properties this.needToLookupVehicle = false; const lastSixChars = this.vin.substring(11, this.vin.length); return `!X!X!X!X!X!X!X!X!X!X!X${lastSixChars}`; @@ -174,6 +176,7 @@ export default { this.resetActiveAlert(); // Temp solution to reset the 'disabled' style on the Continue button this.$refs.siteFooter.enableForwardAction(); + showIssLoadingModal(true); if (this.needToLookupVehicle) { const vehicleLookupResponse = await this.lookupVehicleByVin(this.vin); @@ -186,6 +189,7 @@ export default { // because the form itself actually passes its client-side validation. // SSR-189 Scenario #4. this.$refs.siteFooter.enableForwardAction(); + showIssLoadingModal(false); return; } @@ -193,6 +197,7 @@ export default { this.activeVehicleLookupAlertType = vehicleLookupAlertTypes.NO_SERVICE; this.resetVehicleFromLookup(); this.$refs.siteFooter.disableForwardButton(); + showIssLoadingModal(false); return; } @@ -220,8 +225,8 @@ export default { `${this.vehicleFromLookup.year} ${this.vehicleFromLookup.make} ${this.vehicleFromLookup.model} ${this.forwardButtonCarStyle}`; this.$refs.siteFooter.updateButtonText(`Continue with ${vehicleYearMakeModelStyle}`); this.needToLookupVehicle = false; - - return null; + showIssLoadingModal(false); + return; } let isSelectedGlassAvailableForVehicle = true; @@ -242,7 +247,7 @@ export default { ); // navigate() doesn't stop the processing flow - return null; + return; } // save vehicle to store if it hasn't already been saved @@ -255,7 +260,7 @@ export default { this.navigationScenarios.CORRECTED_VIN_FROM_POLICY_VEHICLE, this.$route ); - return null; + return; } const partsOrQuestionsResponse = await this.getPartsOrQuestions(); diff --git a/src/main.js b/src/main.js index 7b5ca497..03f498c1 100644 --- a/src/main.js +++ b/src/main.js @@ -29,7 +29,7 @@ vueApp.config.compilerOptions.isCustomElement = (tag) => const pinia = createPinia(); vueApp.use(pinia); pinia.use(piniaPluginPersistedstate); -useMainStore().populateInitialState(); +useMainStore().populateInitialState(false); // Additional Vue items to setup vueApp.use(router); diff --git a/src/router/router-constants/routing-table.js b/src/router/router-constants/routing-table.js index dea1daae..2698360f 100644 --- a/src/router/router-constants/routing-table.js +++ b/src/router/router-constants/routing-table.js @@ -277,6 +277,10 @@ const routingTable = () => [ { scenario: navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS, destinationIssPageValue: issPageValues.COVERAGE_STATEMENT + }, + { + scenario: navigationScenarios.CLICKED_NEED_HELP_WITH_BAILOUT, + destinationIssPageValue: issPageValues.BAILOUT_PAGE } ] }, diff --git a/src/store/index.js b/src/store/index.js index d74067f7..6759f16f 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -299,6 +299,11 @@ export const useMainStore = defineStore({ const nonWindshieldItems = damage.glassToReplace?.filter((glass) => glass.glassLocation !== damageLocationsSelected.WINDSHIELD); return !!nonWindshieldItems?.length; }, + hasMoldingPart: (s) => { + return s.order.lineItems.glassParts?.some((glassPart) => { + return glassPart.childParts?.some((childPart) => childPart.partType === partTypeStrings.MOLDING); + }); + }, isMobileAppointment: (state) => state.order.serviceLocation.appointmentType === AppointmentTypeStrings.MOBILE || state.order.serviceLocation.appointmentType === AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP, isDropOffAppointment: (state) => state.order.serviceLocation.appointmentType === AppointmentTypeStrings.DROP_OFF, @@ -1029,46 +1034,35 @@ export const useMainStore = defineStore({ }); }, - getProviders(serviceZipCode, shopRadiusInMiles = 100) { - const { carId, isBigTruck } = this.order.vehicle; - const damageType = this.damage.isRepair ? 'Repair' : 'Replace'; - const { parentAccountNumber } = this.order; - const partsWithRecal = getTopLevelGlassPartsWithRecal(this.order.lineItems.glassParts); - const windshieldPartWithRecal = partsWithRecal?.length > 0 ? partsWithRecal[0] : null; - const safeliteOnly = true; - - let url = `${endpoints.GetProviders.url}/${serviceZipCode}/${damageType}/${shopRadiusInMiles}/${parentAccountNumber}/${safeliteOnly}/${carId}`; - if (windshieldPartWithRecal) { - url += `/${windshieldPartWithRecal.partNumber}`; - } - url += `/${isBigTruck}`; - return globalMethods.callHttpClient({ - method: endpoints.GetProviders.method, - endpoint: url - }); + getSafeliteProviders(zipCode, radius = 100) { + return this.getProviders(zipCode, radius, true) }, getTpaProviders(zipCode, radius) { + return this.getProviders(zipCode, radius, false) + }, + + getProviders(zipcode, radius, safeliteOnly) { const { carId, isBigTruck } = this.order.vehicle; const damageType = this.damage.isRepair ? 'Repair' : 'Replace'; const { parentAccountNumber } = this.order; const partsWithRecal = getTopLevelGlassPartsWithRecal(this.order.lineItems.glassParts); const windshieldPartWithRecal = partsWithRecal?.length > 0 ? partsWithRecal[0] : null; - const safeliteOnly = false; - - let url = `${endpoints.GetProviders.url}/${zipCode}/${damageType}/${radius}/${parentAccountNumber}/${safeliteOnly}/${carId}`; - if (windshieldPartWithRecal) { - url += `/${windshieldPartWithRecal.partNumber}`; - } - url += `/${isBigTruck}`; - - return new Promise((resolve, reject) => { - globalMethods.callHttpClient({ - method: endpoints.GetProviders.method, - endpoint: url - }).then((response) => resolve(response), (error) => reject(error)); + return globalMethods.callHttpClient({ + method: endpoints.GetProviders.method, + endpoint: endpoints.GetProviders.url( + zipcode, + damageType, + radius, + parentAccountNumber, + safeliteOnly, + carId, + windshieldPartWithRecal?.partNumber, + isBigTruck + ) }); }, + getCarrierAccountInfo() { return new Promise((resolve, reject) => { globalMethods.callHttpClient({ @@ -1586,7 +1580,7 @@ export const useMainStore = defineStore({ endpoint: endpoints.LoadSession.url, payload: { referralNumber: duplicate.referralNumber, - referralDate: duplicate.responseDate, + referralDate: duplicate.referralDate, parentAccountNumber: order.parentAccountNumber, referralCorrelationId: duplicate.correlationId } @@ -1596,22 +1590,35 @@ export const useMainStore = defineStore({ return; } - if (this.isPolicyLookupSuccessful) { - order.customer.address.streetAddress = data.customer?.address?.streetAddress; - order.customer.address.streetAddress2 = data.customer?.address?.streetAddress2; - order.customer.address.city = data.customer?.address?.city; - order.customer.address.state = data.customer?.address?.state; - order.customer.address.zipCode = data.customer?.address?.zipCode; + if (data?.customer) { order.customer.firstName = data?.customer?.firstName; order.customer.lastName = data?.customer?.lastName; order.customer.emailAddress = data?.customer?.emailAddress; - + order.contactInfo.extension = data?.customer?.homePhone?.slice(10); order.contactInfo.homePhone = data?.customer?.homePhone?.slice(0, 10); order.contactInfo.servicePhone = data?.customer?.servicePhone; order.contactInfo.alternativePhone = data?.customer?.alternativePhone; order.contactInfo.requestTextUpdates = data?.customer?.isSmsOptIn; + + if (data?.customer?.address) { + order.customer.address.streetAddress = data.customer?.address?.streetAddress; + order.customer.address.streetAddress2 = data.customer?.address?.streetAddress2; + order.customer.address.city = data.customer?.address?.city; + order.customer.address.state = data.customer?.address?.state; + order.customer.address.zipCode = data.customer?.address?.zipCode; + } + } + + if (data?.vehicle) { + order.vehicle.year = data?.vehicle?.year; + order.vehicle.make = data?.vehicle?.make; + order.vehicle.model = data?.vehicle?.model; + order.vehicle.style = data?.vehicle?.style; + } + + if (this.isPolicyLookupSuccessful) { if (data.insuranceCoverage) { order.insuranceCoverage.coverageType = data?.insuranceCoverage?.coverageType; order.insuranceCoverage.coverageStatus = data?.insuranceCoverage?.coverageStatus; @@ -1644,11 +1651,6 @@ export const useMainStore = defineStore({ } } } - - order.vehicle.year = data.vehicle.year; - order.vehicle.make = data.vehicle.make; - order.vehicle.model = data.vehicle.model; - order.vehicle.style = data.vehicle.style; } } @@ -2441,8 +2443,8 @@ export const useMainStore = defineStore({ }, // populate initial state - populateInitialState() { - if (!localStorage.getItem(storeId)) { + populateInitialState(forceReset) { + if (!sessionStorage.getItem(storeId) || forceReset ) { this.$state = state; } }, @@ -2889,14 +2891,15 @@ 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 + // set to sessionStorage window.sessionStorage.setItem(webStorageConstants.SUBMITTED_ORDER, JSON.stringify(submittedOrder)); // clear vuex @@ -2912,11 +2915,14 @@ export const useMainStore = defineStore({ }, resetSubmittedOrder() { - // clear from local storage + // clear from sessionStorage window.sessionStorage.removeItem(webStorageConstants.SUBMITTED_ORDER); } }, - persist: true + persist: { + // Default is localStorage, but sessionStorage is used here to ensure state is cleared when the browser tab is closed, preventing potential issues with stale data on return visits. + storage: sessionStorage + } }); // Private Functions diff --git a/src/store/store.spec.js b/src/store/store.spec.js index 1306221c..4f4173d8 100644 --- a/src/store/store.spec.js +++ b/src/store/store.spec.js @@ -14,6 +14,7 @@ import endpoints from '@/constants/endpoints'; import { AppointmentTypeStrings } from '@/constants/schedule-constants'; import coverageType from '@/constants/coverage-type'; import { getEnumName } from '@/helpers/unit-test-helper'; +import { strictEqual } from 'assert'; describe('Store', () => { let store; @@ -1071,28 +1072,29 @@ describe('Store', () => { experiments: getRandomString(6, 6) }; const vehicle = { - year: getRandomString(6, 6), - make: getRandomString(6, 6), - model: getRandomString(6, 6), - style: getRandomString(6, 6), - vin: getRandomString(6, 6), - carId: getRandomString(6, 6), - licensePlateNumber: getRandomString(6, 6) + year: getRandomInt(2000, 2024).toString(), + make: getRandomString(5, 15), + model: getRandomString(5, 15), + style: getRandomString(5, 15), + carId: getRandomInt(1000, 9999), + vin: getRandomString(17, 17), + licensePlateNumber: getRandomString(7, 7) }; - const customer = { - firstName: getRandomString(6, 6), - lastName: getRandomString(6, 6), - emailAddress: getRandomString(6, 6), - phoneNumber: getRandomString(6, 6), - isSmsOptIn: getRandomString(6, 6), + const customer= { address: { - streetAddress: getRandomString(6, 6), - streetAddress2: getRandomString(6, 6), - city: getRandomString(6, 6), - state: getRandomString(6, 6), - zipCode: getRandomString(6, 6), - zipCodeCtu: getRandomString(6, 6) - } + streetAddress: getRandomString(10, 50), + streetAddress2: getRandomString(5, 20), + city: getRandomString(5, 20), + state: getRandomString(2, 2), + zipCode: getRandomString(5, 5) + }, + firstName: getRandomString(5, 15), + lastName: getRandomString(5, 15), + emailAddress: `${getRandomString(5, 10)}@test.com`, + homePhone: getRandomString(10, 10), + servicePhone: getRandomString(10, 10), + alternativePhone: getRandomString(10, 10), + isSmsOptIn: getRandomBoolean() }; const fullApiResponse = { data: { @@ -1140,7 +1142,7 @@ describe('Store', () => { }; store.order.insuranceCoverage.coverageType = coverageType.Deductible; - store.policy.vehicles = [{ vin: vehicle.vin }]; + store.policy.vehicles = [{ vehicle }]; // Act await store.loadSession(duplicate); @@ -1150,7 +1152,7 @@ describe('Store', () => { expect(store.vehicle.make).toBe(vehicle.make); expect(store.vehicle.model).toBe(vehicle.model); expect(store.vehicle.style).toBe(vehicle.style); - expect(store.vehicle.vin).toBe(vehicle.vin); + // vin is not set directly by loadSession, it's set by updateVehicle after lookupVehicleByVin expect(store.vehicle.registration.licensePlate).toBe(vehicle.licensePlateNumber); }); it('sets expected customer data', async () => { @@ -1981,16 +1983,16 @@ describe('Store', () => { const radius = getRandomInt(5, 100); const isBigTruck = false; store.order.vehicle.isBigTruck = isBigTruck; - const expectedEndpoint = [ - endpoints.GetProviders.url, + const expectedEndpoint = endpoints.GetProviders.url( zipCode, damageType, radius, parentAccountNumber, 'false', carId, + null, isBigTruck - ].join('/'); + ); // Act await store.getTpaProviders(zipCode, radius); @@ -2135,4 +2137,344 @@ describe('Store', () => { expect(result).toBe(expected); }); }); + + describe('loadSession method', () => { + let mockDuplicate; + let mockResponse; + + beforeEach(() => { + mockDuplicate = { + referralNumber: getRandomString(10, 10), + referralDate: new Date().toISOString(), + correlationId: getRandomGuid() + }; + + mockResponse = { + data: { + customer: { + address: { + streetAddress: getRandomString(10, 50), + streetAddress2: getRandomString(5, 20), + city: getRandomString(5, 20), + state: getRandomString(2, 2), + zipCode: getRandomString(5, 5) + }, + firstName: getRandomString(5, 15), + lastName: getRandomString(5, 15), + emailAddress: `${getRandomString(5, 10)}@test.com`, + homePhone: getRandomString(10, 10), + servicePhone: getRandomString(10, 10), + alternativePhone: getRandomString(10, 10), + isSmsOptIn: getRandomBoolean() + }, + vehicle: { + year: getRandomInt(2000, 2024).toString(), + make: getRandomString(5, 15), + model: getRandomString(5, 15), + style: getRandomString(5, 15), + carId: getRandomInt(1000, 9999), + vin: getRandomString(17, 17), + licensePlateNumber: getRandomString(7, 7) + }, + insuranceCoverage: { + coverageType: getRandomEnum(coverageType), + coverageStatus: getRandomEnum(coverageStatuses), + claimNumber: getRandomString(10, 10) + }, + referralNumber: getRandomString(10, 10), + referralDate: new Date().toISOString(), + referralCorrelationId: getRandomGuid(), + referralSequenceNumber: getRandomInt(1, 100), + eon: getRandomString(10, 10) + } + }; + + store.order.parentAccountNumber = getRandomString(6, 6); + store.order.insuranceCoverage.coverageType = coverageType.Deductible; + }); + + it('should call callHttpClient with correct parameters', async () => { + // Arrange + globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve(mockResponse)); + + // Act + await store.loadSession(mockDuplicate); + + // Assert + expect(globalMethods.callHttpClient).toHaveBeenCalledWith({ + method: endpoints.LoadSession.method, + endpoint: endpoints.LoadSession.url, + payload: { + referralNumber: mockDuplicate.referralNumber, + referralDate: mockDuplicate.referralDate, + parentAccountNumber: store.order.parentAccountNumber, + referralCorrelationId: mockDuplicate.correlationId + } + }); + }); + + it('should return early when response data is null/undefined', async () => { + // Arrange + globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve({ data: null })); + const originalStreetAddress = store.order.customer.address.streetAddress; + const originalLoadedFromDupeCheck = store.order.loadedFromDupeCheck; + + // Act + await store.loadSession(mockDuplicate); + // Assert + expect(store.order.customer.address.streetAddress).toBe(originalStreetAddress); + expect(store.order.loadedFromDupeCheck).toBe(originalLoadedFromDupeCheck); + }); + + it('should update customer address fields from response data', async () => { + // Arrange + globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve(mockResponse)); + + // Act + await store.loadSession(mockDuplicate); + + // Assert + expect(store.order.customer.address.streetAddress).toBe(mockResponse.data.customer.address.streetAddress); + expect(store.order.customer.address.streetAddress2).toBe(mockResponse.data.customer.address.streetAddress2); + expect(store.order.customer.address.city).toBe(mockResponse.data.customer.address.city); + expect(store.order.customer.address.state).toBe(mockResponse.data.customer.address.state); + expect(store.order.customer.address.zipCode).toBe(mockResponse.data.customer.address.zipCode); + }); + + it('should update contact information from response data', async () => { + // Arrange + globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve(mockResponse)); + + // Act + await store.loadSession(mockDuplicate); + + // Assert + expect(store.order.customer.firstName).toBe(mockResponse.data.customer.firstName); + expect(store.order.customer.lastName).toBe(mockResponse.data.customer.lastName); + expect(store.order.customer.emailAddress).toBe(mockResponse.data.customer.emailAddress); + expect(store.order.contactInfo.servicePhone).toBe(mockResponse.data.customer.servicePhone); + expect(store.order.contactInfo.alternativePhone).toBe(mockResponse.data.customer.alternativePhone); + expect(store.order.contactInfo.requestTextUpdates).toBe(mockResponse.data.customer.isSmsOptIn); + }); + + it('should extract extension from homePhone correctly', async () => { + // Arrange + mockResponse.data.customer.homePhone = '12345678901234'; + globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve(mockResponse)); + + // Act + await store.loadSession(mockDuplicate); + + // Assert + expect(store.order.contactInfo.homePhone).toBe('1234567890'); + expect(store.order.contactInfo.extension).toBe('1234'); + }); + + it('should update vehicle information from response data', async () => { + // Arrange + globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve(mockResponse)); + + // Act + await store.loadSession(mockDuplicate); + + // Assert + expect(store.order.vehicle.year).toBe(mockResponse.data.vehicle.year); + expect(store.order.vehicle.make).toBe(mockResponse.data.vehicle.make); + expect(store.order.vehicle.model).toBe(mockResponse.data.vehicle.model); + expect(store.order.vehicle.style).toBe(mockResponse.data.vehicle.style); + }); + + it('should update insurance coverage when isPolicyLookupSuccessful is true and insuranceCoverage exists in response', async () => { + // Arrange + store.order.insuranceCoverage.coverageType = coverageType.Deductible; // Make isPolicyLookupSuccessful true + globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve(mockResponse)); + + // Act + await store.loadSession(mockDuplicate); + + // Assert + expect(store.order.insuranceCoverage.coverageType).toBe(mockResponse.data.insuranceCoverage.coverageType); + expect(store.order.insuranceCoverage.coverageStatus).toBe(mockResponse.data.insuranceCoverage.coverageStatus); + expect(store.order.insuranceCoverage.claimNumber).toBe(mockResponse.data.insuranceCoverage.claimNumber); + }); + + it('should update insurance coverage from payment.insuranceCoverage when main insuranceCoverage does not exist', async () => { + // Arrange + store.order.insuranceCoverage.coverageType = coverageType.Deductible; + mockResponse.data.insuranceCoverage = null; + mockResponse.data.payment = { + insuranceCoverage: { + isVerified: true, + claimNumber: getRandomString(10, 10) + } + }; + globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve(mockResponse)); + + // Act + await store.loadSession(mockDuplicate); + + // Assert + expect(store.order.insuranceCoverage.coverageStatus).toBe(coverageStatuses.VERIFIED); + expect(store.order.insuranceCoverage.claimNumber).toBe(mockResponse.data.payment.insuranceCoverage.claimNumber); + }); + + it('should update vehicle registration license plate when policy vehicles exist and carId is present', async () => { + // Arrange + store.order.insuranceCoverage.coverageType = coverageType.Deductible; + store.order.policy.vehicles = [ + { id: 1, vin: mockResponse.data.vehicle.vin } + ]; + globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve(mockResponse)); + + // Act + await store.loadSession(mockDuplicate); + + // Assert + expect(store.order.vehicle.registration.licensePlate).toBe(mockResponse.data.vehicle.licensePlateNumber); + }); + + it('should call lookupVehicleByVin and update vehicle when vin matches policy vehicle', async () => { + // Arrange + const mockVehicleLookupResponse = { + data: { + carId: getRandomInt(1000, 9999), + year: getRandomInt(2000, 2024).toString() + } + }; + const policyVehicle = { + id: getRandomInt(1, 100), + vin: mockResponse.data.vehicle.vin, + deductible: 100, + repairWaived: false, + endorsements: ['OEM Approved'] + }; + + store.order.insuranceCoverage.coverageType = coverageType.Deductible; + store.order.policy.vehicles = [policyVehicle]; + + globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve(mockResponse)); + store.lookupVehicleByVin = jest.fn().mockReturnValue(Promise.resolve(mockVehicleLookupResponse)); + store.updateVehicle = jest.fn(); + store.updateVehicleCoverage = jest.fn(); + + // Act + await store.loadSession(mockDuplicate); + + // Assert + expect(store.lookupVehicleByVin).toHaveBeenCalledWith(mockResponse.data.vehicle.vin); + expect(store.updateVehicle).toHaveBeenCalledWith({ + ...mockVehicleLookupResponse.data, + policyVehicleId: policyVehicle.id, + vin: policyVehicle.vin + }); + expect(store.updateVehicleCoverage).toHaveBeenCalled(); + }); + + it('should not call lookupVehicleByVin when policy vehicles array is empty', async () => { + // Arrange + store.order.insuranceCoverage.coverageType = coverageType.Deductible; + store.order.policy.vehicles = []; + globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve(mockResponse)); + store.lookupVehicleByVin = jest.fn(); + + // Act + await store.loadSession(mockDuplicate); + + // Assert + expect(store.lookupVehicleByVin).not.toHaveBeenCalled(); + }); + + it('should not call lookupVehicleByVin when carId is not present', async () => { + // Arrange + store.order.insuranceCoverage.coverageType = coverageType.Deductible; + store.order.policy.vehicles = [{ id: 1, vin: 'TEST123456789' }]; + mockResponse.data.vehicle.carId = null; + globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve(mockResponse)); + store.lookupVehicleByVin = jest.fn(); + + // Act + await store.loadSession(mockDuplicate); + + // Assert + expect(store.lookupVehicleByVin).not.toHaveBeenCalled(); + }); + + it('should not call lookupVehicleByVin when vin is not present', async () => { + // Arrange + store.order.insuranceCoverage.coverageType = coverageType.Deductible; + store.order.policy.vehicles = [{ id: 1, vin: 'TEST123456789' }]; + mockResponse.data.vehicle.vin = null; + globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve(mockResponse)); + store.lookupVehicleByVin = jest.fn(); + + // Act + await store.loadSession(mockDuplicate); + + // Assert + expect(store.lookupVehicleByVin).not.toHaveBeenCalled(); + }); + + it('should not update insurance coverage when isPolicyLookupSuccessful is false', async () => { + // Arrange + store.order.insuranceCoverage.coverageType = coverageType.NONE; // Make isPolicyLookupSuccessful false + const originalCoverageStatus = store.order.insuranceCoverage.coverageStatus; + globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve(mockResponse)); + + // Act + await store.loadSession(mockDuplicate); + + // Assert + expect(store.order.insuranceCoverage.coverageStatus).toBe(originalCoverageStatus); + }); + + it('should update referral information from response data', async () => { + // Arrange + globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve(mockResponse)); + + // Act + await store.loadSession(mockDuplicate); + + // Assert + expect(store.order.referralNumber).toBe(mockResponse.data.referralNumber); + expect(store.order.referralDate).toBe(mockResponse.data.referralDate); + expect(store.order.referralCorrelationId).toBe(mockResponse.data.referralCorrelationId); + expect(store.order.referralSequenceNumber).toBe(mockResponse.data.referralSequenceNumber); + expect(store.order.eon).toBe(mockResponse.data.eon); + }); + + it('should set loadedFromDupeCheck and loadedSessionClearedPreviousData flags', async () => { + // Arrange + globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve(mockResponse)); + + // Act + await store.loadSession(mockDuplicate); + + // Assert + expect(store.order.loadedFromDupeCheck).toBe(true); + expect(store.order.loadedSessionClearedPreviousData).toBe(false); + }); + + it('should handle API error gracefully', async () => { + // Arrange + const error = new Error('API Error'); + globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.reject(error)); + + // Act & Assert + await expect(store.loadSession(mockDuplicate)).rejects.toThrow('API Error'); + }); + + it('should handle missing optional fields in response data', async () => { + // Arrange + mockResponse.data.customer.address.streetAddress2 = undefined; + mockResponse.data.customer.alternativePhone = undefined; + globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve(mockResponse)); + + // Act + await store.loadSession(mockDuplicate); + + // Assert + expect(store.order.customer.address.streetAddress2).toBeUndefined(); + expect(store.order.contactInfo.alternativePhone).toBeUndefined(); + }); + }); });