test_bug1499961.html (10870B)
1 <!DOCTYPE HTML> 2 <html> 3 <!-- 4 https://bugzilla.mozilla.org/show_bug.cgi?id=1499961 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 14 http://www.apache.org/licenses/LICENSE-2.0 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 1499961</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=1499961">Mozilla Bug 1499961</a> 29 <p id="display"></p> 30 <pre id="test"> 31 <script type="application/javascript"> 32 /* eslint-disable no-shadow */ 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: 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: function (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: function (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: function () { 153 var callbacks = []; 154 var fn = function () { 155 fn.callCount++; 156 fn.lastCall = { args: arguments }; 157 if (callbacks.length) { 158 callbacks.shift()(); 159 } 160 }; 161 fn.callCount = 0; 162 fn.lastCall = { args: [] }; 163 fn.waitForNotification = (fn) => { 164 callbacks.push(fn); 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('move iframe and check reflow', function(done) { 216 217 var spy = sinon.spy(); 218 io = new IntersectionObserver(spy, {root: rootEl}); 219 220 runSequence([ 221 // Do a first change and wait for its intersection observer 222 // notification, to ensure one full reflow was completed. 223 function(done) { 224 targetEl1.style.top = '0px'; 225 io.observe(targetEl1); 226 spy.waitForNotification(function() { 227 var records = sortRecords(spy.lastCall.args[0]); 228 expect(records.length).to.be(1); 229 expect(records[0].target).to.be(targetEl1); 230 done(); 231 }); 232 }, 233 // Do another change, which may trigger an incremental reflow only. 234 function(done) { 235 targetEl4.style.top = '-20px'; 236 targetEl4.style.left = '20px'; 237 io.observe(targetEl4); 238 spy.waitForNotification(function() { 239 expect(spy.callCount).to.be(2); 240 var records = sortRecords(spy.lastCall.args[0]); 241 expect(records.length).to.be(1); 242 expect(records[0].target).to.be(targetEl4); 243 // After the iframe is moved, reflow should include its parent, 244 // even if the iframe is a reflow root. 245 // If moved correctly (outside of rootEl), the intersection ratio 246 // should now be 0. 247 expect(records[0].intersectionRatio).to.be(0); 248 done(); 249 }); 250 } 251 ], done); 252 253 }); 254 255 }); 256 257 }); 258 259 260 /** 261 * Runs a sequence of function and when finished invokes the done callback. 262 * Each function in the sequence is invoked with its own done function and 263 * it should call that function once it's complete. 264 * 265 * @param {Array<Function>} functions An array of async functions. 266 * @param {Function} done A final callback to be invoked once all function 267 * have run. 268 */ 269 function runSequence(functions, done) { 270 var next = functions.shift(); 271 if (next) { 272 next(function() { 273 runSequence(functions, done); 274 }); 275 } else { 276 done && done(); 277 } 278 } 279 280 281 /** 282 * Sorts an array of records alphebetically by ascending ID. Since the current 283 * native implementation doesn't sort change entries by `observe` order, we do 284 * that ourselves for the non-polyfill case. Since all tests call observe 285 * on targets in sequential order, this should always match. 286 * https://crbug.com/613679 287 * 288 * @param {Array<IntersectionObserverEntry>} entries The entries to sort. 289 * @return {Array<IntersectionObserverEntry>} The sorted array. 290 */ 291 function sortRecords(entries) { 292 entries = entries.sort(function(a, b) { 293 return a.target.id < b.target.id ? -1 : 1; 294 }); 295 return entries; 296 } 297 298 299 /** 300 * Adds the common styles used by all tests to the page. 301 */ 302 function addStyles() { 303 var styles = document.createElement('style'); 304 styles.id = 'styles'; 305 document.documentElement.appendChild(styles); 306 307 var cssText = 308 '#root {' + 309 ' position: relative;' + 310 ' width: 400px;' + 311 ' height: 200px;' + 312 ' background: #eee' + 313 '}' + 314 '#grand-parent {' + 315 ' position: relative;' + 316 ' width: 200px;' + 317 ' height: 200px;' + 318 '}' + 319 '#parent {' + 320 ' position: absolute;' + 321 ' top: 0px;' + 322 ' left: 200px;' + 323 ' overflow: hidden;' + 324 ' width: 200px;' + 325 ' height: 200px;' + 326 ' background: #ddd;' + 327 '}' + 328 '#target1, #target2, #target3, #target4 {' + 329 ' position: absolute;' + 330 ' top: 0px;' + 331 ' left: 0px;' + 332 ' width: 20px;' + 333 ' height: 20px;' + 334 ' transform: translateX(0px) translateY(0px);' + 335 ' transition: transform .5s;' + 336 ' background: #f00;' + 337 ' border: none;' + 338 '}'; 339 340 styles.innerHTML = cssText; 341 } 342 343 344 /** 345 * Adds the DOM fixtures used by all tests to the page and assigns them to 346 * global variables so they can be referenced within the tests. 347 */ 348 function addFixtures() { 349 var fixtures = document.createElement('div'); 350 fixtures.id = 'fixtures'; 351 352 fixtures.innerHTML = 353 '<div id="root">' + 354 ' <div id="grand-parent">' + 355 ' <div id="parent">' + 356 ' <div id="target1"></div>' + 357 ' <div id="target2"></div>' + 358 ' <div id="target3"></div>' + 359 ' <iframe id="target4"></iframe>' + 360 ' </div>' + 361 ' </div>' + 362 '</div>'; 363 364 document.body.appendChild(fixtures); 365 366 rootEl = document.getElementById('root'); 367 grandParentEl = document.getElementById('grand-parent'); 368 parentEl = document.getElementById('parent'); 369 targetEl1 = document.getElementById('target1'); 370 targetEl2 = document.getElementById('target2'); 371 targetEl3 = document.getElementById('target3'); 372 targetEl4 = document.getElementById('target4'); 373 } 374 375 376 /** 377 * Removes the common styles from the page. 378 */ 379 function removeStyles() { 380 var styles = document.getElementById('styles'); 381 styles.remove(); 382 } 383 384 385 /** 386 * Removes the DOM fixtures from the page and resets the global references. 387 */ 388 function removeFixtures() { 389 var fixtures = document.getElementById('fixtures'); 390 fixtures.remove(); 391 392 rootEl = null; 393 grandParentEl = null; 394 parentEl = null; 395 targetEl1 = null; 396 targetEl2 = null; 397 targetEl3 = null; 398 targetEl4 = null; 399 } 400 401 function onLoad() { 402 next(); 403 } 404 405 SimpleTest.waitForExplicitFinish(); 406 </script> 407 </pre> 408 <div id="log"> 409 </div> 410 </body> 411 </html>