diff options
author | Luke Bennett <lbennett@gitlab.com> | 2018-11-26 11:57:21 +0300 |
---|---|---|
committer | Fatih Acet <acetfatih@gmail.com> | 2018-11-26 11:57:21 +0300 |
commit | 1877eb49c0bfe855eca74209441f954c4550ffef (patch) | |
tree | 7bc47b4d007f48f8504ee1d6a4c2329d2324adf5 | |
parent | ceae9f5211a77333810dfd704823381730eb1791 (diff) |
Attempt to fix lazy loader spec transient failure
-rw-r--r-- | app/assets/javascripts/lazy_loader.js | 6 | ||||
-rw-r--r-- | spec/javascripts/helpers/scroll_into_view_promise.js | 28 | ||||
-rw-r--r-- | spec/javascripts/helpers/wait_for_attribute_change.js | 16 | ||||
-rw-r--r-- | spec/javascripts/lazy_loader_spec.js | 174 |
4 files changed, 149 insertions, 75 deletions
diff --git a/app/assets/javascripts/lazy_loader.js b/app/assets/javascripts/lazy_loader.js index 61b4862b4e3..af50ea9d6c2 100644 --- a/app/assets/javascripts/lazy_loader.js +++ b/app/assets/javascripts/lazy_loader.js @@ -19,7 +19,7 @@ export default class LazyLoader { } searchLazyImages() { - requestIdleCallback( + window.requestIdleCallback( () => { const lazyImages = [].slice.call(document.querySelectorAll('.lazy')); @@ -107,7 +107,7 @@ export default class LazyLoader { } scrollCheck() { - requestAnimationFrame(() => this.checkElementsInView()); + window.requestAnimationFrame(() => this.checkElementsInView()); } checkElementsInView() { @@ -122,7 +122,7 @@ export default class LazyLoader { const imgBound = imgTop + imgBoundRect.height; if (scrollTop <= imgBound && visHeight >= imgTop) { - requestAnimationFrame(() => { + window.requestAnimationFrame(() => { LazyLoader.loadImage(selectedImage); }); return false; diff --git a/spec/javascripts/helpers/scroll_into_view_promise.js b/spec/javascripts/helpers/scroll_into_view_promise.js new file mode 100644 index 00000000000..0edea2103da --- /dev/null +++ b/spec/javascripts/helpers/scroll_into_view_promise.js @@ -0,0 +1,28 @@ +export default function scrollIntoViewPromise(intersectionTarget, timeout = 100, maxTries = 5) { + return new Promise((resolve, reject) => { + let intersectionObserver; + let retry = 0; + + const intervalId = setInterval(() => { + if (retry >= maxTries) { + intersectionObserver.disconnect(); + clearInterval(intervalId); + reject(new Error(`Could not scroll target into viewPort within ${timeout * maxTries} ms`)); + } + retry += 1; + intersectionTarget.scrollIntoView(); + }, timeout); + + intersectionObserver = new IntersectionObserver(entries => { + if (entries[0].isIntersecting) { + intersectionObserver.disconnect(); + clearInterval(intervalId); + resolve(); + } + }); + + intersectionObserver.observe(intersectionTarget); + + intersectionTarget.scrollIntoView(); + }); +} diff --git a/spec/javascripts/helpers/wait_for_attribute_change.js b/spec/javascripts/helpers/wait_for_attribute_change.js new file mode 100644 index 00000000000..8f22d569222 --- /dev/null +++ b/spec/javascripts/helpers/wait_for_attribute_change.js @@ -0,0 +1,16 @@ +export default (domElement, attributes, timeout = 1500) => + new Promise((resolve, reject) => { + let observer; + const timeoutId = setTimeout(() => { + observer.disconnect(); + reject(new Error(`Could not see an attribute update within ${timeout} ms`)); + }, timeout); + + observer = new MutationObserver(() => { + clearTimeout(timeoutId); + observer.disconnect(); + resolve(); + }); + + observer.observe(domElement, { attributes: true, attributeFilter: attributes }); + }); diff --git a/spec/javascripts/lazy_loader_spec.js b/spec/javascripts/lazy_loader_spec.js index eac4756e8a9..cbdc1644430 100644 --- a/spec/javascripts/lazy_loader_spec.js +++ b/spec/javascripts/lazy_loader_spec.js @@ -1,16 +1,19 @@ import LazyLoader from '~/lazy_loader'; import { TEST_HOST } from './test_constants'; - -let lazyLoader = null; +import scrollIntoViewPromise from './helpers/scroll_into_view_promise'; +import waitForPromises from './helpers/wait_for_promises'; +import waitForAttributeChange from './helpers/wait_for_attribute_change'; const execImmediately = callback => { callback(); }; describe('LazyLoader', function() { + let lazyLoader = null; + preloadFixtures('issues/issue_with_comment.html.raw'); - describe('with IntersectionObserver disabled', () => { + describe('without IntersectionObserver', () => { beforeEach(function() { loadFixtures('issues/issue_with_comment.html.raw'); @@ -36,14 +39,15 @@ describe('LazyLoader', function() { it('should copy value from data-src to src for img 1', function(done) { const img = document.querySelectorAll('img[data-src]')[0]; const originalDataSrc = img.getAttribute('data-src'); - img.scrollIntoView(); - - setTimeout(() => { - expect(LazyLoader.loadImage).toHaveBeenCalled(); - expect(img.getAttribute('src')).toBe(originalDataSrc); - expect(img).toHaveClass('js-lazy-loaded'); - done(); - }, 50); + + Promise.all([scrollIntoViewPromise(img), waitForAttributeChange(img, ['data-src', 'src'])]) + .then(() => { + expect(LazyLoader.loadImage).toHaveBeenCalled(); + expect(img.getAttribute('src')).toBe(originalDataSrc); + expect(img).toHaveClass('js-lazy-loaded'); + done(); + }) + .catch(done.fail); }); it('should lazy load dynamically added data-src images', function(done) { @@ -52,14 +56,18 @@ describe('LazyLoader', function() { newImg.className = 'lazy'; newImg.setAttribute('data-src', testPath); document.body.appendChild(newImg); - newImg.scrollIntoView(); - - setTimeout(() => { - expect(LazyLoader.loadImage).toHaveBeenCalled(); - expect(newImg.getAttribute('src')).toBe(testPath); - expect(newImg).toHaveClass('js-lazy-loaded'); - done(); - }, 50); + + Promise.all([ + scrollIntoViewPromise(newImg), + waitForAttributeChange(newImg, ['data-src', 'src']), + ]) + .then(() => { + expect(LazyLoader.loadImage).toHaveBeenCalled(); + expect(newImg.getAttribute('src')).toBe(testPath); + expect(newImg).toHaveClass('js-lazy-loaded'); + done(); + }) + .catch(done.fail); }); it('should not alter normal images', function(done) { @@ -67,13 +75,15 @@ describe('LazyLoader', function() { const testPath = `${TEST_HOST}/img/testimg.png`; newImg.setAttribute('src', testPath); document.body.appendChild(newImg); - newImg.scrollIntoView(); - setTimeout(() => { - expect(LazyLoader.loadImage).not.toHaveBeenCalled(); - expect(newImg).not.toHaveClass('js-lazy-loaded'); - done(); - }, 50); + scrollIntoViewPromise(newImg) + .then(waitForPromises) + .then(() => { + expect(LazyLoader.loadImage).not.toHaveBeenCalled(); + expect(newImg).not.toHaveClass('js-lazy-loaded'); + done(); + }) + .catch(done.fail); }); it('should not load dynamically added pictures if content observer is turned off', done => { @@ -84,13 +94,15 @@ describe('LazyLoader', function() { newImg.className = 'lazy'; newImg.setAttribute('data-src', testPath); document.body.appendChild(newImg); - newImg.scrollIntoView(); - setTimeout(() => { - expect(LazyLoader.loadImage).not.toHaveBeenCalled(); - expect(newImg).not.toHaveClass('js-lazy-loaded'); - done(); - }, 50); + scrollIntoViewPromise(newImg) + .then(waitForPromises) + .then(() => { + expect(LazyLoader.loadImage).not.toHaveBeenCalled(); + expect(newImg).not.toHaveClass('js-lazy-loaded'); + done(); + }) + .catch(done.fail); }); it('should load dynamically added pictures if content observer is turned off and on again', done => { @@ -102,17 +114,22 @@ describe('LazyLoader', function() { newImg.className = 'lazy'; newImg.setAttribute('data-src', testPath); document.body.appendChild(newImg); - newImg.scrollIntoView(); - setTimeout(() => { - expect(LazyLoader.loadImage).toHaveBeenCalled(); - expect(newImg).toHaveClass('js-lazy-loaded'); - done(); - }, 50); + Promise.all([ + scrollIntoViewPromise(newImg), + waitForAttributeChange(newImg, ['data-src', 'src']), + ]) + .then(waitForPromises) + .then(() => { + expect(LazyLoader.loadImage).toHaveBeenCalled(); + expect(newImg).toHaveClass('js-lazy-loaded'); + done(); + }) + .catch(done.fail); }); }); - describe('with IntersectionObserver enabled', () => { + describe('with IntersectionObserver', () => { beforeEach(function() { loadFixtures('issues/issue_with_comment.html.raw'); @@ -136,14 +153,15 @@ describe('LazyLoader', function() { it('should copy value from data-src to src for img 1', function(done) { const img = document.querySelectorAll('img[data-src]')[0]; const originalDataSrc = img.getAttribute('data-src'); - img.scrollIntoView(); - - setTimeout(() => { - expect(LazyLoader.loadImage).toHaveBeenCalled(); - expect(img.getAttribute('src')).toBe(originalDataSrc); - expect(img).toHaveClass('js-lazy-loaded'); - done(); - }, 50); + + Promise.all([scrollIntoViewPromise(img), waitForAttributeChange(img, ['data-src', 'src'])]) + .then(() => { + expect(LazyLoader.loadImage).toHaveBeenCalled(); + expect(img.getAttribute('src')).toBe(originalDataSrc); + expect(img).toHaveClass('js-lazy-loaded'); + done(); + }) + .catch(done.fail); }); it('should lazy load dynamically added data-src images', function(done) { @@ -152,14 +170,18 @@ describe('LazyLoader', function() { newImg.className = 'lazy'; newImg.setAttribute('data-src', testPath); document.body.appendChild(newImg); - newImg.scrollIntoView(); - - setTimeout(() => { - expect(LazyLoader.loadImage).toHaveBeenCalled(); - expect(newImg.getAttribute('src')).toBe(testPath); - expect(newImg).toHaveClass('js-lazy-loaded'); - done(); - }, 50); + + Promise.all([ + scrollIntoViewPromise(newImg), + waitForAttributeChange(newImg, ['data-src', 'src']), + ]) + .then(() => { + expect(LazyLoader.loadImage).toHaveBeenCalled(); + expect(newImg.getAttribute('src')).toBe(testPath); + expect(newImg).toHaveClass('js-lazy-loaded'); + done(); + }) + .catch(done.fail); }); it('should not alter normal images', function(done) { @@ -167,13 +189,15 @@ describe('LazyLoader', function() { const testPath = `${TEST_HOST}/img/testimg.png`; newImg.setAttribute('src', testPath); document.body.appendChild(newImg); - newImg.scrollIntoView(); - setTimeout(() => { - expect(LazyLoader.loadImage).not.toHaveBeenCalled(); - expect(newImg).not.toHaveClass('js-lazy-loaded'); - done(); - }, 50); + scrollIntoViewPromise(newImg) + .then(waitForPromises) + .then(() => { + expect(LazyLoader.loadImage).not.toHaveBeenCalled(); + expect(newImg).not.toHaveClass('js-lazy-loaded'); + done(); + }) + .catch(done.fail); }); it('should not load dynamically added pictures if content observer is turned off', done => { @@ -184,13 +208,15 @@ describe('LazyLoader', function() { newImg.className = 'lazy'; newImg.setAttribute('data-src', testPath); document.body.appendChild(newImg); - newImg.scrollIntoView(); - setTimeout(() => { - expect(LazyLoader.loadImage).not.toHaveBeenCalled(); - expect(newImg).not.toHaveClass('js-lazy-loaded'); - done(); - }, 50); + scrollIntoViewPromise(newImg) + .then(waitForPromises) + .then(() => { + expect(LazyLoader.loadImage).not.toHaveBeenCalled(); + expect(newImg).not.toHaveClass('js-lazy-loaded'); + done(); + }) + .catch(done.fail); }); it('should load dynamically added pictures if content observer is turned off and on again', done => { @@ -202,13 +228,17 @@ describe('LazyLoader', function() { newImg.className = 'lazy'; newImg.setAttribute('data-src', testPath); document.body.appendChild(newImg); - newImg.scrollIntoView(); - setTimeout(() => { - expect(LazyLoader.loadImage).toHaveBeenCalled(); - expect(newImg).toHaveClass('js-lazy-loaded'); - done(); - }, 50); + Promise.all([ + scrollIntoViewPromise(newImg), + waitForAttributeChange(newImg, ['data-src', 'src']), + ]) + .then(() => { + expect(LazyLoader.loadImage).toHaveBeenCalled(); + expect(newImg).toHaveClass('js-lazy-loaded'); + done(); + }) + .catch(done.fail); }); }); }); |