diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 50ecb0eb..3e06d0dc 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -88,6 +88,7 @@ stages: indexDeployVariables: __VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__: $(__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__) __VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__: $(__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__) + __VUE_APP_GOOGLE_MAPS_API_SCRIPT__: $(__VUE_APP_GOOGLE_MAPS_API_SCRIPT__) - template: templates/digital/invalidate-cloudfront-cache.yml@AzureDevOps parameters: awsCliContainer: awscli @@ -137,6 +138,7 @@ stages: indexDeployVariables: __VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__: $(__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__) __VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__: $(__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__) + __VUE_APP_GOOGLE_MAPS_API_SCRIPT__: $(__VUE_APP_GOOGLE_MAPS_API_SCRIPT__) - template: templates/digital/invalidate-cloudfront-cache.yml@AzureDevOps parameters: awsCliContainer: awscli @@ -186,6 +188,7 @@ stages: indexDeployVariables: __VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__: $(__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__) __VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__: $(__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__) + __VUE_APP_GOOGLE_MAPS_API_SCRIPT__: $(__VUE_APP_GOOGLE_MAPS_API_SCRIPT__) - template: templates/digital/invalidate-cloudfront-cache.yml@AzureDevOps parameters: awsCliContainer: awscli @@ -235,6 +238,7 @@ stages: indexDeployVariables: __VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__: $(__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__) __VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__: $(__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__) + __VUE_APP_GOOGLE_MAPS_API_SCRIPT__: $(__VUE_APP_GOOGLE_MAPS_API_SCRIPT__) - template: templates/digital/invalidate-cloudfront-cache.yml@AzureDevOps parameters: awsCliContainer: awscli diff --git a/public/index.html b/public/index.html index 7edb81cc..6db2d816 100644 --- a/public/index.html +++ b/public/index.html @@ -5,6 +5,7 @@ window.dataLayer = [{}]; + diff --git a/src/constants/endpoints.js b/src/constants/endpoints.js index a24aa40a..fbbf0400 100644 --- a/src/constants/endpoints.js +++ b/src/constants/endpoints.js @@ -140,7 +140,7 @@ const endpoints = Object.freeze({ method: 'GET' }, GooglePlaces: { - url: 'https://maps.googleapis.com/maps/api/js?key={apiKey}&libraries=places' + url: (apiKey) => `https://maps.googleapis.com/maps/api/js?key=${apiKey}&libraries=places` }, ValidateClientTag: { url: '/clientauth/api/v1/clientauth/validate-client-tag', diff --git a/src/iss-components/address-questions/address-questions.vue b/src/iss-components/address-questions/address-questions.vue index 39144ad2..665a3772 100644 --- a/src/iss-components/address-questions/address-questions.vue +++ b/src/iss-components/address-questions/address-questions.vue @@ -196,7 +196,7 @@ export default { const addressField1 = document.getElementById('autocomplete'); const self = this; - const url = endpoints.GooglePlaces.url.replace('{apiKey}', applicationConfig.GOOGLE_PLACES_API_KEY); + const url = endpoints.GooglePlaces.url(applicationConfig.GOOGLE_PLACES_API_KEY); this.$loadScript(url) .then(() => { diff --git a/src/iss-components/google-map/__snapshots__/google-map.spec.js.snap b/src/iss-components/google-map/__snapshots__/google-map.spec.js.snap new file mode 100644 index 00000000..efab5bbf --- /dev/null +++ b/src/iss-components/google-map/__snapshots__/google-map.spec.js.snap @@ -0,0 +1,8 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Google Map should render expected initial data 1`] = ` +Object { + "country": "USA", + "geocoder": null, +} +`; diff --git a/src/iss-components/google-map/google-map.spec.js b/src/iss-components/google-map/google-map.spec.js new file mode 100644 index 00000000..2c4ad709 --- /dev/null +++ b/src/iss-components/google-map/google-map.spec.js @@ -0,0 +1,428 @@ +import { shallowMount } from '@vue/test-utils'; +import googleMap from '@/iss-components/google-map/google-map.vue'; + +const mockFitBounds = jest.fn(); +const mockMapInstance = { + fitBounds: mockFitBounds +}; +const mockMap = jest.fn(() => mockMapInstance); + +const mockGeocode = jest.fn(); +const mockGeocoderInstance = { + geocode: mockGeocode +}; +const mockGeocoder = jest.fn(() => (mockGeocoderInstance)); + +const mockAdvancedMarkerElement = jest.fn(); + +const mockImportLibrary = jest.fn().mockImplementation(() => (Promise.resolve({ + Map: mockMap, + Geocoder: mockGeocoder, + AdvancedMarkerElement: mockAdvancedMarkerElement +}))); + +const mockExtend = jest.fn(); +const mockLatLngBound = { extend: mockExtend }; + +const setupGoogleMock = () => { + global.window.google = { + maps: { + importLibrary: mockImportLibrary, + LatLngBounds: jest.fn(() => (mockLatLngBound)) + } + }; +}; + +beforeEach(() => { + // clear constructors + mockMap.mockClear(); + mockGeocoder.mockClear(); + mockAdvancedMarkerElement.mockClear(); + + // clear methods + mockExtend.mockClear(); + mockGeocode.mockClear(); + mockFitBounds.mockClear(); + + setupGoogleMock(); +}); + +describe('Google Map', () => { + describe('should render', () => { + test('expected initial data', async () => { + // Arrange + const wrapper = shallowMount(googleMap, {}); + await wrapper.vm.$nextTick(); + + // Assert + expect(wrapper.vm.$data).toMatchSnapshot(); + }); + test('map', () => { + // Arrange + const mapReference = '#map'; + const wrapper = shallowMount(googleMap, {}); + + // Act + const map = wrapper.find(mapReference); + + // Assert + expect(map.exists()).toBeTruthy(); + }); + }); + test.each([ + ['13001', '13001 USA'], + ['9', '9 USA'], + ['', ' USA'], + [null, ' USA'] + ])( + 'given that zipcode set to "%p", computed zipCodeAddress should return "%p"', + async (zipCode, expected) => { + // Arrange + const wrapper = shallowMount(googleMap, {}); + await wrapper.setData({ zipCode }); + + // Act + const result = wrapper.vm.zipCodeAddress; + + // Assert + expect(result).toBe(expected); + } + ); + test('watch addresses creates map with expected markers', async () => { + // Arrange + const wrapper = shallowMount(googleMap, {}); + const zipCode = '00882'; + await wrapper.setData({ zipCode }); + + const mockGetCenter = jest.fn(); + const mockNorthEast = jest.fn(); + const mockSouthWest = jest.fn(); + const zipCodeGeocodeResult = { + results: [ + { + geometry: { + bounds: + { + getCenter: mockGetCenter, + getNorthEast: mockNorthEast, + getSouthWest: mockSouthWest + } + } + } + ] + }; + const [address1, address2, position1, position2] = ['a1', 'a2', 'p1', 'p2']; + mockGeocode.mockImplementation((obj) => { + let result = {}; + if (obj.address.includes(zipCode)) { + result = zipCodeGeocodeResult; + } else if (obj.address === address1) { + result = { + results: [ + { geometry: { location: position1 } } + ] + }; + } else if (obj.address === address2) { + result = { + results: [ + { geometry: { location: position2 } } + ] + }; + } + return Promise.resolve(result); + }); + const newAddresses = [address1, address2]; + + // Act + await wrapper.vm.$options.watch.addresses.call(wrapper.vm, newAddresses); + + for (let i = 0; i < 11; i++) { + // eslint-disable-next-line no-await-in-loop + await wrapper.vm.$nextTick(); + } + + // Assert + expect(mockMap).toBeCalledTimes(1); + expect(mockAdvancedMarkerElement).toBeCalledTimes(2); + expect(mockAdvancedMarkerElement).toBeCalledWith(expect.objectContaining({ + map: expect.anything(), + position: position1 + })); + expect(mockAdvancedMarkerElement).toBeCalledWith(expect.objectContaining({ + map: expect.anything(), + position: position2 + })); + expect(mockGeocoder).toBeCalledTimes(1); + + expect(mockExtend).toBeCalledTimes(4); + expect(mockFitBounds).toBeCalledTimes(1); + + expect(mockGetCenter).toBeCalledTimes(1); + expect(mockNorthEast).toBeCalledTimes(1); + expect(mockSouthWest).toBeCalledTimes(1); + }); + describe('method', () => { + test('getMap calls map constructor with expected parameters', async () => { + // Arrange + const wrapper = shallowMount(googleMap, {}); + const center = { lat: 90, lng: -5 }; + + // Act + const result = await wrapper.vm.getMap(center); + + // Assert + expect(mockMap).toHaveBeenCalledWith( + null, + expect.objectContaining({ center, mapId: 'map_id', mapTypeControl: false }) + ); + expect(result).toBe(mockMapInstance); + }); + test.each([ + [undefined, 0], + [null, 0], + [[], 0], + [['loc1'], 1], + [['loc1', 'loc1', 'loc1', 'loc1'], 4] + ])( + 'given parameter %p, getBounds calls extend %p time(s)', + (locations, numberOfExtendCalls) => { + // Arrange + const wrapper = shallowMount(googleMap, {}); + + // Act + const result = wrapper.vm.getBounds(locations); + + // Assert + expect(mockExtend).toHaveBeenCalledTimes(numberOfExtendCalls); + expect(result).toBe(mockLatLngBound); + } + ); + test.each([ + [1, null, mockGeocoderInstance], + [1, undefined, mockGeocoderInstance], + [0, {}, {}], + [0, { value: 'any value' }, { value: 'any value' }] + ])( + 'setGeocoder calls Geocoder constructor %p time(s) when original geocoder %p and geocoder set to %p', + async (numberOfCalls, originalGeocoder, newGeocoder) => { + // Arrange + const wrapper = shallowMount(googleMap, {}); + await wrapper.setData({ geocoder: originalGeocoder }); + + // Act + await wrapper.vm.setGeocoder(); + + // Assert + expect(mockGeocoder).toHaveBeenCalledTimes(numberOfCalls); + expect(wrapper.vm.geocoder).toEqual(newGeocoder); + } + ); + test.each([ + [null, 0], + [undefined, 0], + [[], 0], + [['p1'], 1], + [['p1', 'p2', 'p3'], 3] + ])( + 'addMarkersToMap given positions argument %p, creates AdvancedMarkerElement %p time(s)', + async (positions, numberOfCalls) => { + // Arrange + const wrapper = shallowMount(googleMap, {}); + const map = jest.fn(); + + // Act + await wrapper.vm.addMarkersToMap(map, positions); + + // Assert + expect(mockAdvancedMarkerElement).toHaveBeenCalledTimes(numberOfCalls); + } + ); + describe('getLocationsFromAddresses', () => { + test.each([ + [null, 0], + [undefined, 0], + [[], 0], + [['a1'], 1], + [['a1', 'a2', 'a3'], 3] + ])( + 'given address(es) %p calls geocode %p times', + async (addresses, numberOfCalls) => { + // Arrange + const wrapper = shallowMount(googleMap, {}); + + // Act + await wrapper.vm.getLocationsFromAddresses(addresses); + + // Assert + expect(mockGeocode).toHaveBeenCalledTimes(numberOfCalls); + expect(wrapper.vm.geocoder).not.toBeNull(); + } + ); + test('only returns geocode results that are not null', async () => { + // Arrange + const wrapper = shallowMount(googleMap, {}); + const addresses = ['a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7', 'a8']; + mockGeocode.mockImplementationOnce(() => null) + .mockImplementationOnce(() => ({ results: null })) + .mockImplementationOnce(() => ({ results: [] })) + .mockImplementationOnce(() => ({ results: [{}] })) + .mockImplementationOnce(() => ( + { results: [{ geometry: undefined }] } + )) + .mockImplementationOnce(() => ( + { results: [{ geometry: {} }] } + )) + .mockImplementationOnce(() => ( + { + results: [ + { geometry: { location: 'some value' } } + ] + } + )) + .mockImplementationOnce(() => ( + { + results: [ + { geometry: { location: 'some other value' } } + ] + } + )); + const expectedResult = ['some value', 'some other value']; + + // Act + const result = await wrapper.vm.getLocationsFromAddresses(addresses); + + // Assert + expect(result.length).toBe(2); + expect(result).toEqual(expectedResult); + }); + }); + describe('getBoundsFromAddress', () => { + test('returns expected when geocode returns valid result', async () => { + // Arrange + const wrapper = shallowMount(googleMap, {}); + const address = 'random address'; + const expectedResult = 'bounds returned'; + const geocodeResult = { + results: [ + { + geometry: { bounds: expectedResult } + } + ] + }; + mockGeocode.mockImplementationOnce(() => Promise.resolve(geocodeResult)); + + // Act + const result = await wrapper.vm.getBoundsFromAddress(address); + + // Assert + expect(result).toBe(expectedResult); + expect(wrapper.vm.geocoder).not.toBeNull(); + }); + test('returns null when geocode returns invalid result', async () => { + // Arrange + const wrapper = shallowMount(googleMap, {}); + const address = 'random address'; + mockGeocode.mockImplementationOnce(() => Promise.resolve({})); + + // Act + const result = await wrapper.vm.getBoundsFromAddress(address); + + // Assert + expect(result).toBeNull(); + expect(wrapper.vm.geocoder).not.toBeNull(); + }); + }); + describe('createMapWithMarkersForAddresses', () => { + test('calls expected when addresses provided', async () => { + // Arrange + const wrapper = shallowMount(googleMap, {}); + const zipCode = '00882'; + await wrapper.setData({ zipCode }); + const mockGetCenter = jest.fn(); + const mockNorthEast = jest.fn(); + const mockSouthWest = jest.fn(); + const zipCodeGeocodeResult = { + results: [ + { + geometry: { + bounds: + { + getCenter: mockGetCenter, + getNorthEast: mockNorthEast, + getSouthWest: mockSouthWest + } + } + } + ] + }; + const shopGeocodeResult = { + results: [ + { geometry: { location: 'valid location' } } + ] + }; + mockGeocode.mockImplementation((obj) => + Promise.resolve(obj.address.includes(zipCode) + ? zipCodeGeocodeResult + : shopGeocodeResult)); + const addresses = ['a1', 'a2']; + + // Act + await wrapper.vm.createMapWithMarkersForAddresses(addresses); + + // Assert + expect(mockMap).toBeCalledTimes(1); + expect(mockAdvancedMarkerElement).toBeCalledTimes(2); + expect(mockGeocoder).toBeCalledTimes(1); + + expect(mockExtend).toBeCalledTimes(4); + expect(mockFitBounds).toBeCalledTimes(1); + + expect(mockGetCenter).toBeCalledTimes(1); + expect(mockNorthEast).toBeCalledTimes(1); + expect(mockSouthWest).toBeCalledTimes(1); + }); + test.each([null, undefined, []])( + 'calls expected when addresses argument is "%p"', + async (addresses) => { + // Arrange + const wrapper = shallowMount(googleMap, {}); + const zipCode = '00882'; + await wrapper.setData({ zipCode }); + const mockGetCenter = jest.fn(); + const mockNorthEast = jest.fn(); + const mockSouthWest = jest.fn(); + const zipCodeGeocodeResult = { + results: [ + { + geometry: { + bounds: + { + getCenter: mockGetCenter, + getNorthEast: mockNorthEast, + getSouthWest: mockSouthWest + } + } + } + ] + }; + mockGeocode.mockImplementation(() => Promise.resolve(zipCodeGeocodeResult)); + + // Act + await wrapper.vm.createMapWithMarkersForAddresses(addresses); + + // Assert + expect(mockMap).toBeCalledTimes(1); + expect(mockAdvancedMarkerElement).toBeCalledTimes(0); + expect(mockGeocoder).toBeCalledTimes(1); + + expect(mockExtend).toBeCalledTimes(2); + expect(mockFitBounds).toBeCalledTimes(1); + + expect(mockGetCenter).toBeCalledTimes(1); + expect(mockNorthEast).toBeCalledTimes(1); + expect(mockSouthWest).toBeCalledTimes(1); + } + ); + }); + }); +}); diff --git a/src/iss-components/google-map/google-map.vue b/src/iss-components/google-map/google-map.vue new file mode 100644 index 00000000..fa5a57d8 --- /dev/null +++ b/src/iss-components/google-map/google-map.vue @@ -0,0 +1,93 @@ + + + + + diff --git a/src/iss-components/shop-list-button/shop-list-button.spec.js b/src/iss-components/shop-list-button/shop-list-button.spec.js index f94e0585..0d29c552 100644 --- a/src/iss-components/shop-list-button/shop-list-button.spec.js +++ b/src/iss-components/shop-list-button/shop-list-button.spec.js @@ -152,7 +152,7 @@ describe('Shop list button', () => { expect(buttonLabelSubCopy.classes().length).toBe(4); expect(buttonLabelSubCopy.classes()).toContain('m-0'); expect(buttonLabelSubCopy.classes()).toContain('caption'); - expect(buttonLabelSubCopy.classes()).toContain('ms-1'); + expect(buttonLabelSubCopy.classes()).toContain('ms-2'); expect(buttonLabelSubCopy.classes()).toContain(textPosition); }); test('when "textPosition" prop not provided', async () => { @@ -176,7 +176,7 @@ describe('Shop list button', () => { expect(buttonLabelSubCopy.classes().length).toBe(3); expect(buttonLabelSubCopy.classes()).toContain('m-0'); expect(buttonLabelSubCopy.classes()).toContain('caption'); - expect(buttonLabelSubCopy.classes()).toContain('ms-1'); + expect(buttonLabelSubCopy.classes()).toContain('ms-2'); }); }); describe('availability indicator block with expected when displayAvailabilityIndicators true', () => { diff --git a/src/iss-components/shop-list-button/shop-list-button.vue b/src/iss-components/shop-list-button/shop-list-button.vue index 5b1b154f..935d11fc 100644 --- a/src/iss-components/shop-list-button/shop-list-button.vue +++ b/src/iss-components/shop-list-button/shop-list-button.vue @@ -17,7 +17,7 @@ {{ buttonLabelSubCopy }}
Promise.resolve()); const wrapper = shallowMount(tpaSearch, mountOptions); - wrapper.vm.getCmsContent = jest.fn().mockImplementation(() => cmsContent); + wrapper.vm.getCmsContent = jest.fn().mockImplementation(() => {}); wrapper.vm.setCmsContent = jest.fn(); return { wrapper }; } @@ -149,16 +149,31 @@ describe('TPA search page', () => { // Assert expect(providerSelectionForm.exists()).toBeTruthy(); }); - test('map', () => { + test('map', async () => { // Arrange - const wrapper = shallowMount(tpaSearch, getMountOptions()); + const zipCode = '12345'; + const { wrapper } = getMountedComponent(); + await wrapper.setData({ + providers: [{ + address: { + streetAddress: '123 Lane Ave', + city: 'New York', + state: 'NY', + zipCode: '99230' + } + }], + mapZipCode: zipCode + }); + const expectedAddresses = ['123 Lane Ave, New York, NY 99230']; // Act - const map = wrapper.find('#map'); + const map = wrapper.findComponent('#map'); // Assert expect(map.exists()).toBeTruthy(); expect(map.classes()).toContain('mb-4'); + expect(map.props().addresses).toEqual(expectedAddresses); + expect(map.props().zipCode).toBe(zipCode); }); test('search radius filter', () => { // Arrange @@ -173,9 +188,32 @@ describe('TPA search page', () => { expect(searchRadiusFilter.props().cmsWidgetName).toBe(expectedWidgetName); expect(searchRadiusFilter.props().validationRules).toBe(globalRules.OPTION_REQUIRED); }); - test('select provider question', () => { + test('select provider question', async () => { // Arrange const wrapper = shallowMount(tpaSearch, getMountOptions()); + await wrapper.setData({ + providers: [{ + name: 'Shop Auto Glass', + phoneNumber: '740-555-1234', + distanceInMiles: 1.2345, + providerNumber: '232209', + address: { + streetAddress: '123 Lane Ave', + city: 'New York', + state: 'NY', + zipCode: '99230' + } + }] + }); + const expectedAnswers = [{ + buttonLabel: 'Shop Auto Glass', + buttonLabelSubCopy: '1.2 mi', + buttonBodyCopy: '123 Lane Ave, New York, NY 99230
740-555-1234', + value: '232209' + }]; + const expectedAdditionalButtonData = { + displayAvailabilityIndicators: false + }; // Act const selectProviderQuestion = wrapper.findComponent('#selectProviderQuestion'); @@ -188,6 +226,8 @@ describe('TPA search page', () => { expect(selectProviderQuestion.props().textPosition).toBe('text-start'); expect(selectProviderQuestion.props().isRequired).toBeTruthy(); expect(selectProviderQuestion.props().validationRules).toBe(globalRules.OPTION_REQUIRED); + expect(selectProviderQuestion.props().answers).toEqual(expectedAnswers); + expect(selectProviderQuestion.props().additionalButtonData).toEqual(expectedAdditionalButtonData); }); test.each([[], null, undefined])( 'no network providers alert when providers length is 0, undefined, or null', @@ -447,12 +487,143 @@ describe('TPA search page', () => { expect(result).toEqual(expected); }); }); + describe('providerAddresses', () => { + test.each([null, undefined, []])( + 'returns empty list when providers empty', + async (providers) => { + // Arrange + const wrapper = shallowMount(tpaSearch, getMountOptions()); + await wrapper.setData({ providers }); + + // Act + const result = wrapper.vm.providerAddresses; + + // Assert + expect(result).toEqual([]); + } + ); + test('returns expected when providers not empty', async () => { + // Arrange + const wrapper = shallowMount(tpaSearch, getMountOptions()); + await wrapper.setData({ + providers: [ + { + name: 'Shop Auto Glass', + phoneNumber: '740-555-1234', + distanceInMiles: 1.2345, + providerNumber: '232209', + address: { + streetAddress: '123 Lane Ave', + city: 'New York', + state: 'NY', + zipCode: '99230' + } + }, + { + name: 'Safelite AutoGlass', + phoneNumber: '676-555-0099', + distanceInMiles: 123.9452, + providerNumber: '123456', + address: { + streetAddress: '2003 Coral Ln', + city: 'Birmingham', + state: 'AL', + zipCode: '43001' + } + } + ] + }); + const expectedResult = [ + '123 Lane Ave, New York, NY 99230', + '2003 Coral Ln, Birmingham, AL 43001' + ]; + + // Act + const result = wrapper.vm.providerAddresses; + + // Assert + expect(result).toEqual(expectedResult); + }); + }); + describe('providerButtonData', () => { + test.each([null, undefined, []])( + 'returns empty list when providers empty', + async (providers) => { + // Arrange + const wrapper = shallowMount(tpaSearch, getMountOptions()); + await wrapper.setData({ providers }); + + // Act + const result = wrapper.vm.providerButtonData; + + // Assert + expect(result).toEqual([]); + } + ); + test('returns expected when providers not empty', async () => { + // Arrange + const wrapper = shallowMount(tpaSearch, getMountOptions()); + await wrapper.setData({ + providers: [ + { + name: 'Shop Auto Glass', + phoneNumber: '740-555-1234', + distanceInMiles: 1.2345, + providerNumber: '232209', + address: { + streetAddress: '123 Lane Ave', + city: 'New York', + state: 'NY', + zipCode: '99230' + } + }, + { + name: 'Safelite AutoGlass', + phoneNumber: '676-555-0099', + distanceInMiles: 123.9452, + providerNumber: '123456', + address: { + streetAddress: '2003 Coral Ln', + city: 'Birmingham', + state: 'AL', + zipCode: '43001' + } + } + ] + }); + const expectedResult = [ + { + buttonLabel: 'Shop Auto Glass', + buttonLabelSubCopy: '1.2 mi', + buttonBodyCopy: '123 Lane Ave, New York, NY 99230
740-555-1234', + value: '232209' + }, + { + buttonLabel: 'Safelite AutoGlass', + buttonLabelSubCopy: '123.9 mi', + buttonBodyCopy: '2003 Coral Ln, Birmingham, AL 43001
676-555-0099', + value: '123456' + } + ]; + + // Act + const result = wrapper.vm.providerButtonData; + + // Assert + expect(result).toEqual(expectedResult); + }); + }); }); describe('watch', () => { test('on filter calls getTpaProviders and sets providers', async () => { // Arrange - const { wrapper } = getMountedComponent(); - const providers = { data: ['some data', 'some more data'] }; + const zipCode = '78220'; + const initialMapZipCode = '88800'; + const { wrapper } = getMountedComponent({}, { + zipCode, + mapZipCode: initialMapZipCode + }); + const providers = { data: { shopProviders: ['some data', 'some more data']} }; useMainStore().getTpaProviders = jest.fn().mockImplementationOnce(() => (providers)); const newFilter = 'new filter'; @@ -461,7 +632,8 @@ describe('TPA search page', () => { // Assert expect(useMainStore().getTpaProviders).toBeCalled(); - expect(wrapper.vm.providers).toEqual(providers.data); + expect(wrapper.vm.providers).toEqual(providers.data.shopProviders); + expect(wrapper.vm.mapZipCode).toBe(zipCode); }); describe('on providers', () => { test.each([null, undefined, []])( @@ -481,7 +653,7 @@ describe('TPA search page', () => { // Arrange const { wrapper } = getMountedComponent(); const value = 'some value'; - const newProviders = [{ value }]; + const newProviders = [{ providerNumber: value }]; // Act wrapper.vm.$options.watch.providers.call(wrapper.vm, newProviders); @@ -503,7 +675,35 @@ describe('TPA search page', () => { }); }); describe('method', () => { - describe('getProviders', () => { + test.each([ + ['address', 'city', 'ST', 'zip', 'address, city, ST zip'], + ['address', null, 'ST', 'zip', 'address, ST zip'], + ['address', 'city', null, 'zip', 'address, city zip'], + ['address', 'city', 'ST', null, 'address, city, ST'], + [null, null, null, null, ''] + ])( + 'getProviderAddress returns expected', + (streetAddress, city, state, zipCode, expected) => { + // Arrange + const { wrapper } = getMountedComponent(); + useMainStore().getTpaProviders = jest.fn().mockImplementationOnce(() => (newProviders)); + const provider = { + address: { + streetAddress, + city, + state, + zipCode + } + }; + + // Act + const result = wrapper.vm.getProviderAddress(provider); + + // Assert + expect(result).toEqual(expected); + } + ); + describe('getProviderButtonData', () => { test.each([null, undefined, {}])( 'returns empty list when getTpaProviders returns no data', async (newProviders) => { @@ -512,7 +712,7 @@ describe('TPA search page', () => { useMainStore().getTpaProviders = jest.fn().mockImplementationOnce(() => (newProviders)); // Act - const result = await wrapper.vm.getProviders(); + const result = await wrapper.vm.getProviderButtonData(); // Assert expect(result).toEqual([]); @@ -521,14 +721,18 @@ describe('TPA search page', () => { test('returns expected when getTpaProviders returns data', async () => { // Arrange const { wrapper } = getMountedComponent(); - const providers = { data: [{ name: 'foo' }] }; + const providers = { + data: { + shopProviders: [{ name: 'foo' }] + } + }; useMainStore().getTpaProviders = jest.fn().mockImplementationOnce(() => (providers)); // Act - const result = await wrapper.vm.getProviders(); + const result = await wrapper.vm.getProviderButtonData(); // Assert - expect(result).toEqual(providers.data); + expect(result).toEqual(providers.data.shopProviders); }); }); test('doNotSeeMyShopLinkClick invokes navigate method', () => { @@ -546,7 +750,12 @@ describe('TPA search page', () => { 'sets providers to empty list when no data returned from getProviders', async (newProviders) => { // Arrange - const { wrapper } = getMountedComponent(); + const zipCode = '28331'; + const initialMapZipCode = '10022'; + const { wrapper } = getMountedComponent({}, { + zipCode, + mapZipCode: initialMapZipCode + }); useMainStore().getTpaProviders = jest.fn().mockImplementationOnce(() => (newProviders)); // Act @@ -554,19 +763,28 @@ describe('TPA search page', () => { // Assert expect(wrapper.vm.providers).toEqual([]); + expect(wrapper.vm.mapZipCode).toEqual(zipCode); } ); test('returns value from getProviders', async () => { // Arrange - const { wrapper } = getMountedComponent(); - const newProviders = { data: [{ name: 'foo' }] }; - useMainStore().getTpaProviders = jest.fn().mockImplementationOnce(() => (newProviders)); + const zipCode = '12990'; + const initialMapZipCode = '00981'; + const { wrapper } = getMountedComponent({}, { + zipCode, + mapZipCode: initialMapZipCode + }); + const shopProviders = [{ name: 'foo' }]; + useMainStore().getTpaProviders = jest.fn().mockImplementationOnce(() => ({ + data: { shopProviders } + })); // Act await wrapper.vm.searchClick(); // Assert - expect(wrapper.vm.providers).toEqual(newProviders.data); + expect(wrapper.vm.providers).toEqual(shopProviders); + expect(wrapper.vm.mapZipCode).toEqual(zipCode); }); }); test('backButtonAction invokes navigate method', () => { @@ -613,32 +831,162 @@ describe('TPA search page', () => { expect(result).toBeNull(); }); }); - test.each([undefined, null, 'some value', ''])( - 'setFilter sets value of filter', - (filter) => { + describe('getShopButtonDataFromProvider', () => { + test.each([null, undefined, {}])( + 'returns empty list given parameter with no provider', + (provider) => { + // Arrange + const wrapper = shallowMount(tpaSearch, getMountOptions()); + const expectedResult = { + buttonLabel: '', + buttonLabelSubCopy: '', + buttonBodyCopy: '
', + value: '' + }; + + // Act + const result = wrapper.vm.getShopButtonDataFromProvider(provider); + + // Assert + expect(result).toEqual(expectedResult); + } + ); + test('returns expected given provider', () => { // Arrange - const { wrapper } = getMountedComponent(); + const wrapper = shallowMount(tpaSearch, getMountOptions()); + const provider = { + name: 'Shop Auto Glass', + phoneNumber: '740-555-1234', + distanceInMiles: 1.2345, + providerNumber: '232209', + address: { + streetAddress: '123 Lane Ave', + city: 'New York', + state: 'NY', + zipCode: '99230' + } + }; + const expectedResult = { + buttonLabel: 'Shop Auto Glass', + buttonLabelSubCopy: '1.2 mi', + buttonBodyCopy: '123 Lane Ave, New York, NY 99230
740-555-1234', + value: '232209' + }; // Act - wrapper.vm.setFilter(filter); + const result = wrapper.vm.getShopButtonDataFromProvider(provider); // Assert - expect(wrapper.vm.filter).toEqual(filter); - } - ); - test.each([undefined, null, ['some value'], []])( - 'setProviders sets value of providers', - (providers) => { + expect(result).toEqual(expectedResult); + }); + describe('returns result with button label', () => { + test.each(['', undefined, null])( + 'equal to empty string when name is empty', + (name) => { + // Arrange + const wrapper = shallowMount(tpaSearch, getMountOptions()); + const provider = { name }; + + // Act + const result = wrapper.vm.getShopButtonDataFromProvider(provider); + + // Assert + expect(result.buttonLabel).toEqual(''); + } + ); + test('returns provider name when exists', () => { + // Arrange + const wrapper = shallowMount(tpaSearch, getMountOptions()); + const name = 'Some Shop Name'; + const provider = { name }; + + // Act + const result = wrapper.vm.getShopButtonDataFromProvider(provider); + + // Assert + expect(result.buttonLabel).toEqual(name); + }); + }); + describe('returns result with button label sub copy', () => { + test.each([null, undefined, 'random string'])( + 'as empty string when distanceInMiles is not a number', + (distanceInMiles) => { + // Arrange + const wrapper = shallowMount(tpaSearch, getMountOptions()); + const provider = { distanceInMiles }; + + // Act + const result = wrapper.vm.getShopButtonDataFromProvider(provider); + + // Assert + expect(result.buttonLabelSubCopy).toEqual(''); + } + ); + test.each([[0, 0], [0, 0.000], [-7.89, -7.9], [109, 109], [1.234, 1.2], [2.99, 3]])( + 'displays expected distance value', + (distanceInMiles, displayedDistance) => { + // Arrange + const wrapper = shallowMount(tpaSearch, getMountOptions()); + const provider = { distanceInMiles }; + const expectedSubCopy = `${displayedDistance} mi`; + + // Act + const result = wrapper.vm.getShopButtonDataFromProvider(provider); + + // Assert + expect(result.buttonLabelSubCopy).toEqual(expectedSubCopy); + } + ); + }); + test('returns result with button body copy showing expected address and phone number', () => { // Arrange - const { wrapper } = getMountedComponent(); + const wrapper = shallowMount(tpaSearch, getMountOptions()); + const provider = { + phoneNumber: '123-555-0099', + address: { + streetAddress: '434 Up Down Blvd', + city: 'San Francisco', + state: 'CA', + zipCode: '90887' + } + }; + const expected = '434 Up Down Blvd, San Francisco, CA 90887
123-555-0099'; // Act - wrapper.vm.setProviders(providers); + const result = wrapper.vm.getShopButtonDataFromProvider(provider); // Assert - expect(wrapper.vm.providers).toEqual(providers); - } - ); + expect(result.buttonBodyCopy).toEqual(expected); + }); + describe('returns result with value', () => { + test.each(['', undefined, null])( + 'equal to empty string when providerNumber is empty', + (providerNumber) => { + // Arrange + const wrapper = shallowMount(tpaSearch, getMountOptions()); + const provider = { providerNumber }; + + // Act + const result = wrapper.vm.getShopButtonDataFromProvider(provider); + + // Assert + expect(result.value).toEqual(''); + } + ); + test('returns provider number when exists', () => { + // Arrange + const wrapper = shallowMount(tpaSearch, getMountOptions()); + const providerNumber = '123391'; + const provider = { providerNumber }; + + // Act + const result = wrapper.vm.getShopButtonDataFromProvider(provider); + + // Assert + expect(result.value).toEqual(providerNumber); + }); + }); + }); }); describe('before route enter', () => { test('when providers exist at 25 mile radius, filter is set to "25 miles" and providers set to expected', async () => { @@ -646,8 +994,9 @@ describe('TPA search page', () => { const { wrapper } = getMountedComponent(); const zipCode = '18394'; useMainStore().order.customer.address.zipCode = zipCode; - const providers = { data: [{ name: 'provider' }] }; - useMainStore().getTpaProviders = jest.fn().mockImplementation((_, radius) => (radius === 25 ? providers : [])); + const providers = [{ name: 'provider' }]; + useMainStore().getTpaProviders = jest.fn().mockImplementation((_, radius) => + ({ data: { shopProviders: radius === 25 ? providers : [] } })); const expectedFilter = '25 miles'; // Act @@ -658,6 +1007,9 @@ describe('TPA search page', () => { (c) => c(wrapper.vm) ); + await wrapper.vm.$nextTick(); + await wrapper.vm.$nextTick(); + // Assert expect(wrapper.vm.filter).toBe(expectedFilter); expect(wrapper.vm.providers).toEqual(providers); @@ -667,8 +1019,9 @@ describe('TPA search page', () => { const { wrapper } = getMountedComponent(); const zipCode = '18394'; useMainStore().order.customer.address.zipCode = zipCode; - const providers = { data: [{ name: 'provider' }] }; - useMainStore().getTpaProviders = jest.fn().mockImplementation((_, radius) => (radius === 50 ? providers : [])); + const providers = [{ name: 'provider' }]; + useMainStore().getTpaProviders = jest.fn().mockImplementation((_, radius) => + ({ data: { shopProviders: radius === 50 ? providers : [] } })); const expectedFilter = '50 miles'; // Act @@ -679,6 +1032,9 @@ describe('TPA search page', () => { (c) => c(wrapper.vm) ); + await wrapper.vm.$nextTick(); + await wrapper.vm.$nextTick(); + // Assert expect(wrapper.vm.filter).toBe(expectedFilter); expect(wrapper.vm.providers).toEqual(providers); @@ -690,8 +1046,9 @@ describe('TPA search page', () => { const { wrapper } = getMountedComponent(); const zipCode = '18394'; useMainStore().order.customer.address.zipCode = zipCode; - const providers = { data: [{ name: 'provider' }] }; - useMainStore().getTpaProviders = jest.fn().mockImplementation((_, radius) => (radius === 100 ? providers : [])); + const providers = [{ name: 'provider' }]; + useMainStore().getTpaProviders = jest.fn().mockImplementation((_, radius) => + ({ data: { shopProviders: radius === 100 ? providers : [] } })); const expectedFilter = '100 miles'; // Act @@ -702,6 +1059,9 @@ describe('TPA search page', () => { (c) => c(wrapper.vm) ); + await wrapper.vm.$nextTick(); + await wrapper.vm.$nextTick(); + // Assert expect(wrapper.vm.filter).toBe(expectedFilter); expect(wrapper.vm.providers).toEqual(providers); @@ -725,6 +1085,9 @@ describe('TPA search page', () => { (c) => c(wrapper.vm) ); + await wrapper.vm.$nextTick(); + await wrapper.vm.$nextTick(); + // Assert expect(wrapper.vm.filter).toBe(expectedFilter); expect(wrapper.vm.providers).toEqual([]); diff --git a/src/layouts/tpa-search/tpa-search.vue b/src/layouts/tpa-search/tpa-search.vue index 817c0223..29fce69c 100644 --- a/src/layouts/tpa-search/tpa-search.vue +++ b/src/layouts/tpa-search/tpa-search.vue @@ -1,5 +1,6 @@