UNPKG

maplibre-gl

Version:

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

662 lines (517 loc) 21.9 kB
import {describe, beforeEach, test, expect, vi, type MockInstance} from 'vitest'; import {browser} from '../../util/browser'; import {Map} from '../../ui/map'; import {DOM} from '../../util/dom'; import simulate from '../../../test/unit/lib/simulate_interaction'; import {setPerformance, beforeMapTest, createTerrain} from '../../util/test/util'; function createMap() { return new Map({ container: DOM.create('div', '', window.document.body), style: { 'version': 8, 'sources': {}, 'layers': [] } }); } function scrollOutAtLat(map: Map, lat: number, browserNow: MockInstance<() => number>, deltaY: number = 5) { map.setCenter([0, lat]); map.setZoom(1); for (let i = 0; i < 200; i++) { simulate.wheel(map.getCanvas(), { type: 'wheel', deltaY, clientX: map.transform.width / 2, clientY: map.transform.height / 2}); browserNow.mockReturnValue(browser.now() + 10); map._renderTaskQueue.run(); } } beforeEach(() => { beforeMapTest(); }); describe('ScrollZoomHandler', () => { test('Zooms for single mouse wheel tick', () => { const browserNow = vi.spyOn(browser, 'now'); let now = 1555555555555; browserNow.mockReturnValue(now); const map = createMap(); map._renderTaskQueue.run(); // simulate a single 'wheel' event const startZoom = map.getZoom(); simulate.wheel(map.getCanvas(), {type: 'wheel', deltaY: -simulate.magicWheelZoomDelta}); map._renderTaskQueue.run(); now += 400; browserNow.mockReturnValue(now); map._renderTaskQueue.run(); expect(map.getZoom() - startZoom).toBeCloseTo(0.0285, 3); map.remove(); }); test('Zooms for single mouse wheel tick with easing for smooth zooming', () => { const browserNow = vi.spyOn(browser, 'now'); let now = 1555555555555; browserNow.mockReturnValue(now); const map = createMap(); map.setZoom(5); map._renderTaskQueue.run(); // simulate a single 'wheel' event const startZoom = map.getZoom(); simulate.wheel(map.getCanvas(), {type: 'wheel', deltaY: -simulate.magicWheelZoomDelta}); map._renderTaskQueue.run(); // A single tick zoom with easing completes in approx. 200ms now += 100; browserNow.mockReturnValue(now); map._renderTaskQueue.run(); const midZoom = map.getZoom(); now += 400; browserNow.mockReturnValue(now); map._renderTaskQueue.run(); const endZoom = map.getZoom(); expect(midZoom).toBeGreaterThan(startZoom); expect(midZoom).toBeLessThan(endZoom); map.remove(); }); test('Zooms for multiple fast mouse wheel ticks', () => { const browserNow = vi.spyOn(browser, 'now'); let now = 1555555555555; browserNow.mockReturnValue(now); const map = createMap(); map._renderTaskQueue.run(); // simulate a multiple fast 'wheel' event const startZoom = map.getZoom(); const iterations = 10; for (let i = 0; i < iterations; i++) { simulate.wheel(map.getCanvas(), {type: 'wheel', deltaY: -simulate.magicWheelZoomDelta}); map._renderTaskQueue.run(); now += 0; browserNow.mockReturnValue(now); map._renderTaskQueue.run(); } now += 400; browserNow.mockReturnValue(now); map._renderTaskQueue.run(); expect(map.getZoom() - startZoom).toBeCloseTo(0.0285 * iterations, 2); map.remove(); }); test('Zooms for multiple fast mouse wheel ticks with easing for smooth zooming', () => { const browserNow = vi.spyOn(browser, 'now'); let now = 1555555555555; browserNow.mockReturnValue(now); const map = createMap(); map.setZoom(5); map._renderTaskQueue.run(); // simulate a multiple fast 'wheel' event const startZoom = map.getZoom(); let midZoom = 0; const iterations = 10; for (let i = 0; i < iterations; i++) { simulate.wheel(map.getCanvas(), {type: 'wheel', deltaY: -simulate.magicWheelZoomDelta}); map._renderTaskQueue.run(); now += 0; browserNow.mockReturnValue(now); map._renderTaskQueue.run(); if (i === iterations - 1) { midZoom = map.getZoom(); } } now += 400; browserNow.mockReturnValue(now); map._renderTaskQueue.run(); const endZoom = map.getZoom(); expect(midZoom).toBeGreaterThan(startZoom); expect(midZoom).toBeLessThan(endZoom); map.remove(); }); test('Zooms for single mouse wheel tick with non-magical deltaY', async () => { const browserNow = vi.spyOn(browser, 'now'); const now = 1555555555555; browserNow.mockReturnValue(now); const map = createMap(); map._renderTaskQueue.run(); // Simulate a single 'wheel' event without the magical deltaY value. // This requires the handler to briefly wait to see if a subsequent // event is coming in order to guess trackpad vs. mouse wheel simulate.wheel(map.getCanvas(), {type: 'wheel', deltaY: -20}); await map.once('zoomstart'); map.remove(); }); test('Zooms for single mouse wheel tick with non-magical deltaY with easing for smooth zooming', async () => { const browserNow = vi.spyOn(browser, 'now'); let now = 1555555555555; browserNow.mockReturnValue(now); setPerformance(); const map = createMap(); map.setZoom(5); map._renderTaskQueue.run(); const startZoom = map.getZoom(); // simulate a single 'wheel' event with non-magical deltaY simulate.wheel(map.getCanvas(), {type: 'wheel', deltaY: -20}); await map.once('zoom'); now += 40; map._renderTaskQueue.run(); // A single tick zoom with easing completes in approx. 200ms now += 100; browserNow.mockReturnValue(now); map._renderTaskQueue.run(); const midZoom = map.getZoom(); now += 400; browserNow.mockReturnValue(now); map._renderTaskQueue.run(); const endZoom = map.getZoom(); expect(midZoom).toBeGreaterThan(startZoom); expect(midZoom).toBeLessThan(endZoom); map.remove(); }); test('Zooms for multiple mouse wheel ticks', () => { const browserNow = vi.spyOn(browser, 'now'); let now = 1555555555555; browserNow.mockReturnValue(now); const map = createMap(); map._renderTaskQueue.run(); // simulate 3 'wheel' events const startZoom = map.getZoom(); now += 2; browserNow.mockReturnValue(now); map._renderTaskQueue.run(); simulate.wheel(map.getCanvas(), {type: 'wheel', deltaY: -simulate.magicWheelZoomDelta}); map._renderTaskQueue.run(); now += 7; browserNow.mockReturnValue(now); map._renderTaskQueue.run(); simulate.wheel(map.getCanvas(), {type: 'wheel', deltaY: -simulate.magicWheelZoomDelta}); map._renderTaskQueue.run(); now += 30; browserNow.mockReturnValue(now); map._renderTaskQueue.run(); simulate.wheel(map.getCanvas(), {type: 'wheel', deltaY: -simulate.magicWheelZoomDelta}); map._renderTaskQueue.run(); now += 400; browserNow.mockReturnValue(now); map._renderTaskQueue.run(); expect(map.getZoom() - startZoom).toBeCloseTo(0.0285 * 3, 3); map.remove(); }); test('Zooms for multiple mouse wheel ticks with easing for smooth zooming', () => { const browserNow = vi.spyOn(browser, 'now'); let now = 1555555555555; browserNow.mockReturnValue(now); const map = createMap(); map.setZoom(5); map._renderTaskQueue.run(); // simulate 3 'wheel' events // Event 1 Start now += 2; browserNow.mockReturnValue(now); map._renderTaskQueue.run(); simulate.wheel(map.getCanvas(), {type: 'wheel', deltaY: -simulate.magicWheelZoomDelta}); const startZoomEvent1 = map.getZoom(); map._renderTaskQueue.run(); // Event 1 mid-zoom now += 3; browserNow.mockReturnValue(now); map._renderTaskQueue.run(); const midZoomEvent1 = map.getZoom(); // Event 2 Start now += 4; browserNow.mockReturnValue(now); map._renderTaskQueue.run(); const endZoomEvent1 = map.getZoom(); const startZoomEvent2 = map.getZoom(); simulate.wheel(map.getCanvas(), {type: 'wheel', deltaY: -simulate.magicWheelZoomDelta}); map._renderTaskQueue.run(); // Event 2 mid-zoom now += 15; browserNow.mockReturnValue(now); map._renderTaskQueue.run(); const midZoomEvent2 = map.getZoom(); // Event 3 Start now += 15; browserNow.mockReturnValue(now); map._renderTaskQueue.run(); const endZoomEvent2 = map.getZoom(); const startZoomEvent3 = map.getZoom(); simulate.wheel(map.getCanvas(), {type: 'wheel', deltaY: -simulate.magicWheelZoomDelta}); map._renderTaskQueue.run(); // Event 3 mid-zoom // A single tick zoom with easing completes in approx. 200ms now += 100; browserNow.mockReturnValue(now); map._renderTaskQueue.run(); const midZoomEvent3 = map.getZoom(); now += 400; browserNow.mockReturnValue(now); map._renderTaskQueue.run(); const endZoomEvent3 = map.getZoom(); expect(midZoomEvent1).toBeGreaterThan(startZoomEvent1); expect(midZoomEvent1).toBeLessThan(endZoomEvent1); expect(midZoomEvent2).toBeGreaterThan(startZoomEvent2); expect(midZoomEvent2).toBeLessThan(endZoomEvent2); expect(midZoomEvent3).toBeGreaterThan(startZoomEvent3); expect(midZoomEvent3).toBeLessThan(endZoomEvent3); map.remove(); }); test('Gracefully ignores wheel events with deltaY: 0', () => { const browserNow = vi.spyOn(browser, 'now'); let now = 1555555555555; browserNow.mockReturnValue(now); const map = createMap(); map._renderTaskQueue.run(); const startZoom = map.getZoom(); // simulate shift+'wheel' events simulate.wheel(map.getCanvas(), {type: 'wheel', deltaY: -0, shiftKey: true}); simulate.wheel(map.getCanvas(), {type: 'wheel', deltaY: -0, shiftKey: true}); simulate.wheel(map.getCanvas(), {type: 'wheel', deltaY: -0, shiftKey: true}); simulate.wheel(map.getCanvas(), {type: 'wheel', deltaY: -0, shiftKey: true}); map._renderTaskQueue.run(); now += 400; browserNow.mockReturnValue(now); map._renderTaskQueue.run(); expect(map.getZoom() - startZoom).toBe(0.0); }); test('Gracefully handle wheel events that cancel each other out before the first scroll frame', () => { // See also https://github.com/mapbox/mapbox-gl-js/issues/6782 const browserNow = vi.spyOn(browser, 'now'); let now = 1555555555555; browserNow.mockReturnValue(now); const map = createMap(); map._renderTaskQueue.run(); simulate.wheel(map.getCanvas(), {type: 'wheel', deltaY: -1}); simulate.wheel(map.getCanvas(), {type: 'wheel', deltaY: -1}); now += 1; browserNow.mockReturnValue(now); simulate.wheel(map.getCanvas(), {type: 'wheel', deltaY: 2}); map._renderTaskQueue.run(); now += 400; browserNow.mockReturnValue(now); map._renderTaskQueue.run(); }); test('does not zoom if preventDefault is called on the wheel event', () => { const browserNow = vi.spyOn(browser, 'now'); let now = 1555555555555; browserNow.mockReturnValue(now); const map = createMap(); map.on('wheel', e => e.preventDefault()); simulate.wheel(map.getCanvas(), {type: 'wheel', deltaY: -simulate.magicWheelZoomDelta}); map._renderTaskQueue.run(); now += 400; browserNow.mockReturnValue(now); map._renderTaskQueue.run(); expect(map.getZoom()).toBe(0); map.remove(); }); test('emits one movestart event and one moveend event while zooming', async () => { const browserNow = vi.spyOn(browser, 'now'); let now = 1555555555555; browserNow.mockReturnValue(now); setPerformance(); const map = createMap(); let startCount = 0; map.on('movestart', () => { startCount += 1; }); let endCount = 0; map.on('moveend', () => { endCount += 1; }); const events = [ [2, {type: 'trackpad', deltaY: -1}], [7, {type: 'trackpad', deltaY: -2}], [30, {type: 'wheel', deltaY: -5}] ] as [number, any][]; const end = now + 50; let lastWheelEvent = now; while (now < end) { now += 1; browserNow.mockReturnValue(now); if (events.length && lastWheelEvent + events[0][0] === now) { const [, event] = events.shift(); simulate.wheel(map.getCanvas(), event); lastWheelEvent = now; } if (now % 20 === 0) { map._renderTaskQueue.run(); } } await map.once('zoomend'); map._renderTaskQueue.run(); expect(startCount).toBe(1); expect(endCount).toBe(1); }); test('emits one zoomstart event and one zoomend event while zooming', async () => { const browserNow = vi.spyOn(browser, 'now'); let now = 1555555555555; browserNow.mockReturnValue(now); setPerformance(); const map = createMap(); let startCount = 0; map.on('zoomstart', () => { startCount += 1; }); let endCount = 0; map.on('zoomend', () => { endCount += 1; }); const events = [ [2, {type: 'trackpad', deltaY: -1}], [7, {type: 'trackpad', deltaY: -2}], [30, {type: 'wheel', deltaY: -5}], ] as [number, any][]; const end = now + 50; let lastWheelEvent = now; while (now < end) { now += 1; browserNow.mockReturnValue(now); if (events.length && lastWheelEvent + events[0][0] === now) { const [, event] = events.shift(); simulate.wheel(map.getCanvas(), event); lastWheelEvent = now; } if (now % 20 === 0) { map._renderTaskQueue.run(); } } await map.once('zoomend'); map._renderTaskQueue.run(); expect(startCount).toBe(1); expect(endCount).toBe(1); }); test('Zooms for single mouse wheel tick while in the center of the map, should zoom to center', () => { const browserNow = vi.spyOn(browser, 'now'); let now = 1555555555555; browserNow.mockReturnValue(now); const map = createMap(); map._renderTaskQueue.run(); expect(map.getCenter().lat).toBeCloseTo(0, 10); expect(map.getCenter().lng).toBeCloseTo(0, 10); // simulate a single 'wheel' event simulate.wheel(map.getCanvas(), {type: 'wheel', deltaY: -simulate.magicWheelZoomDelta, clientX: 200, clientY: 150}); map._renderTaskQueue.run(); now += 400; browserNow.mockReturnValue(now); map._renderTaskQueue.run(); expect(map.getCenter().lat).toBeCloseTo(0, 10); expect(map.getCenter().lng).toBeCloseTo(0, 10); expect(map.getZoom()).toBeCloseTo(0.028567106927402726, 10); map.remove(); }); test('Zooms for single mouse wheel tick while not in the center of the map, should zoom according to mouse position', () => { const browserNow = vi.spyOn(browser, 'now'); let now = 1555555555555; browserNow.mockReturnValue(now); const map = createMap(); map._elevateCameraIfInsideTerrain = (_tr : any) => ({}); map._renderTaskQueue.run(); map.terrain = createTerrain(); // simulate a single 'wheel' event simulate.wheel(map.getCanvas(), {type: 'wheel', deltaY: -simulate.magicWheelZoomDelta, clientX: 1000, clientY: 1000}); map._renderTaskQueue.run(); now += 400; browserNow.mockReturnValue(now); map._renderTaskQueue.run(); expect(map.getCenter().lat).toBeCloseTo(-11.6371, 3); expect(map.getCenter().lng).toBeCloseTo(11.0286, 3); expect(map.getZoom()).toBeCloseTo(0.028567106927402726, 10); map.remove(); }); test('Zooms for single mouse wheel tick while not in the center of the map and terrain is on, should zoom according to mouse position', () => { const browserNow = vi.spyOn(browser, 'now'); let now = 1555555555555; browserNow.mockReturnValue(now); const map = createMap(); map._elevateCameraIfInsideTerrain = (_tr : any) => ({}); map._renderTaskQueue.run(); map.terrain = createTerrain(); // simulate a single 'wheel' event simulate.wheel(map.getCanvas(), {type: 'wheel', deltaY: -simulate.magicWheelZoomDelta, clientX: 1000, clientY: 1000}); map._renderTaskQueue.run(); now += 400; browserNow.mockReturnValue(now); map._renderTaskQueue.run(); expect(map.getCenter().lat).toBeCloseTo(-11.6371, 3); expect(map.getCenter().lng).toBeCloseTo(11.0286, 3); map.remove(); }); test('Terrain 3D zoom is in the same direction when pointing above horizon or under horizon', () => { // See also https://github.com/maplibre/maplibre-gl-js/issues/3398 const browserNow = vi.spyOn(browser, 'now'); let now = 1555555555555; browserNow.mockReturnValue(now); let map = createMap(); map._elevateCameraIfInsideTerrain = (_tr : any) => ({}); map._renderTaskQueue.run(); map.terrain = createTerrain(); map.setZoom(5); map.setMaxPitch(85); map.setPitch(80); map._renderTaskQueue.run(); // simulate a single 'wheel' event on top of screen simulate.wheel(map.getCanvas(), {type: 'wheel', deltaY: -simulate.magicWheelZoomDelta, clientX: map.getCanvas().width / 2, clientY: 10}); map._renderTaskQueue.run(); now += 400; browserNow.mockReturnValue(now); map._renderTaskQueue.run(); // On Top, use center point expect(map.getCenter().lat).toBeCloseTo(0, 3); expect(map.getCenter().lng).toBeCloseTo(0, 3); expect(map.getZoom()).toBeCloseTo(5.02856, 3); map.remove(); // do the same test on the bottom map = createMap(); map._elevateCameraIfInsideTerrain = (_tr : any) => ({}); map._renderTaskQueue.run(); map.terrain = createTerrain(); map.setZoom(5); map.setMaxPitch(85); map.setPitch(80); map._renderTaskQueue.run(); // simulate a single 'wheel' event on bottom of screen simulate.wheel(map.getCanvas(), {type: 'wheel', deltaY: -simulate.magicWheelZoomDelta, clientX: map.getCanvas().width / 2, clientY: map.getCanvas().height - 10}); map._renderTaskQueue.run(); now += 400; browserNow.mockReturnValue(now); map._renderTaskQueue.run(); expect(map.getCenter().lat).toBeCloseTo(-0.125643, 3); expect(map.getCenter().lng).toBeCloseTo(0.0, 3); expect(map.getZoom()).toBeCloseTo(5.02856, 3); map.remove(); }); test('Clamps zoom at high latitude to keep globe consistent size', async () => { const browserNow = vi.spyOn(browser, 'now'); const map = createMap(); await map.once('load'); map.setProjection({type: 'globe'}); map.setMinZoom(0); scrollOutAtLat(map, 80, browserNow); expect(map.getZoom()).toBeCloseTo(-2.53, 2); scrollOutAtLat(map, -80, browserNow); expect(map.getZoom()).toBeCloseTo(-2.53, 2); scrollOutAtLat(map, 0, browserNow); expect(map.getZoom()).toBeCloseTo(0, 2); }); test('Clamps zoom at high latitude to keep globe consistent size using mouse wheel', async () => { const browserNow = vi.spyOn(browser, 'now'); const map = createMap(); await map.once('load'); map.setProjection({type: 'globe'}); map.setMinZoom(0); scrollOutAtLat(map, 80, browserNow, simulate.magicWheelZoomDelta); expect(map.getZoom()).toBeCloseTo(-2.53, 2); scrollOutAtLat(map, -80, browserNow, simulate.magicWheelZoomDelta); expect(map.getZoom()).toBeCloseTo(-2.53, 2); scrollOutAtLat(map, 0, browserNow, simulate.magicWheelZoomDelta); expect(map.getZoom()).toBeCloseTo(0, 2); }); test('Clamps to min/max zoom when using mercator projection', async () => { const browserNow = vi.spyOn(browser, 'now'); const map = createMap(); await map.once('load'); map.setProjection({type: 'mercator'}); map.setMinZoom(0); scrollOutAtLat(map, 80, browserNow); expect(map.getZoom()).toBeCloseTo(0, 2); scrollOutAtLat(map, -80, browserNow); expect(map.getZoom()).toBeCloseTo(0, 2); scrollOutAtLat(map, 0, browserNow); expect(map.getZoom()).toBeCloseTo(0, 2); }); });