QUnit Test Suite Source
You can view the full source code for index.html, cli.js and qunit.js below. The unit tests can be run online in-browser at agjVersionless.agjjQuery.org/tests/index.html.
index.html
<!DOCTYPE html>
<!--/**
* Copyright (c) 2026 Andrew G. Johnson <andrew@andrewgjohnson.com>
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the “Software”), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
* @file The in-browser runner for the agjVersionless plugin test suite.
* @copyright 2026 Andrew G. Johnson <andrew@andrewgjohnson.com>
* @license MIT
* @see {@link https://github.com/andrewgjohnson/agjVersionless GitHub Repository}
* @see {@link https://agjVersionless.agjjQuery.org/ Online Documentation}
* @author Andrew G. Johnson <andrew@andrewgjohnson.com>
* @version 1.0.0
*/-->
<html lang="en">
<head>
<meta charset="utf-8" />
<title>agjVersionless QUnit Test Suite</title>
<link rel="canonical" href="https://agjVersionless.agjjQuery.org/tests/index.html" />
<link rel="shortcut icon" href="https://github.githubassets.com/favicon.ico" />
<link rel="icon" href="https://github.githubassets.com/favicon.ico" />
<link rel="stylesheet" href="https://code.jquery.com/qunit/qunit-2.26.0.css" />
<script type="text/javascript" src="https://code.jquery.com/qunit/qunit-2.26.0.js"></script>
</head>
<body>
<div id="qunit"></div>
<div id="qunit-fixture"></div>
<script type="text/javascript">
QUnit.config.autostart = false;
(function() {
// Define the URLs of the various supported versions of jQuery
var jQueryUrls = {
'default': 'https://code.jquery.com/jquery-4.0.0.min.js',
'1.12.4': 'https://code.jquery.com/jquery-1.12.4.min.js',
'2.2.4': 'https://code.jquery.com/jquery-2.2.4.min.js',
'3.7.1': 'https://code.jquery.com/jquery-3.7.1.min.js',
'4.0.0': 'https://code.jquery.com/jquery-4.0.0.min.js'
};
// Define the user-controlled options for the QUnit in-browser experience
QUnit.config.urlConfig.push({
id: 'minified',
label: 'Use minified',
tooltip: 'Whether or not to use the plugin’s minified Javascript'
});
QUnit.config.urlConfig.push({
id: 'jquery',
label: 'jQuery version',
tooltip: 'Which version of jQuery to test with',
value: (function() {
// Generate an array of jQuery versions based on the previously defined jQueryUrls object
var versions = [];
for (var version in jQueryUrls) {
if (Object.prototype.hasOwnProperty.call(jQueryUrls, version) && version !== 'default') {
versions.push(version);
}
}
return versions;
})()
});
// Function to dynamically load Javascript
var loadScript = function(src, callback) {
var script = document.createElement('script');
script.src = src;
script.onload = callback;
document.head.appendChild(script);
};
// Load the selected jQuery version first
var jQueryUrl = jQueryUrls[QUnit.urlParams.jquery || 'default'];
loadScript(jQueryUrl, function() {
// Load the agjVersionless plugin after jQuery
var pluginUrl;
if (QUnit.urlParams.minified === 'true') {
pluginUrl = '../distribution/jquery.agjVersionless.min.js';
} else {
pluginUrl = '../distribution/jquery.agjVersionless.js';
}
loadScript(pluginUrl, function() {
// Load the QUnit test suite after agjVersionless
loadScript('qunit.js', function() {
// Start the QUnit test suite once all the other scripts have loaded
QUnit.start();
});
});
});
})();
</script>
</body>
</html>
cli.js
/**
* Copyright (c) 2026 Andrew G. Johnson <andrew@andrewgjohnson.com>
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
* @file The CLI runner for the agjVersionless plugin test suite.
* @copyright 2026 Andrew G. Johnson <andrew@andrewgjohnson.com>
* @license MIT
* @see {@link https://github.com/andrewgjohnson/agjVersionless GitHub Repository}
* @see {@link https://agjVersionless.agjjQuery.org/ Online Documentation}
* @author Andrew G. Johnson <andrew@andrewgjohnson.com>
* @version 1.0.0
*/
const {JSDOM} = require('jsdom');
const {window} = new JSDOM(
'<!DOCTYPE html>' +
'<html>' +
'<head>' +
'<meta charset="utf-8" />' +
'<title>agjVersionless QUnit Test Suite</title>' +
'</head>' +
'<body>' +
'<div id="qunit"></div>' +
'<div id="qunit-fixture"></div>' +
'</body>' +
'</html>',
);
const {document, navigator} = window;
global.window = window;
global.document = document;
global.navigator = navigator;
const jQuery = require('jquery');
global.jQuery = jQuery;
// The agjVersionless plugin and QUnit test suite
require('../distribution/jquery.agjVersionless.js');
require('./qunit.js');
qunit.js
/**
* Copyright (c) 2026 Andrew G. Johnson <andrew@andrewgjohnson.com>
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
* @file The QUnit test suite for the agjVersionless jQuery plugin.
* @copyright 2026 Andrew G. Johnson <andrew@andrewgjohnson.com>
* @license MIT
* @see {@link https://github.com/andrewgjohnson/agjVersionless GitHub Repository}
* @see {@link https://agjVersionless.agjjQuery.org/ Online Documentation}
* @author Andrew G. Johnson <andrew@andrewgjohnson.com>
* @version 1.0.0
*/
QUnit.on('runEnd', details => {
const formatNumber = number => new Intl.NumberFormat('en-CA').format(number);
if (console && console.log) {
const assertions = {total: 0, passed: 0, skipped: 0, todo: 0};
if (details.childSuites) {
for (let i = 0; i < details.childSuites.length; i++) {
if (details.childSuites[i].tests) {
for (let j = 0; j < details.childSuites[i].tests.length; j++) {
if (details.childSuites[i].tests[j].assertions) {
for (
let k = 0;
k < details.childSuites[i].tests[j].assertions.length;
k++
) {
assertions.total++;
const assertion = details.childSuites[i].tests[j].assertions[k];
if (
typeof assertion.passed !== 'undefined' &&
assertion.passed === true
) {
assertions.passed++;
}
if (
typeof assertion.skipped !== 'undefined' &&
assertion.skipped === true
) {
assertions.skipped++;
}
if (
typeof assertion.todo !== 'undefined' &&
assertion.todo === true
) {
assertions.todo++;
}
}
}
}
}
}
}
const colours = {
reset: '\x1b[0m',
black: '\x1b[30m',
red: '\x1b[31m',
green: '\x1b[32m',
yellow: '\x1b[33m',
blue: '\x1b[34m',
purple: '\x1b[35m',
cyan: '\x1b[36m',
white: '\x1b[37m',
};
console.log('');
console.log(colours.green + 'ASSERTIONS' + colours.reset);
console.log(
colours.white +
'# pass ' +
formatNumber(assertions.passed) +
colours.reset,
);
console.log(
colours.yellow +
'# skip ' +
formatNumber(assertions.skipped) +
colours.reset,
);
console.log(
colours.cyan + '# todo ' + formatNumber(assertions.todo) + colours.reset,
);
console.log(
colours.red +
'# fail ' +
formatNumber(
assertions.total -
assertions.passed -
assertions.skipped -
assertions.todo,
) +
colours.reset,
);
console.log('');
console.log(colours.green + 'RUNTIME' + colours.reset);
if (details.runtime > 1000 * 60 * 60) {
const hours = Math.max(1, Math.round(details.runtime / (1000 * 60 * 60)));
console.log(
colours.white +
'# ' +
formatNumber(hours) +
' ' +
(hours === 1 ? 'hour' : 'hours') +
colours.reset,
);
} else if (details.runtime > 1000 * 60) {
const minutes = Math.max(1, Math.round(details.runtime / (1000 * 60)));
console.log(
colours.white +
'# ' +
minutes +
' ' +
(minutes === 1 ? 'minute' : 'minutes') +
colours.reset,
);
} else if (details.runtime > 1000) {
const seconds = Math.max(1, Math.round(details.runtime / 1000));
console.log(
colours.white +
'# ' +
seconds +
' ' +
(seconds === 1 ? 'second' : 'seconds') +
colours.reset,
);
} else {
const milliseconds = details.runtime;
console.log(
colours.white +
'# ' +
milliseconds +
' ' +
(milliseconds === 1 ? 'millisecond' : 'milliseconds') +
colours.reset,
);
}
}
});
QUnit.module('agjVersionless');
QUnit.test('$.agjVersionless exists', assert => {
assert.strictEqual(
typeof jQuery.agjVersionless,
'object',
'$.agjVersionless should be an object',
);
});
QUnit.test('$.agjVersionless.bind exists', assert => {
assert.strictEqual(
typeof jQuery.agjVersionless.bind,
'function',
'$.agjVersionless.bind should be a function',
);
});
QUnit.test('$.agjVersionless.unbind exists', assert => {
assert.strictEqual(
typeof jQuery.agjVersionless.unbind,
'function',
'$.agjVersionless.unbind should be a function',
);
});
QUnit.test('$.agjVersionless.bind binds an event handler', assert => {
const element = jQuery('<div></div>');
let triggered = false;
jQuery.agjVersionless.bind(element, 'click', () => {
triggered = true;
});
element.trigger('click');
assert.true(triggered, 'Event handler should have been called after trigger');
});
QUnit.test('$.agjVersionless.bind binds an event handler with data', assert => {
const element = jQuery('<div></div>');
let receivedData = null;
const data = {key: 'value'};
jQuery.agjVersionless.bind(element, 'click', data, event => {
receivedData = event.data;
});
element.trigger('click');
assert.deepEqual(
receivedData,
{key: 'value'},
'Event handler should have received the data object',
);
});
QUnit.test('$.agjVersionless.unbind unbinds an event handler', assert => {
const element = jQuery('<div></div>');
let callCount = 0;
const handler = function () {
callCount++;
};
element.on('click', handler);
element.trigger('click');
jQuery.agjVersionless.unbind(element, 'click');
element.trigger('click');
assert.strictEqual(
callCount,
1,
'Event handler should have been called exactly once before unbind',
);
});
QUnit.test('$.agjVersionless.bind returns the element for chaining', assert => {
const element = jQuery('<div></div>');
const result = jQuery.agjVersionless.bind(element, 'click', () => {});
assert.ok(
result instanceof jQuery,
'bind should return a jQuery object for chaining',
);
});
QUnit.test(
'$.agjVersionless.unbind returns the element for chaining',
assert => {
const element = jQuery('<div></div>');
const result = jQuery.agjVersionless.unbind(element, 'click');
assert.ok(
result instanceof jQuery,
'unbind should return a jQuery object for chaining',
);
},
);
QUnit.test('$.fn.agjVersionless exists', assert => {
const element = jQuery('<div></div>');
assert.strictEqual(
typeof element.agjVersionless,
'object',
'$.fn.agjVersionless should be an object',
);
});
QUnit.test('$.fn.agjVersionless.bind exists', assert => {
const element = jQuery('<div></div>');
assert.strictEqual(
typeof element.agjVersionless.bind,
'function',
'$.fn.agjVersionless.bind should be a function',
);
});
QUnit.test('$.fn.agjVersionless.unbind exists', assert => {
const element = jQuery('<div></div>');
assert.strictEqual(
typeof element.agjVersionless.unbind,
'function',
'$.fn.agjVersionless.unbind should be a function',
);
});
QUnit.test('$.fn.agjVersionless.bind binds an event handler', assert => {
const element = jQuery('<div></div>');
let triggered = false;
element.agjVersionless.bind('click', () => {
triggered = true;
});
element.trigger('click');
assert.true(triggered, 'Event handler should have been called after trigger');
});
QUnit.test(
'$.fn.agjVersionless.bind binds an event handler with data',
assert => {
const element = jQuery('<div></div>');
let receivedData = null;
const data = {key: 'value'};
element.agjVersionless.bind('click', data, event => {
receivedData = event.data;
});
element.trigger('click');
assert.deepEqual(
receivedData,
{key: 'value'},
'Event handler should have received the data object',
);
},
);
QUnit.test('$.fn.agjVersionless.unbind unbinds an event handler', assert => {
const element = jQuery('<div></div>');
let callCount = 0;
const handler = function () {
callCount++;
};
element.on('click', handler);
element.trigger('click');
element.agjVersionless.unbind('click');
element.trigger('click');
assert.strictEqual(
callCount,
1,
'Event handler should have been called exactly once before unbind',
);
});
QUnit.test(
'$.fn.agjVersionless.bind returns the element for chaining',
assert => {
const element = jQuery('<div></div>');
const result = element.agjVersionless.bind('click', () => {});
assert.ok(
result instanceof jQuery,
'bind should return a jQuery object for chaining',
);
},
);
QUnit.test(
'$.fn.agjVersionless.unbind returns the element for chaining',
assert => {
const element = jQuery('<div></div>');
const result = element.agjVersionless.unbind('click');
assert.ok(
result instanceof jQuery,
'unbind should return a jQuery object for chaining',
);
},
);
QUnit.test('$.fn.agjVersionless.on exists', assert => {
const element = jQuery('<div></div>');
assert.strictEqual(
typeof element.agjVersionless.on,
'function',
'$.fn.agjVersionless.on should be a function',
);
});
QUnit.test('$.fn.agjVersionless.off exists', assert => {
const element = jQuery('<div></div>');
assert.strictEqual(
typeof element.agjVersionless.off,
'function',
'$.fn.agjVersionless.off should be a function',
);
});
QUnit.test('$.fn.agjVersionless.on binds an event handler', assert => {
const element = jQuery('<div></div>');
let triggered = false;
element.agjVersionless.on('click', () => {
triggered = true;
});
element.trigger('click');
assert.true(triggered, 'Event handler should have been called after trigger');
});
QUnit.test(
'$.fn.agjVersionless.on binds an event handler with data',
assert => {
const element = jQuery('<div></div>');
let receivedData = null;
const data = {key: 'value'};
element.agjVersionless.on('click', data, event => {
receivedData = event.data;
});
element.trigger('click');
assert.deepEqual(
receivedData,
{key: 'value'},
'Event handler should have received the data object',
);
},
);
QUnit.test('$.fn.agjVersionless.off unbinds an event handler', assert => {
const element = jQuery('<div></div>');
let callCount = 0;
const handler = function () {
callCount++;
};
element.on('click', handler);
element.trigger('click');
element.agjVersionless.off('click');
element.trigger('click');
assert.strictEqual(
callCount,
1,
'Event handler should have been called exactly once before off',
);
});
QUnit.test(
'$.fn.agjVersionless.on returns the element for chaining',
assert => {
const element = jQuery('<div></div>');
const result = element.agjVersionless.on('click', () => {});
assert.ok(
result instanceof jQuery,
'on should return a jQuery object for chaining',
);
},
);
QUnit.test(
'$.fn.agjVersionless.off returns the element for chaining',
assert => {
const element = jQuery('<div></div>');
const result = element.agjVersionless.off('click');
assert.ok(
result instanceof jQuery,
'off should return a jQuery object for chaining',
);
},
);