diff options
Diffstat (limited to '')
-rw-r--r-- | dom/base/test/test_intersectionobservers.html | 1221 |
1 files changed, 1221 insertions, 0 deletions
diff --git a/dom/base/test/test_intersectionobservers.html b/dom/base/test/test_intersectionobservers.html new file mode 100644 index 0000000000..97fa173cef --- /dev/null +++ b/dom/base/test/test_intersectionobservers.html @@ -0,0 +1,1221 @@ +<!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('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('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() { + next(); + } + + SimpleTest.waitForExplicitFinish(); +</script> +</pre> +<div id="log"> +</div> +</body> +</html> |