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 efab5bbf..74a1d74d 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 @@ -3,6 +3,22 @@ exports[`Google Map should render expected initial data 1`] = ` Object { "country": "USA", - "geocoder": null, + "geocoder": Object { + "geocode": [MockFunction] { + "calls": Array [ + Array [ + Object { + "address": " USA", + }, + ], + ], + "results": Array [ + Object { + "type": "return", + "value": undefined, + }, + ], + }, + }, } `; diff --git a/src/iss-components/google-map/google-map.spec.js b/src/iss-components/google-map/google-map.spec.js index 2c4ad709..b26d36a2 100644 --- a/src/iss-components/google-map/google-map.spec.js +++ b/src/iss-components/google-map/google-map.spec.js @@ -1,6 +1,11 @@ import { shallowMount } from '@vue/test-utils'; +import { createTestingPinia } from '@pinia/testing'; import googleMap from '@/iss-components/google-map/google-map.vue'; +// Supporting Files +import { getMountOptions } from '@/helpers/unit-test-helper.js'; +import { useMainStore } from '@/store'; + const mockFitBounds = jest.fn(); const mockMapInstance = { fitBounds: mockFitBounds @@ -8,10 +13,13 @@ const mockMapInstance = { const mockMap = jest.fn(() => mockMapInstance); const mockGeocode = jest.fn(); -const mockGeocoderInstance = { +const mockGeocoderInstance1 = { geocode: mockGeocode }; -const mockGeocoder = jest.fn(() => (mockGeocoderInstance)); +const mockGeocoderInstance2 = { + geocode: mockGeocode +}; +const mockGeocoder = jest.fn(() => (mockGeocoderInstance1)); const mockAdvancedMarkerElement = jest.fn(); @@ -47,20 +55,52 @@ beforeEach(() => { setupGoogleMock(); }); +function getMountedComponent(mainInitialState = {}, initialData = {}, propsData = {}) { + const mountOptions = getMountOptions({ + router: { + navigate: jest.fn() + } + }); + + const testingPinia = createTestingPinia({ + initialState: { + main: mainInitialState + } + }); + useMainStore(testingPinia); + + mountOptions.global.plugins = [testingPinia]; + mountOptions.data = () => (initialData); + mountOptions.propsData = propsData; + + const wrapper = shallowMount(googleMap, mountOptions); + wrapper.vm.getCmsContent = jest.fn().mockImplementation(() => {}); + wrapper.vm.setCmsContent = jest.fn(); + return { wrapper }; +} + +async function awaitingSetupTicks(wrapper) { + for (let i = 0; i < 11; i++) { + // eslint-disable-next-line no-await-in-loop + await wrapper.vm.$nextTick(); + } +} + describe('Google Map', () => { describe('should render', () => { test('expected initial data', async () => { // Arrange const wrapper = shallowMount(googleMap, {}); - await wrapper.vm.$nextTick(); + await awaitingSetupTicks(wrapper); // Assert expect(wrapper.vm.$data).toMatchSnapshot(); }); - test('map', () => { + test('map', async () => { // Arrange const mapReference = '#map'; const wrapper = shallowMount(googleMap, {}); + await awaitingSetupTicks(wrapper); // Act const map = wrapper.find(mapReference); @@ -78,8 +118,8 @@ describe('Google Map', () => { 'given that zipcode set to "%p", computed zipCodeAddress should return "%p"', async (zipCode, expected) => { // Arrange - const wrapper = shallowMount(googleMap, {}); - await wrapper.setData({ zipCode }); + const { wrapper } = getMountedComponent({}, {}, { zipCode }); + await awaitingSetupTicks(wrapper); // Act const result = wrapper.vm.zipCodeAddress; @@ -88,11 +128,78 @@ describe('Google Map', () => { expect(result).toBe(expected); } ); + test('before mount addresses creates map with expected markers', async () => { + // Arrange + const mockGetCenter = jest.fn(); + const mockNorthEast = jest.fn(); + const mockSouthWest = jest.fn(); + const zipCodeGeocodeResult = { + results: [ + { + geometry: { + bounds: + { + getCenter: mockGetCenter, + getNorthEast: mockNorthEast, + getSouthWest: mockSouthWest + } + } + } + ] + }; + + const zipCode = '00882'; + const [address1, address2, position1, position2] = ['a1', 'a2', 'p1', 'p2']; + const addresses = [address1, address2]; + 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 propsData = { addresses, zipCode }; + const { wrapper } = getMountedComponent({}, {}, propsData); + await awaitingSetupTicks(wrapper); + + // 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); + }); test('watch addresses creates map with expected markers', async () => { // Arrange - const wrapper = shallowMount(googleMap, {}); const zipCode = '00882'; - await wrapper.setData({ zipCode }); + const { wrapper } = getMountedComponent({}, { zipCode }); + await awaitingSetupTicks(wrapper); const mockGetCenter = jest.fn(); const mockNorthEast = jest.fn(); @@ -135,14 +242,10 @@ describe('Google Map', () => { // 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(); - } + await awaitingSetupTicks(wrapper); // Assert - expect(mockMap).toBeCalledTimes(1); + expect(mockMap).toBeCalledTimes(2); expect(mockAdvancedMarkerElement).toBeCalledTimes(2); expect(mockAdvancedMarkerElement).toBeCalledWith(expect.objectContaining({ map: expect.anything(), @@ -154,8 +257,8 @@ describe('Google Map', () => { })); expect(mockGeocoder).toBeCalledTimes(1); - expect(mockExtend).toBeCalledTimes(4); - expect(mockFitBounds).toBeCalledTimes(1); + expect(mockExtend).toBeCalledTimes(6); + expect(mockFitBounds).toBeCalledTimes(2); expect(mockGetCenter).toBeCalledTimes(1); expect(mockNorthEast).toBeCalledTimes(1); @@ -165,6 +268,7 @@ describe('Google Map', () => { test('getMap calls map constructor with expected parameters', async () => { // Arrange const wrapper = shallowMount(googleMap, {}); + await awaitingSetupTicks(wrapper); const center = { lat: 90, lng: -5 }; // Act @@ -185,32 +289,35 @@ describe('Google Map', () => { [['loc1', 'loc1', 'loc1', 'loc1'], 4] ])( 'given parameter %p, getBounds calls extend %p time(s)', - (locations, numberOfExtendCalls) => { + async (locations, numberOfExtendCallsPostMount) => { // Arrange const wrapper = shallowMount(googleMap, {}); + await awaitingSetupTicks(wrapper); + const numberOfExtendCallsDuringMount = 2; // Act const result = wrapper.vm.getBounds(locations); // Assert - expect(mockExtend).toHaveBeenCalledTimes(numberOfExtendCalls); + expect(mockExtend).toHaveBeenCalledTimes( + numberOfExtendCallsDuringMount + numberOfExtendCallsPostMount); expect(result).toBe(mockLatLngBound); } ); test.each([ - [1, null, mockGeocoderInstance], - [1, undefined, mockGeocoderInstance], - [0, {}, {}], - [0, { value: 'any value' }, { value: 'any value' }] + [1, null, mockGeocoderInstance1], + [1, undefined, mockGeocoderInstance1], + [0, mockGeocoderInstance2, mockGeocoderInstance2] ])( '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 }); + const { wrapper } = getMountedComponent({}, { geocoder: originalGeocoder }, {}); + await awaitingSetupTicks(wrapper); // Act await wrapper.vm.setGeocoder(); + await wrapper.vm.$nextTick(); // Assert expect(mockGeocoder).toHaveBeenCalledTimes(numberOfCalls); @@ -228,6 +335,7 @@ describe('Google Map', () => { async (positions, numberOfCalls) => { // Arrange const wrapper = shallowMount(googleMap, {}); + await awaitingSetupTicks(wrapper); const map = jest.fn(); // Act @@ -246,21 +354,26 @@ describe('Google Map', () => { [['a1', 'a2', 'a3'], 3] ])( 'given address(es) %p calls geocode %p times', - async (addresses, numberOfCalls) => { + async (addresses, numberOfCallsPostMount) => { // Arrange const wrapper = shallowMount(googleMap, {}); + await awaitingSetupTicks(wrapper); + const numberOfCallsFromMount = 1; + expect(mockGeocode).toHaveBeenCalledTimes(numberOfCallsFromMount); // Act await wrapper.vm.getLocationsFromAddresses(addresses); + await wrapper.vm.$nextTick(); // Assert - expect(mockGeocode).toHaveBeenCalledTimes(numberOfCalls); + expect(mockGeocode).toHaveBeenCalledTimes(numberOfCallsFromMount + numberOfCallsPostMount); expect(wrapper.vm.geocoder).not.toBeNull(); } ); test('only returns geocode results that are not null', async () => { // Arrange const wrapper = shallowMount(googleMap, {}); + await awaitingSetupTicks(wrapper); const addresses = ['a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7', 'a8']; mockGeocode.mockImplementationOnce(() => null) .mockImplementationOnce(() => ({ results: null })) @@ -300,6 +413,7 @@ describe('Google Map', () => { test('returns expected when geocode returns valid result', async () => { // Arrange const wrapper = shallowMount(googleMap, {}); + await awaitingSetupTicks(wrapper); const address = 'random address'; const expectedResult = 'bounds returned'; const geocodeResult = { @@ -321,6 +435,7 @@ describe('Google Map', () => { test('returns null when geocode returns invalid result', async () => { // Arrange const wrapper = shallowMount(googleMap, {}); + await awaitingSetupTicks(wrapper); const address = 'random address'; mockGeocode.mockImplementationOnce(() => Promise.resolve({})); @@ -335,9 +450,6 @@ describe('Google Map', () => { 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(); @@ -360,34 +472,35 @@ describe('Google Map', () => { { geometry: { location: 'valid location' } } ] }; + const zipCode = '00882'; mockGeocode.mockImplementation((obj) => Promise.resolve(obj.address.includes(zipCode) ? zipCodeGeocodeResult : shopGeocodeResult)); const addresses = ['a1', 'a2']; + const { wrapper } = getMountedComponent({}, { zipCode }); + await awaitingSetupTicks(wrapper); // Act await wrapper.vm.createMapWithMarkersForAddresses(addresses); + await awaitingSetupTicks(wrapper); // Assert - expect(mockMap).toBeCalledTimes(1); + expect(mockMap).toBeCalledTimes(2); expect(mockAdvancedMarkerElement).toBeCalledTimes(2); expect(mockGeocoder).toBeCalledTimes(1); - expect(mockExtend).toBeCalledTimes(4); - expect(mockFitBounds).toBeCalledTimes(1); + expect(mockExtend).toBeCalledTimes(6); + expect(mockFitBounds).toBeCalledTimes(2); - expect(mockGetCenter).toBeCalledTimes(1); - expect(mockNorthEast).toBeCalledTimes(1); - expect(mockSouthWest).toBeCalledTimes(1); + expect(mockGetCenter).toBeCalledTimes(2); + expect(mockNorthEast).toBeCalledTimes(2); + expect(mockSouthWest).toBeCalledTimes(2); }); 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(); @@ -406,21 +519,25 @@ describe('Google Map', () => { ] }; mockGeocode.mockImplementation(() => Promise.resolve(zipCodeGeocodeResult)); + const zipCode = '00882'; + const { wrapper } = getMountedComponent({}, { zipCode }); + await awaitingSetupTicks(wrapper); // Act await wrapper.vm.createMapWithMarkersForAddresses(addresses); + await awaitingSetupTicks(wrapper); // Assert - expect(mockMap).toBeCalledTimes(1); + expect(mockMap).toBeCalledTimes(2); expect(mockAdvancedMarkerElement).toBeCalledTimes(0); expect(mockGeocoder).toBeCalledTimes(1); - expect(mockExtend).toBeCalledTimes(2); - expect(mockFitBounds).toBeCalledTimes(1); + expect(mockExtend).toBeCalledTimes(4); + expect(mockFitBounds).toBeCalledTimes(2); - expect(mockGetCenter).toBeCalledTimes(1); - expect(mockNorthEast).toBeCalledTimes(1); - expect(mockSouthWest).toBeCalledTimes(1); + expect(mockGetCenter).toBeCalledTimes(2); + expect(mockNorthEast).toBeCalledTimes(2); + expect(mockSouthWest).toBeCalledTimes(2); } ); }); diff --git a/src/iss-components/google-map/google-map.vue b/src/iss-components/google-map/google-map.vue index 4e5ba28f..0c901dec 100644 --- a/src/iss-components/google-map/google-map.vue +++ b/src/iss-components/google-map/google-map.vue @@ -75,13 +75,13 @@ export default { async createMapWithMarkersForAddresses(addresses) { const shopPositions = await this.getLocationsFromAddresses(addresses); const zipBounds = await this.getBoundsFromAddress(this.zipCodeAddress); - const map = await this.getMap(zipBounds.getCenter()); + const map = await this.getMap(zipBounds?.getCenter()); await this.addMarkersToMap(map, shopPositions); - const positionsToDisplay = shopPositions.concat(zipBounds.getNorthEast(), zipBounds.getSouthWest()); + const positionsToDisplay = shopPositions.concat(zipBounds?.getNorthEast(), zipBounds?.getSouthWest()); const bounds = this.getBounds(positionsToDisplay); - map.fitBounds(bounds); + map?.fitBounds(bounds); } } };