You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
leaflet-components/test/components/fit-to-markers.test.ts

164 lines
5.4 KiB
TypeScript

import { describe, expect, it, vi } from 'vitest';
import { Popup, type LatLngBounds } from 'leaflet';
import '../../src/components/leaflet-map.ts';
import '../../src/components/leaflet-tile-layer.ts';
import '../../src/components/leaflet-marker.ts';
// The initial (and every re-)frame is deferred to a microtask so the burst of
// `layeradd` events during load collapses into one `fitBounds`. Awaiting a
// freshly-queued microtask flushes the pending one first.
const flush = () =>
new Promise<void>((resolve) => {
queueMicrotask(resolve);
});
function makeMap(attrs: Record<string, string> = {}) {
const map = document.createElement('leaflet-map');
for (const [k, v] of Object.entries(attrs)) map.setAttribute(k, v);
return map;
}
function marker(lat: number, lng: number) {
const m = document.createElement('leaflet-marker');
m.setAttribute('lat', String(lat));
m.setAttribute('lng', String(lng));
return m;
}
describe('<leaflet-map fit-to-markers>', () => {
it('frames every marker once on load instead of honouring lat/lng/zoom', async () => {
const map = makeMap({ 'fit-to-markers': '', lat: '0', lng: '0', zoom: '2' });
map.append(marker(34.0537, -118.2427), marker(33.9416, -118.4085), marker(34.1341, -118.3215));
document.body.append(map);
// Spy after connect but before the microtask flush, so the initial frame is
// still pending and gets captured.
const spy = vi.spyOn(map.leafletObject!, 'fitBounds');
await flush();
expect(spy).toHaveBeenCalledTimes(1);
const bounds = spy.mock.calls[0]![0] as LatLngBounds;
expect(bounds.contains([34.0537, -118.2427])).toBe(true);
expect(bounds.contains([33.9416, -118.4085])).toBe(true);
expect(bounds.contains([34.1341, -118.3215])).toBe(true);
expect(spy.mock.calls[0]![1]).toMatchObject({ padding: [20, 20] });
map.remove();
});
it('passes fit-padding and fit-max-zoom through to fitBounds', async () => {
const map = makeMap({ 'fit-to-markers': '', 'fit-padding': '50', 'fit-max-zoom': '12' });
map.append(marker(1, 2), marker(3, 4));
document.body.append(map);
const spy = vi.spyOn(map.leafletObject!, 'fitBounds');
await flush();
expect(spy.mock.calls[0]![1]).toMatchObject({ padding: [50, 50], maxZoom: 12 });
map.remove();
});
it('re-frames when a marker is added later', async () => {
const map = makeMap({ 'fit-to-markers': '' });
map.append(marker(10, 10), marker(20, 20));
document.body.append(map);
await flush();
const spy = vi.spyOn(map.leafletObject!, 'fitBounds');
map.append(marker(40, -100));
await flush();
expect(spy).toHaveBeenCalledTimes(1);
expect((spy.mock.calls[0]![0] as LatLngBounds).contains([40, -100])).toBe(true);
map.remove();
});
it('activates when the attribute is toggled on after connect', async () => {
const map = makeMap({ lat: '0', lng: '0', zoom: '3' });
map.append(marker(34, -118), marker(35, -119));
document.body.append(map);
const spy = vi.spyOn(map.leafletObject!, 'fitBounds');
await flush();
expect(spy).not.toHaveBeenCalled();
map.setAttribute('fit-to-markers', '');
await flush();
expect(spy).toHaveBeenCalledTimes(1);
map.remove();
});
it('does not reframe when the user pans or zooms', async () => {
const map = makeMap({ 'fit-to-markers': '' });
map.append(marker(10, 10), marker(20, 20));
document.body.append(map);
await flush();
const spy = vi.spyOn(map.leafletObject!, 'fitBounds');
// Simulate a manual pan + zoom.
map.leafletObject!.setView([0, 0], 6);
map.leafletObject!.fire('moveend');
map.leafletObject!.fire('zoomend');
await flush();
expect(spy).not.toHaveBeenCalled();
map.remove();
});
it('does not reframe when a popup is added to / opened on the map', async () => {
const map = makeMap({ 'fit-to-markers': '' });
map.append(marker(34, -118), marker(35, -119));
document.body.append(map);
await flush();
const spy = vi.spyOn(map.leafletObject!, 'fitBounds');
new Popup().setLatLng([34, -118]).setContent('hi').openOn(map.leafletObject!);
await flush();
expect(spy).not.toHaveBeenCalled();
map.remove();
});
it('does nothing without the attribute', async () => {
const map = makeMap({ lat: '10', lng: '20', zoom: '5' });
map.append(marker(1, 2), marker(3, 4));
document.body.append(map);
const spy = vi.spyOn(map.leafletObject!, 'fitBounds');
await flush();
expect(spy).not.toHaveBeenCalled();
expect(map.leafletObject!.getZoom()).toBe(5);
map.remove();
});
it('leaves the view alone when no layer has a position', async () => {
const map = makeMap({ 'fit-to-markers': '', lat: '5', lng: '6', zoom: '4' });
const tiles = document.createElement('leaflet-tile-layer');
tiles.setAttribute('url', 'https://tile.example/{z}/{x}/{y}.png');
map.append(tiles);
document.body.append(map);
const spy = vi.spyOn(map.leafletObject!, 'fitBounds');
await flush();
expect(spy).not.toHaveBeenCalled();
expect(map.leafletObject!.getZoom()).toBe(4);
map.remove();
});
it('stops re-framing after disconnect', async () => {
const map = makeMap({ 'fit-to-markers': '' });
map.append(marker(1, 1), marker(2, 2));
document.body.append(map);
await flush();
const mapObj = map.leafletObject!;
const spy = vi.spyOn(mapObj, 'fitBounds');
map.remove();
await flush();
expect(spy).not.toHaveBeenCalled();
});
});