Commit 1877eb49 authored by Luke Bennett's avatar Luke Bennett Committed by Fatih Acet

Attempt to fix lazy loader spec transient failure

parent ceae9f52
...@@ -19,7 +19,7 @@ export default class LazyLoader { ...@@ -19,7 +19,7 @@ export default class LazyLoader {
} }
searchLazyImages() { searchLazyImages() {
requestIdleCallback( window.requestIdleCallback(
() => { () => {
const lazyImages = [].slice.call(document.querySelectorAll('.lazy')); const lazyImages = [].slice.call(document.querySelectorAll('.lazy'));
...@@ -107,7 +107,7 @@ export default class LazyLoader { ...@@ -107,7 +107,7 @@ export default class LazyLoader {
} }
scrollCheck() { scrollCheck() {
requestAnimationFrame(() => this.checkElementsInView()); window.requestAnimationFrame(() => this.checkElementsInView());
} }
checkElementsInView() { checkElementsInView() {
...@@ -122,7 +122,7 @@ export default class LazyLoader { ...@@ -122,7 +122,7 @@ export default class LazyLoader {
const imgBound = imgTop + imgBoundRect.height; const imgBound = imgTop + imgBoundRect.height;
if (scrollTop <= imgBound && visHeight >= imgTop) { if (scrollTop <= imgBound && visHeight >= imgTop) {
requestAnimationFrame(() => { window.requestAnimationFrame(() => {
LazyLoader.loadImage(selectedImage); LazyLoader.loadImage(selectedImage);
}); });
return false; return false;
......
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();
});
}
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 });
});
import LazyLoader from '~/lazy_loader'; import LazyLoader from '~/lazy_loader';
import { TEST_HOST } from './test_constants'; import { TEST_HOST } from './test_constants';
import scrollIntoViewPromise from './helpers/scroll_into_view_promise';
let lazyLoader = null; import waitForPromises from './helpers/wait_for_promises';
import waitForAttributeChange from './helpers/wait_for_attribute_change';
const execImmediately = callback => { const execImmediately = callback => {
callback(); callback();
}; };
describe('LazyLoader', function() { describe('LazyLoader', function() {
let lazyLoader = null;
preloadFixtures('issues/issue_with_comment.html.raw'); preloadFixtures('issues/issue_with_comment.html.raw');
describe('with IntersectionObserver disabled', () => { describe('without IntersectionObserver', () => {
beforeEach(function() { beforeEach(function() {
loadFixtures('issues/issue_with_comment.html.raw'); loadFixtures('issues/issue_with_comment.html.raw');
...@@ -36,14 +39,15 @@ describe('LazyLoader', function() { ...@@ -36,14 +39,15 @@ describe('LazyLoader', function() {
it('should copy value from data-src to src for img 1', function(done) { it('should copy value from data-src to src for img 1', function(done) {
const img = document.querySelectorAll('img[data-src]')[0]; const img = document.querySelectorAll('img[data-src]')[0];
const originalDataSrc = img.getAttribute('data-src'); const originalDataSrc = img.getAttribute('data-src');
img.scrollIntoView();
Promise.all([scrollIntoViewPromise(img), waitForAttributeChange(img, ['data-src', 'src'])])
setTimeout(() => { .then(() => {
expect(LazyLoader.loadImage).toHaveBeenCalled(); expect(LazyLoader.loadImage).toHaveBeenCalled();
expect(img.getAttribute('src')).toBe(originalDataSrc); expect(img.getAttribute('src')).toBe(originalDataSrc);
expect(img).toHaveClass('js-lazy-loaded'); expect(img).toHaveClass('js-lazy-loaded');
done(); done();
}, 50); })
.catch(done.fail);
}); });
it('should lazy load dynamically added data-src images', function(done) { it('should lazy load dynamically added data-src images', function(done) {
...@@ -52,14 +56,18 @@ describe('LazyLoader', function() { ...@@ -52,14 +56,18 @@ describe('LazyLoader', function() {
newImg.className = 'lazy'; newImg.className = 'lazy';
newImg.setAttribute('data-src', testPath); newImg.setAttribute('data-src', testPath);
document.body.appendChild(newImg); document.body.appendChild(newImg);
newImg.scrollIntoView();
Promise.all([
setTimeout(() => { scrollIntoViewPromise(newImg),
expect(LazyLoader.loadImage).toHaveBeenCalled(); waitForAttributeChange(newImg, ['data-src', 'src']),
expect(newImg.getAttribute('src')).toBe(testPath); ])
expect(newImg).toHaveClass('js-lazy-loaded'); .then(() => {
done(); expect(LazyLoader.loadImage).toHaveBeenCalled();
}, 50); expect(newImg.getAttribute('src')).toBe(testPath);
expect(newImg).toHaveClass('js-lazy-loaded');
done();
})
.catch(done.fail);
}); });
it('should not alter normal images', function(done) { it('should not alter normal images', function(done) {
...@@ -67,13 +75,15 @@ describe('LazyLoader', function() { ...@@ -67,13 +75,15 @@ describe('LazyLoader', function() {
const testPath = `${TEST_HOST}/img/testimg.png`; const testPath = `${TEST_HOST}/img/testimg.png`;
newImg.setAttribute('src', testPath); newImg.setAttribute('src', testPath);
document.body.appendChild(newImg); document.body.appendChild(newImg);
newImg.scrollIntoView();
setTimeout(() => { scrollIntoViewPromise(newImg)
expect(LazyLoader.loadImage).not.toHaveBeenCalled(); .then(waitForPromises)
expect(newImg).not.toHaveClass('js-lazy-loaded'); .then(() => {
done(); expect(LazyLoader.loadImage).not.toHaveBeenCalled();
}, 50); 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 => { it('should not load dynamically added pictures if content observer is turned off', done => {
...@@ -84,13 +94,15 @@ describe('LazyLoader', function() { ...@@ -84,13 +94,15 @@ describe('LazyLoader', function() {
newImg.className = 'lazy'; newImg.className = 'lazy';
newImg.setAttribute('data-src', testPath); newImg.setAttribute('data-src', testPath);
document.body.appendChild(newImg); document.body.appendChild(newImg);
newImg.scrollIntoView();
setTimeout(() => { scrollIntoViewPromise(newImg)
expect(LazyLoader.loadImage).not.toHaveBeenCalled(); .then(waitForPromises)
expect(newImg).not.toHaveClass('js-lazy-loaded'); .then(() => {
done(); expect(LazyLoader.loadImage).not.toHaveBeenCalled();
}, 50); 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 => { it('should load dynamically added pictures if content observer is turned off and on again', done => {
...@@ -102,17 +114,22 @@ describe('LazyLoader', function() { ...@@ -102,17 +114,22 @@ describe('LazyLoader', function() {
newImg.className = 'lazy'; newImg.className = 'lazy';
newImg.setAttribute('data-src', testPath); newImg.setAttribute('data-src', testPath);
document.body.appendChild(newImg); document.body.appendChild(newImg);
newImg.scrollIntoView();
setTimeout(() => { Promise.all([
expect(LazyLoader.loadImage).toHaveBeenCalled(); scrollIntoViewPromise(newImg),
expect(newImg).toHaveClass('js-lazy-loaded'); waitForAttributeChange(newImg, ['data-src', 'src']),
done(); ])
}, 50); .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() { beforeEach(function() {
loadFixtures('issues/issue_with_comment.html.raw'); loadFixtures('issues/issue_with_comment.html.raw');
...@@ -136,14 +153,15 @@ describe('LazyLoader', function() { ...@@ -136,14 +153,15 @@ describe('LazyLoader', function() {
it('should copy value from data-src to src for img 1', function(done) { it('should copy value from data-src to src for img 1', function(done) {
const img = document.querySelectorAll('img[data-src]')[0]; const img = document.querySelectorAll('img[data-src]')[0];
const originalDataSrc = img.getAttribute('data-src'); const originalDataSrc = img.getAttribute('data-src');
img.scrollIntoView();
Promise.all([scrollIntoViewPromise(img), waitForAttributeChange(img, ['data-src', 'src'])])
setTimeout(() => { .then(() => {
expect(LazyLoader.loadImage).toHaveBeenCalled(); expect(LazyLoader.loadImage).toHaveBeenCalled();
expect(img.getAttribute('src')).toBe(originalDataSrc); expect(img.getAttribute('src')).toBe(originalDataSrc);
expect(img).toHaveClass('js-lazy-loaded'); expect(img).toHaveClass('js-lazy-loaded');
done(); done();
}, 50); })
.catch(done.fail);
}); });
it('should lazy load dynamically added data-src images', function(done) { it('should lazy load dynamically added data-src images', function(done) {
...@@ -152,14 +170,18 @@ describe('LazyLoader', function() { ...@@ -152,14 +170,18 @@ describe('LazyLoader', function() {
newImg.className = 'lazy'; newImg.className = 'lazy';
newImg.setAttribute('data-src', testPath); newImg.setAttribute('data-src', testPath);
document.body.appendChild(newImg); document.body.appendChild(newImg);
newImg.scrollIntoView();
Promise.all([
setTimeout(() => { scrollIntoViewPromise(newImg),
expect(LazyLoader.loadImage).toHaveBeenCalled(); waitForAttributeChange(newImg, ['data-src', 'src']),
expect(newImg.getAttribute('src')).toBe(testPath); ])
expect(newImg).toHaveClass('js-lazy-loaded'); .then(() => {
done(); expect(LazyLoader.loadImage).toHaveBeenCalled();
}, 50); expect(newImg.getAttribute('src')).toBe(testPath);
expect(newImg).toHaveClass('js-lazy-loaded');
done();
})
.catch(done.fail);
}); });
it('should not alter normal images', function(done) { it('should not alter normal images', function(done) {
...@@ -167,13 +189,15 @@ describe('LazyLoader', function() { ...@@ -167,13 +189,15 @@ describe('LazyLoader', function() {
const testPath = `${TEST_HOST}/img/testimg.png`; const testPath = `${TEST_HOST}/img/testimg.png`;
newImg.setAttribute('src', testPath); newImg.setAttribute('src', testPath);
document.body.appendChild(newImg); document.body.appendChild(newImg);
newImg.scrollIntoView();
setTimeout(() => { scrollIntoViewPromise(newImg)
expect(LazyLoader.loadImage).not.toHaveBeenCalled(); .then(waitForPromises)
expect(newImg).not.toHaveClass('js-lazy-loaded'); .then(() => {
done(); expect(LazyLoader.loadImage).not.toHaveBeenCalled();
}, 50); 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 => { it('should not load dynamically added pictures if content observer is turned off', done => {
...@@ -184,13 +208,15 @@ describe('LazyLoader', function() { ...@@ -184,13 +208,15 @@ describe('LazyLoader', function() {
newImg.className = 'lazy'; newImg.className = 'lazy';
newImg.setAttribute('data-src', testPath); newImg.setAttribute('data-src', testPath);
document.body.appendChild(newImg); document.body.appendChild(newImg);
newImg.scrollIntoView();
setTimeout(() => { scrollIntoViewPromise(newImg)
expect(LazyLoader.loadImage).not.toHaveBeenCalled(); .then(waitForPromises)
expect(newImg).not.toHaveClass('js-lazy-loaded'); .then(() => {
done(); expect(LazyLoader.loadImage).not.toHaveBeenCalled();
}, 50); 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 => { it('should load dynamically added pictures if content observer is turned off and on again', done => {
...@@ -202,13 +228,17 @@ describe('LazyLoader', function() { ...@@ -202,13 +228,17 @@ describe('LazyLoader', function() {
newImg.className = 'lazy'; newImg.className = 'lazy';
newImg.setAttribute('data-src', testPath); newImg.setAttribute('data-src', testPath);
document.body.appendChild(newImg); document.body.appendChild(newImg);
newImg.scrollIntoView();
setTimeout(() => { Promise.all([
expect(LazyLoader.loadImage).toHaveBeenCalled(); scrollIntoViewPromise(newImg),
expect(newImg).toHaveClass('js-lazy-loaded'); waitForAttributeChange(newImg, ['data-src', 'src']),
done(); ])
}, 50); .then(() => {
expect(LazyLoader.loadImage).toHaveBeenCalled();
expect(newImg).toHaveClass('js-lazy-loaded');
done();
})
.catch(done.fail);
}); });
}); });
}); });
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment