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 }; const mockMap = jest.fn(() => mockMapInstance); const mockGeocode = jest.fn(); const mockGeocoderInstance1 = { geocode: mockGeocode }; const mockGeocoderInstance2 = { geocode: mockGeocode }; const mockGeocoder = jest.fn(() => (mockGeocoderInstance1)); const mockAdvancedMarkerInstance = { addListener: jest.fn() }; const mockAdvancedMarkerElement = jest.fn(() => (mockAdvancedMarkerInstance)); const mockPinElement = jest.fn(); const mockInfoWindow = jest.fn(); const mockImportLibrary = jest.fn().mockImplementation(() => (Promise.resolve({ Map: mockMap, Geocoder: mockGeocoder, AdvancedMarkerElement: mockAdvancedMarkerElement, PinElement: mockPinElement }))); const mockExtend = jest.fn(); const mockLatLngBound = { extend: mockExtend }; const setupGoogleMock = () => { global.window.google = { maps: { importLibrary: mockImportLibrary, LatLngBounds: jest.fn(() => (mockLatLngBound)), InfoWindow: mockInfoWindow } }; }; beforeEach(() => { // clear constructors mockMap.mockClear(); mockGeocoder.mockClear(); mockAdvancedMarkerElement.mockClear(); mockPinElement.mockClear(); // clear methods mockExtend.mockClear(); mockGeocode.mockClear(); mockFitBounds.mockClear(); 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 awaitingSetupTicks(wrapper); // Assert expect(wrapper.vm.$data).toMatchSnapshot(); }); test('map', async () => { // Arrange const mapReference = '#map'; const wrapper = shallowMount(googleMap, {}); await awaitingSetupTicks(wrapper); // 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 } = getMountedComponent({}, {}, { zipCode }); await awaitingSetupTicks(wrapper); // Act const result = wrapper.vm.zipCodeAddress; // Assert 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 markers = [ { fullAddress: address1 }, { fullAddress: 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 = { markers, 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 zipCode = '00882'; 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 markers = [ { fullAddress: address1 }, { fullAddress: address2 }]; // Act const { wrapper } = getMountedComponent({}, { zipCode }); await awaitingSetupTicks(wrapper); await wrapper.vm.$options.watch.markers.call(wrapper.vm, markers); await awaitingSetupTicks(wrapper); // Assert expect(mockMap).toBeCalledTimes(2); 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(6); expect(mockFitBounds).toBeCalledTimes(2); expect(mockGetCenter).toBeCalledTimes(2); expect(mockNorthEast).toBeCalledTimes(2); expect(mockSouthWest).toBeCalledTimes(2); }); describe('method', () => { test('getMap calls map constructor with expected parameters', async () => { // Arrange const wrapper = shallowMount(googleMap, {}); await awaitingSetupTicks(wrapper); 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)', async (locations, numberOfExtendCallsPostMount) => { // Arrange const wrapper = shallowMount(googleMap, {}); await awaitingSetupTicks(wrapper); mockGeocode.mockImplementation(() => { const result = { results: [ { geometry: { location: 'p1' } } ] }; return Promise.resolve(result); }); // Act const result = wrapper.vm.getBounds(locations); // Assert expect(mockExtend).toHaveBeenCalledTimes(numberOfExtendCallsPostMount); expect(result).toBe(mockLatLngBound); } ); test.each([ [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 } = getMountedComponent({}, { geocoder: originalGeocoder }, {}); await awaitingSetupTicks(wrapper); // Act await wrapper.vm.setGeocoder(); await wrapper.vm.$nextTick(); // 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, {}); await awaitingSetupTicks(wrapper); 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, numberOfCallsPostMount) => { // Arrange const wrapper = shallowMount(googleMap, {}); await awaitingSetupTicks(wrapper); const numberOfCallsFromMount = 1; expect(mockGeocode).toHaveBeenCalledTimes(numberOfCallsFromMount); // Act await wrapper.vm.getLocationsFromAddresses(addresses?.map((a) => ({ fullAddress: a }))); await wrapper.vm.$nextTick(); // Assert 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'].map((a) => ({ fullAddress: a })); 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)).map((a) => a.position); // 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, {}); await awaitingSetupTicks(wrapper); 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, {}); await awaitingSetupTicks(wrapper); 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 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' } } ] }; const zipCode = '00882'; mockGeocode.mockImplementation((obj) => Promise.resolve(obj.address.includes(zipCode) ? zipCodeGeocodeResult : shopGeocodeResult)); const addresses = [{ fullAddress: 'a1' }, { fullAddress: 'a2' }]; const { wrapper } = getMountedComponent({}, { zipCode }); await awaitingSetupTicks(wrapper); // Act await wrapper.vm.createMapWithMarkersForAddresses(addresses); await awaitingSetupTicks(wrapper); // Assert expect(mockMap).toBeCalledTimes(2); expect(mockAdvancedMarkerElement).toBeCalledTimes(2); expect(mockGeocoder).toBeCalledTimes(1); expect(mockExtend).toBeCalledTimes(6); expect(mockFitBounds).toBeCalledTimes(2); 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 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)); const zipCode = '00882'; const { wrapper } = getMountedComponent({}, { zipCode }); await awaitingSetupTicks(wrapper); // Act await wrapper.vm.createMapWithMarkersForAddresses(addresses); await awaitingSetupTicks(wrapper); // Assert expect(mockMap).toBeCalledTimes(2); expect(mockAdvancedMarkerElement).toBeCalledTimes(0); expect(mockGeocoder).toBeCalledTimes(1); expect(mockExtend).toBeCalledTimes(4); expect(mockFitBounds).toBeCalledTimes(2); expect(mockGetCenter).toBeCalledTimes(2); expect(mockNorthEast).toBeCalledTimes(2); expect(mockSouthWest).toBeCalledTimes(2); } ); }); }); });