UNPKG

maplibre-gl

Version:

BSD licensed community fork of mapbox-gl, a WebGL interactive maps library

619 lines (442 loc) 20.8 kB
import simulate, {window} from '../../test/unit/lib/simulate_interaction'; import StyleLayer from '../style/style_layer'; import {createMap, setPerformance, setWebGlContext} from '../util/test/util'; import {MapGeoJSONFeature} from '../util/vectortile_to_geojson'; import {MapLayerEventType} from './events'; beforeEach(() => { setPerformance(); setWebGlContext(); }); 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 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 exiist', () => { 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#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 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 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(); }); (['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 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 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 exiist`, () => { 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} 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 MouseEvent = window(canvas).MouseEvent; 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(); const MouseEvent = window(canvas).MouseEvent; 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(); }); });