diff --git a/js_tests/admin/DateTimeShortcuts.test.js b/js_tests/admin/DateTimeShortcuts.test.js index e993090233..5d5b12ba60 100644 --- a/js_tests/admin/DateTimeShortcuts.test.js +++ b/js_tests/admin/DateTimeShortcuts.test.js @@ -1,10 +1,10 @@ -/* global module, test, DateTimeShortcuts */ +/* global QUnit, DateTimeShortcuts */ /* eslint global-strict: 0, strict: 0 */ 'use strict'; -module('admin.DateTimeShortcuts'); +QUnit.module('admin.DateTimeShortcuts'); -test('init', function(assert) { +QUnit.test('init', function(assert) { var $ = django.jQuery; var dateField = $('
'); diff --git a/js_tests/admin/RelatedObjectLookups.test.js b/js_tests/admin/RelatedObjectLookups.test.js index 83071edd43..9ec1eed102 100644 --- a/js_tests/admin/RelatedObjectLookups.test.js +++ b/js_tests/admin/RelatedObjectLookups.test.js @@ -1,16 +1,16 @@ -/* global module, test, id_to_windowname, +/* global QUnit, id_to_windowname, windowname_to_id */ /* eslint global-strict: 0, strict: 0 */ 'use strict'; -module('admin.RelatedObjectLookups'); +QUnit.module('admin.RelatedObjectLookups'); -test('id_to_windowname', function(assert) { +QUnit.test('id_to_windowname', function(assert) { assert.equal(id_to_windowname('.test'), '__dot__test'); assert.equal(id_to_windowname('misc-test'), 'misc__dash__test'); }); -test('windowname_to_id', function(assert) { +QUnit.test('windowname_to_id', function(assert) { assert.equal(windowname_to_id('__dot__test'), '.test'); assert.equal(windowname_to_id('misc__dash__test'), 'misc-test'); }); diff --git a/js_tests/admin/SelectBox.test.js b/js_tests/admin/SelectBox.test.js index cb0259d12c..23c057c364 100644 --- a/js_tests/admin/SelectBox.test.js +++ b/js_tests/admin/SelectBox.test.js @@ -1,17 +1,17 @@ -/* global module, test, SelectBox */ +/* global QUnit, SelectBox */ /* eslint global-strict: 0, strict: 0 */ 'use strict'; -module('admin.SelectBox'); +QUnit.module('admin.SelectBox'); -test('init: no options', function(assert) { +QUnit.test('init: no options', function(assert) { var $ = django.jQuery; $('').appendTo('#qunit-fixture'); SelectBox.init('id'); assert.equal(SelectBox.cache.id.length, 0); }); -test('filter', function(assert) { +QUnit.test('filter', function(assert) { var $ = django.jQuery; $('').appendTo('#qunit-fixture'); $('').appendTo('#id'); diff --git a/js_tests/admin/SelectFilter2.test.js b/js_tests/admin/SelectFilter2.test.js index 1aa97958cb..c000584f48 100644 --- a/js_tests/admin/SelectFilter2.test.js +++ b/js_tests/admin/SelectFilter2.test.js @@ -1,10 +1,10 @@ -/* global module, test, SelectFilter */ +/* global QUnit, SelectFilter */ /* eslint global-strict: 0, strict: 0 */ 'use strict'; -module('admin.SelectFilter2'); +QUnit.module('admin.SelectFilter2'); -test('init', function(assert) { +QUnit.test('init', function(assert) { var $ = django.jQuery; $('
').appendTo('#qunit-fixture'); $('').appendTo('#id'); diff --git a/js_tests/admin/actions.test.js b/js_tests/admin/actions.test.js index d26d16328c..3ff8709ec6 100644 --- a/js_tests/admin/actions.test.js +++ b/js_tests/admin/actions.test.js @@ -1,8 +1,8 @@ -/* global module, test */ +/* global QUnit */ /* eslint global-strict: 0, strict: 0 */ 'use strict'; -module('admin.actions', { +QUnit.module('admin.actions', { beforeEach: function() { // Number of results shown on page /* eslint-disable */ @@ -16,7 +16,7 @@ module('admin.actions', { } }); -test('check', function(assert) { +QUnit.test('check', function(assert) { var $ = django.jQuery; assert.notOk($('.action-select').is(':checked')); $('#action-toggle').click(); diff --git a/js_tests/admin/core.test.js b/js_tests/admin/core.test.js index 7f0a4d6fda..eaf882dc4b 100644 --- a/js_tests/admin/core.test.js +++ b/js_tests/admin/core.test.js @@ -1,63 +1,63 @@ -/* global module, test */ +/* global QUnit */ /* eslint global-strict: 0, strict: 0 */ 'use strict'; -module('admin.core'); +QUnit.module('admin.core'); -test('Date.getTwelveHours', function(assert) { +QUnit.test('Date.getTwelveHours', function(assert) { assert.equal(new Date(2011, 0, 1, 0, 0).getTwelveHours(), 12, '0:00'); assert.equal(new Date(2011, 0, 1, 11, 0).getTwelveHours(), 11, '11:00'); assert.equal(new Date(2011, 0, 1, 16, 0).getTwelveHours(), 4, '16:00'); }); -test('Date.getTwoDigitMonth', function(assert) { +QUnit.test('Date.getTwoDigitMonth', function(assert) { assert.equal(new Date(2011, 0, 1).getTwoDigitMonth(), '01', 'jan 1'); assert.equal(new Date(2011, 9, 1).getTwoDigitMonth(), '10', 'oct 1'); }); -test('Date.getTwoDigitDate', function(assert) { +QUnit.test('Date.getTwoDigitDate', function(assert) { assert.equal(new Date(2011, 0, 1).getTwoDigitDate(), '01', 'jan 1'); assert.equal(new Date(2011, 0, 15).getTwoDigitDate(), '15', 'jan 15'); }); -test('Date.getTwoDigitTwelveHour', function(assert) { +QUnit.test('Date.getTwoDigitTwelveHour', function(assert) { assert.equal(new Date(2011, 0, 1, 0, 0).getTwoDigitTwelveHour(), '12', '0:00'); assert.equal(new Date(2011, 0, 1, 4, 0).getTwoDigitTwelveHour(), '04', '4:00'); assert.equal(new Date(2011, 0, 1, 22, 0).getTwoDigitTwelveHour(), '10', '22:00'); }); -test('Date.getTwoDigitHour', function(assert) { +QUnit.test('Date.getTwoDigitHour', function(assert) { assert.equal(new Date(2014, 6, 1, 9, 0).getTwoDigitHour(), '09', '9:00 am is 09'); assert.equal(new Date(2014, 6, 1, 11, 0).getTwoDigitHour(), '11', '11:00 am is 11'); }); -test('Date.getTwoDigitMinute', function(assert) { +QUnit.test('Date.getTwoDigitMinute', function(assert) { assert.equal(new Date(2014, 6, 1, 0, 5).getTwoDigitMinute(), '05', '12:05 am is 05'); assert.equal(new Date(2014, 6, 1, 0, 15).getTwoDigitMinute(), '15', '12:15 am is 15'); }); -test('Date.getTwoDigitSecond', function(assert) { +QUnit.test('Date.getTwoDigitSecond', function(assert) { assert.equal(new Date(2014, 6, 1, 0, 0, 2).getTwoDigitSecond(), '02', '12:00:02 am is 02'); assert.equal(new Date(2014, 6, 1, 0, 0, 20).getTwoDigitSecond(), '20', '12:00:20 am is 20'); }); -test('Date.getHourMinute', function(assert) { +QUnit.test('Date.getHourMinute', function(assert) { assert.equal(new Date(2014, 6, 1, 11, 0).getHourMinute(), '11:00', '11:00 am is 11:00'); assert.equal(new Date(2014, 6, 1, 13, 25).getHourMinute(), '13:25', '1:25 pm is 13:25'); }); -test('Date.getHourMinuteSecond', function(assert) { +QUnit.test('Date.getHourMinuteSecond', function(assert) { assert.equal(new Date(2014, 6, 1, 11, 0, 0).getHourMinuteSecond(), '11:00:00', '11:00 am is 11:00:00'); assert.equal(new Date(2014, 6, 1, 17, 45, 30).getHourMinuteSecond(), '17:45:30', '5:45:30 pm is 17:45:30'); }); -test('Date.strftime', function(assert) { +QUnit.test('Date.strftime', function(assert) { var date = new Date(2014, 6, 1, 11, 0, 5); assert.equal(date.strftime('%Y-%m-%d %H:%M:%S'), '2014-07-01 11:00:05'); assert.equal(date.strftime('%B %d, %Y'), 'July 01, 2014'); }); -test('String.strptime', function(assert) { +QUnit.test('String.strptime', function(assert) { // Use UTC functions for extracting dates since the calendar uses them as // well. Month numbering starts with 0 (January). var firstParsedDate = '1988-02-26'.strptime('%Y-%m-%d'); diff --git a/js_tests/admin/inlines.test.js b/js_tests/admin/inlines.test.js index b18b266024..2ff6a14577 100644 --- a/js_tests/admin/inlines.test.js +++ b/js_tests/admin/inlines.test.js @@ -1,8 +1,8 @@ -/* global module, test */ +/* global QUnit */ /* eslint global-strict: 0, strict: 0 */ 'use strict'; -module('admin.inlines: tabular formsets', { +QUnit.module('admin.inlines: tabular formsets', { beforeEach: function() { var $ = django.jQuery; var that = this; @@ -19,19 +19,19 @@ module('admin.inlines: tabular formsets', { } }); -test('no forms', function(assert) { +QUnit.test('no forms', function(assert) { assert.ok(this.inlineRow.hasClass('dynamic-first')); assert.equal(this.table.find('.add-row a').text(), this.addText); }); -test('add form', function(assert) { +QUnit.test('add form', function(assert) { var addButton = this.table.find('.add-row a'); assert.equal(addButton.text(), this.addText); addButton.click(); assert.ok(this.table.find('#first-1').hasClass('row2')); }); -test('add/remove form events', function(assert) { +QUnit.test('add/remove form events', function(assert) { assert.expect(6); var $ = django.jQuery; var $document = $(document); @@ -52,7 +52,7 @@ test('add/remove form events', function(assert) { deleteLink.click(); }); -test('existing add button', function(assert) { +QUnit.test('existing add button', function(assert) { var $ = django.jQuery; $('#qunit-fixture').empty(); // Clear the table added in beforeEach $('#qunit-fixture').append($('#tabular-formset').text()); diff --git a/js_tests/admin/timeparse.test.js b/js_tests/admin/timeparse.test.js index e5d1b2cf26..a162d6d36e 100644 --- a/js_tests/admin/timeparse.test.js +++ b/js_tests/admin/timeparse.test.js @@ -1,10 +1,10 @@ -/* global module, test, parseTimeString */ +/* global QUnit, parseTimeString */ /* eslint global-strict: 0, strict: 0 */ 'use strict'; -module('admin.timeparse'); +QUnit.module('admin.timeparse'); -test('parseTimeString', function(assert) { +QUnit.test('parseTimeString', function(assert) { function time(then, expected) { assert.equal(parseTimeString(then), expected); } diff --git a/js_tests/gis/mapwidget.test.js b/js_tests/gis/mapwidget.test.js index 716e6eca8b..9d1daaaafd 100644 --- a/js_tests/gis/mapwidget.test.js +++ b/js_tests/gis/mapwidget.test.js @@ -1,10 +1,10 @@ -/* global module, test, MapWidget */ +/* global QUnit, MapWidget */ /* eslint global-strict: 0, strict: 0 */ 'use strict'; -module('gis.OLMapWidget'); +QUnit.module('gis.OLMapWidget'); -test('MapWidget.featureAdded', function(assert) { +QUnit.test('MapWidget.featureAdded', function(assert) { var options = {id: 'id_point', map_id: 'id_point_map', geom_name: 'Point'}; var widget = new MapWidget(options); assert.equal(widget.layers.vector.features.length, 1); @@ -15,13 +15,13 @@ test('MapWidget.featureAdded', function(assert) { ); }); -test('MapWidget.map_srid', function(assert) { +QUnit.test('MapWidget.map_srid', function(assert) { var options = {id: 'id_point', map_id: 'id_point_map', geom_name: 'Point'}; var widget = new MapWidget(options); assert.equal(widget.options.map_srid, 4326, 'SRID 4326'); }); -test('MapWidget.defaultCenter', function(assert) { +QUnit.test('MapWidget.defaultCenter', function(assert) { var options = {id: 'id_point', map_id: 'id_point_map', geom_name: 'Point'}; var widget = new MapWidget(options); assert.equal(widget.defaultCenter().toString(), 'lon=0,lat=0', 'Default center at 0, 0'); @@ -35,7 +35,7 @@ test('MapWidget.defaultCenter', function(assert) { ); }); -test('MapWidget.getControls', function(assert) { +QUnit.test('MapWidget.getControls', function(assert) { var options = {id: 'id_point', map_id: 'id_point_map', geom_name: 'Point'}; var widget = new MapWidget(options); widget.getControls(widget.layers.vector); @@ -45,7 +45,7 @@ test('MapWidget.getControls', function(assert) { assert.equal(widget.controls[2].displayClass, 'olControlModifyFeature', 'Modify control'); }); -test('MapWidget.IsCollection', function(assert) { +QUnit.test('MapWidget.IsCollection', function(assert) { var options = {id: 'id_point', map_id: 'id_point_map', geom_name: 'Point'}; var widget = new MapWidget(options); assert.notOk(widget.options.is_collection); diff --git a/js_tests/qunit/qunit.css b/js_tests/qunit/qunit.css index ae68fc412e..5d19d74068 100644 --- a/js_tests/qunit/qunit.css +++ b/js_tests/qunit/qunit.css @@ -1,12 +1,12 @@ /*! - * QUnit 1.23.1 + * QUnit 2.0.1 * https://qunitjs.com/ * * Copyright jQuery Foundation and other contributors * Released under the MIT license * https://jquery.org/license * - * Date: 2016-04-12T17:29Z + * Date: 2016-07-23T19:39Z */ /** Font Family and Sizes */ @@ -27,7 +27,7 @@ } -/** Header */ +/** Header (excluding toolbar) */ #qunit-header { padding: 0.5em 0 0.5em 1em; @@ -52,51 +52,161 @@ color: #FFF; } -#qunit-testrunner-toolbar label { - display: inline-block; - padding: 0 0.5em 0 0.1em; -} - #qunit-banner { height: 5px; } +#qunit-filteredTest { + padding: 0.5em 1em 0.5em 1em; + color: #366097; + background-color: #F4FF77; +} + +#qunit-userAgent { + padding: 0.5em 1em 0.5em 1em; + color: #FFF; + background-color: #2B81AF; + text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; +} + + +/** Toolbar */ + #qunit-testrunner-toolbar { padding: 0.5em 1em 0.5em 1em; color: #5E740B; background-color: #EEE; - overflow: hidden; } -#qunit-filteredTest { - padding: 0.5em 1em 0.5em 1em; - background-color: #F4FF77; - color: #366097; +#qunit-testrunner-toolbar .clearfix { + height: 0; + clear: both; } -#qunit-userAgent { - padding: 0.5em 1em 0.5em 1em; - background-color: #2B81AF; - color: #FFF; - text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; -} - -#qunit-modulefilter-container { - float: right; - padding: 0.2em; -} - -.qunit-url-config { +#qunit-testrunner-toolbar label { display: inline-block; - padding: 0.1em; } -.qunit-filter { - display: block; +#qunit-testrunner-toolbar input[type=checkbox], +#qunit-testrunner-toolbar input[type=radio] { + margin: 3px; + vertical-align: -2px; +} + +#qunit-testrunner-toolbar input[type=text] { + box-sizing: border-box; + height: 1.6em; +} + +.qunit-url-config, +.qunit-filter, +#qunit-modulefilter { + display: inline-block; + line-height: 2.1em; +} + +.qunit-filter, +#qunit-modulefilter { float: right; + position: relative; margin-left: 1em; } +.qunit-url-config label { + margin-right: 0.5em; +} + +#qunit-modulefilter-search { + box-sizing: border-box; + width: 400px; +} + +#qunit-modulefilter-search-container:after { + position: absolute; + right: 0.3em; + content: "\25bc"; + color: black; +} + +#qunit-modulefilter-dropdown { + /* align with #qunit-modulefilter-search */ + box-sizing: border-box; + width: 400px; + position: absolute; + right: 0; + top: 50%; + margin-top: 0.8em; + + border: 1px solid #D3D3D3; + border-top: none; + border-radius: 0 0 .25em .25em; + color: #000; + background-color: #F5F5F5; + z-index: 99; +} + +#qunit-modulefilter-dropdown a { + color: inherit; + text-decoration: none; +} + +#qunit-modulefilter-dropdown .clickable.checked { + font-weight: bold; + color: #000; + background-color: #D2E0E6; +} + +#qunit-modulefilter-dropdown .clickable:hover { + color: #FFF; + background-color: #0D3349; +} + +#qunit-modulefilter-actions { + display: block; + overflow: auto; + + /* align with #qunit-modulefilter-dropdown-list */ + font: smaller/1.5em sans-serif; +} + +#qunit-modulefilter-dropdown #qunit-modulefilter-actions > * { + box-sizing: border-box; + max-height: 2.8em; + display: block; + padding: 0.4em; +} + +#qunit-modulefilter-dropdown #qunit-modulefilter-actions > button { + float: right; + font: inherit; +} + +#qunit-modulefilter-dropdown #qunit-modulefilter-actions > :last-child { + /* insert padding to align with checkbox margins */ + padding-left: 3px; +} + +#qunit-modulefilter-dropdown-list { + max-height: 200px; + overflow-y: auto; + margin: 0; + border-top: 2px groove threedhighlight; + padding: 0.4em 0 0; + font: smaller/1.5em sans-serif; +} + +#qunit-modulefilter-dropdown-list li { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +#qunit-modulefilter-dropdown-list .clickable { + display: block; + padding-left: 0.15em; +} + + /** Tests: Pass/Fail */ #qunit-tests { @@ -202,14 +312,14 @@ } #qunit-tests del { - background-color: #E0F2BE; color: #374E0C; + background-color: #E0F2BE; text-decoration: none; } #qunit-tests ins { - background-color: #FFCACA; color: #500; + background-color: #FFCACA; text-decoration: none; } diff --git a/js_tests/qunit/qunit.js b/js_tests/qunit/qunit.js index 5df0822ea4..47c904cf32 100644 --- a/js_tests/qunit/qunit.js +++ b/js_tests/qunit/qunit.js @@ -1,12 +1,12 @@ /*! - * QUnit 1.23.1 + * QUnit 2.0.1 * https://qunitjs.com/ * * Copyright jQuery Foundation and other contributors * Released under the MIT license * https://jquery.org/license * - * Date: 2016-04-12T17:29Z + * Date: 2016-07-23T19:39Z */ ( function( global ) { @@ -43,6 +43,8 @@ var fileName = ( sourceFromStacktrace( 0 ) || "" ).replace( /(:\d+)+\)?/, "" ).r var globalStartCalled = false; var runStarted = false; +var autorun = false; + var toString = Object.prototype.toString, hasOwn = Object.prototype.hasOwnProperty; @@ -100,16 +102,10 @@ function objectValues ( obj ) { function extend( a, b, undefOnly ) { for ( var prop in b ) { if ( hasOwn.call( b, prop ) ) { - - // Avoid "Member not found" error in IE8 caused by messing with window.constructor - // This block runs on every environment, so `global` is being used instead of `window` - // to avoid errors on node. - if ( prop !== "constructor" || a !== global ) { - if ( b[ prop ] === undefined ) { - delete a[ prop ]; - } else if ( !( undefOnly && typeof a[ prop ] !== "undefined" ) ) { - a[ prop ] = b[ prop ]; - } + if ( b[ prop ] === undefined ) { + delete a[ prop ]; + } else if ( !( undefOnly && typeof a[ prop ] !== "undefined" ) ) { + a[ prop ] = b[ prop ]; } } } @@ -157,7 +153,7 @@ function is( type, obj ) { return QUnit.objectType( obj ) === type; } -// Doesn't support IE6 to IE9, it will return undefined on these browsers +// Doesn't support IE9, it will return undefined on these browsers // See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack function extractStacktrace( e, offset ) { offset = offset === undefined ? 4 : offset; @@ -182,17 +178,6 @@ function extractStacktrace( e, offset ) { } } return stack[ offset ]; - - // Support: Safari <=6 only - } else if ( e.sourceURL ) { - - // Exclude useless self-reference for generated Error objects - if ( /qunit.js$/.test( e.sourceURL ) ) { - return; - } - - // For actual exceptions, this is useful - return e.sourceURL + ":" + e.line; } } @@ -266,8 +251,6 @@ var config = { // Push a loose unnamed module to the modules collection config.modules.push( config.currentModule ); -var loggingCallbacks = {}; - // Register logging callbacks function registerLoggingCallbacks( obj ) { var i, l, key, @@ -285,11 +268,6 @@ function registerLoggingCallbacks( obj ) { config.callbacks[ key ].push( callback ); }; - // DEPRECATED: This will be removed on QUnit 2.0.0+ - // Stores the registered functions allowing restoring - // at verifyLoggingCallbacks() if modified - loggingCallbacks[ key ] = loggingCallback; - return loggingCallback; } @@ -314,34 +292,6 @@ function runLoggingCallbacks( key, args ) { } } -// DEPRECATED: This will be removed on 2.0.0+ -// This function verifies if the loggingCallbacks were modified by the user -// If so, it will restore it, assign the given callback and print a console warning -function verifyLoggingCallbacks() { - var loggingCallback, userCallback; - - for ( loggingCallback in loggingCallbacks ) { - if ( QUnit[ loggingCallback ] !== loggingCallbacks[ loggingCallback ] ) { - - userCallback = QUnit[ loggingCallback ]; - - // Restore the callback function - QUnit[ loggingCallback ] = loggingCallbacks[ loggingCallback ]; - - // Assign the deprecated given callback - QUnit[ loggingCallback ]( userCallback ); - - if ( global.console && global.console.warn ) { - global.console.warn( - "QUnit." + loggingCallback + " was replaced with a new value.\n" + - "Please, check out the documentation on how to apply logging callbacks.\n" + - "Reference: https://api.qunitjs.com/category/callbacks/" - ); - } - } - } -} - ( function() { if ( !defined.document ) { return; @@ -384,7 +334,7 @@ function verifyLoggingCallbacks() { QUnit.isLocal = !( defined.document && window.location.protocol !== "file:" ); // Expose the current QUnit version -QUnit.version = "1.23.1"; +QUnit.version = "2.0.1"; extend( QUnit, { @@ -400,22 +350,21 @@ extend( QUnit, { } } - // DEPRECATED: handles setup/teardown functions, - // beforeEach and afterEach should be used instead - if ( testEnvironment && testEnvironment.setup ) { - testEnvironment.beforeEach = testEnvironment.setup; - delete testEnvironment.setup; - } - if ( testEnvironment && testEnvironment.teardown ) { - testEnvironment.afterEach = testEnvironment.teardown; - delete testEnvironment.teardown; - } - module = createModule(); + if ( testEnvironment && ( testEnvironment.setup || testEnvironment.teardown ) ) { + console.warn( + "Module's `setup` and `teardown` are not hooks anymore on QUnit 2.0, use " + + "`beforeEach` and `afterEach` instead\n" + + "Details in our upgrade guide at https://qunitjs.com/upgrade-guide-2.x/" + ); + } + moduleFns = { + before: setHook( module, "before" ), beforeEach: setHook( module, "beforeEach" ), - afterEach: setHook( module, "afterEach" ) + afterEach: setHook( module, "afterEach" ), + after: setHook( module, "after" ) }; if ( objectType( executeNow ) === "function" ) { @@ -437,11 +386,13 @@ extend( QUnit, { name: moduleName, parentModule: parentModule, tests: [], - moduleId: generateHash( moduleName ) + moduleId: generateHash( moduleName ), + testsRun: 0 }; var env = {}; if ( parentModule ) { + parentModule.childModule = module; extend( env, parentModule.testEnvironment ); delete env.beforeEach; delete env.afterEach; @@ -459,17 +410,12 @@ extend( QUnit, { }, - // DEPRECATED: QUnit.asyncTest() will be removed in QUnit 2.0. - asyncTest: asyncTest, - test: test, skip: skip, only: only, - // DEPRECATED: The functionality of QUnit.start() will be altered in QUnit 2.0. - // In QUnit 2.0, invoking it will ONLY affect the `QUnit.config.autostart` blocking behavior. start: function( count ) { var globalStartAlreadyCalled = globalStartCalled; @@ -477,7 +423,7 @@ extend( QUnit, { globalStartCalled = true; if ( runStarted ) { - throw new Error( "Called start() outside of a test context while already started" ); + throw new Error( "Called start() while test already started running" ); } else if ( globalStartAlreadyCalled || count > 1 ) { throw new Error( "Called start() outside of a test context too many times" ); } else if ( config.autostart ) { @@ -490,53 +436,14 @@ extend( QUnit, { return; } } else { - - // If a test is running, adjust its semaphore - config.current.semaphore -= count || 1; - - // If semaphore is non-numeric, throw error - if ( isNaN( config.current.semaphore ) ) { - config.current.semaphore = 0; - - QUnit.pushFailure( - "Called start() with a non-numeric decrement.", - sourceFromStacktrace( 2 ) - ); - return; - } - - // Don't start until equal number of stop-calls - if ( config.current.semaphore > 0 ) { - return; - } - - // Throw an Error if start is called more often than stop - if ( config.current.semaphore < 0 ) { - config.current.semaphore = 0; - - QUnit.pushFailure( - "Called start() while already started (test's semaphore was 0 already)", - sourceFromStacktrace( 2 ) - ); - return; - } + throw new Error( + "QUnit.start cannot be called inside a test context. This feature is removed in " + + "QUnit 2.0. For async tests, use QUnit.test() with assert.async() instead.\n" + + "Details in our upgrade guide at https://qunitjs.com/upgrade-guide-2.x/" + ); } - resumeProcessing(); - }, - - // DEPRECATED: QUnit.stop() will be removed in QUnit 2.0. - stop: function( count ) { - - // If there isn't a test running, don't allow QUnit.stop() to be called - if ( !config.current ) { - throw new Error( "Called stop() outside of a test context" ); - } - - // If a test is running, adjust its semaphore - config.current.semaphore += count || 1; - - pauseProcessing(); + scheduleBegin(); }, config: config, @@ -560,10 +467,12 @@ extend( QUnit, { filter: "" }, true ); - config.blocking = false; + if ( !runStarted ) { + config.blocking = false; - if ( config.autostart ) { - resumeProcessing(); + if ( config.autostart ) { + scheduleBegin(); + } } }, @@ -575,6 +484,20 @@ extend( QUnit, { registerLoggingCallbacks( QUnit ); +function scheduleBegin() { + + runStarted = true; + + // Add a slight delay to allow definition of more modules and tests. + if ( defined.setTimeout ) { + setTimeout( function() { + begin(); + }, 13 ); + } else { + begin(); + } +} + function begin() { var i, l, modulesLog = []; @@ -585,8 +508,6 @@ function begin() { // Record the time of the test run's beginning config.started = now(); - verifyLoggingCallbacks(); - // Delete the loose unnamed module if unused. if ( config.modules[ 0 ].name === "" && config.modules[ 0 ].tests.length === 0 ) { config.modules.shift(); @@ -638,47 +559,10 @@ function process( last ) { } } -function pauseProcessing() { - config.blocking = true; - - if ( config.testTimeout && defined.setTimeout ) { - clearTimeout( config.timeout ); - config.timeout = setTimeout( function() { - if ( config.current ) { - config.current.semaphore = 0; - QUnit.pushFailure( "Test timed out", sourceFromStacktrace( 2 ) ); - } else { - throw new Error( "Test timed out" ); - } - resumeProcessing(); - }, config.testTimeout ); - } -} - -function resumeProcessing() { - runStarted = true; - - // A slight delay to allow this iteration of the event loop to finish (more assertions, etc.) - if ( defined.setTimeout ) { - setTimeout( function() { - if ( config.current && config.current.semaphore > 0 ) { - return; - } - if ( config.timeout ) { - clearTimeout( config.timeout ); - } - - begin(); - }, 13 ); - } else { - begin(); - } -} - function done() { var runtime, passed; - config.autorun = true; + autorun = true; // Log the last module results if ( config.previousModule ) { @@ -714,15 +598,16 @@ function setHook( module, hookName ) { }; } -var focused = false; -var priorityCount = 0; -var unitSampler; +var unitSampler, + focused = false, + priorityCount = 0; function Test( settings ) { var i, l; ++Test.count; + this.expected = null; extend( this, settings ); this.assertions = []; this.semaphore = 0; @@ -791,8 +676,10 @@ Test.prototype = { config.current = this; if ( this.module.testEnvironment ) { + delete this.module.testEnvironment.before; delete this.module.testEnvironment.beforeEach; delete this.module.testEnvironment.afterEach; + delete this.module.testEnvironment.after; } this.testEnvironment = extend( {}, this.module.testEnvironment ); @@ -813,10 +700,6 @@ Test.prototype = { config.current = this; - if ( this.async ) { - QUnit.stop(); - } - this.callbackStarted = now(); if ( config.notrycatch ) { @@ -835,7 +718,7 @@ Test.prototype = { // Restart the tests if they're blocking if ( config.blocking ) { - QUnit.start(); + internalRecover( this ); } } @@ -849,10 +732,22 @@ Test.prototype = { checkPollution(); }, - queueHook: function( hook, hookName ) { + queueHook: function( hook, hookName, hookOwner ) { var promise, test = this; return function runHook() { + if ( hookName === "before" ) { + if ( hookOwner.testsRun !== 0 ) { + return; + } + + test.preserveEnvironment = true; + } + + if ( hookName === "after" && hookOwner.testsRun !== numberOfTests( hookOwner ) - 1 ) { + return; + } + config.current = test; if ( config.notrycatch ) { callHook(); @@ -882,7 +777,7 @@ Test.prototype = { } if ( module.testEnvironment && QUnit.objectType( module.testEnvironment[ handler ] ) === "function" ) { - hooks.push( test.queueHook( module.testEnvironment[ handler ], handler ) ); + hooks.push( test.queueHook( module.testEnvironment[ handler ], handler, module ) ); } } @@ -907,9 +802,11 @@ Test.prototype = { } var i, + skipped = !!this.skip, bad = 0; this.runtime = now() - this.started; + config.stats.all += this.assertions.length; config.moduleStats.all += this.assertions.length; @@ -921,34 +818,34 @@ Test.prototype = { } } + notifyTestsRan( this.module ); runLoggingCallbacks( "testDone", { name: this.testName, module: this.module.name, - skipped: !!this.skip, + skipped: skipped, failed: bad, passed: this.assertions.length - bad, total: this.assertions.length, - runtime: this.runtime, + runtime: skipped ? 0 : this.runtime, // HTML Reporter use assertions: this.assertions, testId: this.testId, // Source of Test - source: this.stack, - - // DEPRECATED: this property will be removed in 2.0.0, use runtime instead - duration: this.runtime + source: this.stack } ); - // QUnit.reset() is deprecated and will be replaced for a new - // fixture reset function on QUnit 2.0/2.1. - // It's still called here for backwards compatibility handling - QUnit.reset(); - config.current = undefined; }, + preserveTestEnvironment: function() { + if ( this.preserveEnvironment ) { + this.module.testEnvironment = this.testEnvironment; + this.testEnvironment = extend( {}, this.module.testEnvironment ); + } + }, + queue: function() { var priority, test = this; @@ -965,16 +862,25 @@ Test.prototype = { test.before(); }, + test.hooks( "before" ), + + function() { + test.preserveTestEnvironment(); + }, + test.hooks( "beforeEach" ), + function() { test.run(); }, test.hooks( "afterEach" ).reverse(), + test.hooks( "after" ).reverse(), function() { test.after(); }, + function() { test.finish(); } @@ -1049,15 +955,15 @@ Test.prototype = { }, resolvePromise: function( promise, phase ) { - var then, message, + var then, resume, message, test = this; if ( promise != null ) { then = promise.then; if ( QUnit.objectType( then ) === "function" ) { - QUnit.stop(); + resume = internalStop( test ); then.call( promise, - function() { QUnit.start(); }, + function() { resume(); }, function( error ) { message = "Promise rejected " + ( !phase ? "during" : phase.replace( /Each$/, "" ) ) + @@ -1068,7 +974,7 @@ Test.prototype = { saveGlobal(); // Unblock - QUnit.start(); + resume(); } ); } @@ -1153,28 +1059,6 @@ Test.prototype = { } }; -// Resets the test setup. Useful for tests that modify the DOM. -/* -DEPRECATED: Use multiple tests instead of resetting inside a test. -Use testStart or testDone for custom cleanup. -This method will throw an error in 2.0, and will be removed in 2.1 -*/ -QUnit.reset = function() { - - // Return on non-browser environments - // This is necessary to not break on node tests - if ( !defined.document ) { - return; - } - - var fixture = defined.document && document.getElementById && - document.getElementById( "qunit-fixture" ); - - if ( fixture ) { - fixture.innerHTML = config.fixture; - } -}; - QUnit.pushFailure = function() { if ( !QUnit.config.current ) { throw new Error( "pushFailure() assertion outside test context, in " + @@ -1236,7 +1120,7 @@ function synchronize( callback, priority, seed ) { config.queue.push( callback ); } - if ( config.autorun && !config.blocking ) { + if ( autorun && !config.blocking ) { process( last ); } } @@ -1295,31 +1179,14 @@ function checkPollution() { } } -// Will be exposed as QUnit.asyncTest -function asyncTest( testName, expected, callback ) { - if ( arguments.length === 2 ) { - callback = expected; - expected = null; - } - - QUnit.test( testName, expected, callback, true ); -} - // Will be exposed as QUnit.test -function test( testName, expected, callback, async ) { +function test( testName, callback ) { if ( focused ) { return; } var newTest; - if ( arguments.length === 2 ) { - callback = expected; - expected = null; - } - newTest = new Test( { testName: testName, - expected: expected, - async: async, callback: callback } ); @@ -1339,7 +1206,7 @@ function skip( testName ) { } // Will be exposed as QUnit.only -function only( testName, expected, callback, async ) { +function only( testName, callback ) { var newTest; if ( focused ) { return; } @@ -1347,21 +1214,113 @@ function only( testName, expected, callback, async ) { QUnit.config.queue.length = 0; focused = true; - if ( arguments.length === 2 ) { - callback = expected; - expected = null; - } - newTest = new Test( { testName: testName, - expected: expected, - async: async, callback: callback } ); newTest.queue(); } +// Put a hold on processing and return a function that will release it. +function internalStop( test ) { + var released = false; + + test.semaphore += 1; + config.blocking = true; + + // Set a recovery timeout, if so configured. + if ( config.testTimeout && defined.setTimeout ) { + clearTimeout( config.timeout ); + config.timeout = setTimeout( function() { + QUnit.pushFailure( "Test timed out", sourceFromStacktrace( 2 ) ); + internalRecover( test ); + }, config.testTimeout ); + } + + return function resume() { + if ( released ) { + return; + } + + released = true; + test.semaphore -= 1; + internalStart( test ); + }; +} + +// Forcefully release all processing holds. +function internalRecover( test ) { + test.semaphore = 0; + internalStart( test ); +} + +// Release a processing hold, scheduling a resumption attempt if no holds remain. +function internalStart( test ) { + + // If semaphore is non-numeric, throw error + if ( isNaN( test.semaphore ) ) { + test.semaphore = 0; + + QUnit.pushFailure( + "Invalid value on test.semaphore", + sourceFromStacktrace( 2 ) + ); + return; + } + + // Don't start until equal number of stop-calls + if ( test.semaphore > 0 ) { + return; + } + + // Throw an Error if start is called more often than stop + if ( test.semaphore < 0 ) { + test.semaphore = 0; + + QUnit.pushFailure( + "Tried to restart test while already started (test's semaphore was 0 already)", + sourceFromStacktrace( 2 ) + ); + return; + } + + // Add a slight delay to allow more assertions etc. + if ( defined.setTimeout ) { + if ( config.timeout ) { + clearTimeout( config.timeout ); + } + config.timeout = setTimeout( function() { + if ( test.semaphore > 0 ) { + return; + } + + if ( config.timeout ) { + clearTimeout( config.timeout ); + } + + begin(); + }, 13 ); + } else { + begin(); + } +} + +function numberOfTests( module ) { + var count = module.tests.length; + while ( module = module.childModule ) { + count += module.tests.length; + } + return count; +} + +function notifyTestsRan( module ) { + module.testsRun++; + while ( module = module.parentModule ) { + module.testsRun++; + } +} + function Assert( testContext ) { this.test = testContext; } @@ -1379,10 +1338,10 @@ QUnit.assert = Assert.prototype = { } }, - // Increment this Test's semaphore counter, then return a function that - // decrements that counter a maximum of once. + // Put a hold on processing and return a function that will release it a maximum of once. async: function( count ) { - var test = this.test, + var resume, + test = this.test, popped = false, acceptCallCount = count; @@ -1390,9 +1349,8 @@ QUnit.assert = Assert.prototype = { acceptCallCount = 1; } - test.semaphore += 1; test.usedAsync = true; - pauseProcessing(); + resume = internalStop( test ); return function done() { @@ -1406,9 +1364,8 @@ QUnit.assert = Assert.prototype = { return; } - test.semaphore -= 1; popped = true; - resumeProcessing(); + resume(); }; }, @@ -1565,9 +1522,17 @@ QUnit.assert = Assert.prototype = { currentTest = ( this instanceof Assert && this.test ) || QUnit.config.current; // 'expected' is optional unless doing string comparison - if ( message == null && typeof expected === "string" ) { - message = expected; - expected = null; + if ( QUnit.objectType( expected ) === "string" ) { + if ( message == null ) { + message = expected; + expected = null; + } else { + throw new Error( + "throws/raises does not accept a string value for the expected argument.\n" + + "Use a non-string object value (e.g. regExp) instead if it's necessary." + + "Details in our upgrade guide at https://qunitjs.com/upgrade-guide-2.x/" + ); + } } currentTest.ignoreGlobalErrors = true; @@ -1590,10 +1555,6 @@ QUnit.assert = Assert.prototype = { } else if ( expectedType === "regexp" ) { ok = expected.test( errorString( actual ) ); - // Expected is a string - } else if ( expectedType === "string" ) { - ok = expected === errorString( actual ); - // Expected is a constructor, maybe an Error constructor } else if ( expectedType === "function" && actual instanceof expected ) { ok = true; @@ -1947,6 +1908,21 @@ QUnit.dump = ( function() { return join( "[", ret, "]" ); } + function isArray( obj ) { + return ( + + //Native Arrays + toString.call( obj ) === "[object Array]" || + + // NodeList objects + ( typeof obj.length === "number" && obj.item !== undefined ) && + ( obj.length ? + obj.item( 0 ) === obj[ 0 ] : + ( obj.item( 0 ) === null && obj[ 0 ] === undefined ) + ) + ); + } + var reName = /^function (\w+)/, dump = { @@ -1974,6 +1950,7 @@ QUnit.dump = ( function() { }, typeOf: function( obj ) { var type; + if ( obj === null ) { type = "null"; } else if ( typeof obj === "undefined" ) { @@ -1992,16 +1969,7 @@ QUnit.dump = ( function() { type = "document"; } else if ( obj.nodeType ) { type = "node"; - } else if ( - - // Native arrays - toString.call( obj ) === "[object Array]" || - - // NodeList objects - ( typeof obj.length === "number" && obj.item !== undefined && - ( obj.length ? obj.item( 0 ) === obj[ 0 ] : ( obj.item( 0 ) === null && - obj[ 0 ] === undefined ) ) ) - ) { + } else if ( isArray( obj ) ) { type = "array"; } else if ( obj.constructor === Error.prototype.constructor ) { type = "error"; @@ -2163,7 +2131,10 @@ QUnit.dump = ( function() { date: quote, regexp: literal, number: literal, - "boolean": literal + "boolean": literal, + symbol: function( sym ) { + return sym.toString(); + } }, // If true, entities are escaped ( <, >, \t, space and \n ) @@ -2182,54 +2153,72 @@ QUnit.dump = ( function() { // Back compat QUnit.jsDump = QUnit.dump; -// Deprecated -// Extend assert methods to QUnit for Backwards compatibility -( function() { - var i, - assertions = Assert.prototype; +function applyDeprecated( name ) { + return function() { + throw new Error( + name + " is removed in QUnit 2.0.\n" + + "Details in our upgrade guide at https://qunitjs.com/upgrade-guide-2.x/" + ); + }; +} - function applyCurrent( current ) { - return function() { - var assert = new Assert( QUnit.config.current ); - current.apply( assert, arguments ); - }; - } +Object.keys( Assert.prototype ).forEach( function( key ) { + QUnit[ key ] = applyDeprecated( "`QUnit." + key + "`" ); +} ); - for ( i in assertions ) { - QUnit[ i ] = applyCurrent( assertions[ i ] ); - } -}() ); +QUnit.asyncTest = function() { + throw new Error( + "asyncTest is removed in QUnit 2.0, use QUnit.test() with assert.async() instead.\n" + + "Details in our upgrade guide at https://qunitjs.com/upgrade-guide-2.x/" + ); +}; + +QUnit.stop = function() { + throw new Error( + "QUnit.stop is removed in QUnit 2.0, use QUnit.test() with assert.async() instead.\n" + + "Details in our upgrade guide at https://qunitjs.com/upgrade-guide-2.x/" + ); +}; + +function resetThrower() { + throw new Error( + "QUnit.reset is removed in QUnit 2.0 without replacement.\n" + + "Details in our upgrade guide at https://qunitjs.com/upgrade-guide-2.x/" + ); +} + +Object.defineProperty( QUnit, "reset", { + get: function() { + return resetThrower; + }, + set: resetThrower +} ); -// For browser, export only select globals if ( defined.document ) { + if ( window.QUnit ) { + throw new Error( "QUnit has already been defined." ); + } - ( function() { - var i, l, - keys = [ - "test", - "module", - "expect", - "asyncTest", - "start", - "stop", - "ok", - "notOk", - "equal", - "notEqual", - "propEqual", - "notPropEqual", - "deepEqual", - "notDeepEqual", - "strictEqual", - "notStrictEqual", - "throws", - "raises" - ]; - - for ( i = 0, l = keys.length; i < l; i++ ) { - window[ keys[ i ] ] = QUnit[ keys[ i ] ]; - } - }() ); + [ + "test", + "module", + "expect", + "start", + "ok", + "notOk", + "equal", + "notEqual", + "propEqual", + "notPropEqual", + "deepEqual", + "notDeepEqual", + "strictEqual", + "notStrictEqual", + "throws", + "raises" + ].forEach( function( key ) { + window[ key ] = applyDeprecated( "The global `" + key + "`" ); + } ); window.QUnit = QUnit; } @@ -2261,6 +2250,47 @@ if ( typeof define === "function" && define.amd ) { ( function() { +if ( typeof window === "undefined" || !window.document ) { + return; +} + +var config = QUnit.config, + hasOwn = Object.prototype.hasOwnProperty; + +// Stores fixture HTML for resetting later +function storeFixture() { + + // Avoid overwriting user-defined values + if ( hasOwn.call( config, "fixture" ) ) { + return; + } + + var fixture = document.getElementById( "qunit-fixture" ); + if ( fixture ) { + config.fixture = fixture.innerHTML; + } +} + +QUnit.begin( storeFixture ); + +// Resets the fixture DOM element if available. +function resetFixture() { + if ( config.fixture == null ) { + return; + } + + var fixture = document.getElementById( "qunit-fixture" ); + if ( fixture ) { + fixture.innerHTML = config.fixture; + } +} + +QUnit.testStart( resetFixture ); + +}() ); + +( function() { + // Only interact with URLs via window.location var location = typeof window !== "undefined" && window.location; if ( !location ) { @@ -2338,11 +2368,11 @@ function getUrlParams() { for ( i = 0; i < length; i++ ) { if ( params[ i ] ) { param = params[ i ].split( "=" ); - name = decodeURIComponent( param[ 0 ] ); + name = decodeQueryParam( param[ 0 ] ); // Allow just a key to turn on a flag, e.g., test.html?noglobals value = param.length === 1 || - decodeURIComponent( param.slice( 1 ).join( "=" ) ) ; + decodeQueryParam( param.slice( 1 ).join( "=" ) ) ; if ( urlParams[ name ] ) { urlParams[ name ] = [].concat( urlParams[ name ], value ); } else { @@ -2354,27 +2384,20 @@ function getUrlParams() { return urlParams; } +function decodeQueryParam( param ) { + return decodeURIComponent( param.replace( /\+/g, "%20" ) ); +} + // Don't load the HTML Reporter on non-browser environments if ( typeof window === "undefined" || !window.document ) { return; } -// Deprecated QUnit.init - Ref #530 -// Re-initialize the configuration options QUnit.init = function() { - var config = QUnit.config; - - config.stats = { all: 0, bad: 0 }; - config.moduleStats = { all: 0, bad: 0 }; - config.started = 0; - config.updateRate = 1000; - config.blocking = false; - config.autostart = true; - config.autorun = false; - config.filter = ""; - config.queue = []; - - appendInterface(); + throw new Error( + "QUnit.init is removed in QUnit 2.0, use QUnit.test() with assert.async() instead.\n" + + "Details in our upgrade guide at https://qunitjs.com/upgrade-guide-2.x/" + ); }; var config = QUnit.config, @@ -2397,9 +2420,7 @@ var config = QUnit.config, }, modulesList = []; -/** -* Escape text for attribute or text content. -*/ +// Escape text for attribute or text content. function escapeText( s ) { if ( !s ) { return ""; @@ -2423,35 +2444,14 @@ function escapeText( s ) { } ); } -/** - * @param {HTMLElement} elem - * @param {string} type - * @param {Function} fn - */ function addEvent( elem, type, fn ) { - if ( elem.addEventListener ) { - - // Standards-based browsers - elem.addEventListener( type, fn, false ); - } else if ( elem.attachEvent ) { - - // Support: IE <9 - elem.attachEvent( "on" + type, function() { - var event = window.event; - if ( !event.target ) { - event.target = event.srcElement || document; - } - - fn.call( elem, event ); - } ); - } + elem.addEventListener( type, fn, false ); +} + +function removeEvent( elem, type, fn ) { + elem.removeEventListener( type, fn, false ); } -/** - * @param {Array|NodeList} elems - * @param {string} type - * @param {Function} fn - */ function addEvents( elems, type, fn ) { var i = elems.length; while ( i-- ) { @@ -2493,6 +2493,16 @@ function id( name ) { return document.getElementById && document.getElementById( name ); } +function interceptNavigation( ev ) { + applyUrlParams(); + + if ( ev && ev.preventDefault ) { + ev.preventDefault(); + } + + return false; +} + function getUrlConfigHtml() { var i, j, val, escaped, escapedTooltip, @@ -2515,12 +2525,12 @@ function getUrlConfigHtml() { escapedTooltip = escapeText( val.tooltip ); if ( !val.value || typeof val.value === "string" ) { - urlConfigHtml += ""; + " title='" + escapedTooltip + "' />" + escapeText( val.label ) + ""; } else { urlConfigHtml += "