├── .gitignore ├── .jscsrc ├── src ├── outro.js ├── intro.js ├── core │ ├── initialize.js │ ├── onerror.js │ ├── stacktrace.js │ ├── config.js │ ├── logging.js │ └── utilities.js ├── export.js ├── qunit.css ├── assert.js ├── core.js ├── equiv.js ├── dump.js └── test.js ├── .gitattributes ├── .editorconfig ├── test ├── stack-errors.html ├── autostart.js ├── setTimeout.html ├── amd.js ├── logs.html ├── reporter-html │ ├── legacy-markup.html │ ├── no-qunit-element.html │ ├── reporter-html.js │ └── diff.js ├── setTimeout.js ├── main │ ├── stack.js │ ├── test.js │ ├── globals.js │ ├── promise.js │ ├── modules.js │ ├── dump.js │ ├── assert.js │ └── async.js ├── amd.html ├── startError.js ├── globals-node.js ├── headless.html ├── startError.html ├── index.html ├── autostart.html ├── stack-errors.js └── logs.js ├── bower.json ├── .jshintrc ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE.txt ├── package.json ├── README.md ├── .mailmap ├── AUTHORS.txt ├── Gruntfile.js └── reporter └── html.js /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | build/report 4 | browserstack-run.pid 5 | -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "preset": "jquery", 3 | 4 | "requireMultipleVarDecl": null 5 | } 6 | -------------------------------------------------------------------------------- /src/outro.js: -------------------------------------------------------------------------------- 1 | // Get a reference to the global object, like window in browsers 2 | }( (function() { 3 | return this; 4 | })() )); 5 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # JS files must always use LF for tools to work 5 | *.js eol=lf 6 | -------------------------------------------------------------------------------- /src/intro.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * QUnit @VERSION 3 | * http://qunitjs.com/ 4 | * 5 | * Copyright jQuery Foundation and other contributors 6 | * Released under the MIT license 7 | * http://jquery.org/license 8 | * 9 | * Date: @DATE 10 | */ 11 | 12 | (function( global ) { 13 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # This file is for unifying the coding style for different editors and IDEs 2 | # editorconfig.org 3 | 4 | root = true 5 | 6 | [*] 7 | indent_style = tab 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | -------------------------------------------------------------------------------- /test/stack-errors.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | QUnit Main Test Suite 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "qunit", 3 | "main": [ 4 | "qunit/qunit.js", 5 | "qunit/qunit.css" 6 | ], 7 | "license": "https://github.com/jquery/qunit/blob/master/LICENSE.txt", 8 | "ignore": [ 9 | "**/.*", 10 | "!LICENSE.txt", 11 | "package.json", 12 | "Gruntfile.js", 13 | "node_modules", 14 | "test" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /test/autostart.js: -------------------------------------------------------------------------------- 1 | /*global times, beginData */ 2 | 3 | QUnit.start(); 4 | 5 | QUnit.module( "autostart" ); 6 | 7 | QUnit.test( "Prove the test run started as expected", function( assert ) { 8 | assert.expect( 2 ); 9 | assert.ok( times.autostartOff <= times.runStarted ); 10 | assert.strictEqual( beginData.totalTests, 1, "Should have expected 1 test" ); 11 | }); 12 | -------------------------------------------------------------------------------- /test/setTimeout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | QUnit Fake setTimeout Test Suite 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /test/amd.js: -------------------------------------------------------------------------------- 1 | /* global beginData */ 2 | define( [ "qunit" ], function( QUnit ) { 3 | 4 | return function() { 5 | QUnit.module( "AMD autostart" ); 6 | 7 | QUnit.test( "Prove the test run started as expected", function( assert ) { 8 | assert.expect( 1 ); 9 | assert.strictEqual( beginData.totalTests, 1, "Should have expected 1 test" ); 10 | } ); 11 | }; 12 | 13 | } ); 14 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "boss": true, 3 | "curly": true, 4 | "eqeqeq": true, 5 | "eqnull": true, 6 | "expr": true, 7 | "immed": true, 8 | "noarg": true, 9 | "quotmark": "double", 10 | "smarttabs": true, 11 | "trailing": true, 12 | "undef": true, 13 | "unused": true, 14 | 15 | "browser": true, 16 | "es3": true, 17 | 18 | "globals": { 19 | "QUnit": false, 20 | "define": false, 21 | "exports": false, 22 | "module": false 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /test/logs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | QUnit Logs Test Suite 6 | 7 | 8 | 11 | 12 | 13 | 14 |
15 |
test markup
16 | 17 | 18 | -------------------------------------------------------------------------------- /test/reporter-html/legacy-markup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | QUnit HTML Reporter - Legacy Markup 6 | 7 | 8 | 9 | 10 | 11 |

Tests

12 |

13 |
14 |

15 |
    16 | 17 | 18 | -------------------------------------------------------------------------------- /test/setTimeout.js: -------------------------------------------------------------------------------- 1 | (function( window ) { 2 | 3 | QUnit.module( "Module that mucks with time", { 4 | beforeEach: function() { 5 | this.setTimeout = window.setTimeout; 6 | window.setTimeout = function() {}; 7 | }, 8 | 9 | afterEach: function() { 10 | window.setTimeout = this.setTimeout; 11 | } 12 | }); 13 | 14 | QUnit.test( "just a test", function( assert ) { 15 | assert.ok( true ); 16 | }); 17 | 18 | QUnit.test( "just a test", function( assert ) { 19 | assert.ok( true ); 20 | }); 21 | 22 | }( (function() { 23 | return this; 24 | })() )); 25 | -------------------------------------------------------------------------------- /test/main/stack.js: -------------------------------------------------------------------------------- 1 | ( function() { 2 | var stack = QUnit.stack(); 3 | 4 | QUnit.module( "QUnit.stack" ); 5 | 6 | // Flag this test as skipped on browsers that doesn't support stack trace 7 | QUnit[ stack ? "test" : "skip" ]( "returns the proper stack line", function( assert ) { 8 | assert.ok( /\/test\/main\/stack\.js/.test( stack ) ); 9 | 10 | stack = QUnit.stack( 2 ); 11 | assert.ok( stack, "can use offset argument to return a different stacktrace line" ); 12 | assert.notOk( /\/test\/main\/stack\.js/.test( stack ), "stack with offset argument" ); 13 | } ); 14 | } )(); 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - "0.10" 5 | before_install: 6 | - npm install -g grunt-cli 7 | - true && `base64 --decode <<< ZXhwb3J0IEJST1dTRVJTVEFDS19VU0VSTkFNRT1icm93c2Vyc3RhY2txdW5pMQo=` 8 | - true && `base64 --decode <<< ZXhwb3J0IEJST1dTRVJTVEFDS19LRVk9SllzeHJrVWk5aGJGVndkdW44ZUsK=` 9 | script: 10 | - npm run-script ci 11 | cache: 12 | directories: 13 | - node_modules 14 | notifications: 15 | irc: 16 | channels: 17 | - "chat.freenode.net#jquery-dev" 18 | template: 19 | - "%{repository}#%{build_number} (%{branch} - %{commit} %{author}): %{message} - %{build_url}" 20 | -------------------------------------------------------------------------------- /test/amd.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | QUnit AMD Test Suite 6 | 7 | 8 | 9 | 10 |
    11 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /test/startError.js: -------------------------------------------------------------------------------- 1 | /*global autostartStartError, tooManyStartsError */ 2 | 3 | QUnit.module( "global start unrecoverable errors" ); 4 | 5 | QUnit.test( "start() throws when QUnit.config.autostart === true", function( assert ) { 6 | assert.expect( 1 ); 7 | assert.equal( autostartStartError.message, 8 | "Called start() outside of a test context when QUnit.config.autostart was true" ); 9 | }); 10 | 11 | QUnit.test( "Throws after calling start() too many times outside of a test context", 12 | function( assert ) { 13 | assert.expect( 1 ); 14 | assert.equal( tooManyStartsError.message, 15 | "Called start() outside of a test context too many times" ); 16 | }); 17 | -------------------------------------------------------------------------------- /test/reporter-html/no-qunit-element.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | QUnit HTML Reporter - No Markup 6 | 7 | 8 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /test/globals-node.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true */ 2 | 3 | // Don't execute this file directly on Node, this is part of 4 | // Grunt's test-on-node task 5 | (function() { 6 | QUnit.module( "globals for Node.js only" ); 7 | 8 | QUnit.test( "QUnit exports", function( assert ) { 9 | var qunit = require( "../dist/qunit" ); 10 | 11 | assert.ok( qunit, "Required module QUnit truthy" ); 12 | assert.strictEqual( qunit, QUnit, "Required module QUnit matches global QUnit" ); 13 | 14 | assert.ok( qunit.hasOwnProperty( "QUnit" ), "Required module QUnit has property QUnit" ); 15 | assert.strictEqual( 16 | qunit.QUnit, 17 | qunit, 18 | "Required module QUnit's property QUnit is self-referencing" 19 | ); 20 | }); 21 | 22 | })(); 23 | -------------------------------------------------------------------------------- /test/headless.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | QUnit Headless Test Suite 6 | 7 | 8 | 9 | 10 | 24 | 25 | 26 |
    test markup
    27 | 28 | 29 | -------------------------------------------------------------------------------- /test/startError.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | QUnit Start Error Test Suite 6 | 7 | 8 | 9 |
    10 | 11 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | QUnit Main Test Suite 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
    23 |
    test markup
    24 | 25 | 26 | -------------------------------------------------------------------------------- /src/core/initialize.js: -------------------------------------------------------------------------------- 1 | var QUnit = {}; 2 | 3 | var Date = global.Date; 4 | var now = Date.now || function() { 5 | return new Date().getTime(); 6 | }; 7 | 8 | var setTimeout = global.setTimeout; 9 | var clearTimeout = global.clearTimeout; 10 | 11 | // Store a local window from the global to allow direct references. 12 | var window = global.window; 13 | 14 | var defined = { 15 | document: window && window.document !== undefined, 16 | setTimeout: setTimeout !== undefined, 17 | sessionStorage: (function() { 18 | var x = "qunit-test-string"; 19 | try { 20 | sessionStorage.setItem( x, x ); 21 | sessionStorage.removeItem( x ); 22 | return true; 23 | } catch ( e ) { 24 | return false; 25 | } 26 | }() ) 27 | }; 28 | 29 | var fileName = ( sourceFromStacktrace( 0 ) || "" ).replace( /(:\d+)+\)?/, "" ).replace( /.+\//, "" ); 30 | var globalStartCalled = false; 31 | var runStarted = false; 32 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Welcome! Thanks for your interest in contributing to QUnit. You're **almost** in the right place. More information on how to contribute to this and all other jQuery Foundation projects is over at [contribute.jquery.org](http://contribute.jquery.org). You'll definitely want to take a look at the articles on contributing [code](http://contribute.jquery.org/code). 2 | 3 | You may also want to take a look at our [commit & pull request guide](http://contribute.jquery.org/commits-and-pull-requests/) and [style guides](http://contribute.jquery.org/style-guide/) for instructions on how to maintain your fork and submit your code. Before we can merge any pull request, we'll also need you to sign our [contributor license agreement](http://contribute.jquery.org/cla). 4 | 5 | You can find us on [IRC](http://irc.jquery.org), specifically in #jquery-dev should you have any questions. If you've never contributed to open source before, we've put together [a short guide with tips, tricks, and ideas on getting started](http://contribute.jquery.org/open-source/). 6 | -------------------------------------------------------------------------------- /test/autostart.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | QUnit Autostart Test Suite 6 | 7 | 8 | 9 |
    10 | 11 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/core/onerror.js: -------------------------------------------------------------------------------- 1 | ( function() { 2 | if ( !defined.document ) { 3 | return; 4 | } 5 | 6 | // `onErrorFnPrev` initialized at top of scope 7 | // Preserve other handlers 8 | var onErrorFnPrev = window.onerror; 9 | 10 | // Cover uncaught exceptions 11 | // Returning true will suppress the default browser handler, 12 | // returning false will let it run. 13 | window.onerror = function( error, filePath, linerNr ) { 14 | var ret = false; 15 | if ( onErrorFnPrev ) { 16 | ret = onErrorFnPrev( error, filePath, linerNr ); 17 | } 18 | 19 | // Treat return value as window.onerror itself does, 20 | // Only do our handling if not suppressed. 21 | if ( ret !== true ) { 22 | if ( QUnit.config.current ) { 23 | if ( QUnit.config.current.ignoreGlobalErrors ) { 24 | return true; 25 | } 26 | QUnit.pushFailure( error, filePath + ":" + linerNr ); 27 | } else { 28 | QUnit.test( "global failure", extend(function() { 29 | QUnit.pushFailure( error, filePath + ":" + linerNr ); 30 | }, { validTest: true } ) ); 31 | } 32 | return false; 33 | } 34 | 35 | return ret; 36 | }; 37 | } )(); 38 | -------------------------------------------------------------------------------- /src/core/stacktrace.js: -------------------------------------------------------------------------------- 1 | // Doesn't support IE6 to IE9, it will return undefined on these browsers 2 | // See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack 3 | function extractStacktrace( e, offset ) { 4 | offset = offset === undefined ? 4 : offset; 5 | 6 | var stack, include, i; 7 | 8 | if ( e.stack ) { 9 | stack = e.stack.split( "\n" ); 10 | if ( /^error$/i.test( stack[ 0 ] ) ) { 11 | stack.shift(); 12 | } 13 | if ( fileName ) { 14 | include = []; 15 | for ( i = offset; i < stack.length; i++ ) { 16 | if ( stack[ i ].indexOf( fileName ) !== -1 ) { 17 | break; 18 | } 19 | include.push( stack[ i ] ); 20 | } 21 | if ( include.length ) { 22 | return include.join( "\n" ); 23 | } 24 | } 25 | return stack[ offset ]; 26 | 27 | // Support: Safari <=6 only 28 | } else if ( e.sourceURL ) { 29 | 30 | // exclude useless self-reference for generated Error objects 31 | if ( /qunit.js$/.test( e.sourceURL ) ) { 32 | return; 33 | } 34 | 35 | // for actual exceptions, this is useful 36 | return e.sourceURL + ":" + e.line; 37 | } 38 | } 39 | 40 | function sourceFromStacktrace( offset ) { 41 | var error = new Error(); 42 | 43 | // Support: Safari <=7 only, IE <=10 - 11 only 44 | // Not all browsers generate the `stack` property for `new Error()`, see also #636 45 | if ( !error.stack ) { 46 | try { 47 | throw error; 48 | } catch ( err ) { 49 | error = err; 50 | } 51 | } 52 | 53 | return extractStacktrace( error, offset ); 54 | } 55 | -------------------------------------------------------------------------------- /test/main/test.js: -------------------------------------------------------------------------------- 1 | QUnit.test( "expect query and multiple issue", function( assert ) { 2 | assert.expect( 2 ); 3 | assert.ok( true ); 4 | var expected = assert.expect(); 5 | assert.equal( expected, 2 ); 6 | assert.expect( expected + 1 ); 7 | assert.ok( true ); 8 | }); 9 | 10 | if ( typeof document !== "undefined" ) { 11 | 12 | QUnit.module( "fixture" ); 13 | 14 | QUnit.test( "setup", function( assert ) { 15 | assert.expect( 0 ); 16 | document.getElementById( "qunit-fixture" ).innerHTML = "foobar"; 17 | }); 18 | 19 | QUnit.test( "basics", function( assert ) { 20 | assert.equal( 21 | document.getElementById( "qunit-fixture" ).innerHTML, 22 | "test markup", 23 | "automatically reset" 24 | ); 25 | }); 26 | 27 | } 28 | 29 | QUnit.module( "custom assertions" ); 30 | 31 | QUnit.assert.mod2 = function( value, expected, message ) { 32 | var actual = value % 2; 33 | this.push( actual === expected, actual, expected, message ); 34 | }; 35 | 36 | QUnit.test( "mod2", function( assert ) { 37 | assert.expect( 2 ); 38 | 39 | assert.mod2( 2, 0, "2 % 2 == 0" ); 40 | assert.mod2( 3, 1, "3 % 2 == 1" ); 41 | }); 42 | 43 | QUnit.module( "QUnit.skip", { 44 | beforeEach: function( assert ) { 45 | 46 | // skip test hooks for skipped tests 47 | assert.ok( false, "skipped function" ); 48 | throw "Error"; 49 | }, 50 | afterEach: function( assert ) { 51 | assert.ok( false, "skipped function" ); 52 | throw "Error"; 53 | } 54 | }); 55 | 56 | QUnit.skip( "test blocks are skipped", function( assert ) { 57 | 58 | // this test callback won't run, even with broken code 59 | assert.expect( 1000 ); 60 | throw "Error"; 61 | }); 62 | 63 | QUnit.skip( "no function" ); 64 | -------------------------------------------------------------------------------- /src/export.js: -------------------------------------------------------------------------------- 1 | // For browser, export only select globals 2 | if ( defined.document ) { 3 | 4 | // Deprecated 5 | // Extend assert methods to QUnit and Global scope through Backwards compatibility 6 | (function() { 7 | var i, 8 | assertions = Assert.prototype; 9 | 10 | function applyCurrent( current ) { 11 | return function() { 12 | var assert = new Assert( QUnit.config.current ); 13 | current.apply( assert, arguments ); 14 | }; 15 | } 16 | 17 | for ( i in assertions ) { 18 | QUnit[ i ] = applyCurrent( assertions[ i ] ); 19 | } 20 | })(); 21 | 22 | (function() { 23 | var i, l, 24 | keys = [ 25 | "test", 26 | "module", 27 | "expect", 28 | "asyncTest", 29 | "start", 30 | "stop", 31 | "ok", 32 | "notOk", 33 | "equal", 34 | "notEqual", 35 | "propEqual", 36 | "notPropEqual", 37 | "deepEqual", 38 | "notDeepEqual", 39 | "strictEqual", 40 | "notStrictEqual", 41 | "throws" 42 | ]; 43 | 44 | for ( i = 0, l = keys.length; i < l; i++ ) { 45 | window[ keys[ i ] ] = QUnit[ keys[ i ] ]; 46 | } 47 | })(); 48 | 49 | window.QUnit = QUnit; 50 | } 51 | 52 | // For nodejs 53 | if ( typeof module !== "undefined" && module && module.exports ) { 54 | module.exports = QUnit; 55 | 56 | // For consistency with CommonJS environments' exports 57 | module.exports.QUnit = QUnit; 58 | } 59 | 60 | // For CommonJS with exports, but without module.exports, like Rhino 61 | if ( typeof exports !== "undefined" && exports ) { 62 | exports.QUnit = QUnit; 63 | } 64 | 65 | if ( typeof define === "function" && define.amd ) { 66 | define( function() { 67 | return QUnit; 68 | } ); 69 | QUnit.config.autostart = false; 70 | } 71 | -------------------------------------------------------------------------------- /test/main/globals.js: -------------------------------------------------------------------------------- 1 | /*global ok: false, equal: false, throws: false */ 2 | (function( window ) { 3 | 4 | QUnit.module( "globals" ); 5 | 6 | function checkExported( assert, methods, isAssertion ) { 7 | var i, l, method; 8 | 9 | for ( i = 0, l = methods.length; i < l; i++ ) { 10 | method = methods[ i ]; 11 | 12 | assert.strictEqual( typeof( window[ method ] ), "function", "global " + method ); 13 | 14 | assert.strictEqual( 15 | window[ method ], 16 | QUnit[ method ], 17 | "QUnit exports QUnit." + method + " to the global scope" 18 | ); 19 | 20 | if ( isAssertion ) { 21 | assert.strictEqual( 22 | window[ method ], 23 | assert[ method ], 24 | "Global " + method + " is the same of assert." + method 25 | ); 26 | } 27 | } 28 | } 29 | 30 | QUnit.test( "QUnit exported methods", function( assert ) { 31 | var globals = [ 32 | "test", "asyncTest", "module", 33 | "start", "stop" 34 | ]; 35 | 36 | // 2 assertions per item on checkExported 37 | assert.expect( globals.length * 2 ); 38 | 39 | checkExported( assert, globals ); 40 | }); 41 | 42 | // Test deprecated exported Assert methods 43 | QUnit.test( "Exported assertions", function() { 44 | QUnit.expect( 9 ); 45 | 46 | QUnit.ok( true ); 47 | QUnit.equal( 2, 2 ); 48 | QUnit.throws(function() { 49 | throw "error"; 50 | }); 51 | 52 | ok( true ); 53 | equal( 2, 2 ); 54 | throws(function() { 55 | throw "error"; 56 | }); 57 | 58 | QUnit.assert.ok( true ); 59 | QUnit.assert.equal( 2, 2 ); 60 | QUnit.assert.throws(function() { 61 | throw "error"; 62 | }); 63 | }); 64 | 65 | // Get a reference to the global object, like window in browsers 66 | }( (function() { 67 | return this; 68 | }.call()) )); 69 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright jQuery Foundation and other contributors, https://jquery.org/ 2 | 3 | This software consists of voluntary contributions made by many 4 | individuals. For exact contribution history, see the revision history 5 | available at https://github.com/jquery/qunit 6 | 7 | The following license applies to all parts of this software except as 8 | documented below: 9 | 10 | ==== 11 | 12 | Permission is hereby granted, free of charge, to any person obtaining 13 | a copy of this software and associated documentation files (the 14 | "Software"), to deal in the Software without restriction, including 15 | without limitation the rights to use, copy, modify, merge, publish, 16 | distribute, sublicense, and/or sell copies of the Software, and to 17 | permit persons to whom the Software is furnished to do so, subject to 18 | the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be 21 | included in all copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 24 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 25 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 26 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 27 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 28 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 29 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 30 | 31 | ==== 32 | 33 | All files located in the node_modules directory are externally maintained 34 | libraries used by this software which have their own licenses; we 35 | recommend you read them, as their terms may differ from the terms above. 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "qunitjs", 3 | "title": "QUnit", 4 | "description": "An easy-to-use JavaScript Unit Testing framework.", 5 | "version": "1.19.1-pre", 6 | "homepage": "http://qunitjs.com", 7 | "author": { 8 | "name": "jQuery Foundation and other contributors", 9 | "url": "https://github.com/jquery/qunit/blob/master/AUTHORS.txt" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git://github.com/jquery/qunit.git" 14 | }, 15 | "keywords": [ 16 | "testing", 17 | "unit", 18 | "jquery" 19 | ], 20 | "bugs": { 21 | "url": "https://github.com/jquery/qunit/issues" 22 | }, 23 | "license": "MIT", 24 | "files": [ 25 | "qunit/qunit.js", 26 | "qunit/qunit.css", 27 | "LICENSE.txt" 28 | ], 29 | "dependencies": {}, 30 | "devDependencies": { 31 | "browserstack-runner": "0.3.7", 32 | "commitplease": "2.0.0", 33 | "grunt": "0.4.2", 34 | "grunt-contrib-concat": "0.3.0", 35 | "grunt-contrib-jshint": "0.11.2", 36 | "grunt-contrib-watch": "0.5.3", 37 | "grunt-coveralls": "0.3.0", 38 | "grunt-git-authors": "3.0.0", 39 | "grunt-jscs": "0.8.1", 40 | "grunt-qunit-istanbul": "0.5.0", 41 | "grunt-search": "0.1.6", 42 | "load-grunt-tasks": "0.3.0", 43 | "requirejs": "2.1.16" 44 | }, 45 | "scripts": { 46 | "browserstack": "sh build/run-browserstack.sh", 47 | "ci": "grunt && grunt coveralls && npm run browserstack", 48 | "test": "grunt", 49 | "prepublish": "grunt build" 50 | }, 51 | "commitplease": { 52 | "components": [ 53 | "All", 54 | "Assert", 55 | "Build", 56 | "CSS", 57 | "Core", 58 | "Dump", 59 | "HTML Reporter", 60 | "Readme", 61 | "Test", 62 | "Tests" 63 | ] 64 | }, 65 | "main": "qunit/qunit.js" 66 | } 67 | -------------------------------------------------------------------------------- /test/main/promise.js: -------------------------------------------------------------------------------- 1 | // NOTE: Adds 1 assertion 2 | function createMockPromise( assert ) { 3 | 4 | // Return a mock self-fulfilling Promise ("thenable") 5 | var thenable = { 6 | then: function( fulfilledCallback /*, rejectedCallback */ ) { 7 | assert.strictEqual( this, thenable, "`then` was invoked with the Promise as the " + 8 | "context" ); 9 | setTimeout( function() { 10 | return fulfilledCallback.call( thenable, {} ); 11 | }, 13 ); 12 | } 13 | }; 14 | return thenable; 15 | } 16 | 17 | QUnit.module( "Module with Promise-aware beforeEach", { 18 | beforeEach: function( assert ) { 19 | assert.ok( true ); 20 | return {}; 21 | } 22 | }); 23 | 24 | QUnit.test( "non-Promise", function( assert ) { 25 | assert.expect( 1 ); 26 | }); 27 | 28 | QUnit.module( "Module with Promise-aware beforeEach", { 29 | beforeEach: function( assert ) { 30 | 31 | // Adds 1 assertion 32 | return createMockPromise( assert ); 33 | } 34 | }); 35 | 36 | QUnit.test( "fulfilled Promise", function( assert ) { 37 | assert.expect( 1 ); 38 | }); 39 | 40 | QUnit.module( "Module with Promise-aware afterEach", { 41 | afterEach: function( assert ) { 42 | assert.ok( true ); 43 | return {}; 44 | } 45 | }); 46 | 47 | QUnit.test( "non-Promise", function( assert ) { 48 | assert.expect( 1 ); 49 | }); 50 | 51 | QUnit.module( "Module with Promise-aware afterEach", { 52 | afterEach: function( assert ) { 53 | 54 | // Adds 1 assertion 55 | return createMockPromise( assert ); 56 | } 57 | }); 58 | 59 | QUnit.test( "fulfilled Promise", function( assert ) { 60 | assert.expect( 1 ); 61 | }); 62 | 63 | QUnit.module( "Promise-aware return values without beforeEach/afterEach" ); 64 | 65 | QUnit.test( "non-Promise", function( assert ) { 66 | assert.expect( 0 ); 67 | return {}; 68 | }); 69 | 70 | QUnit.test( "fulfilled Promise", function( assert ) { 71 | assert.expect( 1 ); 72 | 73 | // Adds 1 assertion 74 | return createMockPromise( assert ); 75 | }); 76 | 77 | QUnit.test( "fulfilled Promise with non-Promise async assertion", function( assert ) { 78 | assert.expect( 2 ); 79 | 80 | var done = assert.async(); 81 | setTimeout( function() { 82 | assert.ok( true ); 83 | done(); 84 | }, 100 ); 85 | 86 | // Adds 1 assertion 87 | return createMockPromise( assert ); 88 | }); 89 | -------------------------------------------------------------------------------- /src/core/config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Config object: Maintain internal state 3 | * Later exposed as QUnit.config 4 | * `config` initialized at top of scope 5 | */ 6 | var config = { 7 | // The queue of tests to run 8 | queue: [], 9 | 10 | // block until document ready 11 | blocking: true, 12 | 13 | // by default, run previously failed tests first 14 | // very useful in combination with "Hide passed tests" checked 15 | reorder: true, 16 | 17 | // by default, modify document.title when suite is done 18 | altertitle: true, 19 | 20 | // by default, scroll to top of the page when suite is done 21 | scrolltop: true, 22 | 23 | // depth up-to which object will be dumped 24 | maxDepth: 5, 25 | 26 | // when enabled, all tests must call expect() 27 | requireExpects: false, 28 | 29 | // add checkboxes that are persisted in the query-string 30 | // when enabled, the id is set to `true` as a `QUnit.config` property 31 | urlConfig: [ 32 | { 33 | id: "hidepassed", 34 | label: "Hide passed tests", 35 | tooltip: "Only show tests and assertions that fail. Stored as query-strings." 36 | }, 37 | { 38 | id: "noglobals", 39 | label: "Check for Globals", 40 | tooltip: "Enabling this will test if any test introduces new properties on the " + 41 | "global object (`window` in Browsers). Stored as query-strings." 42 | }, 43 | { 44 | id: "notrycatch", 45 | label: "No try-catch", 46 | tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging " + 47 | "exceptions in IE reasonable. Stored as query-strings." 48 | } 49 | ], 50 | 51 | // Set of all modules. 52 | modules: [], 53 | 54 | // The first unnamed module 55 | currentModule: { 56 | name: "", 57 | tests: [] 58 | }, 59 | 60 | callbacks: {} 61 | }; 62 | 63 | var urlParams = defined.document ? getUrlParams() : {}; 64 | 65 | // Push a loose unnamed module to the modules collection 66 | config.modules.push( config.currentModule ); 67 | 68 | if ( urlParams.filter === true ) { 69 | delete urlParams.filter; 70 | } 71 | 72 | // String search anywhere in moduleName+testName 73 | config.filter = urlParams.filter; 74 | 75 | config.testId = []; 76 | if ( urlParams.testId ) { 77 | // Ensure that urlParams.testId is an array 78 | urlParams.testId = decodeURIComponent( urlParams.testId ).split( "," ); 79 | for (var i = 0; i < urlParams.testId.length; i++ ) { 80 | config.testId.push( urlParams.testId[ i ] ); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/core/logging.js: -------------------------------------------------------------------------------- 1 | var loggingCallbacks = {}; 2 | 3 | // Register logging callbacks 4 | function registerLoggingCallbacks( obj ) { 5 | var i, l, key, 6 | callbackNames = [ "begin", "done", "log", "testStart", "testDone", 7 | "moduleStart", "moduleDone" ]; 8 | 9 | function registerLoggingCallback( key ) { 10 | var loggingCallback = function( callback ) { 11 | if ( objectType( callback ) !== "function" ) { 12 | throw new Error( 13 | "QUnit logging methods require a callback function as their first parameters." 14 | ); 15 | } 16 | 17 | config.callbacks[ key ].push( callback ); 18 | }; 19 | 20 | // DEPRECATED: This will be removed on QUnit 2.0.0+ 21 | // Stores the registered functions allowing restoring 22 | // at verifyLoggingCallbacks() if modified 23 | loggingCallbacks[ key ] = loggingCallback; 24 | 25 | return loggingCallback; 26 | } 27 | 28 | for ( i = 0, l = callbackNames.length; i < l; i++ ) { 29 | key = callbackNames[ i ]; 30 | 31 | // Initialize key collection of logging callback 32 | if ( objectType( config.callbacks[ key ] ) === "undefined" ) { 33 | config.callbacks[ key ] = []; 34 | } 35 | 36 | obj[ key ] = registerLoggingCallback( key ); 37 | } 38 | } 39 | 40 | function runLoggingCallbacks( key, args ) { 41 | var i, l, callbacks; 42 | 43 | callbacks = config.callbacks[ key ]; 44 | for ( i = 0, l = callbacks.length; i < l; i++ ) { 45 | callbacks[ i ]( args ); 46 | } 47 | } 48 | 49 | // DEPRECATED: This will be removed on 2.0.0+ 50 | // This function verifies if the loggingCallbacks were modified by the user 51 | // If so, it will restore it, assign the given callback and print a console warning 52 | function verifyLoggingCallbacks() { 53 | var loggingCallback, userCallback; 54 | 55 | for ( loggingCallback in loggingCallbacks ) { 56 | if ( QUnit[ loggingCallback ] !== loggingCallbacks[ loggingCallback ] ) { 57 | 58 | userCallback = QUnit[ loggingCallback ]; 59 | 60 | // Restore the callback function 61 | QUnit[ loggingCallback ] = loggingCallbacks[ loggingCallback ]; 62 | 63 | // Assign the deprecated given callback 64 | QUnit[ loggingCallback ]( userCallback ); 65 | 66 | if ( global.console && global.console.warn ) { 67 | global.console.warn( 68 | "QUnit." + loggingCallback + " was replaced with a new value.\n" + 69 | "Please, check out the documentation on how to apply logging callbacks.\n" + 70 | "Reference: http://api.qunitjs.com/category/callbacks/" 71 | ); 72 | } 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /test/stack-errors.js: -------------------------------------------------------------------------------- 1 | /* globals polluteGlobal: true */ 2 | 3 | // No pollution 4 | QUnit.test( "globals", function( assert ) { 5 | QUnit.config.noglobals = true; 6 | polluteGlobal = 1; 7 | assert.expect( 0 ); 8 | }); 9 | 10 | // Failing test 11 | QUnit.test( "failing", function( assert ) { 12 | assert.equal( "foo", "bar" ); 13 | }); 14 | 15 | // No assertions fail 16 | QUnit.test( "no assertions", function() { 17 | // nothing 18 | }); 19 | 20 | // start error inside of a test context 21 | QUnit.test( "QUnit.start()", function() { 22 | QUnit.start(); 23 | }); 24 | 25 | // Died on test 26 | QUnit.test( "dies on test", function() { 27 | throw new Error( "foo" ); 28 | }); 29 | 30 | // beforeEach die 31 | QUnit.module( "beforeEach fail", { 32 | beforeEach: function() { 33 | throw new Error( "foo" ); 34 | } 35 | }); 36 | QUnit.test( "module fails", function() { 37 | // ... 38 | }); 39 | 40 | // afterEach die 41 | QUnit.module( "afterEach fail", { 42 | afterEach: function() { 43 | throw new Error( "bar" ); 44 | } 45 | }); 46 | QUnit.test( "module fails", function() { 47 | // ... 48 | }); 49 | 50 | // assert.async post-resolution assertions fail 51 | QUnit.module( "assertions fail after assert.async flows are resolved" ); 52 | 53 | QUnit.test( "assert.ok", function( assert ) { 54 | assert.async()(); 55 | assert.ok( true, "This assertion should pass but have a failure logged before it" ); 56 | }); 57 | 58 | QUnit.test( "assert.equal", function( assert ) { 59 | assert.async()(); 60 | assert.equal( 1, 1, "This assertion should pass but have a failure logged before it" ); 61 | }); 62 | 63 | QUnit.test( "assert.throws", function( assert ) { 64 | assert.async()(); 65 | assert.throws(function() { 66 | throw new Error( "foo" ); 67 | }, "This assertion should pass but have a failure logged before it" ); 68 | }); 69 | 70 | QUnit.module( "globals" ); 71 | 72 | // start error outside of a test context 73 | setTimeout(function() { 74 | QUnit.start(); 75 | }, 0 ); 76 | 77 | // pushFailure outside of a test context 78 | setTimeout(function() { 79 | QUnit.pushFailure( true ); 80 | }, 0 ); 81 | 82 | // Assertion outside of a test context 83 | setTimeout(function() { 84 | QUnit.ok( true ); 85 | }, 0 ); 86 | 87 | // Trigger window.onerror 88 | setTimeout(function() { 89 | throw new Error( "foo" ); 90 | }, 0 ); 91 | 92 | // DEPRECATED: To be removed in QUnit 2.0.0 93 | // Trigger warnings by replacing the logging callbacks 94 | QUnit.begin = function() {}; 95 | QUnit.done = function() {}; 96 | QUnit.log = function() {}; 97 | QUnit.testStart = function() {}; 98 | QUnit.testDone = function() {}; 99 | QUnit.moduleStart = function() {}; 100 | QUnit.moduleDone = function() {}; 101 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/jquery/qunit.svg?branch=master)](https://travis-ci.org/jquery/qunit) [![Coverage Status](https://coveralls.io/repos/jquery/qunit/badge.svg)](https://coveralls.io/github/jquery/qunit) 2 | 3 | # [QUnit](http://qunitjs.com) - A JavaScript Unit Testing Framework. 4 | 5 | QUnit is a powerful, easy-to-use, JavaScript unit testing framework. It's used by the jQuery 6 | project to test its code and plugins but is capable of testing any generic 7 | JavaScript code (and even capable of testing JavaScript code on the server-side). 8 | 9 | QUnit is especially useful for regression testing: Whenever a bug is reported, 10 | write a test that asserts the existence of that particular bug. Then fix it and 11 | commit both. Every time you work on the code again, run the tests. If the bug 12 | comes up again - a regression - you'll spot it immediately and know how to fix 13 | it, because you know what code you just changed. 14 | 15 | Having good unit test coverage makes safe refactoring easy and cheap. You can 16 | run the tests after each small refactoring step and always know what change 17 | broke something. 18 | 19 | QUnit is similar to other unit testing frameworks like JUnit, but makes use of 20 | the features JavaScript provides and helps with testing code in the browser, e.g. 21 | with its stop/start facilities for testing asynchronous code. 22 | 23 | If you are interested in helping developing QUnit, you are in the right place. 24 | For related discussions, visit the 25 | [QUnit and Testing forum](http://forum.jquery.com/qunit-and-testing). 26 | 27 | ## Development 28 | 29 | To submit patches, fork the repository, create a branch for the change. Then implement 30 | the change, run `grunt` to lint and test it, then commit, push and create a pull request. 31 | 32 | Include some background for the change in the commit message and `Fixes #nnn`, referring 33 | to the issue number you're addressing. 34 | 35 | To run `grunt`, you need [Node.js](http://nodejs.org/download/), which includes `npm`, then `npm install -g grunt-cli`. That gives you a global grunt binary. For additional grunt tasks, also run `npm install`. 36 | 37 | ## Releases 38 | 39 | Use [jquery-release](https://github.com/jquery/jquery-release). The following aren't handled there, do that first: 40 | 41 | * Install [git-extras](https://github.com/visionmedia/git-extras) and run `git changelog` to update `History.md`. Clean up the changelog, removing merge commits, whitespace cleanups or other irrelevant commits. 42 | * Run `grunt authors` and add any new authors to AUTHORS.txt 43 | * Update the version property in `package.json` to have the right -pre version. Not necessary for patch releases. 44 | 45 | Commit these: 46 | 47 | Build: Prepare @VERSION release, including authors and history update 48 | 49 | Then run the script: 50 | 51 | node release.js --remote=jquery/qunit 52 | 53 | Update `jquery/qunitjs.com`, replacing previous versions with new ones: 54 | 55 | * pages/index.html 56 | * resources/*.html 57 | 58 | Update [GitHub releases](https://github.com/jquery/qunit/releases), use the changelog from `History.md`. 59 | 60 | Finally announce on Twitter @qunitjs (add highlights if possible, otherwise a 2nd tweet might do): 61 | 62 | Released @VERSION: https://github.com/jquery/qunit/releases/tag/1.17.0 63 | -------------------------------------------------------------------------------- /src/core/utilities.js: -------------------------------------------------------------------------------- 1 | var toString = Object.prototype.toString, 2 | hasOwn = Object.prototype.hasOwnProperty; 3 | 4 | // returns a new Array with the elements that are in a but not in b 5 | function diff( a, b ) { 6 | var i, j, 7 | result = a.slice(); 8 | 9 | for ( i = 0; i < result.length; i++ ) { 10 | for ( j = 0; j < b.length; j++ ) { 11 | if ( result[ i ] === b[ j ] ) { 12 | result.splice( i, 1 ); 13 | i--; 14 | break; 15 | } 16 | } 17 | } 18 | return result; 19 | } 20 | 21 | // from jquery.js 22 | function inArray( elem, array ) { 23 | if ( array.indexOf ) { 24 | return array.indexOf( elem ); 25 | } 26 | 27 | for ( var i = 0, length = array.length; i < length; i++ ) { 28 | if ( array[ i ] === elem ) { 29 | return i; 30 | } 31 | } 32 | 33 | return -1; 34 | } 35 | 36 | /** 37 | * Makes a clone of an object using only Array or Object as base, 38 | * and copies over the own enumerable properties. 39 | * 40 | * @param {Object} obj 41 | * @return {Object} New object with only the own properties (recursively). 42 | */ 43 | function objectValues ( obj ) { 44 | var key, val, 45 | vals = QUnit.is( "array", obj ) ? [] : {}; 46 | for ( key in obj ) { 47 | if ( hasOwn.call( obj, key ) ) { 48 | val = obj[ key ]; 49 | vals[ key ] = val === Object( val ) ? objectValues( val ) : val; 50 | } 51 | } 52 | return vals; 53 | } 54 | 55 | function extend( a, b, undefOnly ) { 56 | for ( var prop in b ) { 57 | if ( hasOwn.call( b, prop ) ) { 58 | 59 | // Avoid "Member not found" error in IE8 caused by messing with window.constructor 60 | // This block runs on every environment, so `global` is being used instead of `window` 61 | // to avoid errors on node. 62 | if ( prop !== "constructor" || a !== global ) { 63 | if ( b[ prop ] === undefined ) { 64 | delete a[ prop ]; 65 | } else if ( !( undefOnly && typeof a[ prop ] !== "undefined" ) ) { 66 | a[ prop ] = b[ prop ]; 67 | } 68 | } 69 | } 70 | } 71 | 72 | return a; 73 | } 74 | 75 | function objectType( obj ) { 76 | if ( typeof obj === "undefined" ) { 77 | return "undefined"; 78 | } 79 | 80 | // Consider: typeof null === object 81 | if ( obj === null ) { 82 | return "null"; 83 | } 84 | 85 | var match = toString.call( obj ).match( /^\[object\s(.*)\]$/ ), 86 | type = match && match[ 1 ] || ""; 87 | 88 | switch ( type ) { 89 | case "Number": 90 | if ( isNaN( obj ) ) { 91 | return "nan"; 92 | } 93 | return "number"; 94 | case "String": 95 | case "Boolean": 96 | case "Array": 97 | case "Set": 98 | case "Map": 99 | case "Date": 100 | case "RegExp": 101 | case "Function": 102 | return type.toLowerCase(); 103 | } 104 | if ( typeof obj === "object" ) { 105 | return "object"; 106 | } 107 | return undefined; 108 | } 109 | 110 | // Safe object type checking 111 | function is( type, obj ) { 112 | return QUnit.objectType( obj ) === type; 113 | } 114 | 115 | var getUrlParams = function() { 116 | var i, current; 117 | var urlParams = {}; 118 | var location = window.location; 119 | var params = location.search.slice( 1 ).split( "&" ); 120 | var length = params.length; 121 | 122 | if ( params[ 0 ] ) { 123 | for ( i = 0; i < length; i++ ) { 124 | current = params[ i ].split( "=" ); 125 | current[ 0 ] = decodeURIComponent( current[ 0 ] ); 126 | 127 | // allow just a key to turn on a flag, e.g., test.html?noglobals 128 | current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true; 129 | if ( urlParams[ current[ 0 ] ] ) { 130 | urlParams[ current[ 0 ] ] = [].concat( urlParams[ current[ 0 ] ], current[ 1 ] ); 131 | } else { 132 | urlParams[ current[ 0 ] ] = current[ 1 ]; 133 | } 134 | } 135 | } 136 | 137 | return urlParams; 138 | }; 139 | -------------------------------------------------------------------------------- /test/main/modules.js: -------------------------------------------------------------------------------- 1 | QUnit.module( "beforeEach/afterEach", { 2 | beforeEach: function() { 3 | this.lastHook = "module-beforeEach"; 4 | }, 5 | afterEach: function( assert ) { 6 | if ( this.hooksTest ) { 7 | assert.strictEqual( this.lastHook, "test-block", 8 | "Module's afterEach runs after current test block" ); 9 | this.lastHook = "module-afterEach"; 10 | } 11 | } 12 | }); 13 | 14 | QUnit.test( "hooks order", function( assert ) { 15 | assert.expect( 2 ); 16 | 17 | // This will trigger an assertion on the global and one on the module's afterEach 18 | this.hooksTest = true; 19 | 20 | assert.strictEqual( this.lastHook, "module-beforeEach", 21 | "Module's beforeEach runs before current test block" ); 22 | this.lastHook = "test-block"; 23 | }); 24 | 25 | QUnit.module( "Test context object", { 26 | beforeEach: function( assert ) { 27 | var key, 28 | keys = []; 29 | 30 | for ( key in this ) { 31 | keys.push( key ); 32 | } 33 | assert.deepEqual( keys, [ "helper" ] ); 34 | }, 35 | afterEach: function() {}, 36 | helper: function() {} 37 | }); 38 | 39 | QUnit.test( "keys", function( assert ) { 40 | assert.expect( 1 ); 41 | this.contextTest = true; 42 | }); 43 | 44 | QUnit.module( "afterEach and QUnit.stop", { 45 | beforeEach: function() { 46 | this.state = false; 47 | }, 48 | afterEach: function( assert ) { 49 | assert.strictEqual( this.state, true, "Test afterEach." ); 50 | } 51 | }); 52 | 53 | QUnit.test( "afterEach must be called after test ended", function( assert ) { 54 | var testContext = this; 55 | assert.expect( 1 ); 56 | QUnit.stop(); 57 | setTimeout(function() { 58 | testContext.state = true; 59 | QUnit.start(); 60 | }); 61 | }); 62 | 63 | QUnit.test( "parameter passed to stop increments semaphore n times", function( assert ) { 64 | var testContext = this; 65 | assert.expect( 1 ); 66 | QUnit.stop( 3 ); 67 | setTimeout(function() { 68 | QUnit.start(); 69 | QUnit.start(); 70 | }); 71 | setTimeout(function() { 72 | testContext.state = true; 73 | QUnit.start(); 74 | }, 1 ); 75 | }); 76 | 77 | QUnit.test( "parameter passed to start decrements semaphore n times", function( assert ) { 78 | var testContext = this; 79 | assert.expect( 1 ); 80 | QUnit.stop(); 81 | QUnit.stop(); 82 | QUnit.stop(); 83 | setTimeout(function() { 84 | testContext.state = true; 85 | QUnit.start( 3 ); 86 | }); 87 | }); 88 | 89 | QUnit.module( "async beforeEach test", { 90 | beforeEach: function( assert ) { 91 | QUnit.stop(); 92 | setTimeout(function() { 93 | assert.ok( true ); 94 | QUnit.start(); 95 | }); 96 | } 97 | }); 98 | 99 | QUnit.asyncTest( "module with async beforeEach", function( assert ) { 100 | assert.expect( 2 ); 101 | assert.ok( true ); 102 | QUnit.start(); 103 | }); 104 | 105 | QUnit.module( "async afterEach test", { 106 | afterEach: function( assert ) { 107 | QUnit.stop(); 108 | setTimeout(function() { 109 | assert.ok( true ); 110 | QUnit.start(); 111 | }); 112 | } 113 | }); 114 | 115 | QUnit.asyncTest( "module with async afterEach", function( assert ) { 116 | assert.expect( 2 ); 117 | assert.ok( true ); 118 | QUnit.start(); 119 | }); 120 | 121 | QUnit.module( "save scope", { 122 | foo: "foo", 123 | beforeEach: function( assert ) { 124 | assert.deepEqual( this.foo, "foo" ); 125 | this.foo = "bar"; 126 | }, 127 | afterEach: function( assert ) { 128 | assert.deepEqual( this.foo, "foobar" ); 129 | } 130 | }); 131 | 132 | QUnit.test( "scope check", function( assert ) { 133 | assert.expect( 3 ); 134 | assert.deepEqual( this.foo, "bar" ); 135 | this.foo = "foobar"; 136 | }); 137 | 138 | QUnit.module( "Deprecated setup/teardown", { 139 | setup: function() { 140 | this.deprecatedSetup = true; 141 | }, 142 | teardown: function( assert ) { 143 | assert.ok( this.deprecatedSetup ); 144 | } 145 | }); 146 | 147 | QUnit.test( "before/after order", function( assert ) { 148 | assert.expect( 1 ); 149 | }); 150 | -------------------------------------------------------------------------------- /.mailmap: -------------------------------------------------------------------------------- 1 | Jörn Zaefferer 2 | Jörn Zaefferer 3 | Ariel Flesler 4 | Scott González 5 | Richard Worth 6 | Philippe Rathé 7 | John Resig 8 | Will Moffat 9 | Jan Kassens 10 | Ziling Zhao 11 | Ryan Szulczewski 12 | Chris Lloyd 13 | Louis-Rémi Babé 14 | Louis-Rémi Babé 15 | Louis-Rémi Babé 16 | Jake Archibald 17 | Jake Archibald 18 | Frances Berriman 19 | Rune Halvorsen 20 | Rune Halvorsen 21 | Chris Thatcher 22 | Chris Thatcher 23 | Fábio Rehm 24 | Leon Sorokin 25 | Douglas Neiner 26 | Paul Elliott 27 | Nikita Vasilyev 28 | Benjamin Lee 29 | Paul Irish 30 | Oleg Slobodskoi 31 | Anton Matzneller 32 | Aurélien Bombo 33 | Mathias Bynens 34 | Erik Vold 35 | Wesley Walser 36 | Wesley Walser 37 | Rob Kinninmont 38 | Marc Portier 39 | Michael Righi 40 | Timo Tijhof 41 | Jan Alonzo 42 | Daniel Trebbien 43 | Bob Fanger 44 | Markus Messner-Chaney 45 | Trevor Parscal 46 | Ashar Voultoiz 47 | Jimmy Mabey 48 | Domenic Denicola 49 | Mike Sherov 50 | Seong-A Kong 51 | Graham Conzett 52 | Niall Smart 53 | Johan Sörlin 54 | Gijs Kruitbosch 55 | Erkan Yilmaz 56 | Jonathan Sanchez 57 | Keith Cirkel 58 | Rick Waldron 59 | Herbert Vojčík 60 | Richard Gibson 61 | Alex J Burke 62 | Sergii Kliuchnyk 63 | Sergii Kliuchnyk 64 | Corey Frang 65 | John Reeves 66 | John Reeves 67 | Vivin Paliath 68 | Joshua Niehus 69 | Glen Huang 70 | Glen Huang 71 | Jonas Ulrich 72 | Jamie Hoover 73 | Jamie Hoover 74 | James M. Greene 75 | Rodney Rehm 76 | Peter Wagenet 77 | Clog 78 | Clog 79 | Antranig Basman 80 | Antranig Basman 81 | Matthew Mirande 82 | Jared Wyles 83 | Dmitry Gusev 84 | Ian Wallis 85 | Ian Wallis 86 | Dan Andreescu 87 | Dan Andreescu 88 | Matthew DuVall 89 | Matthew DuVall 90 | Dave K. Smith 91 | David Vollbracht 92 | Jochen Ulrich 93 | -------------------------------------------------------------------------------- /test/reporter-html/reporter-html.js: -------------------------------------------------------------------------------- 1 | // The following tests need to run on their respective order 2 | QUnit.config.reorder = false; 3 | 4 | QUnit.module( "", { 5 | beforeEach: function() { 6 | }, 7 | afterEach: function( assert ) { 8 | 9 | // We can't use ok(false) inside script tags since some browsers 10 | // don't evaluate script tags inserted through innerHTML after domready. 11 | // Counting them before/after doesn't cover everything either as qunit-modulefilter 12 | // is created before any test is ran. So use ids instead. 13 | if ( document.getElementById( "qunit-unescaped-module" ) ) { 14 | 15 | // This can either be from in #qunit-modulefilter or #qunit-testresult 16 | assert.ok( false, "Unescaped module name" ); 17 | } 18 | if ( document.getElementById( "qunit-unescaped-test" ) ) { 19 | assert.ok( false, "Unescaped test name" ); 20 | } 21 | if ( document.getElementById( "qunit-unescaped-assertion" ) ) { 22 | assert.ok( false, "Unescaped test name" ); 23 | } 24 | } 25 | }); 26 | 27 | QUnit.test( "", function( assert ) { 28 | assert.expect( 1 ); 29 | assert.ok( true, "" ); 30 | }); 31 | 32 | QUnit.module( "display test info" ); 33 | 34 | QUnit.test( "running test name displayed", function( assert ) { 35 | assert.expect( 2 ); 36 | 37 | var displaying = document.getElementById( "qunit-testresult" ); 38 | 39 | assert.ok( /running test name displayed/.test( displaying.innerHTML ), 40 | "Expect test name to be found in displayed text" 41 | ); 42 | assert.ok( /display test info/.test( displaying.innerHTML ), 43 | "Expect module name to be found in displayed text" 44 | ); 45 | }); 46 | 47 | QUnit.module( "timing", { 48 | getPreviousTest: function( assert ) { 49 | return document.getElementById( "qunit-test-output-" + assert.test.testId ) 50 | .previousSibling; 51 | }, 52 | filterClass: function( elements ) { 53 | var i; 54 | for ( i = 0; i < elements.length; i++ ) { 55 | if ( /(^| )runtime( |$)/.test( elements[ i ].className ) ) { 56 | return elements[ i ]; 57 | } 58 | } 59 | }, 60 | afterEach: function( assert ) { 61 | var done; 62 | if ( this.delayNextSetup ) { 63 | this.delayNextSetup = false; 64 | done = assert.async(); 65 | setTimeout(function() { 66 | done(); 67 | }, 101 ); 68 | } 69 | } 70 | }); 71 | 72 | QUnit.test( "setup", function( assert ) { 73 | assert.expect( 0 ); 74 | this.delayNextSetup = true; 75 | }); 76 | 77 | QUnit.test( "basics", function( assert ) { 78 | assert.expect( 1 ); 79 | var previous = this.getPreviousTest( assert ), 80 | runtime = this.filterClass( previous.getElementsByTagName( "span" ) ); 81 | 82 | assert.ok( /^\d+ ms$/.test( runtime.innerHTML ), "Runtime reported in ms" ); 83 | }); 84 | 85 | QUnit.test( "values", function( assert ) { 86 | assert.expect( 2 ); 87 | 88 | var basics = this.getPreviousTest( assert ), 89 | setup = basics.previousSibling; 90 | 91 | basics = this.filterClass( basics.getElementsByTagName( "span" ) ); 92 | setup = this.filterClass( setup.getElementsByTagName( "span" ) ); 93 | 94 | assert.ok( parseInt( basics.innerHTML, 10 ) < 100, 95 | "Fast runtime for trivial test" 96 | ); 97 | assert.ok( parseInt( setup.innerHTML, 10 ) > 100, 98 | "Runtime includes beforeEach" 99 | ); 100 | }); 101 | 102 | QUnit.module( "source" ); 103 | 104 | QUnit.test( "setup", function( assert ) { 105 | assert.expect( 0 ); 106 | }); 107 | 108 | QUnit.test( "logs location", function( assert ) { 109 | var previous = document.getElementById( "qunit-test-output-" + assert.test.testId ) 110 | .previousSibling; 111 | var source = previous.lastChild; 112 | var stack = QUnit.stack(); 113 | 114 | // Verify QUnit supported stack trace 115 | if ( !stack ) { 116 | assert.equal( 117 | /(^| )qunit-source( |$)/.test( source.className ), 118 | false, 119 | "Don't add source information on unsupported environments" 120 | ); 121 | return; 122 | } 123 | 124 | assert.ok( /(^| )qunit-source( |$)/.test( source.className ), "Source element exists" ); 125 | assert.equal( source.firstChild.innerHTML, "Source: " ); 126 | 127 | // test/reporter-html/reporter-html.js is a direct reference to this test file 128 | assert.ok( /\/test\/reporter-html\/reporter-html\.js\:\d+/.test( source.innerHTML ), 129 | "Source references to the current file and line number" 130 | ); 131 | }); 132 | -------------------------------------------------------------------------------- /AUTHORS.txt: -------------------------------------------------------------------------------- 1 | Authors ordered by first contribution 2 | 3 | Jörn Zaefferer 4 | Ariel Flesler 5 | Scott González 6 | Richard Worth 7 | Philippe Rathé 8 | John Resig 9 | Will Moffat 10 | Jan Kassens 11 | Ziling Zhao 12 | Ryan Szulczewski 13 | Chris Lloyd 14 | Louis-Rémi Babé 15 | Jake Archibald 16 | Frances Berriman 17 | Rune Halvorsen 18 | Chris Thatcher 19 | Fábio Rehm 20 | Leon Sorokin 21 | Douglas Neiner 22 | Paul Elliott 23 | Nikita Vasilyev 24 | Benjamin Lee 25 | Paul Irish 26 | Oleg Slobodskoi 27 | Anton Matzneller 28 | Aurélien Bombo 29 | Mathias Bynens 30 | Erik Vold 31 | Wesley Walser 32 | Rob Kinninmont 33 | Marc Portier 34 | Michael Righi 35 | Timo Tijhof 36 | Jan Alonzo 37 | Daniel Trebbien 38 | Bob Fanger 39 | Markus Messner-Chaney 40 | Trevor Parscal 41 | Ashar Voultoiz 42 | Jimmy Mabey 43 | Domenic Denicola 44 | Mike Sherov 45 | Seong-A Kong 46 | Graham Conzett 47 | Niall Smart 48 | Johan Sörlin 49 | Gijs Kruitbosch 50 | Erkan Yilmaz 51 | Jonathan Sanchez 52 | Keith Cirkel 53 | Rick Waldron 54 | Herbert Vojčík 55 | Richard Gibson 56 | Alex J Burke 57 | Sergii Kliuchnyk 58 | Corey Frang 59 | John Reeves 60 | Antranig Basman 61 | Vivin Paliath 62 | Joshua Niehus 63 | Glen Huang 64 | Jochen Ulrich 65 | Jamie Hoover 66 | James M. Greene 67 | Rodney Rehm 68 | Peter Wagenet 69 | Clog 70 | Matthew Mirande 71 | Jared Wyles 72 | Dmitry Gusev 73 | Ian Wallis 74 | Dan Andreescu 75 | Matthew DuVall 76 | Dave K. Smith 77 | David Vollbracht 78 | Katie Gengler 79 | Joshua Peek 80 | Leonardo Balter 81 | Jeff Cooper 82 | Corey Frang 83 | Nathan Dauber 84 | Michał Gołębiowski 85 | XhmikosR 86 | Patrick Stapleton 87 | DarkPark 88 | Oleg Gaidarenko 89 | Mike Sidorov 90 | Don Kirkby 91 | don 92 | Sean Xu 93 | Matthew Beale 94 | Mislav Marohnić 95 | Anne-Gaelle Colom 96 | Leonardo Braga 97 | Jon Bretman 98 | Jonny Buchanan 99 | Adrian Phinney 100 | Lam Chau 101 | Henning Beyer 102 | Jesús Leganés Combarro 103 | Shivam Dixit 104 | Gaurav Mittal 105 | Kevin Partington 106 | Braulio Valdivielso Martínez 107 | Shinnosuke Watanabe 108 | Aurelio De Rosa 109 | Toh Chee Chuan 110 | Stefan Penner 111 | -------------------------------------------------------------------------------- /test/reporter-html/diff.js: -------------------------------------------------------------------------------- 1 | QUnit.module( "diff" ); 2 | 3 | QUnit.test( "throws if arguments are not strings", function( assert ) { 4 | assert.throws(function() { QUnit.diff( {}, "" ); }); 5 | assert.throws(function() { QUnit.diff( "", {} ); }); 6 | }); 7 | 8 | QUnit.test( "different strings", function( assert ) { 9 | var a = "abcd"; 10 | var b = "xkcd"; 11 | 12 | assert.equal( 13 | QUnit.diff( a, b ), 14 | "abxkcd", 15 | "QUnit.diff( 'abcd', 'xkcd' )" 16 | ); 17 | assert.equal( 18 | QUnit.diff( b, a ), 19 | "xkabcd", 20 | "QUnit.diff( 'xkcd', 'abcd' )" 21 | ); 22 | 23 | assert.equal( 24 | QUnit.diff( a, "" ), 25 | "abcd", 26 | "QUnit.diff( 'abcd', '' )" 27 | ); 28 | assert.equal( 29 | QUnit.diff( "", a ), 30 | "abcd", 31 | "QUnit.diff( '', 'abcd' )" 32 | ); 33 | 34 | assert.equal( 35 | QUnit.diff( "false", "true" ), 36 | "falstrue", 37 | "QUnit.diff( 'false', 'true' )" 38 | ); 39 | 40 | assert.equal( 41 | QUnit.diff( "true", "false" ), 42 | "trufalse", 43 | "QUnit.diff( 'true', 'false' )" 44 | ); 45 | }); 46 | 47 | QUnit.test( "additions", function( assert ) { 48 | var a = "do less!"; 49 | var b = "do less, write more!"; 50 | 51 | assert.equal( 52 | QUnit.diff( a, b ), 53 | "do less, write more!", 54 | "QUnit.diff( 'do less!', 'do less, write more!' )" 55 | ); 56 | }); 57 | 58 | QUnit.test( "removals", function( assert ) { 59 | var a = "do less, write more!"; 60 | var b = "do less!"; 61 | 62 | assert.equal( 63 | QUnit.diff( a, b ), 64 | "do less, write more!", 65 | "QUnit.diff( 'do less, write more!', 'do less!' )" 66 | ); 67 | }); 68 | 69 | QUnit.test( "equality shifts", function( assert ) { 70 | 71 | // ABAC -> ABAC 72 | var a = "AC"; 73 | var b = "ABAC"; 74 | 75 | assert.equal( 76 | QUnit.diff( a, b ), "ABAC" 77 | ); 78 | }); 79 | 80 | QUnit.test( "test with line mode on long strings", function( assert ) { 81 | var a = "QUnit is a powerful, easy-to-use JavaScript unit testing framework. " + 82 | "It's used by the jQuery, jQuery UI and jQuery Mobile projects and is " + 83 | "capable of testing any generic JavaScript code, including itself!"; 84 | 85 | var b = "QUnit is a very powerful, easy-to-use JavaScript unit testing framework. " + 86 | "It's used by the jQuery Core, jQuery UI and jQuery Mobile projects and is " + 87 | "capable of testing any JavaScript code, including itself!" + 88 | "QUnit was originally developed by John Resig as part of jQuery. In 2008 " + 89 | "it got its own home, name and API documentation, allowing others to use it " + 90 | "for their unit testing as well. At the time it still depended on jQuery. " + 91 | "A rewrite in 2009 fixed that, and now QUnit runs completely standalone. "; 92 | 93 | assert.equal( 94 | QUnit.diff( a, b ), 95 | "QUnit is a very powerful, easy-to-use " + 96 | "JavaScript unit testing framework. It's used by the jQuery " + 97 | "Core, jQuery UI and jQuery Mobile projects and is capable of" + 98 | " testing any generic JavaScript code, including " + 99 | "itself!" + 100 | "QUnit was originally developed by John Resig as part of jQuery. In " + 101 | "2008 it got its own home, name and API documentation, allowing others to" + 102 | " use it for their unit testing as well. At the time it still depended on" + 103 | " jQuery. A rewrite in 2009 fixed that, and now QUnit runs completely " + 104 | "standalone. " 105 | ); 106 | }); 107 | 108 | QUnit.test( "simplified diffs", function( assert ) { 109 | assert.equal( 110 | QUnit.diff( "BXYD", "AXYC" ), 111 | "BXYDAXYC", 112 | "return is not BAXYDC" 113 | ); 114 | 115 | assert.equal( 116 | QUnit.diff( "XD", "AXC" ), 117 | "XDAXC", 118 | "return is not AXDC" 119 | ); 120 | 121 | assert.equal( 122 | QUnit.diff( "A BC ", " B" ), 123 | "A BC ", 124 | "Swap insertions for deletions if diff is reversed" 125 | ); 126 | 127 | assert.equal( 128 | QUnit.diff( "abcxxx", "xxxdef" ), 129 | "abcxxxdef" 130 | ); 131 | 132 | assert.equal( 133 | QUnit.diff( "xxxabc", "defxxx" ), 134 | "defxxxabc" 135 | ); 136 | }); 137 | 138 | QUnit.test( "equal values", function( assert ) { 139 | assert.equal( 140 | QUnit.diff( "abc", "abc" ), 141 | "abc" 142 | ); 143 | 144 | assert.equal( 145 | QUnit.diff( "", "" ), 146 | "" 147 | ); 148 | }); 149 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true */ 2 | module.exports = function( grunt ) { 3 | 4 | require( "load-grunt-tasks" )( grunt ); 5 | 6 | function process( code, filepath ) { 7 | 8 | // Make coverage ignore external files 9 | if ( filepath.match( /^external\// ) ) { 10 | code = "/*istanbul ignore next */\n" + code; 11 | } 12 | 13 | return code 14 | 15 | // Embed version 16 | .replace( /@VERSION/g, grunt.config( "pkg" ).version ) 17 | 18 | // Embed date (yyyy-mm-ddThh:mmZ) 19 | .replace( /@DATE/g, ( new Date() ).toISOString().replace( /:\d+\.\d+Z$/, "Z" ) ); 20 | } 21 | 22 | grunt.initConfig({ 23 | pkg: grunt.file.readJSON( "package.json" ), 24 | concat: { 25 | "src-js": { 26 | options: { process: process }, 27 | src: [ 28 | "src/intro.js", 29 | "src/core/initialize.js", 30 | "src/core/utilities.js", 31 | "src/core/stacktrace.js", 32 | "src/core/config.js", 33 | "src/core/logging.js", 34 | "src/core/onerror.js", 35 | "src/core.js", 36 | "src/test.js", 37 | "src/assert.js", 38 | "src/equiv.js", 39 | "src/dump.js", 40 | "src/export.js", 41 | "src/diff.js", 42 | "src/outro.js", 43 | "reporter/html.js" 44 | ], 45 | dest: "dist/qunit.js" 46 | }, 47 | "src-css": { 48 | options: { process: process }, 49 | src: "src/qunit.css", 50 | dest: "dist/qunit.css" 51 | } 52 | }, 53 | jshint: { 54 | options: { 55 | jshintrc: true 56 | }, 57 | all: [ 58 | "*.js", 59 | "{test,dist}/**/*.js", 60 | "build/*.js" 61 | ] 62 | }, 63 | jscs: { 64 | options: { 65 | config: ".jscsrc" 66 | }, 67 | all: [ 68 | "<%= jshint.all %>", 69 | "!test/main/deepEqual.js" 70 | ] 71 | }, 72 | search: { 73 | options: { 74 | 75 | // Ensure that the only HTML entities used are those with a special status in XHTML 76 | // and that any common singleton/empty HTML elements end with the XHTML-compliant 77 | // "/>"rather than ">" 78 | searchString: /(&(?!gt|lt|amp|quot)[A-Za-z0-9]+;|<(?:hr|HR|br|BR|input|INPUT)(?![^>]*\/>)(?:\s+[^>]*)?>)/g, 79 | logFormat: "console", 80 | failOnMatch: true 81 | }, 82 | xhtml: [ 83 | "src/**/*.js", 84 | "reporter/**/*.js" 85 | ] 86 | }, 87 | qunit: { 88 | options: { 89 | timeout: 30000, 90 | "--web-security": "no", 91 | coverage: { 92 | src: "dist/qunit.js", 93 | instrumentedFiles: "temp/", 94 | htmlReport: "build/report/coverage", 95 | lcovReport: "build/report/lcov", 96 | linesThresholdPct: 70 97 | } 98 | }, 99 | qunit: [ 100 | "test/index.html", 101 | "test/autostart.html", 102 | "test/startError.html", 103 | "test/logs.html", 104 | "test/setTimeout.html", 105 | "test/amd.html", 106 | "test/reporter-html/index.html", 107 | "test/reporter-html/legacy-markup.html", 108 | "test/reporter-html/no-qunit-element.html" 109 | ] 110 | }, 111 | coveralls: { 112 | options: { 113 | force: true 114 | }, 115 | all: { 116 | 117 | // LCOV coverage file relevant to every target 118 | src: "build/report/lcov/lcov.info" 119 | } 120 | }, 121 | watch: { 122 | options: { 123 | atBegin: true 124 | }, 125 | files: [ 126 | ".jshintrc", 127 | "*.js", 128 | "build/*.js", 129 | "{src,test,reporter}/**/*.js", 130 | "src/qunit.css", 131 | "test/**/*.html" 132 | ], 133 | tasks: "default" 134 | } 135 | }); 136 | 137 | // TODO: Extract this task later, if feasible 138 | // Also spawn a separate process to keep tests atomic 139 | grunt.registerTask( "test-on-node", function() { 140 | var testActive = false, 141 | runDone = false, 142 | done = this.async(), 143 | QUnit = require( "./dist/qunit" ); 144 | 145 | global.QUnit = QUnit; 146 | 147 | QUnit.testStart(function() { 148 | testActive = true; 149 | }); 150 | QUnit.log(function( details ) { 151 | if ( !testActive || details.result ) { 152 | return; 153 | } 154 | var message = "name: " + details.name + " module: " + details.module + 155 | " message: " + details.message; 156 | grunt.log.error( message ); 157 | }); 158 | QUnit.testDone(function() { 159 | testActive = false; 160 | }); 161 | QUnit.done(function( details ) { 162 | if ( runDone ) { 163 | return; 164 | } 165 | var succeeded = ( details.failed === 0 ), 166 | message = details.total + " assertions in (" + details.runtime + "ms), passed: " + 167 | details.passed + ", failed: " + details.failed; 168 | if ( succeeded ) { 169 | grunt.log.ok( message ); 170 | } else { 171 | grunt.log.error( message ); 172 | } 173 | done( succeeded ); 174 | runDone = true; 175 | }); 176 | QUnit.config.autorun = false; 177 | 178 | require( "./test/logs" ); 179 | require( "./test/main/test" ); 180 | require( "./test/main/assert" ); 181 | require( "./test/main/async" ); 182 | require( "./test/main/promise" ); 183 | require( "./test/main/modules" ); 184 | require( "./test/main/deepEqual" ); 185 | require( "./test/main/stack" ); 186 | require( "./test/globals-node" ); 187 | 188 | QUnit.load(); 189 | }); 190 | 191 | grunt.registerTask( "build", [ "concat" ] ); 192 | grunt.registerTask( "default", [ "build", "jshint", "jscs", "search", "qunit", "test-on-node" ] ); 193 | 194 | }; 195 | -------------------------------------------------------------------------------- /src/qunit.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * QUnit @VERSION 3 | * http://qunitjs.com/ 4 | * 5 | * Copyright jQuery Foundation and other contributors 6 | * Released under the MIT license 7 | * http://jquery.org/license 8 | * 9 | * Date: @DATE 10 | */ 11 | 12 | /** Font Family and Sizes */ 13 | 14 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult { 15 | font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif; 16 | } 17 | 18 | #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } 19 | #qunit-tests { font-size: smaller; } 20 | 21 | 22 | /** Resets */ 23 | 24 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter { 25 | margin: 0; 26 | padding: 0; 27 | } 28 | 29 | 30 | /** Header */ 31 | 32 | #qunit-header { 33 | padding: 0.5em 0 0.5em 1em; 34 | 35 | color: #8699A4; 36 | background-color: #0D3349; 37 | 38 | font-size: 1.5em; 39 | line-height: 1em; 40 | font-weight: 400; 41 | 42 | border-radius: 5px 5px 0 0; 43 | } 44 | 45 | #qunit-header a { 46 | text-decoration: none; 47 | color: #C2CCD1; 48 | } 49 | 50 | #qunit-header a:hover, 51 | #qunit-header a:focus { 52 | color: #FFF; 53 | } 54 | 55 | #qunit-testrunner-toolbar label { 56 | display: inline-block; 57 | padding: 0 0.5em 0 0.1em; 58 | } 59 | 60 | #qunit-banner { 61 | height: 5px; 62 | } 63 | 64 | #qunit-testrunner-toolbar { 65 | padding: 0.5em 1em 0.5em 1em; 66 | color: #5E740B; 67 | background-color: #EEE; 68 | overflow: hidden; 69 | } 70 | 71 | #qunit-userAgent { 72 | padding: 0.5em 1em 0.5em 1em; 73 | background-color: #2B81AF; 74 | color: #FFF; 75 | text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; 76 | } 77 | 78 | #qunit-modulefilter-container { 79 | float: right; 80 | padding: 0.2em; 81 | } 82 | 83 | .qunit-url-config { 84 | display: inline-block; 85 | padding: 0.1em; 86 | } 87 | 88 | .qunit-filter { 89 | display: block; 90 | float: right; 91 | margin-left: 1em; 92 | } 93 | 94 | /** Tests: Pass/Fail */ 95 | 96 | #qunit-tests { 97 | list-style-position: inside; 98 | } 99 | 100 | #qunit-tests li { 101 | padding: 0.4em 1em 0.4em 1em; 102 | border-bottom: 1px solid #FFF; 103 | list-style-position: inside; 104 | } 105 | 106 | #qunit-tests > li { 107 | display: none; 108 | } 109 | 110 | #qunit-tests li.running, 111 | #qunit-tests li.pass, 112 | #qunit-tests li.fail, 113 | #qunit-tests li.skipped { 114 | display: list-item; 115 | } 116 | 117 | #qunit-tests.hidepass li.running, 118 | #qunit-tests.hidepass li.pass { 119 | visibility: hidden; 120 | position: absolute; 121 | width: 0; 122 | height: 0; 123 | padding: 0; 124 | border: 0; 125 | margin: 0; 126 | } 127 | 128 | #qunit-tests li strong { 129 | cursor: pointer; 130 | } 131 | 132 | #qunit-tests li.skipped strong { 133 | cursor: default; 134 | } 135 | 136 | #qunit-tests li a { 137 | padding: 0.5em; 138 | color: #C2CCD1; 139 | text-decoration: none; 140 | } 141 | 142 | #qunit-tests li p a { 143 | padding: 0.25em; 144 | color: #6B6464; 145 | } 146 | #qunit-tests li a:hover, 147 | #qunit-tests li a:focus { 148 | color: #000; 149 | } 150 | 151 | #qunit-tests li .runtime { 152 | float: right; 153 | font-size: smaller; 154 | } 155 | 156 | .qunit-assert-list { 157 | margin-top: 0.5em; 158 | padding: 0.5em; 159 | 160 | background-color: #FFF; 161 | 162 | border-radius: 5px; 163 | } 164 | 165 | .qunit-source { 166 | margin: 0.6em 0 0.3em; 167 | } 168 | 169 | .qunit-collapsed { 170 | display: none; 171 | } 172 | 173 | #qunit-tests table { 174 | border-collapse: collapse; 175 | margin-top: 0.2em; 176 | } 177 | 178 | #qunit-tests th { 179 | text-align: right; 180 | vertical-align: top; 181 | padding: 0 0.5em 0 0; 182 | } 183 | 184 | #qunit-tests td { 185 | vertical-align: top; 186 | } 187 | 188 | #qunit-tests pre { 189 | margin: 0; 190 | white-space: pre-wrap; 191 | word-wrap: break-word; 192 | } 193 | 194 | #qunit-tests del { 195 | background-color: #E0F2BE; 196 | color: #374E0C; 197 | text-decoration: none; 198 | } 199 | 200 | #qunit-tests ins { 201 | background-color: #FFCACA; 202 | color: #500; 203 | text-decoration: none; 204 | } 205 | 206 | /*** Test Counts */ 207 | 208 | #qunit-tests b.counts { color: #000; } 209 | #qunit-tests b.passed { color: #5E740B; } 210 | #qunit-tests b.failed { color: #710909; } 211 | 212 | #qunit-tests li li { 213 | padding: 5px; 214 | background-color: #FFF; 215 | border-bottom: none; 216 | list-style-position: inside; 217 | } 218 | 219 | /*** Passing Styles */ 220 | 221 | #qunit-tests li li.pass { 222 | color: #3C510C; 223 | background-color: #FFF; 224 | border-left: 10px solid #C6E746; 225 | } 226 | 227 | #qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } 228 | #qunit-tests .pass .test-name { color: #366097; } 229 | 230 | #qunit-tests .pass .test-actual, 231 | #qunit-tests .pass .test-expected { color: #999; } 232 | 233 | #qunit-banner.qunit-pass { background-color: #C6E746; } 234 | 235 | /*** Failing Styles */ 236 | 237 | #qunit-tests li li.fail { 238 | color: #710909; 239 | background-color: #FFF; 240 | border-left: 10px solid #EE5757; 241 | white-space: pre; 242 | } 243 | 244 | #qunit-tests > li:last-child { 245 | border-radius: 0 0 5px 5px; 246 | } 247 | 248 | #qunit-tests .fail { color: #000; background-color: #EE5757; } 249 | #qunit-tests .fail .test-name, 250 | #qunit-tests .fail .module-name { color: #000; } 251 | 252 | #qunit-tests .fail .test-actual { color: #EE5757; } 253 | #qunit-tests .fail .test-expected { color: #008000; } 254 | 255 | #qunit-banner.qunit-fail { background-color: #EE5757; } 256 | 257 | /*** Skipped tests */ 258 | 259 | #qunit-tests .skipped { 260 | background-color: #EBECE9; 261 | } 262 | 263 | #qunit-tests .qunit-skipped-label { 264 | background-color: #F4FF77; 265 | display: inline-block; 266 | font-style: normal; 267 | color: #366097; 268 | line-height: 1.8em; 269 | padding: 0 0.5em; 270 | margin: -0.4em 0.4em -0.4em 0; 271 | } 272 | 273 | /** Result */ 274 | 275 | #qunit-testresult { 276 | padding: 0.5em 1em 0.5em 1em; 277 | 278 | color: #2B81AF; 279 | background-color: #D2E0E6; 280 | 281 | border-bottom: 1px solid #FFF; 282 | } 283 | #qunit-testresult .module-name { 284 | font-weight: 700; 285 | } 286 | 287 | /** Fixture */ 288 | 289 | #qunit-fixture { 290 | position: absolute; 291 | top: -10000px; 292 | left: -10000px; 293 | width: 1000px; 294 | height: 1000px; 295 | } 296 | -------------------------------------------------------------------------------- /src/assert.js: -------------------------------------------------------------------------------- 1 | function Assert( testContext ) { 2 | this.test = testContext; 3 | } 4 | 5 | // Assert helpers 6 | QUnit.assert = Assert.prototype = { 7 | 8 | // Specify the number of expected assertions to guarantee that failed test 9 | // (no assertions are run at all) don't slip through. 10 | expect: function( asserts ) { 11 | if ( arguments.length === 1 ) { 12 | this.test.expected = asserts; 13 | } else { 14 | return this.test.expected; 15 | } 16 | }, 17 | 18 | // Increment this Test's semaphore counter, then return a single-use function that 19 | // decrements that counter a maximum of once. 20 | async: function() { 21 | var test = this.test, 22 | popped = false; 23 | 24 | test.semaphore += 1; 25 | test.usedAsync = true; 26 | pauseProcessing(); 27 | 28 | return function done() { 29 | if ( !popped ) { 30 | test.semaphore -= 1; 31 | popped = true; 32 | resumeProcessing(); 33 | } else { 34 | test.pushFailure( "Called the callback returned from `assert.async` more than once", 35 | sourceFromStacktrace( 2 ) ); 36 | } 37 | }; 38 | }, 39 | 40 | // Exports test.push() to the user API 41 | push: function( /* result, actual, expected, message, negative */ ) { 42 | var assert = this, 43 | currentTest = ( assert instanceof Assert && assert.test ) || QUnit.config.current; 44 | 45 | // Backwards compatibility fix. 46 | // Allows the direct use of global exported assertions and QUnit.assert.* 47 | // Although, it's use is not recommended as it can leak assertions 48 | // to other tests from async tests, because we only get a reference to the current test, 49 | // not exactly the test where assertion were intended to be called. 50 | if ( !currentTest ) { 51 | throw new Error( "assertion outside test context, in " + sourceFromStacktrace( 2 ) ); 52 | } 53 | 54 | if ( currentTest.usedAsync === true && currentTest.semaphore === 0 ) { 55 | currentTest.pushFailure( "Assertion after the final `assert.async` was resolved", 56 | sourceFromStacktrace( 2 ) ); 57 | 58 | // Allow this assertion to continue running anyway... 59 | } 60 | 61 | if ( !( assert instanceof Assert ) ) { 62 | assert = currentTest.assert; 63 | } 64 | return assert.test.push.apply( assert.test, arguments ); 65 | }, 66 | 67 | ok: function( result, message ) { 68 | message = message || ( result ? "okay" : "failed, expected argument to be truthy, was: " + 69 | QUnit.dump.parse( result ) ); 70 | this.push( !!result, result, true, message ); 71 | }, 72 | 73 | notOk: function( result, message ) { 74 | message = message || ( !result ? "okay" : "failed, expected argument to be falsy, was: " + 75 | QUnit.dump.parse( result ) ); 76 | this.push( !result, result, false, message, true ); 77 | }, 78 | 79 | equal: function( actual, expected, message ) { 80 | /*jshint eqeqeq:false */ 81 | this.push( expected == actual, actual, expected, message ); 82 | }, 83 | 84 | notEqual: function( actual, expected, message ) { 85 | /*jshint eqeqeq:false */ 86 | this.push( expected != actual, actual, expected, message, true ); 87 | }, 88 | 89 | propEqual: function( actual, expected, message ) { 90 | actual = objectValues( actual ); 91 | expected = objectValues( expected ); 92 | this.push( QUnit.equiv( actual, expected ), actual, expected, message ); 93 | }, 94 | 95 | notPropEqual: function( actual, expected, message ) { 96 | actual = objectValues( actual ); 97 | expected = objectValues( expected ); 98 | this.push( !QUnit.equiv( actual, expected ), actual, expected, message, true ); 99 | }, 100 | 101 | deepEqual: function( actual, expected, message ) { 102 | this.push( QUnit.equiv( actual, expected ), actual, expected, message ); 103 | }, 104 | 105 | notDeepEqual: function( actual, expected, message ) { 106 | this.push( !QUnit.equiv( actual, expected ), actual, expected, message, true ); 107 | }, 108 | 109 | strictEqual: function( actual, expected, message ) { 110 | this.push( expected === actual, actual, expected, message ); 111 | }, 112 | 113 | notStrictEqual: function( actual, expected, message ) { 114 | this.push( expected !== actual, actual, expected, message, true ); 115 | }, 116 | 117 | "throws": function( block, expected, message ) { 118 | var actual, expectedType, 119 | expectedOutput = expected, 120 | ok = false, 121 | currentTest = ( this instanceof Assert && this.test ) || QUnit.config.current; 122 | 123 | // 'expected' is optional unless doing string comparison 124 | if ( message == null && typeof expected === "string" ) { 125 | message = expected; 126 | expected = null; 127 | } 128 | 129 | currentTest.ignoreGlobalErrors = true; 130 | try { 131 | block.call( currentTest.testEnvironment ); 132 | } catch (e) { 133 | actual = e; 134 | } 135 | currentTest.ignoreGlobalErrors = false; 136 | 137 | if ( actual ) { 138 | expectedType = QUnit.objectType( expected ); 139 | 140 | // we don't want to validate thrown error 141 | if ( !expected ) { 142 | ok = true; 143 | expectedOutput = null; 144 | 145 | // expected is a regexp 146 | } else if ( expectedType === "regexp" ) { 147 | ok = expected.test( errorString( actual ) ); 148 | 149 | // expected is a string 150 | } else if ( expectedType === "string" ) { 151 | ok = expected === errorString( actual ); 152 | 153 | // expected is a constructor, maybe an Error constructor 154 | } else if ( expectedType === "function" && actual instanceof expected ) { 155 | ok = true; 156 | 157 | // expected is an Error object 158 | } else if ( expectedType === "object" ) { 159 | ok = actual instanceof expected.constructor && 160 | actual.name === expected.name && 161 | actual.message === expected.message; 162 | 163 | // expected is a validation function which returns true if validation passed 164 | } else if ( expectedType === "function" && expected.call( {}, actual ) === true ) { 165 | expectedOutput = null; 166 | ok = true; 167 | } 168 | } 169 | 170 | currentTest.assert.push( ok, actual, expectedOutput, message ); 171 | } 172 | }; 173 | 174 | // Provide an alternative to assert.throws(), for environments that consider throws a reserved word 175 | // Known to us are: Closure Compiler, Narwhal 176 | (function() { 177 | /*jshint sub:true */ 178 | Assert.prototype.raises = Assert.prototype[ "throws" ]; 179 | }()); 180 | 181 | function errorString( error ) { 182 | var name, message, 183 | resultErrorString = error.toString(); 184 | if ( resultErrorString.substring( 0, 7 ) === "[object" ) { 185 | name = error.name ? error.name.toString() : "Error"; 186 | message = error.message ? error.message.toString() : ""; 187 | if ( name && message ) { 188 | return name + ": " + message; 189 | } else if ( name ) { 190 | return name; 191 | } else if ( message ) { 192 | return message; 193 | } else { 194 | return "Error"; 195 | } 196 | } else { 197 | return resultErrorString; 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /test/main/dump.js: -------------------------------------------------------------------------------- 1 | QUnit.module( "dump", { 2 | teardown: function() { 3 | QUnit.dump.maxDepth = null; 4 | } 5 | }); 6 | 7 | QUnit.test( "dump output", function( assert ) { 8 | assert.equal( QUnit.dump.parse( [ 1, 2 ] ), "[\n 1,\n 2\n]" ); 9 | assert.equal( QUnit.dump.parse( { top: 5, left: 0 } ), "{\n \"left\": 0,\n \"top\": 5\n}" ); 10 | if ( typeof document !== "undefined" && document.getElementById( "qunit-header" ) ) { 11 | assert.equal( 12 | QUnit.dump.parse( document.getElementById( "qunit-header" ) ), 13 | "

    " 14 | ); 15 | assert.equal( 16 | QUnit.dump.parse( document.getElementsByTagName( "h1" ) ), 17 | "[\n

    \n]" 18 | ); 19 | } 20 | }); 21 | 22 | QUnit.test( "dump output, shallow", function( assert ) { 23 | var obj = { 24 | top: { 25 | middle: { 26 | bottom: 0 27 | } 28 | }, 29 | left: 0 30 | }; 31 | assert.expect( 4 ); 32 | QUnit.dump.maxDepth = 1; 33 | assert.equal( QUnit.dump.parse( obj ), "{\n \"left\": 0,\n \"top\": [object Object]\n}" ); 34 | 35 | QUnit.dump.maxDepth = 2; 36 | assert.equal( 37 | QUnit.dump.parse( obj ), 38 | "{\n \"left\": 0,\n \"top\": {\n \"middle\": [object Object]\n }\n}" 39 | ); 40 | 41 | QUnit.dump.maxDepth = 3; 42 | assert.equal( 43 | QUnit.dump.parse( obj ), 44 | "{\n \"left\": 0,\n \"top\": {\n \"middle\": {\n \"bottom\": 0\n }\n }\n}" 45 | ); 46 | 47 | QUnit.dump.maxDepth = 5; 48 | assert.equal( 49 | QUnit.dump.parse( obj ), 50 | "{\n \"left\": 0,\n \"top\": {\n \"middle\": {\n \"bottom\": 0\n }\n }\n}" 51 | ); 52 | }); 53 | 54 | QUnit.test( "dump, TypeError properties", function( assert ) { 55 | function CustomError( message ) { 56 | this.message = message; 57 | } 58 | 59 | CustomError.prototype.toString = function() { 60 | return this.message; 61 | }; 62 | var customError = new CustomError( "sad puppy" ), 63 | typeError = new TypeError( "crying kitten" ), 64 | expectedCustomMessage = "\"message\": \"sad puppy\"", 65 | expectedTypeMessage = "\"message\": \"crying kitten\"", 66 | expectedTypeName = "\"name\": \"TypeError\"", 67 | 68 | dumpedCustomError = QUnit.dump.parse( customError ), 69 | dumpedTypeError = QUnit.dump.parse( typeError ), 70 | 71 | dumpedTypeErrorWithEnumerable; 72 | 73 | // Test when object has some enumerable properties by adding one 74 | typeError.hasCheeseburger = true; 75 | 76 | dumpedTypeErrorWithEnumerable = QUnit.dump.parse( typeError ); 77 | 78 | assert.push( 79 | dumpedCustomError.indexOf(expectedCustomMessage) >= 0, 80 | dumpedCustomError, 81 | expectedCustomMessage, 82 | "custom error contains message field" ); 83 | assert.push( 84 | dumpedTypeError.indexOf(expectedTypeMessage) >= 0, 85 | dumpedTypeError, 86 | expectedTypeMessage, 87 | "type error contains message field" ); 88 | assert.push( 89 | dumpedTypeError.indexOf(expectedTypeName) >= 0, 90 | dumpedTypeError, 91 | expectedTypeName, 92 | "type error contains name field" ); 93 | assert.push( 94 | dumpedTypeErrorWithEnumerable.indexOf(expectedTypeMessage) >= 0, 95 | dumpedTypeErrorWithEnumerable, 96 | expectedTypeMessage, 97 | "type error with enumerable field contains message field" ); 98 | }); 99 | 100 | QUnit.module( "dump, recursions", { 101 | Wrap: function( x ) { 102 | this.wrap = x; 103 | if ( x === undefined ) { 104 | this.first = true; 105 | } 106 | }, 107 | chainwrap: function( depth, first, prev ) { 108 | depth = depth || 0; 109 | var last = prev || new this.Wrap(); 110 | first = first || last; 111 | 112 | if ( depth === 1 ) { 113 | first.wrap = last; 114 | } 115 | if ( depth > 1 ) { 116 | last = this.chainwrap( depth - 1, first, new this.Wrap( last ) ); 117 | } 118 | 119 | return last; 120 | } 121 | }); 122 | 123 | QUnit.test( "Check dump recursion", function( assert ) { 124 | assert.expect( 4 ); 125 | 126 | var noref, nodump, selfref, selfdump, parentref, parentdump, circref, circdump; 127 | 128 | noref = this.chainwrap( 0 ); 129 | nodump = QUnit.dump.parse( noref ); 130 | assert.equal( nodump, "{\n \"first\": true,\n \"wrap\": undefined\n}" ); 131 | 132 | selfref = this.chainwrap( 1 ); 133 | selfdump = QUnit.dump.parse( selfref ); 134 | assert.equal( selfdump, "{\n \"first\": true,\n \"wrap\": recursion(-1)\n}" ); 135 | 136 | parentref = this.chainwrap( 2 ); 137 | parentdump = QUnit.dump.parse( parentref ); 138 | assert.equal( parentdump, 139 | "{\n \"wrap\": {\n \"first\": true,\n \"wrap\": recursion(-2)\n }\n}" 140 | ); 141 | 142 | circref = this.chainwrap( 10 ); 143 | circdump = QUnit.dump.parse( circref ); 144 | assert.ok( new RegExp( "recursion\\(-10\\)" ).test( circdump ), 145 | "(" + circdump + ") should show -10 recursion level" 146 | ); 147 | }); 148 | 149 | QUnit.test( "Check equal/deepEqual recursion", function( assert ) { 150 | var noRecursion, selfref, circref; 151 | 152 | noRecursion = this.chainwrap( 0 ); 153 | assert.equal( noRecursion, noRecursion, "I should be equal to me." ); 154 | assert.deepEqual( noRecursion, noRecursion, "... and so in depth." ); 155 | 156 | selfref = this.chainwrap( 1 ); 157 | assert.equal( selfref, selfref, "Even so if I nest myself." ); 158 | assert.deepEqual( selfref, selfref, "... into the depth." ); 159 | 160 | circref = this.chainwrap( 10 ); 161 | assert.equal( circref, circref, "Or hide that through some levels of indirection." ); 162 | assert.deepEqual( circref, circref, "... and checked on all levels!" ); 163 | }); 164 | 165 | QUnit.test( "Circular reference with arrays", function( assert ) { 166 | var arr, arrdump, obj, childarr, objdump, childarrdump; 167 | 168 | // pure array self-ref 169 | arr = []; 170 | arr.push( arr ); 171 | 172 | arrdump = QUnit.dump.parse( arr ); 173 | 174 | assert.equal( arrdump, "[\n recursion(-1)\n]" ); 175 | assert.equal( arr, arr[ 0 ], "no endless stack when trying to dump arrays with circular ref" ); 176 | 177 | // mix obj-arr circular ref 178 | obj = {}; 179 | childarr = [ obj ]; 180 | obj.childarr = childarr; 181 | 182 | objdump = QUnit.dump.parse( obj ); 183 | childarrdump = QUnit.dump.parse( childarr ); 184 | 185 | assert.equal( objdump, "{\n \"childarr\": [\n recursion(-2)\n ]\n}" ); 186 | assert.equal( childarrdump, "[\n {\n \"childarr\": recursion(-2)\n }\n]" ); 187 | 188 | assert.equal( obj.childarr, childarr, 189 | "no endless stack when trying to dump array/object mix with circular ref" 190 | ); 191 | assert.equal( childarr[ 0 ], obj, 192 | "no endless stack when trying to dump array/object mix with circular ref" 193 | ); 194 | 195 | }); 196 | 197 | QUnit.test( "Circular reference - test reported by soniciq in #105", function( assert ) { 198 | var a, b, barr, 199 | MyObject = function() {}; 200 | MyObject.prototype.parent = function( obj ) { 201 | if ( obj === undefined ) { 202 | return this._parent; 203 | } 204 | this._parent = obj; 205 | }; 206 | MyObject.prototype.children = function( obj ) { 207 | if ( obj === undefined ) { 208 | return this._children; 209 | } 210 | this._children = obj; 211 | }; 212 | 213 | a = new MyObject(); 214 | b = new MyObject(); 215 | 216 | barr = [ b ]; 217 | a.children( barr ); 218 | b.parent( a ); 219 | 220 | assert.equal( a.children(), barr ); 221 | assert.deepEqual( a.children(), [ b ] ); 222 | }); 223 | -------------------------------------------------------------------------------- /src/core.js: -------------------------------------------------------------------------------- 1 | QUnit.urlParams = urlParams; 2 | 3 | // Figure out if we're running the tests from a server or not 4 | QUnit.isLocal = !( defined.document && window.location.protocol !== "file:" ); 5 | 6 | // Expose the current QUnit version 7 | QUnit.version = "@VERSION"; 8 | 9 | extend( QUnit, { 10 | 11 | // call on start of module test to prepend name to all tests 12 | module: function( name, testEnvironment ) { 13 | var currentModule = { 14 | name: name, 15 | testEnvironment: testEnvironment, 16 | tests: [] 17 | }; 18 | 19 | // DEPRECATED: handles setup/teardown functions, 20 | // beforeEach and afterEach should be used instead 21 | if ( testEnvironment && testEnvironment.setup ) { 22 | testEnvironment.beforeEach = testEnvironment.setup; 23 | delete testEnvironment.setup; 24 | } 25 | if ( testEnvironment && testEnvironment.teardown ) { 26 | testEnvironment.afterEach = testEnvironment.teardown; 27 | delete testEnvironment.teardown; 28 | } 29 | 30 | config.modules.push( currentModule ); 31 | config.currentModule = currentModule; 32 | }, 33 | 34 | // DEPRECATED: QUnit.asyncTest() will be removed in QUnit 2.0. 35 | asyncTest: asyncTest, 36 | 37 | test: test, 38 | 39 | skip: skip, 40 | 41 | // DEPRECATED: The functionality of QUnit.start() will be altered in QUnit 2.0. 42 | // In QUnit 2.0, invoking it will ONLY affect the `QUnit.config.autostart` blocking behavior. 43 | start: function( count ) { 44 | var globalStartAlreadyCalled = globalStartCalled; 45 | 46 | if ( !config.current ) { 47 | globalStartCalled = true; 48 | 49 | if ( runStarted ) { 50 | throw new Error( "Called start() outside of a test context while already started" ); 51 | } else if ( globalStartAlreadyCalled || count > 1 ) { 52 | throw new Error( "Called start() outside of a test context too many times" ); 53 | } else if ( config.autostart ) { 54 | throw new Error( "Called start() outside of a test context when " + 55 | "QUnit.config.autostart was true" ); 56 | } else if ( !config.pageLoaded ) { 57 | 58 | // The page isn't completely loaded yet, so bail out and let `QUnit.load` handle it 59 | config.autostart = true; 60 | return; 61 | } 62 | } else { 63 | 64 | // If a test is running, adjust its semaphore 65 | config.current.semaphore -= count || 1; 66 | 67 | // Don't start until equal number of stop-calls 68 | if ( config.current.semaphore > 0 ) { 69 | return; 70 | } 71 | 72 | // throw an Error if start is called more often than stop 73 | if ( config.current.semaphore < 0 ) { 74 | config.current.semaphore = 0; 75 | 76 | QUnit.pushFailure( 77 | "Called start() while already started (test's semaphore was 0 already)", 78 | sourceFromStacktrace( 2 ) 79 | ); 80 | return; 81 | } 82 | } 83 | 84 | resumeProcessing(); 85 | }, 86 | 87 | // DEPRECATED: QUnit.stop() will be removed in QUnit 2.0. 88 | stop: function( count ) { 89 | 90 | // If there isn't a test running, don't allow QUnit.stop() to be called 91 | if ( !config.current ) { 92 | throw new Error( "Called stop() outside of a test context" ); 93 | } 94 | 95 | // If a test is running, adjust its semaphore 96 | config.current.semaphore += count || 1; 97 | 98 | pauseProcessing(); 99 | }, 100 | 101 | config: config, 102 | 103 | is: is, 104 | 105 | objectType: objectType, 106 | 107 | extend: extend, 108 | 109 | load: function() { 110 | config.pageLoaded = true; 111 | 112 | // Initialize the configuration options 113 | extend( config, { 114 | stats: { all: 0, bad: 0 }, 115 | moduleStats: { all: 0, bad: 0 }, 116 | started: 0, 117 | updateRate: 1000, 118 | autostart: true, 119 | filter: "" 120 | }, true ); 121 | 122 | config.blocking = false; 123 | 124 | if ( config.autostart ) { 125 | resumeProcessing(); 126 | } 127 | }, 128 | 129 | stack: function( offset ) { 130 | offset = ( offset || 0 ) + 2; 131 | return sourceFromStacktrace( offset ); 132 | } 133 | }); 134 | 135 | registerLoggingCallbacks( QUnit ); 136 | 137 | function begin() { 138 | var i, l, 139 | modulesLog = []; 140 | 141 | // If the test run hasn't officially begun yet 142 | if ( !config.started ) { 143 | 144 | // Record the time of the test run's beginning 145 | config.started = now(); 146 | 147 | verifyLoggingCallbacks(); 148 | 149 | // Delete the loose unnamed module if unused. 150 | if ( config.modules[ 0 ].name === "" && config.modules[ 0 ].tests.length === 0 ) { 151 | config.modules.shift(); 152 | } 153 | 154 | // Avoid unnecessary information by not logging modules' test environments 155 | for ( i = 0, l = config.modules.length; i < l; i++ ) { 156 | modulesLog.push({ 157 | name: config.modules[ i ].name, 158 | tests: config.modules[ i ].tests 159 | }); 160 | } 161 | 162 | // The test run is officially beginning now 163 | runLoggingCallbacks( "begin", { 164 | totalTests: Test.count, 165 | modules: modulesLog 166 | }); 167 | } 168 | 169 | config.blocking = false; 170 | process( true ); 171 | } 172 | 173 | function process( last ) { 174 | function next() { 175 | process( last ); 176 | } 177 | var start = now(); 178 | config.depth = ( config.depth || 0 ) + 1; 179 | 180 | while ( config.queue.length && !config.blocking ) { 181 | if ( !defined.setTimeout || config.updateRate <= 0 || 182 | ( ( now() - start ) < config.updateRate ) ) { 183 | if ( config.current ) { 184 | 185 | // Reset async tracking for each phase of the Test lifecycle 186 | config.current.usedAsync = false; 187 | } 188 | config.queue.shift()(); 189 | } else { 190 | setTimeout( next, 13 ); 191 | break; 192 | } 193 | } 194 | config.depth--; 195 | if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) { 196 | done(); 197 | } 198 | } 199 | 200 | function pauseProcessing() { 201 | config.blocking = true; 202 | 203 | if ( config.testTimeout && defined.setTimeout ) { 204 | clearTimeout( config.timeout ); 205 | config.timeout = setTimeout(function() { 206 | if ( config.current ) { 207 | config.current.semaphore = 0; 208 | QUnit.pushFailure( "Test timed out", sourceFromStacktrace( 2 ) ); 209 | } else { 210 | throw new Error( "Test timed out" ); 211 | } 212 | resumeProcessing(); 213 | }, config.testTimeout ); 214 | } 215 | } 216 | 217 | function resumeProcessing() { 218 | runStarted = true; 219 | 220 | // A slight delay to allow this iteration of the event loop to finish (more assertions, etc.) 221 | if ( defined.setTimeout ) { 222 | setTimeout(function() { 223 | if ( config.current && config.current.semaphore > 0 ) { 224 | return; 225 | } 226 | if ( config.timeout ) { 227 | clearTimeout( config.timeout ); 228 | } 229 | 230 | begin(); 231 | }, 13 ); 232 | } else { 233 | begin(); 234 | } 235 | } 236 | 237 | function done() { 238 | var runtime, passed; 239 | 240 | config.autorun = true; 241 | 242 | // Log the last module results 243 | if ( config.previousModule ) { 244 | runLoggingCallbacks( "moduleDone", { 245 | name: config.previousModule.name, 246 | tests: config.previousModule.tests, 247 | failed: config.moduleStats.bad, 248 | passed: config.moduleStats.all - config.moduleStats.bad, 249 | total: config.moduleStats.all, 250 | runtime: now() - config.moduleStats.started 251 | }); 252 | } 253 | delete config.previousModule; 254 | 255 | runtime = now() - config.started; 256 | passed = config.stats.all - config.stats.bad; 257 | 258 | runLoggingCallbacks( "done", { 259 | failed: config.stats.bad, 260 | passed: passed, 261 | total: config.stats.all, 262 | runtime: runtime 263 | }); 264 | } 265 | -------------------------------------------------------------------------------- /src/equiv.js: -------------------------------------------------------------------------------- 1 | // Test for equality any JavaScript type. 2 | // Author: Philippe Rathé 3 | QUnit.equiv = (function() { 4 | 5 | // Stack to decide between skip/abort functions 6 | var callers = []; 7 | 8 | // Stack to avoiding loops from circular referencing 9 | var parents = []; 10 | var parentsB = []; 11 | 12 | function useStrictEquality( b, a ) { 13 | 14 | /*jshint eqeqeq:false */ 15 | if ( b instanceof a.constructor || a instanceof b.constructor ) { 16 | 17 | // To catch short annotation VS 'new' annotation of a declaration. e.g.: 18 | // `var i = 1;` 19 | // `var j = new Number(1);` 20 | return a == b; 21 | } else { 22 | return a === b; 23 | } 24 | } 25 | 26 | function compareConstructors( a, b ) { 27 | var getProto = Object.getPrototypeOf || function( obj ) { 28 | 29 | /*jshint proto: true */ 30 | return obj.__proto__; 31 | }; 32 | var protoA = getProto( a ); 33 | var protoB = getProto( b ); 34 | 35 | // Comparing constructors is more strict than using `instanceof` 36 | if ( a.constructor === b.constructor ) { 37 | return true; 38 | } 39 | 40 | // Ref #851 41 | // If the obj prototype descends from a null constructor, treat it 42 | // as a null prototype. 43 | if ( protoA && protoA.constructor === null ) { 44 | protoA = null; 45 | } 46 | if ( protoB && protoB.constructor === null ) { 47 | protoB = null; 48 | } 49 | 50 | // Allow objects with no prototype to be equivalent to 51 | // objects with Object as their constructor. 52 | if ( ( protoA === null && protoB === Object.prototype ) || 53 | ( protoB === null && protoA === Object.prototype ) ) { 54 | return true; 55 | } 56 | 57 | return false; 58 | } 59 | 60 | var callbacks = { 61 | "string": useStrictEquality, 62 | "boolean": useStrictEquality, 63 | "number": useStrictEquality, 64 | "null": useStrictEquality, 65 | "undefined": useStrictEquality, 66 | 67 | "nan": function( b ) { 68 | return isNaN( b ); 69 | }, 70 | 71 | "date": function( b, a ) { 72 | return QUnit.objectType( b ) === "date" && a.valueOf() === b.valueOf(); 73 | }, 74 | 75 | "regexp": function( b, a ) { 76 | return QUnit.objectType( b ) === "regexp" && 77 | 78 | // The regex itself 79 | a.source === b.source && 80 | 81 | // And its modifiers 82 | a.global === b.global && 83 | 84 | // (gmi) ... 85 | a.ignoreCase === b.ignoreCase && 86 | a.multiline === b.multiline && 87 | a.sticky === b.sticky; 88 | }, 89 | 90 | // - skip when the property is a method of an instance (OOP) 91 | // - abort otherwise, 92 | // initial === would have catch identical references anyway 93 | "function": function() { 94 | var caller = callers[ callers.length - 1 ]; 95 | return caller !== Object && typeof caller !== "undefined"; 96 | }, 97 | 98 | "array": function( b, a ) { 99 | var i, j, len, loop, aCircular, bCircular; 100 | 101 | // b could be an object literal here 102 | if ( QUnit.objectType( b ) !== "array" ) { 103 | return false; 104 | } 105 | 106 | len = a.length; 107 | if ( len !== b.length ) { 108 | // safe and faster 109 | return false; 110 | } 111 | 112 | // Track reference to avoid circular references 113 | parents.push( a ); 114 | parentsB.push( b ); 115 | for ( i = 0; i < len; i++ ) { 116 | loop = false; 117 | for ( j = 0; j < parents.length; j++ ) { 118 | aCircular = parents[ j ] === a[ i ]; 119 | bCircular = parentsB[ j ] === b[ i ]; 120 | if ( aCircular || bCircular ) { 121 | if ( a[ i ] === b[ i ] || aCircular && bCircular ) { 122 | loop = true; 123 | } else { 124 | parents.pop(); 125 | parentsB.pop(); 126 | return false; 127 | } 128 | } 129 | } 130 | if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) { 131 | parents.pop(); 132 | parentsB.pop(); 133 | return false; 134 | } 135 | } 136 | parents.pop(); 137 | parentsB.pop(); 138 | return true; 139 | }, 140 | 141 | "set": function( b, a ) { 142 | var aArray, bArray; 143 | 144 | // `b` could be any object here 145 | if ( QUnit.objectType( b ) !== "set" ) { 146 | return false; 147 | } 148 | 149 | aArray = []; 150 | a.forEach( function( v ) { 151 | aArray.push( v ); 152 | }); 153 | bArray = []; 154 | b.forEach( function( v ) { 155 | bArray.push( v ); 156 | }); 157 | 158 | return innerEquiv( bArray, aArray ); 159 | }, 160 | 161 | "map": function( b, a ) { 162 | var aArray, bArray; 163 | 164 | // `b` could be any object here 165 | if ( QUnit.objectType( b ) !== "map" ) { 166 | return false; 167 | } 168 | 169 | aArray = []; 170 | a.forEach( function( v, k ) { 171 | aArray.push( [ k, v ] ); 172 | }); 173 | bArray = []; 174 | b.forEach( function( v, k ) { 175 | bArray.push( [ k, v ] ); 176 | }); 177 | 178 | return innerEquiv( bArray, aArray ); 179 | }, 180 | 181 | "object": function( b, a ) { 182 | var i, j, loop, aCircular, bCircular; 183 | 184 | // Default to true 185 | var eq = true; 186 | var aProperties = []; 187 | var bProperties = []; 188 | 189 | if ( compareConstructors( a, b ) === false ) { 190 | return false; 191 | } 192 | 193 | // Stack constructor before traversing properties 194 | callers.push( a.constructor ); 195 | 196 | // Track reference to avoid circular references 197 | parents.push( a ); 198 | parentsB.push( b ); 199 | 200 | // Be strict: don't ensure hasOwnProperty and go deep 201 | for ( i in a ) { 202 | loop = false; 203 | for ( j = 0; j < parents.length; j++ ) { 204 | aCircular = parents[ j ] === a[ i ]; 205 | bCircular = parentsB[ j ] === b[ i ]; 206 | if ( aCircular || bCircular ) { 207 | if ( a[ i ] === b[ i ] || aCircular && bCircular ) { 208 | loop = true; 209 | } else { 210 | eq = false; 211 | break; 212 | } 213 | } 214 | } 215 | aProperties.push( i ); 216 | if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) { 217 | eq = false; 218 | break; 219 | } 220 | } 221 | 222 | parents.pop(); 223 | parentsB.pop(); 224 | 225 | // Unstack, we are done 226 | callers.pop(); 227 | 228 | for ( i in b ) { 229 | 230 | // Collect b's properties 231 | bProperties.push( i ); 232 | } 233 | 234 | // Ensures identical properties name 235 | return eq && innerEquiv( aProperties.sort(), bProperties.sort() ); 236 | } 237 | }; 238 | 239 | // Call the o related callback with the given arguments. 240 | function bindCallbacks( o, callbacks, args ) { 241 | var prop = QUnit.objectType( o ); 242 | if ( prop ) { 243 | if ( QUnit.objectType( callbacks[ prop ] ) === "function" ) { 244 | return callbacks[ prop ].apply( callbacks, args ); 245 | } else { 246 | return callbacks[ prop ]; // or undefined 247 | } 248 | } 249 | } 250 | 251 | // The real equiv function 252 | function innerEquiv() { 253 | var args = [].slice.apply( arguments ); 254 | if ( args.length < 2 ) { 255 | 256 | // End transition 257 | return true; 258 | } 259 | 260 | return ( (function( a, b ) { 261 | if ( a === b ) { 262 | 263 | // Catch the most you can 264 | return true; 265 | } else if ( a === null || b === null || typeof a === "undefined" || 266 | typeof b === "undefined" || 267 | QUnit.objectType( a ) !== QUnit.objectType( b ) ) { 268 | 269 | // Don't lose time with error prone cases 270 | return false; 271 | } else { 272 | return bindCallbacks( a, callbacks, [ b, a ] ); 273 | } 274 | 275 | // Apply transition with (1..n) arguments 276 | }( args[ 0 ], args[ 1 ] ) ) && 277 | innerEquiv.apply( this, args.splice( 1, args.length - 1 ) ) ); 278 | } 279 | 280 | return innerEquiv; 281 | }()); 282 | -------------------------------------------------------------------------------- /src/dump.js: -------------------------------------------------------------------------------- 1 | // Based on jsDump by Ariel Flesler 2 | // http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html 3 | QUnit.dump = (function() { 4 | function quote( str ) { 5 | return "\"" + str.toString().replace( /\\/g, "\\\\" ).replace( /"/g, "\\\"" ) + "\""; 6 | } 7 | function literal( o ) { 8 | return o + ""; 9 | } 10 | function join( pre, arr, post ) { 11 | var s = dump.separator(), 12 | base = dump.indent(), 13 | inner = dump.indent( 1 ); 14 | if ( arr.join ) { 15 | arr = arr.join( "," + s + inner ); 16 | } 17 | if ( !arr ) { 18 | return pre + post; 19 | } 20 | return [ pre, inner + arr, base + post ].join( s ); 21 | } 22 | function array( arr, stack ) { 23 | var i = arr.length, 24 | ret = new Array( i ); 25 | 26 | if ( dump.maxDepth && dump.depth > dump.maxDepth ) { 27 | return "[object Array]"; 28 | } 29 | 30 | this.up(); 31 | while ( i-- ) { 32 | ret[ i ] = this.parse( arr[ i ], undefined, stack ); 33 | } 34 | this.down(); 35 | return join( "[", ret, "]" ); 36 | } 37 | 38 | var reName = /^function (\w+)/, 39 | dump = { 40 | 41 | // objType is used mostly internally, you can fix a (custom) type in advance 42 | parse: function( obj, objType, stack ) { 43 | stack = stack || []; 44 | var res, parser, parserType, 45 | inStack = inArray( obj, stack ); 46 | 47 | if ( inStack !== -1 ) { 48 | return "recursion(" + ( inStack - stack.length ) + ")"; 49 | } 50 | 51 | objType = objType || this.typeOf( obj ); 52 | parser = this.parsers[ objType ]; 53 | parserType = typeof parser; 54 | 55 | if ( parserType === "function" ) { 56 | stack.push( obj ); 57 | res = parser.call( this, obj, stack ); 58 | stack.pop(); 59 | return res; 60 | } 61 | return ( parserType === "string" ) ? parser : this.parsers.error; 62 | }, 63 | typeOf: function( obj ) { 64 | var type; 65 | if ( obj === null ) { 66 | type = "null"; 67 | } else if ( typeof obj === "undefined" ) { 68 | type = "undefined"; 69 | } else if ( QUnit.is( "regexp", obj ) ) { 70 | type = "regexp"; 71 | } else if ( QUnit.is( "date", obj ) ) { 72 | type = "date"; 73 | } else if ( QUnit.is( "function", obj ) ) { 74 | type = "function"; 75 | } else if ( obj.setInterval !== undefined && 76 | obj.document !== undefined && 77 | obj.nodeType === undefined ) { 78 | type = "window"; 79 | } else if ( obj.nodeType === 9 ) { 80 | type = "document"; 81 | } else if ( obj.nodeType ) { 82 | type = "node"; 83 | } else if ( 84 | 85 | // native arrays 86 | toString.call( obj ) === "[object Array]" || 87 | 88 | // NodeList objects 89 | ( typeof obj.length === "number" && obj.item !== undefined && 90 | ( obj.length ? obj.item( 0 ) === obj[ 0 ] : ( obj.item( 0 ) === null && 91 | obj[ 0 ] === undefined ) ) ) 92 | ) { 93 | type = "array"; 94 | } else if ( obj.constructor === Error.prototype.constructor ) { 95 | type = "error"; 96 | } else { 97 | type = typeof obj; 98 | } 99 | return type; 100 | }, 101 | separator: function() { 102 | return this.multiline ? this.HTML ? "
    " : "\n" : this.HTML ? " " : " "; 103 | }, 104 | // extra can be a number, shortcut for increasing-calling-decreasing 105 | indent: function( extra ) { 106 | if ( !this.multiline ) { 107 | return ""; 108 | } 109 | var chr = this.indentChar; 110 | if ( this.HTML ) { 111 | chr = chr.replace( /\t/g, " " ).replace( / /g, " " ); 112 | } 113 | return new Array( this.depth + ( extra || 0 ) ).join( chr ); 114 | }, 115 | up: function( a ) { 116 | this.depth += a || 1; 117 | }, 118 | down: function( a ) { 119 | this.depth -= a || 1; 120 | }, 121 | setParser: function( name, parser ) { 122 | this.parsers[ name ] = parser; 123 | }, 124 | // The next 3 are exposed so you can use them 125 | quote: quote, 126 | literal: literal, 127 | join: join, 128 | // 129 | depth: 1, 130 | maxDepth: QUnit.config.maxDepth, 131 | 132 | // This is the list of parsers, to modify them, use dump.setParser 133 | parsers: { 134 | window: "[Window]", 135 | document: "[Document]", 136 | error: function( error ) { 137 | return "Error(\"" + error.message + "\")"; 138 | }, 139 | unknown: "[Unknown]", 140 | "null": "null", 141 | "undefined": "undefined", 142 | "function": function( fn ) { 143 | var ret = "function", 144 | 145 | // functions never have name in IE 146 | name = "name" in fn ? fn.name : ( reName.exec( fn ) || [] )[ 1 ]; 147 | 148 | if ( name ) { 149 | ret += " " + name; 150 | } 151 | ret += "( "; 152 | 153 | ret = [ ret, dump.parse( fn, "functionArgs" ), "){" ].join( "" ); 154 | return join( ret, dump.parse( fn, "functionCode" ), "}" ); 155 | }, 156 | array: array, 157 | nodelist: array, 158 | "arguments": array, 159 | object: function( map, stack ) { 160 | var keys, key, val, i, nonEnumerableProperties, 161 | ret = []; 162 | 163 | if ( dump.maxDepth && dump.depth > dump.maxDepth ) { 164 | return "[object Object]"; 165 | } 166 | 167 | dump.up(); 168 | keys = []; 169 | for ( key in map ) { 170 | keys.push( key ); 171 | } 172 | 173 | // Some properties are not always enumerable on Error objects. 174 | nonEnumerableProperties = [ "message", "name" ]; 175 | for ( i in nonEnumerableProperties ) { 176 | key = nonEnumerableProperties[ i ]; 177 | if ( key in map && inArray( key, keys ) < 0 ) { 178 | keys.push( key ); 179 | } 180 | } 181 | keys.sort(); 182 | for ( i = 0; i < keys.length; i++ ) { 183 | key = keys[ i ]; 184 | val = map[ key ]; 185 | ret.push( dump.parse( key, "key" ) + ": " + 186 | dump.parse( val, undefined, stack ) ); 187 | } 188 | dump.down(); 189 | return join( "{", ret, "}" ); 190 | }, 191 | node: function( node ) { 192 | var len, i, val, 193 | open = dump.HTML ? "<" : "<", 194 | close = dump.HTML ? ">" : ">", 195 | tag = node.nodeName.toLowerCase(), 196 | ret = open + tag, 197 | attrs = node.attributes; 198 | 199 | if ( attrs ) { 200 | for ( i = 0, len = attrs.length; i < len; i++ ) { 201 | val = attrs[ i ].nodeValue; 202 | 203 | // IE6 includes all attributes in .attributes, even ones not explicitly 204 | // set. Those have values like undefined, null, 0, false, "" or 205 | // "inherit". 206 | if ( val && val !== "inherit" ) { 207 | ret += " " + attrs[ i ].nodeName + "=" + 208 | dump.parse( val, "attribute" ); 209 | } 210 | } 211 | } 212 | ret += close; 213 | 214 | // Show content of TextNode or CDATASection 215 | if ( node.nodeType === 3 || node.nodeType === 4 ) { 216 | ret += node.nodeValue; 217 | } 218 | 219 | return ret + open + "/" + tag + close; 220 | }, 221 | 222 | // function calls it internally, it's the arguments part of the function 223 | functionArgs: function( fn ) { 224 | var args, 225 | l = fn.length; 226 | 227 | if ( !l ) { 228 | return ""; 229 | } 230 | 231 | args = new Array( l ); 232 | while ( l-- ) { 233 | 234 | // 97 is 'a' 235 | args[ l ] = String.fromCharCode( 97 + l ); 236 | } 237 | return " " + args.join( ", " ) + " "; 238 | }, 239 | // object calls it internally, the key part of an item in a map 240 | key: quote, 241 | // function calls it internally, it's the content of the function 242 | functionCode: "[code]", 243 | // node calls it internally, it's an html attribute value 244 | attribute: quote, 245 | string: quote, 246 | date: quote, 247 | regexp: literal, 248 | number: literal, 249 | "boolean": literal 250 | }, 251 | // if true, entities are escaped ( <, >, \t, space and \n ) 252 | HTML: false, 253 | // indentation unit 254 | indentChar: " ", 255 | // if true, items in a collection, are separated by a \n, else just a space. 256 | multiline: true 257 | }; 258 | 259 | return dump; 260 | }()); 261 | 262 | // back compat 263 | QUnit.jsDump = QUnit.dump; 264 | -------------------------------------------------------------------------------- /test/logs.js: -------------------------------------------------------------------------------- 1 | var totalTests, moduleContext, moduleDoneContext, testContext, testDoneContext, logContext, 2 | testAutorun, beginModules, 3 | module1Test1, module1Test2, module2Test1, module2Test2, module2Test3, module2Test4, 4 | begin = 0, 5 | moduleStart = 0, 6 | moduleDone = 0, 7 | testStart = 0, 8 | testDone = 0, 9 | log = 0, 10 | module1Context = { 11 | name: "logs1", 12 | tests: [ 13 | (module1Test1 = { 14 | "name": "test1", 15 | "testId": "646e9e25" 16 | }), 17 | (module1Test2 = { 18 | "name": "test2", 19 | "testId": "646e9e26" 20 | }) 21 | ] 22 | }, 23 | module2Context = { 24 | name: "logs2", 25 | tests: [ 26 | (module2Test1 = { 27 | "name": "test1", 28 | "testId": "9954d966" 29 | }), 30 | (module2Test2 = { 31 | "name": "test2", 32 | "testId": "9954d967" 33 | }), 34 | (module2Test3 = { 35 | "name": "a skipped test", 36 | "testId": "3e797d3a" 37 | }), 38 | (module2Test4 = { 39 | "name": "test the log for the skipped test", 40 | "testId": "d3266148" 41 | }) 42 | ] 43 | }; 44 | 45 | QUnit.begin(function( args ) { 46 | totalTests = args.totalTests; 47 | beginModules = args.modules; 48 | begin++; 49 | }); 50 | 51 | QUnit.moduleStart(function( context ) { 52 | moduleStart++; 53 | moduleContext = context; 54 | }); 55 | 56 | QUnit.moduleDone(function( context ) { 57 | moduleDone++; 58 | moduleDoneContext = context; 59 | }); 60 | 61 | QUnit.testStart(function( context ) { 62 | testStart++; 63 | testContext = context; 64 | }); 65 | 66 | QUnit.testDone(function( context ) { 67 | testDone++; 68 | testDoneContext = context; 69 | }); 70 | 71 | QUnit.log(function( context ) { 72 | log++; 73 | logContext = context; 74 | }); 75 | 76 | QUnit.module( module1Context.name ); 77 | 78 | QUnit.test( module1Test1.name, function( assert ) { 79 | assert.expect( 18 ); 80 | 81 | assert.equal( 82 | typeof totalTests, 83 | "number", 84 | "QUnit.begin should pass total amount of tests to callback" 85 | ); 86 | 87 | while ( beginModules.length > 2 ) { 88 | beginModules.pop(); 89 | } 90 | 91 | assert.deepEqual( 92 | beginModules, 93 | [ module1Context, module2Context ], 94 | "QUnit.begin details registered modules and their respective tests" 95 | ); 96 | 97 | assert.equal( begin, 1, "QUnit.begin calls" ); 98 | assert.equal( moduleStart, 1, "QUnit.moduleStart calls" ); 99 | assert.equal( testStart, 1, "QUnit.testStart calls" ); 100 | assert.equal( testDone, 0, "QUnit.testDone calls" ); 101 | assert.equal( moduleDone, 0, "QUnit.moduleDone calls" ); 102 | 103 | assert.equal( 104 | logContext.runtime >= 0 && logContext.runtime < 50, 105 | true, 106 | "log runtime was a reasonable number" 107 | ); 108 | 109 | delete logContext.runtime; 110 | assert.deepEqual( logContext, { 111 | name: module1Test1.name, 112 | module: module1Context.name, 113 | result: true, 114 | message: "log runtime was a reasonable number", 115 | actual: true, 116 | expected: true, 117 | negative: false, 118 | testId: module1Test1.testId 119 | }, "log context after equal(actual, expected, message)" ); 120 | 121 | assert.equal( "foo", "foo" ); 122 | 123 | delete logContext.runtime; 124 | assert.deepEqual( logContext, { 125 | name: module1Test1.name, 126 | module: module1Context.name, 127 | result: true, 128 | message: undefined, 129 | actual: "foo", 130 | expected: "foo", 131 | negative: false, 132 | testId: module1Test1.testId 133 | }, "log context after equal(actual, expected)" ); 134 | 135 | assert.ok( true, "ok(true, message)" ); 136 | 137 | delete logContext.runtime; 138 | assert.deepEqual( logContext, { 139 | module: module1Context.name, 140 | name: module1Test1.name, 141 | result: true, 142 | message: "ok(true, message)", 143 | actual: true, 144 | expected: true, 145 | negative: false, 146 | testId: module1Test1.testId 147 | }, "log context after ok(true, message)" ); 148 | 149 | assert.strictEqual( testDoneContext, undefined, "testDone context" ); 150 | assert.deepEqual( testContext, { 151 | module: module1Context.name, 152 | name: module1Test1.name, 153 | testId: module1Test1.testId 154 | }, "test context" ); 155 | 156 | assert.strictEqual( moduleDoneContext, undefined, "moduleDone context" ); 157 | assert.deepEqual( moduleContext, module1Context, "module context" ); 158 | 159 | assert.equal( log, 17, "QUnit.log calls" ); 160 | }); 161 | 162 | QUnit.test( module1Test2.name, function( assert ) { 163 | assert.expect( 12 ); 164 | assert.equal( begin, 1, "QUnit.begin calls" ); 165 | assert.equal( moduleStart, 1, "QUnit.moduleStart calls" ); 166 | assert.equal( testStart, 2, "QUnit.testStart calls" ); 167 | assert.equal( testDone, 1, "QUnit.testDone calls" ); 168 | assert.equal( moduleDone, 0, "QUnit.moduleDone calls" ); 169 | 170 | assert.equal( 171 | testDoneContext.runtime >= 0 && testDoneContext.runtime < 1000, 172 | true, 173 | "test runtime was a reasonable number" 174 | ); 175 | 176 | assert.ok( testDoneContext.assertions instanceof Array, "testDone context: assertions" ); 177 | 178 | // TODO: more tests for testDoneContext.assertions 179 | 180 | delete testDoneContext.runtime; 181 | 182 | // DEPRECATED: remove this delete when removing the duration property 183 | delete testDoneContext.duration; 184 | 185 | // Delete testDoneContext.assertions so we can easily jump to next assertion 186 | delete testDoneContext.assertions; 187 | 188 | // Delete testDoneContext.source 189 | delete testDoneContext.source; 190 | 191 | assert.deepEqual( testDoneContext, { 192 | module: module1Context.name, 193 | name: module1Test1.name, 194 | failed: 0, 195 | passed: 18, 196 | total: 18, 197 | testId: module1Test1.testId, 198 | skipped: false 199 | }, "testDone context" ); 200 | assert.deepEqual( testContext, { 201 | module: module1Context.name, 202 | name: module1Test2.name, 203 | testId: module1Test2.testId 204 | }, "test context" ); 205 | 206 | assert.strictEqual( moduleDoneContext, undefined, "moduleDone context" ); 207 | assert.deepEqual( moduleContext, module1Context, "module context" ); 208 | assert.equal( log, 29, "QUnit.log calls" ); 209 | }); 210 | 211 | QUnit.module( module2Context.name ); 212 | 213 | QUnit.test( module2Test1.name, function( assert ) { 214 | assert.expect( 10 ); 215 | assert.equal( begin, 1, "QUnit.begin calls" ); 216 | assert.equal( moduleStart, 2, "QUnit.moduleStart calls" ); 217 | assert.equal( testStart, 3, "QUnit.testStart calls" ); 218 | assert.equal( testDone, 2, "QUnit.testDone calls" ); 219 | assert.equal( moduleDone, 1, "QUnit.moduleDone calls" ); 220 | 221 | assert.deepEqual( testContext, { 222 | module: module2Context.name, 223 | name: module2Test1.name, 224 | testId: module2Test1.testId 225 | }, "test context" ); 226 | 227 | assert.equal( 228 | moduleDoneContext.runtime >= 0 && moduleDoneContext.runtime < 5000, 229 | true, 230 | "module runtime was a reasonable number" 231 | ); 232 | delete moduleDoneContext.runtime; 233 | 234 | assert.deepEqual( moduleDoneContext, { 235 | name: module1Context.name, 236 | tests: module1Context.tests, 237 | failed: 0, 238 | passed: 30, 239 | total: 30 240 | }, "moduleDone context" ); 241 | assert.deepEqual( moduleContext, module2Context, "module context" ); 242 | 243 | assert.equal( log, 39, "QUnit.log calls" ); 244 | }); 245 | 246 | QUnit.test( module2Test2.name, function( assert ) { 247 | assert.expect( 8 ); 248 | assert.equal( begin, 1, "QUnit.begin calls" ); 249 | assert.equal( moduleStart, 2, "QUnit.moduleStart calls" ); 250 | assert.equal( testStart, 4, "QUnit.testStart calls" ); 251 | assert.equal( testDone, 3, "QUnit.testDone calls" ); 252 | assert.equal( moduleDone, 1, "QUnit.moduleDone calls" ); 253 | 254 | assert.deepEqual( testContext, { 255 | module: module2Context.name, 256 | name: module2Test2.name, 257 | testId: module2Test2.testId 258 | }, "test context" ); 259 | assert.deepEqual( moduleContext, module2Context, "module context" ); 260 | 261 | assert.equal( log, 47, "QUnit.log calls" ); 262 | }); 263 | 264 | QUnit.skip( module2Test3.name ); 265 | 266 | QUnit.test( module2Test4.name, function( assert ) { 267 | assert.expect( 1 ); 268 | 269 | delete testDoneContext.runtime; 270 | delete testDoneContext.duration; 271 | delete testDoneContext.source; 272 | 273 | assert.deepEqual( testDoneContext, { 274 | assertions: [], 275 | module: module2Context.name, 276 | name: module2Test3.name, 277 | failed: 0, 278 | passed: 0, 279 | total: 0, 280 | skipped: true, 281 | testId: module2Test3.testId 282 | }, "testDone context" ); 283 | }); 284 | 285 | testAutorun = true; 286 | 287 | QUnit.done(function() { 288 | 289 | if ( !testAutorun ) { 290 | return; 291 | } 292 | 293 | testAutorun = false; 294 | 295 | moduleStart = moduleDone = 0; 296 | 297 | // Since these tests run *after* done, and as such 298 | // QUnit is not able to know whether more tests are coming 299 | // the module starts/ends after each test. 300 | QUnit.module( "autorun" ); 301 | 302 | setTimeout(function() { 303 | QUnit.test( "first", function( assert ) { 304 | assert.equal( moduleStart, 1, "test started" ); 305 | assert.equal( moduleDone, 0, "test in progress" ); 306 | }); 307 | 308 | QUnit.test( "second", function( assert ) { 309 | assert.equal( moduleStart, 2, "test started" ); 310 | assert.equal( moduleDone, 1, "test in progress" ); 311 | }); 312 | }, 5000 ); 313 | }); 314 | 315 | QUnit.module( "deprecated log methods" ); 316 | 317 | QUnit.test( "QUnit.reset()", function( assert ) { 318 | 319 | // Skip non-browsers 320 | if ( typeof window === "undefined" || !window.document ) { 321 | assert.expect( 0 ); 322 | return; 323 | } 324 | 325 | var myFixture = document.getElementById( "qunit-fixture" ); 326 | 327 | myFixture.innerHTML = "something different from QUnit.config.fixture"; 328 | 329 | QUnit.reset(); 330 | 331 | assert.strictEqual( myFixture.innerHTML, QUnit.config.fixture, "restores #qunit-fixture" ); 332 | }); 333 | -------------------------------------------------------------------------------- /test/main/assert.js: -------------------------------------------------------------------------------- 1 | QUnit.module( "assert" ); 2 | 3 | QUnit.test( "ok", function( assert ) { 4 | assert.ok( true ); 5 | assert.ok( 1 ); 6 | assert.ok( "1" ); 7 | assert.ok( Infinity ); 8 | assert.ok( {} ); 9 | assert.ok( [] ); 10 | }); 11 | 12 | QUnit.test( "notOk", function( assert ) { 13 | assert.notOk( false ); 14 | assert.notOk( 0 ); 15 | assert.notOk( "" ); 16 | assert.notOk( null ); 17 | assert.notOk( undefined ); 18 | assert.notOk( NaN ); 19 | }); 20 | 21 | QUnit.test( "equal", function( assert ) { 22 | assert.equal( 1, 1 ); 23 | assert.equal( "foo", "foo" ); 24 | assert.equal( "foo", [ "foo" ] ); 25 | assert.equal( "foo", { toString: function() { return "foo"; } } ); 26 | assert.equal( 0, [ 0 ] ); 27 | }); 28 | 29 | QUnit.test( "notEqual", function( assert ) { 30 | assert.notEqual( 1, 2 ); 31 | assert.notEqual( "foo", "bar" ); 32 | assert.notEqual( {}, {} ); 33 | assert.notEqual( [], [] ); 34 | }); 35 | 36 | QUnit.test( "strictEqual", function( assert ) { 37 | assert.strictEqual( 1, 1 ); 38 | assert.strictEqual( "foo", "foo" ); 39 | }); 40 | 41 | QUnit.test( "notStrictEqual", function( assert ) { 42 | assert.notStrictEqual( 1, 2 ); 43 | assert.notStrictEqual( "foo", "bar" ); 44 | assert.notStrictEqual( "foo", [ "foo" ] ); 45 | assert.notStrictEqual( "1", 1 ); 46 | assert.notStrictEqual( "foo", { toString: function() { return "foo"; } } ); 47 | }); 48 | 49 | QUnit.test( "propEqual", function( assert ) { 50 | assert.expect( 5 ); 51 | var objectCreate = Object.create || function( origin ) { 52 | function O() {} 53 | O.prototype = origin; 54 | var r = new O(); 55 | return r; 56 | }; 57 | 58 | function Foo( x, y, z ) { 59 | this.x = x; 60 | this.y = y; 61 | this.z = z; 62 | } 63 | Foo.prototype.doA = function() {}; 64 | Foo.prototype.doB = function() {}; 65 | Foo.prototype.bar = "prototype"; 66 | 67 | function Bar() { 68 | } 69 | Bar.prototype = objectCreate( Foo.prototype ); 70 | Bar.prototype.constructor = Bar; 71 | 72 | assert.propEqual( 73 | new Foo( 1, "2", [] ), 74 | { 75 | x: 1, 76 | y: "2", 77 | z: [] 78 | } 79 | ); 80 | 81 | assert.notPropEqual( 82 | new Foo( "1", 2, 3 ), 83 | { 84 | x: 1, 85 | y: "2", 86 | z: 3 87 | }, 88 | "Primitive values are strictly compared" 89 | ); 90 | 91 | assert.notPropEqual( 92 | new Foo( 1, "2", [] ), 93 | { 94 | x: 1, 95 | y: "2", 96 | z: {} 97 | }, 98 | "Array type is preserved" 99 | ); 100 | 101 | assert.notPropEqual( 102 | new Foo( 1, "2", {} ), 103 | { 104 | x: 1, 105 | y: "2", 106 | z: [] 107 | }, 108 | "Empty array is not the same as empty object" 109 | ); 110 | 111 | assert.propEqual( 112 | new Foo( 1, "2", new Foo( [ 3 ], new Bar(), null ) ), 113 | { 114 | x: 1, 115 | y: "2", 116 | z: { 117 | x: [ 3 ], 118 | y: {}, 119 | z: null 120 | } 121 | }, 122 | "Complex nesting of different types, inheritance and constructors" 123 | ); 124 | }); 125 | 126 | QUnit.test( "throws", function( assert ) { 127 | assert.expect( 16 ); 128 | function CustomError( message ) { 129 | this.message = message; 130 | } 131 | 132 | CustomError.prototype.toString = function() { 133 | return this.message; 134 | }; 135 | 136 | assert.throws( 137 | function() { 138 | throw "my error"; 139 | } 140 | ); 141 | 142 | assert.throws( 143 | function() { 144 | throw "my error"; 145 | }, 146 | "simple string throw, no 'expected' value given" 147 | ); 148 | 149 | // This test is for IE 7 and prior which does not properly 150 | // implement Error.prototype.toString 151 | assert.throws( 152 | function() { 153 | throw new Error( "error message" ); 154 | }, 155 | /error message/, 156 | "use regexp against instance of Error" 157 | ); 158 | 159 | assert.throws( 160 | function() { 161 | throw new TypeError(); 162 | }, 163 | Error, 164 | "thrown TypeError without a message is an instance of Error" 165 | ); 166 | 167 | assert.throws( 168 | function() { 169 | throw new TypeError(); 170 | }, 171 | TypeError, 172 | "thrown TypeError without a message is an instance of TypeError" 173 | ); 174 | 175 | assert.throws( 176 | function() { 177 | throw new TypeError( "error message" ); 178 | }, 179 | Error, 180 | "thrown TypeError with a message is an instance of Error" 181 | ); 182 | 183 | // This test is for IE 8 and prior which goes against the standards 184 | // by considering that the native Error constructors, such TypeError, 185 | // are also instances of the Error constructor. As such, the assertion 186 | // sometimes went down the wrong path. 187 | assert.throws( 188 | function() { 189 | throw new TypeError( "error message" ); 190 | }, 191 | TypeError, 192 | "thrown TypeError with a message is an instance of TypeError" 193 | ); 194 | 195 | assert.throws( 196 | function() { 197 | throw new CustomError( "some error description" ); 198 | }, 199 | CustomError, 200 | "thrown error is an instance of CustomError" 201 | ); 202 | 203 | assert.throws( 204 | function() { 205 | throw new Error( "some error description" ); 206 | }, 207 | /description/, 208 | "use a regex to match against the stringified error" 209 | ); 210 | 211 | assert.throws( 212 | function() { 213 | throw new Error( "foo" ); 214 | }, 215 | new Error( "foo" ), 216 | "thrown error object is similar to the expected Error object" 217 | ); 218 | 219 | assert.throws( 220 | function() { 221 | throw new CustomError( "some error description" ); 222 | }, 223 | new CustomError( "some error description" ), 224 | "thrown error object is similar to the expected CustomError object" 225 | ); 226 | 227 | assert.throws( 228 | function() { 229 | throw { 230 | name: "SomeName", 231 | message: "some message" 232 | }; 233 | }, 234 | { name: "SomeName", message: "some message" }, 235 | "thrown error object is similar to the expected plain object" 236 | ); 237 | 238 | assert.throws( 239 | function() { 240 | throw new CustomError( "some error description" ); 241 | }, 242 | function( err ) { 243 | return err instanceof CustomError && /description/.test( err ); 244 | }, 245 | "custom validation function" 246 | ); 247 | 248 | assert.throws( 249 | function() { 250 | 251 | /*jshint ignore:start */ 252 | ( window.execScript || function( data ) { 253 | window.eval.call( window, data ); 254 | })( "throw 'error';" ); 255 | 256 | /*jshint ignore:end */ 257 | }, 258 | "globally-executed errors caught" 259 | ); 260 | 261 | this.CustomError = CustomError; 262 | 263 | assert.throws( 264 | function() { 265 | throw new this.CustomError( "some error description" ); 266 | }, 267 | /description/, 268 | "throw error from property of 'this' context" 269 | ); 270 | 271 | assert.throws( 272 | function() { 273 | throw "some error description"; 274 | }, 275 | "some error description", 276 | "handle string typed thrown errors" 277 | ); 278 | }); 279 | 280 | QUnit.test( "raises, alias for throws", function( assert ) { 281 | assert.expect( 1 ); 282 | assert.raises(function() { 283 | throw "my error"; 284 | }); 285 | }); 286 | 287 | QUnit.module( "failing assertions", { 288 | beforeEach: function( assert ) { 289 | var originalPush = assert.push; 290 | 291 | assert.push = function( result, actual, expected, message ) { 292 | 293 | // inverts the result so we can test failing assertions 294 | originalPush( !result, actual, expected, message ); 295 | }; 296 | } 297 | }); 298 | 299 | QUnit.test( "ok", function( assert ) { 300 | assert.ok( false ); 301 | assert.ok( 0 ); 302 | assert.ok( "" ); 303 | assert.ok( null ); 304 | assert.ok( undefined ); 305 | assert.ok( NaN ); 306 | }); 307 | 308 | QUnit.test( "notOk", function( assert ) { 309 | assert.notOk( true ); 310 | assert.notOk( 1 ); 311 | assert.notOk( "1" ); 312 | assert.notOk( Infinity ); 313 | assert.notOk( {} ); 314 | assert.notOk( [] ); 315 | }); 316 | 317 | QUnit.test( "equal", function( assert ) { 318 | assert.equal( 1, 2 ); 319 | assert.equal( "foo", "bar" ); 320 | assert.equal( {}, {} ); 321 | assert.equal( [], [] ); 322 | }); 323 | 324 | QUnit.test( "notEqual", function( assert ) { 325 | assert.notEqual( 1, 1 ); 326 | assert.notEqual( "foo", "foo" ); 327 | assert.notEqual( "foo", [ "foo" ] ); 328 | assert.notEqual( "foo", { toString: function() { return "foo"; } } ); 329 | assert.notEqual( 0, [ 0 ] ); 330 | }); 331 | 332 | QUnit.test( "strictEqual", function( assert ) { 333 | assert.strictEqual( 1, 2 ); 334 | assert.strictEqual( "foo", "bar" ); 335 | assert.strictEqual( "foo", [ "foo" ] ); 336 | assert.strictEqual( "1", 1 ); 337 | assert.strictEqual( "foo", { toString: function() { return "foo"; } } ); 338 | }); 339 | 340 | QUnit.test( "notStrictEqual", function( assert ) { 341 | assert.notStrictEqual( 1, 1 ); 342 | assert.notStrictEqual( "foo", "foo" ); 343 | }); 344 | 345 | QUnit.test( "deepEqual", function( assert ) { 346 | assert.deepEqual( [ "foo", "bar" ], [ "foo" ] ); 347 | }); 348 | 349 | QUnit.test( "notDeepEqual", function( assert ) { 350 | assert.notDeepEqual( [ "foo", "bar" ], [ "foo", "bar" ] ); 351 | }); 352 | 353 | QUnit.test( "propEqual", function( assert ) { 354 | function Foo( x, y, z ) { 355 | this.x = x; 356 | this.y = y; 357 | this.z = z; 358 | } 359 | Foo.prototype.baz = function() {}; 360 | Foo.prototype.bar = "prototype"; 361 | 362 | assert.propEqual( 363 | new Foo( "1", 2, 3 ), 364 | { 365 | x: 1, 366 | y: "2", 367 | z: 3 368 | } 369 | ); 370 | }); 371 | 372 | QUnit.test( "notPropEqual", function( assert ) { 373 | function Foo( x, y, z ) { 374 | this.x = x; 375 | this.y = y; 376 | this.z = z; 377 | } 378 | Foo.prototype.baz = function() {}; 379 | Foo.prototype.bar = "prototype"; 380 | 381 | assert.notPropEqual( 382 | new Foo( 1, "2", [] ), 383 | { 384 | x: 1, 385 | y: "2", 386 | z: [] 387 | } 388 | ); 389 | }); 390 | 391 | QUnit.test( "throws", function( assert ) { 392 | assert.throws( 393 | function() { 394 | return; 395 | }, 396 | "throws fails without a thrown error" 397 | ); 398 | 399 | assert.throws( 400 | function() { 401 | throw "foo"; 402 | }, 403 | /bar/, 404 | "throws fail when regexp doens't match the error message" 405 | ); 406 | }); 407 | -------------------------------------------------------------------------------- /test/main/async.js: -------------------------------------------------------------------------------- 1 | var globalStartError, globalStopError; 2 | 3 | function _setupForFailingAssertionsAfterAsyncDone( assert ) { 4 | var errorRegex = new RegExp( "Assertion after the final `assert\\.async` " + 5 | "was resolved" ); 6 | 7 | // Duck-punch to force an Error to be thrown instead of a `pushFailure` call 8 | assert.test.pushFailure = function( msg ) { 9 | 10 | // Increment the semaphore, preventing post-`done` assertions from causing another failure 11 | assert.test.semaphore++; 12 | 13 | throw new Error( msg ); 14 | }; 15 | 16 | // Provide a wrapper for `assert.throws` to allow test to pass this post-`done` assertion 17 | this._assertCatch = function( fn ) { 18 | assert.throws.call( assert, fn, errorRegex ); 19 | 20 | // Decrement the semaphore to undo the effects of the duck-punched `test.pushFailure` above 21 | assert.test.semaphore--; 22 | }; 23 | } 24 | 25 | QUnit.begin(function() { 26 | try { 27 | QUnit.start(); 28 | } 29 | catch ( thrownError ) { 30 | globalStartError = thrownError.message; 31 | } 32 | }); 33 | 34 | try { 35 | QUnit.stop(); 36 | } 37 | catch ( thrownError ) { 38 | globalStopError = thrownError.message; 39 | } 40 | 41 | QUnit.module( "global start/stop errors" ); 42 | 43 | QUnit.test( "Call start() when already started", function( assert ) { 44 | assert.expect( 1 ); 45 | assert.equal( globalStartError, "Called start() outside of a test context while already " + 46 | "started" ); 47 | }); 48 | 49 | QUnit.test( "Call stop() outside of test context", function( assert ) { 50 | assert.expect( 1 ); 51 | assert.equal( globalStopError, "Called stop() outside of a test context" ); 52 | }); 53 | 54 | QUnit.module( "start/stop" ); 55 | 56 | QUnit.test( "parallel calls", function( assert ) { 57 | assert.expect( 2 ); 58 | QUnit.stop(); 59 | setTimeout(function() { 60 | assert.ok( true ); 61 | QUnit.start(); 62 | }); 63 | QUnit.stop(); 64 | setTimeout(function() { 65 | assert.ok( true ); 66 | QUnit.start(); 67 | }); 68 | }); 69 | 70 | QUnit.test( "waterfall calls", function( assert ) { 71 | assert.expect( 2 ); 72 | QUnit.stop(); 73 | setTimeout(function() { 74 | assert.ok( true, "first" ); 75 | QUnit.start(); 76 | QUnit.stop(); 77 | setTimeout(function() { 78 | assert.ok( true, "second" ); 79 | QUnit.start(); 80 | }); 81 | }); 82 | }); 83 | 84 | QUnit.test( "fails if start is called more than stop", function( assert ) { 85 | assert.expect( 1 ); 86 | 87 | // Duck-punch to force an Error to be thrown instead of a `pushFailure` call 88 | assert.test.pushFailure = function( msg ) { 89 | throw new Error( msg ); 90 | }; 91 | assert.throws(function() { 92 | QUnit.start(); 93 | }, new RegExp( "Called start\\(\\) while already started \\(test's semaphore was 0 " + 94 | "already\\)" ) ); 95 | }); 96 | 97 | QUnit.module( "asyncTest" ); 98 | 99 | QUnit.asyncTest( "asyncTest", function( assert ) { 100 | assert.expect( 1 ); 101 | setTimeout(function() { 102 | assert.ok( true ); 103 | QUnit.start(); 104 | }); 105 | }); 106 | 107 | QUnit.module( "assert.async" ); 108 | 109 | QUnit.test( "single call", function( assert ) { 110 | var done = assert.async(); 111 | 112 | assert.expect( 1 ); 113 | setTimeout(function() { 114 | assert.ok( true ); 115 | done(); 116 | }); 117 | }); 118 | 119 | QUnit.test( "parallel calls", function( assert ) { 120 | var done1 = assert.async(), 121 | done2 = assert.async(); 122 | 123 | assert.expect( 2 ); 124 | setTimeout(function() { 125 | assert.ok( true ); 126 | done1(); 127 | }); 128 | setTimeout(function() { 129 | assert.ok( true ); 130 | done2(); 131 | }); 132 | }); 133 | 134 | QUnit.test( "waterfall calls", function( assert ) { 135 | var done2, 136 | done1 = assert.async(); 137 | 138 | assert.expect( 2 ); 139 | setTimeout(function() { 140 | assert.ok( true, "first" ); 141 | done1(); 142 | done2 = assert.async(); 143 | setTimeout(function() { 144 | assert.ok( true, "second" ); 145 | done2(); 146 | }); 147 | }); 148 | }); 149 | 150 | QUnit.test( "fails if callback is called more than once in test", function( assert ) { 151 | 152 | // Having an outer async flow in this test avoids the need to manually modify QUnit internals 153 | // in order to avoid post-`done` assertions causing additional failures 154 | var done = assert.async(); 155 | 156 | assert.expect( 1 ); 157 | 158 | // Duck-punch to force an Error to be thrown instead of a `pushFailure` call 159 | assert.test.pushFailure = function( msg ) { 160 | throw new Error( msg ); 161 | }; 162 | 163 | assert.throws(function() { 164 | var overDone = assert.async(); 165 | overDone(); 166 | overDone(); 167 | }, new RegExp( "Called the callback returned from `assert.async` more than once" ) ); 168 | 169 | done(); 170 | }); 171 | 172 | QUnit.module( "assert.async fails if callback is called more than once in", { 173 | beforeEach: function( assert ) { 174 | 175 | // Having an outer async flow in this test avoids the need to manually modify QUnit 176 | // internals in order to avoid post-`done` assertions causing additional failures 177 | var done = assert.async(); 178 | 179 | assert.expect( 1 ); 180 | 181 | // Duck-punch to force an Error to be thrown instead of a `pushFailure` call 182 | assert.test.pushFailure = function( msg ) { 183 | throw new Error( msg ); 184 | }; 185 | 186 | assert.throws(function() { 187 | var overDone = assert.async(); 188 | overDone(); 189 | overDone(); 190 | }, new RegExp( "Called the callback returned from `assert.async` more than once" ) ); 191 | 192 | done(); 193 | } 194 | }); 195 | 196 | QUnit.test( "beforeEach", function( /* assert */ ) { 197 | // noop 198 | }); 199 | 200 | QUnit.module( "assert.async fails if callback is called more than once in", { 201 | afterEach: function( assert ) { 202 | 203 | // Having an outer async flow in this test avoids the need to manually modify QUnit 204 | // internals in order to avoid post-`done` assertions causing additional failures 205 | var done = assert.async(); 206 | 207 | assert.expect( 1 ); 208 | 209 | // Duck-punch to force an Error to be thrown instead of a `pushFailure` call 210 | assert.test.pushFailure = function( msg ) { 211 | throw new Error( msg ); 212 | }; 213 | 214 | assert.throws(function() { 215 | var overDone = assert.async(); 216 | overDone(); 217 | overDone(); 218 | }, new RegExp( "Called the callback returned from `assert.async` more than once" ) ); 219 | 220 | done(); 221 | } 222 | }); 223 | 224 | QUnit.test( "afterEach", function( /* assert */ ) { 225 | // noop 226 | }); 227 | 228 | QUnit.module( "assert.async in beforeEach", { 229 | beforeEach: function( assert ) { 230 | var done = assert.async(), 231 | testContext = this; 232 | setTimeout(function() { 233 | testContext.state = "beforeEach"; 234 | done(); 235 | }); 236 | } 237 | }); 238 | 239 | QUnit.test( "beforeEach synchronized", function( assert ) { 240 | assert.expect( 1 ); 241 | assert.equal( this.state, "beforeEach", "beforeEach synchronized before test callback was " + 242 | "executed" ); 243 | }); 244 | 245 | QUnit.module( "assert.async before afterEach", { 246 | afterEach: function( assert ) { 247 | assert.equal( this.state, "done", "test callback synchronized before afterEach was " + 248 | "executed" ); 249 | } 250 | }); 251 | 252 | QUnit.test( "afterEach will synchronize", function( assert ) { 253 | assert.expect( 1 ); 254 | var done = assert.async(), 255 | testContext = this; 256 | setTimeout(function() { 257 | testContext.state = "done"; 258 | done(); 259 | }); 260 | }); 261 | 262 | QUnit.module( "assert.async in afterEach", { 263 | afterEach: function( assert ) { 264 | var done = assert.async(); 265 | setTimeout(function() { 266 | assert.ok( true, "afterEach synchronized before test was finished" ); 267 | done(); 268 | }); 269 | } 270 | }); 271 | 272 | QUnit.test( "afterEach will synchronize", function( assert ) { 273 | assert.expect( 1 ); 274 | }); 275 | 276 | QUnit.module( "assert.async callback event loop timing" ); 277 | 278 | QUnit.test( "`done` can be called synchronously", function( assert ) { 279 | var done; 280 | 281 | assert.expect( 1 ); 282 | done = assert.async(); 283 | 284 | assert.ok( true ); 285 | done(); 286 | }); 287 | 288 | QUnit.test( "sole `done` is called last", function( assert ) { 289 | var done; 290 | 291 | assert.expect( 1 ); 292 | done = assert.async(); 293 | setTimeout(function() { 294 | assert.ok( true, "should pass if called before `done`" ); 295 | done(); 296 | }); 297 | }); 298 | 299 | QUnit.test( "multiple `done` calls, no assertions after final `done`", function( assert ) { 300 | var done1, done2; 301 | 302 | assert.expect( 2 ); 303 | done1 = assert.async(); 304 | done2 = assert.async(); 305 | setTimeout(function() { 306 | done1(); 307 | assert.ok( true, "should pass if called after this `done` but before final `done`" ); 308 | }); 309 | setTimeout(function() { 310 | assert.ok( true, "should pass if called before final `done`" ); 311 | done2(); 312 | }); 313 | }); 314 | 315 | QUnit.module( "assertions after final assert.async callback in test callback fail", { 316 | beforeEach: function( assert ) { 317 | _setupForFailingAssertionsAfterAsyncDone.call( this, assert ); 318 | } 319 | }); 320 | 321 | QUnit.test( "sole `done` is called synchronously BEFORE passing assertion", function( assert ) { 322 | assert.expect( 1 ); 323 | 324 | assert.async()(); 325 | 326 | this._assertCatch(function() { 327 | 328 | // FAIL!!! (with duck-punch to force an Error to be thrown instead of a `pushFailure` call) 329 | assert.ok( true, "should fail with a special `done`-related error message if called " + 330 | "after `done` even if result is passing" ); 331 | }); 332 | }); 333 | 334 | QUnit.test( "sole `done` is called BEFORE assertion", function( assert ) { 335 | var testContext = this, 336 | done = assert.async(); 337 | 338 | assert.expect( 1 ); 339 | 340 | setTimeout(function() { 341 | done(); 342 | 343 | testContext._assertCatch(function() { 344 | 345 | // FAIL!!! (with duck-punch to force an Error to be thrown instead of `pushFailure`) 346 | assert.ok( true, "should fail with a special `done`-related error message if called " + 347 | "after `done` even if result is passing" ); 348 | }); 349 | }); 350 | }); 351 | 352 | QUnit.test( "multiple `done` calls, final `done` is called BEFORE assertion", function( assert ) { 353 | var testContext = this, 354 | done1 = assert.async(), 355 | done2 = assert.async(); 356 | 357 | assert.expect( 2 ); 358 | setTimeout(function() { 359 | done1(); 360 | assert.ok( true, "should pass as this is not after the final `done`" ); 361 | }); 362 | setTimeout(function() { 363 | done2(); 364 | 365 | testContext._assertCatch(function() { 366 | 367 | // FAIL!!! (with duck-punch to force an Error to be thrown instead of `pushFailure`) 368 | assert.ok( true, "should fail with a special `done`-related error message if called " + 369 | "after final `done` even if result is passing" ); 370 | }); 371 | }); 372 | }); 373 | 374 | QUnit.test( "cannot allow assertions between first `done` call and second `assert.async` call", 375 | function( assert ) { 376 | var done2, 377 | testContext = this, 378 | done1 = assert.async(); 379 | 380 | assert.expect( 1 ); 381 | setTimeout(function() { 382 | done1(); 383 | 384 | testContext._assertCatch(function() { 385 | 386 | // FAIL!!! (with duck-punch to force an Error to be thrown instead of `pushFailure`) 387 | assert.ok( true, "should fail with a special `done`-related error message if called " + 388 | "after final `done` even if result is passing" ); 389 | 390 | done2 = assert.async(); 391 | setTimeout(function() { 392 | assert.ok( false, "Should never reach this point anyway" ); 393 | done2(); 394 | }); 395 | }); 396 | }); 397 | }); 398 | 399 | QUnit.module( "assert after last done in beforeEach fail, but allow other phases to run", { 400 | beforeEach: function( assert ) { 401 | _setupForFailingAssertionsAfterAsyncDone.call( this, assert ); 402 | 403 | // THIS IS THE ACTUAL TEST! 404 | assert.expect( 3 ); 405 | this._assertCatch(function() { 406 | assert.async()(); 407 | 408 | // FAIL!!! (with duck-punch to force an Error to be thrown instead of `pushFailure`) 409 | assert.ok( true, "should fail with a special `done`-related error message if called " + 410 | "after final `done` even if result is passing" ); 411 | }); 412 | }, 413 | 414 | afterEach: function( assert ) { 415 | assert.ok( true, "This assertion should still run in afterEach" ); 416 | } 417 | }); 418 | 419 | QUnit.test( "beforeEach will fail but test and afterEach will still run", function( assert ) { 420 | assert.ok( true, "This assertion should still run in the test callback" ); 421 | }); 422 | 423 | QUnit.module( "assert after last done in test fail, but allow other phases to run", { 424 | beforeEach: function( assert ) { 425 | _setupForFailingAssertionsAfterAsyncDone.call( this, assert ); 426 | 427 | assert.expect( 3 ); 428 | assert.ok( true, "This assertion should still run in beforeEach" ); 429 | }, 430 | 431 | afterEach: function( assert ) { 432 | assert.ok( true, "This assertion should still run in afterEach" ); 433 | } 434 | }); 435 | 436 | QUnit.test( "test will fail, but beforeEach and afterEach will still run", function( assert ) { 437 | this._assertCatch(function() { 438 | assert.async()(); 439 | 440 | // FAIL!!! (with duck-punch to force an Error to be thrown instead of `pushFailure`) 441 | assert.ok( true, "should fail with a special `done`-related error message if called " + 442 | "after final `done` even if result is passing" ); 443 | }); 444 | }); 445 | 446 | QUnit.module( "assert after last done in afterEach fail, but allow other phases to run", { 447 | beforeEach: function( assert ) { 448 | _setupForFailingAssertionsAfterAsyncDone.call( this, assert ); 449 | 450 | assert.expect( 3 ); 451 | assert.ok( true, "This assertion should still run in beforeEach" ); 452 | }, 453 | 454 | afterEach: function( assert ) { 455 | this._assertCatch(function() { 456 | assert.async()(); 457 | 458 | // FAIL!!! (with duck-punch to force an Error to be thrown instead of `pushFailure`) 459 | assert.ok( true, "should fail with a special `done`-related error message if called " + 460 | "after final `done` even if result is passing" ); 461 | }); 462 | } 463 | }); 464 | 465 | QUnit.test( "afterEach will fail but beforeEach and test will still run", function( assert ) { 466 | assert.ok( true, "This assertion should still run in the test callback" ); 467 | }); 468 | -------------------------------------------------------------------------------- /src/test.js: -------------------------------------------------------------------------------- 1 | function Test( settings ) { 2 | var i, l; 3 | 4 | ++Test.count; 5 | 6 | extend( this, settings ); 7 | this.assertions = []; 8 | this.semaphore = 0; 9 | this.usedAsync = false; 10 | this.module = config.currentModule; 11 | this.stack = sourceFromStacktrace( 3 ); 12 | 13 | // Register unique strings 14 | for ( i = 0, l = this.module.tests; i < l.length; i++ ) { 15 | if ( this.module.tests[ i ].name === this.testName ) { 16 | this.testName += " "; 17 | } 18 | } 19 | 20 | this.testId = generateHash( this.module.name, this.testName ); 21 | 22 | this.module.tests.push({ 23 | name: this.testName, 24 | testId: this.testId 25 | }); 26 | 27 | if ( settings.skip ) { 28 | 29 | // Skipped tests will fully ignore any sent callback 30 | this.callback = function() {}; 31 | this.async = false; 32 | this.expected = 0; 33 | } else { 34 | this.assert = new Assert( this ); 35 | } 36 | } 37 | 38 | Test.count = 0; 39 | 40 | Test.prototype = { 41 | before: function() { 42 | if ( 43 | 44 | // Emit moduleStart when we're switching from one module to another 45 | this.module !== config.previousModule || 46 | 47 | // They could be equal (both undefined) but if the previousModule property doesn't 48 | // yet exist it means this is the first test in a suite that isn't wrapped in a 49 | // module, in which case we'll just emit a moduleStart event for 'undefined'. 50 | // Without this, reporters can get testStart before moduleStart which is a problem. 51 | !hasOwn.call( config, "previousModule" ) 52 | ) { 53 | if ( hasOwn.call( config, "previousModule" ) ) { 54 | runLoggingCallbacks( "moduleDone", { 55 | name: config.previousModule.name, 56 | tests: config.previousModule.tests, 57 | failed: config.moduleStats.bad, 58 | passed: config.moduleStats.all - config.moduleStats.bad, 59 | total: config.moduleStats.all, 60 | runtime: now() - config.moduleStats.started 61 | }); 62 | } 63 | config.previousModule = this.module; 64 | config.moduleStats = { all: 0, bad: 0, started: now() }; 65 | runLoggingCallbacks( "moduleStart", { 66 | name: this.module.name, 67 | tests: this.module.tests 68 | }); 69 | } 70 | 71 | config.current = this; 72 | 73 | if ( this.module.testEnvironment ) { 74 | delete this.module.testEnvironment.beforeEach; 75 | delete this.module.testEnvironment.afterEach; 76 | } 77 | this.testEnvironment = extend( {}, this.module.testEnvironment ); 78 | 79 | this.started = now(); 80 | runLoggingCallbacks( "testStart", { 81 | name: this.testName, 82 | module: this.module.name, 83 | testId: this.testId 84 | }); 85 | 86 | if ( !config.pollution ) { 87 | saveGlobal(); 88 | } 89 | }, 90 | 91 | run: function() { 92 | var promise; 93 | 94 | config.current = this; 95 | 96 | if ( this.async ) { 97 | QUnit.stop(); 98 | } 99 | 100 | this.callbackStarted = now(); 101 | 102 | if ( config.notrycatch ) { 103 | promise = this.callback.call( this.testEnvironment, this.assert ); 104 | this.resolvePromise( promise ); 105 | return; 106 | } 107 | 108 | try { 109 | promise = this.callback.call( this.testEnvironment, this.assert ); 110 | this.resolvePromise( promise ); 111 | } catch ( e ) { 112 | this.pushFailure( "Died on test #" + ( this.assertions.length + 1 ) + " " + 113 | this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) ); 114 | 115 | // else next test will carry the responsibility 116 | saveGlobal(); 117 | 118 | // Restart the tests if they're blocking 119 | if ( config.blocking ) { 120 | QUnit.start(); 121 | } 122 | } 123 | }, 124 | 125 | after: function() { 126 | checkPollution(); 127 | }, 128 | 129 | queueHook: function( hook, hookName ) { 130 | var promise, 131 | test = this; 132 | return function runHook() { 133 | config.current = test; 134 | if ( config.notrycatch ) { 135 | promise = hook.call( test.testEnvironment, test.assert ); 136 | test.resolvePromise( promise, hookName ); 137 | return; 138 | } 139 | try { 140 | promise = hook.call( test.testEnvironment, test.assert ); 141 | test.resolvePromise( promise, hookName ); 142 | } catch ( error ) { 143 | test.pushFailure( hookName + " failed on " + test.testName + ": " + 144 | ( error.message || error ), extractStacktrace( error, 0 ) ); 145 | } 146 | }; 147 | }, 148 | 149 | // Currently only used for module level hooks, can be used to add global level ones 150 | hooks: function( handler ) { 151 | var hooks = []; 152 | 153 | // Hooks are ignored on skipped tests 154 | if ( this.skip ) { 155 | return hooks; 156 | } 157 | 158 | if ( this.module.testEnvironment && 159 | QUnit.objectType( this.module.testEnvironment[ handler ] ) === "function" ) { 160 | hooks.push( this.queueHook( this.module.testEnvironment[ handler ], handler ) ); 161 | } 162 | 163 | return hooks; 164 | }, 165 | 166 | finish: function() { 167 | config.current = this; 168 | if ( config.requireExpects && this.expected === null ) { 169 | this.pushFailure( "Expected number of assertions to be defined, but expect() was " + 170 | "not called.", this.stack ); 171 | } else if ( this.expected !== null && this.expected !== this.assertions.length ) { 172 | this.pushFailure( "Expected " + this.expected + " assertions, but " + 173 | this.assertions.length + " were run", this.stack ); 174 | } else if ( this.expected === null && !this.assertions.length ) { 175 | this.pushFailure( "Expected at least one assertion, but none were run - call " + 176 | "expect(0) to accept zero assertions.", this.stack ); 177 | } 178 | 179 | var i, 180 | bad = 0; 181 | 182 | this.runtime = now() - this.started; 183 | config.stats.all += this.assertions.length; 184 | config.moduleStats.all += this.assertions.length; 185 | 186 | for ( i = 0; i < this.assertions.length; i++ ) { 187 | if ( !this.assertions[ i ].result ) { 188 | bad++; 189 | config.stats.bad++; 190 | config.moduleStats.bad++; 191 | } 192 | } 193 | 194 | runLoggingCallbacks( "testDone", { 195 | name: this.testName, 196 | module: this.module.name, 197 | skipped: !!this.skip, 198 | failed: bad, 199 | passed: this.assertions.length - bad, 200 | total: this.assertions.length, 201 | runtime: this.runtime, 202 | 203 | // HTML Reporter use 204 | assertions: this.assertions, 205 | testId: this.testId, 206 | 207 | // Source of Test 208 | source: this.stack, 209 | 210 | // DEPRECATED: this property will be removed in 2.0.0, use runtime instead 211 | duration: this.runtime 212 | }); 213 | 214 | // QUnit.reset() is deprecated and will be replaced for a new 215 | // fixture reset function on QUnit 2.0/2.1. 216 | // It's still called here for backwards compatibility handling 217 | QUnit.reset(); 218 | 219 | config.current = undefined; 220 | }, 221 | 222 | queue: function() { 223 | var bad, 224 | test = this; 225 | 226 | if ( !this.valid() ) { 227 | return; 228 | } 229 | 230 | function run() { 231 | 232 | // each of these can by async 233 | synchronize([ 234 | function() { 235 | test.before(); 236 | }, 237 | 238 | test.hooks( "beforeEach" ), 239 | 240 | function() { 241 | test.run(); 242 | }, 243 | 244 | test.hooks( "afterEach" ).reverse(), 245 | 246 | function() { 247 | test.after(); 248 | }, 249 | function() { 250 | test.finish(); 251 | } 252 | ]); 253 | } 254 | 255 | // `bad` initialized at top of scope 256 | // defer when previous test run passed, if storage is available 257 | bad = QUnit.config.reorder && defined.sessionStorage && 258 | +sessionStorage.getItem( "qunit-test-" + this.module.name + "-" + this.testName ); 259 | 260 | if ( bad ) { 261 | run(); 262 | } else { 263 | synchronize( run, true ); 264 | } 265 | }, 266 | 267 | push: function( result, actual, expected, message, negative ) { 268 | var source, 269 | details = { 270 | module: this.module.name, 271 | name: this.testName, 272 | result: result, 273 | message: message, 274 | actual: actual, 275 | expected: expected, 276 | testId: this.testId, 277 | negative: negative || false, 278 | runtime: now() - this.started 279 | }; 280 | 281 | if ( !result ) { 282 | source = sourceFromStacktrace(); 283 | 284 | if ( source ) { 285 | details.source = source; 286 | } 287 | } 288 | 289 | runLoggingCallbacks( "log", details ); 290 | 291 | this.assertions.push({ 292 | result: !!result, 293 | message: message 294 | }); 295 | }, 296 | 297 | pushFailure: function( message, source, actual ) { 298 | if ( !( this instanceof Test ) ) { 299 | throw new Error( "pushFailure() assertion outside test context, was " + 300 | sourceFromStacktrace( 2 ) ); 301 | } 302 | 303 | var details = { 304 | module: this.module.name, 305 | name: this.testName, 306 | result: false, 307 | message: message || "error", 308 | actual: actual || null, 309 | testId: this.testId, 310 | runtime: now() - this.started 311 | }; 312 | 313 | if ( source ) { 314 | details.source = source; 315 | } 316 | 317 | runLoggingCallbacks( "log", details ); 318 | 319 | this.assertions.push({ 320 | result: false, 321 | message: message 322 | }); 323 | }, 324 | 325 | resolvePromise: function( promise, phase ) { 326 | var then, message, 327 | test = this; 328 | if ( promise != null ) { 329 | then = promise.then; 330 | if ( QUnit.objectType( then ) === "function" ) { 331 | QUnit.stop(); 332 | then.call( 333 | promise, 334 | function() { QUnit.start(); }, 335 | function( error ) { 336 | message = "Promise rejected " + 337 | ( !phase ? "during" : phase.replace( /Each$/, "" ) ) + 338 | " " + test.testName + ": " + ( error.message || error ); 339 | test.pushFailure( message, extractStacktrace( error, 0 ) ); 340 | 341 | // else next test will carry the responsibility 342 | saveGlobal(); 343 | 344 | // Unblock 345 | QUnit.start(); 346 | } 347 | ); 348 | } 349 | } 350 | }, 351 | 352 | valid: function() { 353 | var include, 354 | filter = config.filter && config.filter.toLowerCase(), 355 | module = QUnit.urlParams.module && QUnit.urlParams.module.toLowerCase(), 356 | fullName = ( this.module.name + ": " + this.testName ).toLowerCase(); 357 | 358 | // Internally-generated tests are always valid 359 | if ( this.callback && this.callback.validTest ) { 360 | return true; 361 | } 362 | 363 | if ( config.testId.length > 0 && inArray( this.testId, config.testId ) < 0 ) { 364 | return false; 365 | } 366 | 367 | if ( module && ( !this.module.name || this.module.name.toLowerCase() !== module ) ) { 368 | return false; 369 | } 370 | 371 | if ( !filter ) { 372 | return true; 373 | } 374 | 375 | include = filter.charAt( 0 ) !== "!"; 376 | if ( !include ) { 377 | filter = filter.slice( 1 ); 378 | } 379 | 380 | // If the filter matches, we need to honour include 381 | if ( fullName.indexOf( filter ) !== -1 ) { 382 | return include; 383 | } 384 | 385 | // Otherwise, do the opposite 386 | return !include; 387 | } 388 | 389 | }; 390 | 391 | // Resets the test setup. Useful for tests that modify the DOM. 392 | /* 393 | DEPRECATED: Use multiple tests instead of resetting inside a test. 394 | Use testStart or testDone for custom cleanup. 395 | This method will throw an error in 2.0, and will be removed in 2.1 396 | */ 397 | QUnit.reset = function() { 398 | 399 | // Return on non-browser environments 400 | // This is necessary to not break on node tests 401 | if ( !defined.document ) { 402 | return; 403 | } 404 | 405 | var fixture = defined.document && document.getElementById && 406 | document.getElementById( "qunit-fixture" ); 407 | 408 | if ( fixture ) { 409 | fixture.innerHTML = config.fixture; 410 | } 411 | }; 412 | 413 | QUnit.pushFailure = function() { 414 | if ( !QUnit.config.current ) { 415 | throw new Error( "pushFailure() assertion outside test context, in " + 416 | sourceFromStacktrace( 2 ) ); 417 | } 418 | 419 | // Gets current test obj 420 | var currentTest = QUnit.config.current; 421 | 422 | return currentTest.pushFailure.apply( currentTest, arguments ); 423 | }; 424 | 425 | // Based on Java's String.hashCode, a simple but not 426 | // rigorously collision resistant hashing function 427 | function generateHash( module, testName ) { 428 | var hex, 429 | i = 0, 430 | hash = 0, 431 | str = module + "\x1C" + testName, 432 | len = str.length; 433 | 434 | for ( ; i < len; i++ ) { 435 | hash = ( ( hash << 5 ) - hash ) + str.charCodeAt( i ); 436 | hash |= 0; 437 | } 438 | 439 | // Convert the possibly negative integer hash code into an 8 character hex string, which isn't 440 | // strictly necessary but increases user understanding that the id is a SHA-like hash 441 | hex = ( 0x100000000 + hash ).toString( 16 ); 442 | if ( hex.length < 8 ) { 443 | hex = "0000000" + hex; 444 | } 445 | 446 | return hex.slice( -8 ); 447 | } 448 | 449 | function synchronize( callback, last ) { 450 | if ( QUnit.objectType( callback ) === "array" ) { 451 | while ( callback.length ) { 452 | synchronize( callback.shift() ); 453 | } 454 | return; 455 | } 456 | config.queue.push( callback ); 457 | 458 | if ( config.autorun && !config.blocking ) { 459 | process( last ); 460 | } 461 | } 462 | 463 | function saveGlobal() { 464 | config.pollution = []; 465 | 466 | if ( config.noglobals ) { 467 | for ( var key in global ) { 468 | if ( hasOwn.call( global, key ) ) { 469 | 470 | // in Opera sometimes DOM element ids show up here, ignore them 471 | if ( /^qunit-test-output/.test( key ) ) { 472 | continue; 473 | } 474 | config.pollution.push( key ); 475 | } 476 | } 477 | } 478 | } 479 | 480 | function checkPollution() { 481 | var newGlobals, 482 | deletedGlobals, 483 | old = config.pollution; 484 | 485 | saveGlobal(); 486 | 487 | newGlobals = diff( config.pollution, old ); 488 | if ( newGlobals.length > 0 ) { 489 | QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join( ", " ) ); 490 | } 491 | 492 | deletedGlobals = diff( old, config.pollution ); 493 | if ( deletedGlobals.length > 0 ) { 494 | QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join( ", " ) ); 495 | } 496 | } 497 | 498 | // Will be exposed as QUnit.asyncTest 499 | function asyncTest( testName, expected, callback ) { 500 | if ( arguments.length === 2 ) { 501 | callback = expected; 502 | expected = null; 503 | } 504 | 505 | QUnit.test( testName, expected, callback, true ); 506 | } 507 | 508 | // Will be exposed as QUnit.test 509 | function test( testName, expected, callback, async ) { 510 | var newTest; 511 | 512 | if ( arguments.length === 2 ) { 513 | callback = expected; 514 | expected = null; 515 | } 516 | 517 | newTest = new Test({ 518 | testName: testName, 519 | expected: expected, 520 | async: async, 521 | callback: callback 522 | }); 523 | 524 | newTest.queue(); 525 | } 526 | 527 | // Will be exposed as QUnit.skip 528 | function skip( testName ) { 529 | var test = new Test({ 530 | testName: testName, 531 | skip: true 532 | }); 533 | 534 | test.queue(); 535 | } 536 | -------------------------------------------------------------------------------- /reporter/html.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | // Don't load the HTML Reporter on non-Browser environments 4 | if ( typeof window === "undefined" || !window.document ) { 5 | return; 6 | } 7 | 8 | // Deprecated QUnit.init - Ref #530 9 | // Re-initialize the configuration options 10 | QUnit.init = function() { 11 | var tests, banner, result, qunit, 12 | config = QUnit.config; 13 | 14 | config.stats = { all: 0, bad: 0 }; 15 | config.moduleStats = { all: 0, bad: 0 }; 16 | config.started = 0; 17 | config.updateRate = 1000; 18 | config.blocking = false; 19 | config.autostart = true; 20 | config.autorun = false; 21 | config.filter = ""; 22 | config.queue = []; 23 | 24 | // Return on non-browser environments 25 | // This is necessary to not break on node tests 26 | if ( typeof window === "undefined" ) { 27 | return; 28 | } 29 | 30 | qunit = id( "qunit" ); 31 | if ( qunit ) { 32 | qunit.innerHTML = 33 | "

    " + escapeText( document.title ) + "

    " + 34 | "

    " + 35 | "
    " + 36 | "

    " + 37 | "
      "; 38 | } 39 | 40 | tests = id( "qunit-tests" ); 41 | banner = id( "qunit-banner" ); 42 | result = id( "qunit-testresult" ); 43 | 44 | if ( tests ) { 45 | tests.innerHTML = ""; 46 | } 47 | 48 | if ( banner ) { 49 | banner.className = ""; 50 | } 51 | 52 | if ( result ) { 53 | result.parentNode.removeChild( result ); 54 | } 55 | 56 | if ( tests ) { 57 | result = document.createElement( "p" ); 58 | result.id = "qunit-testresult"; 59 | result.className = "result"; 60 | tests.parentNode.insertBefore( result, tests ); 61 | result.innerHTML = "Running...
       "; 62 | } 63 | }; 64 | 65 | var config = QUnit.config, 66 | hasOwn = Object.prototype.hasOwnProperty, 67 | defined = { 68 | document: window.document !== undefined, 69 | sessionStorage: (function() { 70 | var x = "qunit-test-string"; 71 | try { 72 | sessionStorage.setItem( x, x ); 73 | sessionStorage.removeItem( x ); 74 | return true; 75 | } catch ( e ) { 76 | return false; 77 | } 78 | }()) 79 | }, 80 | modulesList = []; 81 | 82 | /** 83 | * Escape text for attribute or text content. 84 | */ 85 | function escapeText( s ) { 86 | if ( !s ) { 87 | return ""; 88 | } 89 | s = s + ""; 90 | 91 | // Both single quotes and double quotes (for attributes) 92 | return s.replace( /['"<>&]/g, function( s ) { 93 | switch ( s ) { 94 | case "'": 95 | return "'"; 96 | case "\"": 97 | return """; 98 | case "<": 99 | return "<"; 100 | case ">": 101 | return ">"; 102 | case "&": 103 | return "&"; 104 | } 105 | }); 106 | } 107 | 108 | /** 109 | * @param {HTMLElement} elem 110 | * @param {string} type 111 | * @param {Function} fn 112 | */ 113 | function addEvent( elem, type, fn ) { 114 | if ( elem.addEventListener ) { 115 | 116 | // Standards-based browsers 117 | elem.addEventListener( type, fn, false ); 118 | } else if ( elem.attachEvent ) { 119 | 120 | // support: IE <9 121 | elem.attachEvent( "on" + type, function() { 122 | var event = window.event; 123 | if ( !event.target ) { 124 | event.target = event.srcElement || document; 125 | } 126 | 127 | fn.call( elem, event ); 128 | }); 129 | } 130 | } 131 | 132 | /** 133 | * @param {Array|NodeList} elems 134 | * @param {string} type 135 | * @param {Function} fn 136 | */ 137 | function addEvents( elems, type, fn ) { 138 | var i = elems.length; 139 | while ( i-- ) { 140 | addEvent( elems[ i ], type, fn ); 141 | } 142 | } 143 | 144 | function hasClass( elem, name ) { 145 | return ( " " + elem.className + " " ).indexOf( " " + name + " " ) >= 0; 146 | } 147 | 148 | function addClass( elem, name ) { 149 | if ( !hasClass( elem, name ) ) { 150 | elem.className += ( elem.className ? " " : "" ) + name; 151 | } 152 | } 153 | 154 | function toggleClass( elem, name ) { 155 | if ( hasClass( elem, name ) ) { 156 | removeClass( elem, name ); 157 | } else { 158 | addClass( elem, name ); 159 | } 160 | } 161 | 162 | function removeClass( elem, name ) { 163 | var set = " " + elem.className + " "; 164 | 165 | // Class name may appear multiple times 166 | while ( set.indexOf( " " + name + " " ) >= 0 ) { 167 | set = set.replace( " " + name + " ", " " ); 168 | } 169 | 170 | // trim for prettiness 171 | elem.className = typeof set.trim === "function" ? set.trim() : set.replace( /^\s+|\s+$/g, "" ); 172 | } 173 | 174 | function id( name ) { 175 | return defined.document && document.getElementById && document.getElementById( name ); 176 | } 177 | 178 | function getUrlConfigHtml() { 179 | var i, j, val, 180 | escaped, escapedTooltip, 181 | selection = false, 182 | len = config.urlConfig.length, 183 | urlConfigHtml = ""; 184 | 185 | for ( i = 0; i < len; i++ ) { 186 | val = config.urlConfig[ i ]; 187 | if ( typeof val === "string" ) { 188 | val = { 189 | id: val, 190 | label: val 191 | }; 192 | } 193 | 194 | escaped = escapeText( val.id ); 195 | escapedTooltip = escapeText( val.tooltip ); 196 | 197 | if ( config[ val.id ] === undefined ) { 198 | config[ val.id ] = QUnit.urlParams[ val.id ]; 199 | } 200 | 201 | if ( !val.value || typeof val.value === "string" ) { 202 | urlConfigHtml += ""; 208 | } else { 209 | urlConfigHtml += ""; 238 | } 239 | } 240 | 241 | return urlConfigHtml; 242 | } 243 | 244 | // Handle "click" events on toolbar checkboxes and "change" for select menus. 245 | // Updates the URL with the new state of `config.urlConfig` values. 246 | function toolbarChanged() { 247 | var updatedUrl, value, 248 | field = this, 249 | params = {}; 250 | 251 | // Detect if field is a select menu or a checkbox 252 | if ( "selectedIndex" in field ) { 253 | value = field.options[ field.selectedIndex ].value || undefined; 254 | } else { 255 | value = field.checked ? ( field.defaultValue || true ) : undefined; 256 | } 257 | 258 | params[ field.name ] = value; 259 | updatedUrl = setUrl( params ); 260 | 261 | if ( "hidepassed" === field.name && "replaceState" in window.history ) { 262 | config[ field.name ] = value || false; 263 | if ( value ) { 264 | addClass( id( "qunit-tests" ), "hidepass" ); 265 | } else { 266 | removeClass( id( "qunit-tests" ), "hidepass" ); 267 | } 268 | 269 | // It is not necessary to refresh the whole page 270 | window.history.replaceState( null, "", updatedUrl ); 271 | } else { 272 | window.location = updatedUrl; 273 | } 274 | } 275 | 276 | function setUrl( params ) { 277 | var key, 278 | querystring = "?"; 279 | 280 | params = QUnit.extend( QUnit.extend( {}, QUnit.urlParams ), params ); 281 | 282 | for ( key in params ) { 283 | if ( hasOwn.call( params, key ) ) { 284 | if ( params[ key ] === undefined ) { 285 | continue; 286 | } 287 | querystring += encodeURIComponent( key ); 288 | if ( params[ key ] !== true ) { 289 | querystring += "=" + encodeURIComponent( params[ key ] ); 290 | } 291 | querystring += "&"; 292 | } 293 | } 294 | return location.protocol + "//" + location.host + 295 | location.pathname + querystring.slice( 0, -1 ); 296 | } 297 | 298 | function applyUrlParams() { 299 | var selectedModule, 300 | modulesList = id( "qunit-modulefilter" ), 301 | filter = id( "qunit-filter-input" ).value; 302 | 303 | selectedModule = modulesList ? 304 | decodeURIComponent( modulesList.options[ modulesList.selectedIndex ].value ) : 305 | undefined; 306 | 307 | window.location = setUrl({ 308 | module: ( selectedModule === "" ) ? undefined : selectedModule, 309 | filter: ( filter === "" ) ? undefined : filter, 310 | 311 | // Remove testId filter 312 | testId: undefined 313 | }); 314 | } 315 | 316 | function toolbarUrlConfigContainer() { 317 | var urlConfigContainer = document.createElement( "span" ); 318 | 319 | urlConfigContainer.innerHTML = getUrlConfigHtml(); 320 | addClass( urlConfigContainer, "qunit-url-config" ); 321 | 322 | // For oldIE support: 323 | // * Add handlers to the individual elements instead of the container 324 | // * Use "click" instead of "change" for checkboxes 325 | addEvents( urlConfigContainer.getElementsByTagName( "input" ), "click", toolbarChanged ); 326 | addEvents( urlConfigContainer.getElementsByTagName( "select" ), "change", toolbarChanged ); 327 | 328 | return urlConfigContainer; 329 | } 330 | 331 | function toolbarLooseFilter() { 332 | var filter = document.createElement( "form" ), 333 | label = document.createElement( "label" ), 334 | input = document.createElement( "input" ), 335 | button = document.createElement( "button" ); 336 | 337 | addClass( filter, "qunit-filter" ); 338 | 339 | label.innerHTML = "Filter: "; 340 | 341 | input.type = "text"; 342 | input.value = config.filter || ""; 343 | input.name = "filter"; 344 | input.id = "qunit-filter-input"; 345 | 346 | button.innerHTML = "Go"; 347 | 348 | label.appendChild( input ); 349 | 350 | filter.appendChild( label ); 351 | filter.appendChild( button ); 352 | addEvent( filter, "submit", function( ev ) { 353 | applyUrlParams(); 354 | 355 | if ( ev && ev.preventDefault ) { 356 | ev.preventDefault(); 357 | } 358 | 359 | return false; 360 | }); 361 | 362 | return filter; 363 | } 364 | 365 | function toolbarModuleFilterHtml() { 366 | var i, 367 | moduleFilterHtml = ""; 368 | 369 | if ( !modulesList.length ) { 370 | return false; 371 | } 372 | 373 | modulesList.sort(function( a, b ) { 374 | return a.localeCompare( b ); 375 | }); 376 | 377 | moduleFilterHtml += "" + 378 | ""; 389 | 390 | return moduleFilterHtml; 391 | } 392 | 393 | function toolbarModuleFilter() { 394 | var toolbar = id( "qunit-testrunner-toolbar" ), 395 | moduleFilter = document.createElement( "span" ), 396 | moduleFilterHtml = toolbarModuleFilterHtml(); 397 | 398 | if ( !toolbar || !moduleFilterHtml ) { 399 | return false; 400 | } 401 | 402 | moduleFilter.setAttribute( "id", "qunit-modulefilter-container" ); 403 | moduleFilter.innerHTML = moduleFilterHtml; 404 | 405 | addEvent( moduleFilter.lastChild, "change", applyUrlParams ); 406 | 407 | toolbar.appendChild( moduleFilter ); 408 | } 409 | 410 | function appendToolbar() { 411 | var toolbar = id( "qunit-testrunner-toolbar" ); 412 | 413 | if ( toolbar ) { 414 | toolbar.appendChild( toolbarUrlConfigContainer() ); 415 | toolbar.appendChild( toolbarLooseFilter() ); 416 | } 417 | } 418 | 419 | function appendHeader() { 420 | var header = id( "qunit-header" ); 421 | 422 | if ( header ) { 423 | header.innerHTML = "" + header.innerHTML + " "; 426 | } 427 | } 428 | 429 | function appendBanner() { 430 | var banner = id( "qunit-banner" ); 431 | 432 | if ( banner ) { 433 | banner.className = ""; 434 | } 435 | } 436 | 437 | function appendTestResults() { 438 | var tests = id( "qunit-tests" ), 439 | result = id( "qunit-testresult" ); 440 | 441 | if ( result ) { 442 | result.parentNode.removeChild( result ); 443 | } 444 | 445 | if ( tests ) { 446 | tests.innerHTML = ""; 447 | result = document.createElement( "p" ); 448 | result.id = "qunit-testresult"; 449 | result.className = "result"; 450 | tests.parentNode.insertBefore( result, tests ); 451 | result.innerHTML = "Running...
       "; 452 | } 453 | } 454 | 455 | function storeFixture() { 456 | var fixture = id( "qunit-fixture" ); 457 | if ( fixture ) { 458 | config.fixture = fixture.innerHTML; 459 | } 460 | } 461 | 462 | function appendUserAgent() { 463 | var userAgent = id( "qunit-userAgent" ); 464 | 465 | if ( userAgent ) { 466 | userAgent.innerHTML = ""; 467 | userAgent.appendChild( 468 | document.createTextNode( 469 | "QUnit " + QUnit.version + "; " + navigator.userAgent 470 | ) 471 | ); 472 | } 473 | } 474 | 475 | function appendTestsList( modules ) { 476 | var i, l, x, z, test, moduleObj; 477 | 478 | for ( i = 0, l = modules.length; i < l; i++ ) { 479 | moduleObj = modules[ i ]; 480 | 481 | if ( moduleObj.name ) { 482 | modulesList.push( moduleObj.name ); 483 | } 484 | 485 | for ( x = 0, z = moduleObj.tests.length; x < z; x++ ) { 486 | test = moduleObj.tests[ x ]; 487 | 488 | appendTest( test.name, test.testId, moduleObj.name ); 489 | } 490 | } 491 | } 492 | 493 | function appendTest( name, testId, moduleName ) { 494 | var title, rerunTrigger, testBlock, assertList, 495 | tests = id( "qunit-tests" ); 496 | 497 | if ( !tests ) { 498 | return; 499 | } 500 | 501 | title = document.createElement( "strong" ); 502 | title.innerHTML = getNameHtml( name, moduleName ); 503 | 504 | rerunTrigger = document.createElement( "a" ); 505 | rerunTrigger.innerHTML = "Rerun"; 506 | rerunTrigger.href = setUrl({ testId: testId }); 507 | 508 | testBlock = document.createElement( "li" ); 509 | testBlock.appendChild( title ); 510 | testBlock.appendChild( rerunTrigger ); 511 | testBlock.id = "qunit-test-output-" + testId; 512 | 513 | assertList = document.createElement( "ol" ); 514 | assertList.className = "qunit-assert-list"; 515 | 516 | testBlock.appendChild( assertList ); 517 | 518 | tests.appendChild( testBlock ); 519 | } 520 | 521 | // HTML Reporter initialization and load 522 | QUnit.begin(function( details ) { 523 | var qunit = id( "qunit" ); 524 | 525 | // Fixture is the only one necessary to run without the #qunit element 526 | storeFixture(); 527 | 528 | if ( qunit ) { 529 | qunit.innerHTML = 530 | "

      " + escapeText( document.title ) + "

      " + 531 | "

      " + 532 | "
      " + 533 | "

      " + 534 | "
        "; 535 | } 536 | 537 | appendHeader(); 538 | appendBanner(); 539 | appendTestResults(); 540 | appendUserAgent(); 541 | appendToolbar(); 542 | appendTestsList( details.modules ); 543 | toolbarModuleFilter(); 544 | 545 | if ( qunit && config.hidepassed ) { 546 | addClass( qunit.lastChild, "hidepass" ); 547 | } 548 | }); 549 | 550 | QUnit.done(function( details ) { 551 | var i, key, 552 | banner = id( "qunit-banner" ), 553 | tests = id( "qunit-tests" ), 554 | html = [ 555 | "Tests completed in ", 556 | details.runtime, 557 | " milliseconds.
        ", 558 | "", 559 | details.passed, 560 | " assertions of ", 561 | details.total, 562 | " passed, ", 563 | details.failed, 564 | " failed." 565 | ].join( "" ); 566 | 567 | if ( banner ) { 568 | banner.className = details.failed ? "qunit-fail" : "qunit-pass"; 569 | } 570 | 571 | if ( tests ) { 572 | id( "qunit-testresult" ).innerHTML = html; 573 | } 574 | 575 | if ( config.altertitle && defined.document && document.title ) { 576 | 577 | // show ✖ for good, ✔ for bad suite result in title 578 | // use escape sequences in case file gets loaded with non-utf-8-charset 579 | document.title = [ 580 | ( details.failed ? "\u2716" : "\u2714" ), 581 | document.title.replace( /^[\u2714\u2716] /i, "" ) 582 | ].join( " " ); 583 | } 584 | 585 | // clear own sessionStorage items if all tests passed 586 | if ( config.reorder && defined.sessionStorage && details.failed === 0 ) { 587 | for ( i = 0; i < sessionStorage.length; i++ ) { 588 | key = sessionStorage.key( i++ ); 589 | if ( key.indexOf( "qunit-test-" ) === 0 ) { 590 | sessionStorage.removeItem( key ); 591 | } 592 | } 593 | } 594 | 595 | // scroll back to top to show results 596 | if ( config.scrolltop && window.scrollTo ) { 597 | window.scrollTo( 0, 0 ); 598 | } 599 | }); 600 | 601 | function getNameHtml( name, module ) { 602 | var nameHtml = ""; 603 | 604 | if ( module ) { 605 | nameHtml = "" + escapeText( module ) + ": "; 606 | } 607 | 608 | nameHtml += "" + escapeText( name ) + ""; 609 | 610 | return nameHtml; 611 | } 612 | 613 | QUnit.testStart(function( details ) { 614 | var running, testBlock, bad; 615 | 616 | testBlock = id( "qunit-test-output-" + details.testId ); 617 | if ( testBlock ) { 618 | testBlock.className = "running"; 619 | } else { 620 | 621 | // Report later registered tests 622 | appendTest( details.name, details.testId, details.module ); 623 | } 624 | 625 | running = id( "qunit-testresult" ); 626 | if ( running ) { 627 | bad = QUnit.config.reorder && defined.sessionStorage && 628 | +sessionStorage.getItem( "qunit-test-" + details.module + "-" + details.name ); 629 | 630 | running.innerHTML = ( bad ? 631 | "Rerunning previously failed test:
        " : 632 | "Running:
        " ) + 633 | getNameHtml( details.name, details.module ); 634 | } 635 | 636 | }); 637 | 638 | function stripHtml( string ) { 639 | // strip tags, html entity and whitespaces 640 | return string.replace(/<\/?[^>]+(>|$)/g, "").replace(/\"/g, "").replace(/\s+/g, ""); 641 | } 642 | 643 | QUnit.log(function( details ) { 644 | var assertList, assertLi, 645 | message, expected, actual, diff, 646 | showDiff = false, 647 | testItem = id( "qunit-test-output-" + details.testId ); 648 | 649 | if ( !testItem ) { 650 | return; 651 | } 652 | 653 | message = escapeText( details.message ) || ( details.result ? "okay" : "failed" ); 654 | message = "" + message + ""; 655 | message += "@ " + details.runtime + " ms"; 656 | 657 | // pushFailure doesn't provide details.expected 658 | // when it calls, it's implicit to also not show expected and diff stuff 659 | // Also, we need to check details.expected existence, as it can exist and be undefined 660 | if ( !details.result && hasOwn.call( details, "expected" ) ) { 661 | if ( details.negative ) { 662 | expected = escapeText( "NOT " + QUnit.dump.parse( details.expected ) ); 663 | } else { 664 | expected = escapeText( QUnit.dump.parse( details.expected ) ); 665 | } 666 | 667 | actual = escapeText( QUnit.dump.parse( details.actual ) ); 668 | message += ""; 671 | 672 | if ( actual !== expected ) { 673 | 674 | message += ""; 676 | 677 | // Don't show diff if actual or expected are booleans 678 | if ( !( /^(true|false)$/.test( actual ) ) && 679 | !( /^(true|false)$/.test( expected ) ) ) { 680 | diff = QUnit.diff( expected, actual ); 681 | showDiff = stripHtml( diff ).length !== 682 | stripHtml( expected ).length + 683 | stripHtml( actual ).length; 684 | } 685 | 686 | // Don't show diff if expected and actual are totally different 687 | if ( showDiff ) { 688 | message += ""; 690 | } 691 | } else if ( expected.indexOf( "[object Array]" ) !== -1 || 692 | expected.indexOf( "[object Object]" ) !== -1 ) { 693 | message += ""; 698 | } 699 | 700 | if ( details.source ) { 701 | message += ""; 703 | } 704 | 705 | message += "
        Expected:
        " +
        669 | 			expected +
        670 | 			"
        Result:
        " +
        675 | 				actual + "
        Diff:
        " +
        689 | 					diff + "
        Message: " + 694 | "Diff suppressed as the depth of object is more than current max depth (" + 695 | QUnit.config.maxDepth + ").

        Hint: Use QUnit.dump.maxDepth to " + 696 | " run with a higher max depth or " + 697 | "Rerun without max depth.

        Source:
        " +
        702 | 				escapeText( details.source ) + "
        "; 706 | 707 | // this occours when pushFailure is set and we have an extracted stack trace 708 | } else if ( !details.result && details.source ) { 709 | message += "" + 710 | "" + 712 | "
        Source:
        " +
        711 | 			escapeText( details.source ) + "
        "; 713 | } 714 | 715 | assertList = testItem.getElementsByTagName( "ol" )[ 0 ]; 716 | 717 | assertLi = document.createElement( "li" ); 718 | assertLi.className = details.result ? "pass" : "fail"; 719 | assertLi.innerHTML = message; 720 | assertList.appendChild( assertLi ); 721 | }); 722 | 723 | QUnit.testDone(function( details ) { 724 | var testTitle, time, testItem, assertList, 725 | good, bad, testCounts, skipped, sourceName, 726 | tests = id( "qunit-tests" ); 727 | 728 | if ( !tests ) { 729 | return; 730 | } 731 | 732 | testItem = id( "qunit-test-output-" + details.testId ); 733 | 734 | assertList = testItem.getElementsByTagName( "ol" )[ 0 ]; 735 | 736 | good = details.passed; 737 | bad = details.failed; 738 | 739 | // store result when possible 740 | if ( config.reorder && defined.sessionStorage ) { 741 | if ( bad ) { 742 | sessionStorage.setItem( "qunit-test-" + details.module + "-" + details.name, bad ); 743 | } else { 744 | sessionStorage.removeItem( "qunit-test-" + details.module + "-" + details.name ); 745 | } 746 | } 747 | 748 | if ( bad === 0 ) { 749 | addClass( assertList, "qunit-collapsed" ); 750 | } 751 | 752 | // testItem.firstChild is the test name 753 | testTitle = testItem.firstChild; 754 | 755 | testCounts = bad ? 756 | "" + bad + ", " + "" + good + ", " : 757 | ""; 758 | 759 | testTitle.innerHTML += " (" + testCounts + 760 | details.assertions.length + ")"; 761 | 762 | if ( details.skipped ) { 763 | testItem.className = "skipped"; 764 | skipped = document.createElement( "em" ); 765 | skipped.className = "qunit-skipped-label"; 766 | skipped.innerHTML = "skipped"; 767 | testItem.insertBefore( skipped, testTitle ); 768 | } else { 769 | addEvent( testTitle, "click", function() { 770 | toggleClass( assertList, "qunit-collapsed" ); 771 | }); 772 | 773 | testItem.className = bad ? "fail" : "pass"; 774 | 775 | time = document.createElement( "span" ); 776 | time.className = "runtime"; 777 | time.innerHTML = details.runtime + " ms"; 778 | testItem.insertBefore( time, assertList ); 779 | } 780 | 781 | // Show the source of the test when showing assertions 782 | if ( details.source ) { 783 | sourceName = document.createElement( "p" ); 784 | sourceName.innerHTML = "Source: " + details.source; 785 | addClass( sourceName, "qunit-source" ); 786 | if ( bad === 0 ) { 787 | addClass( sourceName, "qunit-collapsed" ); 788 | } 789 | addEvent( testTitle, "click", function() { 790 | toggleClass( sourceName, "qunit-collapsed" ); 791 | }); 792 | testItem.appendChild( sourceName ); 793 | } 794 | }); 795 | 796 | if ( defined.document ) { 797 | 798 | // Avoid readyState issue with phantomjs 799 | // Ref: #818 800 | var notPhantom = ( function( p ) { 801 | return !( p && p.version && p.version.major > 0 ); 802 | } )( window.phantom ); 803 | 804 | if ( notPhantom && document.readyState === "complete" ) { 805 | QUnit.load(); 806 | } else { 807 | addEvent( window, "load", QUnit.load ); 808 | } 809 | } else { 810 | config.pageLoaded = true; 811 | config.autorun = true; 812 | } 813 | 814 | })(); 815 | --------------------------------------------------------------------------------