Source code

Revision control

Other Tools

1
<!DOCTYPE HTML>
2
<html>
3
<!--
5
6
Some tests ported from IntersectionObserver/polyfill/intersection-observer-test.html
7
8
Original license header:
9
10
Copyright 2016 Google Inc. All Rights Reserved.
11
Licensed under the Apache License, Version 2.0 (the "License");
12
you may not use this file except in compliance with the License.
13
You may obtain a copy of the License at
15
Unless required by applicable law or agreed to in writing, software
16
distributed under the License is distributed on an "AS IS" BASIS,
17
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18
See the License for the specific language governing permissions and
19
limitations under the License.
20
-->
21
<head>
22
<meta charset="utf-8">
23
<title>Test for Bug 1243846</title>
24
<script src="/tests/SimpleTest/SimpleTest.js"></script>
25
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
26
</head>
27
<body onload="onLoad()">
28
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1243846">Mozilla Bug 1243846</a>
29
<p id="display"></p>
30
<pre id="test">
31
<script type="application/javascript">
32
/* eslint "no-shadow": ["error", {"allow": ["done", "next"]}] */
33
var tests = [];
34
var curDescribeMsg = '';
35
var curItMsg = '';
36
37
function beforeEach_fn() { };
38
function afterEach_fn() { };
39
40
function before(fn) {
41
fn();
42
}
43
44
function beforeEach(fn) {
45
beforeEach_fn = fn;
46
}
47
48
function afterEach(fn) {
49
afterEach_fn = fn;
50
}
51
52
function it(msg, fn) {
53
tests.push({
54
msg: `${msg} [${curDescribeMsg}]`,
55
fn
56
});
57
}
58
59
var callbacks = [];
60
function callDelayed(fn) {
61
callbacks.push(fn);
62
}
63
64
requestAnimationFrame(function tick() {
65
var i = callbacks.length;
66
while (i--) {
67
var cb = callbacks[i];
68
SimpleTest.executeSoon(function() { SimpleTest.executeSoon(cb) });
69
callbacks.splice(i, 1);
70
}
71
requestAnimationFrame(tick);
72
});
73
74
function expect(val) {
75
return {
76
to: {
77
throwException (regexp) {
78
try {
79
val();
80
ok(false, `${curItMsg} - an exception should have beeen thrown`);
81
} catch (e) {
82
ok(regexp.test(e), `${curItMsg} - supplied regexp should match thrown exception`);
83
}
84
},
85
get be() {
86
var fn = function (expected) {
87
is(val, expected, curItMsg);
88
};
89
fn.ok = function () {
90
ok(val, curItMsg);
91
};
92
fn.greaterThan = function (other) {
93
ok(val > other, `${curItMsg} - ${val} should be greater than ${other}`);
94
};
95
fn.lessThan = function (other) {
96
ok(val < other, `${curItMsg} - ${val} should be less than ${other}`);
97
};
98
return fn;
99
},
100
eql (expected) {
101
if (Array.isArray(expected)) {
102
if (!Array.isArray(val)) {
103
ok(false, curItMsg, `${curItMsg} - should be an array,`);
104
return;
105
}
106
is(val.length, expected.length, curItMsg, `${curItMsg} - arrays should be the same length`);
107
if (expected.length != val.length) {
108
return;
109
}
110
for (var i = 0; i < expected.length; i++) {
111
is(val[i], expected[i], `${curItMsg} - array elements at position ${i} should be equal`);
112
if (expected[i] != val[i]) {
113
return;
114
}
115
}
116
ok(true);
117
}
118
},
119
}
120
}
121
}
122
123
function describe(msg, fn) {
124
curDescribeMsg = msg;
125
fn();
126
curDescribeMsg = '';
127
}
128
129
function next() {
130
var test = tests.shift();
131
if (test) {
132
console.log(test.msg);
133
curItMsg = test.msg;
134
var fn = test.fn;
135
beforeEach_fn();
136
if (fn.length) {
137
fn(function () {
138
afterEach_fn();
139
next();
140
});
141
} else {
142
fn();
143
afterEach_fn();
144
next();
145
}
146
} else {
147
SimpleTest.finish();
148
}
149
}
150
151
var sinon = {
152
spy () {
153
var cbs = [];
154
var fn = function () {
155
fn.callCount++;
156
fn.lastCall = { args: arguments };
157
if (cbs.length) {
158
cbs.shift()();
159
}
160
};
161
fn.callCount = 0;
162
fn.lastCall = { args: [] };
163
fn.waitForNotification = (fn1) => {
164
cbs.push(fn1);
165
};
166
return fn;
167
}
168
};
169
170
var ASYNC_TIMEOUT = 300;
171
172
173
var io;
174
var noop = function() {};
175
176
177
// References to DOM elements, which are accessible to any test
178
// and reset prior to each test so state isn't shared.
179
var rootEl;
180
var grandParentEl;
181
var parentEl;
182
var targetEl1;
183
var targetEl2;
184
var targetEl3;
185
var targetEl4;
186
var targetEl5;
187
188
189
describe('IntersectionObserver', function() {
190
191
before(function() {
192
193
});
194
195
196
beforeEach(function() {
197
addStyles();
198
addFixtures();
199
});
200
201
202
afterEach(function() {
203
if (io && 'disconnect' in io) io.disconnect();
204
io = null;
205
206
window.onmessage = null;
207
208
removeStyles();
209
removeFixtures();
210
});
211
212
213
describe('constructor', function() {
214
215
it('throws when callback is not a function', function() {
216
expect(function() {
217
io = new IntersectionObserver(null);
218
}).to.throwException(/.*/i);
219
});
220
221
222
it('instantiates root correctly', function() {
223
io = new IntersectionObserver(noop);
224
expect(io.root).to.be(null);
225
226
io = new IntersectionObserver(noop, {root: rootEl});
227
expect(io.root).to.be(rootEl);
228
});
229
230
231
it('throws when root is not an Element', function() {
232
expect(function() {
233
io = new IntersectionObserver(noop, {root: 'foo'});
234
}).to.throwException(/.*/i);
235
});
236
237
238
it('instantiates rootMargin correctly', function() {
239
io = new IntersectionObserver(noop, {rootMargin: '10px'});
240
expect(io.rootMargin).to.be('10px 10px 10px 10px');
241
242
io = new IntersectionObserver(noop, {rootMargin: '10px -5%'});
243
expect(io.rootMargin).to.be('10px -5% 10px -5%');
244
245
io = new IntersectionObserver(noop, {rootMargin: '10px 20% 0px'});
246
expect(io.rootMargin).to.be('10px 20% 0px 20%');
247
248
io = new IntersectionObserver(noop, {rootMargin: '0px 0px -5% 5px'});
249
expect(io.rootMargin).to.be('0px 0px -5% 5px');
250
});
251
252
253
it('throws when rootMargin is not in pixels or percent', function() {
254
expect(function() {
255
io = new IntersectionObserver(noop, {rootMargin: 'auto'});
256
}).to.throwException(/pixels.*percent/i);
257
});
258
259
260
it('instantiates thresholds correctly', function() {
261
io = new IntersectionObserver(noop);
262
expect(io.thresholds).to.eql([0]);
263
264
io = new IntersectionObserver(noop, {threshold: 0.5});
265
expect(io.thresholds).to.eql([0.5]);
266
267
io = new IntersectionObserver(noop, {threshold: [0.25, 0.5, 0.75]});
268
expect(io.thresholds).to.eql([0.25, 0.5, 0.75]);
269
270
io = new IntersectionObserver(noop, {threshold: [1, .5, 0]});
271
expect(io.thresholds).to.eql([0, .5, 1]);
272
});
273
274
it('throws when a threshold value is not between 0 and 1', function() {
275
expect(function() {
276
io = new IntersectionObserver(noop, {threshold: [0, -1]});
277
}).to.throwException(/threshold/i);
278
});
279
280
it('throws when a threshold value is not a number', function() {
281
expect(function() {
282
io = new IntersectionObserver(noop, {threshold: "foo"});
283
}).to.throwException(/.*/i);
284
});
285
286
});
287
288
289
describe('observe', function() {
290
291
it('throws when target is not an Element', function() {
292
expect(function() {
293
io = new IntersectionObserver(noop);
294
io.observe(null);
295
}).to.throwException(/.*/i);
296
});
297
298
299
it('triggers if target intersects when observing begins', function(done) {
300
io = new IntersectionObserver(function(records) {
301
expect(records.length).to.be(1);
302
expect(records[0].intersectionRatio).to.be(1);
303
done();
304
}, {root: rootEl});
305
io.observe(targetEl1);
306
});
307
308
309
it('triggers with the correct arguments', function(done) {
310
io = new IntersectionObserver(function(records, observer) {
311
expect(records.length).to.be(1);
312
expect(records[0] instanceof IntersectionObserverEntry).to.be.ok();
313
expect(observer).to.be(io);
314
expect(this).to.be(io);
315
done();
316
}, {root: rootEl});
317
io.observe(targetEl1);
318
});
319
320
321
it('does trigger if target does not intersect when observing begins',
322
function(done) {
323
324
var spy = sinon.spy();
325
io = new IntersectionObserver(spy, {root: rootEl});
326
327
targetEl2.style.top = '-40px';
328
io.observe(targetEl2);
329
callDelayed(function() {
330
expect(spy.callCount).to.be(1);
331
done();
332
});
333
});
334
335
336
it('does not trigger if target is not a descendant of the intersection root in the containing block chain',
337
function(done) {
338
339
var spy = sinon.spy();
340
io = new IntersectionObserver(spy, {root: parentEl});
341
342
parentEl.style.position = 'static';
343
io.observe(targetEl2);
344
callDelayed(function() {
345
expect(spy.callCount).to.be(0);
346
done();
347
});
348
});
349
350
it('triggers if target or root becomes invisible',
351
function(done) {
352
353
var spy = sinon.spy();
354
io = new IntersectionObserver(spy, {root: rootEl});
355
356
runSequence([
357
function(done) {
358
io.observe(targetEl1);
359
spy.waitForNotification(function() {
360
expect(spy.callCount).to.be(1);
361
var records = sortRecords(spy.lastCall.args[0]);
362
expect(records.length).to.be(1);
363
expect(records[0].intersectionRatio).to.be(1);
364
done();
365
});
366
},
367
function(done) {
368
targetEl1.style.display = 'none';
369
spy.waitForNotification(function() {
370
expect(spy.callCount).to.be(2);
371
var records = sortRecords(spy.lastCall.args[0]);
372
expect(records.length).to.be(1);
373
expect(records[0].intersectionRatio).to.be(0);
374
done();
375
});
376
},
377
function(done) {
378
targetEl1.style.display = 'block';
379
spy.waitForNotification(function() {
380
expect(spy.callCount).to.be(3);
381
var records = sortRecords(spy.lastCall.args[0]);
382
expect(records.length).to.be(1);
383
expect(records[0].intersectionRatio).to.be(1);
384
done();
385
});
386
},
387
function(done) {
388
rootEl.style.display = 'none';
389
spy.waitForNotification(function() {
390
expect(spy.callCount).to.be(4);
391
var records = sortRecords(spy.lastCall.args[0]);
392
expect(records.length).to.be(1);
393
expect(records[0].intersectionRatio).to.be(0);
394
done();
395
});
396
},
397
function(done) {
398
rootEl.style.display = 'block';
399
spy.waitForNotification(function() {
400
expect(spy.callCount).to.be(5);
401
var records = sortRecords(spy.lastCall.args[0]);
402
expect(records.length).to.be(1);
403
expect(records[0].intersectionRatio).to.be(1);
404
done();
405
});
406
},
407
], done);
408
});
409
410
411
it('handles container elements with non-visible overflow',
412
function(done) {
413
414
var spy = sinon.spy();
415
io = new IntersectionObserver(spy, {root: rootEl});
416
417
runSequence([
418
function(done) {
419
io.observe(targetEl1);
420
spy.waitForNotification(function() {
421
expect(spy.callCount).to.be(1);
422
var records = sortRecords(spy.lastCall.args[0]);
423
expect(records.length).to.be(1);
424
expect(records[0].intersectionRatio).to.be(1);
425
done();
426
});
427
},
428
function(done) {
429
targetEl1.style.left = '-40px';
430
spy.waitForNotification(function() {
431
expect(spy.callCount).to.be(2);
432
var records = sortRecords(spy.lastCall.args[0]);
433
expect(records.length).to.be(1);
434
expect(records[0].intersectionRatio).to.be(0);
435
done();
436
});
437
},
438
function(done) {
439
parentEl.style.overflow = 'visible';
440
spy.waitForNotification(function() {
441
expect(spy.callCount).to.be(3);
442
var records = sortRecords(spy.lastCall.args[0]);
443
expect(records.length).to.be(1);
444
expect(records[0].intersectionRatio).to.be(1);
445
done();
446
});
447
}
448
], done);
449
});
450
451
452
it('observes one target at a single threshold correctly', function(done) {
453
454
var spy = sinon.spy();
455
io = new IntersectionObserver(spy, {root: rootEl, threshold: 0.5});
456
457
runSequence([
458
function(done) {
459
targetEl1.style.left = '-5px';
460
io.observe(targetEl1);
461
spy.waitForNotification(function() {
462
expect(spy.callCount).to.be(1);
463
var records = sortRecords(spy.lastCall.args[0]);
464
expect(records.length).to.be(1);
465
expect(records[0].intersectionRatio).to.be.greaterThan(0.5);
466
done();
467
});
468
},
469
function(done) {
470
targetEl1.style.left = '-15px';
471
spy.waitForNotification(function() {
472
expect(spy.callCount).to.be(2);
473
var records = sortRecords(spy.lastCall.args[0]);
474
expect(records.length).to.be(1);
475
expect(records[0].intersectionRatio).to.be.lessThan(0.5);
476
done();
477
});
478
},
479
function(done) {
480
targetEl1.style.left = '-25px';
481
callDelayed(function() {
482
expect(spy.callCount).to.be(2);
483
done();
484
});
485
},
486
function(done) {
487
targetEl1.style.left = '-10px';
488
spy.waitForNotification(function() {
489
expect(spy.callCount).to.be(3);
490
var records = sortRecords(spy.lastCall.args[0]);
491
expect(records.length).to.be(1);
492
expect(records[0].intersectionRatio).to.be(0.5);
493
done();
494
});
495
}
496
], done);
497
498
});
499
500
501
it('observes multiple targets at multiple thresholds correctly',
502
function(done) {
503
504
var spy = sinon.spy();
505
io = new IntersectionObserver(spy, {
506
root: rootEl,
507
threshold: [1, 0.5, 0]
508
});
509
510
runSequence([
511
function(done) {
512
targetEl1.style.top = '0px';
513
targetEl1.style.left = '-15px';
514
targetEl2.style.top = '-5px';
515
targetEl2.style.left = '0px';
516
targetEl3.style.top = '0px';
517
targetEl3.style.left = '205px';
518
io.observe(targetEl1);
519
io.observe(targetEl2);
520
io.observe(targetEl3);
521
spy.waitForNotification(function() {
522
expect(spy.callCount).to.be(1);
523
var records = sortRecords(spy.lastCall.args[0]);
524
expect(records.length).to.be(3);
525
expect(records[0].target).to.be(targetEl1);
526
expect(records[0].intersectionRatio).to.be(0.25);
527
expect(records[1].target).to.be(targetEl2);
528
expect(records[1].intersectionRatio).to.be(0.75);
529
done();
530
});
531
},
532
function(done) {
533
targetEl1.style.top = '0px';
534
targetEl1.style.left = '-5px';
535
targetEl2.style.top = '-15px';
536
targetEl2.style.left = '0px';
537
targetEl3.style.top = '0px';
538
targetEl3.style.left = '195px';
539
spy.waitForNotification(function() {
540
expect(spy.callCount).to.be(2);
541
var records = sortRecords(spy.lastCall.args[0]);
542
expect(records.length).to.be(3);
543
expect(records[0].target).to.be(targetEl1);
544
expect(records[0].intersectionRatio).to.be(0.75);
545
expect(records[1].target).to.be(targetEl2);
546
expect(records[1].intersectionRatio).to.be(0.25);
547
expect(records[2].target).to.be(targetEl3);
548
expect(records[2].intersectionRatio).to.be(0.25);
549
done();
550
});
551
},
552
function(done) {
553
targetEl1.style.top = '0px';
554
targetEl1.style.left = '5px';
555
targetEl2.style.top = '-25px';
556
targetEl2.style.left = '0px';
557
targetEl3.style.top = '0px';
558
targetEl3.style.left = '185px';
559
spy.waitForNotification(function() {
560
expect(spy.callCount).to.be(3);
561
var records = sortRecords(spy.lastCall.args[0]);
562
expect(records.length).to.be(3);
563
expect(records[0].target).to.be(targetEl1);
564
expect(records[0].intersectionRatio).to.be(1);
565
expect(records[1].target).to.be(targetEl2);
566
expect(records[1].intersectionRatio).to.be(0);
567
expect(records[2].target).to.be(targetEl3);
568
expect(records[2].intersectionRatio).to.be(0.75);
569
done();
570
});
571
},
572
function(done) {
573
targetEl1.style.top = '0px';
574
targetEl1.style.left = '15px';
575
targetEl2.style.top = '-35px';
576
targetEl2.style.left = '0px';
577
targetEl3.style.top = '0px';
578
targetEl3.style.left = '175px';
579
spy.waitForNotification(function() {
580
expect(spy.callCount).to.be(4);
581
var records = sortRecords(spy.lastCall.args[0]);
582
expect(records.length).to.be(1);
583
expect(records[0].target).to.be(targetEl3);
584
expect(records[0].intersectionRatio).to.be(1);
585
done();
586
});
587
}
588
], done);
589
});
590
591
592
it('handles rootMargin properly', function(done) {
593
594
parentEl.style.overflow = 'visible';
595
targetEl1.style.top = '0px';
596
targetEl1.style.left = '-20px';
597
targetEl2.style.top = '-20px';
598
targetEl2.style.left = '0px';
599
targetEl3.style.top = '0px';
600
targetEl3.style.left = '200px';
601
targetEl4.style.top = '180px';
602
targetEl4.style.left = '180px';
603
604
runSequence([
605
function(done) {
606
io = new IntersectionObserver(function(records) {
607
records = sortRecords(records);
608
expect(records.length).to.be(4);
609
expect(records[0].target).to.be(targetEl1);
610
expect(records[0].intersectionRatio).to.be(1);
611
expect(records[1].target).to.be(targetEl2);
612
expect(records[1].intersectionRatio).to.be(.5);
613
expect(records[2].target).to.be(targetEl3);
614
expect(records[2].intersectionRatio).to.be(.5);
615
expect(records[3].target).to.be(targetEl4);
616
expect(records[3].intersectionRatio).to.be(1);
617
io.disconnect();
618
done();
619
}, {root: rootEl, rootMargin: '10px'});
620
621
io.observe(targetEl1);
622
io.observe(targetEl2);
623
io.observe(targetEl3);
624
io.observe(targetEl4);
625
},
626
function(done) {
627
io = new IntersectionObserver(function(records) {
628
records = sortRecords(records);
629
expect(records.length).to.be(4);
630
expect(records[0].target).to.be(targetEl1);
631
expect(records[0].intersectionRatio).to.be(0.5);
632
expect(records[2].target).to.be(targetEl3);
633
expect(records[2].intersectionRatio).to.be(0.5);
634
expect(records[3].target).to.be(targetEl4);
635
expect(records[3].intersectionRatio).to.be(0.5);
636
io.disconnect();
637
done();
638
}, {root: rootEl, rootMargin: '-10px 10%'});
639
640
io.observe(targetEl1);
641
io.observe(targetEl2);
642
io.observe(targetEl3);
643
io.observe(targetEl4);
644
},
645
function(done) {
646
io = new IntersectionObserver(function(records) {
647
records = sortRecords(records);
648
expect(records.length).to.be(4);
649
expect(records[0].target).to.be(targetEl1);
650
expect(records[0].intersectionRatio).to.be(0.5);
651
expect(records[3].target).to.be(targetEl4);
652
expect(records[3].intersectionRatio).to.be(0.5);
653
io.disconnect();
654
done();
655
}, {root: rootEl, rootMargin: '-5% -2.5% 0px'});
656
657
io.observe(targetEl1);
658
io.observe(targetEl2);
659
io.observe(targetEl3);
660
io.observe(targetEl4);
661
},
662
function(done) {
663
io = new IntersectionObserver(function(records) {
664
records = sortRecords(records);
665
expect(records.length).to.be(4);
666
expect(records[0].target).to.be(targetEl1);
667
expect(records[0].intersectionRatio).to.be(0.5);
668
expect(records[1].target).to.be(targetEl2);
669
expect(records[1].intersectionRatio).to.be(0.5);
670
expect(records[3].target).to.be(targetEl4);
671
expect(records[3].intersectionRatio).to.be(0.25);
672
io.disconnect();
673
done();
674
}, {root: rootEl, rootMargin: '5% -2.5% -10px -190px'});
675
676
io.observe(targetEl1);
677
io.observe(targetEl2);
678
io.observe(targetEl3);
679
io.observe(targetEl4);
680
}
681
], done);
682
});
683
684
685
it('handles targets on the boundary of root', function(done) {
686
687
var spy = sinon.spy();
688
io = new IntersectionObserver(spy, {root: rootEl});
689
690
runSequence([
691
function(done) {
692
targetEl1.style.top = '0px';
693
targetEl1.style.left = '-21px';
694
targetEl2.style.top = '-20px';
695
targetEl2.style.left = '0px';
696
io.observe(targetEl1);
697
io.observe(targetEl2);
698
spy.waitForNotification(function() {
699
expect(spy.callCount).to.be(1);
700
var records = sortRecords(spy.lastCall.args[0]);
701
expect(records.length).to.be(2);
702
expect(records[1].intersectionRatio).to.be(0);
703
expect(records[1].target).to.be(targetEl2);
704
done();
705
});
706
},
707
function(done) {
708
targetEl1.style.top = '0px';
709
targetEl1.style.left = '-20px';
710
targetEl2.style.top = '-21px';
711
targetEl2.style.left = '0px';
712
spy.waitForNotification(function() {
713
expect(spy.callCount).to.be(2);
714
var records = sortRecords(spy.lastCall.args[0]);
715
expect(records.length).to.be(2);
716
expect(records[0].intersectionRatio).to.be(0);
717
expect(records[0].isIntersecting).to.be.ok();
718
expect(records[0].target).to.be(targetEl1);
719
expect(records[1].intersectionRatio).to.be(0);
720
expect(records[1].target).to.be(targetEl2);
721
done();
722
});
723
},
724
function(done) {
725
targetEl1.style.top = '-20px';
726
targetEl1.style.left = '200px';
727
targetEl2.style.top = '200px';
728
targetEl2.style.left = '200px';
729
spy.waitForNotification(function() {
730
expect(spy.callCount).to.be(3);
731
var records = sortRecords(spy.lastCall.args[0]);
732
expect(records.length).to.be(1);
733
expect(records[0].intersectionRatio).to.be(0);
734
expect(records[0].target).to.be(targetEl2);
735
done();
736
});
737
},
738
function(done) {
739
targetEl3.style.top = '20px';
740
targetEl3.style.left = '-20px';
741
targetEl4.style.top = '-20px';
742
targetEl4.style.left = '20px';
743
io.observe(targetEl3);
744
io.observe(targetEl4);
745
spy.waitForNotification(function() {
746
expect(spy.callCount).to.be(4);
747
var records = sortRecords(spy.lastCall.args[0]);
748
expect(records.length).to.be(2);
749
expect(records[0].intersectionRatio).to.be(0);
750
expect(records[0].isIntersecting).to.be.ok();
751
expect(records[0].target).to.be(targetEl3);
752
expect(records[1].intersectionRatio).to.be(0);
753
expect(records[1].target).to.be(targetEl4);
754
done();
755
});
756
}
757
], done);
758
759
});
760
761
762
it('handles zero-size targets within the root coordinate space',
763
function(done) {
764
765
var spy = sinon.spy();
766
io = new IntersectionObserver(spy, {root: rootEl});
767
768
runSequence([
769
function(done) {
770
targetEl1.style.top = '0px';
771
targetEl1.style.left = '0px';
772
targetEl1.style.width = '0px';
773
targetEl1.style.height = '0px';
774
io.observe(targetEl1);
775
spy.waitForNotification(function() {
776
var records = sortRecords(spy.lastCall.args[0]);
777
expect(records.length).to.be(1);
778
expect(records[0].intersectionRatio).to.be(1);
779
expect(records[0].isIntersecting).to.be.ok();
780
done();
781
});
782
},
783
function(done) {
784
targetEl1.style.top = '-1px';
785
spy.waitForNotification(function() {
786
var records = sortRecords(spy.lastCall.args[0]);
787
expect(records.length).to.be(1);
788
expect(records[0].intersectionRatio).to.be(0);
789
expect(records[0].isIntersecting).to.be(false);
790
done();
791
});
792
}
793
], done);
794
});
795
796
797
it('handles root/target elements not yet in the DOM', function(done) {
798
799
rootEl.remove();
800
targetEl1.remove();
801
802
var spy = sinon.spy();
803
io = new IntersectionObserver(spy, {root: rootEl});
804
805
runSequence([
806
function(done) {
807
io.observe(targetEl1);
808
callDelayed(done);
809
},
810
function(done) {
811
document.getElementById('fixtures').appendChild(rootEl);
812
callDelayed(function() {
813
expect(spy.callCount).to.be(1);
814
done();
815
});
816
},
817
function(done) {
818
parentEl.insertBefore(targetEl1, targetEl2);
819
spy.waitForNotification(function() {
820
expect(spy.callCount).to.be(2);
821
var records = sortRecords(spy.lastCall.args[0]);
822
expect(records.length).to.be(1);
823
expect(records[0].intersectionRatio).to.be(1);
824
expect(records[0].target).to.be(targetEl1);
825
done();
826
});
827
},
828
function(done) {
829
grandParentEl.remove();
830
spy.waitForNotification(function() {
831
expect(spy.callCount).to.be(3);
832
var records = sortRecords(spy.lastCall.args[0]);
833
expect(records.length).to.be(1);
834
expect(records[0].intersectionRatio).to.be(0);
835
expect(records[0].target).to.be(targetEl1);
836
done();
837
});
838
},
839
function(done) {
840
rootEl.appendChild(targetEl1);
841
spy.waitForNotification(function() {
842
expect(spy.callCount).to.be(4);
843
var records = sortRecords(spy.lastCall.args[0]);
844
expect(records.length).to.be(1);
845
expect(records[0].intersectionRatio).to.be(1);
846
expect(records[0].target).to.be(targetEl1);
847
done();
848
});
849
},
850
function(done) {
851
rootEl.remove();
852
spy.waitForNotification(function() {
853
expect(spy.callCount).to.be(5);
854
var records = sortRecords(spy.lastCall.args[0]);
855
expect(records.length).to.be(1);
856
expect(records[0].intersectionRatio).to.be(0);
857
expect(records[0].target).to.be(targetEl1);
858
done();
859
});
860
}
861
], done);
862
});
863
864
865
it('handles sub-root element scrolling', function(done) {
866
io = new IntersectionObserver(function(records) {
867
expect(records.length).to.be(1);
868
expect(records[0].intersectionRatio).to.be(1);
869
done();
870
}, {root: rootEl});
871
872
io.observe(targetEl3);
873
callDelayed(function() {
874
parentEl.scrollLeft = 40;
875
});
876
});
877
878
879
it('supports CSS transitions and transforms', function(done) {
880
881
targetEl1.style.top = '220px';
882
targetEl1.style.left = '220px';
883
884
var callCount = 0;
885
886
io = new IntersectionObserver(function(records) {
887
callCount++;
888
if (callCount <= 1) {
889
return;
890
}
891
expect(records.length).to.be(1);
892
expect(records[0].intersectionRatio).to.be(1);
893
done();
894
}, {root: rootEl, threshold: [1]});
895
896
io.observe(targetEl1);
897
callDelayed(function() {
898
targetEl1.style.transform = 'translateX(-40px) translateY(-40px)';
899
});
900
});
901
902
903
it('uses the viewport when no root is specified', function(done) {
904
window.onmessage = function (e) {
905
expect(e.data).to.be.ok();
906
win.close();
907
done();
908
};
909
910
var win = window.open("intersectionobserver_window.html");
911
});
912
913
it('triggers only once if observed multiple times (and does not crash when collected)', function(done) {
914
var spy = sinon.spy();
915
io = new IntersectionObserver(spy, {root: rootEl});
916
io.observe(targetEl1);
917
io.observe(targetEl1);
918
io.observe(targetEl1);
919
920
spy.waitForNotification(function() {
921
callDelayed(function () {
922
expect(spy.callCount).to.be(1);
923
done();
924
});
925
});
926
});
927
928
});
929
930
describe('observe subframe', function () {
931
932
it('should not trigger if target and root are not in the same document',
933
function(done) {
934
935
var spy = sinon.spy();
936
io = new IntersectionObserver(spy, {root: rootEl});
937
938
targetEl4.onload = function () {
939
targetEl5 = targetEl4.contentDocument.getElementById('target5');
940
io.observe(targetEl5);
941
callDelayed(function() {
942
expect(spy.callCount).to.be(0);
943
done();
944
});
945
}
946
947
targetEl4.src = "intersectionobserver_iframe.html";
948
949
});
950
951
it('boundingClientRect matches target.getBoundingClientRect() for an element inside an iframe',
952
function(done) {
953
954
io = new IntersectionObserver(function(records) {
955
expect(records.length).to.be(1);
956
expect(records[0].boundingClientRect.top, targetEl5.getBoundingClientRect().top);
957
expect(records[0].boundingClientRect.left, targetEl5.getBoundingClientRect().left);
958
expect(records[0].boundingClientRect.width, targetEl5.getBoundingClientRect().width);
959
expect(records[0].boundingClientRect.height, targetEl5.getBoundingClientRect().height);
960
done();
961
}, {threshold: [1]});
962
963
targetEl4.onload = function () {
964
targetEl5 = targetEl4.contentDocument.getElementById('target5');
965
io.observe(targetEl5);
966
}
967
968
targetEl4.src = "intersectionobserver_iframe.html";
969
});
970
971
it('rootBounds is set to null for cross-origin observations', function(done) {
972
973
window.onmessage = function (e) {
974
expect(e.data).to.be(true);
975
done();
976
};
977
979
980
});
981
982
});
983
984
describe('takeRecords', function() {
985
986
it('supports getting records before the callback is invoked', function(done) {
987
988
var lastestRecords = [];
989
io = new IntersectionObserver(function(records) {
990
lastestRecords = lastestRecords.concat(records);
991
}, {root: rootEl});
992
io.observe(targetEl1);
993
994
window.requestAnimationFrame && requestAnimationFrame(function wait() {
995
lastestRecords = lastestRecords.concat(io.takeRecords());
996
if (!lastestRecords.length) {
997
requestAnimationFrame(wait);
998
return;
999
}
1000
callDelayed(function() {
1001
expect(lastestRecords.length).to.be(1);
1002
expect(lastestRecords[0].intersectionRatio).to.be(1);
1003
done();
1004
});
1005
});
1006
1007
});
1008
1009
});
1010
1011
describe('unobserve', function() {
1012
1013
it('removes targets from the internal store', function(done) {
1014
1015
var spy = sinon.spy();
1016
io = new IntersectionObserver(spy, {root: rootEl});
1017
1018
runSequence([
1019
function(done) {
1020
targetEl1.style.top = targetEl2.style.top = '0px';
1021
targetEl1.style.left = targetEl2.style.left = '0px';
1022
io.observe(targetEl1);
1023
io.observe(targetEl2);
1024
spy.waitForNotification(function() {
1025
expect(spy.callCount).to.be(1);
1026
var records = sortRecords(spy.lastCall.args[0]);
1027
expect(records.length).to.be(2);
1028
expect(records[0].target).to.be(targetEl1);
1029
expect(records[0].intersectionRatio).to.be(1);
1030
expect(records[1].target).to.be(targetEl2);
1031
expect(records[1].intersectionRatio).to.be(1);
1032
done();
1033
});
1034
},
1035
function(done) {
1036
io.unobserve(targetEl1);
1037
targetEl1.style.top = targetEl2.style.top = '0px';
1038
targetEl1.style.left = targetEl2.style.left = '-40px';
1039
spy.waitForNotification(function() {
1040
expect(spy.callCount).to.be(2);
1041
var records = sortRecords(spy.lastCall.args[0]);
1042
expect(records.length).to.be(1);
1043
expect(records[0].target).to.be(targetEl2);
1044
expect(records[0].intersectionRatio).to.be(0);
1045
done();
1046
});
1047
},
1048
function(done) {
1049
io.unobserve(targetEl2);
1050
targetEl1.style.top = targetEl2.style.top = '0px';
1051
targetEl1.style.left = targetEl2.style.left = '0px';
1052
callDelayed(function() {
1053
expect(spy.callCount).to.be(2);
1054
done();
1055
});
1056
}
1057
], done);
1058
1059
});
1060
1061
});
1062
1063
describe('disconnect', function() {
1064
1065
it('removes all targets and stops listening for changes', function(done) {
1066
1067
var spy = sinon.spy();
1068
io = new IntersectionObserver(spy, {root: rootEl});
1069
1070
runSequence([
1071
function(done) {
1072
targetEl1.style.top = targetEl2.style.top = '0px';
1073
targetEl1.style.left = targetEl2.style.left = '0px';
1074
io.observe(targetEl1);
1075
io.observe(targetEl2);
1076
spy.waitForNotification(function() {
1077
expect(spy.callCount).to.be(1);
1078
var records = sortRecords(spy.lastCall.args[0]);
1079
expect(records.length).to.be(2);
1080
expect(records[0].target).to.be(targetEl1);
1081
expect(records[0].intersectionRatio).to.be(1);
1082
expect(records[1].target).to.be(targetEl2);
1083
expect(records[1].intersectionRatio).to.be(1);
1084
done();
1085
});
1086
},
1087
function(done) {
1088
io.disconnect();
1089
targetEl1.style.top = targetEl2.style.top = '0px';
1090
targetEl1.style.left = targetEl2.style.left = '-40px';
1091
callDelayed(function() {
1092
expect(spy.callCount).to.be(1);
1093
done();
1094
});
1095
}
1096
], done);
1097
1098
});
1099
1100
});
1101
1102
});
1103
1104
1105
/**
1106
* Runs a sequence of function and when finished invokes the done callback.
1107
* Each function in the sequence is invoked with its own done function and
1108
* it should call that function once it's complete.
1109
* @param {Array<Function>} functions An array of async functions.
1110
* @param {Function} done A final callback to be invoked once all function
1111
* have run.
1112
*/
1113
function runSequence(functions, done) {
1114
var next = functions.shift();
1115
if (next) {
1116
next(function() {
1117
runSequence(functions, done);
1118
});
1119
} else {
1120
done && done();
1121
}
1122
}
1123
1124
1125
/**
1126
* Sorts an array of records alphebetically by ascending ID. Since the current
1127
* native implementation doesn't sort change entries by `observe` order, we do
1128
* that ourselves for the non-polyfill case. Since all tests call observe
1129
* on targets in sequential order, this should always match.
1131
* @param {Array<IntersectionObserverEntry>} entries The entries to sort.
1132
* @return {Array<IntersectionObserverEntry>} The sorted array.
1133
*/
1134
function sortRecords(entries) {
1135
entries = entries.sort(function(a, b) {
1136
return a.target.id < b.target.id ? -1 : 1;
1137
});
1138
return entries;
1139
}
1140
1141
1142
/**
1143
* Adds the common styles used by all tests to the page.
1144
*/
1145
function addStyles() {
1146
var styles = document.createElement('style');
1147
styles.id = 'styles';
1148
document.documentElement.appendChild(styles);
1149
1150
var cssText =
1151
'#root {' +
1152
' position: relative;' +
1153
' width: 400px;' +
1154
' height: 200px;' +
1155
' background: #eee' +
1156
'}' +
1157
'#grand-parent {' +
1158
' position: relative;' +
1159
' width: 200px;' +
1160
' height: 200px;' +
1161
'}' +
1162
'#parent {' +
1163
' position: absolute;' +
1164
' top: 0px;' +
1165
' left: 200px;' +
1166
' overflow: hidden;' +
1167
' width: 200px;' +
1168
' height: 200px;' +
1169
' background: #ddd;' +
1170
'}' +
1171
'#target1, #target2, #target3, #target4 {' +
1172
' position: absolute;' +
1173
' top: 0px;' +
1174
' left: 0px;' +
1175
' width: 20px;' +
1176
' height: 20px;' +
1177
' transform: translateX(0px) translateY(0px);' +
1178
' transition: transform .5s;' +
1179
' background: #f00;' +
1180
' border: none;' +
1181
'}';
1182
1183
styles.innerHTML = cssText;
1184
}
1185
1186
1187
/**
1188
* Adds the DOM fixtures used by all tests to the page and assigns them to
1189
* global variables so they can be referenced within the tests.
1190
*/
1191
function addFixtures() {
1192
var fixtures = document.createElement('div');
1193
fixtures.id = 'fixtures';
1194
1195
fixtures.innerHTML =
1196
'<div id="root">' +
1197
' <div id="grand-parent">' +
1198
' <div id="parent">' +
1199
' <div id="target1"></div>' +
1200
' <div id="target2"></div>' +
1201
' <div id="target3"></div>' +
1202
' <iframe id="target4"></iframe>' +
1203
' </div>' +
1204
' </div>' +
1205
'</div>';
1206
1207
document.body.appendChild(fixtures);
1208
1209
rootEl = document.getElementById('root');
1210
grandParentEl = document.getElementById('grand-parent');
1211
parentEl = document.getElementById('parent');
1212
targetEl1 = document.getElementById('target1');
1213
targetEl2 = document.getElementById('target2');
1214
targetEl3 = document.getElementById('target3');
1215
targetEl4 = document.getElementById('target4');
1216
}
1217
1218
1219
/**
1220
* Removes the common styles from the page.
1221
*/
1222
function removeStyles() {
1223
var styles = document.getElementById('styles');
1224
styles.remove();
1225
}
1226
1227
1228
/**
1229
* Removes the DOM fixtures from the page and resets the global references.
1230
*/
1231
function removeFixtures() {
1232
var fixtures = document.getElementById('fixtures');
1233
fixtures.remove();
1234
1235
rootEl = null;
1236
grandParentEl = null;
1237
parentEl = null;
1238
targetEl1 = null;
1239
targetEl2 = null;
1240
targetEl3 = null;
1241
targetEl4 = null;
1242
}
1243
1244
function onLoad() {
1245
SpecialPowers.pushPrefEnv({"set": [["dom.IntersectionObserver.enabled", true]]}, next);
1246
}
1247
1248
SimpleTest.waitForExplicitFinish();
1249
</script>
1250
</pre>
1251
<div id="log">
1252
</div>
1253
</body>
1254
</html>