forked from Leaflet/Leaflet
-
Notifications
You must be signed in to change notification settings - Fork 0
/
PopupSpec.js
699 lines (553 loc) 路 20.4 KB
/
PopupSpec.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
import {DivIcon, DomUtil, FeatureGroup, Icon, Map, Marker, Point, Polygon, Popup} from 'leaflet';
import Hand from 'prosthetic-hand';
import UIEventSimulator from 'ui-event-simulator';
import {createContainer, removeMapContainer} from '../SpecHelper.js';
describe('Popup', () => {
let container, map;
const center = [55.8, 37.6];
beforeEach(() => {
container = container = createContainer();
map = new Map(container);
map.setView(center, 6);
});
afterEach(() => {
removeMapContainer(map, container);
});
it('closes on map click when map has closePopupOnClick option', () => {
map.options.closePopupOnClick = true;
const popup = new Popup()
.setLatLng(center)
.openOn(map);
UIEventSimulator.fire('click', container);
expect(map.hasLayer(popup)).to.be.false;
});
it('closes on map click when popup has closeOnClick option', () => {
map.options.closePopupOnClick = false;
const popup = new Popup({closeOnClick: true})
.setLatLng(center)
.openOn(map);
UIEventSimulator.fire('click', container);
expect(map.hasLayer(popup)).to.be.false;
});
it('does not close on map click when popup has closeOnClick: false option', () => {
map.options.closePopupOnClick = true;
const popup = new Popup({closeOnClick: false})
.setLatLng(center)
.openOn(map);
UIEventSimulator.fire('click', container);
expect(map.hasLayer(popup)).to.be.true;
});
it('sets the default \'closeButtonLabel\' on the close button', () => {
const popup = new Popup()
.setLatLng(center)
.openOn(map);
expect(popup.getElement().querySelector('[aria-label="Close popup"]')).not.to.equal(null);
});
it('sets a custom \'closeButtonLabel\' on the close button', () => {
const closeButtonLabel = 'TestLabel';
const popup = new Popup({closeButtonLabel})
.setLatLng(center)
.openOn(map);
expect(popup.getElement().querySelector(`[aria-label="${closeButtonLabel}"]`)).not.to.equal(null);
});
it('toggles its visibility when marker is clicked', () => {
const marker = new Marker(center);
map.addLayer(marker);
marker.bindPopup('Popup1');
expect(map.hasLayer(marker._popup)).to.be.false;
// toggle open popup
UIEventSimulator.fire('click', marker._icon);
expect(map.hasLayer(marker._popup)).to.be.true;
// toggle close popup
UIEventSimulator.fire('click', marker._icon);
expect(map.hasLayer(marker._popup)).to.be.false;
});
it('it should use a popup with a function as content with a FeatureGroup', () => {
const marker1 = new Marker(center);
const marker2 = new Marker([54.6, 38.2]);
const group = new FeatureGroup([marker1, marker2]).addTo(map);
marker1.description = 'I\'m marker 1.';
marker2.description = 'I\'m marker 2.';
group.bindPopup(layer => layer.description);
map.options.closePopupOnClick = true;
// toggle popup on marker1
group.fire('click', {
latlng: center,
layer: marker1
});
expect(map.hasLayer(group._popup)).to.be.true;
expect(group._popup._contentNode.innerHTML).to.equal('I\'m marker 1.');
// toggle popup on marker2
group.fire('click', {
latlng: [54.6, 38.2],
layer: marker2
});
expect(map.hasLayer(group._popup)).to.be.true;
expect(group._popup._contentNode.innerHTML).to.equal('I\'m marker 2.');
});
it('it should function for popup content after bindPopup is called', () => {
const marker1 = new Marker(center);
const marker2 = new Marker([54.6, 38.2]);
const group = new FeatureGroup([marker1]).addTo(map);
marker1.description = 'I\'m marker 1.';
marker2.description = 'I\'m marker 2.';
group.bindPopup(layer => layer.description);
group.addLayer(marker2);
map.options.closePopupOnClick = true;
// toggle popup on marker1
group.fire('click', {
latlng: center,
layer: marker1
});
expect(map.hasLayer(group._popup)).to.be.true;
expect(group._popup._contentNode.innerHTML).to.equal('I\'m marker 1.');
// toggle popup on marker2
group.fire('click', {
latlng: [54.6, 38.2],
layer: marker2
});
expect(map.hasLayer(group._popup)).to.be.true;
expect(group._popup._contentNode.innerHTML).to.equal('I\'m marker 2.');
});
it('should use a function for popup content when a source is passed to Popup', () => {
const marker = new Marker(center).addTo(map);
new Popup({}, marker);
marker.description = 'I am a marker.';
marker.bindPopup(layer => layer.description);
marker.fire('click', {
latlng: center
});
expect(map.hasLayer(marker._popup)).to.be.true;
expect(marker._popup._contentNode.innerHTML).to.equal('I am a marker.');
});
it('triggers popupopen on marker when popup opens', () => {
const marker1 = new Marker(center);
const marker2 = new Marker([57.123076977278, 44.861962891635]);
map.addLayer(marker1);
map.addLayer(marker2);
marker1.bindPopup('Popup1');
marker2.bindPopup('Popup2');
const spy = sinon.spy();
marker1.on('popupopen', spy);
expect(spy.called).to.be.false;
marker2.openPopup();
expect(spy.called).to.be.false;
marker1.openPopup();
expect(spy.called).to.be.true;
});
// Related to #8558
it('references the correct targets in popupopen event with multiple markers bound to same popup', () => {
const marker1 = new Marker(center, {testId: 'markerA'});
const marker2 = new Marker([57.123076977278, 44.861962891635], {testId: 'markerB'});
map.addLayer(marker1);
map.addLayer(marker2);
const popup = new Popup().setContent('test');
marker2.bindPopup(popup);
marker1.bindPopup(popup);
const spy = sinon.spy();
const spy2 = sinon.spy();
marker1.on('popupopen', (e) => {
spy();
expect(e.target.options.testId).to.eql('markerA');
});
marker2.on('popupopen', (e) => {
spy2();
expect(e.target.options.testId).to.eql('markerB');
});
expect(spy.called).to.be.false;
marker2.openPopup();
expect(spy.called).to.be.false;
expect(spy2.called).to.be.true;
marker1.closePopup().openPopup();
expect(spy.called).to.be.true;
});
it('triggers popupclose on marker when popup closes', () => {
const marker1 = new Marker(center);
const marker2 = new Marker([57.123076977278, 44.861962891635]);
map.addLayer(marker1);
map.addLayer(marker2);
marker1.bindPopup('Popup1');
marker2.bindPopup('Popup2');
const spy = sinon.spy();
marker1.on('popupclose', spy);
expect(spy.called).to.be.false;
marker2.openPopup();
expect(spy.called).to.be.false;
marker1.openPopup();
expect(spy.called).to.be.false;
marker2.openPopup();
expect(spy.called).to.be.true;
marker1.openPopup();
marker1.closePopup();
expect(spy.callCount).to.equal(2);
});
it('should take into account icon popupAnchor option', () => {
const latlng = center;
const offset = new Point(20, 30);
const autoPanBefore = Popup.prototype.options.autoPan;
Popup.prototype.options.autoPan = false;
const popupAnchorBefore = Icon.Default.prototype.options.popupAnchor;
Icon.Default.prototype.options.popupAnchor = [0, 0];
const icon = new DivIcon({popupAnchor: offset});
const marker1 = new Marker(latlng);
const marker2 = new Marker(latlng, {icon});
marker1.bindPopup('Popup').addTo(map);
marker1.openPopup();
const defaultLeft = DomUtil.getPosition(marker1._popup._container).x;
const defaultTop = DomUtil.getPosition(marker1._popup._container).y;
marker2.bindPopup('Popup').addTo(map);
marker2.openPopup();
let offsetLeft = DomUtil.getPosition(marker2._popup._container).x;
let offsetTop = DomUtil.getPosition(marker2._popup._container).y;
expect(offsetLeft - offset.x).to.eql(defaultLeft);
expect(offsetTop - offset.y).to.eql(defaultTop);
// Now retry passing a popup instance to bindPopup
marker2.bindPopup(new Popup());
marker2.openPopup();
offsetLeft = DomUtil.getPosition(marker2._popup._container).x;
offsetTop = DomUtil.getPosition(marker2._popup._container).y;
expect(offsetLeft - offset.x).to.eql(defaultLeft);
expect(offsetTop - offset.y).to.eql(defaultTop);
Popup.prototype.options.autoPan = autoPanBefore;
Icon.Default.prototype.options.popupAnchor = popupAnchorBefore;
});
it('prevents an underlying map click for Layer', () => {
const layer = new Polygon([center, [55.9, 37.7], [56.0, 37.8]]).addTo(map);
layer.bindPopup('layer popup');
let mapClicked = false;
map.on('click', (e) => {
mapClicked = true;
new Popup()
.setLatLng(e.latlng)
.setContent('map popup')
.openOn(map);
});
expect(map.hasLayer(layer._popup)).to.be.false;
UIEventSimulator.fire('click', layer._path);
expect(mapClicked).to.be.false;
expect(map.hasLayer(layer._popup)).to.be.true;
});
it('can open a popup with enter keypress when marker has focus', () => {
const layer = new Marker(center).addTo(map);
layer.bindPopup('layer popup');
UIEventSimulator.fire('keypress', layer._icon, {
code: 'Enter'
});
expect(map.hasLayer(layer._popup)).to.be.true;
});
it('can change popup content with a click on removed DOM', () => {
const popup = new Popup()
.setLatLng(center)
.setContent('<p onclick="this.parentNode.innerHTML = \'changed\'">initial</p>')
.openOn(map);
UIEventSimulator.fire('click', popup._container.querySelector('p'));
expect(popup._container.innerHTML).to.not.contain('initial');
expect(popup._container.innerHTML).to.contain('changed');
expect(map.hasLayer(popup)).to.be.true;
});
describe('autoPan option should pan popup into visibility', () => {
// Helper function which calculates the offset of the map-container & popup-container in pixel
function getPopupOffset(map, popup) {
const mapOffset = map._container.getBoundingClientRect().top;
const popupOffset = popup._container.getBoundingClientRect().top;
return popupOffset - mapOffset;
}
it('should not pan map to show popup content if autoPan is disabled', (done) => {
map.on('popupopen', (e) => {
const popupTopOffset = getPopupOffset(map, e.popup);
expect(popupTopOffset).to.be.below(0, 'The upper edge of the popup should not be visible');
done();
});
map.openPopup('<div style="height: 400px;"></div>', [58.4, 37.6], {
autoPan: false
});
});
it('should pan map to show popup content if autoPan is enabled', (done) => {
map.on('popupopen', (e) => {
const popupTopOffset = getPopupOffset(map, e.popup);
expect(popupTopOffset).to.equal(10, 'The upper edge of the popup have a padding of 10');
done();
});
map.openPopup('<div style="height: 400px;"></div>', [58.4, 37.6], {
autoPan: true,
autoPanPadding: new Point(10, 10)
});
});
it('should pan map to show popup content if autoPan is enabled even when animating', (done) => {
map.on('popupopen', (e) => {
const popupTopOffset = getPopupOffset(map, e.popup);
expect(popupTopOffset).to.equal(10);
done();
});
map.panTo([55.8, 40.7], {
animate: true,
duration: 1
});
map.openPopup('<div style="height: 400px;"></div>', [58.4, 37.6], {
autoPan: true,
autoPanPadding: new Point(10, 10)
});
});
});
it('opens popup with passed latlng position while initializing', () => {
const popup = new Popup(center)
.openOn(map);
expect(map.hasLayer(popup)).to.be.true;
});
it('opens popup with passed latlng and options position while initializing', () => {
const popup = new Popup(center, {className: 'testClass'})
.addTo(map);
expect(map.hasLayer(popup)).to.be.true;
expect(popup.getElement().classList.contains('testClass')).to.be.true;
});
it('adds popup with passed content in options while initializing', () => {
const popup = new Popup(center, {content: 'Test'})
.addTo(map);
expect(map.hasLayer(popup)).to.be.true;
expect(popup.getContent()).to.equal('Test');
});
describe('Map#openPopup', () => {
it('adds the popup layer to the map', () => {
const popup = new Popup()
.setLatLng(center);
map.openPopup(popup);
expect(map.hasLayer(popup)).to.be.true;
});
it('sets popup location', () => {
const popup = new Popup();
map.openPopup(popup, center);
expect(popup.getLatLng()).to.be.nearLatLng([55.8, 37.6]);
});
it('creates a popup from content', () => {
map.openPopup('<h2>Hello World</h2>', center);
expect(map._popup).to.be.instanceOf(Popup);
expect(map._popup.getContent()).to.eql('<h2>Hello World</h2>');
});
it('closes existing popup', () => {
const p1 = new Popup().setLatLng(center);
const p2 = new Popup().setLatLng(center);
map.openPopup(p1);
map.openPopup(p2);
expect(map.hasLayer(p1)).to.be.false;
});
it('does not close existing popup with autoClose: false option', () => {
const p1 = new Popup({autoClose: false}).setLatLng(center);
const p2 = new Popup().setLatLng(center);
map.openPopup(p1);
map.openPopup(p2);
expect(map.hasLayer(p1)).to.be.true;
expect(map.hasLayer(p2)).to.be.true;
});
it('should not be closen when dragging map', (done) => {
container.style.position = 'absolute';
container.style.left = 0;
container.style.top = 0;
container.style.zIndex = 10000;
const coords = map._container.getBoundingClientRect();
const spy = sinon.spy();
const p = new Popup().setLatLng(center);
map.openPopup(p);
expect(map.hasLayer(p)).to.be.true;
map.on('drag', spy);
const hand = new Hand({
timing: 'fastframe',
onStop() {
expect(spy.called).to.be.true;
expect(map.hasLayer(p)).to.be.true;
done();
}});
const mouse = hand.growFinger('mouse');
mouse.moveTo(coords.left + 100, coords.left + 100, 0)
.down().moveBy(10, 10, 20).up();
});
it('moves the map over a short distance to the popup if it is not in the view (keepInView)', (done) => {
container.style.position = 'absolute';
container.style.left = 0;
container.style.top = 0;
container.style.zIndex = 10000;
// to prevent waiting until the animation is finished
map.options.inertia = false;
const spy = sinon.spy();
map.on('autopanstart', spy);
// Short hop to the edge of the map (at time of writing, will trigger an animated pan)
const p = new Popup({keepInView: true}).setContent('Popup').setLatLng(map.getBounds()._northEast);
map.once('moveend', () => {
expect(spy.callCount).to.equal(1);
expect(map.getBounds().contains(p.getLatLng())).to.be.true;
done();
});
map.openPopup(p);
});
it('moves the map over a long distance to the popup if it is not in the view (keepInView)', (done) => {
container.style.position = 'absolute';
container.style.left = 0;
container.style.top = 0;
container.style.zIndex = 10000;
// to prevent waiting until the animation is finished
map.options.inertia = false;
const spy = sinon.spy();
map.on('autopanstart', spy);
// Long hop (at time of writing, will trigger a view reset)
const p = new Popup({keepInView: true}).setContent('Popup').setLatLng([center[0], center[1] + 50]);
map.once('moveend', () => {
expect(spy.callCount).to.equal(1);
expect(map.getBounds().contains(p.getLatLng())).to.be.true;
done();
});
map.openPopup(p);
});
it('moves on setLatLng after initial autopan', (done) => {
const p = new Popup().setContent('Popup').setLatLng(map.getBounds().getNorthEast());
map.once('moveend', () => {
map.once('moveend', () => {
expect(map.getBounds().contains(p.getLatLng())).to.be.true;
done();
});
p.setLatLng(map.getBounds().getNorthEast());
});
map.openPopup(p);
});
it('shows the popup at the correct location when multiple markers are registered', () => {
const popup = new Popup();
const marker1 = new Marker([86, 32]).bindPopup(popup).addTo(map);
const marker2 = new Marker([26.3, 83.9]).bindPopup(popup).addTo(map);
expect(popup.getLatLng()).to.equal(undefined);
marker1.openPopup();
expect(popup.getLatLng()).to.be.nearLatLng([86, 32]);
marker2.openPopup();
expect(popup.getLatLng()).to.be.nearLatLng([26.3, 83.9]);
});
});
describe('Layer#_popup', () => {
let marker;
beforeEach(() => {
marker = new Marker([55.8, 37.6]).addTo(map);
});
it('only adds a popup to the map when opened', () => {
marker.bindPopup('new layer');
expect(map.hasLayer(marker.getPopup())).to.be.false;
marker.openPopup();
expect(map.hasLayer(marker.getPopup())).to.be.true;
});
it('keeps an open popup on the map when it\'s unbound from the layer', () => {
marker.bindPopup('new layer').openPopup();
const popup = marker.getPopup();
marker.unbindPopup();
expect(map.hasLayer(popup)).to.be.true;
});
it('should not give an error when the marker has no popup', () => {
expect(() => {
marker.isPopupOpen();
}).to.not.throw();
expect(marker.isPopupOpen()).to.be.false;
});
it('should show a popup as closed if it\'s never opened', () => {
marker.bindPopup('new layer');
expect(marker.isPopupOpen()).to.be.false;
});
it('should show a popup as opend if it\'s opened', () => {
marker.bindPopup('new layer').openPopup();
expect(marker.isPopupOpen()).to.be.true;
});
it('should show a popup as closed if it\'s opened and closed', () => {
marker.bindPopup('new layer').openPopup().closePopup();
expect(marker.isPopupOpen()).to.be.false;
});
it('should show the popup as closed if it\'s unbound', () => {
marker.bindPopup('new layer').openPopup().unbindPopup();
expect(() => {
marker.isPopupOpen();
}).to.not.throw();
expect(marker.isPopupOpen()).to.be.false;
});
it('does not throw is popup is inmediately closed', (done) => {
map.on('popupopen', () => {
marker.closePopup();
});
expect(() => {
marker.bindPopup('new layer').openPopup();
done();
}).to.not.throw();
});
it('does not close popup when clicking on it\'s tip', () => {
container.style.position = 'absolute';
container.style.top = '0';
container.style.left = '0';
const popup = new Popup().setLatLng(map.getCenter())
.openOn(map);
const point = map.latLngToContainerPoint(map.getCenter());
point.y -= 2; // move mouse into the popup-tip
const el = document.elementFromPoint(point.x, point.y);
expect(el).to.equal(popup._tip);
UIEventSimulator.fire('click', el, {
clientX: point.x,
clientY: point.y
});
expect(popup.isOpen()).to.be.true;
});
it('does not open for empty FeatureGroup', () => {
const popup = new Popup();
new FeatureGroup([])
.addTo(map)
.bindPopup(popup)
.openPopup();
expect(map.hasLayer(popup)).to.be.false;
});
it('uses only visible layers of FeatureGroup for popup content source', () => {
const marker1 = new Marker([1, 1]);
const marker2 = new Marker([2, 2]);
const marker3 = new Marker([3, 3]);
const popup = new Popup();
const group = new FeatureGroup([marker1, marker2, marker3])
.bindPopup(popup)
.addTo(map);
marker1.remove();
marker3.remove();
group.openPopup();
expect(map.hasLayer(popup)).to.be.true;
expect(popup._source).to.equal(marker2);
});
});
describe('#setInteractive', () => {
it('cannot be called before adding to map', () => {
const popup = new Popup().setLatLng(center);
expect(() => popup.setInteractive(true)).to.throw();
});
it('can set interactive if unset initially', () => {
const popup = new Popup({interactive: false})
.setLatLng(center)
.openOn(map);
expect(popup._container.classList.contains('leaflet-interactive')).to.equal(false);
popup.setInteractive(true);
expect(popup.options.interactive).to.equal(true);
expect(popup._container.classList.contains('leaflet-interactive')).to.equal(true);
});
it('can unset interactive if set initially', () => {
const popup = new Popup({interactive: true})
.setLatLng(center)
.openOn(map);
expect(popup._container.classList.contains('leaflet-interactive')).to.equal(true);
popup.setInteractive(false);
expect(popup.options.interactive).to.equal(false);
expect(popup._container.classList.contains('leaflet-interactive')).to.equal(false);
});
it('can unset interactive if unset initially', () => {
const popup = new Popup({interactive: false})
.setLatLng(center)
.openOn(map);
expect(popup._container.classList.contains('leaflet-interactive')).to.equal(false);
popup.setInteractive(false);
expect(popup.options.interactive).to.equal(false);
expect(popup._container.classList.contains('leaflet-interactive')).to.equal(false);
});
it('can set interactive if set initially', () => {
const popup = new Popup({interactive: true})
.setLatLng(center)
.openOn(map);
expect(popup._container.classList.contains('leaflet-interactive')).to.equal(true);
popup.setInteractive(true);
expect(popup.options.interactive).to.equal(true);
expect(popup._container.classList.contains('leaflet-interactive')).to.equal(true);
});
});
});