-
Notifications
You must be signed in to change notification settings - Fork 125
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(advanced-marker): apply marker class when rendering a Pin (#384)
Previously, the AdvancedMarker would only apply the className prop to the content when custom html contents were specified, but there are some cases (applying css-animations to markers for example) where we also want the className to apply to markers that just contain a PinElement. This also contains a rewrite and extension of the AdvancedMarker tests.
- Loading branch information
1 parent
d608602
commit e8a4cc3
Showing
4 changed files
with
128 additions
and
61 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,100 +1,160 @@ | ||
import React, {JSX} from 'react'; | ||
import React from 'react'; | ||
|
||
import {initialize, mockInstances} from '@googlemaps/jest-mocks'; | ||
import {cleanup, render} from '@testing-library/react'; | ||
import {cleanup, queryByTestId, render} from '@testing-library/react'; | ||
import '@testing-library/jest-dom'; | ||
|
||
import {AdvancedMarker} from '../advanced-marker'; | ||
import {useMap} from '../../hooks/use-map'; | ||
import {useMapsLibrary} from '../../hooks/use-maps-library'; | ||
|
||
import {APIProvider} from '../api-provider'; | ||
import {Map as GoogleMap} from '../map'; | ||
import {AdvancedMarker as GoogleMapsMarker} from '../advanced-marker'; | ||
import {waitForMockInstance} from './__utils__/wait-for-mock-instance'; | ||
|
||
jest.mock('../../libraries/google-maps-api-loader'); | ||
jest.mock('../../hooks/use-map'); | ||
jest.mock('../../hooks/use-maps-library'); | ||
|
||
let wrapper: ({children}: {children: React.ReactNode}) => JSX.Element | null; | ||
let createMarkerSpy: jest.Mock; | ||
let useMapMock: jest.MockedFn<typeof useMap>; | ||
let useMapsLibraryMock: jest.MockedFn<typeof useMapsLibrary>; | ||
|
||
let markerLib: google.maps.MarkerLibrary; | ||
let mapInstance: google.maps.Map; | ||
|
||
beforeEach(() => { | ||
beforeEach(async () => { | ||
initialize(); | ||
jest.clearAllMocks(); | ||
|
||
google.maps.importLibrary = jest.fn(() => Promise.resolve()) as never; | ||
// mocked versions of the useMap and useMapsLibrary functions | ||
useMapMock = jest.mocked(useMap); | ||
useMapsLibraryMock = jest.mocked(useMapsLibrary); | ||
|
||
// Create wrapper component | ||
wrapper = ({children}: {children: React.ReactNode}) => ( | ||
<APIProvider apiKey={'apikey'}> | ||
<GoogleMap zoom={10} center={{lat: 0, lng: 0}}> | ||
{children} | ||
</GoogleMap> | ||
</APIProvider> | ||
); | ||
// load the marker-lib that can be returned from the useMapsLibrary mock | ||
markerLib = (await google.maps.importLibrary( | ||
'marker' | ||
)) as google.maps.MarkerLibrary; | ||
|
||
// custom implementation of the AdvancedMarkerElement that has a properly | ||
// initialized content and an observeable constructor. | ||
createMarkerSpy = jest.fn(); | ||
google.maps.marker.AdvancedMarkerElement = class extends ( | ||
google.maps.marker.AdvancedMarkerElement | ||
) { | ||
const AdvancedMarkerElement = class extends google.maps.marker | ||
.AdvancedMarkerElement { | ||
constructor(o?: google.maps.marker.AdvancedMarkerElementOptions) { | ||
createMarkerSpy(o); | ||
super(o); | ||
|
||
// @googlemaps/js-jest-mocks doesn't initialize the .content property | ||
// as the real implementation does (this would normally be a pin-element, | ||
// but for our purposes a div should suffice) | ||
this.content = document.createElement('div'); | ||
} | ||
}; | ||
|
||
// the element has to be registered for this to work, but since we can | ||
// neither unregister nor re-register an element, a randomized name is | ||
// the element has to be registered for this override to work, but since we | ||
// can neither unregister nor re-register an element, a randomized name is | ||
// used for the element. | ||
customElements.define( | ||
`gmp-advanced-marker-${Math.random().toString(36).slice(2)}`, | ||
google.maps.marker.AdvancedMarkerElement | ||
AdvancedMarkerElement | ||
); | ||
|
||
google.maps.marker.AdvancedMarkerElement = markerLib.AdvancedMarkerElement = | ||
AdvancedMarkerElement; | ||
}); | ||
|
||
afterEach(() => { | ||
cleanup(); | ||
}); | ||
|
||
test('marker should be initialized', async () => { | ||
render(<GoogleMapsMarker position={{lat: 0, lng: 0}} />, { | ||
wrapper | ||
test('creates marker instance once map is ready', async () => { | ||
useMapsLibraryMock.mockReturnValue(null); | ||
useMapMock.mockReturnValue(null); | ||
|
||
const {rerender} = render(<AdvancedMarker position={{lat: 0, lng: 0}} />); | ||
|
||
expect(useMapsLibraryMock).toHaveBeenCalledWith('marker'); | ||
expect(createMarkerSpy).not.toHaveBeenCalled(); | ||
|
||
useMapsLibraryMock.mockImplementation(name => { | ||
expect(name).toEqual('marker'); | ||
return markerLib; | ||
}); | ||
const marker = await waitForMockInstance( | ||
google.maps.marker.AdvancedMarkerElement | ||
); | ||
const mockMap = new google.maps.Map(document.createElement('div')); | ||
useMapMock.mockReturnValue(mockMap); | ||
|
||
expect(createMarkerSpy).toHaveBeenCalledTimes(1); | ||
expect(marker).toBeDefined(); | ||
rerender(<AdvancedMarker position={{lat: 1, lng: 2}} />); | ||
|
||
expect(createMarkerSpy).toHaveBeenCalled(); | ||
}); | ||
|
||
test('multiple markers should be initialized', async () => { | ||
render( | ||
<> | ||
<GoogleMapsMarker position={{lat: 0, lng: 0}} /> | ||
<GoogleMapsMarker position={{lat: 0, lng: 0}} /> | ||
</>, | ||
{ | ||
wrapper | ||
} | ||
); | ||
describe('map and marker-library loaded', () => { | ||
beforeEach(() => { | ||
useMapsLibraryMock.mockImplementation(name => { | ||
expect(name).toEqual('marker'); | ||
return markerLib; | ||
}); | ||
|
||
await waitForMockInstance(google.maps.marker.AdvancedMarkerElement); | ||
mapInstance = new google.maps.Map(document.createElement('div')); | ||
useMapMock.mockReturnValue(mapInstance); | ||
}); | ||
|
||
const markers = mockInstances.get(google.maps.marker.AdvancedMarkerElement); | ||
test('marker should be initialized', async () => { | ||
render(<AdvancedMarker position={{lat: 0, lng: 0}} />); | ||
|
||
expect(markers).toHaveLength(2); | ||
}); | ||
expect(createMarkerSpy).toHaveBeenCalledTimes(1); | ||
expect( | ||
mockInstances.get(google.maps.marker.AdvancedMarkerElement) | ||
).toHaveLength(1); | ||
}); | ||
|
||
test('marker should have and update its position', async () => { | ||
const {rerender} = render(<AdvancedMarker position={{lat: 1, lng: 0}} />); | ||
const marker = await waitForMockInstance( | ||
google.maps.marker.AdvancedMarkerElement | ||
); | ||
|
||
expect(marker.position).toEqual({lat: 1, lng: 0}); | ||
|
||
rerender(<AdvancedMarker position={{lat: 2, lng: 2}} />); | ||
|
||
test('marker should have a position', async () => { | ||
const {rerender} = render(<GoogleMapsMarker position={{lat: 1, lng: 0}} />, { | ||
wrapper | ||
expect(marker.position).toEqual({lat: 2, lng: 2}); | ||
}); | ||
|
||
const marker = await waitForMockInstance( | ||
google.maps.marker.AdvancedMarkerElement | ||
); | ||
test('marker class is set correctly without html content', async () => { | ||
render( | ||
<AdvancedMarker | ||
position={{lat: 1, lng: 2}} | ||
className={'classname-test'} | ||
/> | ||
); | ||
|
||
expect(marker.position).toEqual({lat: 1, lng: 0}); | ||
const marker = mockInstances | ||
.get(google.maps.marker.AdvancedMarkerElement) | ||
.at(0) as google.maps.marker.AdvancedMarkerElement; | ||
|
||
rerender(<GoogleMapsMarker position={{lat: 2, lng: 2}} />); | ||
expect(marker.content).toHaveClass('classname-test'); | ||
}); | ||
|
||
expect(marker.position).toEqual({lat: 2, lng: 2}); | ||
}); | ||
test('marker class and style are set correctly with html content', async () => { | ||
render( | ||
<AdvancedMarker | ||
position={{lat: 1, lng: 2}} | ||
className={'classname-test'} | ||
style={{width: 200}}> | ||
<div data-testid={'marker-content'}>Marker Content!</div> | ||
</AdvancedMarker> | ||
); | ||
|
||
const marker = mockInstances | ||
.get(google.maps.marker.AdvancedMarkerElement) | ||
.at(0) as google.maps.marker.AdvancedMarkerElement; | ||
|
||
expect(marker.content).toHaveClass('classname-test'); | ||
expect(marker.content).toHaveStyle('width: 200px'); | ||
expect( | ||
queryByTestId(marker.content as HTMLElement, 'marker-content') | ||
).toBeTruthy(); | ||
}); | ||
|
||
test.todo('marker should work with options'); | ||
test.todo('marker should have a click listener'); | ||
test.todo('marker should work with options'); | ||
test.todo('marker should have a click listener'); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters