Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: error on missing zoom or center #308

Merged
merged 15 commits into from
Apr 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions src/components/__tests__/__snapshots__/map.test.tsx.snap
Original file line number Diff line number Diff line change
@@ -1,3 +1,33 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`camera configuration logs a warning message when missing configuration 1`] = `
[
"<Map> component is missing configuration. You have to provide zoom and center (via the \`zoom\`/\`defaultZoom\` and \`center\`/\`defaultCenter\` props) or specify the region to show using \`defaultBounds\`. See https://visgl.github.io/react-google-maps/docs/api-reference/components/map#required",
]
`;

exports[`camera configuration logs a warning message when missing configuration 2`] = `
[
"<Map> component is missing configuration. You have to provide zoom and center (via the \`zoom\`/\`defaultZoom\` and \`center\`/\`defaultCenter\` props) or specify the region to show using \`defaultBounds\`. See https://visgl.github.io/react-google-maps/docs/api-reference/components/map#required",
]
`;

exports[`camera configuration logs a warning message when missing configuration 3`] = `
[
"<Map> component is missing configuration. You have to provide zoom and center (via the \`zoom\`/\`defaultZoom\` and \`center\`/\`defaultCenter\` props) or specify the region to show using \`defaultBounds\`. See https://visgl.github.io/react-google-maps/docs/api-reference/components/map#required",
]
`;

exports[`camera configuration logs a warning message when missing configuration 4`] = `
[
"<Map> component is missing configuration. You have to provide zoom and center (via the \`zoom\`/\`defaultZoom\` and \`center\`/\`defaultCenter\` props) or specify the region to show using \`defaultBounds\`. See https://visgl.github.io/react-google-maps/docs/api-reference/components/map#required",
]
`;

exports[`camera configuration logs a warning message when missing configuration 5`] = `
[
"<Map> component is missing configuration. You have to provide zoom and center (via the \`zoom\`/\`defaultZoom\` and \`center\`/\`defaultCenter\` props) or specify the region to show using \`defaultBounds\`. See https://visgl.github.io/react-google-maps/docs/api-reference/components/map#required",
]
`;

exports[`throws an exception when rendering outside API provider 1`] = `"<Map> can only be used inside an <ApiProvider> component."`;
68 changes: 59 additions & 9 deletions src/components/__tests__/map.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {render, screen, waitFor} from '@testing-library/react';
import {initialize, mockInstances} from '@googlemaps/jest-mocks';
import '@testing-library/jest-dom';

import {Map as GoogleMap} from '../map';
import {Map as GoogleMap, MapProps} from '../map';
import {APIProviderContext, APIProviderContextValue} from '../api-provider';
import {APILoadingStatus} from '../../libraries/api-loading-status';

Expand Down Expand Up @@ -56,17 +56,21 @@ afterEach(() => {
test('map instance is created after api is loaded', async () => {
mockContextValue.status = APILoadingStatus.LOADING;

const {rerender} = render(<GoogleMap />, {wrapper});
const {rerender} = render(
<GoogleMap zoom={8} center={{lat: 53.55, lng: 10.05}} />,
{wrapper}
);

expect(createMapSpy).not.toHaveBeenCalled();

// rerender after loading completes
mockContextValue.status = APILoadingStatus.LOADED;
rerender(<GoogleMap />);
rerender(<GoogleMap zoom={8} center={{lat: 53.55, lng: 10.05}} />);
expect(createMapSpy).toHaveBeenCalled();
});

test("map is registered as 'default' when no id is specified", () => {
render(<GoogleMap />, {wrapper});
render(<GoogleMap zoom={8} center={{lat: 53.55, lng: 10.05}} />, {wrapper});

expect(mockContextValue.addMapInstance).toHaveBeenCalledWith(
mockInstances.get(google.maps.Map).at(-1),
Expand All @@ -79,7 +83,9 @@ test('throws an exception when rendering outside API provider', () => {
jest.spyOn(console, 'error').mockImplementation(() => {});

// render without wrapper
expect(() => render(<GoogleMap />)).toThrowErrorMatchingSnapshot();
expect(() =>
render(<GoogleMap zoom={8} center={{lat: 53.55, lng: 10.05}} />)
).toThrowErrorMatchingSnapshot();
});

describe('creating and updating map instance', () => {
Expand Down Expand Up @@ -152,15 +158,59 @@ describe('creating and updating map instance', () => {
});
});

describe('map events and event-props', () => {
test.todo('events dispatched by the map are received via event-props');
});
describe('camera configuration', () => {
test.each([
[{}, true],
[{center: {lat: 0, lng: 0}}, true],
[{defaultCenter: {lat: 0, lng: 0}}, true],
[{zoom: 1}, true],
[{defaultZoom: 1}, true],
[{defaultBounds: {north: 1, east: 2, south: 3, west: 4}}, false],
[{defaultCenter: {lat: 0, lng: 0}, zoom: 0}, false],
[{center: {lat: 0, lng: 0}, zoom: 0}, false],
[{center: {lat: 0, lng: 0}, defaultZoom: 0}, false]
])(
'logs a warning message when missing configuration',
(props: MapProps, expectWarningMessage: boolean) => {
// mute warning in test output
const consoleWarn = jest
.spyOn(console, 'warn')
.mockImplementation(() => {});

render(<GoogleMap {...props} />, {wrapper});

if (expectWarningMessage)
expect(consoleWarn.mock.lastCall).toMatchSnapshot();
else expect(consoleWarn).not.toHaveBeenCalled();
}
);

test('makes sure that map renders without viewport configuration', async () => {
// mute warning in test output
console.warn = jest.fn();

render(<GoogleMap />, {wrapper});
await waitFor(() => expect(screen.getByTestId('map')).toBeInTheDocument());

expect(createMapSpy).toHaveBeenCalled();

const mapInstance = jest.mocked(mockInstances.get(google.maps.Map).at(0)!);
expect(mapInstance.fitBounds).toHaveBeenCalledWith({
east: 180,
north: 90,
south: -90,
west: -180
});
});

describe('camera updates', () => {
test.todo('initial camera state is passed via mapOptions, not moveCamera');
test.todo('updated camera state is passed to moveCamera');
test.todo("re-renders with unchanged camera state don't trigger moveCamera");
test.todo(
"re-renders with props received via events don't trigger moveCamera"
);
});

describe('map events and event-props', () => {
test.todo('events dispatched by the map are received via event-props');
});
19 changes: 19 additions & 0 deletions src/components/map/use-map-instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,20 @@ export function useMapInstance(
...mapOptions
} = props;

const hasZoom = props.zoom !== undefined || props.defaultZoom !== undefined;
const hasCenter =
props.center !== undefined || props.defaultCenter !== undefined;

if (!defaultBounds && (!hasZoom || !hasCenter)) {
console.warn(
'<Map> component is missing configuration. ' +
'You have to provide zoom and center (via the `zoom`/`defaultZoom` and ' +
'`center`/`defaultCenter` props) or specify the region to show using ' +
'`defaultBounds`. See ' +
'https://visgl.github.io/react-google-maps/docs/api-reference/components/map#required'
);
}

// apply default camera props if available and not overwritten by controlled props
if (!mapOptions.center && defaultCenter) mapOptions.center = defaultCenter;
if (!mapOptions.zoom && Number.isFinite(defaultZoom))
Expand Down Expand Up @@ -76,6 +90,11 @@ export function useMapInstance(
newMap.fitBounds(defaultBounds);
}

// prevent map not rendering due to missing configuration
else if (!hasZoom || !hasCenter) {
newMap.fitBounds({east: 180, west: -180, south: -90, north: 90});
}

// the savedMapState is used to restore the camera parameters when the mapId is changed
if (savedMapStateRef.current) {
const {mapId: savedMapId, cameraState: savedCameraState} =
Expand Down
4 changes: 3 additions & 1 deletion src/hooks/__tests__/use-map.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ test('returns the parent map instance when called without id', async () => {
// Create wrapper component
const wrapper = ({children}: React.PropsWithChildren) => (
<MockApiContextProvider>
<GoogleMap>{children}</GoogleMap>
<GoogleMap zoom={8} center={{lat: 53.55, lng: 10.05}}>
{children}
</GoogleMap>
</MockApiContextProvider>
);

Expand Down
Loading