leaflet-rotate 0.2.8 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci.yml +21 -0
- package/README.md +6 -2
- package/build/build.js +69 -0
- package/build/dev.js +24 -0
- package/build/server.js +104 -0
- package/build/test.js +18 -0
- package/cucumber.mjs +5 -0
- package/dist/leaflet-rotate-src.js +1425 -1853
- package/dist/leaflet-rotate-src.js.map +7 -1
- package/dist/leaflet-rotate.js +1 -1
- package/dist/leaflet-rotate.js.map +7 -1
- package/lib/debug.js +98 -69
- package/lib/debug.min.js +1 -1
- package/misc/leaflet-heat-rotate.js +96 -0
- package/misc/loremIpsum.js +1 -1
- package/misc/places.js +1 -1
- package/misc/route.js +1 -1
- package/package.json +10 -17
- package/src/control/Rotate.js +25 -25
- package/src/dom/DomUtil.js +31 -47
- package/src/dom/Draggable.js +1 -1
- package/src/geometry/Point.js +16 -7
- package/src/layer/DivOverlay.js +21 -7
- package/src/layer/Popup.js +15 -13
- package/src/layer/Tooltip.js +5 -5
- package/src/layer/marker/Icon.js +8 -8
- package/src/layer/marker/Marker.js +32 -18
- package/src/layer/tile/GridLayer.js +4 -4
- package/src/layer/vector/Renderer.js +51 -7
- package/src/map/Map.js +121 -57
- package/src/map/handler/CompassBearing.js +10 -8
- package/src/map/handler/ContainerMutation.js +2 -2
- package/src/map/handler/ShiftKeyRotate.js +4 -4
- package/src/map/handler/TouchGestures.js +36 -22
- package/src/map/handler/TouchRotate.js +2 -2
- package/src/map/handler/TouchZoom.js +4 -4
- package/test/features/example.feature +92 -0
- package/test/features/gestures.feature +33 -0
- package/test/features/overlays.feature +58 -0
- package/test/features/rotation.feature +36 -0
- package/test/index.html +59 -22
- package/test/steps/bearing.steps.js +235 -0
- package/test/steps/example.steps.js +424 -0
- package/test/steps/gestures.steps.js +145 -0
- package/test/steps/overlays.steps.js +427 -0
- package/test/support/hooks.js +92 -0
- package/build/rollup.config.js +0 -34
- package/test/setup/http_server.js +0 -104
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { Then, When } from '@cucumber/cucumber';
|
|
3
|
+
|
|
4
|
+
Then('rotation is enabled on the map', async function () {
|
|
5
|
+
const result = await this.page.evaluate(() => ({
|
|
6
|
+
option: map.options.rotate,
|
|
7
|
+
property: map._rotate,
|
|
8
|
+
pane: map._rotatePane instanceof HTMLDivElement,
|
|
9
|
+
}));
|
|
10
|
+
assert.deepEqual(result, { option: true, property: true, pane: true });
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
Then('the map view contains the {string} bounds', function (layerName) {
|
|
14
|
+
assert.equal(this.fittedBounds, true, `${layerName} bounds were not fitted`);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
Then('the rectangle is fitted at zoom 14', function () {
|
|
18
|
+
assert.equal(this.rectangleZoom, 14);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
When('I set the bearing to 10 and zoom in twice', async function () {
|
|
22
|
+
this.bearings = await this.page.evaluate(() => {
|
|
23
|
+
map.setBearing(10);
|
|
24
|
+
const initial = map.getBearing();
|
|
25
|
+
map.zoomIn();
|
|
26
|
+
const firstZoom = map.getBearing();
|
|
27
|
+
map.zoomIn();
|
|
28
|
+
return [initial, firstZoom, map.getBearing()];
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
When('I set the bearing to 10 and change the view twice', async function () {
|
|
33
|
+
this.bearings = await this.page.evaluate(() => {
|
|
34
|
+
map.setBearing(10);
|
|
35
|
+
const initial = map.getBearing();
|
|
36
|
+
map.setView({ lat: 50.5, lng: 30.5 }, 15, {});
|
|
37
|
+
const firstView = map.getBearing();
|
|
38
|
+
map.setView({ lat: 51.5, lng: 34.5 }, 17, {});
|
|
39
|
+
return [initial, firstView, map.getBearing()];
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
Then('the bearing remains 10', function () {
|
|
44
|
+
assert.deepEqual(this.bearings, [10, 10, 10]);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
When('I animate the bearing to 90 degrees', async function () {
|
|
48
|
+
this.animatedBearings = await this.page.evaluate(() => new Promise(resolve => {
|
|
49
|
+
const bearings = [];
|
|
50
|
+
const onRotate = () => {
|
|
51
|
+
const bearing = map.getBearing();
|
|
52
|
+
bearings.push(bearing);
|
|
53
|
+
if (Math.abs(bearing - 90) < 1e-6) {
|
|
54
|
+
map.off('rotate', onRotate);
|
|
55
|
+
map.setBearing(15);
|
|
56
|
+
map.compassBearing._onDeviceOrientation({ alpha: null, absolute: true });
|
|
57
|
+
resolve({ bearings, afterUnavailableHeading: map.getBearing() });
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
map.setBearing(0);
|
|
62
|
+
map.on('rotate', onRotate);
|
|
63
|
+
map.setBearing(90, { animate: true, duration: 0.15, easeLinearity: 0.25 });
|
|
64
|
+
}));
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
Then('the bearing reaches 90 degrees gradually', function () {
|
|
68
|
+
assert.ok(this.animatedBearings.bearings.length > 1);
|
|
69
|
+
assert.ok(this.animatedBearings.bearings.some(bearing => bearing > 0 && bearing < 90));
|
|
70
|
+
assert.equal(this.animatedBearings.bearings.at(-1), 90);
|
|
71
|
+
assert.ok(Math.abs(this.animatedBearings.afterUnavailableHeading - 15) < 1e-6);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
When('I fit the same bounds twice', async function () {
|
|
75
|
+
this.zooms = await this.page.evaluate(() => {
|
|
76
|
+
const bounds = [[60.81123, 25.71632], [60.81028, 25.73166], [60.7997, 25.72892], [60.80061, 25.71362]];
|
|
77
|
+
new L.Rectangle(bounds).addTo(map);
|
|
78
|
+
map.setZoom(0, { animate: false });
|
|
79
|
+
map.fitBounds(bounds, { animate: false });
|
|
80
|
+
const first = map.getZoom();
|
|
81
|
+
map.fitBounds(bounds, { animate: false });
|
|
82
|
+
return [first, map.getZoom()];
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
Then('the map zoom is 14 both times', function () {
|
|
87
|
+
assert.deepEqual(this.zooms, [14, 14]);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
When('I pan inside the Trondheim marker', async function () {
|
|
91
|
+
this.markerBounds = await this.page.evaluate(() => {
|
|
92
|
+
const marker = markers[4];
|
|
93
|
+
const bounds = [];
|
|
94
|
+
map.setBearing(50);
|
|
95
|
+
map.setView(marker.getLatLng(), 30, { animate: false });
|
|
96
|
+
bounds.push(map.getBounds());
|
|
97
|
+
map.panBy([0, map._container.clientHeight / 2], { animate: false });
|
|
98
|
+
bounds.push(map.getBounds());
|
|
99
|
+
map.panBy([0, -marker.getElement().clientHeight], { animate: false });
|
|
100
|
+
bounds.push(map.getBounds());
|
|
101
|
+
map.panInside(marker.getLatLng());
|
|
102
|
+
bounds.push(map.getBounds());
|
|
103
|
+
map.panInside(marker.getLatLng());
|
|
104
|
+
bounds.push(map.getBounds());
|
|
105
|
+
marker.getElement().click();
|
|
106
|
+
bounds.push(map.getBounds());
|
|
107
|
+
return { autoPanOnFocus: marker.options.autoPanOnFocus, bounds };
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
Then('panning inside keeps the expected bounds', function () {
|
|
112
|
+
const { autoPanOnFocus, bounds } = this.markerBounds;
|
|
113
|
+
assert.equal(autoPanOnFocus, true);
|
|
114
|
+
const expected = [
|
|
115
|
+
{ _southWest: { lat: 63.40997749296754, lng: 10.409954421520982 }, _northEast: { lat: 63.41002250228354, lng: 10.410045638381689 } },
|
|
116
|
+
{ _southWest: { lat: 63.40997315210509, lng: 10.409965979159589 }, _northEast: { lat: 63.4100181614279, lng: 10.410057196020334 } },
|
|
117
|
+
{ _southWest: { lat: 63.409974140857145, lng: 10.40996334658635 }, _northEast: { lat: 63.410019150178414, lng: 10.410054563447094 } },
|
|
118
|
+
];
|
|
119
|
+
for (let index = 0; index < expected.length; index++) {
|
|
120
|
+
for (const corner of ['_southWest', '_northEast']) {
|
|
121
|
+
for (const coordinate of ['lat', 'lng']) {
|
|
122
|
+
assert.ok(Math.abs(bounds[index][corner][coordinate] - expected[index][corner][coordinate]) < 1e-6);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
for (const index of [3, 4, 5]) {
|
|
127
|
+
for (const corner of ['_southWest', '_northEast']) {
|
|
128
|
+
for (const coordinate of ['lat', 'lng']) {
|
|
129
|
+
assert.ok(Math.abs(bounds[index][corner][coordinate] - bounds[index - 1][corner][coordinate]) < 1e-6);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
Then('the draggable marker has one drag listener and one drag-end listener', async function () {
|
|
136
|
+
const counts = await this.page.evaluate(() => {
|
|
137
|
+
const marker = markers.find(item => item.dragging.enabled());
|
|
138
|
+
if (!marker) throw new Error('No draggable marker on test page');
|
|
139
|
+
return [
|
|
140
|
+
marker.dragging._draggable._events.drag.length,
|
|
141
|
+
marker.dragging._draggable._events.dragend.length,
|
|
142
|
+
];
|
|
143
|
+
});
|
|
144
|
+
assert.deepEqual(counts, [1, 1]);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
When('I rotate and zoom two fingers over a vector path', async function () {
|
|
148
|
+
this.gesturePathPositions = await this.page.evaluate(() => {
|
|
149
|
+
map.options.zoomAnimation = true;
|
|
150
|
+
map.setView([55, 10], 6, { animate: false });
|
|
151
|
+
const location = new L.LatLng(55.1, 10.1);
|
|
152
|
+
const path = new L.CircleMarker(location, { radius: 8 }).addTo(map);
|
|
153
|
+
const container = map.getContainer();
|
|
154
|
+
const center = map.getSize().divideBy(2);
|
|
155
|
+
const initial = map.getBearing(),
|
|
156
|
+
initialZoom = map.getZoom();
|
|
157
|
+
const touches = (radius, angle) => {
|
|
158
|
+
const dx = radius * Math.cos(angle);
|
|
159
|
+
const dy = radius * Math.sin(angle);
|
|
160
|
+
return [-1, 1].map((sign, identifier) => new Touch({
|
|
161
|
+
identifier,
|
|
162
|
+
target: container,
|
|
163
|
+
clientX: container.getBoundingClientRect().left + center.x + sign * dx,
|
|
164
|
+
clientY: container.getBoundingClientRect().top + center.y + sign * dy,
|
|
165
|
+
}));
|
|
166
|
+
};
|
|
167
|
+
const dispatch = (type, activeTouches) => {
|
|
168
|
+
const event = new TouchEvent(type, {
|
|
169
|
+
bubbles: true, cancelable: true,
|
|
170
|
+
touches: activeTouches, targetTouches: activeTouches, changedTouches: activeTouches,
|
|
171
|
+
});
|
|
172
|
+
(type === 'touchstart' ? container : document).dispatchEvent(event);
|
|
173
|
+
};
|
|
174
|
+
const measure = () => {
|
|
175
|
+
const element = path.getElement();
|
|
176
|
+
const bounds = element.getBBox();
|
|
177
|
+
const screen = new DOMPoint(bounds.x + bounds.width / 2, bounds.y + bounds.height / 2)
|
|
178
|
+
.matrixTransform(element.getScreenCTM());
|
|
179
|
+
const rect = container.getBoundingClientRect();
|
|
180
|
+
const expected = map.latLngToContainerPoint(location);
|
|
181
|
+
return [screen.x - rect.left - expected.x, screen.y - rect.top - expected.y];
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
dispatch('touchstart', touches(40, 0));
|
|
185
|
+
dispatch('touchmove', touches(60, Math.PI / 6));
|
|
186
|
+
const during = measure();
|
|
187
|
+
const bearingDuring = map.getBearing(),
|
|
188
|
+
zoomDuring = map.getZoom();
|
|
189
|
+
dispatch('touchend', []);
|
|
190
|
+
map.setBearing(initial);
|
|
191
|
+
return {
|
|
192
|
+
delta: during,
|
|
193
|
+
bearingChange: Math.abs(bearingDuring - initial),
|
|
194
|
+
zoomChange: zoomDuring - initialZoom,
|
|
195
|
+
};
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
When('I disable touch rotation and rotate two fingers', async function () {
|
|
200
|
+
this.bearings = await this.page.evaluate(() => {
|
|
201
|
+
const container = map.getContainer();
|
|
202
|
+
const rect = container.getBoundingClientRect();
|
|
203
|
+
const centerX = rect.left + rect.width / 2;
|
|
204
|
+
const centerY = rect.top + rect.height / 2;
|
|
205
|
+
const touches = (x1, y1, x2, y2) => [
|
|
206
|
+
new Touch({ identifier: 1, target: container, clientX: centerX + x1, clientY: centerY + y1 }),
|
|
207
|
+
new Touch({ identifier: 2, target: container, clientX: centerX + x2, clientY: centerY + y2 }),
|
|
208
|
+
];
|
|
209
|
+
const dispatch = (target, type, activeTouches, changedTouches = activeTouches) => {
|
|
210
|
+
target.dispatchEvent(new TouchEvent(type, {
|
|
211
|
+
bubbles: true,
|
|
212
|
+
cancelable: true,
|
|
213
|
+
touches: activeTouches,
|
|
214
|
+
targetTouches: activeTouches,
|
|
215
|
+
changedTouches,
|
|
216
|
+
}));
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
map.touchRotate.disable();
|
|
220
|
+
const before = map.getBearing();
|
|
221
|
+
dispatch(container, 'touchstart', touches(-30, 0, 30, 0));
|
|
222
|
+
dispatch(document, 'touchmove', touches(0, -30, 0, 30));
|
|
223
|
+
dispatch(document, 'touchend', [], touches(0, -30, 0, 30));
|
|
224
|
+
return [before, map.getBearing(), map.touchGestures.rotate];
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
Then('its drag callbacks match the marker and pointer coordinates', function () {
|
|
229
|
+
assert.equal(this.markerPosition.moved, true, JSON.stringify(this.markerPosition));
|
|
230
|
+
assert.ok(Math.hypot(...this.markerPosition.delta) < 2, JSON.stringify(this.markerPosition));
|
|
231
|
+
assert.ok(this.markerPosition.callbacks.length > 1, JSON.stringify(this.markerPosition));
|
|
232
|
+
const lastCallback = this.markerPosition.callbacks.at(-1);
|
|
233
|
+
assert.ok(Math.abs(this.markerPosition.marker.lat - lastCallback[0]) < 1e-7, JSON.stringify(this.markerPosition));
|
|
234
|
+
assert.ok(Math.abs(this.markerPosition.marker.lng - lastCallback[1]) < 1e-7, JSON.stringify(this.markerPosition));
|
|
235
|
+
});
|
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { Then, When } from '@cucumber/cucumber';
|
|
3
|
+
|
|
4
|
+
Then('the displayed bounds use the selected padding', function () {
|
|
5
|
+
assert.equal(this.debugPadding.value, -0.01);
|
|
6
|
+
assert.equal(this.debugPadding.refreshed, true);
|
|
7
|
+
assert.equal(this.debugPadding.boundsMatch, true);
|
|
8
|
+
assert.equal(this.debugPadding.centerMatches, true);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
Then('exactly the selected base layer is visible', function () {
|
|
12
|
+
assert.deepEqual(this.visibleBaseLayers, [['Empty'], ['Streets'], ['Satellite']]);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
Then('the zoom returns to its starting level', function () {
|
|
16
|
+
assert.equal(this.zoomAfterIn, this.startingZoom + 1);
|
|
17
|
+
assert.equal(this.finalZoom, this.startingZoom);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
Then('every example marker has a new rotation', function () {
|
|
21
|
+
assert.equal(this.markerRotationsAfter.length, this.markerRotationsBefore.length);
|
|
22
|
+
assert.ok(this.markerRotationsAfter.every((rotation, index) =>
|
|
23
|
+
Number.isFinite(rotation) && rotation > this.markerRotationsBefore[index]));
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
Then('the map settles at {string} at zoom 10', function (place) {
|
|
27
|
+
assert.ok(this.destination.distance < 1, `${place}: ${JSON.stringify(this.destination)}`);
|
|
28
|
+
assert.ok(Math.abs(this.destination.zoom - 10) < 1e-6, JSON.stringify(this.destination));
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
When('I use the debugger rotation controls', async function () {
|
|
32
|
+
this.debugBearings = [];
|
|
33
|
+
for (const bearing of [0, 15, 30, 45, 60, 75, 90, 180]) {
|
|
34
|
+
await this.page.getByRole('button', { name: String(bearing), exact: true }).click();
|
|
35
|
+
this.debugBearings.push(await this.page.evaluate(() => ({
|
|
36
|
+
map: map.getBearing(),
|
|
37
|
+
slider: document.getElementById('rho_input').valueAsNumber,
|
|
38
|
+
arrow: document.querySelector('.leaflet-control-rotate-arrow').style.transform,
|
|
39
|
+
})));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const slider = this.page.locator('#rho_input');
|
|
43
|
+
await slider.focus();
|
|
44
|
+
await slider.press('ArrowRight');
|
|
45
|
+
this.debugBearings.push(await this.page.evaluate(() => ({
|
|
46
|
+
map: map.getBearing(),
|
|
47
|
+
slider: document.getElementById('rho_input').valueAsNumber,
|
|
48
|
+
arrow: document.querySelector('.leaflet-control-rotate-arrow').style.transform,
|
|
49
|
+
})));
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
Then('every debugger rotation control matches the map bearing', function () {
|
|
53
|
+
const expected = [0, 15, 30, 45, 60, 75, 90, 180, 181];
|
|
54
|
+
assert.equal(this.debugBearings.length, expected.length);
|
|
55
|
+
this.debugBearings.forEach((value, index) => {
|
|
56
|
+
assert.ok(Math.abs(value.map - expected[index]) < 1e-6, JSON.stringify(value));
|
|
57
|
+
assert.equal(value.slider, expected[index]);
|
|
58
|
+
assert.equal(value.arrow, `rotate(${expected[index]}deg)`);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
When('I rotate a map with the default rotate control', async function () {
|
|
63
|
+
this.rotateControlVisibility = await this.page.evaluate(() => {
|
|
64
|
+
const container = document.createElement('div');
|
|
65
|
+
container.style.cssText = 'width: 400px; height: 300px';
|
|
66
|
+
document.body.append(container);
|
|
67
|
+
const testMap = new L.Map(container, {
|
|
68
|
+
center: [55, 10],
|
|
69
|
+
zoom: 6,
|
|
70
|
+
rotate: true,
|
|
71
|
+
zoomControl: false,
|
|
72
|
+
});
|
|
73
|
+
const control = container.querySelector('.leaflet-control-rotate');
|
|
74
|
+
const displayAtZero = getComputedStyle(control).display;
|
|
75
|
+
testMap.setBearing(30);
|
|
76
|
+
const displayWhenRotated = getComputedStyle(control).display;
|
|
77
|
+
testMap.setBearing(0);
|
|
78
|
+
const displayAfterReset = getComputedStyle(control).display;
|
|
79
|
+
testMap.remove();
|
|
80
|
+
container.remove();
|
|
81
|
+
return [displayAtZero, displayWhenRotated, displayAfterReset];
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
Then('the default rotate control visibility follows the bearing', function () {
|
|
86
|
+
assert.deepEqual(this.rotateControlVisibility, ['none', 'block', 'none']);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
When('I drag the rotate control', async function () {
|
|
90
|
+
this.controlBearing = await this.page.evaluate(() => {
|
|
91
|
+
map.setBearing(0);
|
|
92
|
+
const link = map.rotateControl._link;
|
|
93
|
+
link.dispatchEvent(new MouseEvent('mousedown', { bubbles: true, clientX: 100, clientY: 100 }));
|
|
94
|
+
document.dispatchEvent(new MouseEvent('mousemove', { bubbles: true, clientX: 135, clientY: 100 }));
|
|
95
|
+
document.dispatchEvent(new MouseEvent('mouseup', { bubbles: true, clientX: 135, clientY: 100 }));
|
|
96
|
+
return map.getBearing();
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
Then('the map bearing follows the control drag', function () {
|
|
101
|
+
assert.equal(this.controlBearing, 35);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
When('I activate compass mode on a permission-based device', async function () {
|
|
105
|
+
this.permissionRequests = await this.page.evaluate(async () => {
|
|
106
|
+
const descriptor = Object.getOwnPropertyDescriptor(window, 'DeviceOrientationEvent');
|
|
107
|
+
window.permissionRequests = 0;
|
|
108
|
+
Object.defineProperty(window, 'DeviceOrientationEvent', {
|
|
109
|
+
configurable: true,
|
|
110
|
+
value: class {
|
|
111
|
+
static requestPermission() {
|
|
112
|
+
window.permissionRequests++;
|
|
113
|
+
return Promise.resolve('denied');
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
const click = () => map.rotateControl._link.dispatchEvent(new MouseEvent('click', {
|
|
118
|
+
bubbles: true,
|
|
119
|
+
cancelable: true,
|
|
120
|
+
}));
|
|
121
|
+
click();
|
|
122
|
+
click();
|
|
123
|
+
await Promise.resolve();
|
|
124
|
+
if (descriptor) {
|
|
125
|
+
Object.defineProperty(window, 'DeviceOrientationEvent', descriptor);
|
|
126
|
+
} else {
|
|
127
|
+
delete window.DeviceOrientationEvent;
|
|
128
|
+
}
|
|
129
|
+
return window.permissionRequests;
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
Then('device-orientation permission is requested', function () {
|
|
134
|
+
assert.equal(this.permissionRequests, 1);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
When('I apply compass heading readings', async function () {
|
|
138
|
+
this.compassReadings = await this.page.evaluate(async () => {
|
|
139
|
+
const compass = map.compassBearing;
|
|
140
|
+
const previousOrientation = window.orientation;
|
|
141
|
+
const setOrientation = value => Object.defineProperty(window, 'orientation', {
|
|
142
|
+
configurable: true,
|
|
143
|
+
value,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
map.setBearing(0);
|
|
147
|
+
compass._onDeviceOrientation({ alpha: 40, absolute: true });
|
|
148
|
+
const absolute = map.getBearing();
|
|
149
|
+
|
|
150
|
+
setOrientation(90);
|
|
151
|
+
compass._onDeviceOrientation({ alpha: 40, absolute: false });
|
|
152
|
+
const relative = map.getBearing();
|
|
153
|
+
|
|
154
|
+
setOrientation(0);
|
|
155
|
+
compass._onDeviceOrientation({ webkitCompassHeading: 30, absolute: false });
|
|
156
|
+
const ios = map.getBearing();
|
|
157
|
+
|
|
158
|
+
compass._onDeviceOrientation({ alpha: null, absolute: true });
|
|
159
|
+
const unavailable = map.getBearing();
|
|
160
|
+
|
|
161
|
+
const absoluteEventDescriptor = Object.getOwnPropertyDescriptor(window, 'ondeviceorientationabsolute');
|
|
162
|
+
Object.defineProperty(window, 'ondeviceorientationabsolute', {
|
|
163
|
+
configurable: true,
|
|
164
|
+
value: null,
|
|
165
|
+
});
|
|
166
|
+
const container = document.createElement('div');
|
|
167
|
+
container.style.cssText = 'width: 400px; height: 300px';
|
|
168
|
+
document.body.append(container);
|
|
169
|
+
const absoluteMap = new L.Map(container, {
|
|
170
|
+
center: [55, 10],
|
|
171
|
+
zoom: 6,
|
|
172
|
+
rotate: true,
|
|
173
|
+
compassBearing: true,
|
|
174
|
+
rotateControl: false,
|
|
175
|
+
zoomControl: false,
|
|
176
|
+
});
|
|
177
|
+
const selectedEvent = absoluteMap.compassBearing.__deviceOrientationEvent;
|
|
178
|
+
const event = new Event('deviceorientationabsolute');
|
|
179
|
+
Object.defineProperties(event, {
|
|
180
|
+
alpha: { value: 60 },
|
|
181
|
+
absolute: { value: true },
|
|
182
|
+
});
|
|
183
|
+
window.dispatchEvent(event);
|
|
184
|
+
await new Promise(resolve => setTimeout(resolve, 150));
|
|
185
|
+
const absoluteEventBearing = absoluteMap.getBearing();
|
|
186
|
+
absoluteMap.remove();
|
|
187
|
+
container.remove();
|
|
188
|
+
if (absoluteEventDescriptor) {
|
|
189
|
+
Object.defineProperty(window, 'ondeviceorientationabsolute', absoluteEventDescriptor);
|
|
190
|
+
} else {
|
|
191
|
+
delete window.ondeviceorientationabsolute;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (previousOrientation === undefined) {
|
|
195
|
+
delete window.orientation;
|
|
196
|
+
} else {
|
|
197
|
+
setOrientation(previousOrientation);
|
|
198
|
+
}
|
|
199
|
+
return { absolute, relative, ios, unavailable, selectedEvent, absoluteEventBearing };
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
Then('valid compass readings update bearing and invalid readings are ignored', function () {
|
|
204
|
+
const { absolute, relative, ios, unavailable, selectedEvent, absoluteEventBearing } = this.compassReadings;
|
|
205
|
+
assert.deepEqual({ absolute, relative, ios, unavailable, selectedEvent }, {
|
|
206
|
+
absolute: 40,
|
|
207
|
+
relative: 310,
|
|
208
|
+
ios: 330,
|
|
209
|
+
unavailable: 330,
|
|
210
|
+
selectedEvent: 'deviceorientationabsolute',
|
|
211
|
+
});
|
|
212
|
+
assert.ok(Math.abs(absoluteEventBearing - 60) < 1e-6);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
When('I use shift-wheel rotation with the handler disabled', async function () {
|
|
216
|
+
this.shiftWheelDisabled = await this.page.evaluate(async () => {
|
|
217
|
+
const container = document.createElement('div');
|
|
218
|
+
container.style.cssText = 'width: 400px; height: 300px';
|
|
219
|
+
document.body.append(container);
|
|
220
|
+
const testMap = new L.Map(container, {
|
|
221
|
+
center: [55, 10],
|
|
222
|
+
zoom: 6,
|
|
223
|
+
rotate: true,
|
|
224
|
+
rotateControl: false,
|
|
225
|
+
shiftKeyRotate: false,
|
|
226
|
+
zoomControl: false,
|
|
227
|
+
zoomAnimation: false,
|
|
228
|
+
});
|
|
229
|
+
const initialZoom = testMap.getZoom();
|
|
230
|
+
const initialBearing = testMap.getBearing();
|
|
231
|
+
container.dispatchEvent(new WheelEvent('wheel', {
|
|
232
|
+
bubbles: true,
|
|
233
|
+
cancelable: true,
|
|
234
|
+
deltaY: -60,
|
|
235
|
+
shiftKey: true,
|
|
236
|
+
}));
|
|
237
|
+
await new Promise(resolve => setTimeout(resolve, 150));
|
|
238
|
+
const result = {
|
|
239
|
+
handlerDisabled: !testMap.shiftKeyRotate.enabled(),
|
|
240
|
+
wheelZoomEnabled: testMap.scrollWheelZoom.enabled(),
|
|
241
|
+
zoomChanged: testMap.getZoom() > initialZoom,
|
|
242
|
+
bearingUnchanged: testMap.getBearing() === initialBearing,
|
|
243
|
+
};
|
|
244
|
+
testMap.remove();
|
|
245
|
+
container.remove();
|
|
246
|
+
return result;
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
Then('shift-wheel input zooms without changing the map bearing', function () {
|
|
251
|
+
assert.deepEqual(this.shiftWheelDisabled, {
|
|
252
|
+
handlerDisabled: true,
|
|
253
|
+
wheelZoomEnabled: true,
|
|
254
|
+
zoomChanged: true,
|
|
255
|
+
bearingUnchanged: true,
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
When('I change the debugger bounds padding from the keyboard', async function () {
|
|
260
|
+
await this.page.evaluate(() => {
|
|
261
|
+
map.setView([55, 10], 6, { animate: false });
|
|
262
|
+
map.setBearing(30);
|
|
263
|
+
window.previousDebugBounds = L.Rotate.___bounds;
|
|
264
|
+
});
|
|
265
|
+
const slider = this.page.locator('#pad_input');
|
|
266
|
+
await slider.focus();
|
|
267
|
+
await slider.press('ArrowLeft');
|
|
268
|
+
this.debugPadding = await this.page.evaluate(() => ({
|
|
269
|
+
value: document.getElementById('pad_input').valueAsNumber,
|
|
270
|
+
refreshed: window.previousDebugBounds !== L.Rotate.___bounds,
|
|
271
|
+
boundsMatch: L.Rotate.___bounds.getBounds().equals(map.getBounds().pad(
|
|
272
|
+
document.getElementById('pad_input').valueAsNumber,
|
|
273
|
+
)),
|
|
274
|
+
centerMatches: L.Rotate.___centerMarker.getLatLng().equals(map.getCenter()),
|
|
275
|
+
}));
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
When('I inspect debugger geometry after resizing and moving the map', async function () {
|
|
279
|
+
await this.page.evaluate(() => {
|
|
280
|
+
Object.assign(map.getContainer().style, {
|
|
281
|
+
width: '420px',
|
|
282
|
+
height: '280px',
|
|
283
|
+
margin: '80px 0 0 90px',
|
|
284
|
+
});
|
|
285
|
+
map.invalidateSize({ animate: false });
|
|
286
|
+
map.setView([48, 8], 5, { animate: false });
|
|
287
|
+
map.setBearing(30);
|
|
288
|
+
map.panBy([20, 15], { animate: false });
|
|
289
|
+
map.setZoom(6, { animate: false });
|
|
290
|
+
window.debugMouseEvent = null;
|
|
291
|
+
if (L.version.startsWith('2.')) {
|
|
292
|
+
map.getContainer().addEventListener('mousemove', event => {
|
|
293
|
+
const rect = map.getContainer().getBoundingClientRect();
|
|
294
|
+
const containerPoint = new L.Point(
|
|
295
|
+
event.clientX - rect.left - map.getContainer().clientLeft,
|
|
296
|
+
event.clientY - rect.top - map.getContainer().clientTop,
|
|
297
|
+
);
|
|
298
|
+
const latlng = map.containerPointToLatLng(containerPoint);
|
|
299
|
+
const layerPoint = map.containerPointToLayerPoint(containerPoint);
|
|
300
|
+
window.debugMouseEvent = {
|
|
301
|
+
latlng: [latlng.lng, latlng.lat],
|
|
302
|
+
layerPoint: [layerPoint.x, layerPoint.y],
|
|
303
|
+
containerPoint: [containerPoint.x, containerPoint.y],
|
|
304
|
+
};
|
|
305
|
+
}, { once: true });
|
|
306
|
+
} else {
|
|
307
|
+
map.once('mousemove', event => {
|
|
308
|
+
window.debugMouseEvent = {
|
|
309
|
+
latlng: [event.latlng.lng, event.latlng.lat],
|
|
310
|
+
layerPoint: [event.layerPoint.x, event.layerPoint.y],
|
|
311
|
+
containerPoint: [event.containerPoint.x, event.containerPoint.y],
|
|
312
|
+
};
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
const bounds = await this.page.locator('#map').boundingBox();
|
|
318
|
+
await this.page.mouse.move(bounds.x + 80, bounds.y + 60);
|
|
319
|
+
await this.page.waitForFunction(() => window.debugMouseEvent !== null);
|
|
320
|
+
this.debugGeometry = await this.page.evaluate(() => {
|
|
321
|
+
const read = id => Number(document.getElementById(id).textContent);
|
|
322
|
+
const size = map.getSize();
|
|
323
|
+
const corners = L.Rotate.___corners.getLayers();
|
|
324
|
+
const targets = [[0, 0], [size.x, 0], [size.x, size.y], [0, size.y]];
|
|
325
|
+
const cornerDeltas = corners.map((marker, index) => {
|
|
326
|
+
const point = map.latLngToContainerPoint(marker.getLatLng());
|
|
327
|
+
return [point.x - targets[index][0], point.y - targets[index][1]];
|
|
328
|
+
});
|
|
329
|
+
const pixelBounds = map.getPixelBounds();
|
|
330
|
+
const debugMin = JSON.parse(document.getElementById('ogbx').textContent);
|
|
331
|
+
const debugMax = JSON.parse(document.getElementById('ogby').textContent);
|
|
332
|
+
return {
|
|
333
|
+
size: [size.x, size.y],
|
|
334
|
+
paneSizes: ['.panebounds.rotate', '.panebounds.main'].map(selector => {
|
|
335
|
+
const element = map.getContainer().querySelector(selector);
|
|
336
|
+
return [parseFloat(element.style.width), parseFloat(element.style.height)];
|
|
337
|
+
}),
|
|
338
|
+
corners: cornerDeltas,
|
|
339
|
+
pivot: [read('pvx') - map._pivot.x, read('pvy') - map._pivot.y],
|
|
340
|
+
paneOffset: [
|
|
341
|
+
read('pox') - map._getMapPanePos().x,
|
|
342
|
+
read('poy') - map._getMapPanePos().y,
|
|
343
|
+
],
|
|
344
|
+
pixelOrigin: [read('ogx') - map._pixelOrigin.x, read('ogy') - map._pixelOrigin.y],
|
|
345
|
+
pixelBounds: [
|
|
346
|
+
debugMin.x - pixelBounds.min.x,
|
|
347
|
+
debugMin.y - pixelBounds.min.y,
|
|
348
|
+
debugMax.x - pixelBounds.max.x,
|
|
349
|
+
debugMax.y - pixelBounds.max.y,
|
|
350
|
+
],
|
|
351
|
+
pointer: [
|
|
352
|
+
read('llx') - window.debugMouseEvent.latlng[0],
|
|
353
|
+
read('lly') - window.debugMouseEvent.latlng[1],
|
|
354
|
+
read('lyx') - window.debugMouseEvent.layerPoint[0],
|
|
355
|
+
read('lyy') - window.debugMouseEvent.layerPoint[1],
|
|
356
|
+
read('cnx') - window.debugMouseEvent.containerPoint[0],
|
|
357
|
+
read('cny') - window.debugMouseEvent.containerPoint[1],
|
|
358
|
+
],
|
|
359
|
+
};
|
|
360
|
+
});
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
Then('the debugger reports the current map geometry', function () {
|
|
364
|
+
const { size, paneSizes, corners, pivot, paneOffset, pixelOrigin, pixelBounds, pointer } = this.debugGeometry;
|
|
365
|
+
assert.deepEqual(paneSizes, [size, size]);
|
|
366
|
+
for (const delta of [...corners, pivot, paneOffset, pixelOrigin, pixelBounds, pointer]) {
|
|
367
|
+
assert.ok(delta.every(value => Math.abs(value) < 1), JSON.stringify(this.debugGeometry));
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
When('I switch between the example base layers', async function () {
|
|
372
|
+
this.visibleBaseLayers = [];
|
|
373
|
+
for (const name of ['Empty', 'Streets', 'Satellite']) {
|
|
374
|
+
await this.page.getByRole('radio', { name, exact: true }).check();
|
|
375
|
+
this.visibleBaseLayers.push(await this.page.evaluate(() => layers._layers
|
|
376
|
+
.filter(entry => !entry.overlay && map.hasLayer(entry.layer))
|
|
377
|
+
.map(entry => entry.name)));
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
When('I use the example zoom controls', async function () {
|
|
382
|
+
this.startingZoom = await this.page.evaluate(() => map.getZoom());
|
|
383
|
+
await this.page.getByRole('button', { name: 'Zoom in' }).click();
|
|
384
|
+
await this.page.waitForFunction(zoom =>
|
|
385
|
+
map.getZoom() === zoom + 1 && !map._animatingZoom, this.startingZoom);
|
|
386
|
+
this.zoomAfterIn = await this.page.evaluate(() => map.getZoom());
|
|
387
|
+
await this.page.getByRole('button', { name: 'Zoom out' }).click();
|
|
388
|
+
await this.page.waitForFunction(zoom =>
|
|
389
|
+
map.getZoom() === zoom && !map._animatingZoom, this.startingZoom);
|
|
390
|
+
this.finalZoom = await this.page.evaluate(() => map.getZoom());
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
When('I rotate the example markers from the debugger', async function () {
|
|
394
|
+
this.markerRotationsBefore = await this.page.evaluate(() => markers.map(marker => marker.options.rotation));
|
|
395
|
+
await this.page.getByRole('button', { name: 'Rotate Markers' }).click();
|
|
396
|
+
await this.page.waitForTimeout(600);
|
|
397
|
+
this.markerRotationsAfter = await this.page.evaluate(() => markers.map(marker => marker.options.rotation));
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
When('I select {string} from the example page', async function (place) {
|
|
401
|
+
await this.page.getByRole('button', { name: place, exact: true }).click();
|
|
402
|
+
await this.page.waitForFunction(name =>
|
|
403
|
+
map.getZoom() === 10 && map.getCenter().distanceTo(new L.LatLng(...places[name])) < 1, place);
|
|
404
|
+
this.destination = await this.page.evaluate(name => ({
|
|
405
|
+
distance: map.getCenter().distanceTo(new L.LatLng(...places[name])),
|
|
406
|
+
zoom: map.getZoom(),
|
|
407
|
+
}), place);
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
When('I fit the {string} bounds from the example page', async function (layerName) {
|
|
411
|
+
await this.page.getByRole('button', { name: layerName.toUpperCase(), exact: true }).click();
|
|
412
|
+
await this.page.waitForFunction(name => map.getBounds().contains(window[name].getBounds()), layerName);
|
|
413
|
+
this.fittedBounds = await this.page.evaluate(name => {
|
|
414
|
+
const layer = window[name];
|
|
415
|
+
const bounds = layer.getBounds();
|
|
416
|
+
return map.getBounds().contains(bounds);
|
|
417
|
+
}, layerName);
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
When('I fit the rectangle from the example page', async function () {
|
|
421
|
+
await this.page.getByRole('button', { name: 'RECTANGLE', exact: true }).click();
|
|
422
|
+
await this.page.waitForFunction(() => map.getZoom() > 0 && map.getBounds().contains(rectangle.getBounds()));
|
|
423
|
+
this.rectangleZoom = await this.page.evaluate(() => map.getZoom());
|
|
424
|
+
});
|