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 index b4ee066e..efab5bbf 100644 --- a/src/iss-components/google-map/__snapshots__/google-map.spec.js.snap +++ b/src/iss-components/google-map/__snapshots__/google-map.spec.js.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Google Maps should render expected initial data 1`] = ` +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 index fbaee73a..2c4ad709 100644 --- a/src/iss-components/google-map/google-map.spec.js +++ b/src/iss-components/google-map/google-map.spec.js @@ -1,7 +1,53 @@ import { shallowMount } from '@vue/test-utils'; import googleMap from '@/iss-components/google-map/google-map.vue'; -describe('Google Maps', () => { +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 @@ -15,7 +61,6 @@ describe('Google Maps', () => { // Arrange const mapReference = '#map'; const wrapper = shallowMount(googleMap, {}); - // await wrapper.vm.$nextTick(); // Act const map = wrapper.find(mapReference); @@ -24,4 +69,360 @@ describe('Google Maps', () => { 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 index 76d8af52..fa5a57d8 100644 --- a/src/iss-components/google-map/google-map.vue +++ b/src/iss-components/google-map/google-map.vue @@ -7,12 +7,10 @@