import simulate from '../../../test/unit/lib/simulate_interaction'; import {StyleLayer} from '../../style/style_layer'; import {createMap, beforeMapTest, createStyle} from '../../util/test/util'; import {MapGeoJSONFeature} from '../../util/vectortile_to_geojson'; import {MapLayerEventType, MapLibreEvent} from '../events'; import {Map, MapOptions} from '../map'; import {Event as EventedEvent, ErrorEvent} from '../../util/evented'; type IsAny = 0 extends T & 1 ? T : never; type NotAny = T extends IsAny ? never : T; function assertNotAny(_x: NotAny) { } beforeEach(() => { beforeMapTest(); }); describe('map events', () => { test('Map#on adds a non-delegated event listener', () => { const map = createMap(); const spy = jest.fn(function (e) { expect(this).toBe(map); expect(e.type).toBe('click'); }); map.on('click', spy); simulate.click(map.getCanvas()); expect(spy).toHaveBeenCalledTimes(1); }); test('Map#off removes a non-delegated event listener', () => { const map = createMap(); const spy = jest.fn(); map.on('click', spy); map.off('click', spy); simulate.click(map.getCanvas()); expect(spy).not.toHaveBeenCalled(); }); test('Map#on adds a listener for an event on a given layer', () => { const map = createMap(); const features = [{} as MapGeoJSONFeature]; jest.spyOn(map, 'getLayer').mockReturnValue({} as StyleLayer); jest.spyOn(map, 'queryRenderedFeatures').mockImplementation((_point, options) => { expect(options).toEqual({layers: ['layer']}); return features; }); const spy = jest.fn(function (e) { expect(this).toBe(map); expect(e.type).toBe('click'); expect(e.features).toBe(features); }); map.on('click', 'layer', spy); simulate.click(map.getCanvas()); expect(spy).toHaveBeenCalledTimes(1); }); test('Map#on adds a listener for an event on multiple layers', () => { const map = createMap(); const features = [{} as MapGeoJSONFeature]; jest.spyOn(map, 'getLayer').mockReturnValue({} as StyleLayer); jest.spyOn(map, 'queryRenderedFeatures') .mockImplementationOnce((_point, options) => { expect(options).toEqual({layers: ['layer1', 'layer2']}); return features; }); const spy = jest.fn(function (e) { expect(this).toBe(map); expect(e.type).toBe('click'); expect(e.features).toBe(features); }); map.on('click', ['layer1', 'layer2'], spy); simulate.click(map.getCanvas()); expect(spy).toHaveBeenCalledTimes(1); }); test('Map#on adds listener which calls queryRenderedFeatures only for existing layers', () => { const map = createMap(); const features = [{} as MapGeoJSONFeature]; jest.spyOn(map, 'getLayer').mockImplementation((id: string) => { if (id === 'nonExistingLayer') { return undefined; } return {} as StyleLayer; }); jest.spyOn(map, 'queryRenderedFeatures') .mockImplementationOnce((_point, options) => { expect(options).toEqual({layers: ['layer1', 'layer2']}); return features; }); const spy = jest.fn(function (e) { expect(this).toBe(map); expect(e.type).toBe('click'); expect(e.features).toBe(features); }); map.on('click', ['layer1', 'layer2', 'nonExistingLayer'], spy); simulate.click(map.getCanvas()); expect(spy).toHaveBeenCalledTimes(1); }); test('Map#on adds a listener not triggered for events not matching any features', () => { const map = createMap(); const features = []; jest.spyOn(map, 'getLayer').mockReturnValue({} as StyleLayer); jest.spyOn(map, 'queryRenderedFeatures').mockImplementation((point, options) => { expect(options).toEqual({layers: ['layer']}); return features; }); const spy = jest.fn(); map.on('click', 'layer', spy); simulate.click(map.getCanvas()); expect(spy).not.toHaveBeenCalled(); }); test('Map#on adds a listener not triggered when the specified layer does not exist', () => { const map = createMap(); jest.spyOn(map, 'getLayer').mockReturnValue(null as unknown as StyleLayer); const spy = jest.fn(); map.on('click', 'layer', spy); simulate.click(map.getCanvas()); expect(spy).not.toHaveBeenCalled(); }); test('Map#on distinguishes distinct event types', () => { const map = createMap(); jest.spyOn(map, 'getLayer').mockReturnValue({} as StyleLayer); jest.spyOn(map, 'queryRenderedFeatures').mockReturnValue([{} as MapGeoJSONFeature]); const spyDown = jest.fn((e) => { expect(e.type).toBe('mousedown'); }); const spyUp = jest.fn((e) => { expect(e.type).toBe('mouseup'); }); map.on('mousedown', 'layer', spyDown); map.on('mouseup', 'layer', spyUp); simulate.click(map.getCanvas()); expect(spyDown).toHaveBeenCalledTimes(1); expect(spyUp).toHaveBeenCalledTimes(1); }); test('Map#on distinguishes distinct layers', () => { const map = createMap(); const featuresA = [{} as MapGeoJSONFeature]; const featuresB = [{} as MapGeoJSONFeature]; jest.spyOn(map, 'getLayer').mockReturnValue({} as StyleLayer); jest.spyOn(map, 'queryRenderedFeatures').mockImplementation((_point, options) => { return (options as any).layers[0] === 'A' ? featuresA : featuresB; }); const spyA = jest.fn((e) => { expect(e.features).toBe(featuresA); }); const spyB = jest.fn((e) => { expect(e.features).toBe(featuresB); }); map.on('click', 'A', spyA); map.on('click', 'B', spyB); simulate.click(map.getCanvas()); expect(spyA).toHaveBeenCalledTimes(1); expect(spyB).toHaveBeenCalledTimes(1); }); test('Map#on distinguishes distinct listeners', () => { const map = createMap(); jest.spyOn(map, 'getLayer').mockReturnValue({} as StyleLayer); jest.spyOn(map, 'queryRenderedFeatures').mockReturnValue([{} as MapGeoJSONFeature]); const spyA = jest.fn(); const spyB = jest.fn(); map.on('click', 'layer', spyA); map.on('click', 'layer', spyB); simulate.click(map.getCanvas()); expect(spyA).toHaveBeenCalledTimes(1); expect(spyB).toHaveBeenCalledTimes(1); }); test('Map#on calls an event listener with no type arguments, defaulting to \'unknown\' originalEvent type', () => { const map = createMap(); const handler = { onMove: function onMove(_event: MapLibreEvent) {} }; jest.spyOn(handler, 'onMove'); map.on('move', (event) => handler.onMove(event)); map.jumpTo({center: {lng: 10, lat: 10}}); expect(handler.onMove).toHaveBeenCalledTimes(1); }); test('Map#on allows a listener to infer the event type ', () => { const map = createMap(); const spy = jest.fn(); map.on('mousemove', (event) => { assertNotAny(event); const {lng, lat} = event.lngLat; spy({lng, lat}); }); simulate.mousemove(map.getCanvas()); expect(spy).toHaveBeenCalledTimes(1); }); test('Map#off removes a delegated event listener', () => { const map = createMap(); jest.spyOn(map, 'getLayer').mockReturnValue({} as StyleLayer); jest.spyOn(map, 'queryRenderedFeatures').mockReturnValue([{} as MapGeoJSONFeature]); const spy = jest.fn(); map.on('click', 'layer', spy); map.off('click', 'layer', spy); simulate.click(map.getCanvas()); expect(spy).not.toHaveBeenCalled(); }); test('Map#off removes a delegated event listener for multiple layers', () => { const map = createMap(); jest.spyOn(map, 'getLayer').mockReturnValue({} as StyleLayer); jest.spyOn(map, 'queryRenderedFeatures').mockReturnValue([{} as MapGeoJSONFeature]); const spy = jest.fn(); map.on('click', ['layer1', 'layer2'], spy); map.off('click', ['layer1', 'layer2'], spy); simulate.click(map.getCanvas()); expect(spy).not.toHaveBeenCalled(); }); test('Map#off distinguishes distinct event types', () => { const map = createMap(); jest.spyOn(map, 'getLayer').mockReturnValue({} as StyleLayer); jest.spyOn(map, 'queryRenderedFeatures').mockReturnValue([{} as MapGeoJSONFeature]); const spy = jest.fn((e) => { expect(e.type).toBe('mousedown'); }); map.on('mousedown', 'layer', spy); map.on('mouseup', 'layer', spy); map.off('mouseup', 'layer', spy); simulate.click(map.getCanvas()); expect(spy).toHaveBeenCalledTimes(1); }); test('Map#off distinguishes distinct layers', () => { const map = createMap(); const featuresA = [{} as MapGeoJSONFeature]; jest.spyOn(map, 'getLayer').mockReturnValue({} as StyleLayer); jest.spyOn(map, 'queryRenderedFeatures').mockImplementation((point, options) => { expect(options).toEqual({layers: ['A']}); return featuresA; }); const spy = jest.fn((e) => { expect(e.features).toBe(featuresA); }); map.on('click', 'A', spy); map.on('click', 'B', spy); map.off('click', 'B', spy); simulate.click(map.getCanvas()); expect(spy).toHaveBeenCalledTimes(1); }); test('Map#off distinguishes distinct layer arrays', () => { const map = createMap(); const featuresAB = [{} as MapGeoJSONFeature]; jest.spyOn(map, 'getLayer').mockReturnValue({} as StyleLayer); jest.spyOn(map, 'queryRenderedFeatures').mockImplementation((point, options) => { expect(options).toEqual({layers: ['A', 'B']}); return featuresAB; }); const spy = jest.fn((e) => { expect(e.features).toBe(featuresAB); }); map.on('click', ['A', 'B'], spy); map.on('click', ['A', 'C'], spy); map.off('click', ['A', 'C'], spy); simulate.click(map.getCanvas()); expect(spy).toHaveBeenCalledTimes(1); }); test('Map#off compares full layer array list, including layers missing in style', () => { const map = createMap(); jest.spyOn(map, 'getLayer').mockImplementation((id: string) => { if (id === 'nonExistingLayer') { return undefined; } return {} as StyleLayer; }); jest.spyOn(map, 'queryRenderedFeatures').mockReturnValue([{} as MapGeoJSONFeature]); const spy = jest.fn(); map.on('click', ['A', 'C', 'nonExistingLayer'], spy); map.off('click', ['A', 'C'], spy); simulate.click(map.getCanvas()); map.off('click', ['A', 'C', 'nonExistingLayer'], spy); simulate.click(map.getCanvas()); expect(spy).toHaveBeenCalledTimes(1); }); test('Map#off distinguishes distinct listeners', () => { const map = createMap(); jest.spyOn(map, 'getLayer').mockReturnValue({} as StyleLayer); jest.spyOn(map, 'queryRenderedFeatures').mockReturnValue([{} as MapGeoJSONFeature]); const spyA = jest.fn(); const spyB = jest.fn(); map.on('click', 'layer', spyA); map.on('click', 'layer', spyB); map.off('click', 'layer', spyB); simulate.click(map.getCanvas()); expect(spyA).toHaveBeenCalledTimes(1); expect(spyB).not.toHaveBeenCalled(); }); test('Map#off calls an event listener with no type arguments, defaulting to \'unknown\' originalEvent type', () => { const map = createMap(); const handler = { onMove: function onMove(_event: MapLibreEvent) {} }; jest.spyOn(handler, 'onMove'); map.off('move', (event) => handler.onMove(event)); map.jumpTo({center: {lng: 10, lat: 10}}); expect(handler.onMove).toHaveBeenCalledTimes(0); }); test('Map#off allows a listener to infer the event type ', () => { const map = createMap(); const spy = jest.fn(); map.off('mousemove', (event) => { assertNotAny(event); const {lng, lat} = event.lngLat; spy({lng, lat}); }); simulate.mousemove(map.getCanvas()); expect(spy).toHaveBeenCalledTimes(0); }); test('Map#once calls an event listener with no type arguments, defaulting to \'unknown\' originalEvent type', () => { const map = createMap(); const handler = { onMoveOnce: function onMoveOnce(_event: MapLibreEvent) {} }; jest.spyOn(handler, 'onMoveOnce'); map.once('move', (event) => handler.onMoveOnce(event)); map.jumpTo({center: {lng: 10, lat: 10}}); expect(handler.onMoveOnce).toHaveBeenCalledTimes(1); }); test('Map#once allows a listener to infer the event type ', () => { const map = createMap(); const spy = jest.fn(); map.once('mousemove', (event) => { assertNotAny(event); const {lng, lat} = event.lngLat; spy({lng, lat}); }); simulate.mousemove(map.getCanvas()); expect(spy).toHaveBeenCalledTimes(1); }); test('Map#off removes listener registered with Map#once', () => { const map = createMap(); jest.spyOn(map, 'getLayer').mockReturnValue({} as StyleLayer); jest.spyOn(map, 'queryRenderedFeatures').mockReturnValue([{} as MapGeoJSONFeature]); const spy = jest.fn(); map.once('click', 'layer', spy); map.off('click', 'layer', spy); simulate.click(map.getCanvas()); expect(spy).not.toHaveBeenCalled(); }); (['mouseenter', 'mouseover'] as (keyof MapLayerEventType)[]).forEach((event) => { test(`Map#on ${event} does not fire if the specified layer does not exist`, () => { const map = createMap(); jest.spyOn(map, 'getLayer').mockReturnValue(null as unknown as StyleLayer); const spy = jest.fn(); map.on(event, 'layer', spy); simulate.mousemove(map.getCanvas()); simulate.mousemove(map.getCanvas()); expect(spy).not.toHaveBeenCalled(); }); test(`Map#on ${event} fires when entering the specified layer`, () => { const map = createMap(); const features = [{} as MapGeoJSONFeature]; jest.spyOn(map, 'getLayer').mockReturnValue({} as StyleLayer); jest.spyOn(map, 'queryRenderedFeatures').mockImplementation((_point, options) => { expect(options).toEqual({layers: ['layer']}); return features; }); const spy = jest.fn(function (e) { expect(this).toBe(map); expect(e.type).toBe(event); expect(e.target).toBe(map); expect(e.features).toBe(features); }); map.on(event, 'layer', spy); simulate.mousemove(map.getCanvas()); expect(spy).toHaveBeenCalledTimes(1); }); test(`Map#on ${event} does not fire on mousemove within the specified layer`, () => { const map = createMap(); jest.spyOn(map, 'getLayer').mockReturnValue({} as StyleLayer); jest.spyOn(map, 'queryRenderedFeatures').mockReturnValue([{} as MapGeoJSONFeature]); const spy = jest.fn(); map.on(event, 'layer', spy); simulate.mousemove(map.getCanvas()); simulate.mousemove(map.getCanvas()); expect(spy).toHaveBeenCalledTimes(1); }); test(`Map#on ${event} fires when reentering the specified layer`, () => { const map = createMap(); jest.spyOn(map, 'getLayer').mockReturnValue({} as StyleLayer); jest.spyOn(map, 'queryRenderedFeatures') .mockReturnValueOnce([{} as MapGeoJSONFeature]) .mockReturnValueOnce([]) .mockReturnValueOnce([{} as MapGeoJSONFeature]); const spy = jest.fn(); map.on(event, 'layer', spy); simulate.mousemove(map.getCanvas()); simulate.mousemove(map.getCanvas()); simulate.mousemove(map.getCanvas()); expect(spy).toHaveBeenCalledTimes(2); }); test(`Map#on ${event} fires when reentering the specified layer after leaving the canvas`, () => { const map = createMap(); jest.spyOn(map, 'getLayer').mockReturnValue({} as StyleLayer); jest.spyOn(map, 'queryRenderedFeatures').mockReturnValue([{} as MapGeoJSONFeature]); const spy = jest.fn(); map.on(event, 'layer', spy); simulate.mousemove(map.getCanvas()); simulate.mouseout(map.getCanvas()); simulate.mousemove(map.getCanvas()); expect(spy).toHaveBeenCalledTimes(2); }); test(`Map#on ${event} distinguishes distinct layers`, () => { const map = createMap(); const featuresA = [{} as MapGeoJSONFeature]; const featuresB = [{} as MapGeoJSONFeature]; jest.spyOn(map, 'getLayer').mockReturnValue({} as StyleLayer); jest.spyOn(map, 'queryRenderedFeatures').mockImplementation((_point, options) => { return (options as any).layers[0] === 'A' ? featuresA : featuresB; }); const spyA = jest.fn((e) => { expect(e.features).toBe(featuresA); }); const spyB = jest.fn((e) => { expect(e.features).toBe(featuresB); }); map.on(event, 'A', spyA); map.on(event, 'B', spyB); simulate.mousemove(map.getCanvas()); simulate.mousemove(map.getCanvas()); expect(spyA).toHaveBeenCalledTimes(1); expect(spyB).toHaveBeenCalledTimes(1); }); test(`Map#on ${event} distinguishes distinct layers when multiple layers provided`, () => { const map = createMap(); const nonEmptyFeatures = [{} as MapGeoJSONFeature]; const emptyFeatures = []; jest.spyOn(map, 'getLayer').mockReturnValue({} as StyleLayer); jest.spyOn(map, 'queryRenderedFeatures').mockImplementation((_point, options) => { const layers = (options as any).layers as string[]; if (layers.includes('A')) { return nonEmptyFeatures; } return emptyFeatures; }); const spyA = jest.fn(); const spyAB = jest.fn(); const spyC = jest.fn(); map.on(event, 'A', spyA); map.on(event, ['A', 'B'], spyAB); map.on(event, 'C', spyC); simulate.mousemove(map.getCanvas()); simulate.mousemove(map.getCanvas()); expect(spyA).toHaveBeenCalledTimes(1); expect(spyAB).toHaveBeenCalledTimes(1); expect(spyC).not.toHaveBeenCalled(); }); test(`Map#on ${event} filters non-existing layers`, () => { const map = createMap(); jest.spyOn(map, 'getLayer').mockImplementation((id: string) => id === 'B' ? undefined : {} as StyleLayer); jest.spyOn(map, 'queryRenderedFeatures').mockImplementation((_point, options) => { expect((options as any).layers).toStrictEqual(['A', 'C']); return [{} as MapGeoJSONFeature]; }); const spyAC = jest.fn(); map.on(event, ['A', 'B', 'C'], spyAC); simulate.mousemove(map.getCanvas()); expect(map.queryRenderedFeatures).toHaveBeenCalled(); expect(spyAC).toHaveBeenCalledTimes(1); }); test(`Map#on ${event} distinguishes distinct listeners`, () => { const map = createMap(); jest.spyOn(map, 'getLayer').mockReturnValue({} as StyleLayer); jest.spyOn(map, 'queryRenderedFeatures').mockReturnValue([{} as MapGeoJSONFeature]); const spyA = jest.fn(); const spyB = jest.fn(); map.on(event, 'layer', spyA); map.on(event, 'layer', spyB); simulate.mousemove(map.getCanvas()); expect(spyA).toHaveBeenCalledTimes(1); expect(spyB).toHaveBeenCalledTimes(1); }); test(`Map#off ${event} removes a delegated event listener`, () => { const map = createMap(); jest.spyOn(map, 'getLayer').mockReturnValue({} as StyleLayer); jest.spyOn(map, 'queryRenderedFeatures').mockReturnValue([{} as MapGeoJSONFeature]); const spy = jest.fn(); map.on(event, 'layer', spy); map.off(event, 'layer', spy); simulate.mousemove(map.getCanvas()); expect(spy).not.toHaveBeenCalled(); }); test(`Map#off ${event} distinguishes distinct layers`, () => { const map = createMap(); const featuresA = [{} as MapGeoJSONFeature]; jest.spyOn(map, 'getLayer').mockReturnValue({} as StyleLayer); jest.spyOn(map, 'queryRenderedFeatures').mockImplementation((_point, options) => { expect(options).toEqual({layers: ['A']}); return featuresA; }); const spy = jest.fn((e) => { expect(e.features).toBe(featuresA); }); map.on(event, 'A', spy); map.on(event, 'B', spy); map.off(event, 'B', spy); simulate.mousemove(map.getCanvas()); expect(spy).toHaveBeenCalledTimes(1); }); test(`Map#off ${event} distinguishes distinct layers when multiple layers provided`, () => { const map = createMap(); const featuresAB = [{} as MapGeoJSONFeature]; jest.spyOn(map, 'getLayer').mockReturnValue({} as StyleLayer); jest.spyOn(map, 'queryRenderedFeatures').mockImplementation((_point, options) => { expect(options).toEqual({layers: ['A', 'B']}); return featuresAB; }); const spy = jest.fn((e) => { expect(e.features).toBe(featuresAB); }); map.on(event, ['A', 'B'], spy); map.on(event, ['B', 'C'], spy); map.off(event, ['B', 'C'], spy); simulate.mousemove(map.getCanvas()); expect(spy).toHaveBeenCalledTimes(1); expect(map.queryRenderedFeatures).toHaveBeenCalledTimes(1); }); test(`Map#off ${event} distinguishes distinct listeners`, () => { const map = createMap(); jest.spyOn(map, 'getLayer').mockReturnValue({} as StyleLayer); jest.spyOn(map, 'queryRenderedFeatures').mockReturnValue([{} as MapGeoJSONFeature]); const spyA = jest.fn(); const spyB = jest.fn(); map.on(event, 'layer', spyA); map.on(event, 'layer', spyB); map.off(event, 'layer', spyB); simulate.mousemove(map.getCanvas()); expect(spyA).toHaveBeenCalledTimes(1); expect(spyB).not.toHaveBeenCalled(); }); }); (['mouseleave', 'mouseout'] as (keyof MapLayerEventType)[]).forEach((event) => { test(`Map#on ${event} does not fire if the specified layer does not exist`, () => { const map = createMap(); jest.spyOn(map, 'getLayer').mockReturnValue(undefined); jest.spyOn(map, 'queryRenderedFeatures'); const spy = jest.fn(); map.on(event, 'layer', spy); simulate.mousemove(map.getCanvas()); simulate.mousemove(map.getCanvas()); expect(spy).not.toHaveBeenCalled(); expect(map.queryRenderedFeatures).not.toHaveBeenCalled(); }); test(`Map#on ${event} fires if one of specified layers exists`, () => { const map = createMap(); jest.spyOn(map, 'getLayer').mockImplementation((id: string) => id === 'A' ? {} as StyleLayer : undefined); jest.spyOn(map, 'queryRenderedFeatures') .mockReturnValueOnce([{} as MapGeoJSONFeature]) .mockReturnValueOnce([]); const spy = jest.fn(); map.on(event, ['A', 'B'], spy); simulate.mousemove(map.getCanvas()); simulate.mousemove(map.getCanvas()); expect(spy).toHaveBeenCalledTimes(1); }); test(`Map#on ${event} does not fire on mousemove when entering or within the specified layer`, () => { const map = createMap(); jest.spyOn(map, 'getLayer').mockReturnValue({} as StyleLayer); jest.spyOn(map, 'queryRenderedFeatures').mockReturnValue([{} as MapGeoJSONFeature]); const spy = jest.fn(); map.on(event, 'layer', spy); simulate.mousemove(map.getCanvas()); simulate.mousemove(map.getCanvas()); expect(spy).not.toHaveBeenCalled(); }); test(`Map#on ${event} fires when exiting the specified layer`, () => { const map = createMap(); jest.spyOn(map, 'getLayer').mockReturnValue({} as StyleLayer); jest.spyOn(map, 'queryRenderedFeatures') .mockReturnValueOnce([{} as MapGeoJSONFeature]) .mockReturnValueOnce([]); const spy = jest.fn(function (e) { expect(this).toBe(map); expect(e.type).toBe(event); expect(e.features).toBeUndefined(); }); map.on(event, 'layer', spy); simulate.mousemove(map.getCanvas()); simulate.mousemove(map.getCanvas()); expect(spy).toHaveBeenCalledTimes(1); }); test(`Map#on ${event} fires when exiting the canvas`, () => { const map = createMap(); jest.spyOn(map, 'getLayer').mockReturnValue({} as StyleLayer); jest.spyOn(map, 'queryRenderedFeatures').mockReturnValue([{} as MapGeoJSONFeature]); const spy = jest.fn(function (e) { expect(this).toBe(map); expect(e.type).toBe(event); expect(e.features).toBeUndefined(); }); map.on(event, 'layer', spy); simulate.mousemove(map.getCanvas()); simulate.mouseout(map.getCanvas()); expect(spy).toHaveBeenCalledTimes(1); }); test(`Map#off ${event} removes a delegated event listener`, () => { const map = createMap(); jest.spyOn(map, 'getLayer').mockReturnValue({} as StyleLayer); jest.spyOn(map, 'queryRenderedFeatures') .mockReturnValueOnce([{} as MapGeoJSONFeature]) .mockReturnValueOnce([]); const spy = jest.fn(); map.on(event, 'layer', spy); map.off(event, 'layer', spy); simulate.mousemove(map.getCanvas()); simulate.mousemove(map.getCanvas()); simulate.mouseout(map.getCanvas()); expect(spy).not.toHaveBeenCalled(); }); }); test('Map#on mousedown can have default behavior prevented and still fire subsequent click event', () => { const map = createMap(); map.on('mousedown', e => e.preventDefault()); const click = jest.fn(); map.on('click', click); simulate.click(map.getCanvas()); expect(click).toHaveBeenCalled(); map.remove(); }); test('Map#on mousedown doesn\'t fire subsequent click event if mousepos changes', () => { const map = createMap(); map.on('mousedown', e => e.preventDefault()); const click = jest.fn(); map.on('click', click); const canvas = map.getCanvas(); simulate.drag(canvas, {}, {clientX: 100, clientY: 100}); expect(click).not.toHaveBeenCalled(); map.remove(); }); test('Map#on mousedown fires subsequent click event if mouse position changes less than click tolerance', () => { const map = createMap({clickTolerance: 4}); map.on('mousedown', e => e.preventDefault()); const click = jest.fn(); map.on('click', click); const canvas = map.getCanvas(); simulate.drag(canvas, {clientX: 100, clientY: 100}, {clientX: 100, clientY: 103}); expect(click).toHaveBeenCalled(); map.remove(); }); test('Map#on mousedown does not fire subsequent click event if mouse position changes more than click tolerance', () => { const map = createMap({clickTolerance: 4}); map.on('mousedown', e => e.preventDefault()); const click = jest.fn(); map.on('click', click); const canvas = map.getCanvas(); simulate.drag(canvas, {clientX: 100, clientY: 100}, {clientX: 100, clientY: 104}); expect(click).not.toHaveBeenCalled(); map.remove(); }); test('Map#on click fires subsequent click event if there is no corresponding mousedown/mouseup event', () => { const map = createMap({clickTolerance: 4}); const click = jest.fn(); map.on('click', click); const canvas = map.getCanvas(); const event = new MouseEvent('click', {bubbles: true, clientX: 100, clientY: 100}); canvas.dispatchEvent(event); expect(click).toHaveBeenCalled(); map.remove(); }); test('Map#isMoving() returns false in mousedown/mouseup/click with no movement', () => { const map = createMap({interactive: true, clickTolerance: 4}); let mousedown, mouseup, click; map.on('mousedown', () => { mousedown = map.isMoving(); }); map.on('mouseup', () => { mouseup = map.isMoving(); }); map.on('click', () => { click = map.isMoving(); }); const canvas = map.getCanvas(); canvas.dispatchEvent(new MouseEvent('mousedown', {bubbles: true, clientX: 100, clientY: 100})); expect(mousedown).toBe(false); map._renderTaskQueue.run(); expect(mousedown).toBe(false); canvas.dispatchEvent(new MouseEvent('mouseup', {bubbles: true, clientX: 100, clientY: 100})); expect(mouseup).toBe(false); map._renderTaskQueue.run(); expect(mouseup).toBe(false); canvas.dispatchEvent(new MouseEvent('click', {bubbles: true, clientX: 100, clientY: 100})); expect(click).toBe(false); map._renderTaskQueue.run(); expect(click).toBe(false); map.remove(); }); test('emits load event after a style is set', done => { const map = new Map({container: window.document.createElement('div')} as any as MapOptions); const fail = () => done('test failed'); const pass = () => done(); map.on('load', fail); setTimeout(() => { map.off('load', fail); map.on('load', pass); map.setStyle(createStyle()); }, 1); }); test('no idle event during move', async () => { const style = createStyle(); const map = createMap({style, fadeDuration: 0}); await map.once('idle'); map.zoomTo(0.5, {duration: 100}); expect(map.isMoving()).toBeTruthy(); await map.once('idle'); expect(map.isMoving()).toBeFalsy(); }); test('fires sourcedataabort event on dataabort event', async () => { const map = createMap(); const sourcePromise = map.once('sourcedataabort'); map.fire(new EventedEvent('dataabort')); await sourcePromise; }); test('getZoom on moveend is the same as after the map end moving, with terrain on', () => { const map = createMap({interactive: true, clickTolerance: 4}); map.terrain = { pointCoordinate: () => null, getElevationForLngLatZoom: () => 1000, } as any; let actualZoom: number; map.on('moveend', () => { // this can't use a promise due to race condition actualZoom = map.getZoom(); }); const canvas = map.getCanvas(); simulate.dragWithMove(canvas, {x: 100, y: 100}, {x: 100, y: 150}); map._renderTaskQueue.run(); expect(actualZoom).toBe(map.getZoom()); }); describe('error event', () => { test('logs errors to console when it has NO listeners', () => { // to avoid seeing error in the console in Jest let stub = jest.spyOn(console, 'error').mockImplementation(() => {}); const map = createMap(); stub.mockReset(); stub = jest.spyOn(console, 'error').mockImplementation(() => {}); const error = new Error('test'); map.fire(new ErrorEvent(error)); expect(stub).toHaveBeenCalledTimes(1); expect(stub.mock.calls[0][0]).toBe(error); }); test('calls listeners', done => { const map = createMap(); const error = new Error('test'); map.on('error', (event) => { expect(event.error).toBe(error); done(); }); map.fire(new ErrorEvent(error)); }); }); });