maplibre-gl
Version:
BSD licensed community fork of mapbox-gl, a WebGL interactive maps library
1,478 lines (1,218 loc) • 63.4 kB
text/typescript
import Camera from '../ui/camera';
import Transform from '../geo/transform';
import TaskQueue, {TaskID} from '../util/task_queue';
import browser from '../util/browser';
import {fixedLngLat, fixedNum} from '../../test/unit/lib/fixed';
import {setMatchMedia} from '../util/test/util';
beforeEach(() => {
setMatchMedia();
Object.defineProperty(browser, 'prefersReducedMotion', {value: false});
});
class CameraMock extends Camera {
// eslint-disable-next-line
_requestRenderFrame(a: () => void): TaskID {
return undefined;
}
_cancelRenderFrame(_: TaskID): void {
return undefined;
}
}
function attachSimulateFrame(camera) {
const queue = new TaskQueue();
camera._requestRenderFrame = (cb) => queue.add(cb);
camera._cancelRenderFrame = (id) => queue.remove(id);
camera.simulateFrame = () => queue.run();
return camera;
}
function createCamera(options?) {
options = options || {};
const transform = new Transform(0, 20, 0, 60, options.renderWorldCopies);
transform.resize(512, 512);
const camera = attachSimulateFrame(new CameraMock(transform, {} as any))
.jumpTo(options);
camera._update = () => {};
return camera;
}
function assertTransitionTime(done, camera, min, max) {
let startTime;
camera
.on('movestart', () => { startTime = new Date(); })
.on('moveend', () => {
const endTime = new Date();
const timeDiff = endTime.getTime() - startTime.getTime();
expect(timeDiff >= min && timeDiff < max).toBeTruthy();
done();
});
}
describe('#jumpTo', () => {
// Choose initial zoom to avoid center being constrained by mercator latitude limits.
const camera = createCamera({zoom: 1});
test('sets center', () => {
camera.jumpTo({center: [1, 2]});
expect(camera.getCenter()).toEqual({lng: 1, lat: 2});
});
test('throws on invalid center argument', () => {
expect(() => {
camera.jumpTo({center: 1});
}).toThrow(Error);
});
test('keeps current center if not specified', () => {
camera.jumpTo({});
expect(camera.getCenter()).toEqual({lng: 1, lat: 2});
});
test('sets zoom', () => {
camera.jumpTo({zoom: 3});
expect(camera.getZoom()).toBe(3);
});
test('keeps current zoom if not specified', () => {
camera.jumpTo({});
expect(camera.getZoom()).toBe(3);
});
test('sets bearing', () => {
camera.jumpTo({bearing: 4});
expect(camera.getBearing()).toBe(4);
});
test('keeps current bearing if not specified', () => {
camera.jumpTo({});
expect(camera.getBearing()).toBe(4);
});
test('sets pitch', () => {
camera.jumpTo({pitch: 45});
expect(camera.getPitch()).toBe(45);
});
test('keeps current pitch if not specified', () => {
camera.jumpTo({});
expect(camera.getPitch()).toBe(45);
});
test('sets multiple properties', () => {
camera.jumpTo({
center: [10, 20],
zoom: 10,
bearing: 180,
pitch: 60
});
expect(camera.getCenter()).toEqual({lng: 10, lat: 20});
expect(camera.getZoom()).toBe(10);
expect(camera.getBearing()).toBe(180);
expect(camera.getPitch()).toBe(60);
});
test('emits move events, preserving eventData', done => {
let started, moved, ended;
const eventData = {data: 'ok'};
camera
.on('movestart', (d) => { started = d.data; })
.on('move', (d) => { moved = d.data; })
.on('moveend', (d) => { ended = d.data; });
camera.jumpTo({center: [1, 2]}, eventData);
expect(started).toBe('ok');
expect(moved).toBe('ok');
expect(ended).toBe('ok');
done();
});
test('emits zoom events, preserving eventData', done => {
let started, zoomed, ended;
const eventData = {data: 'ok'};
camera
.on('zoomstart', (d) => { started = d.data; })
.on('zoom', (d) => { zoomed = d.data; })
.on('zoomend', (d) => { ended = d.data; });
camera.jumpTo({zoom: 3}, eventData);
expect(started).toBe('ok');
expect(zoomed).toBe('ok');
expect(ended).toBe('ok');
done();
});
test('emits rotate events, preserving eventData', done => {
let started, rotated, ended;
const eventData = {data: 'ok'};
camera
.on('rotatestart', (d) => { started = d.data; })
.on('rotate', (d) => { rotated = d.data; })
.on('rotateend', (d) => { ended = d.data; });
camera.jumpTo({bearing: 90}, eventData);
expect(started).toBe('ok');
expect(rotated).toBe('ok');
expect(ended).toBe('ok');
done();
});
test('emits pitch events, preserving eventData', done => {
let started, pitched, ended;
const eventData = {data: 'ok'};
camera
.on('pitchstart', (d) => { started = d.data; })
.on('pitch', (d) => { pitched = d.data; })
.on('pitchend', (d) => { ended = d.data; });
camera.jumpTo({pitch: 10}, eventData);
expect(started).toBe('ok');
expect(pitched).toBe('ok');
expect(ended).toBe('ok');
done();
});
test('cancels in-progress easing', () => {
camera.panTo([3, 4]);
expect(camera.isEasing()).toBeTruthy();
camera.jumpTo({center: [1, 2]});
expect(!camera.isEasing()).toBeTruthy();
});
});
describe('#setCenter', () => {
// Choose initial zoom to avoid center being constrained by mercator latitude limits.
const camera = createCamera({zoom: 1});
test('sets center', () => {
camera.setCenter([1, 2]);
expect(camera.getCenter()).toEqual({lng: 1, lat: 2});
});
test('throws on invalid center argument', () => {
expect(() => {
camera.jumpTo({center: 1});
}).toThrow(Error);
});
test('emits move events, preserving eventData', done => {
let started, moved, ended;
const eventData = {data: 'ok'};
camera.on('movestart', (d) => { started = d.data; })
.on('move', (d) => { moved = d.data; })
.on('moveend', (d) => { ended = d.data; });
camera.setCenter([10, 20], eventData);
expect(started).toBe('ok');
expect(moved).toBe('ok');
expect(ended).toBe('ok');
done();
});
test('cancels in-progress easing', () => {
camera.panTo([3, 4]);
expect(camera.isEasing()).toBeTruthy();
camera.setCenter([1, 2]);
expect(!camera.isEasing()).toBeTruthy();
});
});
describe('#setZoom', () => {
const camera = createCamera();
test('sets zoom', () => {
camera.setZoom(3);
expect(camera.getZoom()).toBe(3);
});
test('emits move and zoom events, preserving eventData', done => {
let movestarted, moved, moveended, zoomstarted, zoomed, zoomended;
const eventData = {data: 'ok'};
camera
.on('movestart', (d) => { movestarted = d.data; })
.on('move', (d) => { moved = d.data; })
.on('moveend', (d) => { moveended = d.data; })
.on('zoomstart', (d) => { zoomstarted = d.data; })
.on('zoom', (d) => { zoomed = d.data; })
.on('zoomend', (d) => { zoomended = d.data; });
camera.setZoom(4, eventData);
expect(movestarted).toBe('ok');
expect(moved).toBe('ok');
expect(moveended).toBe('ok');
expect(zoomstarted).toBe('ok');
expect(zoomed).toBe('ok');
expect(zoomended).toBe('ok');
done();
});
test('cancels in-progress easing', () => {
camera.panTo([3, 4]);
expect(camera.isEasing()).toBeTruthy();
camera.setZoom(5);
expect(!camera.isEasing()).toBeTruthy();
});
});
describe('#setBearing', () => {
const camera = createCamera();
test('sets bearing', () => {
camera.setBearing(4);
expect(camera.getBearing()).toBe(4);
});
test('emits move and rotate events, preserving eventData', done => {
let movestarted, moved, moveended, rotatestarted, rotated, rotateended;
const eventData = {data: 'ok'};
camera
.on('movestart', (d) => { movestarted = d.data; })
.on('move', (d) => { moved = d.data; })
.on('moveend', (d) => { moveended = d.data; })
.on('rotatestart', (d) => { rotatestarted = d.data; })
.on('rotate', (d) => { rotated = d.data; })
.on('rotateend', (d) => { rotateended = d.data; });
camera.setBearing(5, eventData);
expect(movestarted).toBe('ok');
expect(moved).toBe('ok');
expect(moveended).toBe('ok');
expect(rotatestarted).toBe('ok');
expect(rotated).toBe('ok');
expect(rotateended).toBe('ok');
done();
});
test('cancels in-progress easing', () => {
camera.panTo([3, 4]);
expect(camera.isEasing()).toBeTruthy();
camera.setBearing(6);
expect(!camera.isEasing()).toBeTruthy();
});
});
describe('#setPadding', () => {
test('sets padding', () => {
const camera = createCamera();
const padding = {left: 300, top: 100, right: 50, bottom: 10};
camera.setPadding(padding);
expect(camera.getPadding()).toEqual(padding);
});
test('existing padding is retained if no new values are passed in', () => {
const camera = createCamera();
const padding = {left: 300, top: 100, right: 50, bottom: 10};
camera.setPadding(padding);
camera.setPadding({});
const currentPadding = camera.getPadding();
expect(currentPadding).toEqual(padding);
});
test('doesnt change padding thats already present if new value isnt passed in', () => {
const camera = createCamera();
const padding = {left: 300, top: 100, right: 50, bottom: 10};
camera.setPadding(padding);
const padding1 = {right: 100};
camera.setPadding(padding1);
const currentPadding = camera.getPadding();
expect(currentPadding.left).toBe(padding.left);
expect(currentPadding.top).toBe(padding.top);
// padding1 here
expect(currentPadding.right).toBe(padding1.right);
expect(currentPadding.bottom).toBe(padding.bottom);
});
});
describe('#panBy', () => {
test('pans by specified amount', () => {
const camera = createCamera();
camera.panBy([100, 0], {duration: 0});
expect(fixedLngLat(camera.getCenter())).toEqual({lng: 70.3125, lat: 0});
});
test('pans relative to viewport on a rotated camera', () => {
const camera = createCamera({bearing: 180});
camera.panBy([100, 0], {duration: 0});
expect(fixedLngLat(camera.getCenter())).toEqual({lng: -70.3125, lat: 0});
});
test('emits move events, preserving eventData', done => {
const camera = createCamera();
let started, moved;
const eventData = {data: 'ok'};
camera
.on('movestart', (d) => { started = d.data; })
.on('move', (d) => { moved = d.data; })
.on('moveend', (d) => {
expect(started).toBe('ok');
expect(moved).toBe('ok');
expect(d.data).toBe('ok');
done();
});
camera.panBy([100, 0], {duration: 0}, eventData);
});
test('supresses movestart if noMoveStart option is true', done => {
const camera = createCamera();
let started;
// fire once in advance to satisfy assertions that moveend only comes after movestart
camera.fire('movestart');
camera
.on('movestart', () => { started = true; })
.on('moveend', () => {
expect(!started).toBeTruthy();
done();
});
camera.panBy([100, 0], {duration: 0, noMoveStart: true});
});
});
describe('#panTo', () => {
test('pans to specified location', () => {
const camera = createCamera();
camera.panTo([100, 0], {duration: 0});
expect(camera.getCenter()).toEqual({lng: 100, lat: 0});
});
test('throws on invalid center argument', () => {
const camera = createCamera();
expect(() => {
camera.panTo({center: 1});
}).toThrow(Error);
});
test('pans with specified offset', () => {
const camera = createCamera();
camera.panTo([100, 0], {offset: [100, 0], duration: 0});
expect(fixedLngLat(camera.getCenter())).toEqual({lng: 29.6875, lat: 0});
});
test('pans with specified offset relative to viewport on a rotated camera', () => {
const camera = createCamera({bearing: 180});
camera.panTo([100, 0], {offset: [100, 0], duration: 0});
expect(fixedLngLat(camera.getCenter())).toEqual({lng: 170.3125, lat: 0});
});
test('emits move events, preserving eventData', done => {
const camera = createCamera();
let started, moved;
const eventData = {data: 'ok'};
camera
.on('movestart', (d) => { started = d.data; })
.on('move', (d) => { moved = d.data; })
.on('moveend', (d) => {
expect(started).toBe('ok');
expect(moved).toBe('ok');
expect(d.data).toBe('ok');
done();
});
camera.panTo([100, 0], {duration: 0}, eventData);
});
test('supresses movestart if noMoveStart option is true', done => {
const camera = createCamera();
let started;
// fire once in advance to satisfy assertions that moveend only comes after movestart
camera.fire('movestart');
camera
.on('movestart', () => { started = true; })
.on('moveend', () => {
expect(!started).toBeTruthy();
done();
});
camera.panTo([100, 0], {duration: 0, noMoveStart: true});
});
});
describe('#zoomTo', () => {
test('zooms to specified level', () => {
const camera = createCamera();
camera.zoomTo(3.2, {duration: 0});
expect(camera.getZoom()).toBe(3.2);
});
test('zooms around specified location', () => {
const camera = createCamera();
camera.zoomTo(3.2, {around: [5, 0], duration: 0});
expect(camera.getZoom()).toBe(3.2);
expect(fixedLngLat(camera.getCenter())).toEqual(fixedLngLat({lng: 4.455905897939886, lat: 0}));
});
test('zooms with specified offset', () => {
const camera = createCamera();
camera.zoomTo(3.2, {offset: [100, 0], duration: 0});
expect(camera.getZoom()).toBe(3.2);
expect(fixedLngLat(camera.getCenter())).toEqual(fixedLngLat({lng: 62.66117668978015, lat: 0}));
});
test('zooms with specified offset relative to viewport on a rotated camera', () => {
const camera = createCamera({bearing: 180});
camera.zoomTo(3.2, {offset: [100, 0], duration: 0});
expect(camera.getZoom()).toBe(3.2);
expect(fixedLngLat(camera.getCenter())).toEqual(fixedLngLat({lng: -62.66117668978012, lat: 0}));
});
test('emits move and zoom events, preserving eventData', done => {
const camera = createCamera();
let movestarted, moved, zoomstarted, zoomed;
const eventData = {data: 'ok'};
expect.assertions(6);
camera
.on('movestart', (d) => { movestarted = d.data; })
.on('move', (d) => { moved = d.data; })
.on('moveend', (d) => {
expect(movestarted).toBe('ok');
expect(moved).toBe('ok');
expect(d.data).toBe('ok');
});
camera
.on('zoomstart', (d) => { zoomstarted = d.data; })
.on('zoom', (d) => { zoomed = d.data; })
.on('zoomend', (d) => {
expect(zoomstarted).toBe('ok');
expect(zoomed).toBe('ok');
expect(d.data).toBe('ok');
});
camera.zoomTo(5, {duration: 0}, eventData);
done();
});
});
describe('#rotateTo', () => {
test('rotates to specified bearing', () => {
const camera = createCamera();
camera.rotateTo(90, {duration: 0});
expect(camera.getBearing()).toBe(90);
});
test('rotates around specified location', () => {
const camera = createCamera({zoom: 3});
camera.rotateTo(90, {around: [5, 0], duration: 0});
expect(camera.getBearing()).toBe(90);
expect(fixedLngLat(camera.getCenter())).toEqual(fixedLngLat({lng: 4.999999999999972, lat: 4.993665859353271}));
});
test('rotates around specified location, constrained to fit the view', () => {
const camera = createCamera({zoom: 0});
camera.rotateTo(90, {around: [5, 0], duration: 0});
expect(camera.getBearing()).toBe(90);
expect(fixedLngLat(camera.getCenter())).toEqual(fixedLngLat({lng: 4.999999999999972, lat: 0.000002552471840999715}));
});
test('rotates with specified offset', () => {
const camera = createCamera({zoom: 1});
camera.rotateTo(90, {offset: [200, 0], duration: 0});
expect(camera.getBearing()).toBe(90);
expect(fixedLngLat(camera.getCenter())).toEqual(fixedLngLat({lng: 70.3125, lat: 57.3265212252}));
});
test('rotates with specified offset, constrained to fit the view', () => {
const camera = createCamera({zoom: 0});
camera.rotateTo(90, {offset: [100, 0], duration: 0});
expect(camera.getBearing()).toBe(90);
expect(fixedLngLat(camera.getCenter())).toEqual(fixedLngLat({lng: 70.3125, lat: 0.000002552471840999715}));
});
test('rotates with specified offset relative to viewport on a rotated camera', () => {
const camera = createCamera({bearing: 180, zoom: 1});
camera.rotateTo(90, {offset: [200, 0], duration: 0});
expect(camera.getBearing()).toBe(90);
expect(fixedLngLat(camera.getCenter())).toEqual(fixedLngLat({lng: -70.3125, lat: 57.3265212252}));
});
test('emits move and rotate events, preserving eventData', done => {
const camera = createCamera();
let movestarted, moved, rotatestarted, rotated;
const eventData = {data: 'ok'};
expect.assertions(6);
camera
.on('movestart', (d) => { movestarted = d.data; })
.on('move', (d) => { moved = d.data; })
.on('moveend', (d) => {
expect(movestarted).toBe('ok');
expect(moved).toBe('ok');
expect(d.data).toBe('ok');
});
camera
.on('rotatestart', (d) => { rotatestarted = d.data; })
.on('rotate', (d) => { rotated = d.data; })
.on('rotateend', (d) => {
expect(rotatestarted).toBe('ok');
expect(rotated).toBe('ok');
expect(d.data).toBe('ok');
});
camera.rotateTo(90, {duration: 0}, eventData);
done();
});
});
describe('#easeTo', () => {
test('pans to specified location', () => {
const camera = createCamera();
camera.easeTo({center: [100, 0], duration: 0});
expect(camera.getCenter()).toEqual({lng: 100, lat: 0});
});
test('zooms to specified level', () => {
const camera = createCamera();
camera.easeTo({zoom: 3.2, duration: 0});
expect(camera.getZoom()).toBe(3.2);
});
test('rotates to specified bearing', () => {
const camera = createCamera();
camera.easeTo({bearing: 90, duration: 0});
expect(camera.getBearing()).toBe(90);
});
test('pitches to specified pitch', () => {
const camera = createCamera();
camera.easeTo({pitch: 45, duration: 0});
expect(camera.getPitch()).toBe(45);
});
test('pans and zooms', () => {
const camera = createCamera();
camera.easeTo({center: [100, 0], zoom: 3.2, duration: 0});
expect(fixedLngLat(camera.getCenter())).toEqual(fixedLngLat({lng: 100, lat: 0}));
expect(camera.getZoom()).toBe(3.2);
});
test('zooms around a point', () => {
const camera = createCamera();
camera.easeTo({around: [100, 0], zoom: 3, duration: 0});
expect(fixedLngLat(camera.getCenter())).toEqual(fixedLngLat({lng: 87.5, lat: 0}));
expect(camera.getZoom()).toBe(3);
});
test('pans and rotates', () => {
const camera = createCamera();
camera.easeTo({center: [100, 0], bearing: 90, duration: 0});
expect(camera.getCenter()).toEqual({lng: 100, lat: 0});
expect(camera.getBearing()).toBe(90);
});
test('zooms and rotates', () => {
const camera = createCamera();
camera.easeTo({zoom: 3.2, bearing: 90, duration: 0});
expect(camera.getZoom()).toBe(3.2);
expect(camera.getBearing()).toBe(90);
});
test('pans, zooms, and rotates', () => {
const camera = createCamera({bearing: -90});
camera.easeTo({center: [100, 0], zoom: 3.2, bearing: 90, duration: 0});
expect(fixedLngLat(camera.getCenter())).toEqual(fixedLngLat({lng: 100, lat: 0}));
expect(camera.getZoom()).toBe(3.2);
expect(camera.getBearing()).toBe(90);
});
test('noop', () => {
const camera = createCamera();
camera.easeTo({duration: 0});
expect(fixedLngLat(camera.getCenter())).toEqual({lng: 0, lat: 0});
expect(camera.getZoom()).toBe(0);
expect(camera.getBearing()).toBeCloseTo(0);
});
test('noop with offset', () => {
const camera = createCamera();
camera.easeTo({offset: [100, 0], duration: 0});
expect(fixedLngLat(camera.getCenter())).toEqual({lng: 0, lat: 0});
expect(camera.getZoom()).toBe(0);
expect(camera.getBearing()).toBeCloseTo(0);
});
test('pans with specified offset', () => {
const camera = createCamera();
camera.easeTo({center: [100, 0], offset: [100, 0], duration: 0});
expect(fixedLngLat(camera.getCenter())).toEqual({lng: 29.6875, lat: 0});
});
test('pans with specified offset relative to viewport on a rotated camera', () => {
const camera = createCamera({bearing: 180});
camera.easeTo({center: [100, 0], offset: [100, 0], duration: 0});
expect(fixedLngLat(camera.getCenter())).toEqual({lng: 170.3125, lat: 0});
});
test('zooms with specified offset', () => {
const camera = createCamera();
camera.easeTo({zoom: 3.2, offset: [100, 0], duration: 0});
expect(camera.getZoom()).toBe(3.2);
expect(fixedLngLat(camera.getCenter())).toEqual(fixedLngLat({lng: 62.66117668978015, lat: 0}));
});
test('zooms with specified offset relative to viewport on a rotated camera', () => {
const camera = createCamera({bearing: 180});
camera.easeTo({zoom: 3.2, offset: [100, 0], duration: 0});
expect(camera.getZoom()).toBe(3.2);
expect(fixedLngLat(camera.getCenter())).toEqual(fixedLngLat({lng: -62.66117668978012, lat: 0}));
});
test('rotates with specified offset', () => {
const camera = createCamera();
camera.easeTo({bearing: 90, offset: [100, 0], duration: 0});
expect(camera.getBearing()).toBe(90);
expect(fixedLngLat(camera.getCenter())).toEqual(fixedLngLat({lng: 70.3125, lat: 0.000002552471840999715}));
});
test('rotates with specified offset relative to viewport on a rotated camera', () => {
const camera = createCamera({bearing: 180});
camera.easeTo({bearing: 90, offset: [100, 0], duration: 0});
expect(camera.getBearing()).toBe(90);
expect(fixedLngLat(camera.getCenter())).toEqual(fixedLngLat({lng: -70.3125, lat: 0.000002552471840999715}));
});
test('emits move, zoom, rotate, and pitch events, preserving eventData', done => {
const camera = createCamera();
let movestarted, moved, zoomstarted, zoomed, rotatestarted, rotated, pitchstarted, pitched;
const eventData = {data: 'ok'};
expect.assertions(18);
camera
.on('movestart', (d) => { movestarted = d.data; })
.on('move', (d) => { moved = d.data; })
.on('moveend', (d) => {
expect(camera._zooming).toBeFalsy();
expect(camera._panning).toBeFalsy();
expect(camera._rotating).toBeFalsy();
expect(movestarted).toBe('ok');
expect(moved).toBe('ok');
expect(zoomed).toBe('ok');
expect(rotated).toBe('ok');
expect(pitched).toBe('ok');
expect(d.data).toBe('ok');
});
camera
.on('zoomstart', (d) => { zoomstarted = d.data; })
.on('zoom', (d) => { zoomed = d.data; })
.on('zoomend', (d) => {
expect(zoomstarted).toBe('ok');
expect(zoomed).toBe('ok');
expect(d.data).toBe('ok');
});
camera
.on('rotatestart', (d) => { rotatestarted = d.data; })
.on('rotate', (d) => { rotated = d.data; })
.on('rotateend', (d) => {
expect(rotatestarted).toBe('ok');
expect(rotated).toBe('ok');
expect(d.data).toBe('ok');
});
camera
.on('pitchstart', (d) => { pitchstarted = d.data; })
.on('pitch', (d) => { pitched = d.data; })
.on('pitchend', (d) => {
expect(pitchstarted).toBe('ok');
expect(pitched).toBe('ok');
expect(d.data).toBe('ok');
});
camera.easeTo(
{center: [100, 0], zoom: 3.2, bearing: 90, duration: 0, pitch: 45},
eventData);
done();
});
test('does not emit zoom events if not zooming', done => {
const camera = createCamera();
camera
.on('zoomstart', () => { done('zoomstart failed'); })
.on('zoom', () => { done('zoom failed'); })
.on('zoomend', () => { done('zoomend failed'); })
.on('moveend', () => { done(); });
camera.easeTo({center: [100, 0], duration: 0});
});
test('stops existing ease', () => {
const camera = createCamera();
camera.easeTo({center: [200, 0], duration: 100});
camera.easeTo({center: [100, 0], duration: 0});
expect(camera.getCenter()).toEqual({lng: 100, lat: 0});
});
test('can be called from within a moveend event handler', done => {
const camera = createCamera();
const stub = jest.spyOn(browser, 'now');
stub.mockImplementation(() => 0);
camera.easeTo({center: [100, 0], duration: 10});
camera.once('moveend', () => {
camera.easeTo({center: [200, 0], duration: 10});
camera.once('moveend', () => {
camera.easeTo({center: [300, 0], duration: 10});
camera.once('moveend', () => {
done();
});
setTimeout(() => {
stub.mockImplementation(() => 30);
camera.simulateFrame();
}, 0);
});
// setTimeout to avoid a synchronous callback
setTimeout(() => {
stub.mockImplementation(() => 20);
camera.simulateFrame();
}, 0);
});
// setTimeout to avoid a synchronous callback
setTimeout(() => {
stub.mockImplementation(() => 10);
camera.simulateFrame();
}, 0);
});
test('pans eastward across the antimeridian', done => {
const camera = createCamera();
const stub = jest.spyOn(browser, 'now');
camera.setCenter([170, 0]);
let crossedAntimeridian;
camera.on('move', () => {
if (camera.getCenter().lng > 170) {
crossedAntimeridian = true;
}
});
camera.on('moveend', () => {
expect(crossedAntimeridian).toBeTruthy();
done();
});
stub.mockImplementation(() => 0);
camera.easeTo({center: [-170, 0], duration: 10});
setTimeout(() => {
stub.mockImplementation(() => 1);
camera.simulateFrame();
setTimeout(() => {
stub.mockImplementation(() => 10);
camera.simulateFrame();
}, 0);
}, 0);
});
test('pans westward across the antimeridian', done => {
const camera = createCamera();
const stub = jest.spyOn(browser, 'now');
camera.setCenter([-170, 0]);
let crossedAntimeridian;
camera.on('move', () => {
if (camera.getCenter().lng < -170) {
crossedAntimeridian = true;
}
});
camera.on('moveend', () => {
expect(crossedAntimeridian).toBeTruthy();
done();
});
stub.mockImplementation(() => 0);
camera.easeTo({center: [170, 0], duration: 10});
setTimeout(() => {
stub.mockImplementation(() => 1);
camera.simulateFrame();
setTimeout(() => {
stub.mockImplementation(() => 10);
camera.simulateFrame();
}, 0);
}, 0);
});
test('animation occurs when prefers-reduced-motion: reduce is set but overridden by essential: true', done => {
const camera = createCamera();
Object.defineProperty(browser, 'prefersReducedMotion', {value: true});
const stubNow = jest.spyOn(browser, 'now');
// camera transition expected to take in this range when prefersReducedMotion is set and essential: true,
// when a duration of 200 is requested
const min = 100;
const max = 300;
let startTime;
camera
.on('movestart', () => { startTime = browser.now(); })
.on('moveend', () => {
const endTime = browser.now();
const timeDiff = endTime - startTime;
expect(timeDiff >= min && timeDiff < max).toBeTruthy();
done();
});
setTimeout(() => {
stubNow.mockImplementation(() => 0);
camera.simulateFrame();
camera.easeTo({center: [100, 0], zoom: 3.2, bearing: 90, duration: 200, essential: true});
setTimeout(() => {
stubNow.mockImplementation(() => 200);
camera.simulateFrame();
}, 0);
}, 0);
});
test('duration is 0 when prefers-reduced-motion: reduce is set', done => {
const camera = createCamera();
Object.defineProperty(browser, 'prefersReducedMotion', {value: true});
assertTransitionTime(done, camera, 0, 10);
camera.easeTo({center: [100, 0], zoom: 3.2, bearing: 90, duration: 1000});
});
});
describe('#flyTo', () => {
test('pans to specified location', () => {
const camera = createCamera();
camera.flyTo({center: [100, 0], animate: false});
expect(fixedLngLat(camera.getCenter())).toEqual({lng: 100, lat: 0});
});
test('throws on invalid center argument', () => {
const camera = createCamera();
expect(() => {
camera.flyTo({center: 1});
}).toThrow(Error);
});
test('does not throw when cameras current zoom is sufficiently greater than passed zoom option', () => {
const camera = createCamera({zoom: 22, center: [0, 0]});
expect(() => camera.flyTo({zoom: 10, center: [0, 0]})).not.toThrow();
});
test('does not throw when cameras current zoom is above maxzoom and an offset creates infinite zoom out factor', () => {
const transform = new Transform(0, 20.9999, 0, 60, true);
transform.resize(512, 512);
const camera = attachSimulateFrame(new CameraMock(transform, {} as any))
.jumpTo({zoom: 21, center: [0, 0]});
camera._update = () => {};
expect(() => camera.flyTo({zoom: 7.5, center: [0, 0], offset: [0, 70]})).not.toThrow();
});
test('zooms to specified level', () => {
const camera = createCamera();
camera.flyTo({zoom: 3.2, animate: false});
expect(fixedNum(camera.getZoom())).toBe(3.2);
});
test('zooms to integer level without floating point errors', () => {
const camera = createCamera({zoom: 0.6});
camera.flyTo({zoom: 2, animate: false});
expect(camera.getZoom()).toBe(2);
});
test('Zoom out from the same position to the same position with animation', done => {
const pos = {lng: 0, lat: 0};
const camera = createCamera({zoom: 20, center: pos});
const stub = jest.spyOn(browser, 'now');
camera.once('zoomend', () => {
expect(fixedLngLat(camera.getCenter())).toEqual(fixedLngLat(pos));
expect(camera.getZoom()).toBe(19);
done();
});
stub.mockImplementation(() => 0);
camera.flyTo({zoom: 19, center: pos, duration: 2});
stub.mockImplementation(() => 3);
camera.simulateFrame();
});
test('rotates to specified bearing', () => {
const camera = createCamera();
camera.flyTo({bearing: 90, animate: false});
expect(camera.getBearing()).toBe(90);
});
test('tilts to specified pitch', () => {
const camera = createCamera();
camera.flyTo({pitch: 45, animate: false});
expect(camera.getPitch()).toBe(45);
});
test('pans and zooms', () => {
const camera = createCamera();
camera.flyTo({center: [100, 0], zoom: 3.2, animate: false});
expect(fixedLngLat(camera.getCenter())).toEqual({lng: 100, lat: 0});
expect(fixedNum(camera.getZoom())).toBe(3.2);
});
test('pans and rotates', () => {
const camera = createCamera();
camera.flyTo({center: [100, 0], bearing: 90, animate: false});
expect(fixedLngLat(camera.getCenter())).toEqual({lng: 100, lat: 0});
expect(camera.getBearing()).toBe(90);
});
test('zooms and rotates', () => {
const camera = createCamera();
camera.flyTo({zoom: 3.2, bearing: 90, animate: false});
expect(fixedNum(camera.getZoom())).toBe(3.2);
expect(camera.getBearing()).toBe(90);
});
test('pans, zooms, and rotates', () => {
const camera = createCamera();
camera.flyTo({center: [100, 0], zoom: 3.2, bearing: 90, duration: 0, animate: false});
expect(fixedLngLat(camera.getCenter())).toEqual({lng: 100, lat: 0});
expect(fixedNum(camera.getZoom())).toBe(3.2);
expect(camera.getBearing()).toBe(90);
});
test('noop', () => {
const camera = createCamera();
camera.flyTo({animate: false});
expect(fixedLngLat(camera.getCenter())).toEqual({lng: 0, lat: 0});
expect(camera.getZoom()).toBe(0);
expect(camera.getBearing()).toBeCloseTo(0);
});
test('noop with offset', () => {
const camera = createCamera();
camera.flyTo({offset: [100, 0], animate: false});
expect(fixedLngLat(camera.getCenter())).toEqual({lng: 0, lat: 0});
expect(camera.getZoom()).toBe(0);
expect(camera.getBearing()).toBeCloseTo(0);
});
test('pans with specified offset', () => {
const camera = createCamera();
camera.flyTo({center: [100, 0], offset: [100, 0], animate: false});
expect(fixedLngLat(camera.getCenter())).toEqual({lng: 29.6875, lat: 0});
});
test('pans with specified offset relative to viewport on a rotated camera', () => {
const camera = createCamera({bearing: 180});
camera.easeTo({center: [100, 0], offset: [100, 0], animate: false});
expect(fixedLngLat(camera.getCenter())).toEqual({lng: 170.3125, lat: 0});
});
test('emits move, zoom, rotate, and pitch events, preserving eventData', done => {
expect.assertions(18);
const camera = createCamera();
let movestarted, moved, zoomstarted, zoomed, rotatestarted, rotated, pitchstarted, pitched;
const eventData = {data: 'ok'};
camera
.on('movestart', (d) => { movestarted = d.data; })
.on('move', (d) => { moved = d.data; })
.on('rotate', (d) => { rotated = d.data; })
.on('pitch', (d) => { pitched = d.data; })
.on('moveend', function(d) {
expect(this._zooming).toBeFalsy();
expect(this._panning).toBeFalsy();
expect(this._rotating).toBeFalsy();
expect(movestarted).toBe('ok');
expect(moved).toBe('ok');
expect(zoomed).toBe('ok');
expect(rotated).toBe('ok');
expect(pitched).toBe('ok');
expect(d.data).toBe('ok');
});
camera
.on('zoomstart', (d) => { zoomstarted = d.data; })
.on('zoom', (d) => { zoomed = d.data; })
.on('zoomend', (d) => {
expect(zoomstarted).toBe('ok');
expect(zoomed).toBe('ok');
expect(d.data).toBe('ok');
});
camera
.on('rotatestart', (d) => { rotatestarted = d.data; })
.on('rotate', (d) => { rotated = d.data; })
.on('rotateend', (d) => {
expect(rotatestarted).toBe('ok');
expect(rotated).toBe('ok');
expect(d.data).toBe('ok');
});
camera
.on('pitchstart', (d) => { pitchstarted = d.data; })
.on('pitch', (d) => { pitched = d.data; })
.on('pitchend', (d) => {
expect(pitchstarted).toBe('ok');
expect(pitched).toBe('ok');
expect(d.data).toBe('ok');
});
camera.flyTo(
{center: [100, 0], zoom: 3.2, bearing: 90, duration: 0, pitch: 45, animate: false},
eventData);
done();
});
test('for short flights, emits (solely) move events, preserving eventData', done => {
//As I type this, the code path for guiding super-short flights is (and will probably remain) different.
//As such; it deserves a separate test case. This test case flies the map from A to A.
const camera = createCamera({center: [100, 0]});
let movestarted, moved,
zoomstarted, zoomed, zoomended,
rotatestarted, rotated, rotateended,
pitchstarted, pitched, pitchended;
const eventData = {data: 'ok'};
camera
.on('movestart', (d) => { movestarted = d.data; })
.on('move', (d) => { moved = d.data; })
.on('zoomstart', (d) => { zoomstarted = d.data; })
.on('zoom', (d) => { zoomed = d.data; })
.on('zoomend', (d) => { zoomended = d.data; })
.on('rotatestart', (d) => { rotatestarted = d.data; })
.on('rotate', (d) => { rotated = d.data; })
.on('rotateend', (d) => { rotateended = d.data; })
.on('pitchstart', (d) => { pitchstarted = d.data; })
.on('pitch', (d) => { pitched = d.data; })
.on('pitchend', (d) => { pitchended = d.data; })
.on('moveend', function(d) {
expect(this._zooming).toBeFalsy();
expect(this._panning).toBeFalsy();
expect(this._rotating).toBeFalsy();
expect(movestarted).toBe('ok');
expect(moved).toBe('ok');
expect(zoomstarted).toBeUndefined();
expect(zoomed).toBeUndefined();
expect(zoomended).toBeUndefined();
expect(rotatestarted).toBeUndefined();
expect(rotated).toBeUndefined();
expect(rotateended).toBeUndefined();
expect(pitched).toBeUndefined();
expect(pitchstarted).toBeUndefined();
expect(pitchended).toBeUndefined();
expect(d.data).toBe('ok');
done();
});
const stub = jest.spyOn(browser, 'now');
stub.mockImplementation(() => 0);
camera.flyTo({center: [100, 0], duration: 10}, eventData);
setTimeout(() => {
stub.mockImplementation(() => 1);
camera.simulateFrame();
setTimeout(() => {
stub.mockImplementation(() => 10);
camera.simulateFrame();
}, 0);
}, 0);
});
test('stops existing ease', done => {
const camera = createCamera();
camera.flyTo({center: [200, 0], duration: 100});
camera.flyTo({center: [100, 0], duration: 0});
expect(fixedLngLat(camera.getCenter())).toEqual({lng: 100, lat: 0});
done();
});
test('can be called from within a moveend event handler', done => {
const camera = createCamera();
const stub = jest.spyOn(browser, 'now');
stub.mockImplementation(() => 0);
camera.flyTo({center: [100, 0], duration: 10});
camera.once('moveend', () => {
camera.flyTo({center: [200, 0], duration: 10});
camera.once('moveend', () => {
camera.flyTo({center: [300, 0], duration: 10});
camera.once('moveend', () => {
done();
});
});
});
setTimeout(() => {
stub.mockImplementation(() => 10);
camera.simulateFrame();
setTimeout(() => {
stub.mockImplementation(() => 20);
camera.simulateFrame();
setTimeout(() => {
stub.mockImplementation(() => 30);
camera.simulateFrame();
}, 0);
}, 0);
}, 0);
});
test('ascends', done => {
const camera = createCamera();
camera.setZoom(18);
let ascended;
camera.on('zoom', () => {
if (camera.getZoom() < 18) {
ascended = true;
}
});
camera.on('moveend', () => {
expect(ascended).toBeTruthy();
done();
});
const stub = jest.spyOn(browser, 'now');
stub.mockImplementation(() => 0);
camera.flyTo({center: [100, 0], zoom: 18, duration: 10});
setTimeout(() => {
stub.mockImplementation(() => 1);
camera.simulateFrame();
setTimeout(() => {
stub.mockImplementation(() => 10);
camera.simulateFrame();
}, 0);
}, 0);
});
test('pans eastward across the prime meridian', done => {
const camera = createCamera();
const stub = jest.spyOn(browser, 'now');
camera.setCenter([-10, 0]);
let crossedPrimeMeridian;
camera.on('move', () => {
if (Math.abs(camera.getCenter().lng) < 10) {
crossedPrimeMeridian = true;
}
});
camera.on('moveend', () => {
expect(crossedPrimeMeridian).toBeTruthy();
done();
});
stub.mockImplementation(() => 0);
camera.flyTo({center: [10, 0], duration: 20});
setTimeout(() => {
stub.mockImplementation(() => 1);
camera.simulateFrame();
setTimeout(() => {
stub.mockImplementation(() => 20);
camera.simulateFrame();
}, 0);
}, 0);
});
test('pans westward across the prime meridian', done => {
const camera = createCamera();
const stub = jest.spyOn(browser, 'now');
camera.setCenter([10, 0]);
let crossedPrimeMeridian;
camera.on('move', () => {
if (Math.abs(camera.getCenter().lng) < 10) {
crossedPrimeMeridian = true;
}
});
camera.on('moveend', () => {
expect(crossedPrimeMeridian).toBeTruthy();
done();
});
stub.mockImplementation(() => 0);
camera.flyTo({center: [-10, 0], duration: 20});
setTimeout(() => {
stub.mockImplementation(() => 1);
camera.simulateFrame();
setTimeout(() => {
stub.mockImplementation(() => 20);
camera.simulateFrame();
}, 0);
}, 0);
});
test('pans eastward across the antimeridian', done => {
const camera = createCamera();
const stub = jest.spyOn(browser, 'now');
camera.setCenter([170, 0]);
let crossedAntimeridian;
camera.on('move', () => {
if (camera.getCenter().lng > 170) {
crossedAntimeridian = true;
}
});
camera.on('moveend', () => {
expect(crossedAntimeridian).toBeTruthy();
done();
});
stub.mockImplementation(() => 0);
camera.flyTo({center: [-170, 0], duration: 20});
setTimeout(() => {
stub.mockImplementation(() => 1);
camera.simulateFrame();
setTimeout(() => {
stub.mockImplementation(() => 20);
camera.simulateFrame();
}, 0);
}, 0);
});
test('pans westward across the antimeridian', done => {
const camera = createCamera();
const stub = jest.spyOn(browser, 'now');
camera.setCenter([-170, 0]);
let crossedAntimeridian;
camera.on('move', () => {
if (camera.getCenter().lng < -170) {
crossedAntimeridian = true;
}
});
camera.on('moveend', () => {
expect(crossedAntimeridian).toBeTruthy();
done();
});
stub.mockImplementation(() => 0);
camera.flyTo({center: [170, 0], duration: 10});
setTimeout(() => {
stub.mockImplementation(() => 1);
camera.simulateFrame();
setTimeout(() => {
stub.mockImplementation(() => 10);
camera.simulateFrame();
}, 0);
}, 0);
});
test('does not pan eastward across the antimeridian if no world copies', done => {
const camera = createCamera({renderWorldCopies: false});
const stub = jest.spyOn(browser, 'now');
camera.setCenter([170, 0]);
let crossedAntimeridian;
camera.on('move', () => {
if (camera.getCenter().lng > 170) {
crossedAntimeridian = true;
}
});
camera.on('moveend', () => {
expect(crossedAntimeridian).toBeFalsy();
done();
});
stub.mockImplementation(() => 0);
camera.flyTo({center: [-170, 0], duration: 10});
setTimeout(() => {
stub.mockImplementation(() => 1);
camera.simulateFrame();
setTimeout(() => {
stub.mockImplementation(() => 10);
camera.simulateFrame();
}, 0);
}, 0);
});
test('does not pan westward across the antimeridian if no world copies', done => {
const camera = createCamera({renderWorldCopies: false});
const stub = jest.spyOn(browser, 'now');
camera.setCenter([-170, 0]);
let crossedAntimeridian;
camera.on('move', () => {
if (fixedLngLat(camera.getCenter(), 10).lng < -170) {
crossedAntimeridian = true;
}
});
camera.on('moveend', () => {
expect(crossedAntimeridian).toBeFalsy();
done();
});
stub.mockImplementation(() => 0);
camera.flyTo({center: [170, 0], duration: 10});
setTimeout(() => {
stub.mockImplementation(() => 1);
camera.simulateFrame();
setTimeout(() => {
stub.mockImplementation(() => 10);
camera.simulateFrame();
}, 0);
}, 0);
});
test('jumps back to world 0 when crossing the antimeridian', done => {
const camera = createCamera();
const stub = jest.spyOn(browser, 'now');
camera.setCenter([-170, 0]);
let leftWorld0 = false;
camera.on('move', () => {
leftWorld0 = leftWorld0 || (camera.getCenter().lng < -180);
});
camera.on('moveend', () => {
expect(leftWorld0).toBeFalsy();
done();
});
stub.mockImplementation(() => 0);
camera.flyTo({center: [170, 0], duration: 10});
setTimeout(() => {
stub.mockImplementation(() => 1);
camera.simulateFrame();
setTimeout(() => {
stub.mockImplementation(() => 10);
camera.simulateFrame();
}, 0);
}, 0);
});
test('peaks at the specified zoom level', done => {
const camera = createCamera({zoom: 20});
const stub = jest.spyOn(browser, 'now');
const minZoom = 1;
let zoomed = false;
camera.on('zoom', () => {
const zoom = camera.getZoom();
if (zoom < 1) {
fail(`${zoom} should be >= ${minZoom} during flyTo`);
}
if (camera.getZoom() < (minZoom + 1)) {
zoomed = true;
}
});
camera.on('moveend', () => {
expect(zoomed).toBeTruthy();
done();
});
stub.mockImplementation(() => 0);
camera.flyTo({center: [1, 0], zoom: 20, minZoom, duration: 10});