зеркало из https://github.com/mozilla/gecko-dev.git
1255 строки
39 KiB
HTML
1255 строки
39 KiB
HTML
<!DOCTYPE HTML>
|
|
<html>
|
|
<!--
|
|
https://bugzilla.mozilla.org/show_bug.cgi?id=1243846
|
|
|
|
Some tests ported from IntersectionObserver/polyfill/intersection-observer-test.html
|
|
|
|
Original license header:
|
|
|
|
Copyright 2016 Google Inc. All Rights Reserved.
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
-->
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<title>Test for Bug 1243846</title>
|
|
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
|
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
|
</head>
|
|
<body onload="onLoad()">
|
|
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1243846">Mozilla Bug 1243846</a>
|
|
<p id="display"></p>
|
|
<pre id="test">
|
|
<script type="application/javascript">
|
|
/* eslint "no-shadow": ["error", {"allow": ["done", "next"]}] */
|
|
var tests = [];
|
|
var curDescribeMsg = '';
|
|
var curItMsg = '';
|
|
|
|
function beforeEach_fn() { };
|
|
function afterEach_fn() { };
|
|
|
|
function before(fn) {
|
|
fn();
|
|
}
|
|
|
|
function beforeEach(fn) {
|
|
beforeEach_fn = fn;
|
|
}
|
|
|
|
function afterEach(fn) {
|
|
afterEach_fn = fn;
|
|
}
|
|
|
|
function it(msg, fn) {
|
|
tests.push({
|
|
msg: `${msg} [${curDescribeMsg}]`,
|
|
fn
|
|
});
|
|
}
|
|
|
|
var callbacks = [];
|
|
function callDelayed(fn) {
|
|
callbacks.push(fn);
|
|
}
|
|
|
|
requestAnimationFrame(function tick() {
|
|
var i = callbacks.length;
|
|
while (i--) {
|
|
var cb = callbacks[i];
|
|
SimpleTest.executeSoon(function() { SimpleTest.executeSoon(cb) });
|
|
callbacks.splice(i, 1);
|
|
}
|
|
requestAnimationFrame(tick);
|
|
});
|
|
|
|
function expect(val) {
|
|
return {
|
|
to: {
|
|
throwException (regexp) {
|
|
try {
|
|
val();
|
|
ok(false, `${curItMsg} - an exception should have beeen thrown`);
|
|
} catch (e) {
|
|
ok(regexp.test(e), `${curItMsg} - supplied regexp should match thrown exception`);
|
|
}
|
|
},
|
|
get be() {
|
|
var fn = function (expected) {
|
|
is(val, expected, curItMsg);
|
|
};
|
|
fn.ok = function () {
|
|
ok(val, curItMsg);
|
|
};
|
|
fn.greaterThan = function (other) {
|
|
ok(val > other, `${curItMsg} - ${val} should be greater than ${other}`);
|
|
};
|
|
fn.lessThan = function (other) {
|
|
ok(val < other, `${curItMsg} - ${val} should be less than ${other}`);
|
|
};
|
|
return fn;
|
|
},
|
|
eql (expected) {
|
|
if (Array.isArray(expected)) {
|
|
if (!Array.isArray(val)) {
|
|
ok(false, curItMsg, `${curItMsg} - should be an array,`);
|
|
return;
|
|
}
|
|
is(val.length, expected.length, curItMsg, `${curItMsg} - arrays should be the same length`);
|
|
if (expected.length != val.length) {
|
|
return;
|
|
}
|
|
for (var i = 0; i < expected.length; i++) {
|
|
is(val[i], expected[i], `${curItMsg} - array elements at position ${i} should be equal`);
|
|
if (expected[i] != val[i]) {
|
|
return;
|
|
}
|
|
}
|
|
ok(true);
|
|
}
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
function describe(msg, fn) {
|
|
curDescribeMsg = msg;
|
|
fn();
|
|
curDescribeMsg = '';
|
|
}
|
|
|
|
function next() {
|
|
var test = tests.shift();
|
|
if (test) {
|
|
console.log(test.msg);
|
|
curItMsg = test.msg;
|
|
var fn = test.fn;
|
|
beforeEach_fn();
|
|
if (fn.length) {
|
|
fn(function () {
|
|
afterEach_fn();
|
|
next();
|
|
});
|
|
} else {
|
|
fn();
|
|
afterEach_fn();
|
|
next();
|
|
}
|
|
} else {
|
|
SimpleTest.finish();
|
|
}
|
|
}
|
|
|
|
var sinon = {
|
|
spy () {
|
|
var cbs = [];
|
|
var fn = function () {
|
|
fn.callCount++;
|
|
fn.lastCall = { args: arguments };
|
|
if (cbs.length) {
|
|
cbs.shift()();
|
|
}
|
|
};
|
|
fn.callCount = 0;
|
|
fn.lastCall = { args: [] };
|
|
fn.waitForNotification = (fn1) => {
|
|
cbs.push(fn1);
|
|
};
|
|
return fn;
|
|
}
|
|
};
|
|
|
|
var ASYNC_TIMEOUT = 300;
|
|
|
|
|
|
var io;
|
|
var noop = function() {};
|
|
|
|
|
|
// References to DOM elements, which are accessible to any test
|
|
// and reset prior to each test so state isn't shared.
|
|
var rootEl;
|
|
var grandParentEl;
|
|
var parentEl;
|
|
var targetEl1;
|
|
var targetEl2;
|
|
var targetEl3;
|
|
var targetEl4;
|
|
var targetEl5;
|
|
|
|
|
|
describe('IntersectionObserver', function() {
|
|
|
|
before(function() {
|
|
|
|
});
|
|
|
|
|
|
beforeEach(function() {
|
|
addStyles();
|
|
addFixtures();
|
|
});
|
|
|
|
|
|
afterEach(function() {
|
|
if (io && 'disconnect' in io) io.disconnect();
|
|
io = null;
|
|
|
|
window.onmessage = null;
|
|
|
|
removeStyles();
|
|
removeFixtures();
|
|
});
|
|
|
|
|
|
describe('constructor', function() {
|
|
|
|
it('throws when callback is not a function', function() {
|
|
expect(function() {
|
|
io = new IntersectionObserver(null);
|
|
}).to.throwException(/.*/i);
|
|
});
|
|
|
|
|
|
it('instantiates root correctly', function() {
|
|
io = new IntersectionObserver(noop);
|
|
expect(io.root).to.be(null);
|
|
|
|
io = new IntersectionObserver(noop, {root: rootEl});
|
|
expect(io.root).to.be(rootEl);
|
|
});
|
|
|
|
|
|
it('throws when root is not an Element', function() {
|
|
expect(function() {
|
|
io = new IntersectionObserver(noop, {root: 'foo'});
|
|
}).to.throwException(/.*/i);
|
|
});
|
|
|
|
|
|
it('instantiates rootMargin correctly', function() {
|
|
io = new IntersectionObserver(noop, {rootMargin: '10px'});
|
|
expect(io.rootMargin).to.be('10px 10px 10px 10px');
|
|
|
|
io = new IntersectionObserver(noop, {rootMargin: '10px -5%'});
|
|
expect(io.rootMargin).to.be('10px -5% 10px -5%');
|
|
|
|
io = new IntersectionObserver(noop, {rootMargin: '10px 20% 0px'});
|
|
expect(io.rootMargin).to.be('10px 20% 0px 20%');
|
|
|
|
io = new IntersectionObserver(noop, {rootMargin: '0px 0px -5% 5px'});
|
|
expect(io.rootMargin).to.be('0px 0px -5% 5px');
|
|
});
|
|
|
|
|
|
it('throws when rootMargin is not in pixels or percent', function() {
|
|
expect(function() {
|
|
io = new IntersectionObserver(noop, {rootMargin: 'auto'});
|
|
}).to.throwException(/pixels.*percent/i);
|
|
});
|
|
|
|
|
|
it('instantiates thresholds correctly', function() {
|
|
io = new IntersectionObserver(noop);
|
|
expect(io.thresholds).to.eql([0]);
|
|
|
|
io = new IntersectionObserver(noop, {threshold: 0.5});
|
|
expect(io.thresholds).to.eql([0.5]);
|
|
|
|
io = new IntersectionObserver(noop, {threshold: [0.25, 0.5, 0.75]});
|
|
expect(io.thresholds).to.eql([0.25, 0.5, 0.75]);
|
|
|
|
io = new IntersectionObserver(noop, {threshold: [1, .5, 0]});
|
|
expect(io.thresholds).to.eql([0, .5, 1]);
|
|
});
|
|
|
|
it('throws when a threshold value is not between 0 and 1', function() {
|
|
expect(function() {
|
|
io = new IntersectionObserver(noop, {threshold: [0, -1]});
|
|
}).to.throwException(/threshold/i);
|
|
});
|
|
|
|
it('throws when a threshold value is not a number', function() {
|
|
expect(function() {
|
|
io = new IntersectionObserver(noop, {threshold: "foo"});
|
|
}).to.throwException(/.*/i);
|
|
});
|
|
|
|
});
|
|
|
|
|
|
describe('observe', function() {
|
|
|
|
it('throws when target is not an Element', function() {
|
|
expect(function() {
|
|
io = new IntersectionObserver(noop);
|
|
io.observe(null);
|
|
}).to.throwException(/.*/i);
|
|
});
|
|
|
|
|
|
it('triggers if target intersects when observing begins', function(done) {
|
|
io = new IntersectionObserver(function(records) {
|
|
expect(records.length).to.be(1);
|
|
expect(records[0].intersectionRatio).to.be(1);
|
|
done();
|
|
}, {root: rootEl});
|
|
io.observe(targetEl1);
|
|
});
|
|
|
|
|
|
it('triggers with the correct arguments', function(done) {
|
|
io = new IntersectionObserver(function(records, observer) {
|
|
expect(records.length).to.be(1);
|
|
expect(records[0] instanceof IntersectionObserverEntry).to.be.ok();
|
|
expect(observer).to.be(io);
|
|
expect(this).to.be(io);
|
|
done();
|
|
}, {root: rootEl});
|
|
io.observe(targetEl1);
|
|
});
|
|
|
|
|
|
it('does trigger if target does not intersect when observing begins',
|
|
function(done) {
|
|
|
|
var spy = sinon.spy();
|
|
io = new IntersectionObserver(spy, {root: rootEl});
|
|
|
|
targetEl2.style.top = '-40px';
|
|
io.observe(targetEl2);
|
|
callDelayed(function() {
|
|
expect(spy.callCount).to.be(1);
|
|
done();
|
|
});
|
|
});
|
|
|
|
|
|
it('does not trigger if target is not a descendant of the intersection root in the containing block chain',
|
|
function(done) {
|
|
|
|
var spy = sinon.spy();
|
|
io = new IntersectionObserver(spy, {root: parentEl});
|
|
|
|
parentEl.style.position = 'static';
|
|
io.observe(targetEl2);
|
|
callDelayed(function() {
|
|
expect(spy.callCount).to.be(0);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('triggers if target or root becomes invisible',
|
|
function(done) {
|
|
|
|
var spy = sinon.spy();
|
|
io = new IntersectionObserver(spy, {root: rootEl});
|
|
|
|
runSequence([
|
|
function(done) {
|
|
io.observe(targetEl1);
|
|
spy.waitForNotification(function() {
|
|
expect(spy.callCount).to.be(1);
|
|
var records = sortRecords(spy.lastCall.args[0]);
|
|
expect(records.length).to.be(1);
|
|
expect(records[0].intersectionRatio).to.be(1);
|
|
done();
|
|
});
|
|
},
|
|
function(done) {
|
|
targetEl1.style.display = 'none';
|
|
spy.waitForNotification(function() {
|
|
expect(spy.callCount).to.be(2);
|
|
var records = sortRecords(spy.lastCall.args[0]);
|
|
expect(records.length).to.be(1);
|
|
expect(records[0].intersectionRatio).to.be(0);
|
|
done();
|
|
});
|
|
},
|
|
function(done) {
|
|
targetEl1.style.display = 'block';
|
|
spy.waitForNotification(function() {
|
|
expect(spy.callCount).to.be(3);
|
|
var records = sortRecords(spy.lastCall.args[0]);
|
|
expect(records.length).to.be(1);
|
|
expect(records[0].intersectionRatio).to.be(1);
|
|
done();
|
|
});
|
|
},
|
|
function(done) {
|
|
rootEl.style.display = 'none';
|
|
spy.waitForNotification(function() {
|
|
expect(spy.callCount).to.be(4);
|
|
var records = sortRecords(spy.lastCall.args[0]);
|
|
expect(records.length).to.be(1);
|
|
expect(records[0].intersectionRatio).to.be(0);
|
|
done();
|
|
});
|
|
},
|
|
function(done) {
|
|
rootEl.style.display = 'block';
|
|
spy.waitForNotification(function() {
|
|
expect(spy.callCount).to.be(5);
|
|
var records = sortRecords(spy.lastCall.args[0]);
|
|
expect(records.length).to.be(1);
|
|
expect(records[0].intersectionRatio).to.be(1);
|
|
done();
|
|
});
|
|
},
|
|
], done);
|
|
});
|
|
|
|
|
|
it('handles container elements with non-visible overflow',
|
|
function(done) {
|
|
|
|
var spy = sinon.spy();
|
|
io = new IntersectionObserver(spy, {root: rootEl});
|
|
|
|
runSequence([
|
|
function(done) {
|
|
io.observe(targetEl1);
|
|
spy.waitForNotification(function() {
|
|
expect(spy.callCount).to.be(1);
|
|
var records = sortRecords(spy.lastCall.args[0]);
|
|
expect(records.length).to.be(1);
|
|
expect(records[0].intersectionRatio).to.be(1);
|
|
done();
|
|
});
|
|
},
|
|
function(done) {
|
|
targetEl1.style.left = '-40px';
|
|
spy.waitForNotification(function() {
|
|
expect(spy.callCount).to.be(2);
|
|
var records = sortRecords(spy.lastCall.args[0]);
|
|
expect(records.length).to.be(1);
|
|
expect(records[0].intersectionRatio).to.be(0);
|
|
done();
|
|
});
|
|
},
|
|
function(done) {
|
|
parentEl.style.overflow = 'visible';
|
|
spy.waitForNotification(function() {
|
|
expect(spy.callCount).to.be(3);
|
|
var records = sortRecords(spy.lastCall.args[0]);
|
|
expect(records.length).to.be(1);
|
|
expect(records[0].intersectionRatio).to.be(1);
|
|
done();
|
|
});
|
|
}
|
|
], done);
|
|
});
|
|
|
|
|
|
it('observes one target at a single threshold correctly', function(done) {
|
|
|
|
var spy = sinon.spy();
|
|
io = new IntersectionObserver(spy, {root: rootEl, threshold: 0.5});
|
|
|
|
runSequence([
|
|
function(done) {
|
|
targetEl1.style.left = '-5px';
|
|
io.observe(targetEl1);
|
|
spy.waitForNotification(function() {
|
|
expect(spy.callCount).to.be(1);
|
|
var records = sortRecords(spy.lastCall.args[0]);
|
|
expect(records.length).to.be(1);
|
|
expect(records[0].intersectionRatio).to.be.greaterThan(0.5);
|
|
done();
|
|
});
|
|
},
|
|
function(done) {
|
|
targetEl1.style.left = '-15px';
|
|
spy.waitForNotification(function() {
|
|
expect(spy.callCount).to.be(2);
|
|
var records = sortRecords(spy.lastCall.args[0]);
|
|
expect(records.length).to.be(1);
|
|
expect(records[0].intersectionRatio).to.be.lessThan(0.5);
|
|
done();
|
|
});
|
|
},
|
|
function(done) {
|
|
targetEl1.style.left = '-25px';
|
|
callDelayed(function() {
|
|
expect(spy.callCount).to.be(2);
|
|
done();
|
|
});
|
|
},
|
|
function(done) {
|
|
targetEl1.style.left = '-10px';
|
|
spy.waitForNotification(function() {
|
|
expect(spy.callCount).to.be(3);
|
|
var records = sortRecords(spy.lastCall.args[0]);
|
|
expect(records.length).to.be(1);
|
|
expect(records[0].intersectionRatio).to.be(0.5);
|
|
done();
|
|
});
|
|
}
|
|
], done);
|
|
|
|
});
|
|
|
|
|
|
it('observes multiple targets at multiple thresholds correctly',
|
|
function(done) {
|
|
|
|
var spy = sinon.spy();
|
|
io = new IntersectionObserver(spy, {
|
|
root: rootEl,
|
|
threshold: [1, 0.5, 0]
|
|
});
|
|
|
|
runSequence([
|
|
function(done) {
|
|
targetEl1.style.top = '0px';
|
|
targetEl1.style.left = '-15px';
|
|
targetEl2.style.top = '-5px';
|
|
targetEl2.style.left = '0px';
|
|
targetEl3.style.top = '0px';
|
|
targetEl3.style.left = '205px';
|
|
io.observe(targetEl1);
|
|
io.observe(targetEl2);
|
|
io.observe(targetEl3);
|
|
spy.waitForNotification(function() {
|
|
expect(spy.callCount).to.be(1);
|
|
var records = sortRecords(spy.lastCall.args[0]);
|
|
expect(records.length).to.be(3);
|
|
expect(records[0].target).to.be(targetEl1);
|
|
expect(records[0].intersectionRatio).to.be(0.25);
|
|
expect(records[1].target).to.be(targetEl2);
|
|
expect(records[1].intersectionRatio).to.be(0.75);
|
|
done();
|
|
});
|
|
},
|
|
function(done) {
|
|
targetEl1.style.top = '0px';
|
|
targetEl1.style.left = '-5px';
|
|
targetEl2.style.top = '-15px';
|
|
targetEl2.style.left = '0px';
|
|
targetEl3.style.top = '0px';
|
|
targetEl3.style.left = '195px';
|
|
spy.waitForNotification(function() {
|
|
expect(spy.callCount).to.be(2);
|
|
var records = sortRecords(spy.lastCall.args[0]);
|
|
expect(records.length).to.be(3);
|
|
expect(records[0].target).to.be(targetEl1);
|
|
expect(records[0].intersectionRatio).to.be(0.75);
|
|
expect(records[1].target).to.be(targetEl2);
|
|
expect(records[1].intersectionRatio).to.be(0.25);
|
|
expect(records[2].target).to.be(targetEl3);
|
|
expect(records[2].intersectionRatio).to.be(0.25);
|
|
done();
|
|
});
|
|
},
|
|
function(done) {
|
|
targetEl1.style.top = '0px';
|
|
targetEl1.style.left = '5px';
|
|
targetEl2.style.top = '-25px';
|
|
targetEl2.style.left = '0px';
|
|
targetEl3.style.top = '0px';
|
|
targetEl3.style.left = '185px';
|
|
spy.waitForNotification(function() {
|
|
expect(spy.callCount).to.be(3);
|
|
var records = sortRecords(spy.lastCall.args[0]);
|
|
expect(records.length).to.be(3);
|
|
expect(records[0].target).to.be(targetEl1);
|
|
expect(records[0].intersectionRatio).to.be(1);
|
|
expect(records[1].target).to.be(targetEl2);
|
|
expect(records[1].intersectionRatio).to.be(0);
|
|
expect(records[2].target).to.be(targetEl3);
|
|
expect(records[2].intersectionRatio).to.be(0.75);
|
|
done();
|
|
});
|
|
},
|
|
function(done) {
|
|
targetEl1.style.top = '0px';
|
|
targetEl1.style.left = '15px';
|
|
targetEl2.style.top = '-35px';
|
|
targetEl2.style.left = '0px';
|
|
targetEl3.style.top = '0px';
|
|
targetEl3.style.left = '175px';
|
|
spy.waitForNotification(function() {
|
|
expect(spy.callCount).to.be(4);
|
|
var records = sortRecords(spy.lastCall.args[0]);
|
|
expect(records.length).to.be(1);
|
|
expect(records[0].target).to.be(targetEl3);
|
|
expect(records[0].intersectionRatio).to.be(1);
|
|
done();
|
|
});
|
|
}
|
|
], done);
|
|
});
|
|
|
|
|
|
it('handles rootMargin properly', function(done) {
|
|
|
|
parentEl.style.overflow = 'visible';
|
|
targetEl1.style.top = '0px';
|
|
targetEl1.style.left = '-20px';
|
|
targetEl2.style.top = '-20px';
|
|
targetEl2.style.left = '0px';
|
|
targetEl3.style.top = '0px';
|
|
targetEl3.style.left = '200px';
|
|
targetEl4.style.top = '180px';
|
|
targetEl4.style.left = '180px';
|
|
|
|
runSequence([
|
|
function(done) {
|
|
io = new IntersectionObserver(function(records) {
|
|
records = sortRecords(records);
|
|
expect(records.length).to.be(4);
|
|
expect(records[0].target).to.be(targetEl1);
|
|
expect(records[0].intersectionRatio).to.be(1);
|
|
expect(records[1].target).to.be(targetEl2);
|
|
expect(records[1].intersectionRatio).to.be(.5);
|
|
expect(records[2].target).to.be(targetEl3);
|
|
expect(records[2].intersectionRatio).to.be(.5);
|
|
expect(records[3].target).to.be(targetEl4);
|
|
expect(records[3].intersectionRatio).to.be(1);
|
|
io.disconnect();
|
|
done();
|
|
}, {root: rootEl, rootMargin: '10px'});
|
|
|
|
io.observe(targetEl1);
|
|
io.observe(targetEl2);
|
|
io.observe(targetEl3);
|
|
io.observe(targetEl4);
|
|
},
|
|
function(done) {
|
|
io = new IntersectionObserver(function(records) {
|
|
records = sortRecords(records);
|
|
expect(records.length).to.be(4);
|
|
expect(records[0].target).to.be(targetEl1);
|
|
expect(records[0].intersectionRatio).to.be(0.5);
|
|
expect(records[2].target).to.be(targetEl3);
|
|
expect(records[2].intersectionRatio).to.be(0.5);
|
|
expect(records[3].target).to.be(targetEl4);
|
|
expect(records[3].intersectionRatio).to.be(0.5);
|
|
io.disconnect();
|
|
done();
|
|
}, {root: rootEl, rootMargin: '-10px 10%'});
|
|
|
|
io.observe(targetEl1);
|
|
io.observe(targetEl2);
|
|
io.observe(targetEl3);
|
|
io.observe(targetEl4);
|
|
},
|
|
function(done) {
|
|
io = new IntersectionObserver(function(records) {
|
|
records = sortRecords(records);
|
|
expect(records.length).to.be(4);
|
|
expect(records[0].target).to.be(targetEl1);
|
|
expect(records[0].intersectionRatio).to.be(0.5);
|
|
expect(records[3].target).to.be(targetEl4);
|
|
expect(records[3].intersectionRatio).to.be(0.5);
|
|
io.disconnect();
|
|
done();
|
|
}, {root: rootEl, rootMargin: '-5% -2.5% 0px'});
|
|
|
|
io.observe(targetEl1);
|
|
io.observe(targetEl2);
|
|
io.observe(targetEl3);
|
|
io.observe(targetEl4);
|
|
},
|
|
function(done) {
|
|
io = new IntersectionObserver(function(records) {
|
|
records = sortRecords(records);
|
|
expect(records.length).to.be(4);
|
|
expect(records[0].target).to.be(targetEl1);
|
|
expect(records[0].intersectionRatio).to.be(0.5);
|
|
expect(records[1].target).to.be(targetEl2);
|
|
expect(records[1].intersectionRatio).to.be(0.5);
|
|
expect(records[3].target).to.be(targetEl4);
|
|
expect(records[3].intersectionRatio).to.be(0.25);
|
|
io.disconnect();
|
|
done();
|
|
}, {root: rootEl, rootMargin: '5% -2.5% -10px -190px'});
|
|
|
|
io.observe(targetEl1);
|
|
io.observe(targetEl2);
|
|
io.observe(targetEl3);
|
|
io.observe(targetEl4);
|
|
}
|
|
], done);
|
|
});
|
|
|
|
|
|
it('handles targets on the boundary of root', function(done) {
|
|
|
|
var spy = sinon.spy();
|
|
io = new IntersectionObserver(spy, {root: rootEl});
|
|
|
|
runSequence([
|
|
function(done) {
|
|
targetEl1.style.top = '0px';
|
|
targetEl1.style.left = '-21px';
|
|
targetEl2.style.top = '-20px';
|
|
targetEl2.style.left = '0px';
|
|
io.observe(targetEl1);
|
|
io.observe(targetEl2);
|
|
spy.waitForNotification(function() {
|
|
expect(spy.callCount).to.be(1);
|
|
var records = sortRecords(spy.lastCall.args[0]);
|
|
expect(records.length).to.be(2);
|
|
expect(records[1].intersectionRatio).to.be(0);
|
|
expect(records[1].target).to.be(targetEl2);
|
|
done();
|
|
});
|
|
},
|
|
function(done) {
|
|
targetEl1.style.top = '0px';
|
|
targetEl1.style.left = '-20px';
|
|
targetEl2.style.top = '-21px';
|
|
targetEl2.style.left = '0px';
|
|
spy.waitForNotification(function() {
|
|
expect(spy.callCount).to.be(2);
|
|
var records = sortRecords(spy.lastCall.args[0]);
|
|
expect(records.length).to.be(2);
|
|
expect(records[0].intersectionRatio).to.be(0);
|
|
expect(records[0].isIntersecting).to.be.ok();
|
|
expect(records[0].target).to.be(targetEl1);
|
|
expect(records[1].intersectionRatio).to.be(0);
|
|
expect(records[1].target).to.be(targetEl2);
|
|
done();
|
|
});
|
|
},
|
|
function(done) {
|
|
targetEl1.style.top = '-20px';
|
|
targetEl1.style.left = '200px';
|
|
targetEl2.style.top = '200px';
|
|
targetEl2.style.left = '200px';
|
|
spy.waitForNotification(function() {
|
|
expect(spy.callCount).to.be(3);
|
|
var records = sortRecords(spy.lastCall.args[0]);
|
|
expect(records.length).to.be(1);
|
|
expect(records[0].intersectionRatio).to.be(0);
|
|
expect(records[0].target).to.be(targetEl2);
|
|
done();
|
|
});
|
|
},
|
|
function(done) {
|
|
targetEl3.style.top = '20px';
|
|
targetEl3.style.left = '-20px';
|
|
targetEl4.style.top = '-20px';
|
|
targetEl4.style.left = '20px';
|
|
io.observe(targetEl3);
|
|
io.observe(targetEl4);
|
|
spy.waitForNotification(function() {
|
|
expect(spy.callCount).to.be(4);
|
|
var records = sortRecords(spy.lastCall.args[0]);
|
|
expect(records.length).to.be(2);
|
|
expect(records[0].intersectionRatio).to.be(0);
|
|
expect(records[0].isIntersecting).to.be.ok();
|
|
expect(records[0].target).to.be(targetEl3);
|
|
expect(records[1].intersectionRatio).to.be(0);
|
|
expect(records[1].target).to.be(targetEl4);
|
|
done();
|
|
});
|
|
}
|
|
], done);
|
|
|
|
});
|
|
|
|
|
|
it('handles zero-size targets within the root coordinate space',
|
|
function(done) {
|
|
|
|
var spy = sinon.spy();
|
|
io = new IntersectionObserver(spy, {root: rootEl});
|
|
|
|
runSequence([
|
|
function(done) {
|
|
targetEl1.style.top = '0px';
|
|
targetEl1.style.left = '0px';
|
|
targetEl1.style.width = '0px';
|
|
targetEl1.style.height = '0px';
|
|
io.observe(targetEl1);
|
|
spy.waitForNotification(function() {
|
|
var records = sortRecords(spy.lastCall.args[0]);
|
|
expect(records.length).to.be(1);
|
|
expect(records[0].intersectionRatio).to.be(1);
|
|
expect(records[0].isIntersecting).to.be.ok();
|
|
done();
|
|
});
|
|
},
|
|
function(done) {
|
|
targetEl1.style.top = '-1px';
|
|
spy.waitForNotification(function() {
|
|
var records = sortRecords(spy.lastCall.args[0]);
|
|
expect(records.length).to.be(1);
|
|
expect(records[0].intersectionRatio).to.be(0);
|
|
expect(records[0].isIntersecting).to.be(false);
|
|
done();
|
|
});
|
|
}
|
|
], done);
|
|
});
|
|
|
|
|
|
it('handles root/target elements not yet in the DOM', function(done) {
|
|
|
|
rootEl.remove();
|
|
targetEl1.remove();
|
|
|
|
var spy = sinon.spy();
|
|
io = new IntersectionObserver(spy, {root: rootEl});
|
|
|
|
runSequence([
|
|
function(done) {
|
|
io.observe(targetEl1);
|
|
callDelayed(done);
|
|
},
|
|
function(done) {
|
|
document.getElementById('fixtures').appendChild(rootEl);
|
|
callDelayed(function() {
|
|
expect(spy.callCount).to.be(1);
|
|
done();
|
|
});
|
|
},
|
|
function(done) {
|
|
parentEl.insertBefore(targetEl1, targetEl2);
|
|
spy.waitForNotification(function() {
|
|
expect(spy.callCount).to.be(2);
|
|
var records = sortRecords(spy.lastCall.args[0]);
|
|
expect(records.length).to.be(1);
|
|
expect(records[0].intersectionRatio).to.be(1);
|
|
expect(records[0].target).to.be(targetEl1);
|
|
done();
|
|
});
|
|
},
|
|
function(done) {
|
|
grandParentEl.remove();
|
|
spy.waitForNotification(function() {
|
|
expect(spy.callCount).to.be(3);
|
|
var records = sortRecords(spy.lastCall.args[0]);
|
|
expect(records.length).to.be(1);
|
|
expect(records[0].intersectionRatio).to.be(0);
|
|
expect(records[0].target).to.be(targetEl1);
|
|
done();
|
|
});
|
|
},
|
|
function(done) {
|
|
rootEl.appendChild(targetEl1);
|
|
spy.waitForNotification(function() {
|
|
expect(spy.callCount).to.be(4);
|
|
var records = sortRecords(spy.lastCall.args[0]);
|
|
expect(records.length).to.be(1);
|
|
expect(records[0].intersectionRatio).to.be(1);
|
|
expect(records[0].target).to.be(targetEl1);
|
|
done();
|
|
});
|
|
},
|
|
function(done) {
|
|
rootEl.remove();
|
|
spy.waitForNotification(function() {
|
|
expect(spy.callCount).to.be(5);
|
|
var records = sortRecords(spy.lastCall.args[0]);
|
|
expect(records.length).to.be(1);
|
|
expect(records[0].intersectionRatio).to.be(0);
|
|
expect(records[0].target).to.be(targetEl1);
|
|
done();
|
|
});
|
|
}
|
|
], done);
|
|
});
|
|
|
|
|
|
it('handles sub-root element scrolling', function(done) {
|
|
io = new IntersectionObserver(function(records) {
|
|
expect(records.length).to.be(1);
|
|
expect(records[0].intersectionRatio).to.be(1);
|
|
done();
|
|
}, {root: rootEl});
|
|
|
|
io.observe(targetEl3);
|
|
callDelayed(function() {
|
|
parentEl.scrollLeft = 40;
|
|
});
|
|
});
|
|
|
|
|
|
it('supports CSS transitions and transforms', function(done) {
|
|
|
|
targetEl1.style.top = '220px';
|
|
targetEl1.style.left = '220px';
|
|
|
|
var callCount = 0;
|
|
|
|
io = new IntersectionObserver(function(records) {
|
|
callCount++;
|
|
if (callCount <= 1) {
|
|
return;
|
|
}
|
|
expect(records.length).to.be(1);
|
|
expect(records[0].intersectionRatio).to.be(1);
|
|
done();
|
|
}, {root: rootEl, threshold: [1]});
|
|
|
|
io.observe(targetEl1);
|
|
callDelayed(function() {
|
|
targetEl1.style.transform = 'translateX(-40px) translateY(-40px)';
|
|
});
|
|
});
|
|
|
|
|
|
it('uses the viewport when no root is specified', function(done) {
|
|
window.onmessage = function (e) {
|
|
expect(e.data).to.be.ok();
|
|
win.close();
|
|
done();
|
|
};
|
|
|
|
var win = window.open("intersectionobserver_window.html");
|
|
});
|
|
|
|
it('triggers only once if observed multiple times (and does not crash when collected)', function(done) {
|
|
var spy = sinon.spy();
|
|
io = new IntersectionObserver(spy, {root: rootEl});
|
|
io.observe(targetEl1);
|
|
io.observe(targetEl1);
|
|
io.observe(targetEl1);
|
|
|
|
spy.waitForNotification(function() {
|
|
callDelayed(function () {
|
|
expect(spy.callCount).to.be(1);
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
});
|
|
|
|
describe('observe subframe', function () {
|
|
|
|
it('should not trigger if target and root are not in the same document',
|
|
function(done) {
|
|
|
|
var spy = sinon.spy();
|
|
io = new IntersectionObserver(spy, {root: rootEl});
|
|
|
|
targetEl4.onload = function () {
|
|
targetEl5 = targetEl4.contentDocument.getElementById('target5');
|
|
io.observe(targetEl5);
|
|
callDelayed(function() {
|
|
expect(spy.callCount).to.be(0);
|
|
done();
|
|
});
|
|
}
|
|
|
|
targetEl4.src = "intersectionobserver_iframe.html";
|
|
|
|
});
|
|
|
|
it('boundingClientRect matches target.getBoundingClientRect() for an element inside an iframe',
|
|
function(done) {
|
|
|
|
io = new IntersectionObserver(function(records) {
|
|
expect(records.length).to.be(1);
|
|
expect(records[0].boundingClientRect.top, targetEl5.getBoundingClientRect().top);
|
|
expect(records[0].boundingClientRect.left, targetEl5.getBoundingClientRect().left);
|
|
expect(records[0].boundingClientRect.width, targetEl5.getBoundingClientRect().width);
|
|
expect(records[0].boundingClientRect.height, targetEl5.getBoundingClientRect().height);
|
|
done();
|
|
}, {threshold: [1]});
|
|
|
|
targetEl4.onload = function () {
|
|
targetEl5 = targetEl4.contentDocument.getElementById('target5');
|
|
io.observe(targetEl5);
|
|
}
|
|
|
|
targetEl4.src = "intersectionobserver_iframe.html";
|
|
});
|
|
|
|
it('rootBounds is set to null for cross-origin observations', function(done) {
|
|
|
|
window.onmessage = function (e) {
|
|
expect(e.data).to.be(true);
|
|
done();
|
|
};
|
|
|
|
targetEl4.src = "http://example.org/tests/dom/base/test/intersectionobserver_cross_domain_iframe.html";
|
|
|
|
});
|
|
|
|
});
|
|
|
|
describe('takeRecords', function() {
|
|
|
|
it('supports getting records before the callback is invoked', function(done) {
|
|
|
|
var lastestRecords = [];
|
|
io = new IntersectionObserver(function(records) {
|
|
lastestRecords = lastestRecords.concat(records);
|
|
}, {root: rootEl});
|
|
io.observe(targetEl1);
|
|
|
|
window.requestAnimationFrame && requestAnimationFrame(function wait() {
|
|
lastestRecords = lastestRecords.concat(io.takeRecords());
|
|
if (!lastestRecords.length) {
|
|
requestAnimationFrame(wait);
|
|
return;
|
|
}
|
|
callDelayed(function() {
|
|
expect(lastestRecords.length).to.be(1);
|
|
expect(lastestRecords[0].intersectionRatio).to.be(1);
|
|
done();
|
|
});
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
describe('unobserve', function() {
|
|
|
|
it('removes targets from the internal store', function(done) {
|
|
|
|
var spy = sinon.spy();
|
|
io = new IntersectionObserver(spy, {root: rootEl});
|
|
|
|
runSequence([
|
|
function(done) {
|
|
targetEl1.style.top = targetEl2.style.top = '0px';
|
|
targetEl1.style.left = targetEl2.style.left = '0px';
|
|
io.observe(targetEl1);
|
|
io.observe(targetEl2);
|
|
spy.waitForNotification(function() {
|
|
expect(spy.callCount).to.be(1);
|
|
var records = sortRecords(spy.lastCall.args[0]);
|
|
expect(records.length).to.be(2);
|
|
expect(records[0].target).to.be(targetEl1);
|
|
expect(records[0].intersectionRatio).to.be(1);
|
|
expect(records[1].target).to.be(targetEl2);
|
|
expect(records[1].intersectionRatio).to.be(1);
|
|
done();
|
|
});
|
|
},
|
|
function(done) {
|
|
io.unobserve(targetEl1);
|
|
targetEl1.style.top = targetEl2.style.top = '0px';
|
|
targetEl1.style.left = targetEl2.style.left = '-40px';
|
|
spy.waitForNotification(function() {
|
|
expect(spy.callCount).to.be(2);
|
|
var records = sortRecords(spy.lastCall.args[0]);
|
|
expect(records.length).to.be(1);
|
|
expect(records[0].target).to.be(targetEl2);
|
|
expect(records[0].intersectionRatio).to.be(0);
|
|
done();
|
|
});
|
|
},
|
|
function(done) {
|
|
io.unobserve(targetEl2);
|
|
targetEl1.style.top = targetEl2.style.top = '0px';
|
|
targetEl1.style.left = targetEl2.style.left = '0px';
|
|
callDelayed(function() {
|
|
expect(spy.callCount).to.be(2);
|
|
done();
|
|
});
|
|
}
|
|
], done);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
describe('disconnect', function() {
|
|
|
|
it('removes all targets and stops listening for changes', function(done) {
|
|
|
|
var spy = sinon.spy();
|
|
io = new IntersectionObserver(spy, {root: rootEl});
|
|
|
|
runSequence([
|
|
function(done) {
|
|
targetEl1.style.top = targetEl2.style.top = '0px';
|
|
targetEl1.style.left = targetEl2.style.left = '0px';
|
|
io.observe(targetEl1);
|
|
io.observe(targetEl2);
|
|
spy.waitForNotification(function() {
|
|
expect(spy.callCount).to.be(1);
|
|
var records = sortRecords(spy.lastCall.args[0]);
|
|
expect(records.length).to.be(2);
|
|
expect(records[0].target).to.be(targetEl1);
|
|
expect(records[0].intersectionRatio).to.be(1);
|
|
expect(records[1].target).to.be(targetEl2);
|
|
expect(records[1].intersectionRatio).to.be(1);
|
|
done();
|
|
});
|
|
},
|
|
function(done) {
|
|
io.disconnect();
|
|
targetEl1.style.top = targetEl2.style.top = '0px';
|
|
targetEl1.style.left = targetEl2.style.left = '-40px';
|
|
callDelayed(function() {
|
|
expect(spy.callCount).to.be(1);
|
|
done();
|
|
});
|
|
}
|
|
], done);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
/**
|
|
* Runs a sequence of function and when finished invokes the done callback.
|
|
* Each function in the sequence is invoked with its own done function and
|
|
* it should call that function once it's complete.
|
|
* @param {Array<Function>} functions An array of async functions.
|
|
* @param {Function} done A final callback to be invoked once all function
|
|
* have run.
|
|
*/
|
|
function runSequence(functions, done) {
|
|
var next = functions.shift();
|
|
if (next) {
|
|
next(function() {
|
|
runSequence(functions, done);
|
|
});
|
|
} else {
|
|
done && done();
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Sorts an array of records alphebetically by ascending ID. Since the current
|
|
* native implementation doesn't sort change entries by `observe` order, we do
|
|
* that ourselves for the non-polyfill case. Since all tests call observe
|
|
* on targets in sequential order, this should always match.
|
|
* https://crbug.com/613679
|
|
* @param {Array<IntersectionObserverEntry>} entries The entries to sort.
|
|
* @return {Array<IntersectionObserverEntry>} The sorted array.
|
|
*/
|
|
function sortRecords(entries) {
|
|
entries = entries.sort(function(a, b) {
|
|
return a.target.id < b.target.id ? -1 : 1;
|
|
});
|
|
return entries;
|
|
}
|
|
|
|
|
|
/**
|
|
* Adds the common styles used by all tests to the page.
|
|
*/
|
|
function addStyles() {
|
|
var styles = document.createElement('style');
|
|
styles.id = 'styles';
|
|
document.documentElement.appendChild(styles);
|
|
|
|
var cssText =
|
|
'#root {' +
|
|
' position: relative;' +
|
|
' width: 400px;' +
|
|
' height: 200px;' +
|
|
' background: #eee' +
|
|
'}' +
|
|
'#grand-parent {' +
|
|
' position: relative;' +
|
|
' width: 200px;' +
|
|
' height: 200px;' +
|
|
'}' +
|
|
'#parent {' +
|
|
' position: absolute;' +
|
|
' top: 0px;' +
|
|
' left: 200px;' +
|
|
' overflow: hidden;' +
|
|
' width: 200px;' +
|
|
' height: 200px;' +
|
|
' background: #ddd;' +
|
|
'}' +
|
|
'#target1, #target2, #target3, #target4 {' +
|
|
' position: absolute;' +
|
|
' top: 0px;' +
|
|
' left: 0px;' +
|
|
' width: 20px;' +
|
|
' height: 20px;' +
|
|
' transform: translateX(0px) translateY(0px);' +
|
|
' transition: transform .5s;' +
|
|
' background: #f00;' +
|
|
' border: none;' +
|
|
'}';
|
|
|
|
styles.innerHTML = cssText;
|
|
}
|
|
|
|
|
|
/**
|
|
* Adds the DOM fixtures used by all tests to the page and assigns them to
|
|
* global variables so they can be referenced within the tests.
|
|
*/
|
|
function addFixtures() {
|
|
var fixtures = document.createElement('div');
|
|
fixtures.id = 'fixtures';
|
|
|
|
fixtures.innerHTML =
|
|
'<div id="root">' +
|
|
' <div id="grand-parent">' +
|
|
' <div id="parent">' +
|
|
' <div id="target1"></div>' +
|
|
' <div id="target2"></div>' +
|
|
' <div id="target3"></div>' +
|
|
' <iframe id="target4"></iframe>' +
|
|
' </div>' +
|
|
' </div>' +
|
|
'</div>';
|
|
|
|
document.body.appendChild(fixtures);
|
|
|
|
rootEl = document.getElementById('root');
|
|
grandParentEl = document.getElementById('grand-parent');
|
|
parentEl = document.getElementById('parent');
|
|
targetEl1 = document.getElementById('target1');
|
|
targetEl2 = document.getElementById('target2');
|
|
targetEl3 = document.getElementById('target3');
|
|
targetEl4 = document.getElementById('target4');
|
|
}
|
|
|
|
|
|
/**
|
|
* Removes the common styles from the page.
|
|
*/
|
|
function removeStyles() {
|
|
var styles = document.getElementById('styles');
|
|
styles.remove();
|
|
}
|
|
|
|
|
|
/**
|
|
* Removes the DOM fixtures from the page and resets the global references.
|
|
*/
|
|
function removeFixtures() {
|
|
var fixtures = document.getElementById('fixtures');
|
|
fixtures.remove();
|
|
|
|
rootEl = null;
|
|
grandParentEl = null;
|
|
parentEl = null;
|
|
targetEl1 = null;
|
|
targetEl2 = null;
|
|
targetEl3 = null;
|
|
targetEl4 = null;
|
|
}
|
|
|
|
function onLoad() {
|
|
SpecialPowers.pushPrefEnv({"set": [["dom.IntersectionObserver.enabled", true]]}, next);
|
|
}
|
|
|
|
SimpleTest.waitForExplicitFinish();
|
|
</script>
|
|
</pre>
|
|
<div id="log">
|
|
</div>
|
|
</body>
|
|
</html>
|