loadjs
Version:
Tiny async loader for modern browsers
949 lines (747 loc) • 25.6 kB
JavaScript
/**
* loadjs tests
* @module test/tests.js
*/
var pathsLoaded = null, // file register
testEl = null,
assert = chai.assert,
expect = chai.expect;
describe('LoadJS tests', function () {
beforeEach(function () {
// reset register
pathsLoaded = {};
// reset loadjs dependencies
loadjs.reset();
});
// ==========================================================================
// JavaScript file loading tests
// ==========================================================================
describe('JavaScript file loading tests', function () {
it('should call success callback on valid path', function (done) {
loadjs(['assets/file1.js'], {
success: function () {
assert.equal(pathsLoaded['file1.js'], true);
done();
}
});
});
it('should call error callback on invalid path', function (done) {
loadjs(['assets/file-doesntexist.js'], {
success: function () {
throw "Executed success callback";
},
error: function (pathsNotFound) {
assert.equal(pathsNotFound.length, 1);
assert.equal(pathsNotFound[0], 'assets/file-doesntexist.js');
done();
}
});
});
it('should call before callback before embedding into document', function (done) {
var scriptTags = [];
loadjs(['assets/file1.js', 'assets/file2.js'], {
before: function (path, el) {
scriptTags.push({
path: path,
el: el
});
// add cross origin script for file2
if (path === 'assets/file2.js') {
el.crossOrigin = 'anonymous';
}
},
success: function () {
assert.equal(scriptTags[0].path, 'assets/file1.js');
assert.equal(scriptTags[1].path, 'assets/file2.js');
assert.equal(scriptTags[0].el.crossOrigin, undefined);
assert.equal(scriptTags[1].el.crossOrigin, 'anonymous');
done();
}
});
});
it('should bypass insertion if before returns `false`', function (done) {
loadjs(['assets/file1.js'], {
before: function (path, el) {
// append to body (instead of head)
document.body.appendChild(el);
// return `false` to bypass default DOM insertion
return false;
},
success: function () {
assert.equal(pathsLoaded['file1.js'], true);
// verify that file was added to body
var els = document.body.querySelectorAll('script'),
el;
for (var i = 0; i < els.length; i++) {
el = els[i];
if (el.src.indexOf('assets/file1.js') !== -1) done();
}
}
});
});
it('should call success callback on two valid paths', function (done) {
loadjs(['assets/file1.js', 'assets/file2.js'], {
success: function () {
assert.equal(pathsLoaded['file1.js'], true);
assert.equal(pathsLoaded['file2.js'], true);
done();
}
});
});
it('should call error callback on one invalid path', function (done) {
loadjs(['assets/file1.js', 'assets/file-doesntexist.js'], {
success: function () {
throw "Executed success callback";
},
error: function (pathsNotFound) {
assert.equal(pathsLoaded['file1.js'], true);
assert.equal(pathsNotFound.length, 1);
assert.equal(pathsNotFound[0], 'assets/file-doesntexist.js');
done();
}
});
});
it('should support async false', function (done) {
this.timeout(5000);
var numCompleted = 0,
numTests = 20,
paths = ['assets/asyncfalse1.js', 'assets/asyncfalse2.js'];
// run tests sequentially
var testFn = function (paths) {
// add cache busters
var pathsUncached = paths.slice(0);
pathsUncached[0] += '?_=' + Math.random();
pathsUncached[1] += '?_=' + Math.random();
loadjs(pathsUncached, {
success: function () {
var f1 = paths[0].replace('assets/', '');
var f2 = paths[1].replace('assets/', '');
// check load order
assert.isTrue(pathsLoaded[f1]);
assert.isFalse(pathsLoaded[f2]);
// increment tests
numCompleted += 1;
if (numCompleted === numTests) {
// exit
done();
} else {
// reset register
pathsLoaded = {};
// run test again
paths.reverse();
testFn(paths);
}
},
async: false
});
};
// run tests
testFn(paths);
});
it('should support multiple tries', function (done) {
loadjs('assets/file-numretries.js', {
error: function () {
// check number of scripts in document
var selector = 'script[src="assets/file-numretries.js"]',
scripts = document.querySelectorAll(selector);
if (scripts.length === 2) done();
},
numRetries: 1
});
});
// tests for browsers with/without module support
if (caniuseModule()) {
describe('module tests for browsers with module support', function () {
it('should support loading modules with "module!" modifier', function (done) {
loadjs(['module!assets/module1.js'], {
success: function () {
assert.equal(pathsLoaded['module1.js'], true);
done();
}
});
});
it('should call error callback on invalid module! path', function (done) {
loadjs(['module!assets/module-doesntexist.js'], {
success: function () {
throw "Executed success callback";
},
error: function (pathsNotFound) {
assert.equal(pathsNotFound.length, 1);
assert.equal(pathsNotFound[0], 'module!assets/module-doesntexist.js');
done();
}
});
});
it('should support bypassing loading files with "nomodule!" modifier', function (done) {
loadjs(['nomodule!assets/file1.js'], {
success: function () {
assert.equal(pathsLoaded['file1.js'], undefined);
done();
}
});
});
});
} else {
describe('module tests for browsers without module support', function () {
it('should support loading modules with "nomodule!" modifier', function (done) {
loadjs(['nomodule!assets/file1.js'], {
success: function () {
assert.equal(pathsLoaded['file1.js'], true);
done();
}
});
});
it('should call error callback on invalid nomodule! path', function (done) {
loadjs(['nomodule!assets/file-doesntexist.js'], {
success: function () {
throw "Executed success callback";
},
error: function (pathsNotFound) {
assert.equal(pathsNotFound.length, 1);
assert.equal(pathsNotFound[0], 'nomodule!assets/file-doesntexist.js');
done();
}
});
});
it('should support bypassing loading files with "module!" modifier', function (done) {
loadjs(['module!assets/module1.js'], {
success: function () {
assert.equal(pathsLoaded['module1.js'], undefined);
done();
}
});
});
});
}
// Un-'x' this for testing ad blocked scripts.
// Ghostery: Disallow "Google Adservices"
// AdBlock Plus: Add "www.googletagservices.com/tag/js/gpt.js" as a
// custom filter under Options
//
xit('it should report ad blocked scripts as missing', function (done) {
var s1 = 'https://www.googletagservices.com/tag/js/gpt.js',
s2 = 'https://munchkin.marketo.net/munchkin-beta.js';
loadjs([s1, s2, 'assets/file1.js'], {
success: function () {
throw new Error('Executed success callback');
},
error: function (pathsNotFound) {
assert.equal(pathsLoaded['file1.js'], true);
assert.equal(pathsNotFound.length, 2);
assert.equal(pathsNotFound[0], s1);
assert.equal(pathsNotFound[1], s2);
done();
}
});
});
});
// ==========================================================================
// CSS file loading tests
// ==========================================================================
describe('CSS file loading tests', function () {
before(function () {
// add test div to body for css tests
testEl = document.createElement('div');
testEl.className = 'test-div mui-container';
testEl.style.display = 'inline-block';
document.body.appendChild(testEl);
});
afterEach(function () {
var els = document.getElementsByTagName('link'),
i = els.length,
el;
// iteratete through stylesheets
while (i--) {
el = els[i];
// remove test stylesheets
if (el.href.indexOf('mocha.css') === -1) {
el.parentNode.removeChild(el);
}
}
});
it('should load one file', function (done) {
loadjs(['assets/file1.css'], {
success: function () {
assert.equal(testEl.offsetWidth, 100);
done();
}
});
});
it('should load multiple files', function (done) {
loadjs(['assets/file1.css', 'assets/file2.css'], {
success: function () {
assert.equal(testEl.offsetWidth, 200);
done();
}
});
});
it('should call error callback on one invalid path', function (done) {
loadjs(['assets/file1.css', 'assets/file-doesntexist.css'], {
success: function () {
throw new Error('Executed success callback');
},
error: function (pathsNotFound) {
assert.equal(testEl.offsetWidth, 100);
assert.equal(pathsNotFound.length, 1);
assert.equal(pathsNotFound[0], 'assets/file-doesntexist.css');
done();
}
});
});
it('should support mix of css and js', function (done) {
loadjs(['assets/file1.css', 'assets/file1.js'], {
success: function () {
assert.equal(pathsLoaded['file1.js'], true);
assert.equal(testEl.offsetWidth, 100);
done();
}
});
});
it('should support forced "css!" files', function (done) {
loadjs(['css!assets/file1.css'], {
success: function () {
// loop through files
var els = document.getElementsByTagName('link'),
i = els.length,
el;
while (i--) {
if (els[i].href.indexOf('file1.css') !== -1) done();
}
}
});
});
it('supports urls with query arguments', function (done) {
loadjs(['assets/file1.css?x=x'], {
success: function () {
assert.equal(testEl.offsetWidth, 100);
done();
}
});
});
it('supports urls with anchor tags', function (done) {
loadjs(['assets/file1.css#anchortag'], {
success: function () {
assert.equal(testEl.offsetWidth, 100);
done();
}
});
});
it('supports urls with query arguments and anchor tags', function (done) {
loadjs(['assets/file1.css?x=x#anchortag'], {
success: function () {
assert.equal(testEl.offsetWidth, 100);
done();
}
});
});
it('should load external css files', function (done) {
this.timeout(0);
loadjs(['//cdn.muicss.com/mui-0.6.8/css/mui.min.css'], {
success: function () {
var styleObj = getComputedStyle(testEl);
assert.equal(styleObj.getPropertyValue('padding-left'), '15px');
done();
}
});
});
it('should call error on missing external file', function (done) {
this.timeout(0);
loadjs(['//cdn.muicss.com/mui-0.6.8/css/mui-doesnotexist.min.css'], {
success: function () {
throw new Error('Executed success callback');
},
error: function (pathsNotFound) {
var styleObj = getComputedStyle(testEl);
assert.equal(styleObj.getPropertyValue('padding-left'), '0px');
assert.equal(pathsNotFound.length, 1);
done();
}
});
});
// teardown
return after(function () {
// remove test div
testEl.parentNode.removeChild(testEl);
});
});
// ==========================================================================
// Image file loading tests
// ==========================================================================
describe('Image file loading tests', function () {
function assertLoaded(src) {
// loop through images
var imgs = document.getElementsByTagName('img');
Array.prototype.slice.call(imgs).forEach(function (img) {
// verify image was loaded
if (img.src === src) assert.equal(img.naturalWidth > 0, true);
});
}
function assertNotLoaded(src) {
// loop through images
var imgs = document.getElementsByTagName('img');
Array.prototype.slice.call(imgs).forEach(function (img) {
// fail if image was loaded
if (img.src === src) assert.equal(img.naturalWidth, 0);
});
}
it('should load one file', function (done) {
loadjs(['assets/flash.png'], {
success: function () {
assertLoaded('assets/flash.png');
done();
}
});
});
it('should load multiple files', function (done) {
loadjs(['assets/flash.png', 'assets/flash.jpg'], {
success: function () {
assertLoaded('assets/flash.png');
assertLoaded('assets/flash.jpg');
done();
}
});
});
it('detects png|gif|jpg|svg extensions', function (done) {
let files = [
'assets/flash.png',
'assets/flash.gif',
'assets/flash.jpg',
'assets/flash.svg'
];
loadjs(files, function () {
files.forEach(function (file) {
assertLoaded(file);
});
done();
});
});
it('supports urls with query arguments', function (done) {
var src = 'assets/flash.png?' + Math.random();
loadjs([src], {
success: function () {
assertLoaded(src);
done();
}
});
});
it('supports urls with anchor tags', function (done) {
var src = 'assets/flash.png#' + Math.random();
loadjs([src], {
success: function () {
assertLoaded(src);
done();
}
});
});
it('supports urls with query arguments and anchor tags', function (done) {
var src = 'assets/flash.png';
src += '?' + Math.random();
src += '#' + Math.random();
loadjs([src], {
success: function () {
assertLoaded(src);
done();
}
});
});
it('should support forced "img!" files', function (done) {
var src = 'assets/flash.png?' + Math.random();
loadjs(['img!' + src], {
success: function () {
assertLoaded(src);
done();
}
});
});
it('should call error callback on one invalid path', function (done) {
var src1 = 'assets/flash.png?' + Math.random(),
src2 = 'assets/flash-doesntexist.png?' + Math.random();
loadjs(['img!' + src1, 'img!' + src2], {
success: function () {
throw new Error('Executed success callback');
},
error: function (pathsNotFound) {
assert.equal(pathsNotFound.length, 1);
assertLoaded(src1);
assertNotLoaded(src2);
done();
}
});
});
it('should support mix of img and js', function (done) {
var src = 'assets/flash.png?' + Math.random();
loadjs(['img!' + src, 'assets/file1.js'], {
success: function () {
assert.equal(pathsLoaded['file1.js'], true);
assertLoaded(src);
done();
}
});
});
it('should load external img files', function (done) {
this.timeout(0);
var src = 'https://www.muicss.com/static/images/mui-logo.png?';
src += Math.random();
loadjs(['img!' + src], {
success: function () {
assertLoaded(src);
done();
}
});
});
it('should call error on missing external file', function (done) {
this.timeout(0);
var src = 'https://www.muicss.com/static/images/';
src += 'mui-logo-doesntexist.png?' + Math.random();
loadjs(['img!' + src], {
success: function () {
throw new Error('Executed success callback');
},
error: function (pathsNotFound) {
assertNotLoaded(src);
done();
}
});
});
if (caniuseWebp()) {
describe('tests for browsers with webp support', function () {
it('detects webp extensions', function (done) {
loadjs('assets/flash.webp', function () {
assertLoaded('assets/flash.webp');
done();
});
});
});
} else {
describe('tests for browsers without webp support', function () {
it('executes error callback when browser loads webp file', function (done) {
loadjs('assets/flash.webp', {
error: function (pathsNotFound) {
assertNotLoaded('assets/flash.webp');
done();
}
});
});
});
}
});
// ==========================================================================
// API tests
// ==========================================================================
describe('API tests', function () {
it('should throw an error if bundle is already defined', function () {
// define bundle
loadjs(['assets/file1.js'], 'bundle');
// define bundle again
var fn = function () {
loadjs(['assets/file1.js'], 'bundle');
};
expect(fn).to.throw("LoadJS");
});
it('should create a bundle id and a callback inline', function (done) {
loadjs(['assets/file1.js', 'assets/file2.js'], 'bundle', {
success: function () {
assert.equal(pathsLoaded['file1.js'], true);
assert.equal(pathsLoaded['file2.js'], true);
done();
}
});
});
it('should chain loadjs object', function (done) {
function bothDone() {
if (pathsLoaded['file1.js'] && pathsLoaded['file2.js']) done();
}
// define bundles
loadjs('assets/file1.js', 'bundle1');
loadjs('assets/file2.js', 'bundle2');
loadjs
.ready('bundle1', {
success: function () {
assert.equal(pathsLoaded['file1.js'], true);
bothDone();
}
})
.ready('bundle2', {
success: function () {
assert.equal(pathsLoaded['file2.js'], true);
bothDone();
}
});
});
it('should handle multiple dependencies', function (done) {
loadjs('assets/file1.js', 'bundle1');
loadjs('assets/file2.js', 'bundle2');
loadjs.ready(['bundle1', 'bundle2'], {
success: function () {
assert.equal(pathsLoaded['file1.js'], true);
assert.equal(pathsLoaded['file2.js'], true);
done();
}
});
});
it('should error on missing depdendencies', function (done) {
loadjs('assets/file1.js', 'bundle1');
loadjs('assets/file-doesntexist.js', 'bundle2');
loadjs.ready(['bundle1', 'bundle2'], {
success: function () {
throw "Executed success callback";
},
error: function (depsNotFound) {
assert.equal(pathsLoaded['file1.js'], true);
assert.equal(depsNotFound.length, 1);
assert.equal(depsNotFound[0], 'bundle2');
done();
}
});
});
it('should execute callbacks on .done()', function (done) {
// add handler
loadjs.ready('plugin', {
success: function () {
done();
}
});
// execute done
loadjs.done('plugin');
});
it('should execute callbacks created after .done()', function (done) {
// execute done
loadjs.done('plugin');
// add handler
loadjs.ready('plugin', {
success: function () {
done();
}
});
});
it('should define bundles', function (done) {
// define bundle
loadjs(['assets/file1.js', 'assets/file2.js'], 'bundle');
// use 1 second delay to let files load
setTimeout(function () {
loadjs.ready('bundle', {
success: function () {
assert.equal(pathsLoaded['file1.js'], true);
assert.equal(pathsLoaded['file2.js'], true);
done();
}
});
}, 1000);
});
it('should allow bundle callbacks before definitions', function (done) {
// define callback
loadjs.ready('bundle', {
success: function () {
assert.equal(pathsLoaded['file1.js'], true);
assert.equal(pathsLoaded['file2.js'], true);
done();
}
});
// use 1 second delay
setTimeout(function () {
loadjs(['assets/file1.js', 'assets/file2.js'], 'bundle');
}, 1000);
});
it('should reset dependencies statuses', function () {
loadjs(['assets/file1.js'], 'cleared');
loadjs.reset();
// define bundle again
var fn = function () {
loadjs(['assets/file1.js'], 'cleared');
};
expect(fn).not.to.throw("LoadJS");
});
it('should indicate if bundle has already been defined', function () {
loadjs(['assets/file1/js'], 'bundle1');
assert.equal(loadjs.isDefined('bundle1'), true);
assert.equal(loadjs.isDefined('bundleXX'), false);
});
it('should accept success callback functions to loadjs()', function (done) {
loadjs('assets/file1.js', function () {
done();
});
});
it('should accept success callback functions to .ready()', function (done) {
loadjs.done('plugin');
loadjs.ready('plugin', function () {
done();
});
});
if (caniusePromise()) {
describe('tests for browsers with Promise support', function () {
it('should return Promise object if returnPromise is true', function () {
var prom = loadjs(['assets/file1.js'], { returnPromise: true });
// verify that response object is a Promise
assert.equal(prom instanceof Promise, true);
});
it('Promise object should support resolutions', function (done) {
var prom = loadjs(['assets/file1.js'], { returnPromise: true });
prom.then(function () {
assert.equal(pathsLoaded['file1.js'], true);
done();
});
});
it('Promise object should support rejections', function (done) {
var prom = loadjs(['assets/file-doesntexist.js'], { returnPromise: true });
prom.then(
function () { },
function (pathsNotFound) {
assert.equal(pathsNotFound.length, 1);
assert.equal(pathsNotFound[0], 'assets/file-doesntexist.js');
done();
}
);
});
it('Promise object should support catches', function (done) {
var prom = loadjs(['assets/file-doesntexist.js'], { returnPromise: true });
prom
.catch(function (pathsNotFound) {
assert.equal(pathsNotFound.length, 1);
assert.equal(pathsNotFound[0], 'assets/file-doesntexist.js');
done();
});
});
it('supports Promises and success callbacks', function (done) {
var numCompleted = 0;
function completedFn() {
numCompleted += 1;
if (numCompleted === 2) done();
};
var prom = loadjs('assets/file1.js', {
success: completedFn,
returnPromise: true
});
prom.then(completedFn);
});
it('supports Promises and bundle ready events', function (done) {
var numCompleted = 0;
function completedFn() {
numCompleted += 1;
if (numCompleted === 2) done();
};
loadjs('assets/file1.js', 'bundle1', { returnPromise: true })
.then(completedFn);
loadjs.ready('bundle1', completedFn);
});
});
}
});
});
// ==========================================================================
// Utilities
// ==========================================================================
// https://stackoverflow.com/questions/5573096/detecting-webp-support
function caniuseWebp() {
var elem = document.createElement('canvas');
if (!!(elem.getContext && elem.getContext('2d'))) {
return elem.toDataURL('image/webp').indexOf('data:image/webp') == 0;
} else {
return false;
}
}
function caniuseModule() {
return 'noModule' in document.createElement('script');
}
function caniusePromise() {
return typeof Promise !== "undefined";
}