Organize tests

This commit is contained in:
Tim Schaub
2021-04-27 15:41:14 -07:00
parent 278e355795
commit 490cfabe91
599 changed files with 12374 additions and 1603 deletions
@@ -0,0 +1,301 @@
import DragAndDrop from '../../../../../src/ol/interaction/DragAndDrop.js';
import Event from '../../../../../src/ol/events/Event.js';
import EventTarget from '../../../../../src/ol/events/Target.js';
import GeoJSON from '../../../../../src/ol/format/GeoJSON.js';
import MVT from '../../../../../src/ol/format/MVT.js';
import VectorSource from '../../../../../src/ol/source/Vector.js';
import View from '../../../../../src/ol/View.js';
where('FileReader').describe('ol.interaction.DragAndDrop', function () {
let viewport, map, interaction;
beforeEach(function () {
viewport = new EventTarget();
map = {
getViewport: function () {
return viewport;
},
getView: function () {
return new View();
},
};
interaction = new DragAndDrop({
formatConstructors: [GeoJSON],
});
});
describe('constructor', function () {
it('can be constructed without arguments', function () {
const interaction = new DragAndDrop();
expect(interaction).to.be.an(DragAndDrop);
});
it('sets formats on the instance', function () {
expect(interaction.formats_).to.have.length(1);
});
it('accepts a source option', function () {
const source = new VectorSource();
const drop = new DragAndDrop({
formatConstructors: [GeoJSON],
source: source,
});
expect(drop.source_).to.equal(source);
});
});
describe('#setActive()', function () {
it('registers and unregisters listeners', function () {
interaction.setMap(map);
interaction.setActive(true);
expect(viewport.hasListener('dragenter')).to.be(true);
expect(viewport.hasListener('dragover')).to.be(true);
expect(viewport.hasListener('drop')).to.be(true);
interaction.setActive(false);
expect(viewport.hasListener('dragenter')).to.be(false);
expect(viewport.hasListener('dragover')).to.be(false);
expect(viewport.hasListener('drop')).to.be(false);
});
});
describe('#setMap()', function () {
it('registers and unregisters listeners', function () {
interaction.setMap(map);
expect(viewport.hasListener('dragenter')).to.be(true);
expect(viewport.hasListener('dragover')).to.be(true);
expect(viewport.hasListener('drop')).to.be(true);
interaction.setMap(null);
expect(viewport.hasListener('dragenter')).to.be(false);
expect(viewport.hasListener('dragover')).to.be(false);
expect(viewport.hasListener('drop')).to.be(false);
});
it('registers and unregisters listeners on a custom target', function () {
const customTarget = new EventTarget();
interaction = new DragAndDrop({
formatConstructors: [GeoJSON],
target: customTarget,
});
interaction.setMap(map);
expect(customTarget.hasListener('dragenter')).to.be(true);
expect(customTarget.hasListener('dragover')).to.be(true);
expect(customTarget.hasListener('drop')).to.be(true);
interaction.setMap(null);
expect(customTarget.hasListener('dragenter')).to.be(false);
expect(customTarget.hasListener('dragover')).to.be(false);
expect(customTarget.hasListener('drop')).to.be(false);
});
});
describe('#handleDrop_', function () {
let OrigFileReader;
let mockReadAsText;
let mockReadAsArrayBuffer;
beforeEach(function () {
OrigFileReader = FileReader;
mockReadAsText = false;
mockReadAsArrayBuffer = false;
class MockFileReader extends EventTarget {
constructor() {
super(...arguments);
}
readAsText(file) {
mockReadAsText = true;
this.result = file;
this.dispatchEvent('load');
}
readAsArrayBuffer(file) {
mockReadAsArrayBuffer = true;
this.result = new TextEncoder().encode(file).buffer;
this.dispatchEvent('load');
}
}
FileReader = MockFileReader;
});
afterEach(function () {
FileReader = OrigFileReader;
});
it('reads dropped files as text', function (done) {
interaction.on('addfeatures', function (evt) {
expect(evt.features.length).to.be(1);
expect(mockReadAsText).to.be(true);
expect(mockReadAsArrayBuffer).to.be(false);
done();
});
interaction.setMap(map);
const event = new Event();
event.dataTransfer = {};
event.type = 'dragenter';
viewport.dispatchEvent(event);
event.type = 'dragover';
viewport.dispatchEvent(event);
event.type = 'drop';
event.dataTransfer.files = {
length: 1,
item: function () {
return JSON.stringify({
type: 'FeatureCollection',
features: [{type: 'Feature', id: '1'}],
});
},
};
viewport.dispatchEvent(event);
expect(event.dataTransfer.dropEffect).to.be('copy');
expect(event.propagationStopped).to.be(true);
});
it('reads dropped files as arraybuffer', function (done) {
const drop = new DragAndDrop({
formatConstructors: [GeoJSON, MVT],
});
drop.setMap(map);
drop.on('addfeatures', function (evt) {
expect(evt.features.length).to.be(1);
expect(mockReadAsText).to.be(false);
expect(mockReadAsArrayBuffer).to.be(true);
done();
});
const event = new Event();
event.dataTransfer = {};
event.type = 'dragenter';
viewport.dispatchEvent(event);
event.type = 'dragover';
viewport.dispatchEvent(event);
event.type = 'drop';
event.dataTransfer.files = {
length: 1,
item: function () {
return JSON.stringify({
type: 'FeatureCollection',
features: [{type: 'Feature', id: '1'}],
});
},
};
viewport.dispatchEvent(event);
expect(event.dataTransfer.dropEffect).to.be('copy');
expect(event.propagationStopped).to.be(true);
});
it('reads using constructed formats', function (done) {
const drop = new DragAndDrop({
formatConstructors: [new GeoJSON()],
});
drop.setMap(map);
drop.on('addfeatures', function (evt) {
expect(evt.features.length).to.be(1);
expect(mockReadAsText).to.be(true);
expect(mockReadAsArrayBuffer).to.be(false);
done();
});
const event = new Event();
event.dataTransfer = {};
event.type = 'dragenter';
viewport.dispatchEvent(event);
event.type = 'dragover';
viewport.dispatchEvent(event);
event.type = 'drop';
event.dataTransfer.files = {
length: 1,
item: function () {
return JSON.stringify({
type: 'FeatureCollection',
features: [{type: 'Feature', id: '1'}],
});
},
};
viewport.dispatchEvent(event);
expect(event.dataTransfer.dropEffect).to.be('copy');
expect(event.propagationStopped).to.be(true);
});
it('reads using arraybuffer formats', function (done) {
class binaryGeoJSON extends GeoJSON {
constructor(options) {
super(options);
}
getType() {
return 'arraybuffer';
}
readFeatures(source, options) {
const data = new TextDecoder().decode(source);
return super.readFeatures(data, options);
}
}
const drop = new DragAndDrop({
formatConstructors: [binaryGeoJSON],
});
drop.setMap(map);
drop.on('addfeatures', function (evt) {
expect(evt.features.length).to.be(1);
expect(mockReadAsText).to.be(false);
expect(mockReadAsArrayBuffer).to.be(true);
done();
});
const event = new Event();
event.dataTransfer = {};
event.type = 'dragenter';
viewport.dispatchEvent(event);
event.type = 'dragover';
viewport.dispatchEvent(event);
event.type = 'drop';
event.dataTransfer.files = {
length: 1,
item: function () {
return JSON.stringify({
type: 'FeatureCollection',
features: [{type: 'Feature', id: '1'}],
});
},
};
viewport.dispatchEvent(event);
expect(event.dataTransfer.dropEffect).to.be('copy');
expect(event.propagationStopped).to.be(true);
});
it('adds dropped features to a source', function (done) {
const source = new VectorSource();
const drop = new DragAndDrop({
formatConstructors: [GeoJSON],
source: source,
});
drop.setMap(map);
drop.on('addfeatures', function (evt) {
const features = source.getFeatures();
expect(features.length).to.be(1);
done();
});
const event = new Event();
event.dataTransfer = {};
event.type = 'dragenter';
viewport.dispatchEvent(event);
event.type = 'dragover';
viewport.dispatchEvent(event);
event.type = 'drop';
event.dataTransfer.files = {
length: 1,
item: function () {
return JSON.stringify({
type: 'FeatureCollection',
features: [{type: 'Feature', id: '1'}],
});
},
};
viewport.dispatchEvent(event);
expect(event.dataTransfer.dropEffect).to.be('copy');
expect(event.propagationStopped).to.be(true);
});
});
});
@@ -0,0 +1,97 @@
import DragRotateAndZoom from '../../../../../src/ol/interaction/DragRotateAndZoom.js';
import Event from '../../../../../src/ol/events/Event.js';
import Map from '../../../../../src/ol/Map.js';
import MapBrowserEvent from '../../../../../src/ol/MapBrowserEvent.js';
import VectorLayer from '../../../../../src/ol/layer/Vector.js';
import VectorSource from '../../../../../src/ol/source/Vector.js';
import View from '../../../../../src/ol/View.js';
describe('ol.interaction.DragRotateAndZoom', function () {
describe('constructor', function () {
it('can be constructed without arguments', function () {
const instance = new DragRotateAndZoom();
expect(instance).to.be.an(DragRotateAndZoom);
});
});
describe('#handleDragEvent()', function () {
let target, map, interaction;
const width = 360;
const height = 180;
beforeEach(function (done) {
target = document.createElement('div');
const style = target.style;
style.position = 'absolute';
style.left = '-1000px';
style.top = '-1000px';
style.width = width + 'px';
style.height = height + 'px';
document.body.appendChild(target);
const source = new VectorSource();
const layer = new VectorLayer({source: source});
interaction = new DragRotateAndZoom();
map = new Map({
target: target,
layers: [layer],
interactions: [interaction],
view: new View({
projection: 'EPSG:4326',
center: [0, 0],
resolution: 1,
}),
});
map.once('postrender', function () {
done();
});
});
afterEach(function () {
map.dispose();
document.body.removeChild(target);
});
it('does not rotate when rotation is disabled on the view', function () {
const pointerEvent = new Event();
pointerEvent.type = 'pointermove';
pointerEvent.clientX = 20;
pointerEvent.clientY = 10;
pointerEvent.pointerType = 'mouse';
let event = new MapBrowserEvent('pointermove', map, pointerEvent, true);
interaction.lastAngle_ = Math.PI;
let callCount = 0;
let view = map.getView();
view.on('change:rotation', function () {
callCount++;
});
interaction.handleDragEvent(event);
expect(callCount).to.be(1);
expect(interaction.lastAngle_).to.be(-0.8308214428190254);
callCount = 0;
view = new View({
projection: 'EPSG:4326',
center: [0, 0],
resolution: 1,
enableRotation: false,
});
map.setView(view);
view.on('change:rotation', function () {
callCount++;
});
pointerEvent.type = 'pointermove';
pointerEvent.clientX = 24;
pointerEvent.clientY = 16;
pointerEvent.pointerType = 'mouse';
event = new MapBrowserEvent('pointermove', map, pointerEvent, true);
interaction.handleDragEvent(event);
expect(callCount).to.be(0);
});
});
});
@@ -0,0 +1,106 @@
import DragZoom from '../../../../../src/ol/interaction/DragZoom.js';
import Map from '../../../../../src/ol/Map.js';
import RenderBox from '../../../../../src/ol/render/Box.js';
import VectorLayer from '../../../../../src/ol/layer/Vector.js';
import VectorSource from '../../../../../src/ol/source/Vector.js';
import View from '../../../../../src/ol/View.js';
import {getCenter} from '../../../../../src/ol/extent.js';
import {fromExtent as polygonFromExtent} from '../../../../../src/ol/geom/Polygon.js';
describe('ol.interaction.DragZoom', function () {
let target, map, source;
const width = 360;
const height = 180;
beforeEach(function (done) {
target = document.createElement('div');
const style = target.style;
style.position = 'absolute';
style.left = '-1000px';
style.top = '-1000px';
style.width = width + 'px';
style.height = height + 'px';
document.body.appendChild(target);
source = new VectorSource();
const layer = new VectorLayer({source: source});
map = new Map({
target: target,
layers: [layer],
view: new View({
projection: 'EPSG:4326',
center: [0, 0],
resolution: 1,
}),
});
map.once('postrender', function () {
done();
});
});
afterEach(function () {
map.dispose();
document.body.removeChild(target);
});
describe('constructor', function () {
it('can be constructed without arguments', function () {
const instance = new DragZoom();
expect(instance).to.be.an(DragZoom);
});
it('sets "ol-dragzoom" as box className', function () {
const instance = new DragZoom();
expect(instance.box_.element_.className).to.be('ol-box ol-dragzoom');
});
it('sets a custom box className', function () {
const instance = new DragZoom({className: 'test-dragzoom'});
expect(instance.box_.element_.className).to.be('ol-box test-dragzoom');
});
});
describe('#onBoxEnd()', function () {
it('centers the view on the box geometry', function (done) {
const interaction = new DragZoom({
duration: 10,
});
map.addInteraction(interaction);
const box = new RenderBox();
const extent = [-110, 40, -90, 60];
box.geometry_ = polygonFromExtent(extent);
interaction.box_ = box;
interaction.onBoxEnd();
setTimeout(function () {
const view = map.getView();
const center = view.getCenterInternal();
expect(center).to.eql(getCenter(extent));
done();
}, 50);
});
it('sets new resolution while zooming out', function (done) {
const interaction = new DragZoom({
duration: 10,
out: true,
});
map.addInteraction(interaction);
const box = new RenderBox();
const extent = [-11.25, -11.25, 11.25, 11.25];
box.geometry_ = polygonFromExtent(extent);
interaction.box_ = box;
map.getView().setResolution(0.25);
setTimeout(function () {
interaction.onBoxEnd();
setTimeout(function () {
const view = map.getView();
const resolution = view.getResolution();
expect(resolution).to.eql(view.getConstrainedResolution(0.5));
done();
}, 50);
}, 50);
});
});
});
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,140 @@
import ExtentInteraction from '../../../../../src/ol/interaction/Extent.js';
import Map from '../../../../../src/ol/Map.js';
import MapBrowserEvent from '../../../../../src/ol/MapBrowserEvent.js';
import View from '../../../../../src/ol/View.js';
describe('ol.interaction.Extent', function () {
let map, interaction;
const width = 360;
const height = 180;
beforeEach(function () {
const target = createMapDiv(width, height);
map = new Map({
target: target,
layers: [],
view: new View({
projection: 'EPSG:4326',
center: [0, 0],
resolution: 1,
}),
});
map.renderSync();
interaction = new ExtentInteraction();
map.addInteraction(interaction);
});
afterEach(function () {
if (map) {
disposeMap(map);
}
map = null;
interaction = null;
});
/**
* Simulates a browser event on the map viewport. The client x/y location
* will be adjusted as if the map were centered at 0,0.
* @param {string} type Event type.
* @param {number} x Horizontal offset from map center.
* @param {number} y Vertical offset from map center.
* @param {boolean} [opt_shiftKey] Shift key is pressed.
* @param {number} button The mouse button.
*/
function simulateEvent(type, x, y, opt_shiftKey, button) {
const viewport = map.getViewport();
// calculated in case body has top < 0 (test runner with small window)
const position = viewport.getBoundingClientRect();
const shiftKey = opt_shiftKey !== undefined ? opt_shiftKey : false;
const pointerEvent = {};
pointerEvent.type = type;
pointerEvent.target = viewport.firstChild;
pointerEvent.button = button;
pointerEvent.clientX = position.left + x + width / 2;
pointerEvent.clientY = position.top - y + height / 2;
pointerEvent.shiftKey = shiftKey;
pointerEvent.pointerId = 0;
pointerEvent.preventDefault = function () {};
const event = new MapBrowserEvent(type, map, pointerEvent);
event.originalEvent.pointerId = 1;
map.handleMapBrowserEvent(event);
}
describe('Constructor', function () {
it('can be configured with an extent', function () {
expect(function () {
new ExtentInteraction({
extent: [-10, -10, 10, 10],
});
}).to.not.throwException();
});
});
describe('snap to vertex', function () {
it('snap to vertex works', function () {
interaction.setExtent([-50, -50, 50, 50]);
expect(interaction.snapToVertex_([230, 40], map)).to.eql([50, 50]);
expect(interaction.snapToVertex_([231, 41], map)).to.eql([50, 50]);
});
it('snap to edge works', function () {
interaction.setExtent([-50, -50, 50, 50]);
expect(interaction.snapToVertex_([230, 90], map)).to.eql([50, 0]);
expect(interaction.snapToVertex_([230, 89], map)).to.eql([50, 1]);
expect(interaction.snapToVertex_([231, 90], map)).to.eql([50, 0]);
});
});
describe('draw extent', function () {
it('drawing extent works', function () {
simulateEvent('pointerdown', -50, -50, false, 0);
simulateEvent('pointerdrag', 50, 50, false, 0);
simulateEvent('pointerup', 50, 50, false, 0);
expect(interaction.getExtent()).to.eql([-50, -50, 50, 50]);
});
it('clicking off extent nulls extent', function () {
interaction.setExtent([-50, -50, 50, 50]);
simulateEvent('pointerdown', -10, -10, false, 0);
simulateEvent('pointerup', -10, -10, false, 0);
expect(interaction.getExtent()).to.equal(null);
});
it('clicking on extent does not null extent', function () {
interaction.setExtent([-50, -50, 50, 50]);
simulateEvent('pointerdown', 50, 50, false, 0);
simulateEvent('pointerup', 50, 50, false, 0);
expect(interaction.getExtent()).to.eql([-50, -50, 50, 50]);
});
it('snap and drag vertex works', function () {
interaction.setExtent([-50, -50, 50, 50]);
simulateEvent('pointerdown', 51, 49, false, 0);
simulateEvent('pointerdrag', -70, -40, false, 0);
simulateEvent('pointerup', -70, -40, false, 0);
expect(interaction.getExtent()).to.eql([-70, -50, -50, -40]);
});
it('snap and drag edge works', function () {
interaction.setExtent([-50, -50, 50, 50]);
simulateEvent('pointerdown', 51, 5, false, 0);
simulateEvent('pointerdrag', 20, -30, false, 0);
simulateEvent('pointerup', 20, -30, false, 0);
expect(interaction.getExtent()).to.eql([-50, -50, 20, 50]);
});
});
});
@@ -0,0 +1,108 @@
import EventTarget from '../../../../../src/ol/events/Target.js';
import Interaction, {
zoomByDelta,
} from '../../../../../src/ol/interaction/Interaction.js';
import {FALSE} from '../../../../../src/ol/functions.js';
import {Map, View} from '../../../../../src/ol/index.js';
import {
clearUserProjection,
useGeographic,
} from '../../../../../src/ol/proj.js';
describe('ol.interaction.Interaction', function () {
describe('constructor', function () {
let interaction;
beforeEach(function () {
interaction = new Interaction({});
});
it('creates a new interaction', function () {
expect(interaction).to.be.a(Interaction);
expect(interaction).to.be.a(EventTarget);
});
it('creates an active interaction', function () {
expect(interaction.getActive()).to.be(true);
});
});
describe('#getMap()', function () {
it('retrieves the associated map', function () {
const map = new Map({});
const interaction = new Interaction({});
interaction.setMap(map);
expect(interaction.getMap()).to.be(map);
});
it('returns null if no map', function () {
const interaction = new Interaction({});
expect(interaction.getMap()).to.be(null);
});
});
describe('#setMap()', function () {
it('allows a map to be set', function () {
const map = new Map({});
const interaction = new Interaction({});
interaction.setMap(map);
expect(interaction.getMap()).to.be(map);
});
it('accepts null', function () {
const interaction = new Interaction({});
interaction.setMap(null);
expect(interaction.getMap()).to.be(null);
});
});
describe('#handleEvent()', function () {
class MockInteraction extends Interaction {
constructor() {
super(...arguments);
}
handleEvent(mapBrowserEvent) {
return false;
}
}
it('has a default event handler', function () {
const interaction = new Interaction({});
expect(interaction.handleEvent()).to.be(true);
});
it('allows event handler overrides via options', function () {
const interaction = new Interaction({
handleEvent: FALSE,
});
expect(interaction.handleEvent()).to.be(false);
});
it('allows event handler overrides via class extension', function () {
const interaction = new MockInteraction({});
expect(interaction.handleEvent()).to.be(false);
});
});
});
describe('zoomByDelta - useGeographic', () => {
beforeEach(useGeographic);
afterEach(clearUserProjection);
it('works with a user projection set', () => {
const view = new View({
center: [0, 0],
zoom: 0,
});
const spy = sinon.spy(view, 'animate');
const anchor = [90, 45];
const duration = 10;
zoomByDelta(view, 1, anchor, duration);
expect(spy.callCount).to.be(1);
const options = spy.getCall(0).args[0];
expect(options.anchor).to.be(anchor);
});
});
@@ -0,0 +1,57 @@
import Event from '../../../../../src/ol/events/Event.js';
import Map from '../../../../../src/ol/Map.js';
import MapBrowserEvent from '../../../../../src/ol/MapBrowserEvent.js';
import View from '../../../../../src/ol/View.js';
describe('ol.interaction.KeyboardPan', function () {
let map;
beforeEach(function () {
map = new Map({
target: createMapDiv(100, 100),
view: new View({
center: [0, 0],
resolutions: [1],
zoom: 0,
}),
});
map.renderSync();
});
afterEach(function () {
disposeMap(map);
});
describe('handleEvent()', function () {
it('pans on arrow keys', function () {
const view = map.getView();
const spy = sinon.spy(view, 'animateInternal');
const event = new MapBrowserEvent('keydown', map, {
type: 'keydown',
target: map.getTargetElement(),
preventDefault: Event.prototype.preventDefault,
});
event.originalEvent.keyCode = 40; // DOWN
map.handleMapBrowserEvent(event);
expect(spy.getCall(0).args[0].center).to.eql([0, -128]);
view.setCenter([0, 0]);
event.originalEvent.keyCode = 38; // UP
map.handleMapBrowserEvent(event);
expect(spy.getCall(1).args[0].center).to.eql([0, 128]);
view.setCenter([0, 0]);
event.originalEvent.keyCode = 37; // LEFT
map.handleMapBrowserEvent(event);
expect(spy.getCall(2).args[0].center).to.eql([-128, 0]);
view.setCenter([0, 0]);
event.originalEvent.keyCode = 39; // RIGHT
map.handleMapBrowserEvent(event);
expect(spy.getCall(3).args[0].center).to.eql([128, 0]);
view.setCenter([0, 0]);
view.animateInternal.restore();
});
});
});
@@ -0,0 +1,61 @@
import Event from '../../../../../src/ol/events/Event.js';
import Map from '../../../../../src/ol/Map.js';
import MapBrowserEvent from '../../../../../src/ol/MapBrowserEvent.js';
import View from '../../../../../src/ol/View.js';
describe('ol.interaction.KeyboardZoom', function () {
let map;
beforeEach(function () {
map = new Map({
target: createMapDiv(100, 100),
view: new View({
center: [0, 0],
resolutions: [4, 2, 1],
zoom: 1,
}),
});
map.renderSync();
});
afterEach(function () {
disposeMap(map);
});
describe('handleEvent()', function () {
it('zooms on + and - keys', function () {
const view = map.getView();
const spy = sinon.spy(view, 'animateInternal');
const event = new MapBrowserEvent('keydown', map, {
type: 'keydown',
target: map.getTargetElement(),
preventDefault: Event.prototype.preventDefault,
});
event.originalEvent.charCode = '+'.charCodeAt(0);
map.handleMapBrowserEvent(event);
expect(spy.getCall(0).args[0].resolution).to.eql(1);
view.setResolution(2);
event.originalEvent.charCode = '-'.charCodeAt(0);
map.handleMapBrowserEvent(event);
expect(spy.getCall(1).args[0].resolution).to.eql(4);
view.setResolution(2);
view.animateInternal.restore();
});
it('does nothing if the target is editable', function () {
const view = map.getView();
const spy = sinon.spy(view, 'animateInternal');
const event = new MapBrowserEvent('keydown', map, {
type: 'keydown',
target: document.createElement('input'),
preventDefault: Event.prototype.preventDefault,
});
event.originalEvent.charCode = '+'.charCodeAt(0);
map.handleMapBrowserEvent(event);
expect(spy.called).to.be(false);
});
});
});
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,152 @@
import Event from '../../../../../src/ol/events/Event.js';
import Map from '../../../../../src/ol/Map.js';
import MapBrowserEvent from '../../../../../src/ol/MapBrowserEvent.js';
import MouseWheelZoom, {
Mode,
} from '../../../../../src/ol/interaction/MouseWheelZoom.js';
import View from '../../../../../src/ol/View.js';
import {DEVICE_PIXEL_RATIO, FIREFOX} from '../../../../../src/ol/has.js';
describe('ol.interaction.MouseWheelZoom', function () {
let map, interaction;
beforeEach(function () {
interaction = new MouseWheelZoom();
map = new Map({
target: createMapDiv(100, 100),
interactions: [interaction],
view: new View({
center: [0, 0],
resolutions: [2, 1, 0.5],
zoom: 1,
}),
});
map.renderSync();
});
afterEach(function () {
disposeMap(map);
map = null;
interaction = null;
});
describe('timeout duration', function () {
let clock;
beforeEach(function () {
sinon.spy(interaction, 'handleWheelZoom_');
clock = sinon.useFakeTimers();
});
afterEach(function () {
clock.restore();
interaction.handleWheelZoom_.restore();
});
it('works with the default value', function (done) {
const event = new MapBrowserEvent('wheel', map, {
type: 'wheel',
target: map.getViewport(),
preventDefault: Event.prototype.preventDefault,
});
map.handleMapBrowserEvent(event);
clock.tick(50);
// default timeout is 80 ms, not called yet
expect(interaction.handleWheelZoom_.called).to.be(false);
clock.tick(30);
expect(interaction.handleWheelZoom_.called).to.be(true);
done();
});
});
describe('handleEvent()', function () {
if (FIREFOX) {
it('works on Firefox in DOM_DELTA_PIXEL mode (trackpad)', function (done) {
map.once('postrender', function () {
expect(interaction.mode_).to.be(Mode.TRACKPAD);
done();
});
const event = new MapBrowserEvent('wheel', map, {
type: 'wheel',
deltaMode: WheelEvent.DOM_DELTA_PIXEL,
deltaY: DEVICE_PIXEL_RATIO,
target: map.getViewport(),
preventDefault: Event.prototype.preventDefault,
});
event.coordinate = [0, 0];
map.handleMapBrowserEvent(event);
});
}
if (!FIREFOX) {
it('works in DOM_DELTA_PIXEL mode (trackpad)', function (done) {
map.once('postrender', function () {
expect(interaction.mode_).to.be(Mode.TRACKPAD);
done();
});
const event = new MapBrowserEvent('wheel', map, {
type: 'wheel',
deltaMode: WheelEvent.DOM_DELTA_PIXEL,
deltaY: 1,
target: map.getViewport(),
preventDefault: Event.prototype.preventDefault,
});
event.coordinate = [0, 0];
map.handleMapBrowserEvent(event);
});
}
describe('spying on view.animateInternal()', function () {
let view;
beforeEach(function () {
view = map.getView();
sinon.spy(view, 'animateInternal');
});
afterEach(function () {
view.animateInternal.restore();
});
it('works in DOM_DELTA_LINE mode (wheel)', function (done) {
map.once('postrender', function () {
const call = view.animateInternal.getCall(0);
expect(call.args[0].resolution).to.be(2);
expect(call.args[0].anchor).to.eql([0, 0]);
done();
});
const event = new MapBrowserEvent('wheel', map, {
type: 'wheel',
deltaMode: WheelEvent.DOM_DELTA_LINE,
deltaY: 20,
target: map.getViewport(),
preventDefault: Event.prototype.preventDefault,
});
event.coordinate = [0, 0];
map.handleMapBrowserEvent(event);
});
it('works on all browsers (wheel)', function (done) {
map.once('postrender', function () {
const call = view.animateInternal.getCall(0);
expect(call.args[0].resolution).to.be(2);
expect(call.args[0].anchor).to.eql([0, 0]);
done();
});
const event = new MapBrowserEvent('wheel', map, {
type: 'wheel',
deltaY: 300,
target: map.getViewport(),
preventDefault: Event.prototype.preventDefault,
});
event.coordinate = [0, 0];
map.handleMapBrowserEvent(event);
});
});
});
});
@@ -0,0 +1,133 @@
import Event from '../../../../../src/ol/events/Event.js';
import Map from '../../../../../src/ol/Map.js';
import MapBrowserEvent from '../../../../../src/ol/MapBrowserEvent.js';
import PointerInteraction from '../../../../../src/ol/interaction/Pointer.js';
describe('ol.interaction.Pointer', function () {
describe('#handleEvent', function () {
let event;
let defaultPrevented;
beforeEach(function () {
const type = 'pointerdown';
const pointerEvent = new Event();
pointerEvent.type = type;
pointerEvent.pointerId = 0;
pointerEvent.preventDefault = function () {
defaultPrevented = true;
};
event = new MapBrowserEvent(type, new Map(), pointerEvent);
defaultPrevented = false;
});
it('does not prevent default on handled down event', function () {
const interaction = new PointerInteraction({
handleDownEvent: function () {
return true;
},
});
interaction.handleEvent(event);
expect(defaultPrevented).to.be(false);
});
it('does not prevent default on unhandled down event', function () {
const interaction = new PointerInteraction({
handleDownEvent: function () {
return false;
},
});
interaction.handleEvent(event);
expect(defaultPrevented).to.be(false);
});
});
describe('event handlers', function () {
let handleDownCalled, handleDragCalled, handleMoveCalled, handleUpCalled;
const flagHandleDown = function () {
handleDownCalled = true;
};
const flagHandleDrag = function () {
handleDragCalled = true;
};
const flagHandleMove = function () {
handleMoveCalled = true;
};
const flagHandleUp = function () {
handleUpCalled = true;
};
class MockPointerInteraction extends PointerInteraction {
constructor() {
super(...arguments);
}
handleDownEvent(mapBrowserEvent) {
flagHandleDown();
return super.handleDownEvent(mapBrowserEvent);
}
handleDragEvent(mapBrowserEvent) {
flagHandleDrag();
}
handleMoveEvent(mapBrowserEvent) {
flagHandleMove();
}
handleUpEvent(mapBrowserEvent) {
flagHandleUp();
return super.handleUpEvent(mapBrowserEvent);
}
}
beforeEach(function () {
handleDownCalled = false;
handleDragCalled = false;
handleMoveCalled = false;
handleUpCalled = false;
});
it('has default event handlers', function () {
const interaction = new PointerInteraction({});
expect(interaction.handleDownEvent()).to.be(false);
expect(interaction.handleUpEvent()).to.be(false);
});
it('allows event handler overrides via options', function () {
const interaction = new PointerInteraction({
handleDownEvent: flagHandleDown,
handleDragEvent: flagHandleDrag,
handleMoveEvent: flagHandleMove,
handleUpEvent: flagHandleUp,
});
interaction.handleDownEvent();
expect(handleDownCalled).to.be(true);
interaction.handleDragEvent();
expect(handleDragCalled).to.be(true);
interaction.handleMoveEvent();
expect(handleMoveCalled).to.be(true);
interaction.handleUpEvent();
expect(handleUpCalled).to.be(true);
});
it('allows event handler overrides via class extension', function () {
const interaction = new MockPointerInteraction({});
interaction.handleDownEvent();
expect(handleDownCalled).to.be(true);
interaction.handleDragEvent();
expect(handleDragCalled).to.be(true);
interaction.handleMoveEvent();
expect(handleMoveCalled).to.be(true);
interaction.handleUpEvent();
expect(handleUpCalled).to.be(true);
});
});
});
@@ -0,0 +1,516 @@
import Collection from '../../../../../src/ol/Collection.js';
import Feature from '../../../../../src/ol/Feature.js';
import Interaction from '../../../../../src/ol/interaction/Interaction.js';
import Map from '../../../../../src/ol/Map.js';
import MapBrowserEvent from '../../../../../src/ol/MapBrowserEvent.js';
import MapBrowserEventType from '../../../../../src/ol/MapBrowserEventType.js';
import Polygon from '../../../../../src/ol/geom/Polygon.js';
import Select from '../../../../../src/ol/interaction/Select.js';
import Style from '../../../../../src/ol/style/Style.js';
import VectorLayer from '../../../../../src/ol/layer/Vector.js';
import VectorSource from '../../../../../src/ol/source/Vector.js';
import View from '../../../../../src/ol/View.js';
describe('ol.interaction.Select', function () {
let target, map, layer, source;
const width = 360;
const height = 180;
beforeEach(function (done) {
target = document.createElement('div');
const style = target.style;
style.position = 'absolute';
style.left = '-1000px';
style.top = '-1000px';
style.width = width + 'px';
style.height = height + 'px';
document.body.appendChild(target);
const geometry = new Polygon([
[
[0, 0],
[0, 40],
[40, 40],
[40, 0],
],
]);
// Four overlapping features, two features of type "foo" and two features
// of type "bar". The rendering order is, from top to bottom, foo -> bar
// -> foo -> bar.
const features = [];
features.push(
new Feature({
geometry: geometry,
type: 'bar',
}),
new Feature({
geometry: geometry,
type: 'foo',
}),
new Feature({
geometry: geometry,
type: 'bar',
}),
new Feature({
geometry: geometry,
type: 'foo',
})
);
source = new VectorSource({
features: features,
});
layer = new VectorLayer({source: source});
map = new Map({
target: target,
layers: [layer],
view: new View({
projection: 'EPSG:4326',
center: [0, 0],
resolution: 1,
}),
});
map.once('postrender', function () {
done();
});
});
afterEach(function () {
map.dispose();
document.body.removeChild(target);
});
/**
* Simulates a browser event on the map viewport. The client x/y location
* will be adjusted as if the map were centered at 0,0.
* @param {string} type Event type.
* @param {number} x Horizontal offset from map center.
* @param {number} y Vertical offset from map center.
* @param {boolean} [opt_shiftKey] Shift key is pressed.
*/
function simulateEvent(type, x, y, opt_shiftKey) {
const viewport = map.getViewport();
// calculated in case body has top < 0 (test runner with small window)
const position = viewport.getBoundingClientRect();
const shiftKey = opt_shiftKey !== undefined ? opt_shiftKey : false;
const event = {
type: type,
target: viewport.firstChild,
clientX: position.left + x + width / 2,
clientY: position.top + y + height / 2,
shiftKey: shiftKey,
stopPropagation: () => {
event.propagationStopped = true;
},
};
map.handleMapBrowserEvent(new MapBrowserEvent(type, map, event));
}
describe('constructor', function () {
it('creates a new interaction', function () {
const select = new Select();
expect(select).to.be.a(Select);
expect(select).to.be.a(Interaction);
});
describe('user-provided collection', function () {
it('uses the user-provided collection', function () {
const features = new Collection();
const select = new Select({features: features});
expect(select.getFeatures()).to.be(features);
});
});
});
describe('selecting a polygon', function () {
let select;
beforeEach(function () {
select = new Select();
map.addInteraction(select);
});
it('select with single-click', function () {
const listenerSpy = sinon.spy(function (e) {
expect(e.selected).to.have.length(1);
});
select.on('select', listenerSpy);
simulateEvent('singleclick', 10, -20);
expect(listenerSpy.callCount).to.be(1);
const features = select.getFeatures();
expect(features.getLength()).to.equal(1);
});
it('single-click outside the geometry', function () {
const listenerSpy = sinon.spy(function (e) {
expect(e.selected).to.have.length(1);
});
select.on('select', listenerSpy);
simulateEvent(MapBrowserEventType.SINGLECLICK, -10, -10);
expect(listenerSpy.callCount).to.be(0);
const features = select.getFeatures();
expect(features.getLength()).to.equal(0);
});
it('select twice with single-click', function () {
const listenerSpy = sinon.spy(function (e) {
expect(e.selected).to.have.length(1);
});
select.on('select', listenerSpy);
simulateEvent(MapBrowserEventType.SINGLECLICK, 10, -20);
simulateEvent(MapBrowserEventType.SINGLECLICK, 9, -21);
expect(listenerSpy.callCount).to.be(1);
const features = select.getFeatures();
expect(features.getLength()).to.equal(1);
});
it('select with shift single-click', function () {
const listenerSpy = sinon.spy(function (e) {
expect(e.selected).to.have.length(1);
});
select.on('select', listenerSpy);
simulateEvent('singleclick', 10, -20, true);
expect(listenerSpy.callCount).to.be(1);
const features = select.getFeatures();
expect(features.getLength()).to.equal(1);
});
});
describe('multiselecting polygons', function () {
let select;
beforeEach(function () {
select = new Select({
multi: true,
});
map.addInteraction(select);
});
it('select with single-click', function () {
const listenerSpy = sinon.spy(function (e) {
expect(e.selected).to.have.length(4);
});
select.on('select', listenerSpy);
simulateEvent('singleclick', 10, -20);
expect(listenerSpy.callCount).to.be(1);
const features = select.getFeatures();
expect(features.getLength()).to.equal(4);
});
it('select with shift single-click', function () {
const listenerSpy = sinon.spy(function (e) {
expect(e.selected).to.have.length(4);
});
select.on('select', listenerSpy);
simulateEvent('singleclick', 10, -20, true);
expect(listenerSpy.callCount).to.be(1);
let features = select.getFeatures();
expect(features.getLength()).to.equal(4);
expect(select.getLayer(features.item(0))).to.equal(layer);
// Select again to make sure the style change does not break selection
simulateEvent('singleclick', 10, -20);
expect(listenerSpy.callCount).to.be(1);
features = select.getFeatures();
expect(features.getLength()).to.equal(4);
expect(select.getLayer(features.item(0))).to.equal(layer);
});
});
describe('toggle selecting polygons', function () {
let select;
beforeEach(function () {
select = new Select({
multi: true,
});
map.addInteraction(select);
});
it('with SHIFT + single-click', function () {
const listenerSpy = sinon.spy();
select.on('select', listenerSpy);
simulateEvent('singleclick', 10, -20, true);
expect(listenerSpy.callCount).to.be(1);
let features = select.getFeatures();
expect(features.getLength()).to.equal(4);
map.renderSync();
simulateEvent('singleclick', 10, -20, true);
expect(listenerSpy.callCount).to.be(1);
features = select.getFeatures();
expect(features.getLength()).to.equal(4);
});
});
describe('filter features using the filter option', function () {
describe('with multi set to true', function () {
it('only selects features that pass the filter', function () {
const select = new Select({
multi: true,
filter: function (feature, layer) {
return feature.get('type') === 'bar';
},
});
map.addInteraction(select);
simulateEvent('singleclick', 10, -20);
const features = select.getFeatures();
expect(features.getLength()).to.equal(2);
expect(features.item(0).get('type')).to.be('bar');
expect(features.item(1).get('type')).to.be('bar');
});
it(
'only selects features that pass the filter ' +
'using shift single-click',
function () {
const select = new Select({
multi: true,
filter: function (feature, layer) {
return feature.get('type') === 'bar';
},
});
map.addInteraction(select);
simulateEvent('singleclick', 10, -20, true);
const features = select.getFeatures();
expect(features.getLength()).to.equal(2);
expect(features.item(0).get('type')).to.be('bar');
expect(features.item(1).get('type')).to.be('bar');
}
);
});
describe('with multi set to false', function () {
it('only selects the first feature that passes the filter', function () {
const select = new Select({
multi: false,
filter: function (feature, layer) {
return feature.get('type') === 'bar';
},
});
map.addInteraction(select);
simulateEvent('singleclick', 10, -20);
const features = select.getFeatures();
expect(features.getLength()).to.equal(1);
expect(features.item(0).get('type')).to.be('bar');
});
it(
'only selects the first feature that passes the filter ' +
'using shift single-click',
function () {
const select = new Select({
multi: false,
filter: function (feature, layer) {
return feature.get('type') === 'bar';
},
});
map.addInteraction(select);
simulateEvent('singleclick', 10, -20, true);
const features = select.getFeatures();
expect(features.getLength()).to.equal(1);
expect(features.item(0).get('type')).to.be('bar');
}
);
});
});
describe('#getLayer(feature)', function () {
let interaction;
beforeEach(function () {
interaction = new Select();
map.addInteraction(interaction);
});
afterEach(function () {
map.removeInteraction(interaction);
});
it('returns a layer from a selected feature', function () {
const listenerSpy = sinon.spy(function (e) {
const feature = e.selected[0];
const layer_ = interaction.getLayer(feature);
expect(e.selected).to.have.length(1);
expect(feature).to.be.a(Feature);
expect(layer_).to.be.a(VectorLayer);
expect(layer_).to.equal(layer);
});
interaction.on('select', listenerSpy);
simulateEvent('singleclick', 10, -20);
// Select again to make sure the style change does not break selection
simulateEvent('singleclick', 10, -20);
});
});
describe('#setActive()', function () {
let interaction;
beforeEach(function () {
interaction = new Select();
expect(interaction.getActive()).to.be(true);
map.addInteraction(interaction);
simulateEvent('singleclick', 10, -20);
});
afterEach(function () {
map.removeInteraction(interaction);
});
describe('#setActive(false)', function () {
it('keeps the the selection', function () {
interaction.setActive(false);
expect(interaction.getFeatures().getLength()).to.equal(1);
});
});
describe('#setActive(true)', function () {
beforeEach(function () {
interaction.setActive(false);
});
it('fires change:active', function () {
const listenerSpy = sinon.spy();
interaction.on('change:active', listenerSpy);
interaction.setActive(true);
expect(listenerSpy.callCount).to.be(1);
});
});
});
describe('clear event listeners on interaction removal', function () {
let firstInteraction, secondInteraction, feature;
beforeEach(function () {
feature = source.getFeatures()[3]; // top feature is selected
const style = new Style({});
const features = new Collection();
firstInteraction = new Select({style, features});
secondInteraction = new Select({style, features});
});
afterEach(function () {
map.removeInteraction(secondInteraction);
map.removeInteraction(firstInteraction);
});
// The base case
describe('with a single interaction added', function () {
it('changes the selected feature once', function () {
map.addInteraction(firstInteraction);
const listenerSpy = sinon.spy();
feature.on('change', listenerSpy);
simulateEvent('singleclick', 10, -20, false);
expect(listenerSpy.callCount).to.be(1);
});
});
// The "difficult" case. To prevent regression
describe('with a replaced interaction', function () {
it('changes the selected feature once', function () {
map.addInteraction(firstInteraction);
map.removeInteraction(firstInteraction);
map.addInteraction(secondInteraction);
const listenerSpy = sinon.spy();
feature.on('change', listenerSpy);
simulateEvent('singleclick', 10, -20, false);
expect(listenerSpy.callCount).to.be(1);
});
});
});
describe('supports stop propagation', function () {
let firstInteraction, secondInteraction;
beforeEach(function () {
firstInteraction = new Select();
secondInteraction = new Select();
map.addInteraction(firstInteraction);
// note second interaction added to map last
map.addInteraction(secondInteraction);
});
afterEach(function () {
map.removeInteraction(firstInteraction);
map.removeInteraction(secondInteraction);
});
//base case sanity check
describe('without stop propagation', function () {
it('both interactions dispatch select', function () {
const firstSelectSpy = sinon.spy();
firstInteraction.on('select', firstSelectSpy);
const secondSelectSpy = sinon.spy();
secondInteraction.on('select', secondSelectSpy);
simulateEvent('singleclick', 10, -20);
expect(firstSelectSpy.callCount).to.be(1);
expect(secondSelectSpy.callCount).to.be(1);
});
});
describe('calling stop propagation', function () {
it('only "last" added interaction dispatches select', function () {
const firstSelectSpy = sinon.spy();
firstInteraction.on('select', firstSelectSpy);
const secondSelectSpy = sinon.spy(function (e) {
e.mapBrowserEvent.stopPropagation();
});
secondInteraction.on('select', secondSelectSpy);
simulateEvent('singleclick', 10, -20);
expect(firstSelectSpy.callCount).to.be(0);
expect(secondSelectSpy.callCount).to.be(1);
});
});
});
});
@@ -0,0 +1,336 @@
import Circle from '../../../../../src/ol/geom/Circle.js';
import Collection from '../../../../../src/ol/Collection.js';
import Feature from '../../../../../src/ol/Feature.js';
import LineString from '../../../../../src/ol/geom/LineString.js';
import Map from '../../../../../src/ol/Map.js';
import Point from '../../../../../src/ol/geom/Point.js';
import Snap from '../../../../../src/ol/interaction/Snap.js';
import View from '../../../../../src/ol/View.js';
import {
clearUserProjection,
setUserProjection,
transform,
useGeographic,
} from '../../../../../src/ol/proj.js';
import {overrideRAF} from '../../util.js';
describe('ol.interaction.Snap', function () {
describe('constructor', function () {
it('can be constructed without arguments', function () {
const instance = new Snap();
expect(instance).to.be.an(Snap);
});
});
describe('handleEvent', function () {
let target, map;
const width = 360;
const height = 180;
beforeEach(function (done) {
target = document.createElement('div');
const style = target.style;
style.position = 'absolute';
style.left = '-1000px';
style.top = '-1000px';
style.width = width + 'px';
style.height = height + 'px';
document.body.appendChild(target);
map = new Map({
target: target,
view: new View({
projection: 'EPSG:4326',
center: [0, 0],
resolution: 1,
}),
});
map.once('postrender', function () {
done();
});
});
afterEach(function () {
map.dispose();
document.body.removeChild(target);
clearUserProjection();
});
it('can handle XYZ coordinates', function () {
const point = new Feature(new Point([0, 0, 123]));
const snapInteraction = new Snap({
features: new Collection([point]),
});
snapInteraction.setMap(map);
const event = {
pixel: [width / 2, height / 2],
coordinate: [0, 0],
map: map,
};
snapInteraction.handleEvent(event);
// check that the coordinate is in XY and not XYZ
expect(event.coordinate).to.eql([0, 0]);
});
it('snaps to edges only', function () {
const point = new Feature(
new LineString([
[-10, 0],
[10, 0],
])
);
const snapInteraction = new Snap({
features: new Collection([point]),
pixelTolerance: 5,
vertex: false,
});
snapInteraction.setMap(map);
const event = {
pixel: [7 + width / 2, height / 2 - 4],
coordinate: [7, 4],
map: map,
};
snapInteraction.handleEvent(event);
expect(event.coordinate).to.eql([7, 0]);
});
it('snaps to vertices only', function () {
const point = new Feature(
new LineString([
[-10, 0],
[10, 0],
])
);
const snapInteraction = new Snap({
features: new Collection([point]),
pixelTolerance: 5,
edge: false,
});
snapInteraction.setMap(map);
const event = {
pixel: [7 + width / 2, height / 2 - 4],
coordinate: [7, 4],
map: map,
};
snapInteraction.handleEvent(event);
expect(event.coordinate).to.eql([10, 0]);
});
it('snaps to circle', function () {
const circle = new Feature(new Circle([0, 0], 10));
const snapInteraction = new Snap({
features: new Collection([circle]),
pixelTolerance: 5,
});
snapInteraction.setMap(map);
const event = {
pixel: [5 + width / 2, height / 2 - 5],
coordinate: [5, 5],
map: map,
};
snapInteraction.handleEvent(event);
expect(event.coordinate[0]).to.roughlyEqual(
Math.sin(Math.PI / 4) * 10,
1e-10
);
expect(event.coordinate[1]).to.roughlyEqual(
Math.sin(Math.PI / 4) * 10,
1e-10
);
});
it('snaps to circle in a user projection', function () {
const userProjection = 'EPSG:3857';
setUserProjection(userProjection);
const viewProjection = map.getView().getProjection();
const circle = new Feature(
new Circle([0, 0], 10).transform(viewProjection, userProjection)
);
const snapInteraction = new Snap({
features: new Collection([circle]),
pixelTolerance: 5,
});
snapInteraction.setMap(map);
const event = {
pixel: [5 + width / 2, height / 2 - 5],
coordinate: transform([5, 5], viewProjection, userProjection),
map: map,
};
snapInteraction.handleEvent(event);
const coordinate = transform(
[Math.sin(Math.PI / 4) * 10, Math.sin(Math.PI / 4) * 10],
viewProjection,
userProjection
);
expect(event.coordinate[0]).to.roughlyEqual(coordinate[0], 1e-10);
expect(event.coordinate[1]).to.roughlyEqual(coordinate[1], 1e-10);
});
it('handle feature without geometry', function () {
const feature = new Feature();
const snapInteraction = new Snap({
features: new Collection([feature]),
pixelTolerance: 5,
edge: false,
});
snapInteraction.setMap(map);
feature.setGeometry(
new LineString([
[-10, 0],
[10, 0],
])
);
const event = {
pixel: [7 + width / 2, height / 2 - 4],
coordinate: [7, 4],
map: map,
};
snapInteraction.handleEvent(event);
expect(event.coordinate).to.eql([10, 0]);
});
it('handle geometry changes', function () {
const line = new Feature(
new LineString([
[-10, 0],
[0, 0],
])
);
const snapInteraction = new Snap({
features: new Collection([line]),
pixelTolerance: 5,
edge: false,
});
snapInteraction.setMap(map);
line.getGeometry().setCoordinates([
[-10, 0],
[10, 0],
]);
const event = {
pixel: [7 + width / 2, height / 2 - 4],
coordinate: [7, 4],
map: map,
};
snapInteraction.handleEvent(event);
expect(event.coordinate).to.eql([10, 0]);
});
it('handle geometry name changes', function () {
const line = new Feature({
geometry: new LineString([
[-10, 0],
[0, 0],
]),
alt_geometry: new LineString([
[-10, 0],
[10, 0],
]),
});
const snapInteraction = new Snap({
features: new Collection([line]),
pixelTolerance: 5,
edge: false,
});
snapInteraction.setMap(map);
line.setGeometryName('alt_geometry');
const event = {
pixel: [7 + width / 2, height / 2 - 4],
coordinate: [7, 4],
map: map,
};
snapInteraction.handleEvent(event);
expect(event.coordinate).to.eql([10, 0]);
});
});
describe('handleEvent - useGeographic', () => {
let target, map;
const size = 256;
let restoreRAF;
beforeEach((done) => {
restoreRAF = overrideRAF();
useGeographic();
target = document.createElement('div');
Object.assign(target.style, {
position: 'absolute',
top: 0,
left: 0,
width: `${size}px`,
height: `${size}px`,
});
document.body.appendChild(target);
map = new Map({
target: target,
view: new View({
center: [0, 0],
zoom: 0,
}),
});
map.once('postrender', () => {
done();
});
});
afterEach(() => {
map.dispose();
document.body.removeChild(target);
clearUserProjection();
restoreRAF();
});
it('snaps to user coordinates', () => {
const lon = -90;
const lat = 45;
const point = new Feature(new Point([lon, lat]));
const snap = new Snap({
features: new Collection([point]),
});
snap.setMap(map);
const expectedPixel = map
.getPixelFromCoordinate([lon, lat])
.map((value) => Math.round(value));
const delta = 5;
const pixel = expectedPixel.slice();
pixel[0] += delta;
pixel[1] += delta;
const coordinate = map.getCoordinateFromPixel(pixel);
const event = {
pixel: pixel,
coordinate: coordinate,
map: map,
};
snap.handleEvent(event);
expect(event.coordinate).to.eql([lon, lat]);
expect(event.pixel).to.eql(expectedPixel);
});
});
});
@@ -0,0 +1,327 @@
import Collection from '../../../../../src/ol/Collection.js';
import Feature from '../../../../../src/ol/Feature.js';
import Interaction from '../../../../../src/ol/interaction/Interaction.js';
import Map from '../../../../../src/ol/Map.js';
import MapBrowserEvent from '../../../../../src/ol/MapBrowserEvent.js';
import Point from '../../../../../src/ol/geom/Point.js';
import Translate, {
TranslateEvent,
} from '../../../../../src/ol/interaction/Translate.js';
import VectorLayer from '../../../../../src/ol/layer/Vector.js';
import VectorSource from '../../../../../src/ol/source/Vector.js';
import View from '../../../../../src/ol/View.js';
describe('ol.interaction.Translate', function () {
let target, map, source, features;
const width = 360;
const height = 180;
beforeEach(function (done) {
target = document.createElement('div');
const style = target.style;
style.position = 'absolute';
style.left = '-1000px';
style.top = '-1000px';
style.width = width + 'px';
style.height = height + 'px';
document.body.appendChild(target);
source = new VectorSource();
features = [
new Feature({
geometry: new Point([10, -20]),
}),
new Feature({
geometry: new Point([20, -30]),
}),
];
source.addFeatures(features);
const layer = new VectorLayer({source: source});
map = new Map({
target: target,
layers: [layer],
view: new View({
projection: 'EPSG:4326',
center: [0, 0],
resolution: 1,
}),
});
map.once('postrender', function () {
done();
});
});
afterEach(function () {
map.dispose();
document.body.removeChild(target);
});
/**
* Simulates a browser event on the map viewport. The client x/y location
* will be adjusted as if the map were centered at 0,0.
* @param {string} type Event type.
* @param {number} x Horizontal offset from map center.
* @param {number} y Vertical offset from map center.
* @param {boolean} [opt_shiftKey] Shift key is pressed.
*/
function simulateEvent(type, x, y, opt_shiftKey) {
const viewport = map.getViewport();
// calculated in case body has top < 0 (test runner with small window)
const position = viewport.getBoundingClientRect();
const shiftKey = opt_shiftKey !== undefined ? opt_shiftKey : false;
const event = new MapBrowserEvent(type, map, {
type: type,
target: viewport.firstChild,
pointerId: 0,
clientX: position.left + x + width / 2,
clientY: position.top + y + height / 2,
shiftKey: shiftKey,
preventDefault: function () {},
});
map.handleMapBrowserEvent(event);
}
/**
* Tracks events triggered by the interaction as well as feature
* modifications. Helper function to
* @param {ol.Feature} feature Translated feature.
* @param {ol.interaction.Translate} interaction The interaction.
* @return {Array<TranslateEvent|string>} events
*/
function trackEvents(feature, interaction) {
const events = [];
feature.on('change', function (event) {
events.push('change');
});
interaction.on('translatestart', function (event) {
events.push(event);
});
interaction.on('translateend', function (event) {
events.push(event);
});
return events;
}
/**
* Validates the event array to verify proper event sequence. Checks
* that first and last event are correct TranslateEvents and that feature
* modifications event are in between.
* @param {Array<TranslateEvent|string>} events The events.
* @param {Array<ol.Feature>} features The features.
*/
function validateEvents(events, features) {
const startevent = events[0];
const endevent = events[events.length - 1];
// first event should be translatestart
expect(startevent).to.be.an(TranslateEvent);
expect(startevent.type).to.eql('translatestart');
// last event should be translateend
expect(endevent).to.be.an(TranslateEvent);
expect(endevent.type).to.eql('translateend');
// make sure we get change events to events array
expect(events.length > 2).to.be(true);
// middle events should be feature modification events
for (let i = 1; i < events.length - 1; i++) {
expect(events[i]).to.equal('change');
}
// TranslateEvents should include the expected features
expect(startevent.features.getArray()).to.eql(features);
expect(endevent.features.getArray()).to.eql(features);
}
describe('constructor', function () {
it('creates a new interaction', function () {
const translate = new Translate({
features: features,
});
expect(translate).to.be.a(Translate);
expect(translate).to.be.a(Interaction);
});
});
describe('setActive', function () {
it('works when the map is not set', function () {
const translate = new Translate({
features: features,
});
expect(translate.getActive()).to.be(true);
translate.setActive(false);
expect(translate.getActive()).to.be(false);
});
});
describe('moving features, with features option', function () {
let translate;
beforeEach(function () {
translate = new Translate({
features: new Collection([features[0]]),
});
map.addInteraction(translate);
});
it('moves a selected feature', function () {
const events = trackEvents(features[0], translate);
simulateEvent('pointermove', 10, 20);
simulateEvent('pointerdown', 10, 20);
simulateEvent('pointerdrag', 50, -40);
simulateEvent('pointerup', 50, -40);
const geometry = features[0].getGeometry();
expect(geometry).to.be.a(Point);
expect(geometry.getCoordinates()).to.eql([50, 40]);
validateEvents(events, [features[0]]);
});
it('does not move an unselected feature', function () {
const events = trackEvents(features[0], translate);
simulateEvent('pointermove', 20, 30);
simulateEvent('pointerdown', 20, 30);
simulateEvent('pointerdrag', 50, -40);
simulateEvent('pointerup', 50, -40);
const geometry = features[1].getGeometry();
expect(geometry).to.be.a(Point);
expect(geometry.getCoordinates()).to.eql([20, -30]);
expect(events).to.be.empty();
});
});
describe('moving features, without features option', function () {
let translate;
beforeEach(function () {
translate = new Translate();
map.addInteraction(translate);
});
it('moves only targeted feature', function () {
const events = trackEvents(features[0], translate);
simulateEvent('pointermove', 10, 20);
simulateEvent('pointerdown', 10, 20);
simulateEvent('pointerdrag', 50, -40);
simulateEvent('pointerup', 50, -40);
expect(features[0].getGeometry().getCoordinates()).to.eql([50, 40]);
expect(features[1].getGeometry().getCoordinates()).to.eql([20, -30]);
validateEvents(events, [features[0]]);
});
});
describe('moving features, with filter option', function () {
let translate;
beforeEach(function () {
translate = new Translate({
filter: function (feature, layer) {
return feature == features[0];
},
});
map.addInteraction(translate);
});
it('moves a filter-passing feature', function () {
const events = trackEvents(features[0], translate);
simulateEvent('pointermove', 10, 20);
simulateEvent('pointerdown', 10, 20);
simulateEvent('pointerdrag', 50, -40);
simulateEvent('pointerup', 50, -40);
const geometry = features[0].getGeometry();
expect(geometry).to.be.a(Point);
expect(geometry.getCoordinates()).to.eql([50, 40]);
validateEvents(events, [features[0]]);
});
it('does not move a filter-discarded feature', function () {
const events = trackEvents(features[0], translate);
simulateEvent('pointermove', 20, 30);
simulateEvent('pointerdown', 20, 30);
simulateEvent('pointerdrag', 50, -40);
simulateEvent('pointerup', 50, -40);
const geometry = features[1].getGeometry();
expect(geometry).to.be.a(Point);
expect(geometry.getCoordinates()).to.eql([20, -30]);
expect(events).to.be.empty();
});
});
describe('changes css cursor', function () {
let element, translate;
beforeEach(function () {
translate = new Translate();
map.addInteraction(translate);
element = map.getViewport();
});
it('changes css cursor', function () {
expect(element.classList.contains('ol-grabbing')).to.be(false);
expect(element.classList.contains('ol-grab')).to.be(false);
simulateEvent('pointermove', 10, 20);
expect(element.classList.contains('ol-grabbing')).to.be(false);
expect(element.classList.contains('ol-grab')).to.be(true);
simulateEvent('pointerdown', 10, 20);
expect(element.classList.contains('ol-grabbing')).to.be(true);
expect(element.classList.contains('ol-grab')).to.be(false);
simulateEvent('pointerup', 10, 20);
expect(element.classList.contains('ol-grabbing')).to.be(false);
expect(element.classList.contains('ol-grab')).to.be(true);
simulateEvent('pointermove', 0, 0);
expect(element.classList.contains('ol-grabbing')).to.be(false);
expect(element.classList.contains('ol-grab')).to.be(false);
});
it('resets css cursor when interaction is deactivated while pointer is on feature', function () {
simulateEvent('pointermove', 10, 20);
expect(element.classList.contains('ol-grabbing')).to.be(false);
expect(element.classList.contains('ol-grab')).to.be(true);
translate.setActive(false);
simulateEvent('pointermove', 0, 0);
expect(element.classList.contains('ol-grabbing')).to.be(false);
expect(element.classList.contains('ol-grab')).to.be(false);
});
it('resets css cursor interaction is removed while pointer is on feature', function () {
simulateEvent('pointermove', 10, 20);
expect(element.classList.contains('ol-grabbing')).to.be(false);
expect(element.classList.contains('ol-grab')).to.be(true);
map.removeInteraction(translate);
simulateEvent('pointermove', 0, 0);
expect(element.classList.contains('ol-grabbing')).to.be(false);
expect(element.classList.contains('ol-grab')).to.be(false);
});
it('resets css cursor to existing cursor interaction is removed while pointer is on feature', function () {
element.style.cursor = 'pointer';
simulateEvent('pointermove', 10, 20);
expect(element.classList.contains('ol-grabbing')).to.be(false);
expect(element.classList.contains('ol-grab')).to.be(true);
map.removeInteraction(translate);
simulateEvent('pointermove', 0, 0);
expect(element.classList.contains('ol-grabbing')).to.be(false);
expect(element.classList.contains('ol-grab')).to.be(false);
});
});
});