├── .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 |
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 | [](https://travis-ci.org/jquery/qunit) [](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 | "abxk cd ",
15 | "QUnit.diff( 'abcd', 'xkcd' )"
16 | );
17 | assert.equal(
18 | QUnit.diff( b, a ),
19 | "xkab cd ",
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 | "falstru e ",
37 | "QUnit.diff( 'false', 'true' )"
38 | );
39 |
40 | assert.equal(
41 | QUnit.diff( "true", "false" ),
42 | "trufals e ",
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 | // ABA C -> AB AC
72 | var a = "AC";
73 | var b = "ABAC";
74 |
75 | assert.equal(
76 | QUnit.diff( a, b ), "AB AC "
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 BA XY DC "
113 | );
114 |
115 | assert.equal(
116 | QUnit.diff( "XD", "AXC" ),
117 | "XDAXC ",
118 | "return is not A X DC "
119 | );
120 |
121 | assert.equal(
122 | QUnit.diff( "A BC ", " B" ),
123 | "A B C ",
124 | "Swap insertions for deletions if diff is reversed"
125 | );
126 |
127 | assert.equal(
128 | QUnit.diff( "abcxxx", "xxxdef" ),
129 | "abcxxx def "
130 | );
131 |
132 | assert.equal(
133 | QUnit.diff( "xxxabc", "defxxx" ),
134 | "def xxx abc"
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 | "" +
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 += "" + val.label + " ";
208 | } else {
209 | urlConfigHtml += "" + val.label +
211 | ": ";
213 |
214 | if ( QUnit.is( "array", val.value ) ) {
215 | for ( j = 0; j < val.value.length; j++ ) {
216 | escaped = escapeText( val.value[ j ] );
217 | urlConfigHtml += "" + escaped + " ";
221 | }
222 | } else {
223 | for ( j in val.value ) {
224 | if ( hasOwn.call( val.value, j ) ) {
225 | urlConfigHtml += "" + escapeText( val.value[ j ] ) + " ";
229 | }
230 | }
231 | }
232 | if ( config[ val.id ] && !selection ) {
233 | escaped = escapeText( config[ val.id ] );
234 | urlConfigHtml += "" + escaped + " ";
236 | }
237 | 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 += "Module: " +
378 | "< All Modules > ";
381 |
382 | for ( i = 0; i < modulesList.length; i++ ) {
383 | moduleFilterHtml += "" + escapeText( modulesList[ i ] ) + " ";
387 | }
388 | moduleFilterHtml += " ";
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 | "" +
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 += "Expected: " +
669 | expected +
670 | " ";
671 |
672 | if ( actual !== expected ) {
673 |
674 | message += "Result: " +
675 | actual + " ";
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 += "Diff: " +
689 | diff + " ";
690 | }
691 | } else if ( expected.indexOf( "[object Array]" ) !== -1 ||
692 | expected.indexOf( "[object Object]" ) !== -1 ) {
693 | message += "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.
";
698 | }
699 |
700 | if ( details.source ) {
701 | message += "Source: " +
702 | escapeText( details.source ) + " ";
703 | }
704 |
705 | message += "
";
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 | "Source: " +
711 | escapeText( details.source ) + " " +
712 | "
";
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 |
--------------------------------------------------------------------------------