diff -Nru libjs-qunit-1.14.0/AUTHORS.txt libjs-qunit-1.22.0/AUTHORS.txt --- libjs-qunit-1.14.0/AUTHORS.txt 2014-01-31 16:41:24.000000000 +0000 +++ libjs-qunit-1.22.0/AUTHORS.txt 2016-02-23 15:57:56.000000000 +0000 @@ -1,3 +1,5 @@ +Authors ordered by first contribution + Jörn Zaefferer Ariel Flesler Scott González @@ -59,7 +61,7 @@ Vivin Paliath Joshua Niehus Glen Huang -Jonas Ulrich +Jochen Ulrich Jamie Hoover James M. Greene Rodney Rehm @@ -79,5 +81,43 @@ Jeff Cooper Corey Frang Nathan Dauber -Michał Gołębiowski +Michał Gołębiowski XhmikosR +Patrick Stapleton +DarkPark +Oleg Gaidarenko +Mike Sidorov +Don Kirkby +don +Sean Xu +Matthew Beale +Mislav Marohnić +Anne-Gaelle Colom +Leonardo Braga +Jon Bretman +Jonny Buchanan +Adrian Phinney +Lam Chau +Henning Beyer +Jesús Leganés Combarro +Shivam Dixit +Gaurav Mittal +Kevin Partington +Braulio Valdivielso Martínez +Shinnosuke Watanabe +Aurelio De Rosa +Toh Chee Chuan +Stefan Penner +Taehee Kim +YongWoo Jeon +Stephen Jones +Ralph Dugue +bianca c [arghgr] +Erik Benoist +Sergio Cinos +Josh Soref +Jon Dufresne +Maksim Gudow +Trent Willis +Frank Weigel +Jürg Lehni diff -Nru libjs-qunit-1.14.0/bower.json libjs-qunit-1.22.0/bower.json --- libjs-qunit-1.14.0/bower.json 2014-01-31 16:41:24.000000000 +0000 +++ libjs-qunit-1.22.0/bower.json 2016-02-23 15:57:56.000000000 +0000 @@ -1,16 +1,17 @@ { "name": "qunit", - "version": "1.14.0", "main": [ "qunit/qunit.js", "qunit/qunit.css" ], + "license": "https://github.com/jquery/qunit/blob/master/LICENSE.txt", "ignore": [ "**/.*", + "!LICENSE.txt", "package.json", - "bower.json", "Gruntfile.js", "node_modules", "test" - ] + ], + "version": "1.22.0" } diff -Nru libjs-qunit-1.14.0/build/browserstack-current-1.json libjs-qunit-1.22.0/build/browserstack-current-1.json --- libjs-qunit-1.14.0/build/browserstack-current-1.json 1970-01-01 00:00:00.000000000 +0000 +++ libjs-qunit-1.22.0/build/browserstack-current-1.json 2016-02-23 15:57:56.000000000 +0000 @@ -0,0 +1,22 @@ +{ + "username": "BROWSERSTACK_USERNAME", + "key": "BROWSERSTACK_KEY", + "test_framework": "qunit", + "test_path": [ + "test/index.html", + "test/logs.html" + ], + "browsers": [ + "chrome_current", + "firefox_current", + "opera_current", + "safari_current", + "ie_11", + "edge_latest", + { + "os": "iOS", + "os_version": "8.3", + "real_mobile": false + } + ] +} diff -Nru libjs-qunit-1.14.0/build/browserstack-current-2.json libjs-qunit-1.22.0/build/browserstack-current-2.json --- libjs-qunit-1.14.0/build/browserstack-current-2.json 1970-01-01 00:00:00.000000000 +0000 +++ libjs-qunit-1.22.0/build/browserstack-current-2.json 2016-02-23 15:57:56.000000000 +0000 @@ -0,0 +1,23 @@ +{ + "username": "BROWSERSTACK_USERNAME", + "key": "BROWSERSTACK_KEY", + "test_framework": "qunit", + "test_path": [ + "test/startError.html", + "test/amd.html", + "test/autostart.html" + ], + "browsers": [ + "chrome_current", + "firefox_current", + "opera_current", + "safari_current", + "ie_11", + "edge_latest", + { + "os": "iOS", + "os_version": "8.3", + "real_mobile": false + } + ] +} diff -Nru libjs-qunit-1.14.0/build/browserstack-legacy-1.json libjs-qunit-1.22.0/build/browserstack-legacy-1.json --- libjs-qunit-1.14.0/build/browserstack-legacy-1.json 1970-01-01 00:00:00.000000000 +0000 +++ libjs-qunit-1.22.0/build/browserstack-legacy-1.json 2016-02-23 15:57:56.000000000 +0000 @@ -0,0 +1,30 @@ +{ + "username": "BROWSERSTACK_USERNAME", + "key": "BROWSERSTACK_KEY", + "test_framework": "qunit", + "test_path": [ + "test/index.html", + "test/logs.html" + ], + "browsers": [ + "chrome_previous", + "firefox_previous", + "opera_previous", + { + "browser": "safari", + "browser_version": "8.0" + }, + "ie_9", + "ie_10", + { + "os": "iOS", + "os_version": "6.0", + "real_mobile": false + }, + { + "os": "iOS", + "os_version": "7.0", + "real_mobile": false + } + ] +} diff -Nru libjs-qunit-1.14.0/build/browserstack-legacy-2.json libjs-qunit-1.22.0/build/browserstack-legacy-2.json --- libjs-qunit-1.14.0/build/browserstack-legacy-2.json 1970-01-01 00:00:00.000000000 +0000 +++ libjs-qunit-1.22.0/build/browserstack-legacy-2.json 2016-02-23 15:57:56.000000000 +0000 @@ -0,0 +1,31 @@ +{ + "username": "BROWSERSTACK_USERNAME", + "key": "BROWSERSTACK_KEY", + "test_framework": "qunit", + "test_path": [ + "test/startError.html", + "test/amd.html", + "test/autostart.html" + ], + "browsers": [ + "chrome_previous", + "firefox_previous", + "opera_previous", + { + "browser": "safari", + "browser_version": "8.0" + }, + "ie_9", + "ie_10", + { + "os": "iOS", + "os_version": "6.0", + "real_mobile": false + }, + { + "os": "iOS", + "os_version": "7.0", + "real_mobile": false + } + ] +} diff -Nru libjs-qunit-1.14.0/build/release.js libjs-qunit-1.22.0/build/release.js --- libjs-qunit-1.14.0/build/release.js 2014-01-31 16:41:24.000000000 +0000 +++ libjs-qunit-1.22.0/build/release.js 2016-02-23 15:57:56.000000000 +0000 @@ -1,8 +1,7 @@ /*jshint node:true */ module.exports = function( Release ) { -var shell = require( "shelljs" ), - gruntCmd = process.platform === "win32" ? "grunt.cmd" : "grunt"; +var shell = require( "shelljs" ); Release.define({ npmPublish: true, @@ -12,16 +11,18 @@ }, generateArtifacts: function( done ) { - if ( Release.exec( gruntCmd ).code !== 0 ) { - Release.abort("Grunt command failed"); - } + Release.exec( "grunt", "Grunt command failed" ); shell.mkdir( "-p", "qunit" ); shell.cp( "-r", "dist/*", "qunit/" ); shell.mkdir( "-p", "dist/cdn" ); shell.cp( "dist/qunit.js", "dist/cdn/qunit-" + Release.newVersion + ".js" ); shell.cp( "dist/qunit.css", "dist/cdn/qunit-" + Release.newVersion + ".css" ); done([ "qunit/qunit.js", "qunit/qunit.css" ]); - }, + } }); -}; \ No newline at end of file +}; + +module.exports.dependencies = [ + "shelljs@0.2.6" +]; diff -Nru libjs-qunit-1.14.0/build/run-browserstack.sh libjs-qunit-1.22.0/build/run-browserstack.sh --- libjs-qunit-1.14.0/build/run-browserstack.sh 1970-01-01 00:00:00.000000000 +0000 +++ libjs-qunit-1.22.0/build/run-browserstack.sh 2016-02-23 15:57:56.000000000 +0000 @@ -0,0 +1,15 @@ +#!/bin/bash + +for run in \ + "./build/browserstack-current-1.json" \ + "./build/browserstack-current-2.json" \ + "./build/browserstack-legacy-1.json" \ + "./build/browserstack-legacy-2.json" +do + export BROWSERSTACK_JSON=$run + if ! node_modules/.bin/browserstack-runner ; then + exit 1 + fi +done + +exit 0 diff -Nru libjs-qunit-1.14.0/build/tasks/test-on-node.js libjs-qunit-1.22.0/build/tasks/test-on-node.js --- libjs-qunit-1.14.0/build/tasks/test-on-node.js 1970-01-01 00:00:00.000000000 +0000 +++ libjs-qunit-1.22.0/build/tasks/test-on-node.js 2016-02-23 15:57:56.000000000 +0000 @@ -0,0 +1,95 @@ +/*jshint node:true */ +"use strict"; + +var async = require( "async" ); +var path = require( "path" ); + +module.exports = function( grunt ) { + grunt.registerMultiTask( "test-on-node", function() { + var runs = this.data.map( function( file ) { + return function( runEnd ) { + runQUnit( file, runEnd ); + }; + }); + + var done = this.async(); + + async.series( runs, function( error, result ) { + var total = result.reduce(function( previous, details ) { + return previous + details.total; + }, 0 ); + var failed = result.reduce(function( previous, details ) { + return previous + details.failed; + }, 0 ); + var runtime = result.reduce(function( previous, details ) { + return previous + details.runtime; + }, 0 ); + + grunt.log.writeln( "-----" ); + grunt.log.ok( total + " total assertions (in " + runtime + "ms) , " + + "with " + failed + " failed assertions" ); + + done( !error ); + }); + }); + + function runQUnit( file, runEnd ) { + + // Resolve current QUnit path and remove it from the require cache + // to avoid stacking the QUnit logs. + var QUnitFile = path.resolve( __dirname, "../../dist/qunit.js" ); + delete require.cache[ QUnitFile ]; + + var QUnit = require( QUnitFile ); + + // Expose QUnit to the global scope to be seen on the other tests. + global.QUnit = QUnit; + + registerEvents( QUnit, file, runEnd ); + + QUnit.config.autorun = false; + + require( "../../" + file ); + + QUnit.load(); + } + + function registerEvents( QUnit, file, runEnd ) { + var runDone = false; + var testActive = false; + + QUnit.begin( function() { + grunt.log.ok( "Testing " + file + " ..." ); + }); + QUnit.testStart( function() { + testActive = true; + }); + QUnit.log( function( details ) { + if ( !testActive || details.result ) { + return; + } + var message = "name: " + details.name + " module: " + details.module + + " message: " + details.message; + grunt.log.error( message ); + }); + QUnit.testDone( function() { + testActive = false; + }); + QUnit.done( function( details ) { + if ( runDone ) { + return; + } + var message = details.total + " assertions (in " + details.runtime + "ms), passed: " + + details.passed + ", failed: " + details.failed; + + if ( details.failed ) { + grunt.log.error( message ); + } else { + grunt.log.ok( message ); + } + + runDone = true; + runEnd( details.failed, details ); + }); + } +}; diff -Nru libjs-qunit-1.14.0/CONTRIBUTING.md libjs-qunit-1.22.0/CONTRIBUTING.md --- libjs-qunit-1.14.0/CONTRIBUTING.md 2014-01-31 16:41:24.000000000 +0000 +++ libjs-qunit-1.22.0/CONTRIBUTING.md 2016-02-23 15:57:56.000000000 +0000 @@ -1,5 +1,5 @@ -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). +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](https://contribute.jquery.org). You'll definitely want to take a look at the articles on contributing [code](https://contribute.jquery.org/code). -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). +You may also want to take a look at our [commit & pull request guide](https://contribute.jquery.org/commits-and-pull-requests/) and [style guides](https://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](https://contribute.jquery.org/cla). -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/). +You can find us on [IRC](https://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](https://contribute.jquery.org/open-source/). diff -Nru libjs-qunit-1.14.0/debian/changelog libjs-qunit-1.22.0/debian/changelog --- libjs-qunit-1.14.0/debian/changelog 2014-02-14 13:02:31.000000000 +0000 +++ libjs-qunit-1.22.0/debian/changelog 2016-03-04 19:10:37.000000000 +0000 @@ -1,3 +1,12 @@ +libjs-qunit (1.22.0-1) unstable; urgency=low + + * New upstream release. + * Install CSS file as well (closes: #748886). + * Generalize watch file. + * Update Standards-Version to 3.9.7 and debhelper level to 9 . + + -- Laszlo Boszormenyi (GCS) Fri, 04 Mar 2016 12:42:42 +0000 + libjs-qunit (1.14.0-1) unstable; urgency=low * New upstream release with addons removed for now. diff -Nru libjs-qunit-1.14.0/debian/compat libjs-qunit-1.22.0/debian/compat --- libjs-qunit-1.14.0/debian/compat 2013-02-28 13:37:18.000000000 +0000 +++ libjs-qunit-1.22.0/debian/compat 2016-03-04 12:56:15.000000000 +0000 @@ -1 +1 @@ -8 +9 diff -Nru libjs-qunit-1.14.0/debian/control libjs-qunit-1.22.0/debian/control --- libjs-qunit-1.14.0/debian/control 2014-01-08 09:25:39.000000000 +0000 +++ libjs-qunit-1.22.0/debian/control 2016-03-04 13:03:07.000000000 +0000 @@ -5,9 +5,9 @@ Uploaders: Jonas Smedegaard , Laszlo Boszormenyi (GCS) Build-Depends: cdbs, devscripts, - debhelper (>= 8), + debhelper (>= 9), dh-buildinfo -Standards-Version: 3.9.5 +Standards-Version: 3.9.7 Homepage: http://qunitjs.com/ Vcs-Git: git://anonscm.debian.org/git/collab-maint/libjs-qunit Vcs-Browser: http://anonscm.debian.org/gitweb/?p=collab-maint/libjs-qunit.git diff -Nru libjs-qunit-1.14.0/debian/control.in libjs-qunit-1.22.0/debian/control.in --- libjs-qunit-1.14.0/debian/control.in 2014-01-08 09:25:52.000000000 +0000 +++ libjs-qunit-1.22.0/debian/control.in 2016-03-04 12:55:18.000000000 +0000 @@ -4,7 +4,7 @@ Maintainer: Debian Javascript Maintainers Uploaders: Jonas Smedegaard , Laszlo Boszormenyi (GCS) Build-Depends: @cdbs@ -Standards-Version: 3.9.5 +Standards-Version: 3.9.7 Homepage: http://qunitjs.com/ Vcs-Git: git://anonscm.debian.org/git/collab-maint/libjs-qunit Vcs-Browser: http://anonscm.debian.org/gitweb/?p=collab-maint/libjs-qunit.git diff -Nru libjs-qunit-1.14.0/debian/libjs-qunit.install libjs-qunit-1.22.0/debian/libjs-qunit.install --- libjs-qunit-1.14.0/debian/libjs-qunit.install 2014-02-14 12:37:41.000000000 +0000 +++ libjs-qunit-1.22.0/debian/libjs-qunit.install 2016-03-04 19:02:27.000000000 +0000 @@ -1,2 +1,2 @@ -qunit/*.js /usr/share/javascript/qunit/ -#addons/* /usr/share/javascript/qunit/addons/ +qunit/qunit.js /usr/share/javascript/qunit/ +qunit/qunit.css /usr/share/javascript/qunit/ diff -Nru libjs-qunit-1.14.0/debian/watch libjs-qunit-1.22.0/debian/watch --- libjs-qunit-1.14.0/debian/watch 2014-01-25 14:21:35.000000000 +0000 +++ libjs-qunit-1.22.0/debian/watch 2016-03-04 19:09:46.000000000 +0000 @@ -1,2 +1,2 @@ version=3 -https://github.com/jquery/qunit/tags .*/archive/v?(\d[\d\.]+).tar.gz +https://github.com/jquery/qunit/tags .*/archive/v?(\d[\d\.]+)\.(?:tar\.xz|txz|tar\.bz2|tbz2|tar\.gz|tgz) diff -Nru libjs-qunit-1.14.0/.editorconfig libjs-qunit-1.22.0/.editorconfig --- libjs-qunit-1.14.0/.editorconfig 1970-01-01 00:00:00.000000000 +0000 +++ libjs-qunit-1.22.0/.editorconfig 2016-02-23 15:57:56.000000000 +0000 @@ -0,0 +1,11 @@ +# This file is for unifying the coding style for different editors and IDEs +# editorconfig.org + +root = true + +[*] +indent_style = tab +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true diff -Nru libjs-qunit-1.14.0/.gitattributes libjs-qunit-1.22.0/.gitattributes --- libjs-qunit-1.14.0/.gitattributes 1970-01-01 00:00:00.000000000 +0000 +++ libjs-qunit-1.22.0/.gitattributes 2016-02-23 15:57:56.000000000 +0000 @@ -0,0 +1,5 @@ +# Auto detect text files and perform LF normalization +* text=auto + +# JS files must always use LF for tools to work +*.js eol=lf diff -Nru libjs-qunit-1.14.0/.gitignore libjs-qunit-1.22.0/.gitignore --- libjs-qunit-1.14.0/.gitignore 2014-01-31 16:41:24.000000000 +0000 +++ libjs-qunit-1.22.0/.gitignore 2016-02-23 15:57:56.000000000 +0000 @@ -1,2 +1,5 @@ dist node_modules +build/report +browserstack-run.pid +temp/ diff -Nru libjs-qunit-1.14.0/Gruntfile.js libjs-qunit-1.22.0/Gruntfile.js --- libjs-qunit-1.14.0/Gruntfile.js 2014-01-31 16:41:24.000000000 +0000 +++ libjs-qunit-1.22.0/Gruntfile.js 2016-02-23 15:57:56.000000000 +0000 @@ -1,14 +1,15 @@ /*jshint node:true */ module.exports = function( grunt ) { -grunt.loadNpmTasks( "grunt-git-authors" ); -grunt.loadNpmTasks( "grunt-contrib-concat" ); -grunt.loadNpmTasks( "grunt-contrib-jshint" ); -grunt.loadNpmTasks( "grunt-contrib-qunit" ); -grunt.loadNpmTasks( "grunt-contrib-watch" ); +require( "load-grunt-tasks" )( grunt ); +function process( code, filepath ) { + + // Make coverage ignore external files + if ( filepath.match( /^external\// ) ) { + code = "/*istanbul ignore next */\n" + code; + } -function process( code ) { return code // Embed version @@ -25,143 +26,156 @@ options: { process: process }, src: [ "src/intro.js", + "src/core/initialize.js", + "src/core/utilities.js", + "src/core/stacktrace.js", + "src/core/config.js", + "src/core/logging.js", + "src/core/onerror.js", "src/core.js", "src/test.js", "src/assert.js", "src/equiv.js", "src/dump.js", - "src/diff.js", "src/export.js", - "src/outro.js" + "src/diff.js", + "src/outro.js", + "reporter/html.js" ], dest: "dist/qunit.js" }, "src-css": { options: { process: process }, - src: [ - "src/qunit.css" - ], + src: "src/qunit.css", dest: "dist/qunit.css" } }, jshint: { options: { - jshintrc: ".jshintrc" + jshintrc: true }, - gruntfile: [ "Gruntfile.js" ], - dist: [ "dist/*.js" ], - tests: { - options: { - jshintrc: "test/.jshintrc" - }, - files: { - src: [ "test/**/*.js" ] - } - } + all: [ + "*.js", + "{test,dist}/**/*.js", + "build/*.js", + "build/tasks/**/*.js" + ] + }, + jscs: { + options: { + config: ".jscsrc" + }, + all: [ + "<%= jshint.all %>", + "!test/main/deepEqual.js" + ] + }, + search: { + options: { + + // Ensure that the only HTML entities used are those with a special status in XHTML + // and that any common singleton/empty HTML elements end with the XHTML-compliant + // "/>"rather than ">" + searchString: /(&(?!gt|lt|amp|quot)[A-Za-z0-9]+;|<(?:hr|HR|br|BR|input|INPUT)(?![^>]*\/>)(?:\s+[^>]*)?>)/g, + logFormat: "console", + failOnMatch: true + }, + xhtml: [ + "src/**/*.js", + "reporter/**/*.js" + ] }, qunit: { + options: { + timeout: 30000, + "--web-security": "no", + coverage: { + src: "dist/qunit.js", + instrumentedFiles: "temp/", + htmlReport: "build/report/coverage", + lcovReport: "build/report/lcov", + linesThresholdPct: 70 + } + }, qunit: [ "test/index.html", - "test/async.html", + "test/autostart.html", + "test/startError.html", + "test/reorderError1.html", + "test/reorderError2.html", "test/logs.html", - "test/setTimeout.html" + "test/setTimeout.html", + "test/amd.html", + "test/reporter-html/index.html", + "test/reporter-html/legacy-markup.html", + "test/reporter-html/no-qunit-element.html", + "test/reporter-html/single-testid.html", + "test/only.html", + "test/regex-filter.html", + "test/regex-exclude-filter.html", + "test/string-filter.html" + ] + }, + coveralls: { + options: { + force: true + }, + all: { + + // LCOV coverage file relevant to every target + src: "build/report/lcov/lcov.info" + } + }, + "test-on-node": { + files: [ + "test/logs", + "test/main/test", + "test/main/assert", + "test/main/async", + "test/main/promise", + "test/main/modules", + "test/main/deepEqual", + "test/main/stack", + "test/globals-node", + "test/only", + "test/setTimeout", + "test/main/dump", + "test/reporter-html/diff" + ] + }, + concurrent: { + build: [ + "concat:src-js", + "concat:src-css" + ], + test: [ + "jshint", + "jscs", + "search", + "qunit", + "test-on-node" ] }, watch: { - files: [ "*", ".jshintrc", "{src,test}/**/{*,.*}" ], + options: { + atBegin: true, + spawn: false, + interrupt: true + }, + files: [ + ".jshintrc", + "*.js", + "build/*.js", + "{src,test,reporter}/**/*.js", + "src/qunit.css", + "test/**/*.html" + ], tasks: "default" } }); -grunt.registerTask( "testswarm", function( commit, configFile ) { - var testswarm = require( "testswarm" ), - config = grunt.file.readJSON( configFile ).qunit, - runs = {}, - done = this.async(); - - ["index", "async", "setTimeout"].forEach(function (suite) { - runs[suite] = config.testUrl + commit + "/test/" + suite + ".html"; - }); - - testswarm.createClient( { - url: config.swarmUrl, - pollInterval: 10000, - timeout: 1000 * 60 * 30 - } ) - .addReporter( testswarm.reporters.cli ) - .auth( { - id: config.authUsername, - token: config.authToken - } ) - .addjob( - { - name: "Commit " + - commit.substr( 0, 10 ) + "", - runs: runs, - browserSets: [ "popular", "ios" ] - }, function( err, passed ) { - if ( err ) { - grunt.log.error( err ); - } - done( passed ); - } - ); -}); - -// TODO: Extract this task later, if feasible -// Also spawn a separate process to keep tests atomic -grunt.registerTask( "test-on-node", function() { - var testActive = false, - runDone = false, - done = this.async(), - QUnit = require( "./dist/qunit" ); - - // Make the current tests work in the Node.js environment by appending - // a bunch of properties into the `global` object - [ "test", "asyncTest", "start", "stop", "expect" ].forEach(function( method ) { - global[ method ] = QUnit[ method ]; - }); - global.QUnit = QUnit; - - QUnit.testStart(function() { - testActive = true; - }); - QUnit.log(function( details ) { - if ( !testActive || details.result ) { - return; - } - var message = "name: " + details.name + " module: " + details.module + - " message: " + details.message; - grunt.log.error( message ); - }); - QUnit.testDone(function() { - testActive = false; - }); - QUnit.done(function( details ) { - if ( runDone ) { - return; - } - var succeeded = ( details.failed === 0 ), - message = details.total + " assertions in (" + details.runtime + "ms), passed: " + - details.passed + ", failed: " + details.failed; - if ( succeeded ) { - grunt.log.ok( message ); - } else { - grunt.log.error( message ); - } - done( succeeded ); - runDone = true; - }); - QUnit.config.autorun = false; - - require( "./test/logs" ); - require( "./test/test" ); - require( "./test/deepEqual" ); - - QUnit.load(); -}); - +grunt.loadTasks( "build/tasks" ); grunt.registerTask( "build", [ "concat" ] ); -grunt.registerTask( "default", [ "build", "jshint", "qunit", "test-on-node" ] ); +grunt.registerTask( "default", [ "concurrent:build", "concurrent:test" ] ); }; diff -Nru libjs-qunit-1.14.0/History.md libjs-qunit-1.22.0/History.md --- libjs-qunit-1.14.0/History.md 2014-01-31 16:41:24.000000000 +0000 +++ libjs-qunit-1.22.0/History.md 2016-02-23 15:57:56.000000000 +0000 @@ -1,3 +1,135 @@ + +1.22.0 / 2016-02-23 +================== + + * Assert: Implement Assert#pushResult + * Assert: Extend Assert methods to QUnit for backwards compatibility + * HTML Reporter: Escape setUrl output + +1.21.0 / 2016-02-01 +================== + + * Assert: Improve size and speed of QUnit.equiv + * Assert: Fully support Object-wrapped primitives in deepEqual + * Assert: Register notOk as a non-negative assertion + * CSS: Fix hidden test results under static parents + * Core: Improve regular expression comparisons + * Core: Support filtering by regular expression + * Test: Prevents skiping tests after rerun reordering + * Tests: Differentiate QUnit.equiv assertions + +1.20.0 / 2015-10-27 +================== + + * Assert: Exposes assert.raises() to the global scope + * Assert: Add a calls count parameter on assert.async + * Build: Improve grunt speed using grunt-concurrent + * Core: Implement QUnit.only + * Core: Support Symbol types on QUnit.equiv + * Core: QUnit.start fails if called with a non-numeric argument + * Core: Implement Nested modules + * Core: Equivalency for descendants of null constructors + * HTML Reporter: Adds indicator for filtered test + * HTML Reporter: Collapse details for successive failed tests + * Test: Fix regression when a failing test canceled the module hooks + * Tests: Isolate and improve tests for Object equivalency + * Tests: Split browserstack runs on CI to avoid timeout errors + +1.19.0 / 2015-09-01 +================== + + * Assert: Add support to ES6' Map and Set equiv objects + * Build: Enable IRC notifications for Travis CI + * Build: Add 'Readme' to commitplease components + * Build: Remove unintended QUnit global export on Node + * Build: Remove testSwarm job + * Core: Implement QUnit.stack + * Dump: Escape backslash when quoting strings + * HTML Reporter: Avoid readyState issue with PhantomJS + * HTML Reporter: HTML reporter enhancements for negative asserts + * HTML Reporter: Show diff only when it helps + * Tests: Avoid loosen errors on autostart test + * Tests: HTML Reporter tests are now isolated with reordering disabled + * Tests: Rename stack error tests + * Test: Release module hooks to avoid memory leaks + * Test: Don't pass Promise fulfillment value to QUnit.start + * Test: Source Displayed Even for Passed Test + +1.18.0 / 2015-04-03 +================== + + * Assert: throws uses push method only + * Assert: Fix missing test on exported throws + * Assert: Implements notOk to assert falsy values + * Core: More graceful handling of AMD + * Core: Simplify stack trace methods + * Core: Expose Dump maxDepth property + * Core: Expose QUnit version as QUnit.version property + * Core: Handle multiple testId parameters + * Dump: Fix .name/.property doublettes + * HTML Reporter: New diff using Google's Diff-Patch-Match Library + * HTML Reporter: Make it more obvious why diff is suppressed. + * HTML Reporter: Change display text for bad tests + * HTML Reporter: Fix checkbox and select handling in IE <9 + * HTML Reporter: Fix test filter without any module + * HTML Reporter: Retain failed tests numbers + * Test: lowercase the valid test filter before using it + +1.17.1 / 2015-01-20 +================== + + * HTML Reporter: Fix missing toolbar bug + +1.17.0 / 2015-01-19 +================== + + * Build: Remove bower.json from ignored files + * Build: Support Node.js export parity with CommonJS + * HTML Reporter: Add the filter field + * HTML Reporter: Don't hide skipped tests + * HTML Reporter: Fix regression for old markup + * HTML Reporter: Prevent XSS attacks + * HTML Reporter: QUnit.url is now a private function in the HTML Reporter + * HTML Reporter: url params can be set by code + +1.16.0 / 2014-12-03 +================== + + * Assert: Add alias for throws called 'raises' + * Async: Fail assertions after existing assert.async flows are resolved + * Async: Implement assert.async + * Async: Tests are now Promise-aware + * Callbacks: Restore and warn if some logging callback gets modified + * Callbacks: Throws an error on non-function params for logging methods + * Core: change url() helper to output `?foo` instead of `?foo=true` + * Core: Detail modules and tests names in the logging callbacks + * Core: Implements QUnit.skip + * Core: Remove constructor + * Core: Rename config.module to config.moduleFilter + * Core: Use `Error#stack` without throwing when available + * Dump: Configurable limit for object depth + * HTML Reporter: Enable activating config.hidepassed from URL params + * HTML Reporter: Move QUnit.reset back to core to run it before testDone + * HTML Reporter: Output runtime of each assertion in results + * Logging: Add runtime to moduleDone + * Logging: Defer begin event till tests actually starts + * Test: Introduce order independent testId to replace testNumber + * Test: Rename module's setup/teardown to beforeEach/afterEach + +1.15.0 / 2014-08-08 +================== + +* Assert: Implement Assert constructor with test context. This heavily improves debugging of async tests, since assertions can't leak into other tests anymore. Use the assert argument in your test callbacks to run assertions to get the full benefit of this. +* Assert: Improved the default message from assert.ok. Now assert.ok() outputs the exact value it received, instead of only saying it wasn't thruthy. +* Assert: Removal of raises, same and equals. These were deprecated a long time ago and finally removed. Use throws, deepEqual and equal instead. +* Core: Pass total amount of tests to QUnit.begin callback as totalTests. Will be used by Karma and other reporters. +* Dump: Move QUnit.jsDump to QUnit.dump. QUnit.jsDump still exists, but will be removed later. Use QUnit.dump. +* Dump: Output non-enumerable properties of TypeError. Makes it easier to compare properties of error objects. +* Reporter: Output only assertion count for green tests. Less visual clutter for passing tests. +* Reporter: Move HTML reporter to a new JS file. The HTML reporter is still bundled, but the code has been refactored to move it to a separate file. +* Test: Remove deprecated QUnit.current_testEnvironment +* Throws: support for oldIE native Error types. Error objects in IE are buggy, this works around those issues. + 1.14.0 / 2014-01-31 ================== @@ -151,7 +283,7 @@ * Add testswarm integration as grunt task * Added padding on URL config checkboxes. * Cleanup composite addon: Use callback registration instead of overwriting them. Set the correct src on rerun link (and dblclick). Remove the composite test itself, as that was a crazy hack not worth maintaining - * Cleanup reset() test and usage - run testDone callback first, to allow listeneres ignoring reset assertions + * Cleanup reset() test and usage - run testDone callback first, to allow listeners ignoring reset assertions * Double clicking on composite test rows opens individual test page * test-message for all message-bearing API reporting details @@ -205,11 +337,11 @@ * Fix CommonJS export by assigning QUnit to module.exports. * Remove the testEnvironmentArg to test(). Most obscure, never used anywhere. test() is still heavily overloaded with argument shifting, this makes it a little more sane. Fixes #172 * Serialize expected and actual values only when test fails. Speeds up output of valid tests, especially for lots of large objects. Fixes #183 - * Fix sourceFromsTacktrace to get the right line in Firefox. Shift the 'error' line away in Chrome to get a match. + * Fix sourceFromsStacktrace to get the right line in Firefox. Shift the 'error' line away in Chrome to get a match. * Fix references to test/deepEqual.js * In autorun mode, moduleDone is called without matching moduleStart. Fix issue #184 * Fixture test: allow anything falsy in test as getAttribute in oldIE will return empty string instead of null. We don't really care. - * Keep label and checkbox together ( http://i.imgur.com/5Wk3A.png ) + * Keep label and checkbox together ( https://i.imgur.com/5Wk3A.png ) * Add readme for themes * Fix bad global in reset() * Some cleanup in theme addons @@ -236,7 +368,7 @@ * Using node-qunit port, the start/stop function are not exposed so we need to prefix any call to them with 'QUnit'. Aka: start() -> QUnit.start() * Remove the 'let teardown clean up globals test' - IE<9 doesn't support (==buggy) deleting window properties, and that's not worth the trouble, as everything else passes just fine. Fixes #155 * Fix globals in test.js, part 2 - * Fix globals in test.js. ?tell wwalser to use ?noglobals everyonce in a while + * Fix globals in test.js. ?tell wwalser to use ?noglobals every once in a while * Extend readme regarding release process 1.1.0 / 2011-10-11 @@ -400,7 +532,7 @@ * Optimized and cleaned up CSS file * Making the reset-method non-global (only module, test and assertions should be global), and fixing the fixture reset by using jQuery's html() method again, doesn't work with innerHTML, yet * Introducing #qunit-fixture element, deprecating the (never documented) #main element. Doesn't require inline styles and is now independent of jQuery. - * Ammending previous commit: Remove jQuery-core specific resets (will be replaced within jQuery testsuite). Fixes issue #19 - QUnit.reset() removes global jQuery ajax event handlers + * Amending previous commit: Remove jQuery-core specific resets (will be replaced within jQuery testsuite). Fixes issue #19 - QUnit.reset() removes global jQuery ajax event handlers * Remove jQuery-core specific resets (will be replaced within jQuery testsuite). Fixes issue #19 - QUnit.reset() removes global jQuery ajax event handlers * Cleaning up rubble from the previous commit. * Added raises assertion, reusing some of kensnyder's code. @@ -430,8 +562,8 @@ * Expose QUnit.config, but don't make config a global * Expose QUnit.config as global to make external workarounds easier * Merge branch 'asyncsetup' - * Allowing async setup and teardown. Fixes http://github.com/jquery/qunit/issues#issue/20 - * Always output expected and actual result (no reason not to). Fixes http://github.com/jquery/qunit/issues#issue/21 + * Allowing async setup and teardown. Fixes https://github.com/jquery/qunit/issues#issue/20 + * Always output expected and actual result (no reason not to). Fixes https://github.com/jquery/qunit/issues#issue/21 * More changes to the detection of types in jsDump's typeOf. * Change the typeOf checks in QUnit to be more accurate. * Added test for jsDump and modified its options to properly output results when document.createTextNode is used; currently tests for DOM elements cause a stackoverflow error in IEs, works fine, with the correct output, elsewhere @@ -455,7 +587,7 @@ * IE 6 and 7 weren't respecting the CSS rules for the banner, used a different technique instead. * Went a bit further and made extra-sure that the target was specified correctly. * Fixed problem where double-clicking an entry in IE caused an error to occur. - * Path for http://dev.jquery.com/ticket/5426 - fix the microformat test result + * Path for https://dev.jquery.com/ticket/5426 - fix the microformat test result * Fixed test() to use 'expected' 2nd param * Remove the named function expressions, to stop Safari 2 from freaking out. Fixes #5. * Each test can extend the module testEnvironment @@ -517,7 +649,7 @@ * qunit: added missing semicolons * qunit: fixed a semicolon, that should have been a comma * QUnit: implemented error handling for Opera as proposed by #3628 - * qunit: fix for http://dev.jquery.com/ticket/3215 changing wording of testresults, to something more positive (x of y passed, z failed) + * qunit: fix for https://dev.jquery.com/ticket/3215 changing wording of testresults, to something more positive (x of y passed, z failed) * QUnit: testrunner.js: Ensures equality of types (String, Boolean, Number) declared with the 'new' prefix. See comments #3, #4 and #5 on http://philrathe.com/articles/equiv * qunit: wrap name of test in span when a module is used for better styling * qunit: auto-prepend default mark (#header, #banner, #userAgent, #tests) when not present diff -Nru libjs-qunit-1.14.0/.jscsrc libjs-qunit-1.22.0/.jscsrc --- libjs-qunit-1.14.0/.jscsrc 1970-01-01 00:00:00.000000000 +0000 +++ libjs-qunit-1.22.0/.jscsrc 2016-02-23 15:57:56.000000000 +0000 @@ -0,0 +1,5 @@ +{ + "preset": "jquery", + + "requireMultipleVarDecl": null +} diff -Nru libjs-qunit-1.14.0/.jshintrc libjs-qunit-1.22.0/.jshintrc --- libjs-qunit-1.14.0/.jshintrc 2014-01-31 16:41:24.000000000 +0000 +++ libjs-qunit-1.22.0/.jshintrc 2016-02-23 15:57:56.000000000 +0000 @@ -1,8 +1,4 @@ { - "globals": { - "module": false - }, - "boss": true, "curly": true, "eqeqeq": true, @@ -10,23 +6,19 @@ "expr": true, "immed": true, "noarg": true, - "onevar": true, "quotmark": "double", "smarttabs": true, "trailing": true, "undef": true, "unused": true, - "bitwise": true, "browser": true, - "camelcase": true, "es3": true, - "forin": true, - "latedef": false, - "newcap": true, - "noempty": true, - "nonew": true, - "plusplus": false, - "proto": true, - "sub": true + + "globals": { + "QUnit": false, + "define": false, + "exports": false, + "module": false + } } diff -Nru libjs-qunit-1.14.0/LICENSE.txt libjs-qunit-1.22.0/LICENSE.txt --- libjs-qunit-1.14.0/LICENSE.txt 1970-01-01 00:00:00.000000000 +0000 +++ libjs-qunit-1.22.0/LICENSE.txt 2016-02-23 15:57:56.000000000 +0000 @@ -0,0 +1,35 @@ +Copyright jQuery Foundation and other contributors, https://jquery.org/ + +This software consists of voluntary contributions made by many +individuals. For exact contribution history, see the revision history +available at https://github.com/jquery/qunit + +The following license applies to all parts of this software except as +documented below: + +==== + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +==== + +All files located in the node_modules directory are externally maintained +libraries used by this software which have their own licenses; we +recommend you read them, as their terms may differ from the terms above. diff -Nru libjs-qunit-1.14.0/.mailmap libjs-qunit-1.22.0/.mailmap --- libjs-qunit-1.14.0/.mailmap 2014-01-31 16:41:24.000000000 +0000 +++ libjs-qunit-1.22.0/.mailmap 2016-02-23 15:57:56.000000000 +0000 @@ -89,3 +89,4 @@ Matthew DuVall Dave K. Smith David Vollbracht +Jochen Ulrich diff -Nru libjs-qunit-1.14.0/MIT-LICENSE.txt libjs-qunit-1.22.0/MIT-LICENSE.txt --- libjs-qunit-1.14.0/MIT-LICENSE.txt 2014-01-31 16:41:24.000000000 +0000 +++ libjs-qunit-1.22.0/MIT-LICENSE.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1,21 +0,0 @@ -Copyright 2013 jQuery Foundation and other contributors -http://jquery.com/ - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff -Nru libjs-qunit-1.14.0/package.json libjs-qunit-1.22.0/package.json --- libjs-qunit-1.14.0/package.json 2014-01-31 16:41:24.000000000 +0000 +++ libjs-qunit-1.22.0/package.json 2016-02-23 15:57:56.000000000 +0000 @@ -2,11 +2,11 @@ "name": "qunitjs", "title": "QUnit", "description": "An easy-to-use JavaScript Unit Testing framework.", - "version": "1.14.0", - "homepage": "http://qunitjs.com", + "version": "1.22.0", + "homepage": "https://qunitjs.com", "author": { "name": "jQuery Foundation and other contributors", - "url": "https://github.com/jquery/qunit/blob/1.14.0/AUTHORS.txt" + "url": "https://github.com/jquery/qunit/blob/1.22.0/AUTHORS.txt" }, "repository": { "type": "git", @@ -20,25 +20,50 @@ "bugs": { "url": "https://github.com/jquery/qunit/issues" }, - "licenses": [ - { - "type": "MIT", - "url": "https://github.com/jquery/qunit/blob/1.14.0/MIT-LICENSE.txt" - } + "license": "MIT", + "files": [ + "qunit/qunit.js", + "qunit/qunit.css", + "LICENSE.txt" ], "dependencies": {}, "devDependencies": { - "grunt": "0.4.2", + "async": "1.4.2", + "browserstack-runner": "0.3.7", + "commitplease": "2.0.0", + "grunt": "0.4.5", + "grunt-cli": "0.1.13", + "grunt-concurrent": "2.0.3", "grunt-contrib-concat": "0.3.0", - "grunt-contrib-jshint": "0.7.2", - "grunt-contrib-qunit": "0.3.0", + "grunt-contrib-jshint": "0.11.2", "grunt-contrib-watch": "0.5.3", - "grunt-git-authors": "1.2.0", - "testswarm": "1.1.0" + "grunt-coveralls": "1.0.0", + "grunt-git-authors": "3.0.0", + "grunt-jscs": "0.8.1", + "grunt-qunit-istanbul": "0.5.0", + "grunt-search": "0.1.6", + "load-grunt-tasks": "0.3.0", + "requirejs": "2.1.16" }, "scripts": { + "browserstack": "sh build/run-browserstack.sh", + "ci": "grunt && grunt coveralls && npm run browserstack", "test": "grunt", "prepublish": "grunt build" }, + "commitplease": { + "components": [ + "All", + "Assert", + "Build", + "CSS", + "Core", + "Dump", + "HTML Reporter", + "Readme", + "Test", + "Tests" + ] + }, "main": "qunit/qunit.js" } diff -Nru libjs-qunit-1.14.0/qunit/qunit.css libjs-qunit-1.22.0/qunit/qunit.css --- libjs-qunit-1.14.0/qunit/qunit.css 2014-01-31 16:41:24.000000000 +0000 +++ libjs-qunit-1.22.0/qunit/qunit.css 2016-02-23 15:57:56.000000000 +0000 @@ -1,27 +1,27 @@ /*! - * QUnit 1.14.0 - * http://qunitjs.com/ + * QUnit 1.22.0 + * https://qunitjs.com/ * - * Copyright 2013 jQuery Foundation and other contributors + * Copyright jQuery Foundation and other contributors * Released under the MIT license - * http://jquery.org/license + * https://jquery.org/license * - * Date: 2014-01-31T16:40Z + * Date: 2016-02-23T15:57Z */ /** Font Family and Sizes */ -#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult { +#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-filteredTest, #qunit-userAgent, #qunit-testresult { font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif; } -#qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } +#qunit-testrunner-toolbar, #qunit-filteredTest, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } #qunit-tests { font-size: smaller; } /** Resets */ -#qunit-tests, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter { +#qunit-tests, #qunit-header, #qunit-banner, #qunit-filteredTest, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter { margin: 0; padding: 0; } @@ -62,14 +62,20 @@ } #qunit-testrunner-toolbar { - padding: 0.5em 0 0.5em 2em; + padding: 0.5em 1em 0.5em 1em; color: #5E740B; background-color: #EEE; overflow: hidden; } +#qunit-filteredTest { + padding: 0.5em 1em 0.5em 1em; + background-color: #F4FF77; + color: #366097; +} + #qunit-userAgent { - padding: 0.5em 0 0.5em 2.5em; + padding: 0.5em 1em 0.5em 1em; background-color: #2B81AF; color: #FFF; text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; @@ -77,6 +83,18 @@ #qunit-modulefilter-container { float: right; + padding: 0.2em; +} + +.qunit-url-config { + display: inline-block; + padding: 0.1em; +} + +.qunit-filter { + display: block; + float: right; + margin-left: 1em; } /** Tests: Pass/Fail */ @@ -86,24 +104,55 @@ } #qunit-tests li { - padding: 0.4em 0.5em 0.4em 2.5em; + padding: 0.4em 1em 0.4em 1em; border-bottom: 1px solid #FFF; list-style-position: inside; } -#qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running { +#qunit-tests > li { display: none; } +#qunit-tests li.running, +#qunit-tests li.pass, +#qunit-tests li.fail, +#qunit-tests li.skipped { + display: list-item; +} + +#qunit-tests.hidepass { + position: relative; +} + +#qunit-tests.hidepass li.running, +#qunit-tests.hidepass li.pass { + visibility: hidden; + position: absolute; + width: 0; + height: 0; + padding: 0; + border: 0; + margin: 0; +} + #qunit-tests li strong { cursor: pointer; } +#qunit-tests li.skipped strong { + cursor: default; +} + #qunit-tests li a { padding: 0.5em; color: #C2CCD1; text-decoration: none; } + +#qunit-tests li p a { + padding: 0.25em; + color: #6B6464; +} #qunit-tests li a:hover, #qunit-tests li a:focus { color: #000; @@ -123,6 +172,10 @@ border-radius: 5px; } +.qunit-source { + margin: 0.6em 0 0.3em; +} + .qunit-collapsed { display: none; } @@ -211,11 +264,26 @@ #qunit-banner.qunit-fail { background-color: #EE5757; } +/*** Skipped tests */ + +#qunit-tests .skipped { + background-color: #EBECE9; +} + +#qunit-tests .qunit-skipped-label { + background-color: #F4FF77; + display: inline-block; + font-style: normal; + color: #366097; + line-height: 1.8em; + padding: 0 0.5em; + margin: -0.4em 0.4em -0.4em 0; +} /** Result */ #qunit-testresult { - padding: 0.5em 0.5em 0.5em 2.5em; + padding: 0.5em 1em 0.5em 1em; color: #2B81AF; background-color: #D2E0E6; diff -Nru libjs-qunit-1.14.0/qunit/qunit.js libjs-qunit-1.22.0/qunit/qunit.js --- libjs-qunit-1.14.0/qunit/qunit.js 2014-01-31 16:41:24.000000000 +0000 +++ libjs-qunit-1.22.0/qunit/qunit.js 2016-02-23 15:57:56.000000000 +0000 @@ -1,237 +1,255 @@ /*! - * QUnit 1.14.0 - * http://qunitjs.com/ + * QUnit 1.22.0 + * https://qunitjs.com/ * - * Copyright 2013 jQuery Foundation and other contributors + * Copyright jQuery Foundation and other contributors * Released under the MIT license - * http://jquery.org/license + * https://jquery.org/license * - * Date: 2014-01-31T16:40Z + * Date: 2016-02-23T15:57Z */ -(function( window ) { +(function( global ) { -var QUnit, - assert, - config, - onErrorFnPrev, - testId = 0, - fileName = (sourceFromStacktrace( 0 ) || "" ).replace(/(:\d+)+\)?/, "").replace(/.+\//, ""), - toString = Object.prototype.toString, - hasOwn = Object.prototype.hasOwnProperty, - // Keep a local reference to Date (GH-283) - Date = window.Date, - setTimeout = window.setTimeout, - clearTimeout = window.clearTimeout, - defined = { - document: typeof window.document !== "undefined", - setTimeout: typeof window.setTimeout !== "undefined", - sessionStorage: (function() { - var x = "qunit-test-string"; - try { - sessionStorage.setItem( x, x ); - sessionStorage.removeItem( x ); - return true; - } catch( e ) { - return false; - } - }()) - }, - /** - * Provides a normalized error string, correcting an issue - * with IE 7 (and prior) where Error.prototype.toString is - * not properly implemented - * - * Based on http://es5.github.com/#x15.11.4.4 - * - * @param {String|Error} error - * @return {String} error message - */ - errorString = function( error ) { - var name, message, - errorString = error.toString(); - if ( errorString.substring( 0, 7 ) === "[object" ) { - name = error.name ? error.name.toString() : "Error"; - message = error.message ? error.message.toString() : ""; - if ( name && message ) { - return name + ": " + message; - } else if ( name ) { - return name; - } else if ( message ) { - return message; - } else { - return "Error"; - } - } else { - return errorString; - } - }, - /** - * Makes a clone of an object using only Array or Object as base, - * and copies over the own enumerable properties. - * - * @param {Object} obj - * @return {Object} New object with only the own properties (recursively). - */ - objectValues = function( obj ) { - // Grunt 0.3.x uses an older version of jshint that still has jshint/jshint#392. - /*jshint newcap: false */ - var key, val, - vals = QUnit.is( "array", obj ) ? [] : {}; - for ( key in obj ) { - if ( hasOwn.call( obj, key ) ) { - val = obj[key]; - vals[key] = val === Object(val) ? objectValues(val) : val; - } - } - return vals; - }; +var QUnit = {}; +var Date = global.Date; +var now = Date.now || function() { + return new Date().getTime(); +}; -// Root QUnit object. -// `QUnit` initialized at top of scope -QUnit = { +var setTimeout = global.setTimeout; +var clearTimeout = global.clearTimeout; - // call on start of module test to prepend name to all tests - module: function( name, testEnvironment ) { - config.currentModule = name; - config.currentModuleTestEnvironment = testEnvironment; - config.modules[name] = true; - }, +// Store a local window from the global to allow direct references. +var window = global.window; - asyncTest: function( testName, expected, callback ) { - if ( arguments.length === 2 ) { - callback = expected; - expected = null; +var defined = { + document: window && window.document !== undefined, + setTimeout: setTimeout !== undefined, + sessionStorage: (function() { + var x = "qunit-test-string"; + try { + sessionStorage.setItem( x, x ); + sessionStorage.removeItem( x ); + return true; + } catch ( e ) { + return false; } + }() ) +}; - QUnit.test( testName, expected, callback, true ); - }, +var fileName = ( sourceFromStacktrace( 0 ) || "" ).replace( /(:\d+)+\)?/, "" ).replace( /.+\//, "" ); +var globalStartCalled = false; +var runStarted = false; - test: function( testName, expected, callback, async ) { - var test, - nameHtml = "" + escapeText( testName ) + ""; +var toString = Object.prototype.toString, + hasOwn = Object.prototype.hasOwnProperty; - if ( arguments.length === 2 ) { - callback = expected; - expected = null; - } +// returns a new Array with the elements that are in a but not in b +function diff( a, b ) { + var i, j, + result = a.slice(); - if ( config.currentModule ) { - nameHtml = "" + escapeText( config.currentModule ) + ": " + nameHtml; + for ( i = 0; i < result.length; i++ ) { + for ( j = 0; j < b.length; j++ ) { + if ( result[ i ] === b[ j ] ) { + result.splice( i, 1 ); + i--; + break; + } } + } + return result; +} - test = new Test({ - nameHtml: nameHtml, - testName: testName, - expected: expected, - async: async, - callback: callback, - module: config.currentModule, - moduleTestEnvironment: config.currentModuleTestEnvironment, - stack: sourceFromStacktrace( 2 ) - }); +// from jquery.js +function inArray( elem, array ) { + if ( array.indexOf ) { + return array.indexOf( elem ); + } - if ( !validTest( test ) ) { - return; + for ( var i = 0, length = array.length; i < length; i++ ) { + if ( array[ i ] === elem ) { + return i; } + } - test.queue(); - }, + return -1; +} - // Specify the number of expected assertions to guarantee that failed test (no assertions are run at all) don't slip through. - expect: function( asserts ) { - if (arguments.length === 1) { - config.current.expected = asserts; - } else { - return config.current.expected; +/** + * Makes a clone of an object using only Array or Object as base, + * and copies over the own enumerable properties. + * + * @param {Object} obj + * @return {Object} New object with only the own properties (recursively). + */ +function objectValues ( obj ) { + var key, val, + vals = QUnit.is( "array", obj ) ? [] : {}; + for ( key in obj ) { + if ( hasOwn.call( obj, key ) ) { + val = obj[ key ]; + vals[ key ] = val === Object( val ) ? objectValues( val ) : val; } - }, + } + return vals; +} - start: function( count ) { - // QUnit hasn't been initialized yet. - // Note: RequireJS (et al) may delay onLoad - if ( config.semaphore === undefined ) { - QUnit.begin(function() { - // This is triggered at the top of QUnit.load, push start() to the event loop, to allow QUnit.load to finish first - setTimeout(function() { - QUnit.start( count ); - }); - }); - return; +function extend( a, b, undefOnly ) { + for ( var prop in b ) { + if ( hasOwn.call( b, prop ) ) { + + // Avoid "Member not found" error in IE8 caused by messing with window.constructor + // This block runs on every environment, so `global` is being used instead of `window` + // to avoid errors on node. + if ( prop !== "constructor" || a !== global ) { + if ( b[ prop ] === undefined ) { + delete a[ prop ]; + } else if ( !( undefOnly && typeof a[ prop ] !== "undefined" ) ) { + a[ prop ] = b[ prop ]; + } + } } + } - config.semaphore -= count || 1; - // don't start until equal number of stop-calls - if ( config.semaphore > 0 ) { - return; + return a; +} + +function objectType( obj ) { + if ( typeof obj === "undefined" ) { + return "undefined"; + } + + // Consider: typeof null === object + if ( obj === null ) { + return "null"; + } + + var match = toString.call( obj ).match( /^\[object\s(.*)\]$/ ), + type = match && match[ 1 ]; + + switch ( type ) { + case "Number": + if ( isNaN( obj ) ) { + return "nan"; + } + return "number"; + case "String": + case "Boolean": + case "Array": + case "Set": + case "Map": + case "Date": + case "RegExp": + case "Function": + case "Symbol": + return type.toLowerCase(); + } + if ( typeof obj === "object" ) { + return "object"; + } +} + +// Safe object type checking +function is( type, obj ) { + return QUnit.objectType( obj ) === type; +} + +var getUrlParams = function() { + var i, param, name, value; + var urlParams = {}; + var location = window.location; + var params = location.search.slice( 1 ).split( "&" ); + var length = params.length; + + for ( i = 0; i < length; i++ ) { + if ( params[ i ] ) { + param = params[ i ].split( "=" ); + name = decodeURIComponent( param[ 0 ] ); + + // allow just a key to turn on a flag, e.g., test.html?noglobals + value = param.length === 1 || + decodeURIComponent( param.slice( 1 ).join( "=" ) ) ; + if ( urlParams[ name ] ) { + urlParams[ name ] = [].concat( urlParams[ name ], value ); + } else { + urlParams[ name ] = value; + } } - // ignore if start is called more often then stop - if ( config.semaphore < 0 ) { - config.semaphore = 0; - QUnit.pushFailure( "Called start() while already started (QUnit.config.semaphore was 0 already)", null, sourceFromStacktrace(2) ); - return; + } + + return urlParams; +}; + +// Doesn't support IE6 to IE9, it will return undefined on these browsers +// See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack +function extractStacktrace( e, offset ) { + offset = offset === undefined ? 4 : offset; + + var stack, include, i; + + if ( e.stack ) { + stack = e.stack.split( "\n" ); + if ( /^error$/i.test( stack[ 0 ] ) ) { + stack.shift(); } - // A slight delay, to avoid any current callbacks - if ( defined.setTimeout ) { - setTimeout(function() { - if ( config.semaphore > 0 ) { - return; - } - if ( config.timeout ) { - clearTimeout( config.timeout ); + if ( fileName ) { + include = []; + for ( i = offset; i < stack.length; i++ ) { + if ( stack[ i ].indexOf( fileName ) !== -1 ) { + break; } + include.push( stack[ i ] ); + } + if ( include.length ) { + return include.join( "\n" ); + } + } + return stack[ offset ]; - config.blocking = false; - process( true ); - }, 13); - } else { - config.blocking = false; - process( true ); + // Support: Safari <=6 only + } else if ( e.sourceURL ) { + + // exclude useless self-reference for generated Error objects + if ( /qunit.js$/.test( e.sourceURL ) ) { + return; } - }, - stop: function( count ) { - config.semaphore += count || 1; - config.blocking = true; + // for actual exceptions, this is useful + return e.sourceURL + ":" + e.line; + } +} - if ( config.testTimeout && defined.setTimeout ) { - clearTimeout( config.timeout ); - config.timeout = setTimeout(function() { - QUnit.ok( false, "Test timed out" ); - config.semaphore = 1; - QUnit.start(); - }, config.testTimeout ); +function sourceFromStacktrace( offset ) { + var error = new Error(); + + // Support: Safari <=7 only, IE <=10 - 11 only + // Not all browsers generate the `stack` property for `new Error()`, see also #636 + if ( !error.stack ) { + try { + throw error; + } catch ( err ) { + error = err; } } -}; -// We use the prototype to distinguish between properties that should -// be exposed as globals (and in exports) and those that shouldn't -(function() { - function F() {} - F.prototype = QUnit; - QUnit = new F(); - // Make F QUnit's constructor so that we can add to the prototype later - QUnit.constructor = F; -}()); + return extractStacktrace( error, offset ); +} /** * Config object: Maintain internal state * Later exposed as QUnit.config * `config` initialized at top of scope */ -config = { +var config = { // The queue of tests to run queue: [], // block until document ready blocking: true, - // when enabled, show only failing tests - // gets persisted through sessionStorage and can be changed in UI via checkbox - hidepassed: false, - // by default, run previously failed tests first // very useful in combination with "Hide passed tests" checked reorder: true, @@ -239,9 +257,16 @@ // by default, modify document.title when suite is done altertitle: true, + // HTML Reporter: collapse every test except the first failing test + // If false, all failing tests will be expanded + collapse: true, + // by default, scroll to top of the page when suite is done scrolltop: true, + // depth up-to which object will be dumped + maxDepth: 5, + // when enabled, all tests must call expect() requireExpects: false, @@ -249,631 +274,492 @@ // when enabled, the id is set to `true` as a `QUnit.config` property urlConfig: [ { + id: "hidepassed", + label: "Hide passed tests", + tooltip: "Only show tests and assertions that fail. Stored as query-strings." + }, + { id: "noglobals", label: "Check for Globals", - tooltip: "Enabling this will test if any test introduces new properties on the `window` object. Stored as query-strings." + tooltip: "Enabling this will test if any test introduces new properties on the " + + "global object (`window` in Browsers). Stored as query-strings." }, { id: "notrycatch", label: "No try-catch", - tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging exceptions in IE reasonable. Stored as query-strings." + tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging " + + "exceptions in IE reasonable. Stored as query-strings." } ], // Set of all modules. - modules: {}, + modules: [], - // logging callback queues - begin: [], - done: [], - log: [], - testStart: [], - testDone: [], - moduleStart: [], - moduleDone: [] -}; + // Stack of nested modules + moduleStack: [], -// Initialize more QUnit.config and QUnit.urlParams -(function() { - var i, current, - location = window.location || { search: "", protocol: "file:" }, - params = location.search.slice( 1 ).split( "&" ), - length = params.length, - urlParams = {}; - - if ( params[ 0 ] ) { - for ( i = 0; i < length; i++ ) { - current = params[ i ].split( "=" ); - current[ 0 ] = decodeURIComponent( current[ 0 ] ); + // The first unnamed module + currentModule: { + name: "", + tests: [] + }, - // allow just a key to turn on a flag, e.g., test.html?noglobals - current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true; - if ( urlParams[ current[ 0 ] ] ) { - urlParams[ current[ 0 ] ] = [].concat( urlParams[ current[ 0 ] ], current[ 1 ] ); - } else { - urlParams[ current[ 0 ] ] = current[ 1 ]; - } - } - } + callbacks: {} +}; - QUnit.urlParams = urlParams; +var urlParams = defined.document ? getUrlParams() : {}; - // String search anywhere in moduleName+testName - config.filter = urlParams.filter; +// Push a loose unnamed module to the modules collection +config.modules.push( config.currentModule ); - // Exact match of the module name - config.module = urlParams.module; +if ( urlParams.filter === true ) { + delete urlParams.filter; +} - config.testNumber = []; - if ( urlParams.testNumber ) { +// String search anywhere in moduleName+testName +config.filter = urlParams.filter; - // Ensure that urlParams.testNumber is an array - urlParams.testNumber = [].concat( urlParams.testNumber ); - for ( i = 0; i < urlParams.testNumber.length; i++ ) { - current = urlParams.testNumber[ i ]; - config.testNumber.push( parseInt( current, 10 ) ); - } +config.testId = []; +if ( urlParams.testId ) { + // Ensure that urlParams.testId is an array + urlParams.testId = decodeURIComponent( urlParams.testId ).split( "," ); + for (var i = 0; i < urlParams.testId.length; i++ ) { + config.testId.push( urlParams.testId[ i ] ); } +} - // Figure out if we're running the tests from a server or not - QUnit.isLocal = location.protocol === "file:"; -}()); +var loggingCallbacks = {}; -extend( QUnit, { +// Register logging callbacks +function registerLoggingCallbacks( obj ) { + var i, l, key, + callbackNames = [ "begin", "done", "log", "testStart", "testDone", + "moduleStart", "moduleDone" ]; + + function registerLoggingCallback( key ) { + var loggingCallback = function( callback ) { + if ( objectType( callback ) !== "function" ) { + throw new Error( + "QUnit logging methods require a callback function as their first parameters." + ); + } - config: config, + config.callbacks[ key ].push( callback ); + }; - // Initialize the configuration options - init: function() { - extend( config, { - stats: { all: 0, bad: 0 }, - moduleStats: { all: 0, bad: 0 }, - started: +new Date(), - updateRate: 1000, - blocking: false, - autostart: true, - autorun: false, - filter: "", - queue: [], - semaphore: 1 - }); + // DEPRECATED: This will be removed on QUnit 2.0.0+ + // Stores the registered functions allowing restoring + // at verifyLoggingCallbacks() if modified + loggingCallbacks[ key ] = loggingCallback; + + return loggingCallback; + } - var tests, banner, result, - qunit = id( "qunit" ); + for ( i = 0, l = callbackNames.length; i < l; i++ ) { + key = callbackNames[ i ]; - if ( qunit ) { - qunit.innerHTML = - "

" + escapeText( document.title ) + "

" + - "

" + - "
" + - "

" + - "
    "; + // Initialize key collection of logging callback + if ( objectType( config.callbacks[ key ] ) === "undefined" ) { + config.callbacks[ key ] = []; } - tests = id( "qunit-tests" ); - banner = id( "qunit-banner" ); - result = id( "qunit-testresult" ); + obj[ key ] = registerLoggingCallback( key ); + } +} - if ( tests ) { - tests.innerHTML = ""; - } +function runLoggingCallbacks( key, args ) { + var i, l, callbacks; - if ( banner ) { - banner.className = ""; - } + callbacks = config.callbacks[ key ]; + for ( i = 0, l = callbacks.length; i < l; i++ ) { + callbacks[ i ]( args ); + } +} - if ( result ) { - result.parentNode.removeChild( result ); +// DEPRECATED: This will be removed on 2.0.0+ +// This function verifies if the loggingCallbacks were modified by the user +// If so, it will restore it, assign the given callback and print a console warning +function verifyLoggingCallbacks() { + var loggingCallback, userCallback; + + for ( loggingCallback in loggingCallbacks ) { + if ( QUnit[ loggingCallback ] !== loggingCallbacks[ loggingCallback ] ) { + + userCallback = QUnit[ loggingCallback ]; + + // Restore the callback function + QUnit[ loggingCallback ] = loggingCallbacks[ loggingCallback ]; + + // Assign the deprecated given callback + QUnit[ loggingCallback ]( userCallback ); + + if ( global.console && global.console.warn ) { + global.console.warn( + "QUnit." + loggingCallback + " was replaced with a new value.\n" + + "Please, check out the documentation on how to apply logging callbacks.\n" + + "Reference: https://api.qunitjs.com/category/callbacks/" + ); + } } + } +} - if ( tests ) { - result = document.createElement( "p" ); - result.id = "qunit-testresult"; - result.className = "result"; - tests.parentNode.insertBefore( result, tests ); - result.innerHTML = "Running...
     "; +( function() { + if ( !defined.document ) { + return; + } + + // `onErrorFnPrev` initialized at top of scope + // Preserve other handlers + var onErrorFnPrev = window.onerror; + + // Cover uncaught exceptions + // Returning true will suppress the default browser handler, + // returning false will let it run. + window.onerror = function( error, filePath, linerNr ) { + var ret = false; + if ( onErrorFnPrev ) { + ret = onErrorFnPrev( error, filePath, linerNr ); + } + + // Treat return value as window.onerror itself does, + // Only do our handling if not suppressed. + if ( ret !== true ) { + if ( QUnit.config.current ) { + if ( QUnit.config.current.ignoreGlobalErrors ) { + return true; + } + QUnit.pushFailure( error, filePath + ":" + linerNr ); + } else { + QUnit.test( "global failure", extend(function() { + QUnit.pushFailure( error, filePath + ":" + linerNr ); + }, { validTest: true } ) ); + } + return false; } - }, - // Resets the test setup. Useful for tests that modify the DOM. - /* - DEPRECATED: Use multiple tests instead of resetting inside a test. - Use testStart or testDone for custom cleanup. - This method will throw an error in 2.0, and will be removed in 2.1 - */ - reset: function() { - var fixture = id( "qunit-fixture" ); - if ( fixture ) { - fixture.innerHTML = config.fixture; - } - }, + return ret; + }; +} )(); - // Safe object type checking - is: function( type, obj ) { - return QUnit.objectType( obj ) === type; - }, +QUnit.urlParams = urlParams; - objectType: function( obj ) { - if ( typeof obj === "undefined" ) { - return "undefined"; - } +// Figure out if we're running the tests from a server or not +QUnit.isLocal = !( defined.document && window.location.protocol !== "file:" ); - // Consider: typeof null === object - if ( obj === null ) { - return "null"; - } +// Expose the current QUnit version +QUnit.version = "1.22.0"; - var match = toString.call( obj ).match(/^\[object\s(.*)\]$/), - type = match && match[1] || ""; +extend( QUnit, { - switch ( type ) { - case "Number": - if ( isNaN(obj) ) { - return "nan"; - } - return "number"; - case "String": - case "Boolean": - case "Array": - case "Date": - case "RegExp": - case "Function": - return type.toLowerCase(); - } - if ( typeof obj === "object" ) { - return "object"; - } - return undefined; - }, + // call on start of module test to prepend name to all tests + module: function( name, testEnvironment, executeNow ) { + var module, moduleFns; + var currentModule = config.currentModule; - push: function( result, actual, expected, message ) { - if ( !config.current ) { - throw new Error( "assertion outside test context, was " + sourceFromStacktrace() ); + if ( arguments.length === 2 ) { + if ( testEnvironment instanceof Function ) { + executeNow = testEnvironment; + testEnvironment = undefined; + } } - var output, source, - details = { - module: config.current.module, - name: config.current.testName, - result: result, - message: message, - actual: actual, - expected: expected - }; - - message = escapeText( message ) || ( result ? "okay" : "failed" ); - message = "" + message + ""; - output = message; - - if ( !result ) { - expected = escapeText( QUnit.jsDump.parse(expected) ); - actual = escapeText( QUnit.jsDump.parse(actual) ); - output += ""; - - if ( actual !== expected ) { - output += ""; - output += ""; - } + // DEPRECATED: handles setup/teardown functions, + // beforeEach and afterEach should be used instead + if ( testEnvironment && testEnvironment.setup ) { + testEnvironment.beforeEach = testEnvironment.setup; + delete testEnvironment.setup; + } + if ( testEnvironment && testEnvironment.teardown ) { + testEnvironment.afterEach = testEnvironment.teardown; + delete testEnvironment.teardown; + } + + module = createModule(); + + moduleFns = { + beforeEach: setHook( module, "beforeEach" ), + afterEach: setHook( module, "afterEach" ) + }; - source = sourceFromStacktrace(); + if ( executeNow instanceof Function ) { + config.moduleStack.push( module ); + setCurrentModule( module ); + executeNow.call( module.testEnvironment, moduleFns ); + config.moduleStack.pop(); + module = module.parentModule || currentModule; + } + + setCurrentModule( module ); + + function createModule() { + var parentModule = config.moduleStack.length ? + config.moduleStack.slice( -1 )[ 0 ] : null; + var moduleName = parentModule !== null ? + [ parentModule.name, name ].join( " > " ) : name; + var module = { + name: moduleName, + parentModule: parentModule, + tests: [] + }; - if ( source ) { - details.source = source; - output += ""; + var env = {}; + if ( parentModule ) { + extend( env, parentModule.testEnvironment ); + delete env.beforeEach; + delete env.afterEach; } + extend( env, testEnvironment ); + module.testEnvironment = env; - output += "
    Expected:
    " + expected + "
    Result:
    " + actual + "
    Diff:
    " + QUnit.diff( expected, actual ) + "
    Source:
    " + escapeText( source ) + "
    "; + config.modules.push( module ); + return module; } - runLoggingCallbacks( "log", QUnit, details ); + function setCurrentModule( module ) { + config.currentModule = module; + } - config.current.assertions.push({ - result: !!result, - message: output - }); }, - pushFailure: function( message, source, actual ) { - if ( !config.current ) { - throw new Error( "pushFailure() assertion outside test context, was " + sourceFromStacktrace(2) ); - } + // DEPRECATED: QUnit.asyncTest() will be removed in QUnit 2.0. + asyncTest: asyncTest, - var output, - details = { - module: config.current.module, - name: config.current.testName, - result: false, - message: message - }; + test: test, - message = escapeText( message ) || "error"; - message = "" + message + ""; - output = message; + skip: skip, - output += ""; + only: only, - if ( actual ) { - output += ""; - } + // DEPRECATED: The functionality of QUnit.start() will be altered in QUnit 2.0. + // In QUnit 2.0, invoking it will ONLY affect the `QUnit.config.autostart` blocking behavior. + start: function( count ) { + var globalStartAlreadyCalled = globalStartCalled; - if ( source ) { - details.source = source; - output += ""; - } + if ( !config.current ) { + globalStartCalled = true; + + if ( runStarted ) { + throw new Error( "Called start() outside of a test context while already started" ); + } else if ( globalStartAlreadyCalled || count > 1 ) { + throw new Error( "Called start() outside of a test context too many times" ); + } else if ( config.autostart ) { + throw new Error( "Called start() outside of a test context when " + + "QUnit.config.autostart was true" ); + } else if ( !config.pageLoaded ) { + + // The page isn't completely loaded yet, so bail out and let `QUnit.load` handle it + config.autostart = true; + return; + } + } else { - output += "
    Result:
    " + escapeText( actual ) + "
    Source:
    " + escapeText( source ) + "
    "; + // If a test is running, adjust its semaphore + config.current.semaphore -= count || 1; - runLoggingCallbacks( "log", QUnit, details ); + // If semaphore is non-numeric, throw error + if ( isNaN( config.current.semaphore ) ) { + config.current.semaphore = 0; + + QUnit.pushFailure( + "Called start() with a non-numeric decrement.", + sourceFromStacktrace( 2 ) + ); + return; + } - config.current.assertions.push({ - result: false, - message: output - }); - }, + // Don't start until equal number of stop-calls + if ( config.current.semaphore > 0 ) { + return; + } - url: function( params ) { - params = extend( extend( {}, QUnit.urlParams ), params ); - var key, - querystring = "?"; - - for ( key in params ) { - if ( hasOwn.call( params, key ) ) { - querystring += encodeURIComponent( key ) + "=" + - encodeURIComponent( params[ key ] ) + "&"; + // throw an Error if start is called more often than stop + if ( config.current.semaphore < 0 ) { + config.current.semaphore = 0; + + QUnit.pushFailure( + "Called start() while already started (test's semaphore was 0 already)", + sourceFromStacktrace( 2 ) + ); + return; } } - return window.location.protocol + "//" + window.location.host + - window.location.pathname + querystring.slice( 0, -1 ); + + resumeProcessing(); }, - extend: extend, - id: id, - addEvent: addEvent, - addClass: addClass, - hasClass: hasClass, - removeClass: removeClass - // load, equiv, jsDump, diff: Attached later -}); + // DEPRECATED: QUnit.stop() will be removed in QUnit 2.0. + stop: function( count ) { -/** - * @deprecated: Created for backwards compatibility with test runner that set the hook function - * into QUnit.{hook}, instead of invoking it and passing the hook function. - * QUnit.constructor is set to the empty F() above so that we can add to it's prototype here. - * Doing this allows us to tell if the following methods have been overwritten on the actual - * QUnit object. - */ -extend( QUnit.constructor.prototype, { + // If there isn't a test running, don't allow QUnit.stop() to be called + if ( !config.current ) { + throw new Error( "Called stop() outside of a test context" ); + } - // Logging callbacks; all receive a single argument with the listed properties - // run test/logs.html for any related changes - begin: registerLoggingCallback( "begin" ), + // If a test is running, adjust its semaphore + config.current.semaphore += count || 1; - // done: { failed, passed, total, runtime } - done: registerLoggingCallback( "done" ), + pauseProcessing(); + }, - // log: { result, actual, expected, message } - log: registerLoggingCallback( "log" ), + config: config, - // testStart: { name } - testStart: registerLoggingCallback( "testStart" ), + is: is, - // testDone: { name, failed, passed, total, runtime } - testDone: registerLoggingCallback( "testDone" ), + objectType: objectType, - // moduleStart: { name } - moduleStart: registerLoggingCallback( "moduleStart" ), + extend: extend, - // moduleDone: { name, failed, passed, total } - moduleDone: registerLoggingCallback( "moduleDone" ) -}); + load: function() { + config.pageLoaded = true; -if ( !defined.document || document.readyState === "complete" ) { - config.autorun = true; -} + // Initialize the configuration options + extend( config, { + stats: { all: 0, bad: 0 }, + moduleStats: { all: 0, bad: 0 }, + started: 0, + updateRate: 1000, + autostart: true, + filter: "" + }, true ); -QUnit.load = function() { - runLoggingCallbacks( "begin", QUnit, {} ); + config.blocking = false; - // Initialize the config, saving the execution queue - var banner, filter, i, j, label, len, main, ol, toolbar, val, selection, - urlConfigContainer, moduleFilter, userAgent, - numModules = 0, - moduleNames = [], - moduleFilterHtml = "", - urlConfigHtml = "", - oldconfig = extend( {}, config ); + if ( config.autostart ) { + resumeProcessing(); + } + }, - QUnit.init(); - extend(config, oldconfig); + stack: function( offset ) { + offset = ( offset || 0 ) + 2; + return sourceFromStacktrace( offset ); + } +}); - config.blocking = false; +registerLoggingCallbacks( QUnit ); - len = config.urlConfig.length; +function begin() { + var i, l, + modulesLog = []; - for ( i = 0; i < len; i++ ) { - val = config.urlConfig[i]; - if ( typeof val === "string" ) { - val = { - id: val, - label: val - }; - } - config[ val.id ] = QUnit.urlParams[ val.id ]; - if ( !val.value || typeof val.value === "string" ) { - urlConfigHtml += ""; - } else { - urlConfigHtml += ""; - } - } - for ( i in config.modules ) { - if ( config.modules.hasOwnProperty( i ) ) { - moduleNames.push(i); + // If the test run hasn't officially begun yet + if ( !config.started ) { + + // Record the time of the test run's beginning + config.started = now(); + + verifyLoggingCallbacks(); + + // Delete the loose unnamed module if unused. + if ( config.modules[ 0 ].name === "" && config.modules[ 0 ].tests.length === 0 ) { + config.modules.shift(); } - } - numModules = moduleNames.length; - moduleNames.sort( function( a, b ) { - return a.localeCompare( b ); - }); - moduleFilterHtml += ""; - // `userAgent` initialized at top of scope - userAgent = id( "qunit-userAgent" ); - if ( userAgent ) { - userAgent.innerHTML = navigator.userAgent; - } + config.blocking = false; + process( true ); +} - // `banner` initialized at top of scope - banner = id( "qunit-header" ); - if ( banner ) { - banner.innerHTML = "" + banner.innerHTML + " "; +function process( last ) { + function next() { + process( last ); } + var start = now(); + config.depth = ( config.depth || 0 ) + 1; - // `toolbar` initialized at top of scope - toolbar = id( "qunit-testrunner-toolbar" ); - if ( toolbar ) { - // `filter` initialized at top of scope - filter = document.createElement( "input" ); - filter.type = "checkbox"; - filter.id = "qunit-filter-pass"; - - addEvent( filter, "click", function() { - var tmp, - ol = id( "qunit-tests" ); + while ( config.queue.length && !config.blocking ) { + if ( !defined.setTimeout || config.updateRate <= 0 || + ( ( now() - start ) < config.updateRate ) ) { + if ( config.current ) { - if ( filter.checked ) { - ol.className = ol.className + " hidepass"; - } else { - tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " "; - ol.className = tmp.replace( / hidepass /, " " ); - } - if ( defined.sessionStorage ) { - if (filter.checked) { - sessionStorage.setItem( "qunit-filter-passed-tests", "true" ); - } else { - sessionStorage.removeItem( "qunit-filter-passed-tests" ); - } + // Reset async tracking for each phase of the Test lifecycle + config.current.usedAsync = false; } - }); - - if ( config.hidepassed || defined.sessionStorage && sessionStorage.getItem( "qunit-filter-passed-tests" ) ) { - filter.checked = true; - // `ol` initialized at top of scope - ol = id( "qunit-tests" ); - ol.className = ol.className + " hidepass"; - } - toolbar.appendChild( filter ); - - // `label` initialized at top of scope - label = document.createElement( "label" ); - label.setAttribute( "for", "qunit-filter-pass" ); - label.setAttribute( "title", "Only show tests and assertions that fail. Stored in sessionStorage." ); - label.innerHTML = "Hide passed tests"; - toolbar.appendChild( label ); - - urlConfigContainer = document.createElement("span"); - urlConfigContainer.innerHTML = urlConfigHtml; - // For oldIE support: - // * Add handlers to the individual elements instead of the container - // * Use "click" instead of "change" for checkboxes - // * Fallback from event.target to event.srcElement - addEvents( urlConfigContainer.getElementsByTagName("input"), "click", function( event ) { - var params = {}, - target = event.target || event.srcElement; - params[ target.name ] = target.checked ? - target.defaultValue || true : - undefined; - window.location = QUnit.url( params ); - }); - addEvents( urlConfigContainer.getElementsByTagName("select"), "change", function( event ) { - var params = {}, - target = event.target || event.srcElement; - params[ target.name ] = target.options[ target.selectedIndex ].value || undefined; - window.location = QUnit.url( params ); - }); - toolbar.appendChild( urlConfigContainer ); - - if (numModules > 1) { - moduleFilter = document.createElement( "span" ); - moduleFilter.setAttribute( "id", "qunit-modulefilter-container" ); - moduleFilter.innerHTML = moduleFilterHtml; - addEvent( moduleFilter.lastChild, "change", function() { - var selectBox = moduleFilter.getElementsByTagName("select")[0], - selectedModule = decodeURIComponent(selectBox.options[selectBox.selectedIndex].value); - - window.location = QUnit.url({ - module: ( selectedModule === "" ) ? undefined : selectedModule, - // Remove any existing filters - filter: undefined, - testNumber: undefined - }); - }); - toolbar.appendChild(moduleFilter); + config.queue.shift()(); + } else { + setTimeout( next, 13 ); + break; } } - - // `main` initialized at top of scope - main = id( "qunit-fixture" ); - if ( main ) { - config.fixture = main.innerHTML; + config.depth--; + if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) { + done(); } +} - if ( config.autostart ) { - QUnit.start(); - } -}; +function pauseProcessing() { + config.blocking = true; -if ( defined.document ) { - addEvent( window, "load", QUnit.load ); + if ( config.testTimeout && defined.setTimeout ) { + clearTimeout( config.timeout ); + config.timeout = setTimeout(function() { + if ( config.current ) { + config.current.semaphore = 0; + QUnit.pushFailure( "Test timed out", sourceFromStacktrace( 2 ) ); + } else { + throw new Error( "Test timed out" ); + } + resumeProcessing(); + }, config.testTimeout ); + } } -// `onErrorFnPrev` initialized at top of scope -// Preserve other handlers -onErrorFnPrev = window.onerror; - -// Cover uncaught exceptions -// Returning true will suppress the default browser handler, -// returning false will let it run. -window.onerror = function ( error, filePath, linerNr ) { - var ret = false; - if ( onErrorFnPrev ) { - ret = onErrorFnPrev( error, filePath, linerNr ); - } - - // Treat return value as window.onerror itself does, - // Only do our handling if not suppressed. - if ( ret !== true ) { - if ( QUnit.config.current ) { - if ( QUnit.config.current.ignoreGlobalErrors ) { - return true; +function resumeProcessing() { + runStarted = true; + + // A slight delay to allow this iteration of the event loop to finish (more assertions, etc.) + if ( defined.setTimeout ) { + setTimeout(function() { + if ( config.current && config.current.semaphore > 0 ) { + return; + } + if ( config.timeout ) { + clearTimeout( config.timeout ); } - QUnit.pushFailure( error, filePath + ":" + linerNr ); - } else { - QUnit.test( "global failure", extend( function() { - QUnit.pushFailure( error, filePath + ":" + linerNr ); - }, { validTest: validTest } ) ); - } - return false; - } - return ret; -}; + begin(); + }, 13 ); + } else { + begin(); + } +} function done() { + var runtime, passed; + config.autorun = true; // Log the last module results if ( config.previousModule ) { - runLoggingCallbacks( "moduleDone", QUnit, { - name: config.previousModule, + runLoggingCallbacks( "moduleDone", { + name: config.previousModule.name, + tests: config.previousModule.tests, failed: config.moduleStats.bad, passed: config.moduleStats.all - config.moduleStats.bad, - total: config.moduleStats.all + total: config.moduleStats.all, + runtime: now() - config.moduleStats.started }); } delete config.previousModule; - var i, key, - banner = id( "qunit-banner" ), - tests = id( "qunit-tests" ), - runtime = +new Date() - config.started, - passed = config.stats.all - config.stats.bad, - html = [ - "Tests completed in ", - runtime, - " milliseconds.
    ", - "", - passed, - " assertions of ", - config.stats.all, - " passed, ", - config.stats.bad, - " failed." - ].join( "" ); - - if ( banner ) { - banner.className = ( config.stats.bad ? "qunit-fail" : "qunit-pass" ); - } - - if ( tests ) { - id( "qunit-testresult" ).innerHTML = html; - } - - if ( config.altertitle && defined.document && document.title ) { - // show ✖ for good, ✔ for bad suite result in title - // use escape sequences in case file gets loaded with non-utf-8-charset - document.title = [ - ( config.stats.bad ? "\u2716" : "\u2714" ), - document.title.replace( /^[\u2714\u2716] /i, "" ) - ].join( " " ); - } - - // clear own sessionStorage items if all tests passed - if ( config.reorder && defined.sessionStorage && config.stats.bad === 0 ) { - // `key` & `i` initialized at top of scope - for ( i = 0; i < sessionStorage.length; i++ ) { - key = sessionStorage.key( i++ ); - if ( key.indexOf( "qunit-test-" ) === 0 ) { - sessionStorage.removeItem( key ); - } - } - } - - // scroll back to top to show results - if ( config.scrolltop && window.scrollTo ) { - window.scrollTo(0, 0); - } + runtime = now() - config.started; + passed = config.stats.all - config.stats.bad; - runLoggingCallbacks( "done", QUnit, { + runLoggingCallbacks( "done", { failed: config.stats.bad, passed: passed, total: config.stats.all, @@ -881,431 +767,131 @@ }); } -/** @return Boolean: true if this test should be ran */ -function validTest( test ) { - var include, - filter = config.filter && config.filter.toLowerCase(), - module = config.module && config.module.toLowerCase(), - fullName = ( test.module + ": " + test.testName ).toLowerCase(); - - // Internally-generated tests are always valid - if ( test.callback && test.callback.validTest === validTest ) { - delete test.callback.validTest; - return true; - } - - if ( config.testNumber.length > 0 ) { - if ( inArray( test.testNumber, config.testNumber ) < 0 ) { - return false; - } - } - - if ( module && ( !test.module || test.module.toLowerCase() !== module ) ) { - return false; - } - - if ( !filter ) { - return true; +function setHook( module, hookName ) { + if ( module.testEnvironment === undefined ) { + module.testEnvironment = {}; } - include = filter.charAt( 0 ) !== "!"; - if ( !include ) { - filter = filter.slice( 1 ); - } + return function( callback ) { + module.testEnvironment[ hookName ] = callback; + }; +} - // If the filter matches, we need to honour include - if ( fullName.indexOf( filter ) !== -1 ) { - return include; - } +var focused = false; +var priorityCount = 0; - // Otherwise, do the opposite - return !include; -} +function Test( settings ) { + var i, l; -// so far supports only Firefox, Chrome and Opera (buggy), Safari (for real exceptions) -// Later Safari and IE10 are supposed to support error.stack as well -// See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack -function extractStacktrace( e, offset ) { - offset = offset === undefined ? 3 : offset; + ++Test.count; - var stack, include, i; + extend( this, settings ); + this.assertions = []; + this.semaphore = 0; + this.usedAsync = false; + this.module = config.currentModule; + this.stack = sourceFromStacktrace( 3 ); - if ( e.stacktrace ) { - // Opera - return e.stacktrace.split( "\n" )[ offset + 3 ]; - } else if ( e.stack ) { - // Firefox, Chrome - stack = e.stack.split( "\n" ); - if (/^error$/i.test( stack[0] ) ) { - stack.shift(); - } - if ( fileName ) { - include = []; - for ( i = offset; i < stack.length; i++ ) { - if ( stack[ i ].indexOf( fileName ) !== -1 ) { - break; - } - include.push( stack[ i ] ); - } - if ( include.length ) { - return include.join( "\n" ); - } - } - return stack[ offset ]; - } else if ( e.sourceURL ) { - // Safari, PhantomJS - // hopefully one day Safari provides actual stacktraces - // exclude useless self-reference for generated Error objects - if ( /qunit.js$/.test( e.sourceURL ) ) { - return; + // Register unique strings + for ( i = 0, l = this.module.tests; i < l.length; i++ ) { + if ( this.module.tests[ i ].name === this.testName ) { + this.testName += " "; } - // for actual exceptions, this is useful - return e.sourceURL + ":" + e.line; - } -} -function sourceFromStacktrace( offset ) { - try { - throw new Error(); - } catch ( e ) { - return extractStacktrace( e, offset ); } -} -/** - * Escape text for attribute or text content. - */ -function escapeText( s ) { - if ( !s ) { - return ""; - } - s = s + ""; - // Both single quotes and double quotes (for attributes) - return s.replace( /['"<>&]/g, function( s ) { - switch( s ) { - case "'": - return "'"; - case "\"": - return """; - case "<": - return "<"; - case ">": - return ">"; - case "&": - return "&"; - } + this.testId = generateHash( this.module.name, this.testName ); + + this.module.tests.push({ + name: this.testName, + testId: this.testId }); -} -function synchronize( callback, last ) { - config.queue.push( callback ); + if ( settings.skip ) { - if ( config.autorun && !config.blocking ) { - process( last ); + // Skipped tests will fully ignore any sent callback + this.callback = function() {}; + this.async = false; + this.expected = 0; + } else { + this.assert = new Assert( this ); } } -function process( last ) { - function next() { - process( last ); - } - var start = new Date().getTime(); - config.depth = config.depth ? config.depth + 1 : 1; +Test.count = 0; - while ( config.queue.length && !config.blocking ) { - if ( !defined.setTimeout || config.updateRate <= 0 || ( ( new Date().getTime() - start ) < config.updateRate ) ) { - config.queue.shift()(); - } else { - setTimeout( next, 13 ); - break; - } - } - config.depth--; - if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) { - done(); - } -} +Test.prototype = { + before: function() { + if ( -function saveGlobal() { - config.pollution = []; + // Emit moduleStart when we're switching from one module to another + this.module !== config.previousModule || - if ( config.noglobals ) { - for ( var key in window ) { - if ( hasOwn.call( window, key ) ) { - // in Opera sometimes DOM element ids show up here, ignore them - if ( /^qunit-test-output/.test( key ) ) { - continue; - } - config.pollution.push( key ); + // They could be equal (both undefined) but if the previousModule property doesn't + // yet exist it means this is the first test in a suite that isn't wrapped in a + // module, in which case we'll just emit a moduleStart event for 'undefined'. + // Without this, reporters can get testStart before moduleStart which is a problem. + !hasOwn.call( config, "previousModule" ) + ) { + if ( hasOwn.call( config, "previousModule" ) ) { + runLoggingCallbacks( "moduleDone", { + name: config.previousModule.name, + tests: config.previousModule.tests, + failed: config.moduleStats.bad, + passed: config.moduleStats.all - config.moduleStats.bad, + total: config.moduleStats.all, + runtime: now() - config.moduleStats.started + }); } + config.previousModule = this.module; + config.moduleStats = { all: 0, bad: 0, started: now() }; + runLoggingCallbacks( "moduleStart", { + name: this.module.name, + tests: this.module.tests + }); } - } -} -function checkPollution() { - var newGlobals, - deletedGlobals, - old = config.pollution; + config.current = this; - saveGlobal(); + if ( this.module.testEnvironment ) { + delete this.module.testEnvironment.beforeEach; + delete this.module.testEnvironment.afterEach; + } + this.testEnvironment = extend( {}, this.module.testEnvironment ); - newGlobals = diff( config.pollution, old ); - if ( newGlobals.length > 0 ) { - QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join(", ") ); - } - - deletedGlobals = diff( old, config.pollution ); - if ( deletedGlobals.length > 0 ) { - QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join(", ") ); - } -} - -// returns a new Array with the elements that are in a but not in b -function diff( a, b ) { - var i, j, - result = a.slice(); - - for ( i = 0; i < result.length; i++ ) { - for ( j = 0; j < b.length; j++ ) { - if ( result[i] === b[j] ) { - result.splice( i, 1 ); - i--; - break; - } - } - } - return result; -} - -function extend( a, b ) { - for ( var prop in b ) { - if ( hasOwn.call( b, prop ) ) { - // Avoid "Member not found" error in IE8 caused by messing with window.constructor - if ( !( prop === "constructor" && a === window ) ) { - if ( b[ prop ] === undefined ) { - delete a[ prop ]; - } else { - a[ prop ] = b[ prop ]; - } - } - } - } - - return a; -} - -/** - * @param {HTMLElement} elem - * @param {string} type - * @param {Function} fn - */ -function addEvent( elem, type, fn ) { - if ( elem.addEventListener ) { - - // Standards-based browsers - elem.addEventListener( type, fn, false ); - } else if ( elem.attachEvent ) { - - // support: IE <9 - elem.attachEvent( "on" + type, fn ); - } else { - - // Caller must ensure support for event listeners is present - throw new Error( "addEvent() was called in a context without event listener support" ); - } -} - -/** - * @param {Array|NodeList} elems - * @param {string} type - * @param {Function} fn - */ -function addEvents( elems, type, fn ) { - var i = elems.length; - while ( i-- ) { - addEvent( elems[i], type, fn ); - } -} - -function hasClass( elem, name ) { - return (" " + elem.className + " ").indexOf(" " + name + " ") > -1; -} - -function addClass( elem, name ) { - if ( !hasClass( elem, name ) ) { - elem.className += (elem.className ? " " : "") + name; - } -} - -function removeClass( elem, name ) { - var set = " " + elem.className + " "; - // Class name may appear multiple times - while ( set.indexOf(" " + name + " ") > -1 ) { - set = set.replace(" " + name + " " , " "); - } - // If possible, trim it for prettiness, but not necessarily - elem.className = typeof set.trim === "function" ? set.trim() : set.replace(/^\s+|\s+$/g, ""); -} - -function id( name ) { - return defined.document && document.getElementById && document.getElementById( name ); -} - -function registerLoggingCallback( key ) { - return function( callback ) { - config[key].push( callback ); - }; -} - -// Supports deprecated method of completely overwriting logging callbacks -function runLoggingCallbacks( key, scope, args ) { - var i, callbacks; - if ( QUnit.hasOwnProperty( key ) ) { - QUnit[ key ].call(scope, args ); - } else { - callbacks = config[ key ]; - for ( i = 0; i < callbacks.length; i++ ) { - callbacks[ i ].call( scope, args ); - } - } -} - -// from jquery.js -function inArray( elem, array ) { - if ( array.indexOf ) { - return array.indexOf( elem ); - } - - for ( var i = 0, length = array.length; i < length; i++ ) { - if ( array[ i ] === elem ) { - return i; - } - } - - return -1; -} - -function Test( settings ) { - extend( this, settings ); - this.assertions = []; - this.testNumber = ++Test.count; -} - -Test.count = 0; - -Test.prototype = { - init: function() { - var a, b, li, - tests = id( "qunit-tests" ); - - if ( tests ) { - b = document.createElement( "strong" ); - b.innerHTML = this.nameHtml; - - // `a` initialized at top of scope - a = document.createElement( "a" ); - a.innerHTML = "Rerun"; - a.href = QUnit.url({ testNumber: this.testNumber }); - - li = document.createElement( "li" ); - li.appendChild( b ); - li.appendChild( a ); - li.className = "running"; - li.id = this.id = "qunit-test-output" + testId++; - - tests.appendChild( li ); - } - }, - setup: function() { - if ( - // Emit moduleStart when we're switching from one module to another - this.module !== config.previousModule || - // They could be equal (both undefined) but if the previousModule property doesn't - // yet exist it means this is the first test in a suite that isn't wrapped in a - // module, in which case we'll just emit a moduleStart event for 'undefined'. - // Without this, reporters can get testStart before moduleStart which is a problem. - !hasOwn.call( config, "previousModule" ) - ) { - if ( hasOwn.call( config, "previousModule" ) ) { - runLoggingCallbacks( "moduleDone", QUnit, { - name: config.previousModule, - failed: config.moduleStats.bad, - passed: config.moduleStats.all - config.moduleStats.bad, - total: config.moduleStats.all - }); - } - config.previousModule = this.module; - config.moduleStats = { all: 0, bad: 0 }; - runLoggingCallbacks( "moduleStart", QUnit, { - name: this.module - }); - } - - config.current = this; - - this.testEnvironment = extend({ - setup: function() {}, - teardown: function() {} - }, this.moduleTestEnvironment ); - - this.started = +new Date(); - runLoggingCallbacks( "testStart", QUnit, { - name: this.testName, - module: this.module - }); - - /*jshint camelcase:false */ - - - /** - * Expose the current test environment. - * - * @deprecated since 1.12.0: Use QUnit.config.current.testEnvironment instead. - */ - QUnit.current_testEnvironment = this.testEnvironment; - - /*jshint camelcase:true */ + this.started = now(); + runLoggingCallbacks( "testStart", { + name: this.testName, + module: this.module.name, + testId: this.testId + }); if ( !config.pollution ) { saveGlobal(); } - if ( config.notrycatch ) { - this.testEnvironment.setup.call( this.testEnvironment, QUnit.assert ); - return; - } - try { - this.testEnvironment.setup.call( this.testEnvironment, QUnit.assert ); - } catch( e ) { - QUnit.pushFailure( "Setup failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 1 ) ); - } }, - run: function() { - config.current = this; - var running = id( "qunit-testresult" ); + run: function() { + var promise; - if ( running ) { - running.innerHTML = "Running:
    " + this.nameHtml; - } + config.current = this; if ( this.async ) { QUnit.stop(); } - this.callbackStarted = +new Date(); + this.callbackStarted = now(); if ( config.notrycatch ) { - this.callback.call( this.testEnvironment, QUnit.assert ); - this.callbackRuntime = +new Date() - this.callbackStarted; + runTest( this ); return; } try { - this.callback.call( this.testEnvironment, QUnit.assert ); - this.callbackRuntime = +new Date() - this.callbackStarted; - } catch( e ) { - this.callbackRuntime = +new Date() - this.callbackStarted; + runTest( this ); + } catch ( e ) { + this.pushFailure( "Died on test #" + ( this.assertions.length + 1 ) + " " + + this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) ); - QUnit.pushFailure( "Died on test #" + (this.assertions.length + 1) + " " + this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) ); // else next test will carry the responsibility saveGlobal(); @@ -1314,643 +900,1077 @@ QUnit.start(); } } + + function runTest( test ) { + promise = test.callback.call( test.testEnvironment, test.assert ); + test.resolvePromise( promise ); + } }, - teardown: function() { - config.current = this; - if ( config.notrycatch ) { - if ( typeof this.callbackRuntime === "undefined" ) { - this.callbackRuntime = +new Date() - this.callbackStarted; + + after: function() { + checkPollution(); + }, + + queueHook: function( hook, hookName ) { + var promise, + test = this; + return function runHook() { + config.current = test; + if ( config.notrycatch ) { + callHook(); + return; } - this.testEnvironment.teardown.call( this.testEnvironment, QUnit.assert ); - return; - } else { try { - this.testEnvironment.teardown.call( this.testEnvironment, QUnit.assert ); - } catch( e ) { - QUnit.pushFailure( "Teardown failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 1 ) ); + callHook(); + } catch ( error ) { + test.pushFailure( hookName + " failed on " + test.testName + ": " + + ( error.message || error ), extractStacktrace( error, 0 ) ); + } + + function callHook() { + promise = hook.call( test.testEnvironment, test.assert ); + test.resolvePromise( promise, hookName ); + } + }; + }, + + // Currently only used for module level hooks, can be used to add global level ones + hooks: function( handler ) { + var hooks = []; + + function processHooks( test, module ) { + if ( module.parentModule ) { + processHooks( test, module.parentModule ); + } + if ( module.testEnvironment && + QUnit.objectType( module.testEnvironment[ handler ] ) === "function" ) { + hooks.push( test.queueHook( module.testEnvironment[ handler ], handler ) ); } } - checkPollution(); + + // Hooks are ignored on skipped tests + if ( !this.skip ) { + processHooks( this, this.module ); + } + return hooks; }, + finish: function() { config.current = this; if ( config.requireExpects && this.expected === null ) { - QUnit.pushFailure( "Expected number of assertions to be defined, but expect() was not called.", this.stack ); + this.pushFailure( "Expected number of assertions to be defined, but expect() was " + + "not called.", this.stack ); } else if ( this.expected !== null && this.expected !== this.assertions.length ) { - QUnit.pushFailure( "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run", this.stack ); + this.pushFailure( "Expected " + this.expected + " assertions, but " + + this.assertions.length + " were run", this.stack ); } else if ( this.expected === null && !this.assertions.length ) { - QUnit.pushFailure( "Expected at least one assertion, but none were run - call expect(0) to accept zero assertions.", this.stack ); + this.pushFailure( "Expected at least one assertion, but none were run - call " + + "expect(0) to accept zero assertions.", this.stack ); } - var i, assertion, a, b, time, li, ol, - test = this, - good = 0, - bad = 0, - tests = id( "qunit-tests" ); + var i, + bad = 0; - this.runtime = +new Date() - this.started; + this.runtime = now() - this.started; config.stats.all += this.assertions.length; config.moduleStats.all += this.assertions.length; - if ( tests ) { - ol = document.createElement( "ol" ); - ol.className = "qunit-assert-list"; - - for ( i = 0; i < this.assertions.length; i++ ) { - assertion = this.assertions[i]; - - li = document.createElement( "li" ); - li.className = assertion.result ? "pass" : "fail"; - li.innerHTML = assertion.message || ( assertion.result ? "okay" : "failed" ); - ol.appendChild( li ); - - if ( assertion.result ) { - good++; - } else { - bad++; - config.stats.bad++; - config.moduleStats.bad++; - } - } - - // store result when possible - if ( QUnit.config.reorder && defined.sessionStorage ) { - if ( bad ) { - sessionStorage.setItem( "qunit-test-" + this.module + "-" + this.testName, bad ); - } else { - sessionStorage.removeItem( "qunit-test-" + this.module + "-" + this.testName ); - } - } - - if ( bad === 0 ) { - addClass( ol, "qunit-collapsed" ); - } - - // `b` initialized at top of scope - b = document.createElement( "strong" ); - b.innerHTML = this.nameHtml + " (" + bad + ", " + good + ", " + this.assertions.length + ")"; - - addEvent(b, "click", function() { - var next = b.parentNode.lastChild, - collapsed = hasClass( next, "qunit-collapsed" ); - ( collapsed ? removeClass : addClass )( next, "qunit-collapsed" ); - }); - - addEvent(b, "dblclick", function( e ) { - var target = e && e.target ? e.target : window.event.srcElement; - if ( target.nodeName.toLowerCase() === "span" || target.nodeName.toLowerCase() === "b" ) { - target = target.parentNode; - } - if ( window.location && target.nodeName.toLowerCase() === "strong" ) { - window.location = QUnit.url({ testNumber: test.testNumber }); - } - }); - - // `time` initialized at top of scope - time = document.createElement( "span" ); - time.className = "runtime"; - time.innerHTML = this.runtime + " ms"; - - // `li` initialized at top of scope - li = id( this.id ); - li.className = bad ? "fail" : "pass"; - li.removeChild( li.firstChild ); - a = li.firstChild; - li.appendChild( b ); - li.appendChild( a ); - li.appendChild( time ); - li.appendChild( ol ); - - } else { - for ( i = 0; i < this.assertions.length; i++ ) { - if ( !this.assertions[i].result ) { - bad++; - config.stats.bad++; - config.moduleStats.bad++; - } + for ( i = 0; i < this.assertions.length; i++ ) { + if ( !this.assertions[ i ].result ) { + bad++; + config.stats.bad++; + config.moduleStats.bad++; } } - runLoggingCallbacks( "testDone", QUnit, { + runLoggingCallbacks( "testDone", { name: this.testName, - module: this.module, + module: this.module.name, + skipped: !!this.skip, failed: bad, passed: this.assertions.length - bad, total: this.assertions.length, runtime: this.runtime, + + // HTML Reporter use + assertions: this.assertions, + testId: this.testId, + + // Source of Test + source: this.stack, + // DEPRECATED: this property will be removed in 2.0.0, use runtime instead duration: this.runtime }); + // QUnit.reset() is deprecated and will be replaced for a new + // fixture reset function on QUnit 2.0/2.1. + // It's still called here for backwards compatibility handling QUnit.reset(); config.current = undefined; }, queue: function() { - var bad, + var priority, test = this; - synchronize(function() { - test.init(); - }); + if ( !this.valid() ) { + return; + } + function run() { + // each of these can by async - synchronize(function() { - test.setup(); - }); - synchronize(function() { - test.run(); - }); - synchronize(function() { - test.teardown(); - }); - synchronize(function() { - test.finish(); - }); - } + synchronize([ + function() { + test.before(); + }, - // `bad` initialized at top of scope - // defer when previous test run passed, if storage is available - bad = QUnit.config.reorder && defined.sessionStorage && - +sessionStorage.getItem( "qunit-test-" + this.module + "-" + this.testName ); + test.hooks( "beforeEach" ), + function() { + test.run(); + }, - if ( bad ) { - run(); - } else { - synchronize( run, true ); - } - } -}; + test.hooks( "afterEach" ).reverse(), -// `assert` initialized at top of scope -// Assert helpers -// All of these must either call QUnit.push() or manually do: -// - runLoggingCallbacks( "log", .. ); -// - config.current.assertions.push({ .. }); -assert = QUnit.assert = { - /** - * Asserts rough true-ish result. - * @name ok - * @function - * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); - */ - ok: function( result, msg ) { - if ( !config.current ) { - throw new Error( "ok() assertion outside test context, was " + sourceFromStacktrace(2) ); + function() { + test.after(); + }, + function() { + test.finish(); + } + ]); } - result = !!result; - msg = msg || ( result ? "okay" : "failed" ); + // Prioritize previously failed tests, detected from sessionStorage + priority = QUnit.config.reorder && defined.sessionStorage && + +sessionStorage.getItem( "qunit-test-" + this.module.name + "-" + this.testName ); + + return synchronize( run, priority ); + }, + + pushResult: function( resultInfo ) { + + // resultInfo = { result, actual, expected, message, negative } var source, details = { - module: config.current.module, - name: config.current.testName, - result: result, - message: msg + module: this.module.name, + name: this.testName, + result: resultInfo.result, + message: resultInfo.message, + actual: resultInfo.actual, + expected: resultInfo.expected, + testId: this.testId, + negative: resultInfo.negative || false, + runtime: now() - this.started }; - msg = "" + escapeText( msg ) + ""; + if ( !resultInfo.result ) { + source = sourceFromStacktrace(); - if ( !result ) { - source = sourceFromStacktrace( 2 ); if ( source ) { details.source = source; - msg += "
    Source:
    " +
    -					escapeText( source ) +
    -					"
    "; } } - runLoggingCallbacks( "log", QUnit, details ); - config.current.assertions.push({ - result: result, - message: msg - }); - }, - /** - * Assert that the first two arguments are equal, with an optional message. - * Prints out both actual and expected values. - * @name equal - * @function - * @example equal( format( "Received {0} bytes.", 2), "Received 2 bytes.", "format() replaces {0} with next argument" ); - */ - equal: function( actual, expected, message ) { - /*jshint eqeqeq:false */ - QUnit.push( expected == actual, actual, expected, message ); - }, + runLoggingCallbacks( "log", details ); - /** - * @name notEqual - * @function - */ - notEqual: function( actual, expected, message ) { - /*jshint eqeqeq:false */ - QUnit.push( expected != actual, actual, expected, message ); + this.assertions.push({ + result: !!resultInfo.result, + message: resultInfo.message + }); }, - /** - * @name propEqual - * @function - */ - propEqual: function( actual, expected, message ) { - actual = objectValues(actual); - expected = objectValues(expected); - QUnit.push( QUnit.equiv(actual, expected), actual, expected, message ); - }, + pushFailure: function( message, source, actual ) { + if ( !( this instanceof Test ) ) { + throw new Error( "pushFailure() assertion outside test context, was " + + sourceFromStacktrace( 2 ) ); + } + + var details = { + module: this.module.name, + name: this.testName, + result: false, + message: message || "error", + actual: actual || null, + testId: this.testId, + runtime: now() - this.started + }; + + if ( source ) { + details.source = source; + } + + runLoggingCallbacks( "log", details ); + + this.assertions.push({ + result: false, + message: message + }); + }, + + resolvePromise: function( promise, phase ) { + var then, message, + test = this; + if ( promise != null ) { + then = promise.then; + if ( QUnit.objectType( then ) === "function" ) { + QUnit.stop(); + then.call( + promise, + function() { QUnit.start(); }, + function( error ) { + message = "Promise rejected " + + ( !phase ? "during" : phase.replace( /Each$/, "" ) ) + + " " + test.testName + ": " + ( error.message || error ); + test.pushFailure( message, extractStacktrace( error, 0 ) ); + + // else next test will carry the responsibility + saveGlobal(); + + // Unblock + QUnit.start(); + } + ); + } + } + }, + + valid: function() { + var filter = config.filter, + regexFilter = /^(!?)\/([\w\W]*)\/(i?$)/.exec( filter ), + module = QUnit.urlParams.module && QUnit.urlParams.module.toLowerCase(), + fullName = ( this.module.name + ": " + this.testName ); + + function testInModuleChain( testModule ) { + var testModuleName = testModule.name ? testModule.name.toLowerCase() : null; + if ( testModuleName === module ) { + return true; + } else if ( testModule.parentModule ) { + return testInModuleChain( testModule.parentModule ); + } else { + return false; + } + } + + // Internally-generated tests are always valid + if ( this.callback && this.callback.validTest ) { + return true; + } + + if ( config.testId.length > 0 && inArray( this.testId, config.testId ) < 0 ) { + return false; + } + + if ( module && !testInModuleChain( this.module ) ) { + return false; + } + + if ( !filter ) { + return true; + } + + return regexFilter ? + this.regexFilter( !!regexFilter[1], regexFilter[2], regexFilter[3], fullName ) : + this.stringFilter( filter, fullName ); + }, + + regexFilter: function( exclude, pattern, flags, fullName ) { + var regex = new RegExp( pattern, flags ); + var match = regex.test( fullName ); + + return match !== exclude; + }, + + stringFilter: function( filter, fullName ) { + filter = filter.toLowerCase(); + fullName = fullName.toLowerCase(); + + var include = filter.charAt( 0 ) !== "!"; + if ( !include ) { + filter = filter.slice( 1 ); + } + + // If the filter matches, we need to honour include + if ( fullName.indexOf( filter ) !== -1 ) { + return include; + } + + // Otherwise, do the opposite + return !include; + } +}; + +// Resets the test setup. Useful for tests that modify the DOM. +/* +DEPRECATED: Use multiple tests instead of resetting inside a test. +Use testStart or testDone for custom cleanup. +This method will throw an error in 2.0, and will be removed in 2.1 +*/ +QUnit.reset = function() { + + // Return on non-browser environments + // This is necessary to not break on node tests + if ( !defined.document ) { + return; + } + + var fixture = defined.document && document.getElementById && + document.getElementById( "qunit-fixture" ); + + if ( fixture ) { + fixture.innerHTML = config.fixture; + } +}; + +QUnit.pushFailure = function() { + if ( !QUnit.config.current ) { + throw new Error( "pushFailure() assertion outside test context, in " + + sourceFromStacktrace( 2 ) ); + } + + // Gets current test obj + var currentTest = QUnit.config.current; + + return currentTest.pushFailure.apply( currentTest, arguments ); +}; + +// Based on Java's String.hashCode, a simple but not +// rigorously collision resistant hashing function +function generateHash( module, testName ) { + var hex, + i = 0, + hash = 0, + str = module + "\x1C" + testName, + len = str.length; + + for ( ; i < len; i++ ) { + hash = ( ( hash << 5 ) - hash ) + str.charCodeAt( i ); + hash |= 0; + } + + // Convert the possibly negative integer hash code into an 8 character hex string, which isn't + // strictly necessary but increases user understanding that the id is a SHA-like hash + hex = ( 0x100000000 + hash ).toString( 16 ); + if ( hex.length < 8 ) { + hex = "0000000" + hex; + } + + return hex.slice( -8 ); +} + +function synchronize( callback, priority ) { + var last = !priority; + + if ( QUnit.objectType( callback ) === "array" ) { + while ( callback.length ) { + synchronize( callback.shift() ); + } + return; + } + + if ( priority ) { + config.queue.splice( priorityCount++, 0, callback ); + } else { + config.queue.push( callback ); + } + + if ( config.autorun && !config.blocking ) { + process( last ); + } +} + +function saveGlobal() { + config.pollution = []; + + if ( config.noglobals ) { + for ( var key in global ) { + if ( hasOwn.call( global, key ) ) { + + // in Opera sometimes DOM element ids show up here, ignore them + if ( /^qunit-test-output/.test( key ) ) { + continue; + } + config.pollution.push( key ); + } + } + } +} + +function checkPollution() { + var newGlobals, + deletedGlobals, + old = config.pollution; + + saveGlobal(); + + newGlobals = diff( config.pollution, old ); + if ( newGlobals.length > 0 ) { + QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join( ", " ) ); + } + + deletedGlobals = diff( old, config.pollution ); + if ( deletedGlobals.length > 0 ) { + QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join( ", " ) ); + } +} + +// Will be exposed as QUnit.asyncTest +function asyncTest( testName, expected, callback ) { + if ( arguments.length === 2 ) { + callback = expected; + expected = null; + } + + QUnit.test( testName, expected, callback, true ); +} + +// Will be exposed as QUnit.test +function test( testName, expected, callback, async ) { + if ( focused ) { return; } + + var newTest; + + if ( arguments.length === 2 ) { + callback = expected; + expected = null; + } + + newTest = new Test({ + testName: testName, + expected: expected, + async: async, + callback: callback + }); + + newTest.queue(); +} + +// Will be exposed as QUnit.skip +function skip( testName ) { + if ( focused ) { return; } + + var test = new Test({ + testName: testName, + skip: true + }); + + test.queue(); +} + +// Will be exposed as QUnit.only +function only( testName, expected, callback, async ) { + var newTest; + + if ( focused ) { return; } + + QUnit.config.queue.length = 0; + focused = true; + + if ( arguments.length === 2 ) { + callback = expected; + expected = null; + } + + newTest = new Test({ + testName: testName, + expected: expected, + async: async, + callback: callback + }); + + newTest.queue(); +} + +function Assert( testContext ) { + this.test = testContext; +} + +// Assert helpers +QUnit.assert = Assert.prototype = { + + // Specify the number of expected assertions to guarantee that failed test + // (no assertions are run at all) don't slip through. + expect: function( asserts ) { + if ( arguments.length === 1 ) { + this.test.expected = asserts; + } else { + return this.test.expected; + } + }, + + // Increment this Test's semaphore counter, then return a function that + // decrements that counter a maximum of once. + async: function( count ) { + var test = this.test, + popped = false, + acceptCallCount = count; + + if ( typeof acceptCallCount === "undefined" ) { + acceptCallCount = 1; + } + + test.semaphore += 1; + test.usedAsync = true; + pauseProcessing(); + + return function done() { + + if ( popped ) { + test.pushFailure( "Too many calls to the `assert.async` callback", + sourceFromStacktrace( 2 ) ); + return; + } + acceptCallCount -= 1; + if ( acceptCallCount > 0 ) { + return; + } + + test.semaphore -= 1; + popped = true; + resumeProcessing(); + }; + }, + + // Exports test.push() to the user API + // Alias of pushResult. + push: function( result, actual, expected, message, negative ) { + var currentAssert = this instanceof Assert ? this : QUnit.config.current.assert; + return currentAssert.pushResult( { + result: result, + actual: actual, + expected: expected, + message: message, + negative: negative + } ); + }, + + pushResult: function( resultInfo ) { + + // resultInfo = { result, actual, expected, message, negative } + var assert = this, + currentTest = ( assert instanceof Assert && assert.test ) || QUnit.config.current; + + // Backwards compatibility fix. + // Allows the direct use of global exported assertions and QUnit.assert.* + // Although, it's use is not recommended as it can leak assertions + // to other tests from async tests, because we only get a reference to the current test, + // not exactly the test where assertion were intended to be called. + if ( !currentTest ) { + throw new Error( "assertion outside test context, in " + sourceFromStacktrace( 2 ) ); + } + + if ( currentTest.usedAsync === true && currentTest.semaphore === 0 ) { + currentTest.pushFailure( "Assertion after the final `assert.async` was resolved", + sourceFromStacktrace( 2 ) ); + + // Allow this assertion to continue running anyway... + } + + if ( !( assert instanceof Assert ) ) { + assert = currentTest.assert; + } + + return assert.test.pushResult( resultInfo ); + }, + + ok: function( result, message ) { + message = message || ( result ? "okay" : "failed, expected argument to be truthy, was: " + + QUnit.dump.parse( result ) ); + this.pushResult( { + result: !!result, + actual: result, + expected: true, + message: message + } ); + }, + + notOk: function( result, message ) { + message = message || ( !result ? "okay" : "failed, expected argument to be falsy, was: " + + QUnit.dump.parse( result ) ); + this.pushResult( { + result: !result, + actual: result, + expected: false, + message: message + } ); + }, + + equal: function( actual, expected, message ) { + /*jshint eqeqeq:false */ + this.pushResult( { + result: expected == actual, + actual: actual, + expected: expected, + message: message + } ); + }, + + notEqual: function( actual, expected, message ) { + /*jshint eqeqeq:false */ + this.pushResult( { + result: expected != actual, + actual: actual, + expected: expected, + message: message, + negative: true + } ); + }, + + propEqual: function( actual, expected, message ) { + actual = objectValues( actual ); + expected = objectValues( expected ); + this.pushResult( { + result: QUnit.equiv( actual, expected ), + actual: actual, + expected: expected, + message: message + } ); + }, - /** - * @name notPropEqual - * @function - */ notPropEqual: function( actual, expected, message ) { - actual = objectValues(actual); - expected = objectValues(expected); - QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message ); + actual = objectValues( actual ); + expected = objectValues( expected ); + this.pushResult( { + result: !QUnit.equiv( actual, expected ), + actual: actual, + expected: expected, + message: message, + negative: true + } ); }, - /** - * @name deepEqual - * @function - */ deepEqual: function( actual, expected, message ) { - QUnit.push( QUnit.equiv(actual, expected), actual, expected, message ); + this.pushResult( { + result: QUnit.equiv( actual, expected ), + actual: actual, + expected: expected, + message: message + } ); }, - /** - * @name notDeepEqual - * @function - */ notDeepEqual: function( actual, expected, message ) { - QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message ); + this.pushResult( { + result: !QUnit.equiv( actual, expected ), + actual: actual, + expected: expected, + message: message, + negative: true + } ); }, - /** - * @name strictEqual - * @function - */ strictEqual: function( actual, expected, message ) { - QUnit.push( expected === actual, actual, expected, message ); + this.pushResult( { + result: expected === actual, + actual: actual, + expected: expected, + message: message + } ); }, - /** - * @name notStrictEqual - * @function - */ notStrictEqual: function( actual, expected, message ) { - QUnit.push( expected !== actual, actual, expected, message ); + this.pushResult( { + result: expected !== actual, + actual: actual, + expected: expected, + message: message, + negative: true + } ); }, "throws": function( block, expected, message ) { - var actual, + var actual, expectedType, expectedOutput = expected, - ok = false; + ok = false, + currentTest = ( this instanceof Assert && this.test ) || QUnit.config.current; - // 'expected' is optional - if ( !message && typeof expected === "string" ) { + // 'expected' is optional unless doing string comparison + if ( message == null && typeof expected === "string" ) { message = expected; expected = null; } - config.current.ignoreGlobalErrors = true; + currentTest.ignoreGlobalErrors = true; try { - block.call( config.current.testEnvironment ); + block.call( currentTest.testEnvironment ); } catch (e) { actual = e; } - config.current.ignoreGlobalErrors = false; + currentTest.ignoreGlobalErrors = false; if ( actual ) { + expectedType = QUnit.objectType( expected ); // we don't want to validate thrown error if ( !expected ) { ok = true; expectedOutput = null; - // expected is an Error object - } else if ( expected instanceof Error ) { - ok = actual instanceof Error && - actual.name === expected.name && - actual.message === expected.message; - // expected is a regexp - } else if ( QUnit.objectType( expected ) === "regexp" ) { + } else if ( expectedType === "regexp" ) { ok = expected.test( errorString( actual ) ); // expected is a string - } else if ( QUnit.objectType( expected ) === "string" ) { + } else if ( expectedType === "string" ) { ok = expected === errorString( actual ); - // expected is a constructor - } else if ( actual instanceof expected ) { + // expected is a constructor, maybe an Error constructor + } else if ( expectedType === "function" && actual instanceof expected ) { ok = true; - // expected is a validation function which returns true is validation passed - } else if ( expected.call( {}, actual ) === true ) { + // expected is an Error object + } else if ( expectedType === "object" ) { + ok = actual instanceof expected.constructor && + actual.name === expected.name && + actual.message === expected.message; + + // expected is a validation function which returns true if validation passed + } else if ( expectedType === "function" && expected.call( {}, actual ) === true ) { expectedOutput = null; ok = true; } - - QUnit.push( ok, actual, expectedOutput, message ); - } else { - QUnit.pushFailure( message, null, "No exception was thrown." ); } + + currentTest.assert.pushResult( { + result: ok, + actual: actual, + expected: expectedOutput, + message: message + } ); } }; -/** - * @deprecated since 1.8.0 - * Kept assertion helpers in root for backwards compatibility. - */ -extend( QUnit.constructor.prototype, assert ); - -/** - * @deprecated since 1.9.0 - * Kept to avoid TypeErrors for undefined methods. - */ -QUnit.constructor.prototype.raises = function() { - QUnit.push( false, false, false, "QUnit.raises has been deprecated since 2012 (fad3c1ea), use QUnit.throws instead" ); -}; +// Provide an alternative to assert.throws(), for environments that consider throws a reserved word +// Known to us are: Closure Compiler, Narwhal +(function() { + /*jshint sub:true */ + Assert.prototype.raises = Assert.prototype[ "throws" ]; +}()); -/** - * @deprecated since 1.0.0, replaced with error pushes since 1.3.0 - * Kept to avoid TypeErrors for undefined methods. - */ -QUnit.constructor.prototype.equals = function() { - QUnit.push( false, false, false, "QUnit.equals has been deprecated since 2009 (e88049a0), use QUnit.equal instead" ); -}; -QUnit.constructor.prototype.same = function() { - QUnit.push( false, false, false, "QUnit.same has been deprecated since 2009 (e88049a0), use QUnit.deepEqual instead" ); -}; +function errorString( error ) { + var name, message, + resultErrorString = error.toString(); + if ( resultErrorString.substring( 0, 7 ) === "[object" ) { + name = error.name ? error.name.toString() : "Error"; + message = error.message ? error.message.toString() : ""; + if ( name && message ) { + return name + ": " + message; + } else if ( name ) { + return name; + } else if ( message ) { + return message; + } else { + return "Error"; + } + } else { + return resultErrorString; + } +} // Test for equality any JavaScript type. // Author: Philippe Rathé QUnit.equiv = (function() { - // Call the o related callback with the given arguments. - function bindCallbacks( o, callbacks, args ) { - var prop = QUnit.objectType( o ); - if ( prop ) { - if ( QUnit.objectType( callbacks[ prop ] ) === "function" ) { - return callbacks[ prop ].apply( callbacks, args ); - } else { - return callbacks[ prop ]; // or undefined - } + // Stack to decide between skip/abort functions + var callers = []; + + // Stack to avoiding loops from circular referencing + var parents = []; + var parentsB = []; + + var getProto = Object.getPrototypeOf || function( obj ) { + + /*jshint proto: true */ + return obj.__proto__; + }; + + function useStrictEquality( b, a ) { + + // To catch short annotation VS 'new' annotation of a declaration. e.g.: + // `var i = 1;` + // `var j = new Number(1);` + if ( typeof a === "object" ) { + a = a.valueOf(); + } + if ( typeof b === "object" ) { + b = b.valueOf(); } + + return a === b; } - // the real equiv function - var innerEquiv, - // stack to decide between skip/abort functions - callers = [], - // stack to avoiding loops from circular referencing - parents = [], - parentsB = [], + function compareConstructors( a, b ) { + var protoA = getProto( a ); + var protoB = getProto( b ); - getProto = Object.getPrototypeOf || function ( obj ) { - /*jshint camelcase:false */ - return obj.__proto__; - }, - callbacks = (function () { + // Comparing constructors is more strict than using `instanceof` + if ( a.constructor === b.constructor ) { + return true; + } - // for string, boolean, number and null - function useStrictEquality( b, a ) { - /*jshint eqeqeq:false */ - if ( b instanceof a.constructor || a instanceof b.constructor ) { - // to catch short annotation VS 'new' annotation of a - // declaration - // e.g. var i = 1; - // var j = new Number(1); - return a == b; - } else { - return a === b; - } - } + // Ref #851 + // If the obj prototype descends from a null constructor, treat it + // as a null prototype. + if ( protoA && protoA.constructor === null ) { + protoA = null; + } + if ( protoB && protoB.constructor === null ) { + protoB = null; + } - return { - "string": useStrictEquality, - "boolean": useStrictEquality, - "number": useStrictEquality, - "null": useStrictEquality, - "undefined": useStrictEquality, + // Allow objects with no prototype to be equivalent to + // objects with Object as their constructor. + if ( ( protoA === null && protoB === Object.prototype ) || + ( protoB === null && protoA === Object.prototype ) ) { + return true; + } - "nan": function( b ) { - return isNaN( b ); - }, + return false; + } - "date": function( b, a ) { - return QUnit.objectType( b ) === "date" && a.valueOf() === b.valueOf(); - }, + function getRegExpFlags( regexp ) { + return "flags" in regexp ? regexp.flags : regexp.toString().match( /[gimuy]*$/ )[ 0 ]; + } - "regexp": function( b, a ) { - return QUnit.objectType( b ) === "regexp" && - // the regex itself - a.source === b.source && - // and its modifiers - a.global === b.global && - // (gmi) ... - a.ignoreCase === b.ignoreCase && - a.multiline === b.multiline && - a.sticky === b.sticky; - }, + var callbacks = { + "string": useStrictEquality, + "boolean": useStrictEquality, + "number": useStrictEquality, + "null": useStrictEquality, + "undefined": useStrictEquality, + "symbol": useStrictEquality, + "date": useStrictEquality, - // - skip when the property is a method of an instance (OOP) - // - abort otherwise, - // initial === would have catch identical references anyway - "function": function() { - var caller = callers[callers.length - 1]; - return caller !== Object && typeof caller !== "undefined"; - }, + "nan": function() { + return true; + }, - "array": function( b, a ) { - var i, j, len, loop, aCircular, bCircular; + "regexp": function( b, a ) { + return a.source === b.source && - // b could be an object literal here - if ( QUnit.objectType( b ) !== "array" ) { - return false; - } - - len = a.length; - if ( len !== b.length ) { - // safe and faster - return false; - } - - // track reference to avoid circular references - parents.push( a ); - parentsB.push( b ); - for ( i = 0; i < len; i++ ) { - loop = false; - for ( j = 0; j < parents.length; j++ ) { - aCircular = parents[j] === a[i]; - bCircular = parentsB[j] === b[i]; - if ( aCircular || bCircular ) { - if ( a[i] === b[i] || aCircular && bCircular ) { - loop = true; - } else { - parents.pop(); - parentsB.pop(); - return false; - } - } - } - if ( !loop && !innerEquiv(a[i], b[i]) ) { + // Include flags in the comparison + getRegExpFlags( a ) === getRegExpFlags( b ); + }, + + // - skip when the property is a method of an instance (OOP) + // - abort otherwise, + // initial === would have catch identical references anyway + "function": function() { + var caller = callers[ callers.length - 1 ]; + return caller !== Object && typeof caller !== "undefined"; + }, + + "array": function( b, a ) { + var i, j, len, loop, aCircular, bCircular; + + len = a.length; + if ( len !== b.length ) { + // safe and faster + return false; + } + + // Track reference to avoid circular references + parents.push( a ); + parentsB.push( b ); + for ( i = 0; i < len; i++ ) { + loop = false; + for ( j = 0; j < parents.length; j++ ) { + aCircular = parents[ j ] === a[ i ]; + bCircular = parentsB[ j ] === b[ i ]; + if ( aCircular || bCircular ) { + if ( a[ i ] === b[ i ] || aCircular && bCircular ) { + loop = true; + } else { parents.pop(); parentsB.pop(); return false; } } + } + if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) { parents.pop(); parentsB.pop(); - return true; - }, + return false; + } + } + parents.pop(); + parentsB.pop(); + return true; + }, - "object": function( b, a ) { - /*jshint forin:false */ - var i, j, loop, aCircular, bCircular, - // Default to true - eq = true, - aProperties = [], - bProperties = []; - - // comparing constructors is more strict than using - // instanceof - if ( a.constructor !== b.constructor ) { - // Allow objects with no prototype to be equivalent to - // objects with Object as their constructor. - if ( !(( getProto(a) === null && getProto(b) === Object.prototype ) || - ( getProto(b) === null && getProto(a) === Object.prototype ) ) ) { - return false; - } - } + "set": function( b, a ) { + var aArray, bArray; - // stack constructor before traversing properties - callers.push( a.constructor ); + aArray = []; + a.forEach( function( v ) { + aArray.push( v ); + }); + bArray = []; + b.forEach( function( v ) { + bArray.push( v ); + }); - // track reference to avoid circular references - parents.push( a ); - parentsB.push( b ); - - // be strict: don't ensure hasOwnProperty and go deep - for ( i in a ) { - loop = false; - for ( j = 0; j < parents.length; j++ ) { - aCircular = parents[j] === a[i]; - bCircular = parentsB[j] === b[i]; - if ( aCircular || bCircular ) { - if ( a[i] === b[i] || aCircular && bCircular ) { - loop = true; - } else { - eq = false; - break; - } - } - } - aProperties.push(i); - if ( !loop && !innerEquiv(a[i], b[i]) ) { + return innerEquiv( bArray, aArray ); + }, + + "map": function( b, a ) { + var aArray, bArray; + + aArray = []; + a.forEach( function( v, k ) { + aArray.push( [ k, v ] ); + }); + bArray = []; + b.forEach( function( v, k ) { + bArray.push( [ k, v ] ); + }); + + return innerEquiv( bArray, aArray ); + }, + + "object": function( b, a ) { + var i, j, loop, aCircular, bCircular; + + // Default to true + var eq = true; + var aProperties = []; + var bProperties = []; + + if ( compareConstructors( a, b ) === false ) { + return false; + } + + // Stack constructor before traversing properties + callers.push( a.constructor ); + + // Track reference to avoid circular references + parents.push( a ); + parentsB.push( b ); + + // Be strict: don't ensure hasOwnProperty and go deep + for ( i in a ) { + loop = false; + for ( j = 0; j < parents.length; j++ ) { + aCircular = parents[ j ] === a[ i ]; + bCircular = parentsB[ j ] === b[ i ]; + if ( aCircular || bCircular ) { + if ( a[ i ] === b[ i ] || aCircular && bCircular ) { + loop = true; + } else { eq = false; break; } } + } + aProperties.push( i ); + if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) { + eq = false; + break; + } + } - parents.pop(); - parentsB.pop(); - callers.pop(); // unstack, we are done - - for ( i in b ) { - bProperties.push( i ); // collect b's properties - } + parents.pop(); + parentsB.pop(); - // Ensures identical properties name - return eq && innerEquiv( aProperties.sort(), bProperties.sort() ); - } - }; - }()); + // Unstack, we are done + callers.pop(); - innerEquiv = function() { // can take multiple arguments - var args = [].slice.apply( arguments ); - if ( args.length < 2 ) { - return true; // end transition - } + for ( i in b ) { - return (function( a, b ) { - if ( a === b ) { - return true; // catch the most you can - } else if ( a === null || b === null || typeof a === "undefined" || - typeof b === "undefined" || - QUnit.objectType(a) !== QUnit.objectType(b) ) { - return false; // don't lose time with error prone cases - } else { - return bindCallbacks(a, callbacks, [ b, a ]); + // Collect b's properties + bProperties.push( i ); } - // apply transition with (1..n) arguments - }( args[0], args[1] ) && innerEquiv.apply( this, args.splice(1, args.length - 1 )) ); + // Ensures identical properties name + return eq && innerEquiv( aProperties.sort(), bProperties.sort() ); + } }; + function typeEquiv( a, b ) { + var type = QUnit.objectType( a ); + return QUnit.objectType( b ) === type && callbacks[ type ]( b, a ); + } + + // The real equiv function + function innerEquiv( a, b ) { + + // We're done when there's nothing more to compare + if ( arguments.length < 2 ) { + return true; + } + + // Require type-specific equality + return ( a === b || typeEquiv( a, b ) ) && + + // ...across all consecutive argument pairs + ( arguments.length === 2 || innerEquiv.apply( this, [].slice.call( arguments, 1 ) ) ); + } + return innerEquiv; }()); -/** - * jsDump Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | - * http://flesler.blogspot.com Licensed under BSD - * (http://www.opensource.org/licenses/bsd-license.php) Date: 5/15/2008 - * - * @projectDescription Advanced and extensible data dumping for Javascript. - * @version 1.0.0 - * @author Ariel Flesler - * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html} - */ -QUnit.jsDump = (function() { +// Based on jsDump by Ariel Flesler +// http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html +QUnit.dump = (function() { function quote( str ) { - return "\"" + str.toString().replace( /"/g, "\\\"" ) + "\""; + return "\"" + str.toString().replace( /\\/g, "\\\\" ).replace( /"/g, "\\\"" ) + "\""; } function literal( o ) { return o + ""; } function join( pre, arr, post ) { - var s = jsDump.separator(), - base = jsDump.indent(), - inner = jsDump.indent(1); + var s = dump.separator(), + base = dump.indent(), + inner = dump.indent( 1 ); if ( arr.join ) { arr = arr.join( "," + s + inner ); } if ( !arr ) { return pre + post; } - return [ pre, inner + arr, base + post ].join(s); + return [ pre, inner + arr, base + post ].join( s ); } function array( arr, stack ) { - var i = arr.length, ret = new Array(i); + var i = arr.length, + ret = new Array( i ); + + if ( dump.maxDepth && dump.depth > dump.maxDepth ) { + return "[object Array]"; + } + this.up(); while ( i-- ) { - ret[i] = this.parse( arr[i] , undefined , stack); + ret[ i ] = this.parse( arr[ i ], undefined, stack ); } this.down(); return join( "[", ret, "]" ); } var reName = /^function (\w+)/, - jsDump = { - // type is used mostly internally, you can fix a (custom)type in advance - parse: function( obj, type, stack ) { - stack = stack || [ ]; - var inStack, res, - parser = this.parsers[ type || this.typeOf(obj) ]; + dump = { - type = typeof parser; - inStack = inArray( obj, stack ); + // objType is used mostly internally, you can fix a (custom) type in advance + parse: function( obj, objType, stack ) { + stack = stack || []; + var res, parser, parserType, + inStack = inArray( obj, stack ); if ( inStack !== -1 ) { - return "recursion(" + (inStack - stack.length) + ")"; + return "recursion(" + ( inStack - stack.length ) + ")"; } - if ( type === "function" ) { + + objType = objType || this.typeOf( obj ); + parser = this.parsers[ objType ]; + parserType = typeof parser; + + if ( parserType === "function" ) { stack.push( obj ); res = parser.call( this, obj, stack ); stack.pop(); return res; } - return ( type === "string" ) ? parser : this.parsers.error; + return ( parserType === "string" ) ? parser : this.parsers.error; }, typeOf: function( obj ) { var type; @@ -1958,23 +1978,29 @@ type = "null"; } else if ( typeof obj === "undefined" ) { type = "undefined"; - } else if ( QUnit.is( "regexp", obj) ) { + } else if ( QUnit.is( "regexp", obj ) ) { type = "regexp"; - } else if ( QUnit.is( "date", obj) ) { + } else if ( QUnit.is( "date", obj ) ) { type = "date"; - } else if ( QUnit.is( "function", obj) ) { + } else if ( QUnit.is( "function", obj ) ) { type = "function"; - } else if ( typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined" ) { + } else if ( obj.setInterval !== undefined && + obj.document !== undefined && + obj.nodeType === undefined ) { type = "window"; } else if ( obj.nodeType === 9 ) { type = "document"; } else if ( obj.nodeType ) { type = "node"; } else if ( + // native arrays toString.call( obj ) === "[object Array]" || + // NodeList objects - ( typeof obj.length === "number" && typeof obj.item !== "undefined" && ( obj.length ? obj.item(0) === obj[0] : ( obj.item( 0 ) === null && typeof obj[0] === "undefined" ) ) ) + ( typeof obj.length === "number" && obj.item !== undefined && + ( obj.length ? obj.item( 0 ) === obj[ 0 ] : ( obj.item( 0 ) === null && + obj[ 0 ] === undefined ) ) ) ) { type = "array"; } else if ( obj.constructor === Error.prototype.constructor ) { @@ -1985,7 +2011,7 @@ return type; }, separator: function() { - return this.multiline ? this.HTML ? "
    " : "\n" : this.HTML ? " " : " "; + return this.multiline ? this.HTML ? "
    " : "\n" : this.HTML ? " " : " "; }, // extra can be a number, shortcut for increasing-calling-decreasing indent: function( extra ) { @@ -1994,9 +2020,9 @@ } var chr = this.indentChar; if ( this.HTML ) { - chr = chr.replace( /\t/g, " " ).replace( / /g, " " ); + chr = chr.replace( /\t/g, " " ).replace( / /g, " " ); } - return new Array( this.depth + ( extra || 0 ) ).join(chr); + return new Array( this.depth + ( extra || 0 ) ).join( chr ); }, up: function( a ) { this.depth += a || 1; @@ -2005,7 +2031,7 @@ this.depth -= a || 1; }, setParser: function( name, parser ) { - this.parsers[name] = parser; + this.parsers[ name ] = parser; }, // The next 3 are exposed so you can use them quote: quote, @@ -2013,11 +2039,13 @@ join: join, // depth: 1, - // This is the list of parsers, to modify them, use jsDump.setParser + maxDepth: QUnit.config.maxDepth, + + // This is the list of parsers, to modify them, use dump.setParser parsers: { window: "[Window]", document: "[Document]", - error: function(error) { + error: function( error ) { return "Error(\"" + error.message + "\")"; }, unknown: "[Unknown]", @@ -2025,52 +2053,71 @@ "undefined": "undefined", "function": function( fn ) { var ret = "function", + // functions never have name in IE - name = "name" in fn ? fn.name : (reName.exec(fn) || [])[1]; + name = "name" in fn ? fn.name : ( reName.exec( fn ) || [] )[ 1 ]; if ( name ) { ret += " " + name; } ret += "( "; - ret = [ ret, QUnit.jsDump.parse( fn, "functionArgs" ), "){" ].join( "" ); - return join( ret, QUnit.jsDump.parse(fn,"functionCode" ), "}" ); + ret = [ ret, dump.parse( fn, "functionArgs" ), "){" ].join( "" ); + return join( ret, dump.parse( fn, "functionCode" ), "}" ); }, array: array, nodelist: array, "arguments": array, object: function( map, stack ) { - /*jshint forin:false */ - var ret = [ ], keys, key, val, i; - QUnit.jsDump.up(); + var keys, key, val, i, nonEnumerableProperties, + ret = []; + + if ( dump.maxDepth && dump.depth > dump.maxDepth ) { + return "[object Object]"; + } + + dump.up(); keys = []; for ( key in map ) { keys.push( key ); } + + // Some properties are not always enumerable on Error objects. + nonEnumerableProperties = [ "message", "name" ]; + for ( i in nonEnumerableProperties ) { + key = nonEnumerableProperties[ i ]; + if ( key in map && inArray( key, keys ) < 0 ) { + keys.push( key ); + } + } keys.sort(); for ( i = 0; i < keys.length; i++ ) { key = keys[ i ]; val = map[ key ]; - ret.push( QUnit.jsDump.parse( key, "key" ) + ": " + QUnit.jsDump.parse( val, undefined, stack ) ); + ret.push( dump.parse( key, "key" ) + ": " + + dump.parse( val, undefined, stack ) ); } - QUnit.jsDump.down(); + dump.down(); return join( "{", ret, "}" ); }, node: function( node ) { var len, i, val, - open = QUnit.jsDump.HTML ? "<" : "<", - close = QUnit.jsDump.HTML ? ">" : ">", + open = dump.HTML ? "<" : "<", + close = dump.HTML ? ">" : ">", tag = node.nodeName.toLowerCase(), ret = open + tag, attrs = node.attributes; if ( attrs ) { for ( i = 0, len = attrs.length; i < len; i++ ) { - val = attrs[i].nodeValue; - // IE6 includes all attributes in .attributes, even ones not explicitly set. - // Those have values like undefined, null, 0, false, "" or "inherit". + val = attrs[ i ].nodeValue; + + // IE6 includes all attributes in .attributes, even ones not explicitly + // set. Those have values like undefined, null, 0, false, "" or + // "inherit". if ( val && val !== "inherit" ) { - ret += " " + attrs[i].nodeName + "=" + QUnit.jsDump.parse( val, "attribute" ); + ret += " " + attrs[ i ].nodeName + "=" + + dump.parse( val, "attribute" ); } } } @@ -2083,6 +2130,7 @@ return ret + open + "/" + tag + close; }, + // function calls it internally, it's the arguments part of the function functionArgs: function( fn ) { var args, @@ -2092,10 +2140,11 @@ return ""; } - args = new Array(l); + args = new Array( l ); while ( l-- ) { + // 97 is 'a' - args[l] = String.fromCharCode(97+l); + args[ l ] = String.fromCharCode( 97 + l ); } return " " + args.join( ", " ) + " "; }, @@ -2103,7 +2152,7 @@ key: quote, // function calls it internally, it's the content of the function functionCode: "[code]", - // node calls it internally, it's an html attribute value + // node calls it internally, it's a html attribute value attribute: quote, string: quote, date: quote, @@ -2119,170 +2168,2037 @@ multiline: true }; - return jsDump; + return dump; }()); +// back compat +QUnit.jsDump = QUnit.dump; + +// Deprecated +// Extend assert methods to QUnit for Backwards compatibility +(function() { + var i, + assertions = Assert.prototype; + + function applyCurrent( current ) { + return function() { + var assert = new Assert( QUnit.config.current ); + current.apply( assert, arguments ); + }; + } + + for ( i in assertions ) { + QUnit[ i ] = applyCurrent( assertions[ i ] ); + } +})(); + +// For browser, export only select globals +if ( defined.document ) { + + (function() { + var i, l, + keys = [ + "test", + "module", + "expect", + "asyncTest", + "start", + "stop", + "ok", + "notOk", + "equal", + "notEqual", + "propEqual", + "notPropEqual", + "deepEqual", + "notDeepEqual", + "strictEqual", + "notStrictEqual", + "throws", + "raises" + ]; + + for ( i = 0, l = keys.length; i < l; i++ ) { + window[ keys[ i ] ] = QUnit[ keys[ i ] ]; + } + })(); + + window.QUnit = QUnit; +} + +// For nodejs +if ( typeof module !== "undefined" && module && module.exports ) { + module.exports = QUnit; + + // For consistency with CommonJS environments' exports + module.exports.QUnit = QUnit; +} + +// For CommonJS with exports, but without module.exports, like Rhino +if ( typeof exports !== "undefined" && exports ) { + exports.QUnit = QUnit; +} + +if ( typeof define === "function" && define.amd ) { + define( function() { + return QUnit; + } ); + QUnit.config.autostart = false; +} + /* - * Javascript Diff Algorithm - * By John Resig (http://ejohn.org/) - * Modified by Chu Alan "sprite" + * This file is a modified version of google-diff-match-patch's JavaScript implementation + * (https://code.google.com/p/google-diff-match-patch/source/browse/trunk/javascript/diff_match_patch_uncompressed.js), + * modifications are licensed as more fully set forth in LICENSE.txt. + * + * The original source of google-diff-match-patch is attributable and licensed as follows: + * + * Copyright 2006 Google Inc. + * https://code.google.com/p/google-diff-match-patch/ * - * Released under the MIT license. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. * * More Info: - * http://ejohn.org/projects/javascript-diff-algorithm/ + * https://code.google.com/p/google-diff-match-patch/ * * Usage: QUnit.diff(expected, actual) * - * QUnit.diff( "the quick brown fox jumped over", "the quick fox jumps over" ) == "the quick brown fox jumped jumps over" */ -QUnit.diff = (function() { - /*jshint eqeqeq:false, eqnull:true */ - function diff( o, n ) { - var i, - ns = {}, - os = {}; +QUnit.diff = ( function() { + function DiffMatchPatch() { + } - for ( i = 0; i < n.length; i++ ) { - if ( !hasOwn.call( ns, n[i] ) ) { - ns[ n[i] ] = { - rows: [], - o: null - }; - } - ns[ n[i] ].rows.push( i ); - } - - for ( i = 0; i < o.length; i++ ) { - if ( !hasOwn.call( os, o[i] ) ) { - os[ o[i] ] = { - rows: [], - n: null - }; - } - os[ o[i] ].rows.push( i ); - } - - for ( i in ns ) { - if ( hasOwn.call( ns, i ) ) { - if ( ns[i].rows.length === 1 && hasOwn.call( os, i ) && os[i].rows.length === 1 ) { - n[ ns[i].rows[0] ] = { - text: n[ ns[i].rows[0] ], - row: os[i].rows[0] - }; - o[ os[i].rows[0] ] = { - text: o[ os[i].rows[0] ], - row: ns[i].rows[0] - }; - } - } - } - - for ( i = 0; i < n.length - 1; i++ ) { - if ( n[i].text != null && n[ i + 1 ].text == null && n[i].row + 1 < o.length && o[ n[i].row + 1 ].text == null && - n[ i + 1 ] == o[ n[i].row + 1 ] ) { - - n[ i + 1 ] = { - text: n[ i + 1 ], - row: n[i].row + 1 - }; - o[ n[i].row + 1 ] = { - text: o[ n[i].row + 1 ], - row: i + 1 - }; - } - } - - for ( i = n.length - 1; i > 0; i-- ) { - if ( n[i].text != null && n[ i - 1 ].text == null && n[i].row > 0 && o[ n[i].row - 1 ].text == null && - n[ i - 1 ] == o[ n[i].row - 1 ]) { - - n[ i - 1 ] = { - text: n[ i - 1 ], - row: n[i].row - 1 - }; - o[ n[i].row - 1 ] = { - text: o[ n[i].row - 1 ], - row: i - 1 - }; + // DIFF FUNCTIONS + + /** + * The data structure representing a diff is an array of tuples: + * [[DIFF_DELETE, 'Hello'], [DIFF_INSERT, 'Goodbye'], [DIFF_EQUAL, ' world.']] + * which means: delete 'Hello', add 'Goodbye' and keep ' world.' + */ + var DIFF_DELETE = -1, + DIFF_INSERT = 1, + DIFF_EQUAL = 0; + + /** + * Find the differences between two texts. Simplifies the problem by stripping + * any common prefix or suffix off the texts before diffing. + * @param {string} text1 Old string to be diffed. + * @param {string} text2 New string to be diffed. + * @param {boolean=} optChecklines Optional speedup flag. If present and false, + * then don't run a line-level diff first to identify the changed areas. + * Defaults to true, which does a faster, slightly less optimal diff. + * @return {!Array.} Array of diff tuples. + */ + DiffMatchPatch.prototype.DiffMain = function( text1, text2, optChecklines ) { + var deadline, checklines, commonlength, + commonprefix, commonsuffix, diffs; + + // The diff must be complete in up to 1 second. + deadline = ( new Date() ).getTime() + 1000; + + // Check for null inputs. + if ( text1 === null || text2 === null ) { + throw new Error( "Null input. (DiffMain)" ); + } + + // Check for equality (speedup). + if ( text1 === text2 ) { + if ( text1 ) { + return [ + [ DIFF_EQUAL, text1 ] + ]; } + return []; } - return { - o: o, - n: n - }; - } + if ( typeof optChecklines === "undefined" ) { + optChecklines = true; + } - return function( o, n ) { - o = o.replace( /\s+$/, "" ); - n = n.replace( /\s+$/, "" ); + checklines = optChecklines; + + // Trim off common prefix (speedup). + commonlength = this.diffCommonPrefix( text1, text2 ); + commonprefix = text1.substring( 0, commonlength ); + text1 = text1.substring( commonlength ); + text2 = text2.substring( commonlength ); + + // Trim off common suffix (speedup). + commonlength = this.diffCommonSuffix( text1, text2 ); + commonsuffix = text1.substring( text1.length - commonlength ); + text1 = text1.substring( 0, text1.length - commonlength ); + text2 = text2.substring( 0, text2.length - commonlength ); + + // Compute the diff on the middle block. + diffs = this.diffCompute( text1, text2, checklines, deadline ); + + // Restore the prefix and suffix. + if ( commonprefix ) { + diffs.unshift( [ DIFF_EQUAL, commonprefix ] ); + } + if ( commonsuffix ) { + diffs.push( [ DIFF_EQUAL, commonsuffix ] ); + } + this.diffCleanupMerge( diffs ); + return diffs; + }; + + /** + * Reduce the number of edits by eliminating operationally trivial equalities. + * @param {!Array.} diffs Array of diff tuples. + */ + DiffMatchPatch.prototype.diffCleanupEfficiency = function( diffs ) { + var changes, equalities, equalitiesLength, lastequality, + pointer, preIns, preDel, postIns, postDel; + changes = false; + equalities = []; // Stack of indices where equalities are found. + equalitiesLength = 0; // Keeping our own length var is faster in JS. + /** @type {?string} */ + lastequality = null; + // Always equal to diffs[equalities[equalitiesLength - 1]][1] + pointer = 0; // Index of current position. + // Is there an insertion operation before the last equality. + preIns = false; + // Is there a deletion operation before the last equality. + preDel = false; + // Is there an insertion operation after the last equality. + postIns = false; + // Is there a deletion operation after the last equality. + postDel = false; + while ( pointer < diffs.length ) { + + // Equality found. + if ( diffs[ pointer ][ 0 ] === DIFF_EQUAL ) { + if ( diffs[ pointer ][ 1 ].length < 4 && ( postIns || postDel ) ) { + + // Candidate found. + equalities[ equalitiesLength++ ] = pointer; + preIns = postIns; + preDel = postDel; + lastequality = diffs[ pointer ][ 1 ]; + } else { + + // Not a candidate, and can never become one. + equalitiesLength = 0; + lastequality = null; + } + postIns = postDel = false; + + // An insertion or deletion. + } else { + + if ( diffs[ pointer ][ 0 ] === DIFF_DELETE ) { + postDel = true; + } else { + postIns = true; + } + + /* + * Five types to be split: + * ABXYCD + * AXCD + * ABXC + * AXCD + * ABXC + */ + if ( lastequality && ( ( preIns && preDel && postIns && postDel ) || + ( ( lastequality.length < 2 ) && + ( preIns + preDel + postIns + postDel ) === 3 ) ) ) { + + // Duplicate record. + diffs.splice( + equalities[ equalitiesLength - 1 ], + 0, + [ DIFF_DELETE, lastequality ] + ); + + // Change second copy to insert. + diffs[ equalities[ equalitiesLength - 1 ] + 1 ][ 0 ] = DIFF_INSERT; + equalitiesLength--; // Throw away the equality we just deleted; + lastequality = null; + if ( preIns && preDel ) { + // No changes made which could affect previous entry, keep going. + postIns = postDel = true; + equalitiesLength = 0; + } else { + equalitiesLength--; // Throw away the previous equality. + pointer = equalitiesLength > 0 ? equalities[ equalitiesLength - 1 ] : -1; + postIns = postDel = false; + } + changes = true; + } + } + pointer++; + } + + if ( changes ) { + this.diffCleanupMerge( diffs ); + } + }; + + /** + * Convert a diff array into a pretty HTML report. + * @param {!Array.} diffs Array of diff tuples. + * @param {integer} string to be beautified. + * @return {string} HTML representation. + */ + DiffMatchPatch.prototype.diffPrettyHtml = function( diffs ) { + var op, data, x, + html = []; + for ( x = 0; x < diffs.length; x++ ) { + op = diffs[ x ][ 0 ]; // Operation (insert, delete, equal) + data = diffs[ x ][ 1 ]; // Text of change. + switch ( op ) { + case DIFF_INSERT: + html[ x ] = "" + data + ""; + break; + case DIFF_DELETE: + html[ x ] = "" + data + ""; + break; + case DIFF_EQUAL: + html[ x ] = "" + data + ""; + break; + } + } + return html.join( "" ); + }; + + /** + * Determine the common prefix of two strings. + * @param {string} text1 First string. + * @param {string} text2 Second string. + * @return {number} The number of characters common to the start of each + * string. + */ + DiffMatchPatch.prototype.diffCommonPrefix = function( text1, text2 ) { + var pointermid, pointermax, pointermin, pointerstart; + // Quick check for common null cases. + if ( !text1 || !text2 || text1.charAt( 0 ) !== text2.charAt( 0 ) ) { + return 0; + } + // Binary search. + // Performance analysis: https://neil.fraser.name/news/2007/10/09/ + pointermin = 0; + pointermax = Math.min( text1.length, text2.length ); + pointermid = pointermax; + pointerstart = 0; + while ( pointermin < pointermid ) { + if ( text1.substring( pointerstart, pointermid ) === + text2.substring( pointerstart, pointermid ) ) { + pointermin = pointermid; + pointerstart = pointermin; + } else { + pointermax = pointermid; + } + pointermid = Math.floor( ( pointermax - pointermin ) / 2 + pointermin ); + } + return pointermid; + }; + + /** + * Determine the common suffix of two strings. + * @param {string} text1 First string. + * @param {string} text2 Second string. + * @return {number} The number of characters common to the end of each string. + */ + DiffMatchPatch.prototype.diffCommonSuffix = function( text1, text2 ) { + var pointermid, pointermax, pointermin, pointerend; + // Quick check for common null cases. + if ( !text1 || + !text2 || + text1.charAt( text1.length - 1 ) !== text2.charAt( text2.length - 1 ) ) { + return 0; + } + // Binary search. + // Performance analysis: https://neil.fraser.name/news/2007/10/09/ + pointermin = 0; + pointermax = Math.min( text1.length, text2.length ); + pointermid = pointermax; + pointerend = 0; + while ( pointermin < pointermid ) { + if ( text1.substring( text1.length - pointermid, text1.length - pointerend ) === + text2.substring( text2.length - pointermid, text2.length - pointerend ) ) { + pointermin = pointermid; + pointerend = pointermin; + } else { + pointermax = pointermid; + } + pointermid = Math.floor( ( pointermax - pointermin ) / 2 + pointermin ); + } + return pointermid; + }; + + /** + * Find the differences between two texts. Assumes that the texts do not + * have any common prefix or suffix. + * @param {string} text1 Old string to be diffed. + * @param {string} text2 New string to be diffed. + * @param {boolean} checklines Speedup flag. If false, then don't run a + * line-level diff first to identify the changed areas. + * If true, then run a faster, slightly less optimal diff. + * @param {number} deadline Time when the diff should be complete by. + * @return {!Array.} Array of diff tuples. + * @private + */ + DiffMatchPatch.prototype.diffCompute = function( text1, text2, checklines, deadline ) { + var diffs, longtext, shorttext, i, hm, + text1A, text2A, text1B, text2B, + midCommon, diffsA, diffsB; + + if ( !text1 ) { + // Just add some text (speedup). + return [ + [ DIFF_INSERT, text2 ] + ]; + } + + if ( !text2 ) { + // Just delete some text (speedup). + return [ + [ DIFF_DELETE, text1 ] + ]; + } + + longtext = text1.length > text2.length ? text1 : text2; + shorttext = text1.length > text2.length ? text2 : text1; + i = longtext.indexOf( shorttext ); + if ( i !== -1 ) { + // Shorter text is inside the longer text (speedup). + diffs = [ + [ DIFF_INSERT, longtext.substring( 0, i ) ], + [ DIFF_EQUAL, shorttext ], + [ DIFF_INSERT, longtext.substring( i + shorttext.length ) ] + ]; + // Swap insertions for deletions if diff is reversed. + if ( text1.length > text2.length ) { + diffs[ 0 ][ 0 ] = diffs[ 2 ][ 0 ] = DIFF_DELETE; + } + return diffs; + } + + if ( shorttext.length === 1 ) { + // Single character string. + // After the previous speedup, the character can't be an equality. + return [ + [ DIFF_DELETE, text1 ], + [ DIFF_INSERT, text2 ] + ]; + } + + // Check to see if the problem can be split in two. + hm = this.diffHalfMatch( text1, text2 ); + if ( hm ) { + // A half-match was found, sort out the return data. + text1A = hm[ 0 ]; + text1B = hm[ 1 ]; + text2A = hm[ 2 ]; + text2B = hm[ 3 ]; + midCommon = hm[ 4 ]; + // Send both pairs off for separate processing. + diffsA = this.DiffMain( text1A, text2A, checklines, deadline ); + diffsB = this.DiffMain( text1B, text2B, checklines, deadline ); + // Merge the results. + return diffsA.concat( [ + [ DIFF_EQUAL, midCommon ] + ], diffsB ); + } + + if ( checklines && text1.length > 100 && text2.length > 100 ) { + return this.diffLineMode( text1, text2, deadline ); + } - var i, pre, - str = "", - out = diff( o === "" ? [] : o.split(/\s+/), n === "" ? [] : n.split(/\s+/) ), - oSpace = o.match(/\s+/g), - nSpace = n.match(/\s+/g); + return this.diffBisect( text1, text2, deadline ); + }; - if ( oSpace == null ) { - oSpace = [ " " ]; + /** + * Do the two texts share a substring which is at least half the length of the + * longer text? + * This speedup can produce non-minimal diffs. + * @param {string} text1 First string. + * @param {string} text2 Second string. + * @return {Array.} Five element Array, containing the prefix of + * text1, the suffix of text1, the prefix of text2, the suffix of + * text2 and the common middle. Or null if there was no match. + * @private + */ + DiffMatchPatch.prototype.diffHalfMatch = function( text1, text2 ) { + var longtext, shorttext, dmp, + text1A, text2B, text2A, text1B, midCommon, + hm1, hm2, hm; + + longtext = text1.length > text2.length ? text1 : text2; + shorttext = text1.length > text2.length ? text2 : text1; + if ( longtext.length < 4 || shorttext.length * 2 < longtext.length ) { + return null; // Pointless. } - else { - oSpace.push( " " ); + dmp = this; // 'this' becomes 'window' in a closure. + + /** + * Does a substring of shorttext exist within longtext such that the substring + * is at least half the length of longtext? + * Closure, but does not reference any external variables. + * @param {string} longtext Longer string. + * @param {string} shorttext Shorter string. + * @param {number} i Start index of quarter length substring within longtext. + * @return {Array.} Five element Array, containing the prefix of + * longtext, the suffix of longtext, the prefix of shorttext, the suffix + * of shorttext and the common middle. Or null if there was no match. + * @private + */ + function diffHalfMatchI( longtext, shorttext, i ) { + var seed, j, bestCommon, prefixLength, suffixLength, + bestLongtextA, bestLongtextB, bestShorttextA, bestShorttextB; + // Start with a 1/4 length substring at position i as a seed. + seed = longtext.substring( i, i + Math.floor( longtext.length / 4 ) ); + j = -1; + bestCommon = ""; + while ( ( j = shorttext.indexOf( seed, j + 1 ) ) !== -1 ) { + prefixLength = dmp.diffCommonPrefix( longtext.substring( i ), + shorttext.substring( j ) ); + suffixLength = dmp.diffCommonSuffix( longtext.substring( 0, i ), + shorttext.substring( 0, j ) ); + if ( bestCommon.length < suffixLength + prefixLength ) { + bestCommon = shorttext.substring( j - suffixLength, j ) + + shorttext.substring( j, j + prefixLength ); + bestLongtextA = longtext.substring( 0, i - suffixLength ); + bestLongtextB = longtext.substring( i + prefixLength ); + bestShorttextA = shorttext.substring( 0, j - suffixLength ); + bestShorttextB = shorttext.substring( j + prefixLength ); + } + } + if ( bestCommon.length * 2 >= longtext.length ) { + return [ bestLongtextA, bestLongtextB, + bestShorttextA, bestShorttextB, bestCommon + ]; + } else { + return null; + } } - if ( nSpace == null ) { - nSpace = [ " " ]; + // First check if the second quarter is the seed for a half-match. + hm1 = diffHalfMatchI( longtext, shorttext, + Math.ceil( longtext.length / 4 ) ); + // Check again based on the third quarter. + hm2 = diffHalfMatchI( longtext, shorttext, + Math.ceil( longtext.length / 2 ) ); + if ( !hm1 && !hm2 ) { + return null; + } else if ( !hm2 ) { + hm = hm1; + } else if ( !hm1 ) { + hm = hm2; + } else { + // Both matched. Select the longest. + hm = hm1[ 4 ].length > hm2[ 4 ].length ? hm1 : hm2; } - else { - nSpace.push( " " ); + + // A half-match was found, sort out the return data. + text1A, text1B, text2A, text2B; + if ( text1.length > text2.length ) { + text1A = hm[ 0 ]; + text1B = hm[ 1 ]; + text2A = hm[ 2 ]; + text2B = hm[ 3 ]; + } else { + text2A = hm[ 0 ]; + text2B = hm[ 1 ]; + text1A = hm[ 2 ]; + text1B = hm[ 3 ]; } + midCommon = hm[ 4 ]; + return [ text1A, text1B, text2A, text2B, midCommon ]; + }; - if ( out.n.length === 0 ) { - for ( i = 0; i < out.o.length; i++ ) { - str += "" + out.o[i] + oSpace[i] + ""; + /** + * Do a quick line-level diff on both strings, then rediff the parts for + * greater accuracy. + * This speedup can produce non-minimal diffs. + * @param {string} text1 Old string to be diffed. + * @param {string} text2 New string to be diffed. + * @param {number} deadline Time when the diff should be complete by. + * @return {!Array.} Array of diff tuples. + * @private + */ + DiffMatchPatch.prototype.diffLineMode = function( text1, text2, deadline ) { + var a, diffs, linearray, pointer, countInsert, + countDelete, textInsert, textDelete, j; + // Scan the text on a line-by-line basis first. + a = this.diffLinesToChars( text1, text2 ); + text1 = a.chars1; + text2 = a.chars2; + linearray = a.lineArray; + + diffs = this.DiffMain( text1, text2, false, deadline ); + + // Convert the diff back to original text. + this.diffCharsToLines( diffs, linearray ); + // Eliminate freak matches (e.g. blank lines) + this.diffCleanupSemantic( diffs ); + + // Rediff any replacement blocks, this time character-by-character. + // Add a dummy entry at the end. + diffs.push( [ DIFF_EQUAL, "" ] ); + pointer = 0; + countDelete = 0; + countInsert = 0; + textDelete = ""; + textInsert = ""; + while ( pointer < diffs.length ) { + switch ( diffs[ pointer ][ 0 ] ) { + case DIFF_INSERT: + countInsert++; + textInsert += diffs[ pointer ][ 1 ]; + break; + case DIFF_DELETE: + countDelete++; + textDelete += diffs[ pointer ][ 1 ]; + break; + case DIFF_EQUAL: + // Upon reaching an equality, check for prior redundancies. + if ( countDelete >= 1 && countInsert >= 1 ) { + // Delete the offending records and add the merged ones. + diffs.splice( pointer - countDelete - countInsert, + countDelete + countInsert ); + pointer = pointer - countDelete - countInsert; + a = this.DiffMain( textDelete, textInsert, false, deadline ); + for ( j = a.length - 1; j >= 0; j-- ) { + diffs.splice( pointer, 0, a[ j ] ); + } + pointer = pointer + a.length; + } + countInsert = 0; + countDelete = 0; + textDelete = ""; + textInsert = ""; + break; } + pointer++; } - else { - if ( out.n[0].text == null ) { - for ( n = 0; n < out.o.length && out.o[n].text == null; n++ ) { - str += "" + out.o[n] + oSpace[n] + ""; + diffs.pop(); // Remove the dummy entry at the end. + + return diffs; + }; + + /** + * Find the 'middle snake' of a diff, split the problem in two + * and return the recursively constructed diff. + * See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations. + * @param {string} text1 Old string to be diffed. + * @param {string} text2 New string to be diffed. + * @param {number} deadline Time at which to bail if not yet complete. + * @return {!Array.} Array of diff tuples. + * @private + */ + DiffMatchPatch.prototype.diffBisect = function( text1, text2, deadline ) { + var text1Length, text2Length, maxD, vOffset, vLength, + v1, v2, x, delta, front, k1start, k1end, k2start, + k2end, k2Offset, k1Offset, x1, x2, y1, y2, d, k1, k2; + // Cache the text lengths to prevent multiple calls. + text1Length = text1.length; + text2Length = text2.length; + maxD = Math.ceil( ( text1Length + text2Length ) / 2 ); + vOffset = maxD; + vLength = 2 * maxD; + v1 = new Array( vLength ); + v2 = new Array( vLength ); + // Setting all elements to -1 is faster in Chrome & Firefox than mixing + // integers and undefined. + for ( x = 0; x < vLength; x++ ) { + v1[ x ] = -1; + v2[ x ] = -1; + } + v1[ vOffset + 1 ] = 0; + v2[ vOffset + 1 ] = 0; + delta = text1Length - text2Length; + // If the total number of characters is odd, then the front path will collide + // with the reverse path. + front = ( delta % 2 !== 0 ); + // Offsets for start and end of k loop. + // Prevents mapping of space beyond the grid. + k1start = 0; + k1end = 0; + k2start = 0; + k2end = 0; + for ( d = 0; d < maxD; d++ ) { + // Bail out if deadline is reached. + if ( ( new Date() ).getTime() > deadline ) { + break; + } + + // Walk the front path one step. + for ( k1 = -d + k1start; k1 <= d - k1end; k1 += 2 ) { + k1Offset = vOffset + k1; + if ( k1 === -d || ( k1 !== d && v1[ k1Offset - 1 ] < v1[ k1Offset + 1 ] ) ) { + x1 = v1[ k1Offset + 1 ]; + } else { + x1 = v1[ k1Offset - 1 ] + 1; + } + y1 = x1 - k1; + while ( x1 < text1Length && y1 < text2Length && + text1.charAt( x1 ) === text2.charAt( y1 ) ) { + x1++; + y1++; + } + v1[ k1Offset ] = x1; + if ( x1 > text1Length ) { + // Ran off the right of the graph. + k1end += 2; + } else if ( y1 > text2Length ) { + // Ran off the bottom of the graph. + k1start += 2; + } else if ( front ) { + k2Offset = vOffset + delta - k1; + if ( k2Offset >= 0 && k2Offset < vLength && v2[ k2Offset ] !== -1 ) { + // Mirror x2 onto top-left coordinate system. + x2 = text1Length - v2[ k2Offset ]; + if ( x1 >= x2 ) { + // Overlap detected. + return this.diffBisectSplit( text1, text2, x1, y1, deadline ); + } + } + } + } + + // Walk the reverse path one step. + for ( k2 = -d + k2start; k2 <= d - k2end; k2 += 2 ) { + k2Offset = vOffset + k2; + if ( k2 === -d || ( k2 !== d && v2[ k2Offset - 1 ] < v2[ k2Offset + 1 ] ) ) { + x2 = v2[ k2Offset + 1 ]; + } else { + x2 = v2[ k2Offset - 1 ] + 1; + } + y2 = x2 - k2; + while ( x2 < text1Length && y2 < text2Length && + text1.charAt( text1Length - x2 - 1 ) === + text2.charAt( text2Length - y2 - 1 ) ) { + x2++; + y2++; + } + v2[ k2Offset ] = x2; + if ( x2 > text1Length ) { + // Ran off the left of the graph. + k2end += 2; + } else if ( y2 > text2Length ) { + // Ran off the top of the graph. + k2start += 2; + } else if ( !front ) { + k1Offset = vOffset + delta - k2; + if ( k1Offset >= 0 && k1Offset < vLength && v1[ k1Offset ] !== -1 ) { + x1 = v1[ k1Offset ]; + y1 = vOffset + x1 - k1Offset; + // Mirror x2 onto top-left coordinate system. + x2 = text1Length - x2; + if ( x1 >= x2 ) { + // Overlap detected. + return this.diffBisectSplit( text1, text2, x1, y1, deadline ); + } + } } } + } + // Diff took too long and hit the deadline or + // number of diffs equals number of characters, no commonality at all. + return [ + [ DIFF_DELETE, text1 ], + [ DIFF_INSERT, text2 ] + ]; + }; + + /** + * Given the location of the 'middle snake', split the diff in two parts + * and recurse. + * @param {string} text1 Old string to be diffed. + * @param {string} text2 New string to be diffed. + * @param {number} x Index of split point in text1. + * @param {number} y Index of split point in text2. + * @param {number} deadline Time at which to bail if not yet complete. + * @return {!Array.} Array of diff tuples. + * @private + */ + DiffMatchPatch.prototype.diffBisectSplit = function( text1, text2, x, y, deadline ) { + var text1a, text1b, text2a, text2b, diffs, diffsb; + text1a = text1.substring( 0, x ); + text2a = text2.substring( 0, y ); + text1b = text1.substring( x ); + text2b = text2.substring( y ); + + // Compute both diffs serially. + diffs = this.DiffMain( text1a, text2a, false, deadline ); + diffsb = this.DiffMain( text1b, text2b, false, deadline ); - for ( i = 0; i < out.n.length; i++ ) { - if (out.n[i].text == null) { - str += "" + out.n[i] + nSpace[i] + ""; + return diffs.concat( diffsb ); + }; + + /** + * Reduce the number of edits by eliminating semantically trivial equalities. + * @param {!Array.} diffs Array of diff tuples. + */ + DiffMatchPatch.prototype.diffCleanupSemantic = function( diffs ) { + var changes, equalities, equalitiesLength, lastequality, + pointer, lengthInsertions2, lengthDeletions2, lengthInsertions1, + lengthDeletions1, deletion, insertion, overlapLength1, overlapLength2; + changes = false; + equalities = []; // Stack of indices where equalities are found. + equalitiesLength = 0; // Keeping our own length var is faster in JS. + /** @type {?string} */ + lastequality = null; + // Always equal to diffs[equalities[equalitiesLength - 1]][1] + pointer = 0; // Index of current position. + // Number of characters that changed prior to the equality. + lengthInsertions1 = 0; + lengthDeletions1 = 0; + // Number of characters that changed after the equality. + lengthInsertions2 = 0; + lengthDeletions2 = 0; + while ( pointer < diffs.length ) { + if ( diffs[ pointer ][ 0 ] === DIFF_EQUAL ) { // Equality found. + equalities[ equalitiesLength++ ] = pointer; + lengthInsertions1 = lengthInsertions2; + lengthDeletions1 = lengthDeletions2; + lengthInsertions2 = 0; + lengthDeletions2 = 0; + lastequality = diffs[ pointer ][ 1 ]; + } else { // An insertion or deletion. + if ( diffs[ pointer ][ 0 ] === DIFF_INSERT ) { + lengthInsertions2 += diffs[ pointer ][ 1 ].length; + } else { + lengthDeletions2 += diffs[ pointer ][ 1 ].length; + } + // Eliminate an equality that is smaller or equal to the edits on both + // sides of it. + if ( lastequality && ( lastequality.length <= + Math.max( lengthInsertions1, lengthDeletions1 ) ) && + ( lastequality.length <= Math.max( lengthInsertions2, + lengthDeletions2 ) ) ) { + + // Duplicate record. + diffs.splice( + equalities[ equalitiesLength - 1 ], + 0, + [ DIFF_DELETE, lastequality ] + ); + + // Change second copy to insert. + diffs[ equalities[ equalitiesLength - 1 ] + 1 ][ 0 ] = DIFF_INSERT; + + // Throw away the equality we just deleted. + equalitiesLength--; + + // Throw away the previous equality (it needs to be reevaluated). + equalitiesLength--; + pointer = equalitiesLength > 0 ? equalities[ equalitiesLength - 1 ] : -1; + + // Reset the counters. + lengthInsertions1 = 0; + lengthDeletions1 = 0; + lengthInsertions2 = 0; + lengthDeletions2 = 0; + lastequality = null; + changes = true; } - else { - // `pre` initialized at top of scope - pre = ""; + } + pointer++; + } - for ( n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++ ) { - pre += "" + out.o[n] + oSpace[n] + ""; + // Normalize the diff. + if ( changes ) { + this.diffCleanupMerge( diffs ); + } + + // Find any overlaps between deletions and insertions. + // e.g: abcxxxxxxdef + // -> abcxxxdef + // e.g: xxxabcdefxxx + // -> defxxxabc + // Only extract an overlap if it is as big as the edit ahead or behind it. + pointer = 1; + while ( pointer < diffs.length ) { + if ( diffs[ pointer - 1 ][ 0 ] === DIFF_DELETE && + diffs[ pointer ][ 0 ] === DIFF_INSERT ) { + deletion = diffs[ pointer - 1 ][ 1 ]; + insertion = diffs[ pointer ][ 1 ]; + overlapLength1 = this.diffCommonOverlap( deletion, insertion ); + overlapLength2 = this.diffCommonOverlap( insertion, deletion ); + if ( overlapLength1 >= overlapLength2 ) { + if ( overlapLength1 >= deletion.length / 2 || + overlapLength1 >= insertion.length / 2 ) { + // Overlap found. Insert an equality and trim the surrounding edits. + diffs.splice( + pointer, + 0, + [ DIFF_EQUAL, insertion.substring( 0, overlapLength1 ) ] + ); + diffs[ pointer - 1 ][ 1 ] = + deletion.substring( 0, deletion.length - overlapLength1 ); + diffs[ pointer + 1 ][ 1 ] = insertion.substring( overlapLength1 ); + pointer++; + } + } else { + if ( overlapLength2 >= deletion.length / 2 || + overlapLength2 >= insertion.length / 2 ) { + + // Reverse overlap found. + // Insert an equality and swap and trim the surrounding edits. + diffs.splice( + pointer, + 0, + [ DIFF_EQUAL, deletion.substring( 0, overlapLength2 ) ] + ); + + diffs[ pointer - 1 ][ 0 ] = DIFF_INSERT; + diffs[ pointer - 1 ][ 1 ] = + insertion.substring( 0, insertion.length - overlapLength2 ); + diffs[ pointer + 1 ][ 0 ] = DIFF_DELETE; + diffs[ pointer + 1 ][ 1 ] = + deletion.substring( overlapLength2 ); + pointer++; } - str += " " + out.n[i].text + nSpace[i] + pre; } + pointer++; } + pointer++; } + }; - return str; + /** + * Determine if the suffix of one string is the prefix of another. + * @param {string} text1 First string. + * @param {string} text2 Second string. + * @return {number} The number of characters common to the end of the first + * string and the start of the second string. + * @private + */ + DiffMatchPatch.prototype.diffCommonOverlap = function( text1, text2 ) { + var text1Length, text2Length, textLength, + best, length, pattern, found; + // Cache the text lengths to prevent multiple calls. + text1Length = text1.length; + text2Length = text2.length; + // Eliminate the null case. + if ( text1Length === 0 || text2Length === 0 ) { + return 0; + } + // Truncate the longer string. + if ( text1Length > text2Length ) { + text1 = text1.substring( text1Length - text2Length ); + } else if ( text1Length < text2Length ) { + text2 = text2.substring( 0, text1Length ); + } + textLength = Math.min( text1Length, text2Length ); + // Quick check for the worst case. + if ( text1 === text2 ) { + return textLength; + } + + // Start by looking for a single character match + // and increase length until no match is found. + // Performance analysis: https://neil.fraser.name/news/2010/11/04/ + best = 0; + length = 1; + while ( true ) { + pattern = text1.substring( textLength - length ); + found = text2.indexOf( pattern ); + if ( found === -1 ) { + return best; + } + length += found; + if ( found === 0 || text1.substring( textLength - length ) === + text2.substring( 0, length ) ) { + best = length; + length++; + } + } }; -}()); -// For browser, export only select globals -if ( typeof window !== "undefined" ) { - extend( window, QUnit.constructor.prototype ); - window.QUnit = QUnit; -} + /** + * Split two texts into an array of strings. Reduce the texts to a string of + * hashes where each Unicode character represents one line. + * @param {string} text1 First string. + * @param {string} text2 Second string. + * @return {{chars1: string, chars2: string, lineArray: !Array.}} + * An object containing the encoded text1, the encoded text2 and + * the array of unique strings. + * The zeroth element of the array of unique strings is intentionally blank. + * @private + */ + DiffMatchPatch.prototype.diffLinesToChars = function( text1, text2 ) { + var lineArray, lineHash, chars1, chars2; + lineArray = []; // e.g. lineArray[4] === 'Hello\n' + lineHash = {}; // e.g. lineHash['Hello\n'] === 4 + + // '\x00' is a valid character, but various debuggers don't like it. + // So we'll insert a junk entry to avoid generating a null character. + lineArray[ 0 ] = ""; -// For CommonJS environments, export everything -if ( typeof module !== "undefined" && module.exports ) { - module.exports = QUnit; -} + /** + * Split a text into an array of strings. Reduce the texts to a string of + * hashes where each Unicode character represents one line. + * Modifies linearray and linehash through being a closure. + * @param {string} text String to encode. + * @return {string} Encoded string. + * @private + */ + function diffLinesToCharsMunge( text ) { + var chars, lineStart, lineEnd, lineArrayLength, line; + chars = ""; + // Walk the text, pulling out a substring for each line. + // text.split('\n') would would temporarily double our memory footprint. + // Modifying text would create many large strings to garbage collect. + lineStart = 0; + lineEnd = -1; + // Keeping our own length variable is faster than looking it up. + lineArrayLength = lineArray.length; + while ( lineEnd < text.length - 1 ) { + lineEnd = text.indexOf( "\n", lineStart ); + if ( lineEnd === -1 ) { + lineEnd = text.length - 1; + } + line = text.substring( lineStart, lineEnd + 1 ); + lineStart = lineEnd + 1; + if ( lineHash.hasOwnProperty ? lineHash.hasOwnProperty( line ) : + ( lineHash[ line ] !== undefined ) ) { + chars += String.fromCharCode( lineHash[ line ] ); + } else { + chars += String.fromCharCode( lineArrayLength ); + lineHash[ line ] = lineArrayLength; + lineArray[ lineArrayLength++ ] = line; + } + } + return chars; + } -// Get a reference to the global object, like window in browsers -}( (function() { - return this; -})() )); + chars1 = diffLinesToCharsMunge( text1 ); + chars2 = diffLinesToCharsMunge( text2 ); + return { + chars1: chars1, + chars2: chars2, + lineArray: lineArray + }; + }; + + /** + * Rehydrate the text in a diff from a string of line hashes to real lines of + * text. + * @param {!Array.} diffs Array of diff tuples. + * @param {!Array.} lineArray Array of unique strings. + * @private + */ + DiffMatchPatch.prototype.diffCharsToLines = function( diffs, lineArray ) { + var x, chars, text, y; + for ( x = 0; x < diffs.length; x++ ) { + chars = diffs[ x ][ 1 ]; + text = []; + for ( y = 0; y < chars.length; y++ ) { + text[ y ] = lineArray[ chars.charCodeAt( y ) ]; + } + diffs[ x ][ 1 ] = text.join( "" ); + } + }; + + /** + * Reorder and merge like edit sections. Merge equalities. + * Any edit section can move as long as it doesn't cross an equality. + * @param {!Array.} diffs Array of diff tuples. + */ + DiffMatchPatch.prototype.diffCleanupMerge = function( diffs ) { + var pointer, countDelete, countInsert, textInsert, textDelete, + commonlength, changes, diffPointer, position; + diffs.push( [ DIFF_EQUAL, "" ] ); // Add a dummy entry at the end. + pointer = 0; + countDelete = 0; + countInsert = 0; + textDelete = ""; + textInsert = ""; + commonlength; + while ( pointer < diffs.length ) { + switch ( diffs[ pointer ][ 0 ] ) { + case DIFF_INSERT: + countInsert++; + textInsert += diffs[ pointer ][ 1 ]; + pointer++; + break; + case DIFF_DELETE: + countDelete++; + textDelete += diffs[ pointer ][ 1 ]; + pointer++; + break; + case DIFF_EQUAL: + // Upon reaching an equality, check for prior redundancies. + if ( countDelete + countInsert > 1 ) { + if ( countDelete !== 0 && countInsert !== 0 ) { + // Factor out any common prefixes. + commonlength = this.diffCommonPrefix( textInsert, textDelete ); + if ( commonlength !== 0 ) { + if ( ( pointer - countDelete - countInsert ) > 0 && + diffs[ pointer - countDelete - countInsert - 1 ][ 0 ] === + DIFF_EQUAL ) { + diffs[ pointer - countDelete - countInsert - 1 ][ 1 ] += + textInsert.substring( 0, commonlength ); + } else { + diffs.splice( 0, 0, [ DIFF_EQUAL, + textInsert.substring( 0, commonlength ) + ] ); + pointer++; + } + textInsert = textInsert.substring( commonlength ); + textDelete = textDelete.substring( commonlength ); + } + // Factor out any common suffixies. + commonlength = this.diffCommonSuffix( textInsert, textDelete ); + if ( commonlength !== 0 ) { + diffs[ pointer ][ 1 ] = textInsert.substring( textInsert.length - + commonlength ) + diffs[ pointer ][ 1 ]; + textInsert = textInsert.substring( 0, textInsert.length - + commonlength ); + textDelete = textDelete.substring( 0, textDelete.length - + commonlength ); + } + } + // Delete the offending records and add the merged ones. + if ( countDelete === 0 ) { + diffs.splice( pointer - countInsert, + countDelete + countInsert, [ DIFF_INSERT, textInsert ] ); + } else if ( countInsert === 0 ) { + diffs.splice( pointer - countDelete, + countDelete + countInsert, [ DIFF_DELETE, textDelete ] ); + } else { + diffs.splice( + pointer - countDelete - countInsert, + countDelete + countInsert, + [ DIFF_DELETE, textDelete ], [ DIFF_INSERT, textInsert ] + ); + } + pointer = pointer - countDelete - countInsert + + ( countDelete ? 1 : 0 ) + ( countInsert ? 1 : 0 ) + 1; + } else if ( pointer !== 0 && diffs[ pointer - 1 ][ 0 ] === DIFF_EQUAL ) { + + // Merge this equality with the previous one. + diffs[ pointer - 1 ][ 1 ] += diffs[ pointer ][ 1 ]; + diffs.splice( pointer, 1 ); + } else { + pointer++; + } + countInsert = 0; + countDelete = 0; + textDelete = ""; + textInsert = ""; + break; + } + } + if ( diffs[ diffs.length - 1 ][ 1 ] === "" ) { + diffs.pop(); // Remove the dummy entry at the end. + } + + // Second pass: look for single edits surrounded on both sides by equalities + // which can be shifted sideways to eliminate an equality. + // e.g: ABAC -> ABAC + changes = false; + pointer = 1; + + // Intentionally ignore the first and last element (don't need checking). + while ( pointer < diffs.length - 1 ) { + if ( diffs[ pointer - 1 ][ 0 ] === DIFF_EQUAL && + diffs[ pointer + 1 ][ 0 ] === DIFF_EQUAL ) { + + diffPointer = diffs[ pointer ][ 1 ]; + position = diffPointer.substring( + diffPointer.length - diffs[ pointer - 1 ][ 1 ].length + ); + + // This is a single edit surrounded by equalities. + if ( position === diffs[ pointer - 1 ][ 1 ] ) { + + // Shift the edit over the previous equality. + diffs[ pointer ][ 1 ] = diffs[ pointer - 1 ][ 1 ] + + diffs[ pointer ][ 1 ].substring( 0, diffs[ pointer ][ 1 ].length - + diffs[ pointer - 1 ][ 1 ].length ); + diffs[ pointer + 1 ][ 1 ] = + diffs[ pointer - 1 ][ 1 ] + diffs[ pointer + 1 ][ 1 ]; + diffs.splice( pointer - 1, 1 ); + changes = true; + } else if ( diffPointer.substring( 0, diffs[ pointer + 1 ][ 1 ].length ) === + diffs[ pointer + 1 ][ 1 ] ) { + + // Shift the edit over the next equality. + diffs[ pointer - 1 ][ 1 ] += diffs[ pointer + 1 ][ 1 ]; + diffs[ pointer ][ 1 ] = + diffs[ pointer ][ 1 ].substring( diffs[ pointer + 1 ][ 1 ].length ) + + diffs[ pointer + 1 ][ 1 ]; + diffs.splice( pointer + 1, 1 ); + changes = true; + } + } + pointer++; + } + // If shifts were made, the diff needs reordering and another shift sweep. + if ( changes ) { + this.diffCleanupMerge( diffs ); + } + }; + + return function( o, n ) { + var diff, output, text; + diff = new DiffMatchPatch(); + output = diff.DiffMain( o, n ); + diff.diffCleanupEfficiency( output ); + text = diff.diffPrettyHtml( output ); + + return text; + }; +}() ); + +// Get a reference to the global object, like window in browsers +}( (function() { + return this; +})() )); + +(function() { + +// Don't load the HTML Reporter on non-Browser environments +if ( typeof window === "undefined" || !window.document ) { + return; +} + +// Deprecated QUnit.init - Ref #530 +// Re-initialize the configuration options +QUnit.init = function() { + var tests, banner, result, qunit, + config = QUnit.config; + + config.stats = { all: 0, bad: 0 }; + config.moduleStats = { all: 0, bad: 0 }; + config.started = 0; + config.updateRate = 1000; + config.blocking = false; + config.autostart = true; + config.autorun = false; + config.filter = ""; + config.queue = []; + + // Return on non-browser environments + // This is necessary to not break on node tests + if ( typeof window === "undefined" ) { + return; + } + + qunit = id( "qunit" ); + if ( qunit ) { + qunit.innerHTML = + "

    " + escapeText( document.title ) + "

    " + + "

    " + + "
    " + + "

    " + + "
      "; + } + + tests = id( "qunit-tests" ); + banner = id( "qunit-banner" ); + result = id( "qunit-testresult" ); + + if ( tests ) { + tests.innerHTML = ""; + } + + if ( banner ) { + banner.className = ""; + } + + if ( result ) { + result.parentNode.removeChild( result ); + } + + if ( tests ) { + result = document.createElement( "p" ); + result.id = "qunit-testresult"; + result.className = "result"; + tests.parentNode.insertBefore( result, tests ); + result.innerHTML = "Running...
       "; + } +}; + +var config = QUnit.config, + collapseNext = false, + hasOwn = Object.prototype.hasOwnProperty, + defined = { + document: window.document !== undefined, + sessionStorage: (function() { + var x = "qunit-test-string"; + try { + sessionStorage.setItem( x, x ); + sessionStorage.removeItem( x ); + return true; + } catch ( e ) { + return false; + } + }()) + }, + modulesList = []; + +/** +* Escape text for attribute or text content. +*/ +function escapeText( s ) { + if ( !s ) { + return ""; + } + s = s + ""; + + // Both single quotes and double quotes (for attributes) + return s.replace( /['"<>&]/g, function( s ) { + switch ( s ) { + case "'": + return "'"; + case "\"": + return """; + case "<": + return "<"; + case ">": + return ">"; + case "&": + return "&"; + } + }); +} + +/** + * @param {HTMLElement} elem + * @param {string} type + * @param {Function} fn + */ +function addEvent( elem, type, fn ) { + if ( elem.addEventListener ) { + + // Standards-based browsers + elem.addEventListener( type, fn, false ); + } else if ( elem.attachEvent ) { + + // support: IE <9 + elem.attachEvent( "on" + type, function() { + var event = window.event; + if ( !event.target ) { + event.target = event.srcElement || document; + } + + fn.call( elem, event ); + }); + } +} + +/** + * @param {Array|NodeList} elems + * @param {string} type + * @param {Function} fn + */ +function addEvents( elems, type, fn ) { + var i = elems.length; + while ( i-- ) { + addEvent( elems[ i ], type, fn ); + } +} + +function hasClass( elem, name ) { + return ( " " + elem.className + " " ).indexOf( " " + name + " " ) >= 0; +} + +function addClass( elem, name ) { + if ( !hasClass( elem, name ) ) { + elem.className += ( elem.className ? " " : "" ) + name; + } +} + +function toggleClass( elem, name ) { + if ( hasClass( elem, name ) ) { + removeClass( elem, name ); + } else { + addClass( elem, name ); + } +} + +function removeClass( elem, name ) { + var set = " " + elem.className + " "; + + // Class name may appear multiple times + while ( set.indexOf( " " + name + " " ) >= 0 ) { + set = set.replace( " " + name + " ", " " ); + } + + // trim for prettiness + elem.className = typeof set.trim === "function" ? set.trim() : set.replace( /^\s+|\s+$/g, "" ); +} + +function id( name ) { + return defined.document && document.getElementById && document.getElementById( name ); +} + +function getUrlConfigHtml() { + var i, j, val, + escaped, escapedTooltip, + selection = false, + len = config.urlConfig.length, + urlConfigHtml = ""; + + for ( i = 0; i < len; i++ ) { + val = config.urlConfig[ i ]; + if ( typeof val === "string" ) { + val = { + id: val, + label: val + }; + } + + escaped = escapeText( val.id ); + escapedTooltip = escapeText( val.tooltip ); + + if ( config[ val.id ] === undefined ) { + config[ val.id ] = QUnit.urlParams[ val.id ]; + } + + if ( !val.value || typeof val.value === "string" ) { + urlConfigHtml += ""; + } else { + urlConfigHtml += ""; + } + } + + return urlConfigHtml; +} + +// Handle "click" events on toolbar checkboxes and "change" for select menus. +// Updates the URL with the new state of `config.urlConfig` values. +function toolbarChanged() { + var updatedUrl, value, + field = this, + params = {}; + + // Detect if field is a select menu or a checkbox + if ( "selectedIndex" in field ) { + value = field.options[ field.selectedIndex ].value || undefined; + } else { + value = field.checked ? ( field.defaultValue || true ) : undefined; + } + + params[ field.name ] = value; + updatedUrl = setUrl( params ); + + if ( "hidepassed" === field.name && "replaceState" in window.history ) { + config[ field.name ] = value || false; + if ( value ) { + addClass( id( "qunit-tests" ), "hidepass" ); + } else { + removeClass( id( "qunit-tests" ), "hidepass" ); + } + + // It is not necessary to refresh the whole page + window.history.replaceState( null, "", updatedUrl ); + } else { + window.location = updatedUrl; + } +} + +function setUrl( params ) { + var key, + querystring = "?"; + + params = QUnit.extend( QUnit.extend( {}, QUnit.urlParams ), params ); + + for ( key in params ) { + if ( hasOwn.call( params, key ) ) { + if ( params[ key ] === undefined ) { + continue; + } + querystring += encodeURIComponent( key ); + if ( params[ key ] !== true ) { + querystring += "=" + encodeURIComponent( params[ key ] ); + } + querystring += "&"; + } + } + return location.protocol + "//" + location.host + + location.pathname + querystring.slice( 0, -1 ); +} + +function applyUrlParams() { + var selectedModule, + modulesList = id( "qunit-modulefilter" ), + filter = id( "qunit-filter-input" ).value; + + selectedModule = modulesList ? + decodeURIComponent( modulesList.options[ modulesList.selectedIndex ].value ) : + undefined; + + window.location = setUrl({ + module: ( selectedModule === "" ) ? undefined : selectedModule, + filter: ( filter === "" ) ? undefined : filter, + + // Remove testId filter + testId: undefined + }); +} + +function toolbarUrlConfigContainer() { + var urlConfigContainer = document.createElement( "span" ); + + urlConfigContainer.innerHTML = getUrlConfigHtml(); + addClass( urlConfigContainer, "qunit-url-config" ); + + // For oldIE support: + // * Add handlers to the individual elements instead of the container + // * Use "click" instead of "change" for checkboxes + addEvents( urlConfigContainer.getElementsByTagName( "input" ), "click", toolbarChanged ); + addEvents( urlConfigContainer.getElementsByTagName( "select" ), "change", toolbarChanged ); + + return urlConfigContainer; +} + +function toolbarLooseFilter() { + var filter = document.createElement( "form" ), + label = document.createElement( "label" ), + input = document.createElement( "input" ), + button = document.createElement( "button" ); + + addClass( filter, "qunit-filter" ); + + label.innerHTML = "Filter: "; + + input.type = "text"; + input.value = config.filter || ""; + input.name = "filter"; + input.id = "qunit-filter-input"; + + button.innerHTML = "Go"; + + label.appendChild( input ); + + filter.appendChild( label ); + filter.appendChild( button ); + addEvent( filter, "submit", function( ev ) { + applyUrlParams(); + + if ( ev && ev.preventDefault ) { + ev.preventDefault(); + } + + return false; + }); + + return filter; +} + +function toolbarModuleFilterHtml() { + var i, + moduleFilterHtml = ""; + + if ( !modulesList.length ) { + return false; + } + + modulesList.sort(function( a, b ) { + return a.localeCompare( b ); + }); + + moduleFilterHtml += "" + + ""; + + return moduleFilterHtml; +} + +function toolbarModuleFilter() { + var toolbar = id( "qunit-testrunner-toolbar" ), + moduleFilter = document.createElement( "span" ), + moduleFilterHtml = toolbarModuleFilterHtml(); + + if ( !toolbar || !moduleFilterHtml ) { + return false; + } + + moduleFilter.setAttribute( "id", "qunit-modulefilter-container" ); + moduleFilter.innerHTML = moduleFilterHtml; + + addEvent( moduleFilter.lastChild, "change", applyUrlParams ); + + toolbar.appendChild( moduleFilter ); +} + +function appendToolbar() { + var toolbar = id( "qunit-testrunner-toolbar" ); + + if ( toolbar ) { + toolbar.appendChild( toolbarUrlConfigContainer() ); + toolbar.appendChild( toolbarLooseFilter() ); + } +} + +function appendHeader() { + var header = id( "qunit-header" ); + + if ( header ) { + header.innerHTML = "" + header.innerHTML + " "; + } +} + +function appendBanner() { + var banner = id( "qunit-banner" ); + + if ( banner ) { + banner.className = ""; + } +} + +function appendTestResults() { + var tests = id( "qunit-tests" ), + result = id( "qunit-testresult" ); + + if ( result ) { + result.parentNode.removeChild( result ); + } + + if ( tests ) { + tests.innerHTML = ""; + result = document.createElement( "p" ); + result.id = "qunit-testresult"; + result.className = "result"; + tests.parentNode.insertBefore( result, tests ); + result.innerHTML = "Running...
       "; + } +} + +function storeFixture() { + var fixture = id( "qunit-fixture" ); + if ( fixture ) { + config.fixture = fixture.innerHTML; + } +} + +function appendFilteredTest() { + var testId = QUnit.config.testId; + if ( !testId || testId.length <= 0 ) { + return ""; + } + return "
      Rerunning selected tests: " + + escapeText( testId.join(", ") ) + + " " + "Run all tests" + "
      "; +} + +function appendUserAgent() { + var userAgent = id( "qunit-userAgent" ); + + if ( userAgent ) { + userAgent.innerHTML = ""; + userAgent.appendChild( + document.createTextNode( + "QUnit " + QUnit.version + "; " + navigator.userAgent + ) + ); + } +} + +function appendTestsList( modules ) { + var i, l, x, z, test, moduleObj; + + for ( i = 0, l = modules.length; i < l; i++ ) { + moduleObj = modules[ i ]; + + if ( moduleObj.name ) { + modulesList.push( moduleObj.name ); + } + + for ( x = 0, z = moduleObj.tests.length; x < z; x++ ) { + test = moduleObj.tests[ x ]; + + appendTest( test.name, test.testId, moduleObj.name ); + } + } +} + +function appendTest( name, testId, moduleName ) { + var title, rerunTrigger, testBlock, assertList, + tests = id( "qunit-tests" ); + + if ( !tests ) { + return; + } + + title = document.createElement( "strong" ); + title.innerHTML = getNameHtml( name, moduleName ); + + rerunTrigger = document.createElement( "a" ); + rerunTrigger.innerHTML = "Rerun"; + rerunTrigger.href = setUrl({ testId: testId }); + + testBlock = document.createElement( "li" ); + testBlock.appendChild( title ); + testBlock.appendChild( rerunTrigger ); + testBlock.id = "qunit-test-output-" + testId; + + assertList = document.createElement( "ol" ); + assertList.className = "qunit-assert-list"; + + testBlock.appendChild( assertList ); + + tests.appendChild( testBlock ); +} + +// HTML Reporter initialization and load +QUnit.begin(function( details ) { + var qunit = id( "qunit" ); + + // Fixture is the only one necessary to run without the #qunit element + storeFixture(); + + if ( qunit ) { + qunit.innerHTML = + "

      " + escapeText( document.title ) + "

      " + + "

      " + + "
      " + + appendFilteredTest() + + "

      " + + "
        "; + } + + appendHeader(); + appendBanner(); + appendTestResults(); + appendUserAgent(); + appendToolbar(); + appendTestsList( details.modules ); + toolbarModuleFilter(); + + if ( qunit && config.hidepassed ) { + addClass( qunit.lastChild, "hidepass" ); + } +}); + +QUnit.done(function( details ) { + var i, key, + banner = id( "qunit-banner" ), + tests = id( "qunit-tests" ), + html = [ + "Tests completed in ", + details.runtime, + " milliseconds.
        ", + "", + details.passed, + " assertions of ", + details.total, + " passed, ", + details.failed, + " failed." + ].join( "" ); + + if ( banner ) { + banner.className = details.failed ? "qunit-fail" : "qunit-pass"; + } + + if ( tests ) { + id( "qunit-testresult" ).innerHTML = html; + } + + if ( config.altertitle && defined.document && document.title ) { + + // show ✖ for good, ✔ for bad suite result in title + // use escape sequences in case file gets loaded with non-utf-8-charset + document.title = [ + ( details.failed ? "\u2716" : "\u2714" ), + document.title.replace( /^[\u2714\u2716] /i, "" ) + ].join( " " ); + } + + // clear own sessionStorage items if all tests passed + if ( config.reorder && defined.sessionStorage && details.failed === 0 ) { + for ( i = 0; i < sessionStorage.length; i++ ) { + key = sessionStorage.key( i++ ); + if ( key.indexOf( "qunit-test-" ) === 0 ) { + sessionStorage.removeItem( key ); + } + } + } + + // scroll back to top to show results + if ( config.scrolltop && window.scrollTo ) { + window.scrollTo( 0, 0 ); + } +}); + +function getNameHtml( name, module ) { + var nameHtml = ""; + + if ( module ) { + nameHtml = "" + escapeText( module ) + ": "; + } + + nameHtml += "" + escapeText( name ) + ""; + + return nameHtml; +} + +QUnit.testStart(function( details ) { + var running, testBlock, bad; + + testBlock = id( "qunit-test-output-" + details.testId ); + if ( testBlock ) { + testBlock.className = "running"; + } else { + + // Report later registered tests + appendTest( details.name, details.testId, details.module ); + } + + running = id( "qunit-testresult" ); + if ( running ) { + bad = QUnit.config.reorder && defined.sessionStorage && + +sessionStorage.getItem( "qunit-test-" + details.module + "-" + details.name ); + + running.innerHTML = ( bad ? + "Rerunning previously failed test:
        " : + "Running:
        " ) + + getNameHtml( details.name, details.module ); + } + +}); + +function stripHtml( string ) { + // strip tags, html entity and whitespaces + return string.replace(/<\/?[^>]+(>|$)/g, "").replace(/\"/g, "").replace(/\s+/g, ""); +} + +QUnit.log(function( details ) { + var assertList, assertLi, + message, expected, actual, diff, + showDiff = false, + testItem = id( "qunit-test-output-" + details.testId ); + + if ( !testItem ) { + return; + } + + message = escapeText( details.message ) || ( details.result ? "okay" : "failed" ); + message = "" + message + ""; + message += "@ " + details.runtime + " ms"; + + // pushFailure doesn't provide details.expected + // when it calls, it's implicit to also not show expected and diff stuff + // Also, we need to check details.expected existence, as it can exist and be undefined + if ( !details.result && hasOwn.call( details, "expected" ) ) { + if ( details.negative ) { + expected = escapeText( "NOT " + QUnit.dump.parse( details.expected ) ); + } else { + expected = escapeText( QUnit.dump.parse( details.expected ) ); + } + + actual = escapeText( QUnit.dump.parse( details.actual ) ); + message += ""; + + if ( actual !== expected ) { + + message += ""; + + // Don't show diff if actual or expected are booleans + if ( !( /^(true|false)$/.test( actual ) ) && + !( /^(true|false)$/.test( expected ) ) ) { + diff = QUnit.diff( expected, actual ); + showDiff = stripHtml( diff ).length !== + stripHtml( expected ).length + + stripHtml( actual ).length; + } + + // Don't show diff if expected and actual are totally different + if ( showDiff ) { + message += ""; + } + } else if ( expected.indexOf( "[object Array]" ) !== -1 || + expected.indexOf( "[object Object]" ) !== -1 ) { + message += ""; + } + + if ( details.source ) { + message += ""; + } + + message += "
        Expected:
        " +
        +			expected +
        +			"
        Result:
        " +
        +				actual + "
        Diff:
        " +
        +					diff + "
        Message: " + + "Diff suppressed as the depth of object is more than current max depth (" + + QUnit.config.maxDepth + ").

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

        Source:
        " +
        +				escapeText( details.source ) + "
        "; + + // this occurs when pushFailure is set and we have an extracted stack trace + } else if ( !details.result && details.source ) { + message += "" + + "" + + "
        Source:
        " +
        +			escapeText( details.source ) + "
        "; + } + + assertList = testItem.getElementsByTagName( "ol" )[ 0 ]; + + assertLi = document.createElement( "li" ); + assertLi.className = details.result ? "pass" : "fail"; + assertLi.innerHTML = message; + assertList.appendChild( assertLi ); +}); + +QUnit.testDone(function( details ) { + var testTitle, time, testItem, assertList, + good, bad, testCounts, skipped, sourceName, + tests = id( "qunit-tests" ); + + if ( !tests ) { + return; + } + + testItem = id( "qunit-test-output-" + details.testId ); + + assertList = testItem.getElementsByTagName( "ol" )[ 0 ]; + + good = details.passed; + bad = details.failed; + + // store result when possible + if ( config.reorder && defined.sessionStorage ) { + if ( bad ) { + sessionStorage.setItem( "qunit-test-" + details.module + "-" + details.name, bad ); + } else { + sessionStorage.removeItem( "qunit-test-" + details.module + "-" + details.name ); + } + } + + if ( bad === 0 ) { + + // Collapse the passing tests + addClass( assertList, "qunit-collapsed" ); + } else if ( bad && config.collapse && !collapseNext ) { + + // Skip collapsing the first failing test + collapseNext = true; + } else { + + // Collapse remaining tests + addClass( assertList, "qunit-collapsed" ); + } + + // testItem.firstChild is the test name + testTitle = testItem.firstChild; + + testCounts = bad ? + "" + bad + ", " + "" + good + ", " : + ""; + + testTitle.innerHTML += " (" + testCounts + + details.assertions.length + ")"; + + if ( details.skipped ) { + testItem.className = "skipped"; + skipped = document.createElement( "em" ); + skipped.className = "qunit-skipped-label"; + skipped.innerHTML = "skipped"; + testItem.insertBefore( skipped, testTitle ); + } else { + addEvent( testTitle, "click", function() { + toggleClass( assertList, "qunit-collapsed" ); + }); + + testItem.className = bad ? "fail" : "pass"; + + time = document.createElement( "span" ); + time.className = "runtime"; + time.innerHTML = details.runtime + " ms"; + testItem.insertBefore( time, assertList ); + } + + // Show the source of the test when showing assertions + if ( details.source ) { + sourceName = document.createElement( "p" ); + sourceName.innerHTML = "Source: " + details.source; + addClass( sourceName, "qunit-source" ); + if ( bad === 0 ) { + addClass( sourceName, "qunit-collapsed" ); + } + addEvent( testTitle, "click", function() { + toggleClass( sourceName, "qunit-collapsed" ); + }); + testItem.appendChild( sourceName ); + } +}); + +if ( defined.document ) { + + // Avoid readyState issue with phantomjs + // Ref: #818 + var notPhantom = ( function( p ) { + return !( p && p.version && p.version.major > 0 ); + } )( window.phantom ); + + if ( notPhantom && document.readyState === "complete" ) { + QUnit.load(); + } else { + addEvent( window, "load", QUnit.load ); + } +} else { + config.pageLoaded = true; + config.autorun = true; +} + +})(); diff -Nru libjs-qunit-1.14.0/README.md libjs-qunit-1.22.0/README.md --- libjs-qunit-1.14.0/README.md 2014-01-31 16:41:24.000000000 +0000 +++ libjs-qunit-1.22.0/README.md 2016-02-23 15:57:56.000000000 +0000 @@ -1,6 +1,6 @@ -[![Build Status](http://jenkins.jquery.com/job/QUnit/badge/icon)](http://jenkins.jquery.com/job/QUnit/) +[![Build Status](https://travis-ci.org/jquery/qunit.svg?branch=master)](https://travis-ci.org/jquery/qunit) [![Coverage Status](https://coveralls.io/repos/jquery/qunit/badge.svg)](https://coveralls.io/github/jquery/qunit) -# [QUnit](http://qunitjs.com) - A JavaScript Unit Testing Framework. +# [QUnit](https://qunitjs.com) - A JavaScript Unit Testing Framework. QUnit is a powerful, easy-to-use, JavaScript unit testing framework. It's used by the jQuery project to test its code and plugins but is capable of testing any generic @@ -22,33 +22,41 @@ If you are interested in helping developing QUnit, you are in the right place. For related discussions, visit the -[QUnit and Testing forum](http://forum.jquery.com/qunit-and-testing). +[QUnit and Testing forum](https://forum.jquery.com/qunit-and-testing). ## Development To submit patches, fork the repository, create a branch for the change. Then implement -the change, run `grunt` to lint and test it, then commit, push and create a pull request. +the change, run `npm test` to lint and test it, then commit, push and create a pull request. Include some background for the change in the commit message and `Fixes #nnn`, referring to the issue number you're addressing. -To run `grunt`, you need `node` and `npm`, then `npm install grunt -g`. That gives you a global -grunt binary. For additional grunt tasks, also run `npm install`. +To run `npm test`, you need [Node.js](https://nodejs.org/download/), which includes `npm`. ## Releases -Use [jquery-release](https://github.com/jquery/jquery-release). The following aren't yet handled there: +Use [jquery-release](https://github.com/jquery/jquery-release). The following aren't handled there, do that first: -Install [git-extras](https://github.com/visionmedia/git-extras) and run `git changelog` to update History.md. Clean up the changelog, removing merge commits or whitespace cleanups. Commit this before using the release script. +* 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. +* Run `grunt authors` and add any new authors to AUTHORS.txt +* Update the version property in `package.json` to have the right -pre version. Not necessary for patch releases. -Then run the script. +Commit these: -Update web sites, replacing previous versions with new ones: + Build: Prepare @VERSION release, including authors and history update -* jquery/jquery-wp-content themes/jquery/footer-qunit.php -* jquery/qunitjs.com pages/index.html +Then run the script: -Finally announce on Twitter @qunitjs + node release.js --remote=jquery/qunit - Released @VERSION: https://github.com/jquery/qunit/tree/@VERSION - Changelog: https://github.com/jquery/qunit/blob/@VERSION/History.md \ No newline at end of file +Update `jquery/qunitjs.com`, replacing previous versions with new ones: + +* pages/index.html +* resources/*.html + +Update [GitHub releases](https://github.com/jquery/qunit/releases), use the changelog from `History.md`. + +Finally announce on Twitter @qunitjs (add highlights if possible, otherwise a 2nd tweet might do): + + Released @VERSION: https://github.com/jquery/qunit/releases/tag/1.17.0 diff -Nru libjs-qunit-1.14.0/reporter/html.js libjs-qunit-1.22.0/reporter/html.js --- libjs-qunit-1.14.0/reporter/html.js 1970-01-01 00:00:00.000000000 +0000 +++ libjs-qunit-1.22.0/reporter/html.js 2016-02-23 15:57:56.000000000 +0000 @@ -0,0 +1,839 @@ +(function() { + +// Don't load the HTML Reporter on non-Browser environments +if ( typeof window === "undefined" || !window.document ) { + return; +} + +// Deprecated QUnit.init - Ref #530 +// Re-initialize the configuration options +QUnit.init = function() { + var tests, banner, result, qunit, + config = QUnit.config; + + config.stats = { all: 0, bad: 0 }; + config.moduleStats = { all: 0, bad: 0 }; + config.started = 0; + config.updateRate = 1000; + config.blocking = false; + config.autostart = true; + config.autorun = false; + config.filter = ""; + config.queue = []; + + // Return on non-browser environments + // This is necessary to not break on node tests + if ( typeof window === "undefined" ) { + return; + } + + qunit = id( "qunit" ); + if ( qunit ) { + qunit.innerHTML = + "

        " + escapeText( document.title ) + "

        " + + "

        " + + "
        " + + "

        " + + "
          "; + } + + tests = id( "qunit-tests" ); + banner = id( "qunit-banner" ); + result = id( "qunit-testresult" ); + + if ( tests ) { + tests.innerHTML = ""; + } + + if ( banner ) { + banner.className = ""; + } + + if ( result ) { + result.parentNode.removeChild( result ); + } + + if ( tests ) { + result = document.createElement( "p" ); + result.id = "qunit-testresult"; + result.className = "result"; + tests.parentNode.insertBefore( result, tests ); + result.innerHTML = "Running...
           "; + } +}; + +var config = QUnit.config, + collapseNext = false, + hasOwn = Object.prototype.hasOwnProperty, + defined = { + document: window.document !== undefined, + sessionStorage: (function() { + var x = "qunit-test-string"; + try { + sessionStorage.setItem( x, x ); + sessionStorage.removeItem( x ); + return true; + } catch ( e ) { + return false; + } + }()) + }, + modulesList = []; + +/** +* Escape text for attribute or text content. +*/ +function escapeText( s ) { + if ( !s ) { + return ""; + } + s = s + ""; + + // Both single quotes and double quotes (for attributes) + return s.replace( /['"<>&]/g, function( s ) { + switch ( s ) { + case "'": + return "'"; + case "\"": + return """; + case "<": + return "<"; + case ">": + return ">"; + case "&": + return "&"; + } + }); +} + +/** + * @param {HTMLElement} elem + * @param {string} type + * @param {Function} fn + */ +function addEvent( elem, type, fn ) { + if ( elem.addEventListener ) { + + // Standards-based browsers + elem.addEventListener( type, fn, false ); + } else if ( elem.attachEvent ) { + + // support: IE <9 + elem.attachEvent( "on" + type, function() { + var event = window.event; + if ( !event.target ) { + event.target = event.srcElement || document; + } + + fn.call( elem, event ); + }); + } +} + +/** + * @param {Array|NodeList} elems + * @param {string} type + * @param {Function} fn + */ +function addEvents( elems, type, fn ) { + var i = elems.length; + while ( i-- ) { + addEvent( elems[ i ], type, fn ); + } +} + +function hasClass( elem, name ) { + return ( " " + elem.className + " " ).indexOf( " " + name + " " ) >= 0; +} + +function addClass( elem, name ) { + if ( !hasClass( elem, name ) ) { + elem.className += ( elem.className ? " " : "" ) + name; + } +} + +function toggleClass( elem, name ) { + if ( hasClass( elem, name ) ) { + removeClass( elem, name ); + } else { + addClass( elem, name ); + } +} + +function removeClass( elem, name ) { + var set = " " + elem.className + " "; + + // Class name may appear multiple times + while ( set.indexOf( " " + name + " " ) >= 0 ) { + set = set.replace( " " + name + " ", " " ); + } + + // trim for prettiness + elem.className = typeof set.trim === "function" ? set.trim() : set.replace( /^\s+|\s+$/g, "" ); +} + +function id( name ) { + return defined.document && document.getElementById && document.getElementById( name ); +} + +function getUrlConfigHtml() { + var i, j, val, + escaped, escapedTooltip, + selection = false, + len = config.urlConfig.length, + urlConfigHtml = ""; + + for ( i = 0; i < len; i++ ) { + val = config.urlConfig[ i ]; + if ( typeof val === "string" ) { + val = { + id: val, + label: val + }; + } + + escaped = escapeText( val.id ); + escapedTooltip = escapeText( val.tooltip ); + + if ( config[ val.id ] === undefined ) { + config[ val.id ] = QUnit.urlParams[ val.id ]; + } + + if ( !val.value || typeof val.value === "string" ) { + urlConfigHtml += ""; + } else { + urlConfigHtml += ""; + } + } + + return urlConfigHtml; +} + +// Handle "click" events on toolbar checkboxes and "change" for select menus. +// Updates the URL with the new state of `config.urlConfig` values. +function toolbarChanged() { + var updatedUrl, value, + field = this, + params = {}; + + // Detect if field is a select menu or a checkbox + if ( "selectedIndex" in field ) { + value = field.options[ field.selectedIndex ].value || undefined; + } else { + value = field.checked ? ( field.defaultValue || true ) : undefined; + } + + params[ field.name ] = value; + updatedUrl = setUrl( params ); + + if ( "hidepassed" === field.name && "replaceState" in window.history ) { + config[ field.name ] = value || false; + if ( value ) { + addClass( id( "qunit-tests" ), "hidepass" ); + } else { + removeClass( id( "qunit-tests" ), "hidepass" ); + } + + // It is not necessary to refresh the whole page + window.history.replaceState( null, "", updatedUrl ); + } else { + window.location = updatedUrl; + } +} + +function setUrl( params ) { + var key, + querystring = "?"; + + params = QUnit.extend( QUnit.extend( {}, QUnit.urlParams ), params ); + + for ( key in params ) { + if ( hasOwn.call( params, key ) ) { + if ( params[ key ] === undefined ) { + continue; + } + querystring += encodeURIComponent( key ); + if ( params[ key ] !== true ) { + querystring += "=" + encodeURIComponent( params[ key ] ); + } + querystring += "&"; + } + } + return location.protocol + "//" + location.host + + location.pathname + querystring.slice( 0, -1 ); +} + +function applyUrlParams() { + var selectedModule, + modulesList = id( "qunit-modulefilter" ), + filter = id( "qunit-filter-input" ).value; + + selectedModule = modulesList ? + decodeURIComponent( modulesList.options[ modulesList.selectedIndex ].value ) : + undefined; + + window.location = setUrl({ + module: ( selectedModule === "" ) ? undefined : selectedModule, + filter: ( filter === "" ) ? undefined : filter, + + // Remove testId filter + testId: undefined + }); +} + +function toolbarUrlConfigContainer() { + var urlConfigContainer = document.createElement( "span" ); + + urlConfigContainer.innerHTML = getUrlConfigHtml(); + addClass( urlConfigContainer, "qunit-url-config" ); + + // For oldIE support: + // * Add handlers to the individual elements instead of the container + // * Use "click" instead of "change" for checkboxes + addEvents( urlConfigContainer.getElementsByTagName( "input" ), "click", toolbarChanged ); + addEvents( urlConfigContainer.getElementsByTagName( "select" ), "change", toolbarChanged ); + + return urlConfigContainer; +} + +function toolbarLooseFilter() { + var filter = document.createElement( "form" ), + label = document.createElement( "label" ), + input = document.createElement( "input" ), + button = document.createElement( "button" ); + + addClass( filter, "qunit-filter" ); + + label.innerHTML = "Filter: "; + + input.type = "text"; + input.value = config.filter || ""; + input.name = "filter"; + input.id = "qunit-filter-input"; + + button.innerHTML = "Go"; + + label.appendChild( input ); + + filter.appendChild( label ); + filter.appendChild( button ); + addEvent( filter, "submit", function( ev ) { + applyUrlParams(); + + if ( ev && ev.preventDefault ) { + ev.preventDefault(); + } + + return false; + }); + + return filter; +} + +function toolbarModuleFilterHtml() { + var i, + moduleFilterHtml = ""; + + if ( !modulesList.length ) { + return false; + } + + modulesList.sort(function( a, b ) { + return a.localeCompare( b ); + }); + + moduleFilterHtml += "" + + ""; + + return moduleFilterHtml; +} + +function toolbarModuleFilter() { + var toolbar = id( "qunit-testrunner-toolbar" ), + moduleFilter = document.createElement( "span" ), + moduleFilterHtml = toolbarModuleFilterHtml(); + + if ( !toolbar || !moduleFilterHtml ) { + return false; + } + + moduleFilter.setAttribute( "id", "qunit-modulefilter-container" ); + moduleFilter.innerHTML = moduleFilterHtml; + + addEvent( moduleFilter.lastChild, "change", applyUrlParams ); + + toolbar.appendChild( moduleFilter ); +} + +function appendToolbar() { + var toolbar = id( "qunit-testrunner-toolbar" ); + + if ( toolbar ) { + toolbar.appendChild( toolbarUrlConfigContainer() ); + toolbar.appendChild( toolbarLooseFilter() ); + } +} + +function appendHeader() { + var header = id( "qunit-header" ); + + if ( header ) { + header.innerHTML = "" + header.innerHTML + " "; + } +} + +function appendBanner() { + var banner = id( "qunit-banner" ); + + if ( banner ) { + banner.className = ""; + } +} + +function appendTestResults() { + var tests = id( "qunit-tests" ), + result = id( "qunit-testresult" ); + + if ( result ) { + result.parentNode.removeChild( result ); + } + + if ( tests ) { + tests.innerHTML = ""; + result = document.createElement( "p" ); + result.id = "qunit-testresult"; + result.className = "result"; + tests.parentNode.insertBefore( result, tests ); + result.innerHTML = "Running...
           "; + } +} + +function storeFixture() { + var fixture = id( "qunit-fixture" ); + if ( fixture ) { + config.fixture = fixture.innerHTML; + } +} + +function appendFilteredTest() { + var testId = QUnit.config.testId; + if ( !testId || testId.length <= 0 ) { + return ""; + } + return "
          Rerunning selected tests: " + + escapeText( testId.join(", ") ) + + " " + "Run all tests" + "
          "; +} + +function appendUserAgent() { + var userAgent = id( "qunit-userAgent" ); + + if ( userAgent ) { + userAgent.innerHTML = ""; + userAgent.appendChild( + document.createTextNode( + "QUnit " + QUnit.version + "; " + navigator.userAgent + ) + ); + } +} + +function appendTestsList( modules ) { + var i, l, x, z, test, moduleObj; + + for ( i = 0, l = modules.length; i < l; i++ ) { + moduleObj = modules[ i ]; + + if ( moduleObj.name ) { + modulesList.push( moduleObj.name ); + } + + for ( x = 0, z = moduleObj.tests.length; x < z; x++ ) { + test = moduleObj.tests[ x ]; + + appendTest( test.name, test.testId, moduleObj.name ); + } + } +} + +function appendTest( name, testId, moduleName ) { + var title, rerunTrigger, testBlock, assertList, + tests = id( "qunit-tests" ); + + if ( !tests ) { + return; + } + + title = document.createElement( "strong" ); + title.innerHTML = getNameHtml( name, moduleName ); + + rerunTrigger = document.createElement( "a" ); + rerunTrigger.innerHTML = "Rerun"; + rerunTrigger.href = setUrl({ testId: testId }); + + testBlock = document.createElement( "li" ); + testBlock.appendChild( title ); + testBlock.appendChild( rerunTrigger ); + testBlock.id = "qunit-test-output-" + testId; + + assertList = document.createElement( "ol" ); + assertList.className = "qunit-assert-list"; + + testBlock.appendChild( assertList ); + + tests.appendChild( testBlock ); +} + +// HTML Reporter initialization and load +QUnit.begin(function( details ) { + var qunit = id( "qunit" ); + + // Fixture is the only one necessary to run without the #qunit element + storeFixture(); + + if ( qunit ) { + qunit.innerHTML = + "

          " + escapeText( document.title ) + "

          " + + "

          " + + "
          " + + appendFilteredTest() + + "

          " + + "
            "; + } + + appendHeader(); + appendBanner(); + appendTestResults(); + appendUserAgent(); + appendToolbar(); + appendTestsList( details.modules ); + toolbarModuleFilter(); + + if ( qunit && config.hidepassed ) { + addClass( qunit.lastChild, "hidepass" ); + } +}); + +QUnit.done(function( details ) { + var i, key, + banner = id( "qunit-banner" ), + tests = id( "qunit-tests" ), + html = [ + "Tests completed in ", + details.runtime, + " milliseconds.
            ", + "", + details.passed, + " assertions of ", + details.total, + " passed, ", + details.failed, + " failed." + ].join( "" ); + + if ( banner ) { + banner.className = details.failed ? "qunit-fail" : "qunit-pass"; + } + + if ( tests ) { + id( "qunit-testresult" ).innerHTML = html; + } + + if ( config.altertitle && defined.document && document.title ) { + + // show ✖ for good, ✔ for bad suite result in title + // use escape sequences in case file gets loaded with non-utf-8-charset + document.title = [ + ( details.failed ? "\u2716" : "\u2714" ), + document.title.replace( /^[\u2714\u2716] /i, "" ) + ].join( " " ); + } + + // clear own sessionStorage items if all tests passed + if ( config.reorder && defined.sessionStorage && details.failed === 0 ) { + for ( i = 0; i < sessionStorage.length; i++ ) { + key = sessionStorage.key( i++ ); + if ( key.indexOf( "qunit-test-" ) === 0 ) { + sessionStorage.removeItem( key ); + } + } + } + + // scroll back to top to show results + if ( config.scrolltop && window.scrollTo ) { + window.scrollTo( 0, 0 ); + } +}); + +function getNameHtml( name, module ) { + var nameHtml = ""; + + if ( module ) { + nameHtml = "" + escapeText( module ) + ": "; + } + + nameHtml += "" + escapeText( name ) + ""; + + return nameHtml; +} + +QUnit.testStart(function( details ) { + var running, testBlock, bad; + + testBlock = id( "qunit-test-output-" + details.testId ); + if ( testBlock ) { + testBlock.className = "running"; + } else { + + // Report later registered tests + appendTest( details.name, details.testId, details.module ); + } + + running = id( "qunit-testresult" ); + if ( running ) { + bad = QUnit.config.reorder && defined.sessionStorage && + +sessionStorage.getItem( "qunit-test-" + details.module + "-" + details.name ); + + running.innerHTML = ( bad ? + "Rerunning previously failed test:
            " : + "Running:
            " ) + + getNameHtml( details.name, details.module ); + } + +}); + +function stripHtml( string ) { + // strip tags, html entity and whitespaces + return string.replace(/<\/?[^>]+(>|$)/g, "").replace(/\"/g, "").replace(/\s+/g, ""); +} + +QUnit.log(function( details ) { + var assertList, assertLi, + message, expected, actual, diff, + showDiff = false, + testItem = id( "qunit-test-output-" + details.testId ); + + if ( !testItem ) { + return; + } + + message = escapeText( details.message ) || ( details.result ? "okay" : "failed" ); + message = "" + message + ""; + message += "@ " + details.runtime + " ms"; + + // pushFailure doesn't provide details.expected + // when it calls, it's implicit to also not show expected and diff stuff + // Also, we need to check details.expected existence, as it can exist and be undefined + if ( !details.result && hasOwn.call( details, "expected" ) ) { + if ( details.negative ) { + expected = escapeText( "NOT " + QUnit.dump.parse( details.expected ) ); + } else { + expected = escapeText( QUnit.dump.parse( details.expected ) ); + } + + actual = escapeText( QUnit.dump.parse( details.actual ) ); + message += ""; + + if ( actual !== expected ) { + + message += ""; + + // Don't show diff if actual or expected are booleans + if ( !( /^(true|false)$/.test( actual ) ) && + !( /^(true|false)$/.test( expected ) ) ) { + diff = QUnit.diff( expected, actual ); + showDiff = stripHtml( diff ).length !== + stripHtml( expected ).length + + stripHtml( actual ).length; + } + + // Don't show diff if expected and actual are totally different + if ( showDiff ) { + message += ""; + } + } else if ( expected.indexOf( "[object Array]" ) !== -1 || + expected.indexOf( "[object Object]" ) !== -1 ) { + message += ""; + } + + if ( details.source ) { + message += ""; + } + + message += "
            Expected:
            " +
            +			expected +
            +			"
            Result:
            " +
            +				actual + "
            Diff:
            " +
            +					diff + "
            Message: " + + "Diff suppressed as the depth of object is more than current max depth (" + + QUnit.config.maxDepth + ").

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

            Source:
            " +
            +				escapeText( details.source ) + "
            "; + + // this occurs when pushFailure is set and we have an extracted stack trace + } else if ( !details.result && details.source ) { + message += "" + + "" + + "
            Source:
            " +
            +			escapeText( details.source ) + "
            "; + } + + assertList = testItem.getElementsByTagName( "ol" )[ 0 ]; + + assertLi = document.createElement( "li" ); + assertLi.className = details.result ? "pass" : "fail"; + assertLi.innerHTML = message; + assertList.appendChild( assertLi ); +}); + +QUnit.testDone(function( details ) { + var testTitle, time, testItem, assertList, + good, bad, testCounts, skipped, sourceName, + tests = id( "qunit-tests" ); + + if ( !tests ) { + return; + } + + testItem = id( "qunit-test-output-" + details.testId ); + + assertList = testItem.getElementsByTagName( "ol" )[ 0 ]; + + good = details.passed; + bad = details.failed; + + // store result when possible + if ( config.reorder && defined.sessionStorage ) { + if ( bad ) { + sessionStorage.setItem( "qunit-test-" + details.module + "-" + details.name, bad ); + } else { + sessionStorage.removeItem( "qunit-test-" + details.module + "-" + details.name ); + } + } + + if ( bad === 0 ) { + + // Collapse the passing tests + addClass( assertList, "qunit-collapsed" ); + } else if ( bad && config.collapse && !collapseNext ) { + + // Skip collapsing the first failing test + collapseNext = true; + } else { + + // Collapse remaining tests + addClass( assertList, "qunit-collapsed" ); + } + + // testItem.firstChild is the test name + testTitle = testItem.firstChild; + + testCounts = bad ? + "" + bad + ", " + "" + good + ", " : + ""; + + testTitle.innerHTML += " (" + testCounts + + details.assertions.length + ")"; + + if ( details.skipped ) { + testItem.className = "skipped"; + skipped = document.createElement( "em" ); + skipped.className = "qunit-skipped-label"; + skipped.innerHTML = "skipped"; + testItem.insertBefore( skipped, testTitle ); + } else { + addEvent( testTitle, "click", function() { + toggleClass( assertList, "qunit-collapsed" ); + }); + + testItem.className = bad ? "fail" : "pass"; + + time = document.createElement( "span" ); + time.className = "runtime"; + time.innerHTML = details.runtime + " ms"; + testItem.insertBefore( time, assertList ); + } + + // Show the source of the test when showing assertions + if ( details.source ) { + sourceName = document.createElement( "p" ); + sourceName.innerHTML = "Source: " + details.source; + addClass( sourceName, "qunit-source" ); + if ( bad === 0 ) { + addClass( sourceName, "qunit-collapsed" ); + } + addEvent( testTitle, "click", function() { + toggleClass( sourceName, "qunit-collapsed" ); + }); + testItem.appendChild( sourceName ); + } +}); + +if ( defined.document ) { + + // Avoid readyState issue with phantomjs + // Ref: #818 + var notPhantom = ( function( p ) { + return !( p && p.version && p.version.major > 0 ); + } )( window.phantom ); + + if ( notPhantom && document.readyState === "complete" ) { + QUnit.load(); + } else { + addEvent( window, "load", QUnit.load ); + } +} else { + config.pageLoaded = true; + config.autorun = true; +} + +})(); diff -Nru libjs-qunit-1.14.0/src/assert.js libjs-qunit-1.22.0/src/assert.js --- libjs-qunit-1.14.0/src/assert.js 2014-01-31 16:41:24.000000000 +0000 +++ libjs-qunit-1.22.0/src/assert.js 2016-02-23 15:57:56.000000000 +0000 @@ -1,199 +1,284 @@ -// `assert` initialized at top of scope +function Assert( testContext ) { + this.test = testContext; +} + // Assert helpers -// All of these must either call QUnit.push() or manually do: -// - runLoggingCallbacks( "log", .. ); -// - config.current.assertions.push({ .. }); -assert = QUnit.assert = { - /** - * Asserts rough true-ish result. - * @name ok - * @function - * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); - */ - ok: function( result, msg ) { - if ( !config.current ) { - throw new Error( "ok() assertion outside test context, was " + sourceFromStacktrace(2) ); - } - result = !!result; - msg = msg || ( result ? "okay" : "failed" ); - - var source, - details = { - module: config.current.module, - name: config.current.testName, - result: result, - message: msg - }; - - msg = "" + escapeText( msg ) + ""; - - if ( !result ) { - source = sourceFromStacktrace( 2 ); - if ( source ) { - details.source = source; - msg += "
            Source:
            " +
            -					escapeText( source ) +
            -					"
            "; - } +QUnit.assert = Assert.prototype = { + + // Specify the number of expected assertions to guarantee that failed test + // (no assertions are run at all) don't slip through. + expect: function( asserts ) { + if ( arguments.length === 1 ) { + this.test.expected = asserts; + } else { + return this.test.expected; } - runLoggingCallbacks( "log", QUnit, details ); - config.current.assertions.push({ + }, + + // Increment this Test's semaphore counter, then return a function that + // decrements that counter a maximum of once. + async: function( count ) { + var test = this.test, + popped = false, + acceptCallCount = count; + + if ( typeof acceptCallCount === "undefined" ) { + acceptCallCount = 1; + } + + test.semaphore += 1; + test.usedAsync = true; + pauseProcessing(); + + return function done() { + + if ( popped ) { + test.pushFailure( "Too many calls to the `assert.async` callback", + sourceFromStacktrace( 2 ) ); + return; + } + acceptCallCount -= 1; + if ( acceptCallCount > 0 ) { + return; + } + + test.semaphore -= 1; + popped = true; + resumeProcessing(); + }; + }, + + // Exports test.push() to the user API + // Alias of pushResult. + push: function( result, actual, expected, message, negative ) { + var currentAssert = this instanceof Assert ? this : QUnit.config.current.assert; + return currentAssert.pushResult( { result: result, - message: msg - }); + actual: actual, + expected: expected, + message: message, + negative: negative + } ); + }, + + pushResult: function( resultInfo ) { + + // resultInfo = { result, actual, expected, message, negative } + var assert = this, + currentTest = ( assert instanceof Assert && assert.test ) || QUnit.config.current; + + // Backwards compatibility fix. + // Allows the direct use of global exported assertions and QUnit.assert.* + // Although, it's use is not recommended as it can leak assertions + // to other tests from async tests, because we only get a reference to the current test, + // not exactly the test where assertion were intended to be called. + if ( !currentTest ) { + throw new Error( "assertion outside test context, in " + sourceFromStacktrace( 2 ) ); + } + + if ( currentTest.usedAsync === true && currentTest.semaphore === 0 ) { + currentTest.pushFailure( "Assertion after the final `assert.async` was resolved", + sourceFromStacktrace( 2 ) ); + + // Allow this assertion to continue running anyway... + } + + if ( !( assert instanceof Assert ) ) { + assert = currentTest.assert; + } + + return assert.test.pushResult( resultInfo ); + }, + + ok: function( result, message ) { + message = message || ( result ? "okay" : "failed, expected argument to be truthy, was: " + + QUnit.dump.parse( result ) ); + this.pushResult( { + result: !!result, + actual: result, + expected: true, + message: message + } ); + }, + + notOk: function( result, message ) { + message = message || ( !result ? "okay" : "failed, expected argument to be falsy, was: " + + QUnit.dump.parse( result ) ); + this.pushResult( { + result: !result, + actual: result, + expected: false, + message: message + } ); }, - /** - * Assert that the first two arguments are equal, with an optional message. - * Prints out both actual and expected values. - * @name equal - * @function - * @example equal( format( "Received {0} bytes.", 2), "Received 2 bytes.", "format() replaces {0} with next argument" ); - */ equal: function( actual, expected, message ) { /*jshint eqeqeq:false */ - QUnit.push( expected == actual, actual, expected, message ); + this.pushResult( { + result: expected == actual, + actual: actual, + expected: expected, + message: message + } ); }, - /** - * @name notEqual - * @function - */ notEqual: function( actual, expected, message ) { /*jshint eqeqeq:false */ - QUnit.push( expected != actual, actual, expected, message ); + this.pushResult( { + result: expected != actual, + actual: actual, + expected: expected, + message: message, + negative: true + } ); }, - /** - * @name propEqual - * @function - */ propEqual: function( actual, expected, message ) { - actual = objectValues(actual); - expected = objectValues(expected); - QUnit.push( QUnit.equiv(actual, expected), actual, expected, message ); + actual = objectValues( actual ); + expected = objectValues( expected ); + this.pushResult( { + result: QUnit.equiv( actual, expected ), + actual: actual, + expected: expected, + message: message + } ); }, - /** - * @name notPropEqual - * @function - */ notPropEqual: function( actual, expected, message ) { - actual = objectValues(actual); - expected = objectValues(expected); - QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message ); + actual = objectValues( actual ); + expected = objectValues( expected ); + this.pushResult( { + result: !QUnit.equiv( actual, expected ), + actual: actual, + expected: expected, + message: message, + negative: true + } ); }, - /** - * @name deepEqual - * @function - */ deepEqual: function( actual, expected, message ) { - QUnit.push( QUnit.equiv(actual, expected), actual, expected, message ); + this.pushResult( { + result: QUnit.equiv( actual, expected ), + actual: actual, + expected: expected, + message: message + } ); }, - /** - * @name notDeepEqual - * @function - */ notDeepEqual: function( actual, expected, message ) { - QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message ); + this.pushResult( { + result: !QUnit.equiv( actual, expected ), + actual: actual, + expected: expected, + message: message, + negative: true + } ); }, - /** - * @name strictEqual - * @function - */ strictEqual: function( actual, expected, message ) { - QUnit.push( expected === actual, actual, expected, message ); + this.pushResult( { + result: expected === actual, + actual: actual, + expected: expected, + message: message + } ); }, - /** - * @name notStrictEqual - * @function - */ notStrictEqual: function( actual, expected, message ) { - QUnit.push( expected !== actual, actual, expected, message ); + this.pushResult( { + result: expected !== actual, + actual: actual, + expected: expected, + message: message, + negative: true + } ); }, "throws": function( block, expected, message ) { - var actual, + var actual, expectedType, expectedOutput = expected, - ok = false; + ok = false, + currentTest = ( this instanceof Assert && this.test ) || QUnit.config.current; - // 'expected' is optional - if ( !message && typeof expected === "string" ) { + // 'expected' is optional unless doing string comparison + if ( message == null && typeof expected === "string" ) { message = expected; expected = null; } - config.current.ignoreGlobalErrors = true; + currentTest.ignoreGlobalErrors = true; try { - block.call( config.current.testEnvironment ); + block.call( currentTest.testEnvironment ); } catch (e) { actual = e; } - config.current.ignoreGlobalErrors = false; + currentTest.ignoreGlobalErrors = false; if ( actual ) { + expectedType = QUnit.objectType( expected ); // we don't want to validate thrown error if ( !expected ) { ok = true; expectedOutput = null; - // expected is an Error object - } else if ( expected instanceof Error ) { - ok = actual instanceof Error && - actual.name === expected.name && - actual.message === expected.message; - // expected is a regexp - } else if ( QUnit.objectType( expected ) === "regexp" ) { + } else if ( expectedType === "regexp" ) { ok = expected.test( errorString( actual ) ); // expected is a string - } else if ( QUnit.objectType( expected ) === "string" ) { + } else if ( expectedType === "string" ) { ok = expected === errorString( actual ); - // expected is a constructor - } else if ( actual instanceof expected ) { + // expected is a constructor, maybe an Error constructor + } else if ( expectedType === "function" && actual instanceof expected ) { ok = true; - // expected is a validation function which returns true is validation passed - } else if ( expected.call( {}, actual ) === true ) { + // expected is an Error object + } else if ( expectedType === "object" ) { + ok = actual instanceof expected.constructor && + actual.name === expected.name && + actual.message === expected.message; + + // expected is a validation function which returns true if validation passed + } else if ( expectedType === "function" && expected.call( {}, actual ) === true ) { expectedOutput = null; ok = true; } - - QUnit.push( ok, actual, expectedOutput, message ); - } else { - QUnit.pushFailure( message, null, "No exception was thrown." ); } - } -}; -/** - * @deprecated since 1.8.0 - * Kept assertion helpers in root for backwards compatibility. - */ -extend( QUnit.constructor.prototype, assert ); - -/** - * @deprecated since 1.9.0 - * Kept to avoid TypeErrors for undefined methods. - */ -QUnit.constructor.prototype.raises = function() { - QUnit.push( false, false, false, "QUnit.raises has been deprecated since 2012 (fad3c1ea), use QUnit.throws instead" ); + currentTest.assert.pushResult( { + result: ok, + actual: actual, + expected: expectedOutput, + message: message + } ); + } }; -/** - * @deprecated since 1.0.0, replaced with error pushes since 1.3.0 - * Kept to avoid TypeErrors for undefined methods. - */ -QUnit.constructor.prototype.equals = function() { - QUnit.push( false, false, false, "QUnit.equals has been deprecated since 2009 (e88049a0), use QUnit.equal instead" ); -}; -QUnit.constructor.prototype.same = function() { - QUnit.push( false, false, false, "QUnit.same has been deprecated since 2009 (e88049a0), use QUnit.deepEqual instead" ); -}; +// Provide an alternative to assert.throws(), for environments that consider throws a reserved word +// Known to us are: Closure Compiler, Narwhal +(function() { + /*jshint sub:true */ + Assert.prototype.raises = Assert.prototype[ "throws" ]; +}()); + +function errorString( error ) { + var name, message, + resultErrorString = error.toString(); + if ( resultErrorString.substring( 0, 7 ) === "[object" ) { + name = error.name ? error.name.toString() : "Error"; + message = error.message ? error.message.toString() : ""; + if ( name && message ) { + return name + ": " + message; + } else if ( name ) { + return name; + } else if ( message ) { + return message; + } else { + return "Error"; + } + } else { + return resultErrorString; + } +} diff -Nru libjs-qunit-1.14.0/src/core/config.js libjs-qunit-1.22.0/src/core/config.js --- libjs-qunit-1.14.0/src/core/config.js 1970-01-01 00:00:00.000000000 +0000 +++ libjs-qunit-1.22.0/src/core/config.js 2016-02-23 15:57:56.000000000 +0000 @@ -0,0 +1,89 @@ +/** + * Config object: Maintain internal state + * Later exposed as QUnit.config + * `config` initialized at top of scope + */ +var config = { + // The queue of tests to run + queue: [], + + // block until document ready + blocking: true, + + // by default, run previously failed tests first + // very useful in combination with "Hide passed tests" checked + reorder: true, + + // by default, modify document.title when suite is done + altertitle: true, + + // HTML Reporter: collapse every test except the first failing test + // If false, all failing tests will be expanded + collapse: true, + + // by default, scroll to top of the page when suite is done + scrolltop: true, + + // depth up-to which object will be dumped + maxDepth: 5, + + // when enabled, all tests must call expect() + requireExpects: false, + + // add checkboxes that are persisted in the query-string + // when enabled, the id is set to `true` as a `QUnit.config` property + urlConfig: [ + { + id: "hidepassed", + label: "Hide passed tests", + tooltip: "Only show tests and assertions that fail. Stored as query-strings." + }, + { + id: "noglobals", + label: "Check for Globals", + tooltip: "Enabling this will test if any test introduces new properties on the " + + "global object (`window` in Browsers). Stored as query-strings." + }, + { + id: "notrycatch", + label: "No try-catch", + tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging " + + "exceptions in IE reasonable. Stored as query-strings." + } + ], + + // Set of all modules. + modules: [], + + // Stack of nested modules + moduleStack: [], + + // The first unnamed module + currentModule: { + name: "", + tests: [] + }, + + callbacks: {} +}; + +var urlParams = defined.document ? getUrlParams() : {}; + +// Push a loose unnamed module to the modules collection +config.modules.push( config.currentModule ); + +if ( urlParams.filter === true ) { + delete urlParams.filter; +} + +// String search anywhere in moduleName+testName +config.filter = urlParams.filter; + +config.testId = []; +if ( urlParams.testId ) { + // Ensure that urlParams.testId is an array + urlParams.testId = decodeURIComponent( urlParams.testId ).split( "," ); + for (var i = 0; i < urlParams.testId.length; i++ ) { + config.testId.push( urlParams.testId[ i ] ); + } +} diff -Nru libjs-qunit-1.14.0/src/core/initialize.js libjs-qunit-1.22.0/src/core/initialize.js --- libjs-qunit-1.14.0/src/core/initialize.js 1970-01-01 00:00:00.000000000 +0000 +++ libjs-qunit-1.22.0/src/core/initialize.js 2016-02-23 15:57:56.000000000 +0000 @@ -0,0 +1,31 @@ +var QUnit = {}; + +var Date = global.Date; +var now = Date.now || function() { + return new Date().getTime(); +}; + +var setTimeout = global.setTimeout; +var clearTimeout = global.clearTimeout; + +// Store a local window from the global to allow direct references. +var window = global.window; + +var defined = { + document: window && window.document !== undefined, + setTimeout: setTimeout !== undefined, + sessionStorage: (function() { + var x = "qunit-test-string"; + try { + sessionStorage.setItem( x, x ); + sessionStorage.removeItem( x ); + return true; + } catch ( e ) { + return false; + } + }() ) +}; + +var fileName = ( sourceFromStacktrace( 0 ) || "" ).replace( /(:\d+)+\)?/, "" ).replace( /.+\//, "" ); +var globalStartCalled = false; +var runStarted = false; diff -Nru libjs-qunit-1.14.0/src/core/logging.js libjs-qunit-1.22.0/src/core/logging.js --- libjs-qunit-1.14.0/src/core/logging.js 1970-01-01 00:00:00.000000000 +0000 +++ libjs-qunit-1.22.0/src/core/logging.js 2016-02-23 15:57:56.000000000 +0000 @@ -0,0 +1,75 @@ +var loggingCallbacks = {}; + +// Register logging callbacks +function registerLoggingCallbacks( obj ) { + var i, l, key, + callbackNames = [ "begin", "done", "log", "testStart", "testDone", + "moduleStart", "moduleDone" ]; + + function registerLoggingCallback( key ) { + var loggingCallback = function( callback ) { + if ( objectType( callback ) !== "function" ) { + throw new Error( + "QUnit logging methods require a callback function as their first parameters." + ); + } + + config.callbacks[ key ].push( callback ); + }; + + // DEPRECATED: This will be removed on QUnit 2.0.0+ + // Stores the registered functions allowing restoring + // at verifyLoggingCallbacks() if modified + loggingCallbacks[ key ] = loggingCallback; + + return loggingCallback; + } + + for ( i = 0, l = callbackNames.length; i < l; i++ ) { + key = callbackNames[ i ]; + + // Initialize key collection of logging callback + if ( objectType( config.callbacks[ key ] ) === "undefined" ) { + config.callbacks[ key ] = []; + } + + obj[ key ] = registerLoggingCallback( key ); + } +} + +function runLoggingCallbacks( key, args ) { + var i, l, callbacks; + + callbacks = config.callbacks[ key ]; + for ( i = 0, l = callbacks.length; i < l; i++ ) { + callbacks[ i ]( args ); + } +} + +// DEPRECATED: This will be removed on 2.0.0+ +// This function verifies if the loggingCallbacks were modified by the user +// If so, it will restore it, assign the given callback and print a console warning +function verifyLoggingCallbacks() { + var loggingCallback, userCallback; + + for ( loggingCallback in loggingCallbacks ) { + if ( QUnit[ loggingCallback ] !== loggingCallbacks[ loggingCallback ] ) { + + userCallback = QUnit[ loggingCallback ]; + + // Restore the callback function + QUnit[ loggingCallback ] = loggingCallbacks[ loggingCallback ]; + + // Assign the deprecated given callback + QUnit[ loggingCallback ]( userCallback ); + + if ( global.console && global.console.warn ) { + global.console.warn( + "QUnit." + loggingCallback + " was replaced with a new value.\n" + + "Please, check out the documentation on how to apply logging callbacks.\n" + + "Reference: https://api.qunitjs.com/category/callbacks/" + ); + } + } + } +} diff -Nru libjs-qunit-1.14.0/src/core/onerror.js libjs-qunit-1.22.0/src/core/onerror.js --- libjs-qunit-1.14.0/src/core/onerror.js 1970-01-01 00:00:00.000000000 +0000 +++ libjs-qunit-1.22.0/src/core/onerror.js 2016-02-23 15:57:56.000000000 +0000 @@ -0,0 +1,37 @@ +( function() { + if ( !defined.document ) { + return; + } + + // `onErrorFnPrev` initialized at top of scope + // Preserve other handlers + var onErrorFnPrev = window.onerror; + + // Cover uncaught exceptions + // Returning true will suppress the default browser handler, + // returning false will let it run. + window.onerror = function( error, filePath, linerNr ) { + var ret = false; + if ( onErrorFnPrev ) { + ret = onErrorFnPrev( error, filePath, linerNr ); + } + + // Treat return value as window.onerror itself does, + // Only do our handling if not suppressed. + if ( ret !== true ) { + if ( QUnit.config.current ) { + if ( QUnit.config.current.ignoreGlobalErrors ) { + return true; + } + QUnit.pushFailure( error, filePath + ":" + linerNr ); + } else { + QUnit.test( "global failure", extend(function() { + QUnit.pushFailure( error, filePath + ":" + linerNr ); + }, { validTest: true } ) ); + } + return false; + } + + return ret; + }; +} )(); diff -Nru libjs-qunit-1.14.0/src/core/stacktrace.js libjs-qunit-1.22.0/src/core/stacktrace.js --- libjs-qunit-1.14.0/src/core/stacktrace.js 1970-01-01 00:00:00.000000000 +0000 +++ libjs-qunit-1.22.0/src/core/stacktrace.js 2016-02-23 15:57:56.000000000 +0000 @@ -0,0 +1,54 @@ +// Doesn't support IE6 to IE9, it will return undefined on these browsers +// See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack +function extractStacktrace( e, offset ) { + offset = offset === undefined ? 4 : offset; + + var stack, include, i; + + if ( e.stack ) { + stack = e.stack.split( "\n" ); + if ( /^error$/i.test( stack[ 0 ] ) ) { + stack.shift(); + } + if ( fileName ) { + include = []; + for ( i = offset; i < stack.length; i++ ) { + if ( stack[ i ].indexOf( fileName ) !== -1 ) { + break; + } + include.push( stack[ i ] ); + } + if ( include.length ) { + return include.join( "\n" ); + } + } + return stack[ offset ]; + + // Support: Safari <=6 only + } else if ( e.sourceURL ) { + + // exclude useless self-reference for generated Error objects + if ( /qunit.js$/.test( e.sourceURL ) ) { + return; + } + + // for actual exceptions, this is useful + return e.sourceURL + ":" + e.line; + } +} + +function sourceFromStacktrace( offset ) { + var error = new Error(); + + // Support: Safari <=7 only, IE <=10 - 11 only + // Not all browsers generate the `stack` property for `new Error()`, see also #636 + if ( !error.stack ) { + try { + throw error; + } catch ( err ) { + error = err; + } + } + + return extractStacktrace( error, offset ); +} diff -Nru libjs-qunit-1.14.0/src/core/utilities.js libjs-qunit-1.22.0/src/core/utilities.js --- libjs-qunit-1.14.0/src/core/utilities.js 1970-01-01 00:00:00.000000000 +0000 +++ libjs-qunit-1.22.0/src/core/utilities.js 2016-02-23 15:57:56.000000000 +0000 @@ -0,0 +1,139 @@ +var toString = Object.prototype.toString, + hasOwn = Object.prototype.hasOwnProperty; + +// returns a new Array with the elements that are in a but not in b +function diff( a, b ) { + var i, j, + result = a.slice(); + + for ( i = 0; i < result.length; i++ ) { + for ( j = 0; j < b.length; j++ ) { + if ( result[ i ] === b[ j ] ) { + result.splice( i, 1 ); + i--; + break; + } + } + } + return result; +} + +// from jquery.js +function inArray( elem, array ) { + if ( array.indexOf ) { + return array.indexOf( elem ); + } + + for ( var i = 0, length = array.length; i < length; i++ ) { + if ( array[ i ] === elem ) { + return i; + } + } + + return -1; +} + +/** + * Makes a clone of an object using only Array or Object as base, + * and copies over the own enumerable properties. + * + * @param {Object} obj + * @return {Object} New object with only the own properties (recursively). + */ +function objectValues ( obj ) { + var key, val, + vals = QUnit.is( "array", obj ) ? [] : {}; + for ( key in obj ) { + if ( hasOwn.call( obj, key ) ) { + val = obj[ key ]; + vals[ key ] = val === Object( val ) ? objectValues( val ) : val; + } + } + return vals; +} + +function extend( a, b, undefOnly ) { + for ( var prop in b ) { + if ( hasOwn.call( b, prop ) ) { + + // Avoid "Member not found" error in IE8 caused by messing with window.constructor + // This block runs on every environment, so `global` is being used instead of `window` + // to avoid errors on node. + if ( prop !== "constructor" || a !== global ) { + if ( b[ prop ] === undefined ) { + delete a[ prop ]; + } else if ( !( undefOnly && typeof a[ prop ] !== "undefined" ) ) { + a[ prop ] = b[ prop ]; + } + } + } + } + + return a; +} + +function objectType( obj ) { + if ( typeof obj === "undefined" ) { + return "undefined"; + } + + // Consider: typeof null === object + if ( obj === null ) { + return "null"; + } + + var match = toString.call( obj ).match( /^\[object\s(.*)\]$/ ), + type = match && match[ 1 ]; + + switch ( type ) { + case "Number": + if ( isNaN( obj ) ) { + return "nan"; + } + return "number"; + case "String": + case "Boolean": + case "Array": + case "Set": + case "Map": + case "Date": + case "RegExp": + case "Function": + case "Symbol": + return type.toLowerCase(); + } + if ( typeof obj === "object" ) { + return "object"; + } +} + +// Safe object type checking +function is( type, obj ) { + return QUnit.objectType( obj ) === type; +} + +var getUrlParams = function() { + var i, param, name, value; + var urlParams = {}; + var location = window.location; + var params = location.search.slice( 1 ).split( "&" ); + var length = params.length; + + for ( i = 0; i < length; i++ ) { + if ( params[ i ] ) { + param = params[ i ].split( "=" ); + name = decodeURIComponent( param[ 0 ] ); + + // allow just a key to turn on a flag, e.g., test.html?noglobals + value = param.length === 1 || + decodeURIComponent( param.slice( 1 ).join( "=" ) ) ; + if ( urlParams[ name ] ) { + urlParams[ name ] = [].concat( urlParams[ name ], value ); + } else { + urlParams[ name ] = value; + } + } + } + + return urlParams; +}; diff -Nru libjs-qunit-1.14.0/src/core.js libjs-qunit-1.22.0/src/core.js --- libjs-qunit-1.14.0/src/core.js 2014-01-31 16:41:24.000000000 +0000 +++ libjs-qunit-1.22.0/src/core.js 2016-02-23 15:57:56.000000000 +0000 @@ -1,1005 +1,250 @@ -var QUnit, - assert, - config, - onErrorFnPrev, - testId = 0, - fileName = (sourceFromStacktrace( 0 ) || "" ).replace(/(:\d+)+\)?/, "").replace(/.+\//, ""), - toString = Object.prototype.toString, - hasOwn = Object.prototype.hasOwnProperty, - // Keep a local reference to Date (GH-283) - Date = window.Date, - setTimeout = window.setTimeout, - clearTimeout = window.clearTimeout, - defined = { - document: typeof window.document !== "undefined", - setTimeout: typeof window.setTimeout !== "undefined", - sessionStorage: (function() { - var x = "qunit-test-string"; - try { - sessionStorage.setItem( x, x ); - sessionStorage.removeItem( x ); - return true; - } catch( e ) { - return false; - } - }()) - }, - /** - * Provides a normalized error string, correcting an issue - * with IE 7 (and prior) where Error.prototype.toString is - * not properly implemented - * - * Based on http://es5.github.com/#x15.11.4.4 - * - * @param {String|Error} error - * @return {String} error message - */ - errorString = function( error ) { - var name, message, - errorString = error.toString(); - if ( errorString.substring( 0, 7 ) === "[object" ) { - name = error.name ? error.name.toString() : "Error"; - message = error.message ? error.message.toString() : ""; - if ( name && message ) { - return name + ": " + message; - } else if ( name ) { - return name; - } else if ( message ) { - return message; - } else { - return "Error"; - } - } else { - return errorString; - } - }, - /** - * Makes a clone of an object using only Array or Object as base, - * and copies over the own enumerable properties. - * - * @param {Object} obj - * @return {Object} New object with only the own properties (recursively). - */ - objectValues = function( obj ) { - // Grunt 0.3.x uses an older version of jshint that still has jshint/jshint#392. - /*jshint newcap: false */ - var key, val, - vals = QUnit.is( "array", obj ) ? [] : {}; - for ( key in obj ) { - if ( hasOwn.call( obj, key ) ) { - val = obj[key]; - vals[key] = val === Object(val) ? objectValues(val) : val; - } - } - return vals; - }; +QUnit.urlParams = urlParams; +// Figure out if we're running the tests from a server or not +QUnit.isLocal = !( defined.document && window.location.protocol !== "file:" ); -// Root QUnit object. -// `QUnit` initialized at top of scope -QUnit = { +// Expose the current QUnit version +QUnit.version = "@VERSION"; - // call on start of module test to prepend name to all tests - module: function( name, testEnvironment ) { - config.currentModule = name; - config.currentModuleTestEnvironment = testEnvironment; - config.modules[name] = true; - }, - - asyncTest: function( testName, expected, callback ) { - if ( arguments.length === 2 ) { - callback = expected; - expected = null; - } - - QUnit.test( testName, expected, callback, true ); - }, +extend( QUnit, { - test: function( testName, expected, callback, async ) { - var test, - nameHtml = "" + escapeText( testName ) + ""; + // call on start of module test to prepend name to all tests + module: function( name, testEnvironment, executeNow ) { + var module, moduleFns; + var currentModule = config.currentModule; if ( arguments.length === 2 ) { - callback = expected; - expected = null; + if ( testEnvironment instanceof Function ) { + executeNow = testEnvironment; + testEnvironment = undefined; + } } - if ( config.currentModule ) { - nameHtml = "" + escapeText( config.currentModule ) + ": " + nameHtml; + // DEPRECATED: handles setup/teardown functions, + // beforeEach and afterEach should be used instead + if ( testEnvironment && testEnvironment.setup ) { + testEnvironment.beforeEach = testEnvironment.setup; + delete testEnvironment.setup; } - - test = new Test({ - nameHtml: nameHtml, - testName: testName, - expected: expected, - async: async, - callback: callback, - module: config.currentModule, - moduleTestEnvironment: config.currentModuleTestEnvironment, - stack: sourceFromStacktrace( 2 ) - }); - - if ( !validTest( test ) ) { - return; + if ( testEnvironment && testEnvironment.teardown ) { + testEnvironment.afterEach = testEnvironment.teardown; + delete testEnvironment.teardown; } - test.queue(); - }, + module = createModule(); - // Specify the number of expected assertions to guarantee that failed test (no assertions are run at all) don't slip through. - expect: function( asserts ) { - if (arguments.length === 1) { - config.current.expected = asserts; - } else { - return config.current.expected; - } - }, + moduleFns = { + beforeEach: setHook( module, "beforeEach" ), + afterEach: setHook( module, "afterEach" ) + }; - start: function( count ) { - // QUnit hasn't been initialized yet. - // Note: RequireJS (et al) may delay onLoad - if ( config.semaphore === undefined ) { - QUnit.begin(function() { - // This is triggered at the top of QUnit.load, push start() to the event loop, to allow QUnit.load to finish first - setTimeout(function() { - QUnit.start( count ); - }); - }); - return; + if ( executeNow instanceof Function ) { + config.moduleStack.push( module ); + setCurrentModule( module ); + executeNow.call( module.testEnvironment, moduleFns ); + config.moduleStack.pop(); + module = module.parentModule || currentModule; } - config.semaphore -= count || 1; - // don't start until equal number of stop-calls - if ( config.semaphore > 0 ) { - return; - } - // ignore if start is called more often then stop - if ( config.semaphore < 0 ) { - config.semaphore = 0; - QUnit.pushFailure( "Called start() while already started (QUnit.config.semaphore was 0 already)", null, sourceFromStacktrace(2) ); - return; - } - // A slight delay, to avoid any current callbacks - if ( defined.setTimeout ) { - setTimeout(function() { - if ( config.semaphore > 0 ) { - return; - } - if ( config.timeout ) { - clearTimeout( config.timeout ); - } - - config.blocking = false; - process( true ); - }, 13); - } else { - config.blocking = false; - process( true ); - } - }, + setCurrentModule( module ); - stop: function( count ) { - config.semaphore += count || 1; - config.blocking = true; - - if ( config.testTimeout && defined.setTimeout ) { - clearTimeout( config.timeout ); - config.timeout = setTimeout(function() { - QUnit.ok( false, "Test timed out" ); - config.semaphore = 1; - QUnit.start(); - }, config.testTimeout ); - } - } -}; - -// We use the prototype to distinguish between properties that should -// be exposed as globals (and in exports) and those that shouldn't -(function() { - function F() {} - F.prototype = QUnit; - QUnit = new F(); - // Make F QUnit's constructor so that we can add to the prototype later - QUnit.constructor = F; -}()); - -/** - * Config object: Maintain internal state - * Later exposed as QUnit.config - * `config` initialized at top of scope - */ -config = { - // The queue of tests to run - queue: [], - - // block until document ready - blocking: true, - - // when enabled, show only failing tests - // gets persisted through sessionStorage and can be changed in UI via checkbox - hidepassed: false, - - // by default, run previously failed tests first - // very useful in combination with "Hide passed tests" checked - reorder: true, - - // by default, modify document.title when suite is done - altertitle: true, - - // by default, scroll to top of the page when suite is done - scrolltop: true, - - // when enabled, all tests must call expect() - requireExpects: false, - - // add checkboxes that are persisted in the query-string - // when enabled, the id is set to `true` as a `QUnit.config` property - urlConfig: [ - { - id: "noglobals", - label: "Check for Globals", - tooltip: "Enabling this will test if any test introduces new properties on the `window` object. Stored as query-strings." - }, - { - id: "notrycatch", - label: "No try-catch", - tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging exceptions in IE reasonable. Stored as query-strings." - } - ], - - // Set of all modules. - modules: {}, + function createModule() { + var parentModule = config.moduleStack.length ? + config.moduleStack.slice( -1 )[ 0 ] : null; + var moduleName = parentModule !== null ? + [ parentModule.name, name ].join( " > " ) : name; + var module = { + name: moduleName, + parentModule: parentModule, + tests: [] + }; - // logging callback queues - begin: [], - done: [], - log: [], - testStart: [], - testDone: [], - moduleStart: [], - moduleDone: [] -}; - -// Initialize more QUnit.config and QUnit.urlParams -(function() { - var i, current, - location = window.location || { search: "", protocol: "file:" }, - params = location.search.slice( 1 ).split( "&" ), - length = params.length, - urlParams = {}; - - if ( params[ 0 ] ) { - for ( i = 0; i < length; i++ ) { - current = params[ i ].split( "=" ); - current[ 0 ] = decodeURIComponent( current[ 0 ] ); - - // allow just a key to turn on a flag, e.g., test.html?noglobals - current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true; - if ( urlParams[ current[ 0 ] ] ) { - urlParams[ current[ 0 ] ] = [].concat( urlParams[ current[ 0 ] ], current[ 1 ] ); - } else { - urlParams[ current[ 0 ] ] = current[ 1 ]; + var env = {}; + if ( parentModule ) { + extend( env, parentModule.testEnvironment ); + delete env.beforeEach; + delete env.afterEach; } - } - } - - QUnit.urlParams = urlParams; - - // String search anywhere in moduleName+testName - config.filter = urlParams.filter; - - // Exact match of the module name - config.module = urlParams.module; - - config.testNumber = []; - if ( urlParams.testNumber ) { - - // Ensure that urlParams.testNumber is an array - urlParams.testNumber = [].concat( urlParams.testNumber ); - for ( i = 0; i < urlParams.testNumber.length; i++ ) { - current = urlParams.testNumber[ i ]; - config.testNumber.push( parseInt( current, 10 ) ); - } - } - - // Figure out if we're running the tests from a server or not - QUnit.isLocal = location.protocol === "file:"; -}()); - -extend( QUnit, { - - config: config, - - // Initialize the configuration options - init: function() { - extend( config, { - stats: { all: 0, bad: 0 }, - moduleStats: { all: 0, bad: 0 }, - started: +new Date(), - updateRate: 1000, - blocking: false, - autostart: true, - autorun: false, - filter: "", - queue: [], - semaphore: 1 - }); - - var tests, banner, result, - qunit = id( "qunit" ); - - if ( qunit ) { - qunit.innerHTML = - "

            " + escapeText( document.title ) + "

            " + - "

            " + - "
            " + - "

            " + - "
              "; - } - - tests = id( "qunit-tests" ); - banner = id( "qunit-banner" ); - result = id( "qunit-testresult" ); - - if ( tests ) { - tests.innerHTML = ""; - } + extend( env, testEnvironment ); + module.testEnvironment = env; - if ( banner ) { - banner.className = ""; + config.modules.push( module ); + return module; } - if ( result ) { - result.parentNode.removeChild( result ); + function setCurrentModule( module ) { + config.currentModule = module; } - if ( tests ) { - result = document.createElement( "p" ); - result.id = "qunit-testresult"; - result.className = "result"; - tests.parentNode.insertBefore( result, tests ); - result.innerHTML = "Running...
               "; - } }, - // Resets the test setup. Useful for tests that modify the DOM. - /* - DEPRECATED: Use multiple tests instead of resetting inside a test. - Use testStart or testDone for custom cleanup. - This method will throw an error in 2.0, and will be removed in 2.1 - */ - reset: function() { - var fixture = id( "qunit-fixture" ); - if ( fixture ) { - fixture.innerHTML = config.fixture; - } - }, + // DEPRECATED: QUnit.asyncTest() will be removed in QUnit 2.0. + asyncTest: asyncTest, - // Safe object type checking - is: function( type, obj ) { - return QUnit.objectType( obj ) === type; - }, + test: test, - objectType: function( obj ) { - if ( typeof obj === "undefined" ) { - return "undefined"; - } + skip: skip, - // Consider: typeof null === object - if ( obj === null ) { - return "null"; - } - - var match = toString.call( obj ).match(/^\[object\s(.*)\]$/), - type = match && match[1] || ""; + only: only, - switch ( type ) { - case "Number": - if ( isNaN(obj) ) { - return "nan"; - } - return "number"; - case "String": - case "Boolean": - case "Array": - case "Date": - case "RegExp": - case "Function": - return type.toLowerCase(); - } - if ( typeof obj === "object" ) { - return "object"; - } - return undefined; - }, + // DEPRECATED: The functionality of QUnit.start() will be altered in QUnit 2.0. + // In QUnit 2.0, invoking it will ONLY affect the `QUnit.config.autostart` blocking behavior. + start: function( count ) { + var globalStartAlreadyCalled = globalStartCalled; - push: function( result, actual, expected, message ) { if ( !config.current ) { - throw new Error( "assertion outside test context, was " + sourceFromStacktrace() ); - } - - var output, source, - details = { - module: config.current.module, - name: config.current.testName, - result: result, - message: message, - actual: actual, - expected: expected - }; + globalStartCalled = true; - message = escapeText( message ) || ( result ? "okay" : "failed" ); - message = "" + message + ""; - output = message; - - if ( !result ) { - expected = escapeText( QUnit.jsDump.parse(expected) ); - actual = escapeText( QUnit.jsDump.parse(actual) ); - output += ""; - - if ( actual !== expected ) { - output += ""; - output += ""; + if ( runStarted ) { + throw new Error( "Called start() outside of a test context while already started" ); + } else if ( globalStartAlreadyCalled || count > 1 ) { + throw new Error( "Called start() outside of a test context too many times" ); + } else if ( config.autostart ) { + throw new Error( "Called start() outside of a test context when " + + "QUnit.config.autostart was true" ); + } else if ( !config.pageLoaded ) { + + // The page isn't completely loaded yet, so bail out and let `QUnit.load` handle it + config.autostart = true; + return; } + } else { - source = sourceFromStacktrace(); + // If a test is running, adjust its semaphore + config.current.semaphore -= count || 1; - if ( source ) { - details.source = source; - output += ""; - } + // If semaphore is non-numeric, throw error + if ( isNaN( config.current.semaphore ) ) { + config.current.semaphore = 0; - output += "
              Expected:
              " + expected + "
              Result:
              " + actual + "
              Diff:
              " + QUnit.diff( expected, actual ) + "
              Source:
              " + escapeText( source ) + "
              "; - } + QUnit.pushFailure( + "Called start() with a non-numeric decrement.", + sourceFromStacktrace( 2 ) + ); + return; + } - runLoggingCallbacks( "log", QUnit, details ); + // Don't start until equal number of stop-calls + if ( config.current.semaphore > 0 ) { + return; + } - config.current.assertions.push({ - result: !!result, - message: output - }); - }, + // throw an Error if start is called more often than stop + if ( config.current.semaphore < 0 ) { + config.current.semaphore = 0; - pushFailure: function( message, source, actual ) { - if ( !config.current ) { - throw new Error( "pushFailure() assertion outside test context, was " + sourceFromStacktrace(2) ); + QUnit.pushFailure( + "Called start() while already started (test's semaphore was 0 already)", + sourceFromStacktrace( 2 ) + ); + return; + } } - var output, - details = { - module: config.current.module, - name: config.current.testName, - result: false, - message: message - }; - - message = escapeText( message ) || "error"; - message = "" + message + ""; - output = message; - - output += ""; + resumeProcessing(); + }, - if ( actual ) { - output += ""; - } + // DEPRECATED: QUnit.stop() will be removed in QUnit 2.0. + stop: function( count ) { - if ( source ) { - details.source = source; - output += ""; + // If there isn't a test running, don't allow QUnit.stop() to be called + if ( !config.current ) { + throw new Error( "Called stop() outside of a test context" ); } - output += "
              Result:
              " + escapeText( actual ) + "
              Source:
              " + escapeText( source ) + "
              "; - - runLoggingCallbacks( "log", QUnit, details ); - - config.current.assertions.push({ - result: false, - message: output - }); - }, + // If a test is running, adjust its semaphore + config.current.semaphore += count || 1; - url: function( params ) { - params = extend( extend( {}, QUnit.urlParams ), params ); - var key, - querystring = "?"; - - for ( key in params ) { - if ( hasOwn.call( params, key ) ) { - querystring += encodeURIComponent( key ) + "=" + - encodeURIComponent( params[ key ] ) + "&"; - } - } - return window.location.protocol + "//" + window.location.host + - window.location.pathname + querystring.slice( 0, -1 ); + pauseProcessing(); }, - extend: extend, - id: id, - addEvent: addEvent, - addClass: addClass, - hasClass: hasClass, - removeClass: removeClass - // load, equiv, jsDump, diff: Attached later -}); - -/** - * @deprecated: Created for backwards compatibility with test runner that set the hook function - * into QUnit.{hook}, instead of invoking it and passing the hook function. - * QUnit.constructor is set to the empty F() above so that we can add to it's prototype here. - * Doing this allows us to tell if the following methods have been overwritten on the actual - * QUnit object. - */ -extend( QUnit.constructor.prototype, { - - // Logging callbacks; all receive a single argument with the listed properties - // run test/logs.html for any related changes - begin: registerLoggingCallback( "begin" ), - - // done: { failed, passed, total, runtime } - done: registerLoggingCallback( "done" ), - - // log: { result, actual, expected, message } - log: registerLoggingCallback( "log" ), - - // testStart: { name } - testStart: registerLoggingCallback( "testStart" ), - - // testDone: { name, failed, passed, total, runtime } - testDone: registerLoggingCallback( "testDone" ), - - // moduleStart: { name } - moduleStart: registerLoggingCallback( "moduleStart" ), - - // moduleDone: { name, failed, passed, total } - moduleDone: registerLoggingCallback( "moduleDone" ) -}); + config: config, -if ( !defined.document || document.readyState === "complete" ) { - config.autorun = true; -} + is: is, -QUnit.load = function() { - runLoggingCallbacks( "begin", QUnit, {} ); + objectType: objectType, - // Initialize the config, saving the execution queue - var banner, filter, i, j, label, len, main, ol, toolbar, val, selection, - urlConfigContainer, moduleFilter, userAgent, - numModules = 0, - moduleNames = [], - moduleFilterHtml = "", - urlConfigHtml = "", - oldconfig = extend( {}, config ); + extend: extend, - QUnit.init(); - extend(config, oldconfig); + load: function() { + config.pageLoaded = true; - config.blocking = false; + // Initialize the configuration options + extend( config, { + stats: { all: 0, bad: 0 }, + moduleStats: { all: 0, bad: 0 }, + started: 0, + updateRate: 1000, + autostart: true, + filter: "" + }, true ); - len = config.urlConfig.length; + config.blocking = false; - for ( i = 0; i < len; i++ ) { - val = config.urlConfig[i]; - if ( typeof val === "string" ) { - val = { - id: val, - label: val - }; + if ( config.autostart ) { + resumeProcessing(); } - config[ val.id ] = QUnit.urlParams[ val.id ]; - if ( !val.value || typeof val.value === "string" ) { - urlConfigHtml += ""; - } else { - urlConfigHtml += ""; - } - } - for ( i in config.modules ) { - if ( config.modules.hasOwnProperty( i ) ) { - moduleNames.push(i); - } - } - numModules = moduleNames.length; - moduleNames.sort( function( a, b ) { - return a.localeCompare( b ); - }); - moduleFilterHtml += ""; +}); - // `userAgent` initialized at top of scope - userAgent = id( "qunit-userAgent" ); - if ( userAgent ) { - userAgent.innerHTML = navigator.userAgent; - } +registerLoggingCallbacks( QUnit ); - // `banner` initialized at top of scope - banner = id( "qunit-header" ); - if ( banner ) { - banner.innerHTML = "" + banner.innerHTML + " "; - } +function begin() { + var i, l, + modulesLog = []; - // `toolbar` initialized at top of scope - toolbar = id( "qunit-testrunner-toolbar" ); - if ( toolbar ) { - // `filter` initialized at top of scope - filter = document.createElement( "input" ); - filter.type = "checkbox"; - filter.id = "qunit-filter-pass"; - - addEvent( filter, "click", function() { - var tmp, - ol = id( "qunit-tests" ); + // If the test run hasn't officially begun yet + if ( !config.started ) { - if ( filter.checked ) { - ol.className = ol.className + " hidepass"; - } else { - tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " "; - ol.className = tmp.replace( / hidepass /, " " ); - } - if ( defined.sessionStorage ) { - if (filter.checked) { - sessionStorage.setItem( "qunit-filter-passed-tests", "true" ); - } else { - sessionStorage.removeItem( "qunit-filter-passed-tests" ); - } - } - }); + // Record the time of the test run's beginning + config.started = now(); - if ( config.hidepassed || defined.sessionStorage && sessionStorage.getItem( "qunit-filter-passed-tests" ) ) { - filter.checked = true; - // `ol` initialized at top of scope - ol = id( "qunit-tests" ); - ol.className = ol.className + " hidepass"; - } - toolbar.appendChild( filter ); + verifyLoggingCallbacks(); - // `label` initialized at top of scope - label = document.createElement( "label" ); - label.setAttribute( "for", "qunit-filter-pass" ); - label.setAttribute( "title", "Only show tests and assertions that fail. Stored in sessionStorage." ); - label.innerHTML = "Hide passed tests"; - toolbar.appendChild( label ); - - urlConfigContainer = document.createElement("span"); - urlConfigContainer.innerHTML = urlConfigHtml; - // For oldIE support: - // * Add handlers to the individual elements instead of the container - // * Use "click" instead of "change" for checkboxes - // * Fallback from event.target to event.srcElement - addEvents( urlConfigContainer.getElementsByTagName("input"), "click", function( event ) { - var params = {}, - target = event.target || event.srcElement; - params[ target.name ] = target.checked ? - target.defaultValue || true : - undefined; - window.location = QUnit.url( params ); - }); - addEvents( urlConfigContainer.getElementsByTagName("select"), "change", function( event ) { - var params = {}, - target = event.target || event.srcElement; - params[ target.name ] = target.options[ target.selectedIndex ].value || undefined; - window.location = QUnit.url( params ); - }); - toolbar.appendChild( urlConfigContainer ); - - if (numModules > 1) { - moduleFilter = document.createElement( "span" ); - moduleFilter.setAttribute( "id", "qunit-modulefilter-container" ); - moduleFilter.innerHTML = moduleFilterHtml; - addEvent( moduleFilter.lastChild, "change", function() { - var selectBox = moduleFilter.getElementsByTagName("select")[0], - selectedModule = decodeURIComponent(selectBox.options[selectBox.selectedIndex].value); - - window.location = QUnit.url({ - module: ( selectedModule === "" ) ? undefined : selectedModule, - // Remove any existing filters - filter: undefined, - testNumber: undefined - }); - }); - toolbar.appendChild(moduleFilter); + // Delete the loose unnamed module if unused. + if ( config.modules[ 0 ].name === "" && config.modules[ 0 ].tests.length === 0 ) { + config.modules.shift(); } - } - - // `main` initialized at top of scope - main = id( "qunit-fixture" ); - if ( main ) { - config.fixture = main.innerHTML; - } - - if ( config.autostart ) { - QUnit.start(); - } -}; - -if ( defined.document ) { - addEvent( window, "load", QUnit.load ); -} - -// `onErrorFnPrev` initialized at top of scope -// Preserve other handlers -onErrorFnPrev = window.onerror; - -// Cover uncaught exceptions -// Returning true will suppress the default browser handler, -// returning false will let it run. -window.onerror = function ( error, filePath, linerNr ) { - var ret = false; - if ( onErrorFnPrev ) { - ret = onErrorFnPrev( error, filePath, linerNr ); - } - // Treat return value as window.onerror itself does, - // Only do our handling if not suppressed. - if ( ret !== true ) { - if ( QUnit.config.current ) { - if ( QUnit.config.current.ignoreGlobalErrors ) { - return true; - } - QUnit.pushFailure( error, filePath + ":" + linerNr ); - } else { - QUnit.test( "global failure", extend( function() { - QUnit.pushFailure( error, filePath + ":" + linerNr ); - }, { validTest: validTest } ) ); + // Avoid unnecessary information by not logging modules' test environments + for ( i = 0, l = config.modules.length; i < l; i++ ) { + modulesLog.push({ + name: config.modules[ i ].name, + tests: config.modules[ i ].tests + }); } - return false; - } - - return ret; -}; - -function done() { - config.autorun = true; - // Log the last module results - if ( config.previousModule ) { - runLoggingCallbacks( "moduleDone", QUnit, { - name: config.previousModule, - failed: config.moduleStats.bad, - passed: config.moduleStats.all - config.moduleStats.bad, - total: config.moduleStats.all + // The test run is officially beginning now + runLoggingCallbacks( "begin", { + totalTests: Test.count, + modules: modulesLog }); } - delete config.previousModule; - - var i, key, - banner = id( "qunit-banner" ), - tests = id( "qunit-tests" ), - runtime = +new Date() - config.started, - passed = config.stats.all - config.stats.bad, - html = [ - "Tests completed in ", - runtime, - " milliseconds.
              ", - "", - passed, - " assertions of ", - config.stats.all, - " passed, ", - config.stats.bad, - " failed." - ].join( "" ); - - if ( banner ) { - banner.className = ( config.stats.bad ? "qunit-fail" : "qunit-pass" ); - } - - if ( tests ) { - id( "qunit-testresult" ).innerHTML = html; - } - - if ( config.altertitle && defined.document && document.title ) { - // show ✖ for good, ✔ for bad suite result in title - // use escape sequences in case file gets loaded with non-utf-8-charset - document.title = [ - ( config.stats.bad ? "\u2716" : "\u2714" ), - document.title.replace( /^[\u2714\u2716] /i, "" ) - ].join( " " ); - } - - // clear own sessionStorage items if all tests passed - if ( config.reorder && defined.sessionStorage && config.stats.bad === 0 ) { - // `key` & `i` initialized at top of scope - for ( i = 0; i < sessionStorage.length; i++ ) { - key = sessionStorage.key( i++ ); - if ( key.indexOf( "qunit-test-" ) === 0 ) { - sessionStorage.removeItem( key ); - } - } - } - - // scroll back to top to show results - if ( config.scrolltop && window.scrollTo ) { - window.scrollTo(0, 0); - } - - runLoggingCallbacks( "done", QUnit, { - failed: config.stats.bad, - passed: passed, - total: config.stats.all, - runtime: runtime - }); -} - -/** @return Boolean: true if this test should be ran */ -function validTest( test ) { - var include, - filter = config.filter && config.filter.toLowerCase(), - module = config.module && config.module.toLowerCase(), - fullName = ( test.module + ": " + test.testName ).toLowerCase(); - - // Internally-generated tests are always valid - if ( test.callback && test.callback.validTest === validTest ) { - delete test.callback.validTest; - return true; - } - - if ( config.testNumber.length > 0 ) { - if ( inArray( test.testNumber, config.testNumber ) < 0 ) { - return false; - } - } - - if ( module && ( !test.module || test.module.toLowerCase() !== module ) ) { - return false; - } - - if ( !filter ) { - return true; - } - - include = filter.charAt( 0 ) !== "!"; - if ( !include ) { - filter = filter.slice( 1 ); - } - - // If the filter matches, we need to honour include - if ( fullName.indexOf( filter ) !== -1 ) { - return include; - } - - // Otherwise, do the opposite - return !include; -} - -// so far supports only Firefox, Chrome and Opera (buggy), Safari (for real exceptions) -// Later Safari and IE10 are supposed to support error.stack as well -// See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack -function extractStacktrace( e, offset ) { - offset = offset === undefined ? 3 : offset; - - var stack, include, i; - - if ( e.stacktrace ) { - // Opera - return e.stacktrace.split( "\n" )[ offset + 3 ]; - } else if ( e.stack ) { - // Firefox, Chrome - stack = e.stack.split( "\n" ); - if (/^error$/i.test( stack[0] ) ) { - stack.shift(); - } - if ( fileName ) { - include = []; - for ( i = offset; i < stack.length; i++ ) { - if ( stack[ i ].indexOf( fileName ) !== -1 ) { - break; - } - include.push( stack[ i ] ); - } - if ( include.length ) { - return include.join( "\n" ); - } - } - return stack[ offset ]; - } else if ( e.sourceURL ) { - // Safari, PhantomJS - // hopefully one day Safari provides actual stacktraces - // exclude useless self-reference for generated Error objects - if ( /qunit.js$/.test( e.sourceURL ) ) { - return; - } - // for actual exceptions, this is useful - return e.sourceURL + ":" + e.line; - } -} -function sourceFromStacktrace( offset ) { - try { - throw new Error(); - } catch ( e ) { - return extractStacktrace( e, offset ); - } -} -/** - * Escape text for attribute or text content. - */ -function escapeText( s ) { - if ( !s ) { - return ""; - } - s = s + ""; - // Both single quotes and double quotes (for attributes) - return s.replace( /['"<>&]/g, function( s ) { - switch( s ) { - case "'": - return "'"; - case "\"": - return """; - case "<": - return "<"; - case ">": - return ">"; - case "&": - return "&"; - } - }); -} - -function synchronize( callback, last ) { - config.queue.push( callback ); - - if ( config.autorun && !config.blocking ) { - process( last ); - } + config.blocking = false; + process( true ); } function process( last ) { function next() { process( last ); } - var start = new Date().getTime(); - config.depth = config.depth ? config.depth + 1 : 1; + var start = now(); + config.depth = ( config.depth || 0 ) + 1; while ( config.queue.length && !config.blocking ) { - if ( !defined.setTimeout || config.updateRate <= 0 || ( ( new Date().getTime() - start ) < config.updateRate ) ) { + if ( !defined.setTimeout || config.updateRate <= 0 || + ( ( now() - start ) < config.updateRate ) ) { + if ( config.current ) { + + // Reset async tracking for each phase of the Test lifecycle + config.current.usedAsync = false; + } config.queue.shift()(); } else { setTimeout( next, 13 ); @@ -1012,161 +257,78 @@ } } -function saveGlobal() { - config.pollution = []; +function pauseProcessing() { + config.blocking = true; - if ( config.noglobals ) { - for ( var key in window ) { - if ( hasOwn.call( window, key ) ) { - // in Opera sometimes DOM element ids show up here, ignore them - if ( /^qunit-test-output/.test( key ) ) { - continue; - } - config.pollution.push( key ); + if ( config.testTimeout && defined.setTimeout ) { + clearTimeout( config.timeout ); + config.timeout = setTimeout(function() { + if ( config.current ) { + config.current.semaphore = 0; + QUnit.pushFailure( "Test timed out", sourceFromStacktrace( 2 ) ); + } else { + throw new Error( "Test timed out" ); } - } + resumeProcessing(); + }, config.testTimeout ); } } -function checkPollution() { - var newGlobals, - deletedGlobals, - old = config.pollution; - - saveGlobal(); - - newGlobals = diff( config.pollution, old ); - if ( newGlobals.length > 0 ) { - QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join(", ") ); - } +function resumeProcessing() { + runStarted = true; - deletedGlobals = diff( old, config.pollution ); - if ( deletedGlobals.length > 0 ) { - QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join(", ") ); - } -} - -// returns a new Array with the elements that are in a but not in b -function diff( a, b ) { - var i, j, - result = a.slice(); - - for ( i = 0; i < result.length; i++ ) { - for ( j = 0; j < b.length; j++ ) { - if ( result[i] === b[j] ) { - result.splice( i, 1 ); - i--; - break; + // A slight delay to allow this iteration of the event loop to finish (more assertions, etc.) + if ( defined.setTimeout ) { + setTimeout(function() { + if ( config.current && config.current.semaphore > 0 ) { + return; } - } - } - return result; -} - -function extend( a, b ) { - for ( var prop in b ) { - if ( hasOwn.call( b, prop ) ) { - // Avoid "Member not found" error in IE8 caused by messing with window.constructor - if ( !( prop === "constructor" && a === window ) ) { - if ( b[ prop ] === undefined ) { - delete a[ prop ]; - } else { - a[ prop ] = b[ prop ]; - } + if ( config.timeout ) { + clearTimeout( config.timeout ); } - } - } - - return a; -} - -/** - * @param {HTMLElement} elem - * @param {string} type - * @param {Function} fn - */ -function addEvent( elem, type, fn ) { - if ( elem.addEventListener ) { - - // Standards-based browsers - elem.addEventListener( type, fn, false ); - } else if ( elem.attachEvent ) { - // support: IE <9 - elem.attachEvent( "on" + type, fn ); + begin(); + }, 13 ); } else { - - // Caller must ensure support for event listeners is present - throw new Error( "addEvent() was called in a context without event listener support" ); + begin(); } } -/** - * @param {Array|NodeList} elems - * @param {string} type - * @param {Function} fn - */ -function addEvents( elems, type, fn ) { - var i = elems.length; - while ( i-- ) { - addEvent( elems[i], type, fn ); - } -} - -function hasClass( elem, name ) { - return (" " + elem.className + " ").indexOf(" " + name + " ") > -1; -} +function done() { + var runtime, passed; -function addClass( elem, name ) { - if ( !hasClass( elem, name ) ) { - elem.className += (elem.className ? " " : "") + name; - } -} + config.autorun = true; -function removeClass( elem, name ) { - var set = " " + elem.className + " "; - // Class name may appear multiple times - while ( set.indexOf(" " + name + " ") > -1 ) { - set = set.replace(" " + name + " " , " "); + // Log the last module results + if ( config.previousModule ) { + runLoggingCallbacks( "moduleDone", { + name: config.previousModule.name, + tests: config.previousModule.tests, + failed: config.moduleStats.bad, + passed: config.moduleStats.all - config.moduleStats.bad, + total: config.moduleStats.all, + runtime: now() - config.moduleStats.started + }); } - // If possible, trim it for prettiness, but not necessarily - elem.className = typeof set.trim === "function" ? set.trim() : set.replace(/^\s+|\s+$/g, ""); -} - -function id( name ) { - return defined.document && document.getElementById && document.getElementById( name ); -} + delete config.previousModule; -function registerLoggingCallback( key ) { - return function( callback ) { - config[key].push( callback ); - }; -} + runtime = now() - config.started; + passed = config.stats.all - config.stats.bad; -// Supports deprecated method of completely overwriting logging callbacks -function runLoggingCallbacks( key, scope, args ) { - var i, callbacks; - if ( QUnit.hasOwnProperty( key ) ) { - QUnit[ key ].call(scope, args ); - } else { - callbacks = config[ key ]; - for ( i = 0; i < callbacks.length; i++ ) { - callbacks[ i ].call( scope, args ); - } - } + runLoggingCallbacks( "done", { + failed: config.stats.bad, + passed: passed, + total: config.stats.all, + runtime: runtime + }); } -// from jquery.js -function inArray( elem, array ) { - if ( array.indexOf ) { - return array.indexOf( elem ); - } - - for ( var i = 0, length = array.length; i < length; i++ ) { - if ( array[ i ] === elem ) { - return i; - } +function setHook( module, hookName ) { + if ( module.testEnvironment === undefined ) { + module.testEnvironment = {}; } - return -1; + return function( callback ) { + module.testEnvironment[ hookName ] = callback; + }; } diff -Nru libjs-qunit-1.14.0/src/diff.js libjs-qunit-1.22.0/src/diff.js --- libjs-qunit-1.14.0/src/diff.js 2014-01-31 16:41:24.000000000 +0000 +++ libjs-qunit-1.22.0/src/diff.js 2016-02-23 15:57:56.000000000 +0000 @@ -1,147 +1,1111 @@ /* - * Javascript Diff Algorithm - * By John Resig (http://ejohn.org/) - * Modified by Chu Alan "sprite" + * This file is a modified version of google-diff-match-patch's JavaScript implementation + * (https://code.google.com/p/google-diff-match-patch/source/browse/trunk/javascript/diff_match_patch_uncompressed.js), + * modifications are licensed as more fully set forth in LICENSE.txt. * - * Released under the MIT license. + * The original source of google-diff-match-patch is attributable and licensed as follows: + * + * Copyright 2006 Google Inc. + * https://code.google.com/p/google-diff-match-patch/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. * * More Info: - * http://ejohn.org/projects/javascript-diff-algorithm/ + * https://code.google.com/p/google-diff-match-patch/ * * Usage: QUnit.diff(expected, actual) * - * QUnit.diff( "the quick brown fox jumped over", "the quick fox jumps over" ) == "the quick brown fox jumped jumps over" */ -QUnit.diff = (function() { - /*jshint eqeqeq:false, eqnull:true */ - function diff( o, n ) { - var i, - ns = {}, - os = {}; - - for ( i = 0; i < n.length; i++ ) { - if ( !hasOwn.call( ns, n[i] ) ) { - ns[ n[i] ] = { - rows: [], - o: null - }; - } - ns[ n[i] ].rows.push( i ); - } - - for ( i = 0; i < o.length; i++ ) { - if ( !hasOwn.call( os, o[i] ) ) { - os[ o[i] ] = { - rows: [], - n: null - }; - } - os[ o[i] ].rows.push( i ); - } - - for ( i in ns ) { - if ( hasOwn.call( ns, i ) ) { - if ( ns[i].rows.length === 1 && hasOwn.call( os, i ) && os[i].rows.length === 1 ) { - n[ ns[i].rows[0] ] = { - text: n[ ns[i].rows[0] ], - row: os[i].rows[0] - }; - o[ os[i].rows[0] ] = { - text: o[ os[i].rows[0] ], - row: ns[i].rows[0] - }; - } - } - } - - for ( i = 0; i < n.length - 1; i++ ) { - if ( n[i].text != null && n[ i + 1 ].text == null && n[i].row + 1 < o.length && o[ n[i].row + 1 ].text == null && - n[ i + 1 ] == o[ n[i].row + 1 ] ) { - - n[ i + 1 ] = { - text: n[ i + 1 ], - row: n[i].row + 1 - }; - o[ n[i].row + 1 ] = { - text: o[ n[i].row + 1 ], - row: i + 1 - }; - } - } - - for ( i = n.length - 1; i > 0; i-- ) { - if ( n[i].text != null && n[ i - 1 ].text == null && n[i].row > 0 && o[ n[i].row - 1 ].text == null && - n[ i - 1 ] == o[ n[i].row - 1 ]) { - - n[ i - 1 ] = { - text: n[ i - 1 ], - row: n[i].row - 1 - }; - o[ n[i].row - 1 ] = { - text: o[ n[i].row - 1 ], - row: i - 1 - }; +QUnit.diff = ( function() { + function DiffMatchPatch() { + } + + // DIFF FUNCTIONS + + /** + * The data structure representing a diff is an array of tuples: + * [[DIFF_DELETE, 'Hello'], [DIFF_INSERT, 'Goodbye'], [DIFF_EQUAL, ' world.']] + * which means: delete 'Hello', add 'Goodbye' and keep ' world.' + */ + var DIFF_DELETE = -1, + DIFF_INSERT = 1, + DIFF_EQUAL = 0; + + /** + * Find the differences between two texts. Simplifies the problem by stripping + * any common prefix or suffix off the texts before diffing. + * @param {string} text1 Old string to be diffed. + * @param {string} text2 New string to be diffed. + * @param {boolean=} optChecklines Optional speedup flag. If present and false, + * then don't run a line-level diff first to identify the changed areas. + * Defaults to true, which does a faster, slightly less optimal diff. + * @return {!Array.} Array of diff tuples. + */ + DiffMatchPatch.prototype.DiffMain = function( text1, text2, optChecklines ) { + var deadline, checklines, commonlength, + commonprefix, commonsuffix, diffs; + + // The diff must be complete in up to 1 second. + deadline = ( new Date() ).getTime() + 1000; + + // Check for null inputs. + if ( text1 === null || text2 === null ) { + throw new Error( "Null input. (DiffMain)" ); + } + + // Check for equality (speedup). + if ( text1 === text2 ) { + if ( text1 ) { + return [ + [ DIFF_EQUAL, text1 ] + ]; } + return []; } - return { - o: o, - n: n - }; - } + if ( typeof optChecklines === "undefined" ) { + optChecklines = true; + } - return function( o, n ) { - o = o.replace( /\s+$/, "" ); - n = n.replace( /\s+$/, "" ); + checklines = optChecklines; + + // Trim off common prefix (speedup). + commonlength = this.diffCommonPrefix( text1, text2 ); + commonprefix = text1.substring( 0, commonlength ); + text1 = text1.substring( commonlength ); + text2 = text2.substring( commonlength ); + + // Trim off common suffix (speedup). + commonlength = this.diffCommonSuffix( text1, text2 ); + commonsuffix = text1.substring( text1.length - commonlength ); + text1 = text1.substring( 0, text1.length - commonlength ); + text2 = text2.substring( 0, text2.length - commonlength ); + + // Compute the diff on the middle block. + diffs = this.diffCompute( text1, text2, checklines, deadline ); + + // Restore the prefix and suffix. + if ( commonprefix ) { + diffs.unshift( [ DIFF_EQUAL, commonprefix ] ); + } + if ( commonsuffix ) { + diffs.push( [ DIFF_EQUAL, commonsuffix ] ); + } + this.diffCleanupMerge( diffs ); + return diffs; + }; + + /** + * Reduce the number of edits by eliminating operationally trivial equalities. + * @param {!Array.} diffs Array of diff tuples. + */ + DiffMatchPatch.prototype.diffCleanupEfficiency = function( diffs ) { + var changes, equalities, equalitiesLength, lastequality, + pointer, preIns, preDel, postIns, postDel; + changes = false; + equalities = []; // Stack of indices where equalities are found. + equalitiesLength = 0; // Keeping our own length var is faster in JS. + /** @type {?string} */ + lastequality = null; + // Always equal to diffs[equalities[equalitiesLength - 1]][1] + pointer = 0; // Index of current position. + // Is there an insertion operation before the last equality. + preIns = false; + // Is there a deletion operation before the last equality. + preDel = false; + // Is there an insertion operation after the last equality. + postIns = false; + // Is there a deletion operation after the last equality. + postDel = false; + while ( pointer < diffs.length ) { + + // Equality found. + if ( diffs[ pointer ][ 0 ] === DIFF_EQUAL ) { + if ( diffs[ pointer ][ 1 ].length < 4 && ( postIns || postDel ) ) { + + // Candidate found. + equalities[ equalitiesLength++ ] = pointer; + preIns = postIns; + preDel = postDel; + lastequality = diffs[ pointer ][ 1 ]; + } else { + + // Not a candidate, and can never become one. + equalitiesLength = 0; + lastequality = null; + } + postIns = postDel = false; + + // An insertion or deletion. + } else { + + if ( diffs[ pointer ][ 0 ] === DIFF_DELETE ) { + postDel = true; + } else { + postIns = true; + } + + /* + * Five types to be split: + * ABXYCD + * AXCD + * ABXC + * AXCD + * ABXC + */ + if ( lastequality && ( ( preIns && preDel && postIns && postDel ) || + ( ( lastequality.length < 2 ) && + ( preIns + preDel + postIns + postDel ) === 3 ) ) ) { + + // Duplicate record. + diffs.splice( + equalities[ equalitiesLength - 1 ], + 0, + [ DIFF_DELETE, lastequality ] + ); + + // Change second copy to insert. + diffs[ equalities[ equalitiesLength - 1 ] + 1 ][ 0 ] = DIFF_INSERT; + equalitiesLength--; // Throw away the equality we just deleted; + lastequality = null; + if ( preIns && preDel ) { + // No changes made which could affect previous entry, keep going. + postIns = postDel = true; + equalitiesLength = 0; + } else { + equalitiesLength--; // Throw away the previous equality. + pointer = equalitiesLength > 0 ? equalities[ equalitiesLength - 1 ] : -1; + postIns = postDel = false; + } + changes = true; + } + } + pointer++; + } + + if ( changes ) { + this.diffCleanupMerge( diffs ); + } + }; - var i, pre, - str = "", - out = diff( o === "" ? [] : o.split(/\s+/), n === "" ? [] : n.split(/\s+/) ), - oSpace = o.match(/\s+/g), - nSpace = n.match(/\s+/g); + /** + * Convert a diff array into a pretty HTML report. + * @param {!Array.} diffs Array of diff tuples. + * @param {integer} string to be beautified. + * @return {string} HTML representation. + */ + DiffMatchPatch.prototype.diffPrettyHtml = function( diffs ) { + var op, data, x, + html = []; + for ( x = 0; x < diffs.length; x++ ) { + op = diffs[ x ][ 0 ]; // Operation (insert, delete, equal) + data = diffs[ x ][ 1 ]; // Text of change. + switch ( op ) { + case DIFF_INSERT: + html[ x ] = "" + data + ""; + break; + case DIFF_DELETE: + html[ x ] = "" + data + ""; + break; + case DIFF_EQUAL: + html[ x ] = "" + data + ""; + break; + } + } + return html.join( "" ); + }; - if ( oSpace == null ) { - oSpace = [ " " ]; + /** + * Determine the common prefix of two strings. + * @param {string} text1 First string. + * @param {string} text2 Second string. + * @return {number} The number of characters common to the start of each + * string. + */ + DiffMatchPatch.prototype.diffCommonPrefix = function( text1, text2 ) { + var pointermid, pointermax, pointermin, pointerstart; + // Quick check for common null cases. + if ( !text1 || !text2 || text1.charAt( 0 ) !== text2.charAt( 0 ) ) { + return 0; + } + // Binary search. + // Performance analysis: https://neil.fraser.name/news/2007/10/09/ + pointermin = 0; + pointermax = Math.min( text1.length, text2.length ); + pointermid = pointermax; + pointerstart = 0; + while ( pointermin < pointermid ) { + if ( text1.substring( pointerstart, pointermid ) === + text2.substring( pointerstart, pointermid ) ) { + pointermin = pointermid; + pointerstart = pointermin; + } else { + pointermax = pointermid; + } + pointermid = Math.floor( ( pointermax - pointermin ) / 2 + pointermin ); + } + return pointermid; + }; + + /** + * Determine the common suffix of two strings. + * @param {string} text1 First string. + * @param {string} text2 Second string. + * @return {number} The number of characters common to the end of each string. + */ + DiffMatchPatch.prototype.diffCommonSuffix = function( text1, text2 ) { + var pointermid, pointermax, pointermin, pointerend; + // Quick check for common null cases. + if ( !text1 || + !text2 || + text1.charAt( text1.length - 1 ) !== text2.charAt( text2.length - 1 ) ) { + return 0; + } + // Binary search. + // Performance analysis: https://neil.fraser.name/news/2007/10/09/ + pointermin = 0; + pointermax = Math.min( text1.length, text2.length ); + pointermid = pointermax; + pointerend = 0; + while ( pointermin < pointermid ) { + if ( text1.substring( text1.length - pointermid, text1.length - pointerend ) === + text2.substring( text2.length - pointermid, text2.length - pointerend ) ) { + pointermin = pointermid; + pointerend = pointermin; + } else { + pointermax = pointermid; + } + pointermid = Math.floor( ( pointermax - pointermin ) / 2 + pointermin ); } - else { - oSpace.push( " " ); + return pointermid; + }; + + /** + * Find the differences between two texts. Assumes that the texts do not + * have any common prefix or suffix. + * @param {string} text1 Old string to be diffed. + * @param {string} text2 New string to be diffed. + * @param {boolean} checklines Speedup flag. If false, then don't run a + * line-level diff first to identify the changed areas. + * If true, then run a faster, slightly less optimal diff. + * @param {number} deadline Time when the diff should be complete by. + * @return {!Array.} Array of diff tuples. + * @private + */ + DiffMatchPatch.prototype.diffCompute = function( text1, text2, checklines, deadline ) { + var diffs, longtext, shorttext, i, hm, + text1A, text2A, text1B, text2B, + midCommon, diffsA, diffsB; + + if ( !text1 ) { + // Just add some text (speedup). + return [ + [ DIFF_INSERT, text2 ] + ]; + } + + if ( !text2 ) { + // Just delete some text (speedup). + return [ + [ DIFF_DELETE, text1 ] + ]; + } + + longtext = text1.length > text2.length ? text1 : text2; + shorttext = text1.length > text2.length ? text2 : text1; + i = longtext.indexOf( shorttext ); + if ( i !== -1 ) { + // Shorter text is inside the longer text (speedup). + diffs = [ + [ DIFF_INSERT, longtext.substring( 0, i ) ], + [ DIFF_EQUAL, shorttext ], + [ DIFF_INSERT, longtext.substring( i + shorttext.length ) ] + ]; + // Swap insertions for deletions if diff is reversed. + if ( text1.length > text2.length ) { + diffs[ 0 ][ 0 ] = diffs[ 2 ][ 0 ] = DIFF_DELETE; + } + return diffs; + } + + if ( shorttext.length === 1 ) { + // Single character string. + // After the previous speedup, the character can't be an equality. + return [ + [ DIFF_DELETE, text1 ], + [ DIFF_INSERT, text2 ] + ]; + } + + // Check to see if the problem can be split in two. + hm = this.diffHalfMatch( text1, text2 ); + if ( hm ) { + // A half-match was found, sort out the return data. + text1A = hm[ 0 ]; + text1B = hm[ 1 ]; + text2A = hm[ 2 ]; + text2B = hm[ 3 ]; + midCommon = hm[ 4 ]; + // Send both pairs off for separate processing. + diffsA = this.DiffMain( text1A, text2A, checklines, deadline ); + diffsB = this.DiffMain( text1B, text2B, checklines, deadline ); + // Merge the results. + return diffsA.concat( [ + [ DIFF_EQUAL, midCommon ] + ], diffsB ); + } + + if ( checklines && text1.length > 100 && text2.length > 100 ) { + return this.diffLineMode( text1, text2, deadline ); + } + + return this.diffBisect( text1, text2, deadline ); + }; + + /** + * Do the two texts share a substring which is at least half the length of the + * longer text? + * This speedup can produce non-minimal diffs. + * @param {string} text1 First string. + * @param {string} text2 Second string. + * @return {Array.} Five element Array, containing the prefix of + * text1, the suffix of text1, the prefix of text2, the suffix of + * text2 and the common middle. Or null if there was no match. + * @private + */ + DiffMatchPatch.prototype.diffHalfMatch = function( text1, text2 ) { + var longtext, shorttext, dmp, + text1A, text2B, text2A, text1B, midCommon, + hm1, hm2, hm; + + longtext = text1.length > text2.length ? text1 : text2; + shorttext = text1.length > text2.length ? text2 : text1; + if ( longtext.length < 4 || shorttext.length * 2 < longtext.length ) { + return null; // Pointless. + } + dmp = this; // 'this' becomes 'window' in a closure. + + /** + * Does a substring of shorttext exist within longtext such that the substring + * is at least half the length of longtext? + * Closure, but does not reference any external variables. + * @param {string} longtext Longer string. + * @param {string} shorttext Shorter string. + * @param {number} i Start index of quarter length substring within longtext. + * @return {Array.} Five element Array, containing the prefix of + * longtext, the suffix of longtext, the prefix of shorttext, the suffix + * of shorttext and the common middle. Or null if there was no match. + * @private + */ + function diffHalfMatchI( longtext, shorttext, i ) { + var seed, j, bestCommon, prefixLength, suffixLength, + bestLongtextA, bestLongtextB, bestShorttextA, bestShorttextB; + // Start with a 1/4 length substring at position i as a seed. + seed = longtext.substring( i, i + Math.floor( longtext.length / 4 ) ); + j = -1; + bestCommon = ""; + while ( ( j = shorttext.indexOf( seed, j + 1 ) ) !== -1 ) { + prefixLength = dmp.diffCommonPrefix( longtext.substring( i ), + shorttext.substring( j ) ); + suffixLength = dmp.diffCommonSuffix( longtext.substring( 0, i ), + shorttext.substring( 0, j ) ); + if ( bestCommon.length < suffixLength + prefixLength ) { + bestCommon = shorttext.substring( j - suffixLength, j ) + + shorttext.substring( j, j + prefixLength ); + bestLongtextA = longtext.substring( 0, i - suffixLength ); + bestLongtextB = longtext.substring( i + prefixLength ); + bestShorttextA = shorttext.substring( 0, j - suffixLength ); + bestShorttextB = shorttext.substring( j + prefixLength ); + } + } + if ( bestCommon.length * 2 >= longtext.length ) { + return [ bestLongtextA, bestLongtextB, + bestShorttextA, bestShorttextB, bestCommon + ]; + } else { + return null; + } + } + + // First check if the second quarter is the seed for a half-match. + hm1 = diffHalfMatchI( longtext, shorttext, + Math.ceil( longtext.length / 4 ) ); + // Check again based on the third quarter. + hm2 = diffHalfMatchI( longtext, shorttext, + Math.ceil( longtext.length / 2 ) ); + if ( !hm1 && !hm2 ) { + return null; + } else if ( !hm2 ) { + hm = hm1; + } else if ( !hm1 ) { + hm = hm2; + } else { + // Both matched. Select the longest. + hm = hm1[ 4 ].length > hm2[ 4 ].length ? hm1 : hm2; + } + + // A half-match was found, sort out the return data. + text1A, text1B, text2A, text2B; + if ( text1.length > text2.length ) { + text1A = hm[ 0 ]; + text1B = hm[ 1 ]; + text2A = hm[ 2 ]; + text2B = hm[ 3 ]; + } else { + text2A = hm[ 0 ]; + text2B = hm[ 1 ]; + text1A = hm[ 2 ]; + text1B = hm[ 3 ]; + } + midCommon = hm[ 4 ]; + return [ text1A, text1B, text2A, text2B, midCommon ]; + }; + + /** + * Do a quick line-level diff on both strings, then rediff the parts for + * greater accuracy. + * This speedup can produce non-minimal diffs. + * @param {string} text1 Old string to be diffed. + * @param {string} text2 New string to be diffed. + * @param {number} deadline Time when the diff should be complete by. + * @return {!Array.} Array of diff tuples. + * @private + */ + DiffMatchPatch.prototype.diffLineMode = function( text1, text2, deadline ) { + var a, diffs, linearray, pointer, countInsert, + countDelete, textInsert, textDelete, j; + // Scan the text on a line-by-line basis first. + a = this.diffLinesToChars( text1, text2 ); + text1 = a.chars1; + text2 = a.chars2; + linearray = a.lineArray; + + diffs = this.DiffMain( text1, text2, false, deadline ); + + // Convert the diff back to original text. + this.diffCharsToLines( diffs, linearray ); + // Eliminate freak matches (e.g. blank lines) + this.diffCleanupSemantic( diffs ); + + // Rediff any replacement blocks, this time character-by-character. + // Add a dummy entry at the end. + diffs.push( [ DIFF_EQUAL, "" ] ); + pointer = 0; + countDelete = 0; + countInsert = 0; + textDelete = ""; + textInsert = ""; + while ( pointer < diffs.length ) { + switch ( diffs[ pointer ][ 0 ] ) { + case DIFF_INSERT: + countInsert++; + textInsert += diffs[ pointer ][ 1 ]; + break; + case DIFF_DELETE: + countDelete++; + textDelete += diffs[ pointer ][ 1 ]; + break; + case DIFF_EQUAL: + // Upon reaching an equality, check for prior redundancies. + if ( countDelete >= 1 && countInsert >= 1 ) { + // Delete the offending records and add the merged ones. + diffs.splice( pointer - countDelete - countInsert, + countDelete + countInsert ); + pointer = pointer - countDelete - countInsert; + a = this.DiffMain( textDelete, textInsert, false, deadline ); + for ( j = a.length - 1; j >= 0; j-- ) { + diffs.splice( pointer, 0, a[ j ] ); + } + pointer = pointer + a.length; + } + countInsert = 0; + countDelete = 0; + textDelete = ""; + textInsert = ""; + break; + } + pointer++; } + diffs.pop(); // Remove the dummy entry at the end. + + return diffs; + }; - if ( nSpace == null ) { - nSpace = [ " " ]; + /** + * Find the 'middle snake' of a diff, split the problem in two + * and return the recursively constructed diff. + * See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations. + * @param {string} text1 Old string to be diffed. + * @param {string} text2 New string to be diffed. + * @param {number} deadline Time at which to bail if not yet complete. + * @return {!Array.} Array of diff tuples. + * @private + */ + DiffMatchPatch.prototype.diffBisect = function( text1, text2, deadline ) { + var text1Length, text2Length, maxD, vOffset, vLength, + v1, v2, x, delta, front, k1start, k1end, k2start, + k2end, k2Offset, k1Offset, x1, x2, y1, y2, d, k1, k2; + // Cache the text lengths to prevent multiple calls. + text1Length = text1.length; + text2Length = text2.length; + maxD = Math.ceil( ( text1Length + text2Length ) / 2 ); + vOffset = maxD; + vLength = 2 * maxD; + v1 = new Array( vLength ); + v2 = new Array( vLength ); + // Setting all elements to -1 is faster in Chrome & Firefox than mixing + // integers and undefined. + for ( x = 0; x < vLength; x++ ) { + v1[ x ] = -1; + v2[ x ] = -1; } - else { - nSpace.push( " " ); + v1[ vOffset + 1 ] = 0; + v2[ vOffset + 1 ] = 0; + delta = text1Length - text2Length; + // If the total number of characters is odd, then the front path will collide + // with the reverse path. + front = ( delta % 2 !== 0 ); + // Offsets for start and end of k loop. + // Prevents mapping of space beyond the grid. + k1start = 0; + k1end = 0; + k2start = 0; + k2end = 0; + for ( d = 0; d < maxD; d++ ) { + // Bail out if deadline is reached. + if ( ( new Date() ).getTime() > deadline ) { + break; + } + + // Walk the front path one step. + for ( k1 = -d + k1start; k1 <= d - k1end; k1 += 2 ) { + k1Offset = vOffset + k1; + if ( k1 === -d || ( k1 !== d && v1[ k1Offset - 1 ] < v1[ k1Offset + 1 ] ) ) { + x1 = v1[ k1Offset + 1 ]; + } else { + x1 = v1[ k1Offset - 1 ] + 1; + } + y1 = x1 - k1; + while ( x1 < text1Length && y1 < text2Length && + text1.charAt( x1 ) === text2.charAt( y1 ) ) { + x1++; + y1++; + } + v1[ k1Offset ] = x1; + if ( x1 > text1Length ) { + // Ran off the right of the graph. + k1end += 2; + } else if ( y1 > text2Length ) { + // Ran off the bottom of the graph. + k1start += 2; + } else if ( front ) { + k2Offset = vOffset + delta - k1; + if ( k2Offset >= 0 && k2Offset < vLength && v2[ k2Offset ] !== -1 ) { + // Mirror x2 onto top-left coordinate system. + x2 = text1Length - v2[ k2Offset ]; + if ( x1 >= x2 ) { + // Overlap detected. + return this.diffBisectSplit( text1, text2, x1, y1, deadline ); + } + } + } + } + + // Walk the reverse path one step. + for ( k2 = -d + k2start; k2 <= d - k2end; k2 += 2 ) { + k2Offset = vOffset + k2; + if ( k2 === -d || ( k2 !== d && v2[ k2Offset - 1 ] < v2[ k2Offset + 1 ] ) ) { + x2 = v2[ k2Offset + 1 ]; + } else { + x2 = v2[ k2Offset - 1 ] + 1; + } + y2 = x2 - k2; + while ( x2 < text1Length && y2 < text2Length && + text1.charAt( text1Length - x2 - 1 ) === + text2.charAt( text2Length - y2 - 1 ) ) { + x2++; + y2++; + } + v2[ k2Offset ] = x2; + if ( x2 > text1Length ) { + // Ran off the left of the graph. + k2end += 2; + } else if ( y2 > text2Length ) { + // Ran off the top of the graph. + k2start += 2; + } else if ( !front ) { + k1Offset = vOffset + delta - k2; + if ( k1Offset >= 0 && k1Offset < vLength && v1[ k1Offset ] !== -1 ) { + x1 = v1[ k1Offset ]; + y1 = vOffset + x1 - k1Offset; + // Mirror x2 onto top-left coordinate system. + x2 = text1Length - x2; + if ( x1 >= x2 ) { + // Overlap detected. + return this.diffBisectSplit( text1, text2, x1, y1, deadline ); + } + } + } + } } + // Diff took too long and hit the deadline or + // number of diffs equals number of characters, no commonality at all. + return [ + [ DIFF_DELETE, text1 ], + [ DIFF_INSERT, text2 ] + ]; + }; + + /** + * Given the location of the 'middle snake', split the diff in two parts + * and recurse. + * @param {string} text1 Old string to be diffed. + * @param {string} text2 New string to be diffed. + * @param {number} x Index of split point in text1. + * @param {number} y Index of split point in text2. + * @param {number} deadline Time at which to bail if not yet complete. + * @return {!Array.} Array of diff tuples. + * @private + */ + DiffMatchPatch.prototype.diffBisectSplit = function( text1, text2, x, y, deadline ) { + var text1a, text1b, text2a, text2b, diffs, diffsb; + text1a = text1.substring( 0, x ); + text2a = text2.substring( 0, y ); + text1b = text1.substring( x ); + text2b = text2.substring( y ); + + // Compute both diffs serially. + diffs = this.DiffMain( text1a, text2a, false, deadline ); + diffsb = this.DiffMain( text1b, text2b, false, deadline ); + + return diffs.concat( diffsb ); + }; + + /** + * Reduce the number of edits by eliminating semantically trivial equalities. + * @param {!Array.} diffs Array of diff tuples. + */ + DiffMatchPatch.prototype.diffCleanupSemantic = function( diffs ) { + var changes, equalities, equalitiesLength, lastequality, + pointer, lengthInsertions2, lengthDeletions2, lengthInsertions1, + lengthDeletions1, deletion, insertion, overlapLength1, overlapLength2; + changes = false; + equalities = []; // Stack of indices where equalities are found. + equalitiesLength = 0; // Keeping our own length var is faster in JS. + /** @type {?string} */ + lastequality = null; + // Always equal to diffs[equalities[equalitiesLength - 1]][1] + pointer = 0; // Index of current position. + // Number of characters that changed prior to the equality. + lengthInsertions1 = 0; + lengthDeletions1 = 0; + // Number of characters that changed after the equality. + lengthInsertions2 = 0; + lengthDeletions2 = 0; + while ( pointer < diffs.length ) { + if ( diffs[ pointer ][ 0 ] === DIFF_EQUAL ) { // Equality found. + equalities[ equalitiesLength++ ] = pointer; + lengthInsertions1 = lengthInsertions2; + lengthDeletions1 = lengthDeletions2; + lengthInsertions2 = 0; + lengthDeletions2 = 0; + lastequality = diffs[ pointer ][ 1 ]; + } else { // An insertion or deletion. + if ( diffs[ pointer ][ 0 ] === DIFF_INSERT ) { + lengthInsertions2 += diffs[ pointer ][ 1 ].length; + } else { + lengthDeletions2 += diffs[ pointer ][ 1 ].length; + } + // Eliminate an equality that is smaller or equal to the edits on both + // sides of it. + if ( lastequality && ( lastequality.length <= + Math.max( lengthInsertions1, lengthDeletions1 ) ) && + ( lastequality.length <= Math.max( lengthInsertions2, + lengthDeletions2 ) ) ) { + + // Duplicate record. + diffs.splice( + equalities[ equalitiesLength - 1 ], + 0, + [ DIFF_DELETE, lastequality ] + ); + + // Change second copy to insert. + diffs[ equalities[ equalitiesLength - 1 ] + 1 ][ 0 ] = DIFF_INSERT; - if ( out.n.length === 0 ) { - for ( i = 0; i < out.o.length; i++ ) { - str += "" + out.o[i] + oSpace[i] + ""; + // Throw away the equality we just deleted. + equalitiesLength--; + + // Throw away the previous equality (it needs to be reevaluated). + equalitiesLength--; + pointer = equalitiesLength > 0 ? equalities[ equalitiesLength - 1 ] : -1; + + // Reset the counters. + lengthInsertions1 = 0; + lengthDeletions1 = 0; + lengthInsertions2 = 0; + lengthDeletions2 = 0; + lastequality = null; + changes = true; + } } + pointer++; + } + + // Normalize the diff. + if ( changes ) { + this.diffCleanupMerge( diffs ); } - else { - if ( out.n[0].text == null ) { - for ( n = 0; n < out.o.length && out.o[n].text == null; n++ ) { - str += "" + out.o[n] + oSpace[n] + ""; + + // Find any overlaps between deletions and insertions. + // e.g: abcxxxxxxdef + // -> abcxxxdef + // e.g: xxxabcdefxxx + // -> defxxxabc + // Only extract an overlap if it is as big as the edit ahead or behind it. + pointer = 1; + while ( pointer < diffs.length ) { + if ( diffs[ pointer - 1 ][ 0 ] === DIFF_DELETE && + diffs[ pointer ][ 0 ] === DIFF_INSERT ) { + deletion = diffs[ pointer - 1 ][ 1 ]; + insertion = diffs[ pointer ][ 1 ]; + overlapLength1 = this.diffCommonOverlap( deletion, insertion ); + overlapLength2 = this.diffCommonOverlap( insertion, deletion ); + if ( overlapLength1 >= overlapLength2 ) { + if ( overlapLength1 >= deletion.length / 2 || + overlapLength1 >= insertion.length / 2 ) { + // Overlap found. Insert an equality and trim the surrounding edits. + diffs.splice( + pointer, + 0, + [ DIFF_EQUAL, insertion.substring( 0, overlapLength1 ) ] + ); + diffs[ pointer - 1 ][ 1 ] = + deletion.substring( 0, deletion.length - overlapLength1 ); + diffs[ pointer + 1 ][ 1 ] = insertion.substring( overlapLength1 ); + pointer++; + } + } else { + if ( overlapLength2 >= deletion.length / 2 || + overlapLength2 >= insertion.length / 2 ) { + + // Reverse overlap found. + // Insert an equality and swap and trim the surrounding edits. + diffs.splice( + pointer, + 0, + [ DIFF_EQUAL, deletion.substring( 0, overlapLength2 ) ] + ); + + diffs[ pointer - 1 ][ 0 ] = DIFF_INSERT; + diffs[ pointer - 1 ][ 1 ] = + insertion.substring( 0, insertion.length - overlapLength2 ); + diffs[ pointer + 1 ][ 0 ] = DIFF_DELETE; + diffs[ pointer + 1 ][ 1 ] = + deletion.substring( overlapLength2 ); + pointer++; + } } + pointer++; } + pointer++; + } + }; + + /** + * Determine if the suffix of one string is the prefix of another. + * @param {string} text1 First string. + * @param {string} text2 Second string. + * @return {number} The number of characters common to the end of the first + * string and the start of the second string. + * @private + */ + DiffMatchPatch.prototype.diffCommonOverlap = function( text1, text2 ) { + var text1Length, text2Length, textLength, + best, length, pattern, found; + // Cache the text lengths to prevent multiple calls. + text1Length = text1.length; + text2Length = text2.length; + // Eliminate the null case. + if ( text1Length === 0 || text2Length === 0 ) { + return 0; + } + // Truncate the longer string. + if ( text1Length > text2Length ) { + text1 = text1.substring( text1Length - text2Length ); + } else if ( text1Length < text2Length ) { + text2 = text2.substring( 0, text1Length ); + } + textLength = Math.min( text1Length, text2Length ); + // Quick check for the worst case. + if ( text1 === text2 ) { + return textLength; + } + + // Start by looking for a single character match + // and increase length until no match is found. + // Performance analysis: https://neil.fraser.name/news/2010/11/04/ + best = 0; + length = 1; + while ( true ) { + pattern = text1.substring( textLength - length ); + found = text2.indexOf( pattern ); + if ( found === -1 ) { + return best; + } + length += found; + if ( found === 0 || text1.substring( textLength - length ) === + text2.substring( 0, length ) ) { + best = length; + length++; + } + } + }; - for ( i = 0; i < out.n.length; i++ ) { - if (out.n[i].text == null) { - str += "" + out.n[i] + nSpace[i] + ""; + /** + * Split two texts into an array of strings. Reduce the texts to a string of + * hashes where each Unicode character represents one line. + * @param {string} text1 First string. + * @param {string} text2 Second string. + * @return {{chars1: string, chars2: string, lineArray: !Array.}} + * An object containing the encoded text1, the encoded text2 and + * the array of unique strings. + * The zeroth element of the array of unique strings is intentionally blank. + * @private + */ + DiffMatchPatch.prototype.diffLinesToChars = function( text1, text2 ) { + var lineArray, lineHash, chars1, chars2; + lineArray = []; // e.g. lineArray[4] === 'Hello\n' + lineHash = {}; // e.g. lineHash['Hello\n'] === 4 + + // '\x00' is a valid character, but various debuggers don't like it. + // So we'll insert a junk entry to avoid generating a null character. + lineArray[ 0 ] = ""; + + /** + * Split a text into an array of strings. Reduce the texts to a string of + * hashes where each Unicode character represents one line. + * Modifies linearray and linehash through being a closure. + * @param {string} text String to encode. + * @return {string} Encoded string. + * @private + */ + function diffLinesToCharsMunge( text ) { + var chars, lineStart, lineEnd, lineArrayLength, line; + chars = ""; + // Walk the text, pulling out a substring for each line. + // text.split('\n') would would temporarily double our memory footprint. + // Modifying text would create many large strings to garbage collect. + lineStart = 0; + lineEnd = -1; + // Keeping our own length variable is faster than looking it up. + lineArrayLength = lineArray.length; + while ( lineEnd < text.length - 1 ) { + lineEnd = text.indexOf( "\n", lineStart ); + if ( lineEnd === -1 ) { + lineEnd = text.length - 1; } - else { - // `pre` initialized at top of scope - pre = ""; + line = text.substring( lineStart, lineEnd + 1 ); + lineStart = lineEnd + 1; + + if ( lineHash.hasOwnProperty ? lineHash.hasOwnProperty( line ) : + ( lineHash[ line ] !== undefined ) ) { + chars += String.fromCharCode( lineHash[ line ] ); + } else { + chars += String.fromCharCode( lineArrayLength ); + lineHash[ line ] = lineArrayLength; + lineArray[ lineArrayLength++ ] = line; + } + } + return chars; + } + + chars1 = diffLinesToCharsMunge( text1 ); + chars2 = diffLinesToCharsMunge( text2 ); + return { + chars1: chars1, + chars2: chars2, + lineArray: lineArray + }; + }; - for ( n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++ ) { - pre += "" + out.o[n] + oSpace[n] + ""; + /** + * Rehydrate the text in a diff from a string of line hashes to real lines of + * text. + * @param {!Array.} diffs Array of diff tuples. + * @param {!Array.} lineArray Array of unique strings. + * @private + */ + DiffMatchPatch.prototype.diffCharsToLines = function( diffs, lineArray ) { + var x, chars, text, y; + for ( x = 0; x < diffs.length; x++ ) { + chars = diffs[ x ][ 1 ]; + text = []; + for ( y = 0; y < chars.length; y++ ) { + text[ y ] = lineArray[ chars.charCodeAt( y ) ]; + } + diffs[ x ][ 1 ] = text.join( "" ); + } + }; + + /** + * Reorder and merge like edit sections. Merge equalities. + * Any edit section can move as long as it doesn't cross an equality. + * @param {!Array.} diffs Array of diff tuples. + */ + DiffMatchPatch.prototype.diffCleanupMerge = function( diffs ) { + var pointer, countDelete, countInsert, textInsert, textDelete, + commonlength, changes, diffPointer, position; + diffs.push( [ DIFF_EQUAL, "" ] ); // Add a dummy entry at the end. + pointer = 0; + countDelete = 0; + countInsert = 0; + textDelete = ""; + textInsert = ""; + commonlength; + while ( pointer < diffs.length ) { + switch ( diffs[ pointer ][ 0 ] ) { + case DIFF_INSERT: + countInsert++; + textInsert += diffs[ pointer ][ 1 ]; + pointer++; + break; + case DIFF_DELETE: + countDelete++; + textDelete += diffs[ pointer ][ 1 ]; + pointer++; + break; + case DIFF_EQUAL: + // Upon reaching an equality, check for prior redundancies. + if ( countDelete + countInsert > 1 ) { + if ( countDelete !== 0 && countInsert !== 0 ) { + // Factor out any common prefixes. + commonlength = this.diffCommonPrefix( textInsert, textDelete ); + if ( commonlength !== 0 ) { + if ( ( pointer - countDelete - countInsert ) > 0 && + diffs[ pointer - countDelete - countInsert - 1 ][ 0 ] === + DIFF_EQUAL ) { + diffs[ pointer - countDelete - countInsert - 1 ][ 1 ] += + textInsert.substring( 0, commonlength ); + } else { + diffs.splice( 0, 0, [ DIFF_EQUAL, + textInsert.substring( 0, commonlength ) + ] ); + pointer++; + } + textInsert = textInsert.substring( commonlength ); + textDelete = textDelete.substring( commonlength ); + } + // Factor out any common suffixies. + commonlength = this.diffCommonSuffix( textInsert, textDelete ); + if ( commonlength !== 0 ) { + diffs[ pointer ][ 1 ] = textInsert.substring( textInsert.length - + commonlength ) + diffs[ pointer ][ 1 ]; + textInsert = textInsert.substring( 0, textInsert.length - + commonlength ); + textDelete = textDelete.substring( 0, textDelete.length - + commonlength ); + } + } + // Delete the offending records and add the merged ones. + if ( countDelete === 0 ) { + diffs.splice( pointer - countInsert, + countDelete + countInsert, [ DIFF_INSERT, textInsert ] ); + } else if ( countInsert === 0 ) { + diffs.splice( pointer - countDelete, + countDelete + countInsert, [ DIFF_DELETE, textDelete ] ); + } else { + diffs.splice( + pointer - countDelete - countInsert, + countDelete + countInsert, + [ DIFF_DELETE, textDelete ], [ DIFF_INSERT, textInsert ] + ); } - str += " " + out.n[i].text + nSpace[i] + pre; + pointer = pointer - countDelete - countInsert + + ( countDelete ? 1 : 0 ) + ( countInsert ? 1 : 0 ) + 1; + } else if ( pointer !== 0 && diffs[ pointer - 1 ][ 0 ] === DIFF_EQUAL ) { + + // Merge this equality with the previous one. + diffs[ pointer - 1 ][ 1 ] += diffs[ pointer ][ 1 ]; + diffs.splice( pointer, 1 ); + } else { + pointer++; + } + countInsert = 0; + countDelete = 0; + textDelete = ""; + textInsert = ""; + break; + } + } + if ( diffs[ diffs.length - 1 ][ 1 ] === "" ) { + diffs.pop(); // Remove the dummy entry at the end. + } + + // Second pass: look for single edits surrounded on both sides by equalities + // which can be shifted sideways to eliminate an equality. + // e.g: ABAC -> ABAC + changes = false; + pointer = 1; + + // Intentionally ignore the first and last element (don't need checking). + while ( pointer < diffs.length - 1 ) { + if ( diffs[ pointer - 1 ][ 0 ] === DIFF_EQUAL && + diffs[ pointer + 1 ][ 0 ] === DIFF_EQUAL ) { + + diffPointer = diffs[ pointer ][ 1 ]; + position = diffPointer.substring( + diffPointer.length - diffs[ pointer - 1 ][ 1 ].length + ); + + // This is a single edit surrounded by equalities. + if ( position === diffs[ pointer - 1 ][ 1 ] ) { + + // Shift the edit over the previous equality. + diffs[ pointer ][ 1 ] = diffs[ pointer - 1 ][ 1 ] + + diffs[ pointer ][ 1 ].substring( 0, diffs[ pointer ][ 1 ].length - + diffs[ pointer - 1 ][ 1 ].length ); + diffs[ pointer + 1 ][ 1 ] = + diffs[ pointer - 1 ][ 1 ] + diffs[ pointer + 1 ][ 1 ]; + diffs.splice( pointer - 1, 1 ); + changes = true; + } else if ( diffPointer.substring( 0, diffs[ pointer + 1 ][ 1 ].length ) === + diffs[ pointer + 1 ][ 1 ] ) { + + // Shift the edit over the next equality. + diffs[ pointer - 1 ][ 1 ] += diffs[ pointer + 1 ][ 1 ]; + diffs[ pointer ][ 1 ] = + diffs[ pointer ][ 1 ].substring( diffs[ pointer + 1 ][ 1 ].length ) + + diffs[ pointer + 1 ][ 1 ]; + diffs.splice( pointer + 1, 1 ); + changes = true; } } + pointer++; + } + // If shifts were made, the diff needs reordering and another shift sweep. + if ( changes ) { + this.diffCleanupMerge( diffs ); } + }; + + return function( o, n ) { + var diff, output, text; + diff = new DiffMatchPatch(); + output = diff.DiffMain( o, n ); + diff.diffCleanupEfficiency( output ); + text = diff.diffPrettyHtml( output ); - return str; + return text; }; -}()); +}() ); diff -Nru libjs-qunit-1.14.0/src/dump.js libjs-qunit-1.22.0/src/dump.js --- libjs-qunit-1.14.0/src/dump.js 2014-01-31 16:41:24.000000000 +0000 +++ libjs-qunit-1.22.0/src/dump.js 2016-02-23 15:57:56.000000000 +0000 @@ -1,63 +1,64 @@ -/** - * jsDump Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | - * http://flesler.blogspot.com Licensed under BSD - * (http://www.opensource.org/licenses/bsd-license.php) Date: 5/15/2008 - * - * @projectDescription Advanced and extensible data dumping for Javascript. - * @version 1.0.0 - * @author Ariel Flesler - * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html} - */ -QUnit.jsDump = (function() { +// Based on jsDump by Ariel Flesler +// http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html +QUnit.dump = (function() { function quote( str ) { - return "\"" + str.toString().replace( /"/g, "\\\"" ) + "\""; + return "\"" + str.toString().replace( /\\/g, "\\\\" ).replace( /"/g, "\\\"" ) + "\""; } function literal( o ) { return o + ""; } function join( pre, arr, post ) { - var s = jsDump.separator(), - base = jsDump.indent(), - inner = jsDump.indent(1); + var s = dump.separator(), + base = dump.indent(), + inner = dump.indent( 1 ); if ( arr.join ) { arr = arr.join( "," + s + inner ); } if ( !arr ) { return pre + post; } - return [ pre, inner + arr, base + post ].join(s); + return [ pre, inner + arr, base + post ].join( s ); } function array( arr, stack ) { - var i = arr.length, ret = new Array(i); + var i = arr.length, + ret = new Array( i ); + + if ( dump.maxDepth && dump.depth > dump.maxDepth ) { + return "[object Array]"; + } + this.up(); while ( i-- ) { - ret[i] = this.parse( arr[i] , undefined , stack); + ret[ i ] = this.parse( arr[ i ], undefined, stack ); } this.down(); return join( "[", ret, "]" ); } var reName = /^function (\w+)/, - jsDump = { - // type is used mostly internally, you can fix a (custom)type in advance - parse: function( obj, type, stack ) { - stack = stack || [ ]; - var inStack, res, - parser = this.parsers[ type || this.typeOf(obj) ]; + dump = { - type = typeof parser; - inStack = inArray( obj, stack ); + // objType is used mostly internally, you can fix a (custom) type in advance + parse: function( obj, objType, stack ) { + stack = stack || []; + var res, parser, parserType, + inStack = inArray( obj, stack ); if ( inStack !== -1 ) { - return "recursion(" + (inStack - stack.length) + ")"; + return "recursion(" + ( inStack - stack.length ) + ")"; } - if ( type === "function" ) { + + objType = objType || this.typeOf( obj ); + parser = this.parsers[ objType ]; + parserType = typeof parser; + + if ( parserType === "function" ) { stack.push( obj ); res = parser.call( this, obj, stack ); stack.pop(); return res; } - return ( type === "string" ) ? parser : this.parsers.error; + return ( parserType === "string" ) ? parser : this.parsers.error; }, typeOf: function( obj ) { var type; @@ -65,23 +66,29 @@ type = "null"; } else if ( typeof obj === "undefined" ) { type = "undefined"; - } else if ( QUnit.is( "regexp", obj) ) { + } else if ( QUnit.is( "regexp", obj ) ) { type = "regexp"; - } else if ( QUnit.is( "date", obj) ) { + } else if ( QUnit.is( "date", obj ) ) { type = "date"; - } else if ( QUnit.is( "function", obj) ) { + } else if ( QUnit.is( "function", obj ) ) { type = "function"; - } else if ( typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined" ) { + } else if ( obj.setInterval !== undefined && + obj.document !== undefined && + obj.nodeType === undefined ) { type = "window"; } else if ( obj.nodeType === 9 ) { type = "document"; } else if ( obj.nodeType ) { type = "node"; } else if ( + // native arrays toString.call( obj ) === "[object Array]" || + // NodeList objects - ( typeof obj.length === "number" && typeof obj.item !== "undefined" && ( obj.length ? obj.item(0) === obj[0] : ( obj.item( 0 ) === null && typeof obj[0] === "undefined" ) ) ) + ( typeof obj.length === "number" && obj.item !== undefined && + ( obj.length ? obj.item( 0 ) === obj[ 0 ] : ( obj.item( 0 ) === null && + obj[ 0 ] === undefined ) ) ) ) { type = "array"; } else if ( obj.constructor === Error.prototype.constructor ) { @@ -92,7 +99,7 @@ return type; }, separator: function() { - return this.multiline ? this.HTML ? "
              " : "\n" : this.HTML ? " " : " "; + return this.multiline ? this.HTML ? "
              " : "\n" : this.HTML ? " " : " "; }, // extra can be a number, shortcut for increasing-calling-decreasing indent: function( extra ) { @@ -101,9 +108,9 @@ } var chr = this.indentChar; if ( this.HTML ) { - chr = chr.replace( /\t/g, " " ).replace( / /g, " " ); + chr = chr.replace( /\t/g, " " ).replace( / /g, " " ); } - return new Array( this.depth + ( extra || 0 ) ).join(chr); + return new Array( this.depth + ( extra || 0 ) ).join( chr ); }, up: function( a ) { this.depth += a || 1; @@ -112,7 +119,7 @@ this.depth -= a || 1; }, setParser: function( name, parser ) { - this.parsers[name] = parser; + this.parsers[ name ] = parser; }, // The next 3 are exposed so you can use them quote: quote, @@ -120,11 +127,13 @@ join: join, // depth: 1, - // This is the list of parsers, to modify them, use jsDump.setParser + maxDepth: QUnit.config.maxDepth, + + // This is the list of parsers, to modify them, use dump.setParser parsers: { window: "[Window]", document: "[Document]", - error: function(error) { + error: function( error ) { return "Error(\"" + error.message + "\")"; }, unknown: "[Unknown]", @@ -132,52 +141,71 @@ "undefined": "undefined", "function": function( fn ) { var ret = "function", + // functions never have name in IE - name = "name" in fn ? fn.name : (reName.exec(fn) || [])[1]; + name = "name" in fn ? fn.name : ( reName.exec( fn ) || [] )[ 1 ]; if ( name ) { ret += " " + name; } ret += "( "; - ret = [ ret, QUnit.jsDump.parse( fn, "functionArgs" ), "){" ].join( "" ); - return join( ret, QUnit.jsDump.parse(fn,"functionCode" ), "}" ); + ret = [ ret, dump.parse( fn, "functionArgs" ), "){" ].join( "" ); + return join( ret, dump.parse( fn, "functionCode" ), "}" ); }, array: array, nodelist: array, "arguments": array, object: function( map, stack ) { - /*jshint forin:false */ - var ret = [ ], keys, key, val, i; - QUnit.jsDump.up(); + var keys, key, val, i, nonEnumerableProperties, + ret = []; + + if ( dump.maxDepth && dump.depth > dump.maxDepth ) { + return "[object Object]"; + } + + dump.up(); keys = []; for ( key in map ) { keys.push( key ); } + + // Some properties are not always enumerable on Error objects. + nonEnumerableProperties = [ "message", "name" ]; + for ( i in nonEnumerableProperties ) { + key = nonEnumerableProperties[ i ]; + if ( key in map && inArray( key, keys ) < 0 ) { + keys.push( key ); + } + } keys.sort(); for ( i = 0; i < keys.length; i++ ) { key = keys[ i ]; val = map[ key ]; - ret.push( QUnit.jsDump.parse( key, "key" ) + ": " + QUnit.jsDump.parse( val, undefined, stack ) ); + ret.push( dump.parse( key, "key" ) + ": " + + dump.parse( val, undefined, stack ) ); } - QUnit.jsDump.down(); + dump.down(); return join( "{", ret, "}" ); }, node: function( node ) { var len, i, val, - open = QUnit.jsDump.HTML ? "<" : "<", - close = QUnit.jsDump.HTML ? ">" : ">", + open = dump.HTML ? "<" : "<", + close = dump.HTML ? ">" : ">", tag = node.nodeName.toLowerCase(), ret = open + tag, attrs = node.attributes; if ( attrs ) { for ( i = 0, len = attrs.length; i < len; i++ ) { - val = attrs[i].nodeValue; - // IE6 includes all attributes in .attributes, even ones not explicitly set. - // Those have values like undefined, null, 0, false, "" or "inherit". + val = attrs[ i ].nodeValue; + + // IE6 includes all attributes in .attributes, even ones not explicitly + // set. Those have values like undefined, null, 0, false, "" or + // "inherit". if ( val && val !== "inherit" ) { - ret += " " + attrs[i].nodeName + "=" + QUnit.jsDump.parse( val, "attribute" ); + ret += " " + attrs[ i ].nodeName + "=" + + dump.parse( val, "attribute" ); } } } @@ -190,6 +218,7 @@ return ret + open + "/" + tag + close; }, + // function calls it internally, it's the arguments part of the function functionArgs: function( fn ) { var args, @@ -199,10 +228,11 @@ return ""; } - args = new Array(l); + args = new Array( l ); while ( l-- ) { + // 97 is 'a' - args[l] = String.fromCharCode(97+l); + args[ l ] = String.fromCharCode( 97 + l ); } return " " + args.join( ", " ) + " "; }, @@ -210,7 +240,7 @@ key: quote, // function calls it internally, it's the content of the function functionCode: "[code]", - // node calls it internally, it's an html attribute value + // node calls it internally, it's a html attribute value attribute: quote, string: quote, date: quote, @@ -226,5 +256,8 @@ multiline: true }; - return jsDump; + return dump; }()); + +// back compat +QUnit.jsDump = QUnit.dump; diff -Nru libjs-qunit-1.14.0/src/equiv.js libjs-qunit-1.22.0/src/equiv.js --- libjs-qunit-1.14.0/src/equiv.js 2014-01-31 16:41:24.000000000 +0000 +++ libjs-qunit-1.22.0/src/equiv.js 2016-02-23 15:57:56.000000000 +0000 @@ -2,206 +2,240 @@ // Author: Philippe Rathé QUnit.equiv = (function() { - // Call the o related callback with the given arguments. - function bindCallbacks( o, callbacks, args ) { - var prop = QUnit.objectType( o ); - if ( prop ) { - if ( QUnit.objectType( callbacks[ prop ] ) === "function" ) { - return callbacks[ prop ].apply( callbacks, args ); - } else { - return callbacks[ prop ]; // or undefined - } + // Stack to decide between skip/abort functions + var callers = []; + + // Stack to avoiding loops from circular referencing + var parents = []; + var parentsB = []; + + var getProto = Object.getPrototypeOf || function( obj ) { + + /*jshint proto: true */ + return obj.__proto__; + }; + + function useStrictEquality( b, a ) { + + // To catch short annotation VS 'new' annotation of a declaration. e.g.: + // `var i = 1;` + // `var j = new Number(1);` + if ( typeof a === "object" ) { + a = a.valueOf(); } + if ( typeof b === "object" ) { + b = b.valueOf(); + } + + return a === b; } - // the real equiv function - var innerEquiv, - // stack to decide between skip/abort functions - callers = [], - // stack to avoiding loops from circular referencing - parents = [], - parentsB = [], - - getProto = Object.getPrototypeOf || function ( obj ) { - /*jshint camelcase:false */ - return obj.__proto__; - }, - callbacks = (function () { - - // for string, boolean, number and null - function useStrictEquality( b, a ) { - /*jshint eqeqeq:false */ - if ( b instanceof a.constructor || a instanceof b.constructor ) { - // to catch short annotation VS 'new' annotation of a - // declaration - // e.g. var i = 1; - // var j = new Number(1); - return a == b; - } else { - return a === b; - } - } + function compareConstructors( a, b ) { + var protoA = getProto( a ); + var protoB = getProto( b ); + + // Comparing constructors is more strict than using `instanceof` + if ( a.constructor === b.constructor ) { + return true; + } - return { - "string": useStrictEquality, - "boolean": useStrictEquality, - "number": useStrictEquality, - "null": useStrictEquality, - "undefined": useStrictEquality, - - "nan": function( b ) { - return isNaN( b ); - }, - - "date": function( b, a ) { - return QUnit.objectType( b ) === "date" && a.valueOf() === b.valueOf(); - }, - - "regexp": function( b, a ) { - return QUnit.objectType( b ) === "regexp" && - // the regex itself - a.source === b.source && - // and its modifiers - a.global === b.global && - // (gmi) ... - a.ignoreCase === b.ignoreCase && - a.multiline === b.multiline && - a.sticky === b.sticky; - }, - - // - skip when the property is a method of an instance (OOP) - // - abort otherwise, - // initial === would have catch identical references anyway - "function": function() { - var caller = callers[callers.length - 1]; - return caller !== Object && typeof caller !== "undefined"; - }, - - "array": function( b, a ) { - var i, j, len, loop, aCircular, bCircular; - - // b could be an object literal here - if ( QUnit.objectType( b ) !== "array" ) { - return false; - } + // Ref #851 + // If the obj prototype descends from a null constructor, treat it + // as a null prototype. + if ( protoA && protoA.constructor === null ) { + protoA = null; + } + if ( protoB && protoB.constructor === null ) { + protoB = null; + } - len = a.length; - if ( len !== b.length ) { - // safe and faster - return false; - } + // Allow objects with no prototype to be equivalent to + // objects with Object as their constructor. + if ( ( protoA === null && protoB === Object.prototype ) || + ( protoB === null && protoA === Object.prototype ) ) { + return true; + } - // track reference to avoid circular references - parents.push( a ); - parentsB.push( b ); - for ( i = 0; i < len; i++ ) { - loop = false; - for ( j = 0; j < parents.length; j++ ) { - aCircular = parents[j] === a[i]; - bCircular = parentsB[j] === b[i]; - if ( aCircular || bCircular ) { - if ( a[i] === b[i] || aCircular && bCircular ) { - loop = true; - } else { - parents.pop(); - parentsB.pop(); - return false; - } - } - } - if ( !loop && !innerEquiv(a[i], b[i]) ) { + return false; + } + + function getRegExpFlags( regexp ) { + return "flags" in regexp ? regexp.flags : regexp.toString().match( /[gimuy]*$/ )[ 0 ]; + } + + var callbacks = { + "string": useStrictEquality, + "boolean": useStrictEquality, + "number": useStrictEquality, + "null": useStrictEquality, + "undefined": useStrictEquality, + "symbol": useStrictEquality, + "date": useStrictEquality, + + "nan": function() { + return true; + }, + + "regexp": function( b, a ) { + return a.source === b.source && + + // Include flags in the comparison + getRegExpFlags( a ) === getRegExpFlags( b ); + }, + + // - skip when the property is a method of an instance (OOP) + // - abort otherwise, + // initial === would have catch identical references anyway + "function": function() { + var caller = callers[ callers.length - 1 ]; + return caller !== Object && typeof caller !== "undefined"; + }, + + "array": function( b, a ) { + var i, j, len, loop, aCircular, bCircular; + + len = a.length; + if ( len !== b.length ) { + // safe and faster + return false; + } + + // Track reference to avoid circular references + parents.push( a ); + parentsB.push( b ); + for ( i = 0; i < len; i++ ) { + loop = false; + for ( j = 0; j < parents.length; j++ ) { + aCircular = parents[ j ] === a[ i ]; + bCircular = parentsB[ j ] === b[ i ]; + if ( aCircular || bCircular ) { + if ( a[ i ] === b[ i ] || aCircular && bCircular ) { + loop = true; + } else { parents.pop(); parentsB.pop(); return false; } } + } + if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) { parents.pop(); parentsB.pop(); - return true; - }, + return false; + } + } + parents.pop(); + parentsB.pop(); + return true; + }, - "object": function( b, a ) { - /*jshint forin:false */ - var i, j, loop, aCircular, bCircular, - // Default to true - eq = true, - aProperties = [], - bProperties = []; - - // comparing constructors is more strict than using - // instanceof - if ( a.constructor !== b.constructor ) { - // Allow objects with no prototype to be equivalent to - // objects with Object as their constructor. - if ( !(( getProto(a) === null && getProto(b) === Object.prototype ) || - ( getProto(b) === null && getProto(a) === Object.prototype ) ) ) { - return false; - } - } + "set": function( b, a ) { + var aArray, bArray; - // stack constructor before traversing properties - callers.push( a.constructor ); + aArray = []; + a.forEach( function( v ) { + aArray.push( v ); + }); + bArray = []; + b.forEach( function( v ) { + bArray.push( v ); + }); - // track reference to avoid circular references - parents.push( a ); - parentsB.push( b ); - - // be strict: don't ensure hasOwnProperty and go deep - for ( i in a ) { - loop = false; - for ( j = 0; j < parents.length; j++ ) { - aCircular = parents[j] === a[i]; - bCircular = parentsB[j] === b[i]; - if ( aCircular || bCircular ) { - if ( a[i] === b[i] || aCircular && bCircular ) { - loop = true; - } else { - eq = false; - break; - } - } - } - aProperties.push(i); - if ( !loop && !innerEquiv(a[i], b[i]) ) { + return innerEquiv( bArray, aArray ); + }, + + "map": function( b, a ) { + var aArray, bArray; + + aArray = []; + a.forEach( function( v, k ) { + aArray.push( [ k, v ] ); + }); + bArray = []; + b.forEach( function( v, k ) { + bArray.push( [ k, v ] ); + }); + + return innerEquiv( bArray, aArray ); + }, + + "object": function( b, a ) { + var i, j, loop, aCircular, bCircular; + + // Default to true + var eq = true; + var aProperties = []; + var bProperties = []; + + if ( compareConstructors( a, b ) === false ) { + return false; + } + + // Stack constructor before traversing properties + callers.push( a.constructor ); + + // Track reference to avoid circular references + parents.push( a ); + parentsB.push( b ); + + // Be strict: don't ensure hasOwnProperty and go deep + for ( i in a ) { + loop = false; + for ( j = 0; j < parents.length; j++ ) { + aCircular = parents[ j ] === a[ i ]; + bCircular = parentsB[ j ] === b[ i ]; + if ( aCircular || bCircular ) { + if ( a[ i ] === b[ i ] || aCircular && bCircular ) { + loop = true; + } else { eq = false; break; } } + } + aProperties.push( i ); + if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) { + eq = false; + break; + } + } - parents.pop(); - parentsB.pop(); - callers.pop(); // unstack, we are done + parents.pop(); + parentsB.pop(); - for ( i in b ) { - bProperties.push( i ); // collect b's properties - } + // Unstack, we are done + callers.pop(); - // Ensures identical properties name - return eq && innerEquiv( aProperties.sort(), bProperties.sort() ); - } - }; - }()); + for ( i in b ) { - innerEquiv = function() { // can take multiple arguments - var args = [].slice.apply( arguments ); - if ( args.length < 2 ) { - return true; // end transition - } - - return (function( a, b ) { - if ( a === b ) { - return true; // catch the most you can - } else if ( a === null || b === null || typeof a === "undefined" || - typeof b === "undefined" || - QUnit.objectType(a) !== QUnit.objectType(b) ) { - return false; // don't lose time with error prone cases - } else { - return bindCallbacks(a, callbacks, [ b, a ]); + // Collect b's properties + bProperties.push( i ); } - // apply transition with (1..n) arguments - }( args[0], args[1] ) && innerEquiv.apply( this, args.splice(1, args.length - 1 )) ); + // Ensures identical properties name + return eq && innerEquiv( aProperties.sort(), bProperties.sort() ); + } }; + function typeEquiv( a, b ) { + var type = QUnit.objectType( a ); + return QUnit.objectType( b ) === type && callbacks[ type ]( b, a ); + } + + // The real equiv function + function innerEquiv( a, b ) { + + // We're done when there's nothing more to compare + if ( arguments.length < 2 ) { + return true; + } + + // Require type-specific equality + return ( a === b || typeEquiv( a, b ) ) && + + // ...across all consecutive argument pairs + ( arguments.length === 2 || innerEquiv.apply( this, [].slice.call( arguments, 1 ) ) ); + } + return innerEquiv; }()); diff -Nru libjs-qunit-1.14.0/src/export.js libjs-qunit-1.22.0/src/export.js --- libjs-qunit-1.14.0/src/export.js 2014-01-31 16:41:24.000000000 +0000 +++ libjs-qunit-1.22.0/src/export.js 2016-02-23 15:57:56.000000000 +0000 @@ -1,10 +1,71 @@ +// Deprecated +// Extend assert methods to QUnit for Backwards compatibility +(function() { + var i, + assertions = Assert.prototype; + + function applyCurrent( current ) { + return function() { + var assert = new Assert( QUnit.config.current ); + current.apply( assert, arguments ); + }; + } + + for ( i in assertions ) { + QUnit[ i ] = applyCurrent( assertions[ i ] ); + } +})(); + // For browser, export only select globals -if ( typeof window !== "undefined" ) { - extend( window, QUnit.constructor.prototype ); +if ( defined.document ) { + + (function() { + var i, l, + keys = [ + "test", + "module", + "expect", + "asyncTest", + "start", + "stop", + "ok", + "notOk", + "equal", + "notEqual", + "propEqual", + "notPropEqual", + "deepEqual", + "notDeepEqual", + "strictEqual", + "notStrictEqual", + "throws", + "raises" + ]; + + for ( i = 0, l = keys.length; i < l; i++ ) { + window[ keys[ i ] ] = QUnit[ keys[ i ] ]; + } + })(); + window.QUnit = QUnit; } -// For CommonJS environments, export everything -if ( typeof module !== "undefined" && module.exports ) { +// For nodejs +if ( typeof module !== "undefined" && module && module.exports ) { module.exports = QUnit; + + // For consistency with CommonJS environments' exports + module.exports.QUnit = QUnit; +} + +// For CommonJS with exports, but without module.exports, like Rhino +if ( typeof exports !== "undefined" && exports ) { + exports.QUnit = QUnit; +} + +if ( typeof define === "function" && define.amd ) { + define( function() { + return QUnit; + } ); + QUnit.config.autostart = false; } diff -Nru libjs-qunit-1.14.0/src/intro.js libjs-qunit-1.22.0/src/intro.js --- libjs-qunit-1.14.0/src/intro.js 2014-01-31 16:41:24.000000000 +0000 +++ libjs-qunit-1.22.0/src/intro.js 2016-02-23 15:57:56.000000000 +0000 @@ -1,12 +1,12 @@ /*! * QUnit @VERSION - * http://qunitjs.com/ + * https://qunitjs.com/ * - * Copyright 2013 jQuery Foundation and other contributors + * Copyright jQuery Foundation and other contributors * Released under the MIT license - * http://jquery.org/license + * https://jquery.org/license * * Date: @DATE */ -(function( window ) { +(function( global ) { diff -Nru libjs-qunit-1.14.0/src/outro.js libjs-qunit-1.22.0/src/outro.js --- libjs-qunit-1.14.0/src/outro.js 2014-01-31 16:41:24.000000000 +0000 +++ libjs-qunit-1.22.0/src/outro.js 2016-02-23 15:57:56.000000000 +0000 @@ -1,4 +1,3 @@ - // Get a reference to the global object, like window in browsers }( (function() { return this; diff -Nru libjs-qunit-1.14.0/src/qunit.css libjs-qunit-1.22.0/src/qunit.css --- libjs-qunit-1.14.0/src/qunit.css 2014-01-31 16:41:24.000000000 +0000 +++ libjs-qunit-1.22.0/src/qunit.css 2016-02-23 15:57:56.000000000 +0000 @@ -1,27 +1,27 @@ /*! * QUnit @VERSION - * http://qunitjs.com/ + * https://qunitjs.com/ * - * Copyright 2013 jQuery Foundation and other contributors + * Copyright jQuery Foundation and other contributors * Released under the MIT license - * http://jquery.org/license + * https://jquery.org/license * * Date: @DATE */ /** Font Family and Sizes */ -#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult { +#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-filteredTest, #qunit-userAgent, #qunit-testresult { font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif; } -#qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } +#qunit-testrunner-toolbar, #qunit-filteredTest, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } #qunit-tests { font-size: smaller; } /** Resets */ -#qunit-tests, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter { +#qunit-tests, #qunit-header, #qunit-banner, #qunit-filteredTest, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter { margin: 0; padding: 0; } @@ -62,14 +62,20 @@ } #qunit-testrunner-toolbar { - padding: 0.5em 0 0.5em 2em; + padding: 0.5em 1em 0.5em 1em; color: #5E740B; background-color: #EEE; overflow: hidden; } +#qunit-filteredTest { + padding: 0.5em 1em 0.5em 1em; + background-color: #F4FF77; + color: #366097; +} + #qunit-userAgent { - padding: 0.5em 0 0.5em 2.5em; + padding: 0.5em 1em 0.5em 1em; background-color: #2B81AF; color: #FFF; text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; @@ -77,6 +83,18 @@ #qunit-modulefilter-container { float: right; + padding: 0.2em; +} + +.qunit-url-config { + display: inline-block; + padding: 0.1em; +} + +.qunit-filter { + display: block; + float: right; + margin-left: 1em; } /** Tests: Pass/Fail */ @@ -86,24 +104,55 @@ } #qunit-tests li { - padding: 0.4em 0.5em 0.4em 2.5em; + padding: 0.4em 1em 0.4em 1em; border-bottom: 1px solid #FFF; list-style-position: inside; } -#qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running { +#qunit-tests > li { display: none; } +#qunit-tests li.running, +#qunit-tests li.pass, +#qunit-tests li.fail, +#qunit-tests li.skipped { + display: list-item; +} + +#qunit-tests.hidepass { + position: relative; +} + +#qunit-tests.hidepass li.running, +#qunit-tests.hidepass li.pass { + visibility: hidden; + position: absolute; + width: 0; + height: 0; + padding: 0; + border: 0; + margin: 0; +} + #qunit-tests li strong { cursor: pointer; } +#qunit-tests li.skipped strong { + cursor: default; +} + #qunit-tests li a { padding: 0.5em; color: #C2CCD1; text-decoration: none; } + +#qunit-tests li p a { + padding: 0.25em; + color: #6B6464; +} #qunit-tests li a:hover, #qunit-tests li a:focus { color: #000; @@ -123,6 +172,10 @@ border-radius: 5px; } +.qunit-source { + margin: 0.6em 0 0.3em; +} + .qunit-collapsed { display: none; } @@ -211,11 +264,26 @@ #qunit-banner.qunit-fail { background-color: #EE5757; } +/*** Skipped tests */ + +#qunit-tests .skipped { + background-color: #EBECE9; +} + +#qunit-tests .qunit-skipped-label { + background-color: #F4FF77; + display: inline-block; + font-style: normal; + color: #366097; + line-height: 1.8em; + padding: 0 0.5em; + margin: -0.4em 0.4em -0.4em 0; +} /** Result */ #qunit-testresult { - padding: 0.5em 0.5em 0.5em 2.5em; + padding: 0.5em 1em 0.5em 1em; color: #2B81AF; background-color: #D2E0E6; diff -Nru libjs-qunit-1.14.0/src/test.js libjs-qunit-1.22.0/src/test.js --- libjs-qunit-1.14.0/src/test.js 2014-01-31 16:41:24.000000000 +0000 +++ libjs-qunit-1.22.0/src/test.js 2016-02-23 15:57:56.000000000 +0000 @@ -1,38 +1,52 @@ +var focused = false; +var priorityCount = 0; + function Test( settings ) { + var i, l; + + ++Test.count; + extend( this, settings ); this.assertions = []; - this.testNumber = ++Test.count; + this.semaphore = 0; + this.usedAsync = false; + this.module = config.currentModule; + this.stack = sourceFromStacktrace( 3 ); + + // Register unique strings + for ( i = 0, l = this.module.tests; i < l.length; i++ ) { + if ( this.module.tests[ i ].name === this.testName ) { + this.testName += " "; + } + } + + this.testId = generateHash( this.module.name, this.testName ); + + this.module.tests.push({ + name: this.testName, + testId: this.testId + }); + + if ( settings.skip ) { + + // Skipped tests will fully ignore any sent callback + this.callback = function() {}; + this.async = false; + this.expected = 0; + } else { + this.assert = new Assert( this ); + } } Test.count = 0; Test.prototype = { - init: function() { - var a, b, li, - tests = id( "qunit-tests" ); - - if ( tests ) { - b = document.createElement( "strong" ); - b.innerHTML = this.nameHtml; - - // `a` initialized at top of scope - a = document.createElement( "a" ); - a.innerHTML = "Rerun"; - a.href = QUnit.url({ testNumber: this.testNumber }); - - li = document.createElement( "li" ); - li.appendChild( b ); - li.appendChild( a ); - li.className = "running"; - li.id = this.id = "qunit-test-output" + testId++; - - tests.appendChild( li ); - } - }, - setup: function() { + before: function() { if ( + // Emit moduleStart when we're switching from one module to another this.module !== config.previousModule || + // They could be equal (both undefined) but if the previousModule property doesn't // yet exist it means this is the first test in a suite that isn't wrapped in a // module, in which case we'll just emit a moduleStart event for 'undefined'. @@ -40,86 +54,65 @@ !hasOwn.call( config, "previousModule" ) ) { if ( hasOwn.call( config, "previousModule" ) ) { - runLoggingCallbacks( "moduleDone", QUnit, { - name: config.previousModule, + runLoggingCallbacks( "moduleDone", { + name: config.previousModule.name, + tests: config.previousModule.tests, failed: config.moduleStats.bad, passed: config.moduleStats.all - config.moduleStats.bad, - total: config.moduleStats.all + total: config.moduleStats.all, + runtime: now() - config.moduleStats.started }); } config.previousModule = this.module; - config.moduleStats = { all: 0, bad: 0 }; - runLoggingCallbacks( "moduleStart", QUnit, { - name: this.module + config.moduleStats = { all: 0, bad: 0, started: now() }; + runLoggingCallbacks( "moduleStart", { + name: this.module.name, + tests: this.module.tests }); } config.current = this; - this.testEnvironment = extend({ - setup: function() {}, - teardown: function() {} - }, this.moduleTestEnvironment ); + if ( this.module.testEnvironment ) { + delete this.module.testEnvironment.beforeEach; + delete this.module.testEnvironment.afterEach; + } + this.testEnvironment = extend( {}, this.module.testEnvironment ); - this.started = +new Date(); - runLoggingCallbacks( "testStart", QUnit, { + this.started = now(); + runLoggingCallbacks( "testStart", { name: this.testName, - module: this.module + module: this.module.name, + testId: this.testId }); - /*jshint camelcase:false */ - - - /** - * Expose the current test environment. - * - * @deprecated since 1.12.0: Use QUnit.config.current.testEnvironment instead. - */ - QUnit.current_testEnvironment = this.testEnvironment; - - /*jshint camelcase:true */ - if ( !config.pollution ) { saveGlobal(); } - if ( config.notrycatch ) { - this.testEnvironment.setup.call( this.testEnvironment, QUnit.assert ); - return; - } - try { - this.testEnvironment.setup.call( this.testEnvironment, QUnit.assert ); - } catch( e ) { - QUnit.pushFailure( "Setup failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 1 ) ); - } }, - run: function() { - config.current = this; - var running = id( "qunit-testresult" ); + run: function() { + var promise; - if ( running ) { - running.innerHTML = "Running:
              " + this.nameHtml; - } + config.current = this; if ( this.async ) { QUnit.stop(); } - this.callbackStarted = +new Date(); + this.callbackStarted = now(); if ( config.notrycatch ) { - this.callback.call( this.testEnvironment, QUnit.assert ); - this.callbackRuntime = +new Date() - this.callbackStarted; + runTest( this ); return; } try { - this.callback.call( this.testEnvironment, QUnit.assert ); - this.callbackRuntime = +new Date() - this.callbackStarted; - } catch( e ) { - this.callbackRuntime = +new Date() - this.callbackStarted; + runTest( this ); + } catch ( e ) { + this.pushFailure( "Died on test #" + ( this.assertions.length + 1 ) + " " + + this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) ); - QUnit.pushFailure( "Died on test #" + (this.assertions.length + 1) + " " + this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) ); // else next test will carry the responsibility saveGlobal(); @@ -128,171 +121,485 @@ QUnit.start(); } } + + function runTest( test ) { + promise = test.callback.call( test.testEnvironment, test.assert ); + test.resolvePromise( promise ); + } }, - teardown: function() { - config.current = this; - if ( config.notrycatch ) { - if ( typeof this.callbackRuntime === "undefined" ) { - this.callbackRuntime = +new Date() - this.callbackStarted; + + after: function() { + checkPollution(); + }, + + queueHook: function( hook, hookName ) { + var promise, + test = this; + return function runHook() { + config.current = test; + if ( config.notrycatch ) { + callHook(); + return; } - this.testEnvironment.teardown.call( this.testEnvironment, QUnit.assert ); - return; - } else { try { - this.testEnvironment.teardown.call( this.testEnvironment, QUnit.assert ); - } catch( e ) { - QUnit.pushFailure( "Teardown failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 1 ) ); + callHook(); + } catch ( error ) { + test.pushFailure( hookName + " failed on " + test.testName + ": " + + ( error.message || error ), extractStacktrace( error, 0 ) ); + } + + function callHook() { + promise = hook.call( test.testEnvironment, test.assert ); + test.resolvePromise( promise, hookName ); + } + }; + }, + + // Currently only used for module level hooks, can be used to add global level ones + hooks: function( handler ) { + var hooks = []; + + function processHooks( test, module ) { + if ( module.parentModule ) { + processHooks( test, module.parentModule ); + } + if ( module.testEnvironment && + QUnit.objectType( module.testEnvironment[ handler ] ) === "function" ) { + hooks.push( test.queueHook( module.testEnvironment[ handler ], handler ) ); } } - checkPollution(); + + // Hooks are ignored on skipped tests + if ( !this.skip ) { + processHooks( this, this.module ); + } + return hooks; }, + finish: function() { config.current = this; if ( config.requireExpects && this.expected === null ) { - QUnit.pushFailure( "Expected number of assertions to be defined, but expect() was not called.", this.stack ); + this.pushFailure( "Expected number of assertions to be defined, but expect() was " + + "not called.", this.stack ); } else if ( this.expected !== null && this.expected !== this.assertions.length ) { - QUnit.pushFailure( "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run", this.stack ); + this.pushFailure( "Expected " + this.expected + " assertions, but " + + this.assertions.length + " were run", this.stack ); } else if ( this.expected === null && !this.assertions.length ) { - QUnit.pushFailure( "Expected at least one assertion, but none were run - call expect(0) to accept zero assertions.", this.stack ); + this.pushFailure( "Expected at least one assertion, but none were run - call " + + "expect(0) to accept zero assertions.", this.stack ); } - var i, assertion, a, b, time, li, ol, - test = this, - good = 0, - bad = 0, - tests = id( "qunit-tests" ); + var i, + bad = 0; - this.runtime = +new Date() - this.started; + this.runtime = now() - this.started; config.stats.all += this.assertions.length; config.moduleStats.all += this.assertions.length; - if ( tests ) { - ol = document.createElement( "ol" ); - ol.className = "qunit-assert-list"; - - for ( i = 0; i < this.assertions.length; i++ ) { - assertion = this.assertions[i]; - - li = document.createElement( "li" ); - li.className = assertion.result ? "pass" : "fail"; - li.innerHTML = assertion.message || ( assertion.result ? "okay" : "failed" ); - ol.appendChild( li ); - - if ( assertion.result ) { - good++; - } else { - bad++; - config.stats.bad++; - config.moduleStats.bad++; - } - } - - // store result when possible - if ( QUnit.config.reorder && defined.sessionStorage ) { - if ( bad ) { - sessionStorage.setItem( "qunit-test-" + this.module + "-" + this.testName, bad ); - } else { - sessionStorage.removeItem( "qunit-test-" + this.module + "-" + this.testName ); - } - } - - if ( bad === 0 ) { - addClass( ol, "qunit-collapsed" ); - } - - // `b` initialized at top of scope - b = document.createElement( "strong" ); - b.innerHTML = this.nameHtml + " (" + bad + ", " + good + ", " + this.assertions.length + ")"; - - addEvent(b, "click", function() { - var next = b.parentNode.lastChild, - collapsed = hasClass( next, "qunit-collapsed" ); - ( collapsed ? removeClass : addClass )( next, "qunit-collapsed" ); - }); - - addEvent(b, "dblclick", function( e ) { - var target = e && e.target ? e.target : window.event.srcElement; - if ( target.nodeName.toLowerCase() === "span" || target.nodeName.toLowerCase() === "b" ) { - target = target.parentNode; - } - if ( window.location && target.nodeName.toLowerCase() === "strong" ) { - window.location = QUnit.url({ testNumber: test.testNumber }); - } - }); - - // `time` initialized at top of scope - time = document.createElement( "span" ); - time.className = "runtime"; - time.innerHTML = this.runtime + " ms"; - - // `li` initialized at top of scope - li = id( this.id ); - li.className = bad ? "fail" : "pass"; - li.removeChild( li.firstChild ); - a = li.firstChild; - li.appendChild( b ); - li.appendChild( a ); - li.appendChild( time ); - li.appendChild( ol ); - - } else { - for ( i = 0; i < this.assertions.length; i++ ) { - if ( !this.assertions[i].result ) { - bad++; - config.stats.bad++; - config.moduleStats.bad++; - } + for ( i = 0; i < this.assertions.length; i++ ) { + if ( !this.assertions[ i ].result ) { + bad++; + config.stats.bad++; + config.moduleStats.bad++; } } - runLoggingCallbacks( "testDone", QUnit, { + runLoggingCallbacks( "testDone", { name: this.testName, - module: this.module, + module: this.module.name, + skipped: !!this.skip, failed: bad, passed: this.assertions.length - bad, total: this.assertions.length, runtime: this.runtime, + + // HTML Reporter use + assertions: this.assertions, + testId: this.testId, + + // Source of Test + source: this.stack, + // DEPRECATED: this property will be removed in 2.0.0, use runtime instead duration: this.runtime }); + // QUnit.reset() is deprecated and will be replaced for a new + // fixture reset function on QUnit 2.0/2.1. + // It's still called here for backwards compatibility handling QUnit.reset(); config.current = undefined; }, queue: function() { - var bad, + var priority, test = this; - synchronize(function() { - test.init(); - }); + if ( !this.valid() ) { + return; + } + function run() { + // each of these can by async - synchronize(function() { - test.setup(); - }); - synchronize(function() { - test.run(); - }); - synchronize(function() { - test.teardown(); - }); - synchronize(function() { - test.finish(); - }); + synchronize([ + function() { + test.before(); + }, + + test.hooks( "beforeEach" ), + function() { + test.run(); + }, + + test.hooks( "afterEach" ).reverse(), + + function() { + test.after(); + }, + function() { + test.finish(); + } + ]); } - // `bad` initialized at top of scope - // defer when previous test run passed, if storage is available - bad = QUnit.config.reorder && defined.sessionStorage && - +sessionStorage.getItem( "qunit-test-" + this.module + "-" + this.testName ); - - if ( bad ) { - run(); - } else { - synchronize( run, true ); + // Prioritize previously failed tests, detected from sessionStorage + priority = QUnit.config.reorder && defined.sessionStorage && + +sessionStorage.getItem( "qunit-test-" + this.module.name + "-" + this.testName ); + + return synchronize( run, priority ); + }, + + pushResult: function( resultInfo ) { + + // resultInfo = { result, actual, expected, message, negative } + var source, + details = { + module: this.module.name, + name: this.testName, + result: resultInfo.result, + message: resultInfo.message, + actual: resultInfo.actual, + expected: resultInfo.expected, + testId: this.testId, + negative: resultInfo.negative || false, + runtime: now() - this.started + }; + + if ( !resultInfo.result ) { + source = sourceFromStacktrace(); + + if ( source ) { + details.source = source; + } } + + runLoggingCallbacks( "log", details ); + + this.assertions.push({ + result: !!resultInfo.result, + message: resultInfo.message + }); + }, + + pushFailure: function( message, source, actual ) { + if ( !( this instanceof Test ) ) { + throw new Error( "pushFailure() assertion outside test context, was " + + sourceFromStacktrace( 2 ) ); + } + + var details = { + module: this.module.name, + name: this.testName, + result: false, + message: message || "error", + actual: actual || null, + testId: this.testId, + runtime: now() - this.started + }; + + if ( source ) { + details.source = source; + } + + runLoggingCallbacks( "log", details ); + + this.assertions.push({ + result: false, + message: message + }); + }, + + resolvePromise: function( promise, phase ) { + var then, message, + test = this; + if ( promise != null ) { + then = promise.then; + if ( QUnit.objectType( then ) === "function" ) { + QUnit.stop(); + then.call( + promise, + function() { QUnit.start(); }, + function( error ) { + message = "Promise rejected " + + ( !phase ? "during" : phase.replace( /Each$/, "" ) ) + + " " + test.testName + ": " + ( error.message || error ); + test.pushFailure( message, extractStacktrace( error, 0 ) ); + + // else next test will carry the responsibility + saveGlobal(); + + // Unblock + QUnit.start(); + } + ); + } + } + }, + + valid: function() { + var filter = config.filter, + regexFilter = /^(!?)\/([\w\W]*)\/(i?$)/.exec( filter ), + module = QUnit.urlParams.module && QUnit.urlParams.module.toLowerCase(), + fullName = ( this.module.name + ": " + this.testName ); + + function testInModuleChain( testModule ) { + var testModuleName = testModule.name ? testModule.name.toLowerCase() : null; + if ( testModuleName === module ) { + return true; + } else if ( testModule.parentModule ) { + return testInModuleChain( testModule.parentModule ); + } else { + return false; + } + } + + // Internally-generated tests are always valid + if ( this.callback && this.callback.validTest ) { + return true; + } + + if ( config.testId.length > 0 && inArray( this.testId, config.testId ) < 0 ) { + return false; + } + + if ( module && !testInModuleChain( this.module ) ) { + return false; + } + + if ( !filter ) { + return true; + } + + return regexFilter ? + this.regexFilter( !!regexFilter[1], regexFilter[2], regexFilter[3], fullName ) : + this.stringFilter( filter, fullName ); + }, + + regexFilter: function( exclude, pattern, flags, fullName ) { + var regex = new RegExp( pattern, flags ); + var match = regex.test( fullName ); + + return match !== exclude; + }, + + stringFilter: function( filter, fullName ) { + filter = filter.toLowerCase(); + fullName = fullName.toLowerCase(); + + var include = filter.charAt( 0 ) !== "!"; + if ( !include ) { + filter = filter.slice( 1 ); + } + + // If the filter matches, we need to honour include + if ( fullName.indexOf( filter ) !== -1 ) { + return include; + } + + // Otherwise, do the opposite + return !include; + } +}; + +// Resets the test setup. Useful for tests that modify the DOM. +/* +DEPRECATED: Use multiple tests instead of resetting inside a test. +Use testStart or testDone for custom cleanup. +This method will throw an error in 2.0, and will be removed in 2.1 +*/ +QUnit.reset = function() { + + // Return on non-browser environments + // This is necessary to not break on node tests + if ( !defined.document ) { + return; + } + + var fixture = defined.document && document.getElementById && + document.getElementById( "qunit-fixture" ); + + if ( fixture ) { + fixture.innerHTML = config.fixture; } }; + +QUnit.pushFailure = function() { + if ( !QUnit.config.current ) { + throw new Error( "pushFailure() assertion outside test context, in " + + sourceFromStacktrace( 2 ) ); + } + + // Gets current test obj + var currentTest = QUnit.config.current; + + return currentTest.pushFailure.apply( currentTest, arguments ); +}; + +// Based on Java's String.hashCode, a simple but not +// rigorously collision resistant hashing function +function generateHash( module, testName ) { + var hex, + i = 0, + hash = 0, + str = module + "\x1C" + testName, + len = str.length; + + for ( ; i < len; i++ ) { + hash = ( ( hash << 5 ) - hash ) + str.charCodeAt( i ); + hash |= 0; + } + + // Convert the possibly negative integer hash code into an 8 character hex string, which isn't + // strictly necessary but increases user understanding that the id is a SHA-like hash + hex = ( 0x100000000 + hash ).toString( 16 ); + if ( hex.length < 8 ) { + hex = "0000000" + hex; + } + + return hex.slice( -8 ); +} + +function synchronize( callback, priority ) { + var last = !priority; + + if ( QUnit.objectType( callback ) === "array" ) { + while ( callback.length ) { + synchronize( callback.shift() ); + } + return; + } + + if ( priority ) { + config.queue.splice( priorityCount++, 0, callback ); + } else { + config.queue.push( callback ); + } + + if ( config.autorun && !config.blocking ) { + process( last ); + } +} + +function saveGlobal() { + config.pollution = []; + + if ( config.noglobals ) { + for ( var key in global ) { + if ( hasOwn.call( global, key ) ) { + + // in Opera sometimes DOM element ids show up here, ignore them + if ( /^qunit-test-output/.test( key ) ) { + continue; + } + config.pollution.push( key ); + } + } + } +} + +function checkPollution() { + var newGlobals, + deletedGlobals, + old = config.pollution; + + saveGlobal(); + + newGlobals = diff( config.pollution, old ); + if ( newGlobals.length > 0 ) { + QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join( ", " ) ); + } + + deletedGlobals = diff( old, config.pollution ); + if ( deletedGlobals.length > 0 ) { + QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join( ", " ) ); + } +} + +// Will be exposed as QUnit.asyncTest +function asyncTest( testName, expected, callback ) { + if ( arguments.length === 2 ) { + callback = expected; + expected = null; + } + + QUnit.test( testName, expected, callback, true ); +} + +// Will be exposed as QUnit.test +function test( testName, expected, callback, async ) { + if ( focused ) { return; } + + var newTest; + + if ( arguments.length === 2 ) { + callback = expected; + expected = null; + } + + newTest = new Test({ + testName: testName, + expected: expected, + async: async, + callback: callback + }); + + newTest.queue(); +} + +// Will be exposed as QUnit.skip +function skip( testName ) { + if ( focused ) { return; } + + var test = new Test({ + testName: testName, + skip: true + }); + + test.queue(); +} + +// Will be exposed as QUnit.only +function only( testName, expected, callback, async ) { + var newTest; + + if ( focused ) { return; } + + QUnit.config.queue.length = 0; + focused = true; + + if ( arguments.length === 2 ) { + callback = expected; + expected = null; + } + + newTest = new Test({ + testName: testName, + expected: expected, + async: async, + callback: callback + }); + + newTest.queue(); +} diff -Nru libjs-qunit-1.14.0/test/amd.html libjs-qunit-1.22.0/test/amd.html --- libjs-qunit-1.14.0/test/amd.html 1970-01-01 00:00:00.000000000 +0000 +++ libjs-qunit-1.22.0/test/amd.html 2016-02-23 15:57:56.000000000 +0000 @@ -0,0 +1,29 @@ + + + + + QUnit AMD Test Suite + + + + +
              + + + diff -Nru libjs-qunit-1.14.0/test/amd.js libjs-qunit-1.22.0/test/amd.js --- libjs-qunit-1.14.0/test/amd.js 1970-01-01 00:00:00.000000000 +0000 +++ libjs-qunit-1.22.0/test/amd.js 2016-02-23 15:57:56.000000000 +0000 @@ -0,0 +1,13 @@ +/* global beginData */ +define( [ "qunit" ], function( QUnit ) { + +return function() { + QUnit.module( "AMD autostart" ); + + QUnit.test( "Prove the test run started as expected", function( assert ) { + assert.expect( 1 ); + assert.strictEqual( beginData.totalTests, 1, "Should have expected 1 test" ); + } ); +}; + +} ); diff -Nru libjs-qunit-1.14.0/test/async.html libjs-qunit-1.22.0/test/async.html --- libjs-qunit-1.14.0/test/async.html 2014-01-31 16:41:24.000000000 +0000 +++ libjs-qunit-1.22.0/test/async.html 1970-01-01 00:00:00.000000000 +0000 @@ -1,26 +0,0 @@ - - - - - QUnit Async Test Suite - - - -
              -
              test markup
              - - - - - diff -Nru libjs-qunit-1.14.0/test/async.js libjs-qunit-1.22.0/test/async.js --- libjs-qunit-1.14.0/test/async.js 2014-01-31 16:41:24.000000000 +0000 +++ libjs-qunit-1.22.0/test/async.js 1970-01-01 00:00:00.000000000 +0000 @@ -1,6 +0,0 @@ -QUnit.start(); - -test("just a test", function( assert ) { - expect(1); - assert.ok(true); -}); diff -Nru libjs-qunit-1.14.0/test/autostart.html libjs-qunit-1.22.0/test/autostart.html --- libjs-qunit-1.14.0/test/autostart.html 1970-01-01 00:00:00.000000000 +0000 +++ libjs-qunit-1.22.0/test/autostart.html 2016-02-23 15:57:56.000000000 +0000 @@ -0,0 +1,41 @@ + + + + + QUnit Autostart Test Suite + + + +
              + + + + diff -Nru libjs-qunit-1.14.0/test/autostart.js libjs-qunit-1.22.0/test/autostart.js --- libjs-qunit-1.14.0/test/autostart.js 1970-01-01 00:00:00.000000000 +0000 +++ libjs-qunit-1.22.0/test/autostart.js 2016-02-23 15:57:56.000000000 +0000 @@ -0,0 +1,11 @@ +/*global times, beginData */ + +QUnit.start(); + +QUnit.module( "autostart" ); + +QUnit.test( "Prove the test run started as expected", function( assert ) { + assert.expect( 2 ); + assert.ok( times.autostartOff <= times.runStarted ); + assert.strictEqual( beginData.totalTests, 1, "Should have expected 1 test" ); +}); diff -Nru libjs-qunit-1.14.0/test/deepEqual.js libjs-qunit-1.22.0/test/deepEqual.js --- libjs-qunit-1.14.0/test/deepEqual.js 2014-01-31 16:41:24.000000000 +0000 +++ libjs-qunit-1.22.0/test/deepEqual.js 1970-01-01 00:00:00.000000000 +0000 @@ -1,1477 +0,0 @@ -QUnit.module("equiv"); - - -test("Primitive types and constants", function ( assert ) { - assert.equal(QUnit.equiv(null, null), true, "null"); - assert.equal(QUnit.equiv(null, {}), false, "null"); - assert.equal(QUnit.equiv(null, undefined), false, "null"); - assert.equal(QUnit.equiv(null, 0), false, "null"); - assert.equal(QUnit.equiv(null, false), false, "null"); - assert.equal(QUnit.equiv(null, ''), false, "null"); - assert.equal(QUnit.equiv(null, []), false, "null"); - - assert.equal(QUnit.equiv(undefined, undefined), true, "undefined"); - assert.equal(QUnit.equiv(undefined, null), false, "undefined"); - assert.equal(QUnit.equiv(undefined, 0), false, "undefined"); - assert.equal(QUnit.equiv(undefined, false), false, "undefined"); - assert.equal(QUnit.equiv(undefined, {}), false, "undefined"); - assert.equal(QUnit.equiv(undefined, []), false, "undefined"); - assert.equal(QUnit.equiv(undefined, ""), false, "undefined"); - - // Nan usually doest not equal to Nan using the '==' operator. - // Only isNaN() is able to do it. - assert.equal(QUnit.equiv(0/0, 0/0), true, "NaN"); // NaN VS NaN - assert.equal(QUnit.equiv(1/0, 2/0), true, "Infinity"); // Infinity VS Infinity - assert.equal(QUnit.equiv(-1/0, 2/0), false, "-Infinity, Infinity"); // -Infinity VS Infinity - assert.equal(QUnit.equiv(-1/0, -2/0), true, "-Infinity, -Infinity"); // -Infinity VS -Infinity - assert.equal(QUnit.equiv(0/0, 1/0), false, "NaN, Infinity"); // Nan VS Infinity - assert.equal(QUnit.equiv(1/0, 0/0), false, "NaN, Infinity"); // Nan VS Infinity - assert.equal(QUnit.equiv(0/0, null), false, "NaN"); - assert.equal(QUnit.equiv(0/0, undefined), false, "NaN"); - assert.equal(QUnit.equiv(0/0, 0), false, "NaN"); - assert.equal(QUnit.equiv(0/0, false), false, "NaN"); - assert.equal(QUnit.equiv(0/0, function () {}), false, "NaN"); - assert.equal(QUnit.equiv(1/0, null), false, "NaN, Infinity"); - assert.equal(QUnit.equiv(1/0, undefined), false, "NaN, Infinity"); - assert.equal(QUnit.equiv(1/0, 0), false, "NaN, Infinity"); - assert.equal(QUnit.equiv(1/0, 1), false, "NaN, Infinity"); - assert.equal(QUnit.equiv(1/0, false), false, "NaN, Infinity"); - assert.equal(QUnit.equiv(1/0, true), false, "NaN, Infinity"); - assert.equal(QUnit.equiv(1/0, function () {}), false, "NaN, Infinity"); - - assert.equal(QUnit.equiv(0, 0), true, "number"); - assert.equal(QUnit.equiv(0, 1), false, "number"); - assert.equal(QUnit.equiv(1, 0), false, "number"); - assert.equal(QUnit.equiv(1, 1), true, "number"); - assert.equal(QUnit.equiv(1.1, 1.1), true, "number"); - assert.equal(QUnit.equiv(0.0000005, 0.0000005), true, "number"); - assert.equal(QUnit.equiv(0, ''), false, "number"); - assert.equal(QUnit.equiv(0, '0'), false, "number"); - assert.equal(QUnit.equiv(1, '1'), false, "number"); - assert.equal(QUnit.equiv(0, false), false, "number"); - assert.equal(QUnit.equiv(1, true), false, "number"); - - assert.equal(QUnit.equiv(true, true), true, "boolean"); - assert.equal(QUnit.equiv(true, false), false, "boolean"); - assert.equal(QUnit.equiv(false, true), false, "boolean"); - assert.equal(QUnit.equiv(false, 0), false, "boolean"); - assert.equal(QUnit.equiv(false, null), false, "boolean"); - assert.equal(QUnit.equiv(false, undefined), false, "boolean"); - assert.equal(QUnit.equiv(true, 1), false, "boolean"); - assert.equal(QUnit.equiv(true, null), false, "boolean"); - assert.equal(QUnit.equiv(true, undefined), false, "boolean"); - - assert.equal(QUnit.equiv('', ''), true, "string"); - assert.equal(QUnit.equiv('a', 'a'), true, "string"); - assert.equal(QUnit.equiv("foobar", "foobar"), true, "string"); - assert.equal(QUnit.equiv("foobar", "foo"), false, "string"); - assert.equal(QUnit.equiv('', 0), false, "string"); - assert.equal(QUnit.equiv('', false), false, "string"); - assert.equal(QUnit.equiv('', null), false, "string"); - assert.equal(QUnit.equiv('', undefined), false, "string"); - - // Rename for lint validation. - // We know this is bad, we are asserting whether we can coop with bad code like this. - var SafeNumber = Number, SafeString = String, SafeBoolean = Boolean, SafeObject = Object; - - // primitives vs. objects - - assert.equal(QUnit.equiv(0, new SafeNumber()), true, "primitives vs. objects"); - assert.equal(QUnit.equiv(new SafeNumber(), 0), true, "primitives vs. objects"); - assert.equal(QUnit.equiv(1, new SafeNumber(1)), true, "primitives vs. objects"); - assert.equal(QUnit.equiv(new SafeNumber(1), 1), true, "primitives vs. objects"); - assert.equal(QUnit.equiv(new SafeNumber(0), 1), false, "primitives vs. objects"); - assert.equal(QUnit.equiv(0, new SafeNumber(1)), false, "primitives vs. objects"); - - assert.equal(QUnit.equiv(new SafeString(), ""), true, "primitives vs. objects"); - assert.equal(QUnit.equiv("", new SafeString()), true, "primitives vs. objects"); - assert.equal(QUnit.equiv(new SafeString("My String"), "My String"), true, "primitives vs. objects"); - assert.equal(QUnit.equiv("My String", new SafeString("My String")), true, "primitives vs. objects"); - assert.equal(QUnit.equiv("Bad String", new SafeString("My String")), false, "primitives vs. objects"); - assert.equal(QUnit.equiv(new SafeString("Bad String"), "My String"), false, "primitives vs. objects"); - - assert.equal(QUnit.equiv(false, new SafeBoolean()), true, "primitives vs. objects"); - assert.equal(QUnit.equiv(new SafeBoolean(), false), true, "primitives vs. objects"); - assert.equal(QUnit.equiv(true, new SafeBoolean(true)), true, "primitives vs. objects"); - assert.equal(QUnit.equiv(new SafeBoolean(true), true), true, "primitives vs. objects"); - assert.equal(QUnit.equiv(true, new SafeBoolean(1)), true, "primitives vs. objects"); - assert.equal(QUnit.equiv(false, new SafeBoolean(false)), true, "primitives vs. objects"); - assert.equal(QUnit.equiv(new SafeBoolean(false), false), true, "primitives vs. objects"); - assert.equal(QUnit.equiv(false, new SafeBoolean(0)), true, "primitives vs. objects"); - assert.equal(QUnit.equiv(true, new SafeBoolean(false)), false, "primitives vs. objects"); - assert.equal(QUnit.equiv(new SafeBoolean(false), true), false, "primitives vs. objects"); - - assert.equal(QUnit.equiv(new SafeObject(), {}), true, "object literal vs. instantiation"); - assert.equal(QUnit.equiv({}, new SafeObject()), true, "object literal vs. instantiation"); - assert.equal(QUnit.equiv(new SafeObject(), {a:1}), false, "object literal vs. instantiation"); - assert.equal(QUnit.equiv({a:1}, new SafeObject()), false, "object literal vs. instantiation"); - assert.equal(QUnit.equiv({a:undefined}, new SafeObject()), false, "object literal vs. instantiation"); - assert.equal(QUnit.equiv(new SafeObject(), {a:undefined}), false, "object literal vs. instantiation"); -}); - -test("Objects basics", function( assert ) { - assert.equal(QUnit.equiv({}, {}), true); - assert.equal(QUnit.equiv({}, null), false); - assert.equal(QUnit.equiv({}, undefined), false); - assert.equal(QUnit.equiv({}, 0), false); - assert.equal(QUnit.equiv({}, false), false); - - // This test is a hard one, it is very important - // REASONS: - // 1) They are of the same type "object" - // 2) [] instanceof Object is true - // 3) Their properties are the same (doesn't exists) - assert.equal(QUnit.equiv({}, []), false); - - assert.equal(QUnit.equiv({a:1}, {a:1}), true); - assert.equal(QUnit.equiv({a:1}, {a:"1"}), false); - assert.equal(QUnit.equiv({a:[]}, {a:[]}), true); - assert.equal(QUnit.equiv({a:{}}, {a:null}), false); - assert.equal(QUnit.equiv({a:1}, {}), false); - assert.equal(QUnit.equiv({}, {a:1}), false); - - // Hard ones - assert.equal(QUnit.equiv({a:undefined}, {}), false); - assert.equal(QUnit.equiv({}, {a:undefined}), false); - assert.equal(QUnit.equiv( - { - a: [{ bar: undefined }] - }, - { - a: [{ bat: undefined }] - } - ), false); - - // Objects with no prototype, created via Object.create(null), are used e.g. as dictionaries. - // Being able to test equivalence against object literals is quite useful. - if (typeof Object.create === 'function') { - assert.equal(QUnit.equiv(Object.create(null), {}), true, "empty object without prototype VS empty object"); - - var nonEmptyWithNoProto = Object.create(null); - nonEmptyWithNoProto.foo = "bar"; - - assert.equal(QUnit.equiv(nonEmptyWithNoProto, { foo: "bar" }), true, "object without prototype VS object"); - } -}); - - -test("Arrays basics", function( assert ) { - - assert.equal(QUnit.equiv([], []), true); - - // May be a hard one, can invoke a crash at execution. - // because their types are both "object" but null isn't - // like a true object, it doesn't have any property at all. - assert.equal(QUnit.equiv([], null), false); - - assert.equal(QUnit.equiv([], undefined), false); - assert.equal(QUnit.equiv([], false), false); - assert.equal(QUnit.equiv([], 0), false); - assert.equal(QUnit.equiv([], ""), false); - - // May be a hard one, but less hard - // than {} with [] (note the order) - assert.equal(QUnit.equiv([], {}), false); - - assert.equal(QUnit.equiv([null],[]), false); - assert.equal(QUnit.equiv([undefined],[]), false); - assert.equal(QUnit.equiv([],[null]), false); - assert.equal(QUnit.equiv([],[undefined]), false); - assert.equal(QUnit.equiv([null],[undefined]), false); - assert.equal(QUnit.equiv([[]],[[]]), true); - assert.equal(QUnit.equiv([[],[],[]],[[],[],[]]), true); - assert.equal(QUnit.equiv( - [[],[],[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]], - [[],[],[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]), - true); - assert.equal(QUnit.equiv( - [[],[],[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]], - [[],[],[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]), // shorter - false); - assert.equal(QUnit.equiv( - [[],[],[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[{}]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]], - [[],[],[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]), // deepest element not an array - false); - - // same multidimensional - assert.equal(QUnit.equiv( - [1,2,3,4,5,6,7,8,9, [ - 1,2,3,4,5,6,7,8,9, [ - 1,2,3,4,5,[ - [6,7,8,9, [ - [ - 1,2,3,4,[ - 2,3,4,[ - 1,2,[ - 1,2,3,4,[ - 1,2,3,4,5,6,7,8,9,[ - 0 - ],1,2,3,4,5,6,7,8,9 - ],5,6,7,8,9 - ],4,5,6,7,8,9 - ],5,6,7,8,9 - ],5,6,7 - ] - ] - ] - ] - ]]], - [1,2,3,4,5,6,7,8,9, [ - 1,2,3,4,5,6,7,8,9, [ - 1,2,3,4,5,[ - [6,7,8,9, [ - [ - 1,2,3,4,[ - 2,3,4,[ - 1,2,[ - 1,2,3,4,[ - 1,2,3,4,5,6,7,8,9,[ - 0 - ],1,2,3,4,5,6,7,8,9 - ],5,6,7,8,9 - ],4,5,6,7,8,9 - ],5,6,7,8,9 - ],5,6,7 - ] - ] - ] - ] - ]]]), - true, "Multidimensional"); - - // different multidimensional - assert.equal(QUnit.equiv( - [1,2,3,4,5,6,7,8,9, [ - 1,2,3,4,5,6,7,8,9, [ - 1,2,3,4,5,[ - [6,7,8,9, [ - [ - 1,2,3,4,[ - 2,3,4,[ - 1,2,[ - 1,2,3,4,[ - 1,2,3,4,5,6,7,8,9,[ - 0 - ],1,2,3,4,5,6,7,8,9 - ],5,6,7,8,9 - ],4,5,6,7,8,9 - ],5,6,7,8,9 - ],5,6,7 - ] - ] - ] - ] - ]]], - [1,2,3,4,5,6,7,8,9, [ - 1,2,3,4,5,6,7,8,9, [ - 1,2,3,4,5,[ - [6,7,8,9, [ - [ - 1,2,3,4,[ - 2,3,4,[ - 1,2,[ - '1',2,3,4,[ // string instead of number - 1,2,3,4,5,6,7,8,9,[ - 0 - ],1,2,3,4,5,6,7,8,9 - ],5,6,7,8,9 - ],4,5,6,7,8,9 - ],5,6,7,8,9 - ],5,6,7 - ] - ] - ] - ] - ]]]), - false, "Multidimensional"); - - // different multidimensional - assert.equal(QUnit.equiv( - [1,2,3,4,5,6,7,8,9, [ - 1,2,3,4,5,6,7,8,9, [ - 1,2,3,4,5,[ - [6,7,8,9, [ - [ - 1,2,3,4,[ - 2,3,4,[ - 1,2,[ - 1,2,3,4,[ - 1,2,3,4,5,6,7,8,9,[ - 0 - ],1,2,3,4,5,6,7,8,9 - ],5,6,7,8,9 - ],4,5,6,7,8,9 - ],5,6,7,8,9 - ],5,6,7 - ] - ] - ] - ] - ]]], - [1,2,3,4,5,6,7,8,9, [ - 1,2,3,4,5,6,7,8,9, [ - 1,2,3,4,5,[ - [6,7,8,9, [ - [ - 1,2,3,4,[ - 2,3,[ // missing an element (4) - 1,2,[ - 1,2,3,4,[ - 1,2,3,4,5,6,7,8,9,[ - 0 - ],1,2,3,4,5,6,7,8,9 - ],5,6,7,8,9 - ],4,5,6,7,8,9 - ],5,6,7,8,9 - ],5,6,7 - ] - ] - ] - ] - ]]]), - false, "Multidimensional"); -}); - -test("Functions", function( assert ) { - var f0 = function () {}; - var f1 = function () {}; - - // f2 and f3 have the same code, formatted differently - var f2 = function () {return 0;}; - var f3 = function () { - /*jshint asi:true */ - return 0 // this comment and no semicoma as difference - }; - - assert.equal(QUnit.equiv(function() {}, function() {}), false, "Anonymous functions"); // exact source code - assert.equal(QUnit.equiv(function() {}, function() {return true;}), false, "Anonymous functions"); - - assert.equal(QUnit.equiv(f0, f0), true, "Function references"); // same references - assert.equal(QUnit.equiv(f0, f1), false, "Function references"); // exact source code, different references - assert.equal(QUnit.equiv(f2, f3), false, "Function references"); // equivalent source code, different references - assert.equal(QUnit.equiv(f1, f2), false, "Function references"); // different source code, different references - assert.equal(QUnit.equiv(function() {}, true), false); - assert.equal(QUnit.equiv(function() {}, undefined), false); - assert.equal(QUnit.equiv(function() {}, null), false); - assert.equal(QUnit.equiv(function() {}, {}), false); -}); - - -test("Date instances", function( assert ) { - // Date, we don't need to test Date.parse() because it returns a number. - // Only test the Date instances by setting them a fix date. - // The date use is midnight January 1, 1970 - - var d1 = new Date(); - d1.setTime(0); // fix the date - - var d2 = new Date(); - d2.setTime(0); // fix the date - - var d3 = new Date(); // The very now - - // Anyway their types differs, just in case the code fails in the order in which it deals with date - assert.equal(QUnit.equiv(d1, 0), false); // d1.valueOf() returns 0, but d1 and 0 are different - // test same values date and different instances equality - assert.equal(QUnit.equiv(d1, d2), true); - // test different date and different instances difference - assert.equal(QUnit.equiv(d1, d3), false); -}); - - -test("RegExp", function( assert ) { - // Must test cases that imply those traps: - // var a = /./; - // a instanceof Object; // Oops - // a instanceof RegExp; // Oops - // typeof a === "function"; // Oops, false in IE and Opera, true in FF and Safari ("object") - - // Tests same regex with same modifiers in different order - var r = /foo/; - var r5 = /foo/gim; - var r6 = /foo/gmi; - var r7 = /foo/igm; - var r8 = /foo/img; - var r9 = /foo/mig; - var r10 = /foo/mgi; - var ri1 = /foo/i; - var ri2 = /foo/i; - var rm1 = /foo/m; - var rm2 = /foo/m; - var rg1 = /foo/g; - var rg2 = /foo/g; - - assert.equal(QUnit.equiv(r5, r6), true, "Modifier order"); - assert.equal(QUnit.equiv(r5, r7), true, "Modifier order"); - assert.equal(QUnit.equiv(r5, r8), true, "Modifier order"); - assert.equal(QUnit.equiv(r5, r9), true, "Modifier order"); - assert.equal(QUnit.equiv(r5, r10), true, "Modifier order"); - assert.equal(QUnit.equiv(r, r5), false, "Modifier"); - - assert.equal(QUnit.equiv(ri1, ri2), true, "Modifier"); - assert.equal(QUnit.equiv(r, ri1), false, "Modifier"); - assert.equal(QUnit.equiv(ri1, rm1), false, "Modifier"); - assert.equal(QUnit.equiv(r, rm1), false, "Modifier"); - assert.equal(QUnit.equiv(rm1, ri1), false, "Modifier"); - assert.equal(QUnit.equiv(rm1, rm2), true, "Modifier"); - assert.equal(QUnit.equiv(rg1, rm1), false, "Modifier"); - assert.equal(QUnit.equiv(rm1, rg1), false, "Modifier"); - assert.equal(QUnit.equiv(rg1, rg2), true, "Modifier"); - - // Different regex, same modifiers - var r11 = /[a-z]/gi; - var r13 = /[0-9]/gi; // oops! different - assert.equal(QUnit.equiv(r11, r13), false, "Regex pattern"); - - var r14 = /0/ig; - var r15 = /"0"/ig; // oops! different - assert.equal(QUnit.equiv(r14, r15), false, "Regex pattern"); - - var r1 = /[\n\r\u2028\u2029]/g; - var r2 = /[\n\r\u2028\u2029]/g; - var r3 = /[\n\r\u2028\u2028]/g; // differs from r1 - var r4 = /[\n\r\u2028\u2029]/; // differs from r1 - - assert.equal(QUnit.equiv(r1, r2), true, "Regex pattern"); - assert.equal(QUnit.equiv(r1, r3), false, "Regex pattern"); - assert.equal(QUnit.equiv(r1, r4), false, "Regex pattern"); - - // More complex regex - var regex1 = "^[-_.a-z0-9]+@([-_a-z0-9]+\\.)+([A-Za-z][A-Za-z]|[A-Za-z][A-Za-z][A-Za-z])|(([0-9][0-9]?|[0-1][0-9][0-9]|[2][0-4][0-9]|[2][5][0-5]))$"; - var regex2 = "^[-_.a-z0-9]+@([-_a-z0-9]+\\.)+([A-Za-z][A-Za-z]|[A-Za-z][A-Za-z][A-Za-z])|(([0-9][0-9]?|[0-1][0-9][0-9]|[2][0-4][0-9]|[2][5][0-5]))$"; - // regex 3 is different: '.' not escaped - var regex3 = "^[-_.a-z0-9]+@([-_a-z0-9]+.)+([A-Za-z][A-Za-z]|[A-Za-z][A-Za-z][A-Za-z])|(([0-9][0-9]?|[0-1][0-9][0-9]|[2][0-4][0-9]|[2][5][0-5]))$"; - - var r21 = new RegExp(regex1); - var r22 = new RegExp(regex2); - var r23 = new RegExp(regex3); // diff from r21, not same pattern - var r23a = new RegExp(regex3, "gi"); // diff from r23, not same modifier - var r24a = new RegExp(regex3, "ig"); // same as r23a - - assert.equal(QUnit.equiv(r21, r22), true, "Complex Regex"); - assert.equal(QUnit.equiv(r21, r23), false, "Complex Regex"); - assert.equal(QUnit.equiv(r23, r23a), false, "Complex Regex"); - assert.equal(QUnit.equiv(r23a, r24a), true, "Complex Regex"); - - // typeof r1 is "function" in some browsers and "object" in others so we must cover this test - var re = / /; - assert.equal(QUnit.equiv(re, function () {}), false, "Regex internal"); - assert.equal(QUnit.equiv(re, {}), false, "Regex internal"); -}); - - -test("Complex objects", function( assert ) { - - function fn1() { - return "fn1"; - } - function fn2() { - return "fn2"; - } - - // Try to invert the order of some properties to make sure it is covered. - // It can failed when properties are compared between unsorted arrays. - assert.equal(QUnit.equiv( - { - a: 1, - b: null, - c: [{}], - d: { - a: 3.14159, - b: false, - c: { - e: fn1, - f: [[[]]], - g: { - j: { - k: { - n: { - r: "r", - s: [1,2,3], - t: undefined, - u: 0, - v: { - w: { - x: { - y: "Yahoo!", - z: null - } - } - } - }, - q: [], - p: 1/0, - o: 99 - }, - l: undefined, - m: null - } - }, - d: 0, - i: true, - h: "false" - } - }, - e: undefined, - g: "", - h: "h", - f: {}, - i: [] - }, - { - a: 1, - b: null, - c: [{}], - d: { - b: false, - a: 3.14159, - c: { - d: 0, - e: fn1, - f: [[[]]], - g: { - j: { - k: { - n: { - r: "r", - t: undefined, - u: 0, - s: [1,2,3], - v: { - w: { - x: { - z: null, - y: "Yahoo!" - } - } - } - }, - o: 99, - p: 1/0, - q: [] - }, - l: undefined, - m: null - } - }, - i: true, - h: "false" - } - }, - e: undefined, - g: "", - f: {}, - h: "h", - i: [] - } - ), true); - - assert.equal(QUnit.equiv( - { - a: 1, - b: null, - c: [{}], - d: { - a: 3.14159, - b: false, - c: { - d: 0, - e: fn1, - f: [[[]]], - g: { - j: { - k: { - n: { - //r: "r", // different: missing a property - s: [1,2,3], - t: undefined, - u: 0, - v: { - w: { - x: { - y: "Yahoo!", - z: null - } - } - } - }, - o: 99, - p: 1/0, - q: [] - }, - l: undefined, - m: null - } - }, - h: "false", - i: true - } - }, - e: undefined, - f: {}, - g: "", - h: "h", - i: [] - }, - { - a: 1, - b: null, - c: [{}], - d: { - a: 3.14159, - b: false, - c: { - d: 0, - e: fn1, - f: [[[]]], - g: { - j: { - k: { - n: { - r: "r", - s: [1,2,3], - t: undefined, - u: 0, - v: { - w: { - x: { - y: "Yahoo!", - z: null - } - } - } - }, - o: 99, - p: 1/0, - q: [] - }, - l: undefined, - m: null - } - }, - h: "false", - i: true - } - }, - e: undefined, - f: {}, - g: "", - h: "h", - i: [] - } - ), false); - - assert.equal(QUnit.equiv( - { - a: 1, - b: null, - c: [{}], - d: { - a: 3.14159, - b: false, - c: { - d: 0, - e: fn1, - f: [[[]]], - g: { - j: { - k: { - n: { - r: "r", - s: [1,2,3], - t: undefined, - u: 0, - v: { - w: { - x: { - y: "Yahoo!", - z: null - } - } - } - }, - o: 99, - p: 1/0, - q: [] - }, - l: undefined, - m: null - } - }, - h: "false", - i: true - } - }, - e: undefined, - f: {}, - g: "", - h: "h", - i: [] - }, - { - a: 1, - b: null, - c: [{}], - d: { - a: 3.14159, - b: false, - c: { - d: 0, - e: fn1, - f: [[[]]], - g: { - j: { - k: { - n: { - r: "r", - s: [1,2,3], - //t: undefined, // different: missing a property with an undefined value - u: 0, - v: { - w: { - x: { - y: "Yahoo!", - z: null - } - } - } - }, - o: 99, - p: 1/0, - q: [] - }, - l: undefined, - m: null - } - }, - h: "false", - i: true - } - }, - e: undefined, - f: {}, - g: "", - h: "h", - i: [] - } - ), false); - - assert.equal(QUnit.equiv( - { - a: 1, - b: null, - c: [{}], - d: { - a: 3.14159, - b: false, - c: { - d: 0, - e: fn1, - f: [[[]]], - g: { - j: { - k: { - n: { - r: "r", - s: [1,2,3], - t: undefined, - u: 0, - v: { - w: { - x: { - y: "Yahoo!", - z: null - } - } - } - }, - o: 99, - p: 1/0, - q: [] - }, - l: undefined, - m: null - } - }, - h: "false", - i: true - } - }, - e: undefined, - f: {}, - g: "", - h: "h", - i: [] - }, - { - a: 1, - b: null, - c: [{}], - d: { - a: 3.14159, - b: false, - c: { - d: 0, - e: fn1, - f: [[[]]], - g: { - j: { - k: { - n: { - r: "r", - s: [1,2,3], - t: undefined, - u: 0, - v: { - w: { - x: { - y: "Yahoo!", - z: null - } - } - } - }, - o: 99, - p: 1/0, - q: {} // different was [] - }, - l: undefined, - m: null - } - }, - h: "false", - i: true - } - }, - e: undefined, - f: {}, - g: "", - h: "h", - i: [] - } - ), false); - - var same1 = { - a: [ - "string", null, 0, "1", 1, { - prop: null, - foo: [1,2,null,{}, [], [1,2,3]], - bar: undefined - }, 3, "Hey!", "Κάνε πάντα γνωÏίζουμε ας των, μηχανής επιδιόÏθωσης επιδιοÏθώσεις ÏŽÏ‚ μια. Κλπ ας" - ], - unicode: "è€ æ±‰è¯ä¸å˜åœ¨ 港澳和海外的åŽäººåœˆä¸ 贵州 我去了书店 现在尚有争", - b: "b", - c: fn1 - }; - - var same2 = { - a: [ - "string", null, 0, "1", 1, { - prop: null, - foo: [1,2,null,{}, [], [1,2,3]], - bar: undefined - }, 3, "Hey!", "Κάνε πάντα γνωÏίζουμε ας των, μηχανής επιδιόÏθωσης επιδιοÏθώσεις ÏŽÏ‚ μια. Κλπ ας" - ], - unicode: "è€ æ±‰è¯ä¸å˜åœ¨ 港澳和海外的åŽäººåœˆä¸ 贵州 我去了书店 现在尚有争", - b: "b", - c: fn1 - }; - - var diff1 = { - a: [ - "string", null, 0, "1", 1, { - prop: null, - foo: [1,2,null,{}, [], [1,2,3,4]], // different: 4 was add to the array - bar: undefined - }, 3, "Hey!", "Κάνε πάντα γνωÏίζουμε ας των, μηχανής επιδιόÏθωσης επιδιοÏθώσεις ÏŽÏ‚ μια. Κλπ ας" - ], - unicode: "è€ æ±‰è¯ä¸å˜åœ¨ 港澳和海外的åŽäººåœˆä¸ 贵州 我去了书店 现在尚有争", - b: "b", - c: fn1 - }; - - var diff2 = { - a: [ - "string", null, 0, "1", 1, { - prop: null, - foo: [1,2,null,{}, [], [1,2,3]], - newprop: undefined, // different: newprop was added - bar: undefined - }, 3, "Hey!", "Κάνε πάντα γνωÏίζουμε ας των, μηχανής επιδιόÏθωσης επιδιοÏθώσεις ÏŽÏ‚ μια. Κλπ ας" - ], - unicode: "è€ æ±‰è¯ä¸å˜åœ¨ 港澳和海外的åŽäººåœˆä¸ 贵州 我去了书店 现在尚有争", - b: "b", - c: fn1 - }; - - var diff3 = { - a: [ - "string", null, 0, "1", 1, { - prop: null, - foo: [1,2,null,{}, [], [1,2,3]], - bar: undefined - }, 3, "Hey!", "Κάνε πάντα γνωÏίζουμε ας των, μηχανής επιδιόÏθωσης επιδιοÏθώσεις ÏŽÏ‚ μια. Κλπ α" // different: missing last char - ], - unicode: "è€ æ±‰è¯ä¸å˜åœ¨ 港澳和海外的åŽäººåœˆä¸ 贵州 我去了书店 现在尚有争", - b: "b", - c: fn1 - }; - - var diff4 = { - a: [ - "string", null, 0, "1", 1, { - prop: null, - foo: [1,2,undefined,{}, [], [1,2,3]], // different: undefined instead of null - bar: undefined - }, 3, "Hey!", "Κάνε πάντα γνωÏίζουμε ας των, μηχανής επιδιόÏθωσης επιδιοÏθώσεις ÏŽÏ‚ μια. Κλπ ας" - ], - unicode: "è€ æ±‰è¯ä¸å˜åœ¨ 港澳和海外的åŽäººåœˆä¸ 贵州 我去了书店 现在尚有争", - b: "b", - c: fn1 - }; - - var diff5 = { - a: [ - "string", null, 0, "1", 1, { - prop: null, - foo: [1,2,null,{}, [], [1,2,3]], - bat: undefined // different: property name not "bar" - }, 3, "Hey!", "Κάνε πάντα γνωÏίζουμε ας των, μηχανής επιδιόÏθωσης επιδιοÏθώσεις ÏŽÏ‚ μια. Κλπ ας" - ], - unicode: "è€ æ±‰è¯ä¸å˜åœ¨ 港澳和海外的åŽäººåœˆä¸ 贵州 我去了书店 现在尚有争", - b: "b", - c: fn1 - }; - - var diff6 = { - a: [ - "string", null, 0, "1", 1, { - prop: null, - foo: [1,2,null,{}, [], [1,2,3]], - bar: undefined - }, 3, "Hey!", "Κάνε πάντα γνωÏίζουμε ας των, μηχανής επιδιόÏθωσης επιδιοÏθώσεις ÏŽÏ‚ μια. Κλπ ας" - ], - unicode: "è€ æ±‰è¯ä¸å˜åœ¨ 港澳和海外的åŽäººåœˆä¸ 贵州 我去了书店 现在尚有争", - b: "b", - c: fn2 // different: fn2 instead of fn1 - }; - - assert.equal(QUnit.equiv(same1, same2), true); - assert.equal(QUnit.equiv(same2, same1), true); - assert.equal(QUnit.equiv(same2, diff1), false); - assert.equal(QUnit.equiv(diff1, same2), false); - - assert.equal(QUnit.equiv(same1, diff1), false); - assert.equal(QUnit.equiv(same1, diff2), false); - assert.equal(QUnit.equiv(same1, diff3), false); - assert.equal(QUnit.equiv(same1, diff3), false); - assert.equal(QUnit.equiv(same1, diff4), false); - assert.equal(QUnit.equiv(same1, diff5), false); - assert.equal(QUnit.equiv(same1, diff6), false); - assert.equal(QUnit.equiv(diff5, diff1), false); -}); - - -test("Complex Arrays", function( assert ) { - - function fn() { - } - - assert.equal(QUnit.equiv( - [1, 2, 3, true, {}, null, [ - { - a: ["", '1', 0] - }, - 5, 6, 7 - ], "foo"], - [1, 2, 3, true, {}, null, [ - { - a: ["", '1', 0] - }, - 5, 6, 7 - ], "foo"]), - true); - - assert.equal(QUnit.equiv( - [1, 2, 3, true, {}, null, [ - { - a: ["", '1', 0] - }, - 5, 6, 7 - ], "foo"], - [1, 2, 3, true, {}, null, [ - { - b: ["", '1', 0] // not same property name - }, - 5, 6, 7 - ], "foo"]), - false); - - var a = [{ - b: fn, - c: false, - "do": "reserved word", - "for": { - ar: [3,5,9,"hey!", [], { - ar: [1,[ - 3,4,6,9, null, [], [] - ]], - e: fn, - f: undefined - }] - }, - e: 0.43445 - }, 5, "string", 0, fn, false, null, undefined, 0, [ - 4,5,6,7,8,9,11,22,33,44,55,"66", null, [], [[[[[3]]]], "3"], {}, 1/0 - ], [], [[[], "foo", null, { - n: 1/0, - z: { - a: [3,4,5,6,"yep!", undefined, undefined], - b: {} - } - }, {}]]]; - - assert.equal(QUnit.equiv(a, - [{ - b: fn, - c: false, - "do": "reserved word", - "for": { - ar: [3,5,9,"hey!", [], { - ar: [1,[ - 3,4,6,9, null, [], [] - ]], - e: fn, - f: undefined - }] - }, - e: 0.43445 - }, 5, "string", 0, fn, false, null, undefined, 0, [ - 4,5,6,7,8,9,11,22,33,44,55,"66", null, [], [[[[[3]]]], "3"], {}, 1/0 - ], [], [[[], "foo", null, { - n: 1/0, - z: { - a: [3,4,5,6,"yep!", undefined, undefined], - b: {} - } - }, {}]]]), true); - - assert.equal(QUnit.equiv(a, - [{ - b: fn, - c: false, - "do": "reserved word", - "for": { - ar: [3,5,9,"hey!", [], { - ar: [1,[ - 3,4,6,9, null, [], [] - ]], - e: fn, - f: undefined - }] - }, - e: 0.43445 - }, 5, "string", 0, fn, false, null, undefined, 0, [ - 4,5,6,7,8,9,11,22,33,44,55,"66", null, [], [[[[[2]]]], "3"], {}, 1/0 // different: [[[[[2]]]]] instead of [[[[[3]]]]] - ], [], [[[], "foo", null, { - n: 1/0, - z: { - a: [3,4,5,6,"yep!", undefined, undefined], - b: {} - } - }, {}]]]), false); - - assert.equal(QUnit.equiv(a, - [{ - b: fn, - c: false, - "do": "reserved word", - "for": { - ar: [3,5,9,"hey!", [], { - ar: [1,[ - 3,4,6,9, null, [], [] - ]], - e: fn, - f: undefined - }] - }, - e: 0.43445 - }, 5, "string", 0, fn, false, null, undefined, 0, [ - 4,5,6,7,8,9,11,22,33,44,55,"66", null, [], [[[[[3]]]], "3"], {}, 1/0 - ], [], [[[], "foo", null, { - n: -1/0, // different, -Infinity instead of Infinity - z: { - a: [3,4,5,6,"yep!", undefined, undefined], - b: {} - } - }, {}]]]), false); - - assert.equal(QUnit.equiv(a, - [{ - b: fn, - c: false, - "do": "reserved word", - "for": { - ar: [3,5,9,"hey!", [], { - ar: [1,[ - 3,4,6,9, null, [], [] - ]], - e: fn, - f: undefined - }] - }, - e: 0.43445 - }, 5, "string", 0, fn, false, null, undefined, 0, [ - 4,5,6,7,8,9,11,22,33,44,55,"66", null, [], [[[[[3]]]], "3"], {}, 1/0 - ], [], [[[], "foo", { // different: null is missing - n: 1/0, - z: { - a: [3,4,5,6,"yep!", undefined, undefined], - b: {} - } - }, {}]]]), false); - - assert.equal(QUnit.equiv(a, - [{ - b: fn, - c: false, - "do": "reserved word", - "for": { - ar: [3,5,9,"hey!", [], { - ar: [1,[ - 3,4,6,9, null, [], [] - ]], - e: fn - // different: missing property f: undefined - }] - }, - e: 0.43445 - }, 5, "string", 0, fn, false, null, undefined, 0, [ - 4,5,6,7,8,9,11,22,33,44,55,"66", null, [], [[[[[3]]]], "3"], {}, 1/0 - ], [], [[[], "foo", null, { - n: 1/0, - z: { - a: [3,4,5,6,"yep!", undefined, undefined], - b: {} - } - }, {}]]]), false); -}); - - -test("Prototypal inheritance", function( assert ) { - function Gizmo(id) { - this.id = id; - } - - function Hoozit(id) { - this.id = id; - } - Hoozit.prototype = new Gizmo(); - - var gizmo = new Gizmo("ok"); - var hoozit = new Hoozit("ok"); - - // Try this test many times after test on instances that hold function - // to make sure that our code does not mess with last object constructor memoization. - assert.equal(QUnit.equiv(function () {}, function () {}), false); - - // Hoozit inherit from Gizmo - // hoozit instanceof Hoozit; // true - // hoozit instanceof Gizmo; // true - assert.equal(QUnit.equiv(hoozit, gizmo), true); - - Gizmo.prototype.bar = true; // not a function just in case we skip them - - // Hoozit inherit from Gizmo - // They are equivalent - assert.equal(QUnit.equiv(hoozit, gizmo), true); - - // Make sure this is still true !important - // The reason for this is that I forgot to reset the last - // caller to where it were called from. - assert.equal(QUnit.equiv(function () {}, function () {}), false); - - // Make sure this is still true !important - assert.equal(QUnit.equiv(hoozit, gizmo), true); - - Hoozit.prototype.foo = true; // not a function just in case we skip them - - // Gizmo does not inherit from Hoozit - // gizmo instanceof Gizmo; // true - // gizmo instanceof Hoozit; // false - // They are not equivalent - assert.equal(QUnit.equiv(hoozit, gizmo), false); - - // Make sure this is still true !important - assert.equal(QUnit.equiv(function () {}, function () {}), false); -}); - - -test("Instances", function( assert ) { - function A() {} - var a1 = new A(); - var a2 = new A(); - - function B() { - this.fn = function () {}; - } - var b1 = new B(); - var b2 = new B(); - - assert.equal(QUnit.equiv(a1, a2), true, "Same property, same constructor"); - - // b1.fn and b2.fn are functions but they are different references - // But we decided to skip function for instances. - assert.equal(QUnit.equiv(b1, b2), true, "Same property, same constructor"); - assert.equal(QUnit.equiv(a1, b1), false, "Same properties but different constructor"); // failed - - function Car(year) { - var privateVar = 0; - this.year = year; - this.isOld = function() { - return privateVar > 10; - }; - } - - function Human(year) { - var privateVar = 1; - this.year = year; - this.isOld = function() { - return privateVar > 80; - }; - } - - var car = new Car(30); - var carSame = new Car(30); - var carDiff = new Car(10); - var human = new Human(30); - - /** - * difference: - * - year: 30 - * same: - * - year: 30, - * - isOld: function () {} - */ - - assert.equal(QUnit.equiv(car, car), true); - assert.equal(QUnit.equiv(car, carDiff), false); - assert.equal(QUnit.equiv(car, carSame), true); - assert.equal(QUnit.equiv(car, human), false); -}); - - -test("Complex instance nesting (with function values in literals and/or in nested instances)", function( assert ) { - function A(fn) { - this.a = {}; - this.fn = fn; - this.b = {a: []}; - this.o = {}; - this.fn1 = fn; - } - function B(fn) { - this.fn = fn; - this.fn1 = function () {}; - this.a = new A(function () {}); - } - - function fnOutside() { - } - - function C(fn) { - function fnInside() { - } - this.x = 10; - this.fn = fn; - this.fn1 = function () {}; - this.fn2 = fnInside; - this.fn3 = { - a: true, - b: fnOutside // ok make reference to a function in all instances scope - }; - this.o1 = {}; - - // This function will be ignored. - // Even if it is not visible for all instances (e.g. locked in a closures), - // it is from a property that makes part of an instance (e.g. from the C constructor) - this.b1 = new B(function () {}); - this.b2 = new B({ - x: { - b2: new B(function() {}) - } - }); - } - - function D(fn) { - function fnInside() { - } - this.x = 10; - this.fn = fn; - this.fn1 = function () {}; - this.fn2 = fnInside; - this.fn3 = { - a: true, - b: fnOutside, // ok make reference to a function in all instances scope - - // This function won't be ingored. - // It isn't visible for all C instances - // and it is not in a property of an instance. (in an Object instances e.g. the object literal) - c: fnInside - }; - this.o1 = {}; - - // This function will be ignored. - // Even if it is not visible for all instances (e.g. locked in a closures), - // it is from a property that makes part of an instance (e.g. from the C constructor) - this.b1 = new B(function () {}); - this.b2 = new B({ - x: { - b2: new B(function() {}) - } - }); - } - - function E(fn) { - function fnInside() { - } - this.x = 10; - this.fn = fn; - this.fn1 = function () {}; - this.fn2 = fnInside; - this.fn3 = { - a: true, - b: fnOutside // ok make reference to a function in all instances scope - }; - this.o1 = {}; - - // This function will be ignored. - // Even if it is not visible for all instances (e.g. locked in a closures), - // it is from a property that makes part of an instance (e.g. from the C constructor) - this.b1 = new B(function () {}); - this.b2 = new B({ - x: { - b1: new B({a: function() {}}), - b2: new B(function() {}) - } - }); - } - - - var a1 = new A(function () {}); - var a2 = new A(function () {}); - assert.equal(QUnit.equiv(a1, a2), true); - - assert.equal(QUnit.equiv(a1, a2), true); // different instances - - var b1 = new B(function () {}); - var b2 = new B(function () {}); - assert.equal(QUnit.equiv(b1, b2), true); - - var c1 = new C(function () {}); - var c2 = new C(function () {}); - assert.equal(QUnit.equiv(c1, c2), true); - - var d1 = new D(function () {}); - var d2 = new D(function () {}); - assert.equal(QUnit.equiv(d1, d2), false); - - var e1 = new E(function () {}); - var e2 = new E(function () {}); - assert.equal(QUnit.equiv(e1, e2), false); - -}); - - -test("Object with circular references", function( assert ) { - var circularA = { - abc: null - }, circularB = { - abc: null - }; - circularA.abc = circularA; - circularB.abc = circularB; - assert.equal(QUnit.equiv(circularA, circularB), true, "Should not repeat test on object (ambiguous test)"); - - circularA.def = 1; - circularB.def = 1; - assert.equal(QUnit.equiv(circularA, circularB), true, "Should not repeat test on object (ambiguous test)"); - - circularA.def = 1; - circularB.def = 0; - assert.equal(QUnit.equiv(circularA, circularB), false, "Should not repeat test on object (unambiguous test)"); -}); - -test("Array with circular references", function( assert ) { - var circularA = [], - circularB = []; - circularA.push(circularA); - circularB.push(circularB); - assert.equal(QUnit.equiv(circularA, circularB), true, "Should not repeat test on array (ambiguous test)"); - - circularA.push( "abc" ); - circularB.push( "abc" ); - assert.equal(QUnit.equiv(circularA, circularB), true, "Should not repeat test on array (ambiguous test)"); - - circularA.push( "hello" ); - circularB.push( "goodbye" ); - assert.equal(QUnit.equiv(circularA, circularB), false, "Should not repeat test on array (unambiguous test)"); -}); - -test("Mixed object/array with references to self wont loop", function( assert ) { - var circularA = [{abc:null}], - circularB = [{abc:null}]; - circularA[0].abc = circularA; - circularB[0].abc = circularB; - - circularA.push(circularA); - circularB.push(circularB); - assert.equal(QUnit.equiv(circularA, circularB), true, "Should not repeat test on object/array (ambiguous test)"); - - circularA[0].def = 1; - circularB[0].def = 1; - assert.equal(QUnit.equiv(circularA, circularB), true, "Should not repeat test on object/array (ambiguous test)"); - - circularA[0].def = 1; - circularB[0].def = 0; - assert.equal(QUnit.equiv(circularA, circularB), false, "Should not repeat test on object/array (unambiguous test)"); -}); - -test("Compare self-referent to tree", function ( assert ) { - var temp, - circularA = [0], - treeA = [0, null], - circularO = {}, - treeO = { - o: null - }; - - circularA[1] = circularA; - circularO.o = circularO; - - assert.equal(QUnit.equiv(circularA, treeA), false, "Array: Should not consider circular equal to tree"); - assert.equal(QUnit.equiv(circularO, treeO), false, "Object: Should not consider circular equal to tree"); - - temp = [ 0, circularA ]; - assert.equal(QUnit.equiv(circularA, temp), true, "Array: Reference is circular for one, but equal on other"); - assert.equal(QUnit.equiv(temp, circularA), true, "Array: Reference is circular for one, but equal on other"); - - temp = { - o: circularO - }; - assert.equal(QUnit.equiv(circularO, temp), true, "Object: Reference is circular for one, but equal on other"); - assert.equal(QUnit.equiv(temp, circularO), true, "Object: Reference is circular for one, but equal on other"); -}); - -test("Test that must be done at the end because they extend some primitive's prototype", function( assert ) { - // Try that a function looks like our regular expression. - // This tests if we check that a and b are really both instance of RegExp - Function.prototype.global = true; - Function.prototype.multiline = true; - Function.prototype.ignoreCase = false; - Function.prototype.source = "my regex"; - var re = /my regex/gm; - assert.equal(QUnit.equiv(re, function () {}), false, "A function that looks that a regex isn't a regex"); - // This test will ensures it works in both ways, and ALSO especially that we can make differences - // between RegExp and Function constructor because typeof on a RegExpt instance is "function" - assert.equal(QUnit.equiv(function () {}, re), false, "Same conversely, but ensures that function and regexp are distinct because their constructor are different"); -}); diff -Nru libjs-qunit-1.14.0/test/globals-node.js libjs-qunit-1.22.0/test/globals-node.js --- libjs-qunit-1.14.0/test/globals-node.js 1970-01-01 00:00:00.000000000 +0000 +++ libjs-qunit-1.22.0/test/globals-node.js 2016-02-23 15:57:56.000000000 +0000 @@ -0,0 +1,22 @@ +/*jshint node:true */ + +// Don't execute this file directly on Node, this is part of +// Grunt's test-on-node task +(function() { +QUnit.module( "globals for Node.js only" ); + +QUnit.test( "QUnit exports", function( assert ) { + var qunit = require( "../dist/qunit" ); + + assert.ok( qunit, "Required module QUnit truthy" ); + assert.strictEqual( qunit, QUnit, "Required module QUnit matches global QUnit" ); + + assert.ok( qunit.hasOwnProperty( "QUnit" ), "Required module QUnit has property QUnit" ); + assert.strictEqual( + qunit.QUnit, + qunit, + "Required module QUnit's property QUnit is self-referencing" + ); +}); + +})(); diff -Nru libjs-qunit-1.14.0/test/headless.html libjs-qunit-1.22.0/test/headless.html --- libjs-qunit-1.14.0/test/headless.html 2014-01-31 16:41:24.000000000 +0000 +++ libjs-qunit-1.22.0/test/headless.html 2016-02-23 15:57:56.000000000 +0000 @@ -5,8 +5,8 @@ QUnit Headless Test Suite - - + + - - - + + + + + + + + + + + +
              diff -Nru libjs-qunit-1.14.0/test/.jshintrc libjs-qunit-1.22.0/test/.jshintrc --- libjs-qunit-1.14.0/test/.jshintrc 2014-01-31 16:41:24.000000000 +0000 +++ libjs-qunit-1.22.0/test/.jshintrc 1970-01-01 00:00:00.000000000 +0000 @@ -1,33 +0,0 @@ -{ - "predef": [ - "QUnit", - "module", - "test", - "asyncTest", - "expect", - "start", - "stop", - "raises" - ], - - "bitwise": true, - "camelcase": true, - "curly": true, - "eqeqeq": true, - "forin": true, - "immed": true, - "latedef": false, - "newcap": true, - "noarg": true, - "noempty": true, - "nonew": true, - "plusplus": false, - "quotmark": false, - "undef": true, - "unused": true, - "trailing": true, - - "browser": true, - - "onevar": false -} diff -Nru libjs-qunit-1.14.0/test/logs.js libjs-qunit-1.22.0/test/logs.js --- libjs-qunit-1.14.0/test/logs.js 2014-01-31 16:41:24.000000000 +0000 +++ libjs-qunit-1.22.0/test/logs.js 2016-02-23 15:57:56.000000000 +0000 @@ -1,127 +1,217 @@ -// TODO disable reordering for this suite! - -var begin = 0, +var totalTests, moduleContext, moduleDoneContext, testContext, testDoneContext, logContext, + testAutorun, beginModules, + module1Test1, module1Test2, module2Test1, module2Test2, module2Test3, module2Test4, + begin = 0, moduleStart = 0, moduleDone = 0, testStart = 0, testDone = 0, log = 0, - moduleContext, - moduleDoneContext, - testContext, - testDoneContext, - logContext; - -QUnit.begin(function() { + module1Context = { + name: "logs1", + tests: [ + (module1Test1 = { + "name": "test1", + "testId": "646e9e25" + }), + (module1Test2 = { + "name": "test2", + "testId": "646e9e26" + }) + ] + }, + module2Context = { + name: "logs2", + tests: [ + (module2Test1 = { + "name": "test1", + "testId": "9954d966" + }), + (module2Test2 = { + "name": "test2", + "testId": "9954d967" + }), + (module2Test3 = { + "name": "a skipped test", + "testId": "3e797d3a" + }), + (module2Test4 = { + "name": "test the log for the skipped test", + "testId": "d3266148" + }) + ] + }; + +QUnit.begin(function( args ) { + totalTests = args.totalTests; + beginModules = args.modules; begin++; }); -QUnit.done(function() { -}); -QUnit.moduleStart(function(context) { + +QUnit.moduleStart(function( context ) { moduleStart++; moduleContext = context; }); -QUnit.moduleDone(function(context) { + +QUnit.moduleDone(function( context ) { moduleDone++; moduleDoneContext = context; }); -QUnit.testStart(function(context) { + +QUnit.testStart(function( context ) { testStart++; testContext = context; }); -QUnit.testDone(function(context) { + +QUnit.testDone(function( context ) { testDone++; testDoneContext = context; }); -QUnit.log(function(context) { + +QUnit.log(function( context ) { log++; logContext = context; }); -QUnit.module("logs1"); +QUnit.module( module1Context.name ); + +QUnit.test( module1Test1.name, function( assert ) { + assert.expect( 18 ); + + assert.equal( + typeof totalTests, + "number", + "QUnit.begin should pass total amount of tests to callback" + ); + + while ( beginModules.length > 2 ) { + beginModules.pop(); + } + + assert.deepEqual( + beginModules, + [ module1Context, module2Context ], + "QUnit.begin details registered modules and their respective tests" + ); -test("test1", function( assert ) { - expect( 15 ); assert.equal( begin, 1, "QUnit.begin calls" ); assert.equal( moduleStart, 1, "QUnit.moduleStart calls" ); assert.equal( testStart, 1, "QUnit.testStart calls" ); assert.equal( testDone, 0, "QUnit.testDone calls" ); assert.equal( moduleDone, 0, "QUnit.moduleDone calls" ); + + assert.equal( + logContext.runtime >= 0 && logContext.runtime < 50, + true, + "log runtime was a reasonable number" + ); + + delete logContext.runtime; assert.deepEqual( logContext, { - name: "test1", - module: "logs1", + name: module1Test1.name, + module: module1Context.name, result: true, - message: "QUnit.moduleDone calls", - actual: 0, - expected: 0 + message: "log runtime was a reasonable number", + actual: true, + expected: true, + negative: false, + testId: module1Test1.testId }, "log context after equal(actual, expected, message)" ); assert.equal( "foo", "foo" ); - assert.deepEqual(logContext, { - name: "test1", - module: "logs1", + + delete logContext.runtime; + assert.deepEqual( logContext, { + name: module1Test1.name, + module: module1Context.name, result: true, message: undefined, actual: "foo", - expected: "foo" + expected: "foo", + negative: false, + testId: module1Test1.testId }, "log context after equal(actual, expected)" ); assert.ok( true, "ok(true, message)" ); + + delete logContext.runtime; assert.deepEqual( logContext, { - module: "logs1", - name: "test1", + module: module1Context.name, + name: module1Test1.name, result: true, - message: "ok(true, message)" + message: "ok(true, message)", + actual: true, + expected: true, + negative: false, + testId: module1Test1.testId }, "log context after ok(true, message)" ); assert.strictEqual( testDoneContext, undefined, "testDone context" ); assert.deepEqual( testContext, { - module: "logs1", - name: "test1" + module: module1Context.name, + name: module1Test1.name, + testId: module1Test1.testId }, "test context" ); + assert.strictEqual( moduleDoneContext, undefined, "moduleDone context" ); - assert.deepEqual( moduleContext, { - name: "logs1" - }, "module context" ); + assert.deepEqual( moduleContext, module1Context, "module context" ); - assert.equal( log, 14, "QUnit.log calls" ); + assert.equal( log, 17, "QUnit.log calls" ); }); -test("test2", function( assert ) { - expect( 11 ); +QUnit.test( module1Test2.name, function( assert ) { + assert.expect( 12 ); assert.equal( begin, 1, "QUnit.begin calls" ); assert.equal( moduleStart, 1, "QUnit.moduleStart calls" ); assert.equal( testStart, 2, "QUnit.testStart calls" ); assert.equal( testDone, 1, "QUnit.testDone calls" ); assert.equal( moduleDone, 0, "QUnit.moduleDone calls" ); - assert.equal( typeof testDoneContext.runtime, "number" , "testDone context: runtime" ); + assert.equal( + testDoneContext.runtime >= 0 && testDoneContext.runtime < 1000, + true, + "test runtime was a reasonable number" + ); + + assert.ok( testDoneContext.assertions instanceof Array, "testDone context: assertions" ); + + // TODO: more tests for testDoneContext.assertions + delete testDoneContext.runtime; + // DEPRECATED: remove this delete when removing the duration property delete testDoneContext.duration; + + // Delete testDoneContext.assertions so we can easily jump to next assertion + delete testDoneContext.assertions; + + // Delete testDoneContext.source + delete testDoneContext.source; + assert.deepEqual( testDoneContext, { - module: "logs1", - name: "test1", + module: module1Context.name, + name: module1Test1.name, failed: 0, - passed: 15, - total: 15 + passed: 18, + total: 18, + testId: module1Test1.testId, + skipped: false }, "testDone context" ); assert.deepEqual( testContext, { - module: "logs1", - name: "test2" + module: module1Context.name, + name: module1Test2.name, + testId: module1Test2.testId }, "test context" ); - assert.strictEqual( moduleDoneContext, undefined, "moduleDone context" ); - assert.deepEqual( moduleContext, { - name: "logs1" - }, "module context" ); - assert.equal( log, 25, "QUnit.log calls" ); + assert.strictEqual( moduleDoneContext, undefined, "moduleDone context" ); + assert.deepEqual( moduleContext, module1Context, "module context" ); + assert.equal( log, 29, "QUnit.log calls" ); }); -QUnit.module("logs2"); +QUnit.module( module2Context.name ); -test( "test1", function( assert ) { - expect( 9 ); +QUnit.test( module2Test1.name, function( assert ) { + assert.expect( 10 ); assert.equal( begin, 1, "QUnit.begin calls" ); assert.equal( moduleStart, 2, "QUnit.moduleStart calls" ); assert.equal( testStart, 3, "QUnit.testStart calls" ); @@ -129,23 +219,32 @@ assert.equal( moduleDone, 1, "QUnit.moduleDone calls" ); assert.deepEqual( testContext, { - module: "logs2", - name: "test1" + module: module2Context.name, + name: module2Test1.name, + testId: module2Test1.testId }, "test context" ); + + assert.equal( + moduleDoneContext.runtime >= 0 && moduleDoneContext.runtime < 5000, + true, + "module runtime was a reasonable number" + ); + delete moduleDoneContext.runtime; + assert.deepEqual( moduleDoneContext, { - name: "logs1", + name: module1Context.name, + tests: module1Context.tests, failed: 0, - passed: 26, - total: 26 + passed: 30, + total: 30 }, "moduleDone context" ); - assert.deepEqual( moduleContext, { - name: "logs2" - }, "module context" ); + assert.deepEqual( moduleContext, module2Context, "module context" ); - assert.equal( log, 34, "QUnit.log calls" ); + assert.equal( log, 39, "QUnit.log calls" ); }); -test( "test2", function( assert ) { - expect( 8 ); + +QUnit.test( module2Test2.name, function( assert ) { + assert.expect( 8 ); assert.equal( begin, 1, "QUnit.begin calls" ); assert.equal( moduleStart, 2, "QUnit.moduleStart calls" ); assert.equal( testStart, 4, "QUnit.testStart calls" ); @@ -153,17 +252,37 @@ assert.equal( moduleDone, 1, "QUnit.moduleDone calls" ); assert.deepEqual( testContext, { - module: "logs2", - name: "test2" + module: module2Context.name, + name: module2Test2.name, + testId: module2Test2.testId }, "test context" ); - assert.deepEqual( moduleContext, { - name: "logs2" - }, "module context" ); + assert.deepEqual( moduleContext, module2Context, "module context" ); - assert.equal( log, 42, "QUnit.log calls" ); + assert.equal( log, 47, "QUnit.log calls" ); }); -var testAutorun = true; +QUnit.skip( module2Test3.name ); + +QUnit.test( module2Test4.name, function( assert ) { + assert.expect( 1 ); + + delete testDoneContext.runtime; + delete testDoneContext.duration; + delete testDoneContext.source; + + assert.deepEqual( testDoneContext, { + assertions: [], + module: module2Context.name, + name: module2Test3.name, + failed: 0, + passed: 0, + total: 0, + skipped: true, + testId: module2Test3.testId + }, "testDone context" ); +}); + +testAutorun = true; QUnit.done(function() { @@ -181,14 +300,33 @@ QUnit.module( "autorun" ); setTimeout(function() { - test( "first", function( assert ) { + QUnit.test( "first", function( assert ) { assert.equal( moduleStart, 1, "test started" ); assert.equal( moduleDone, 0, "test in progress" ); }); - test( "second", function( assert ) { + QUnit.test( "second", function( assert ) { assert.equal( moduleStart, 2, "test started" ); assert.equal( moduleDone, 1, "test in progress" ); }); }, 5000 ); }); + +QUnit.module( "deprecated log methods" ); + +QUnit.test( "QUnit.reset()", function( assert ) { + + // Skip non-browsers + if ( typeof window === "undefined" || !window.document ) { + assert.expect( 0 ); + return; + } + + var myFixture = document.getElementById( "qunit-fixture" ); + + myFixture.innerHTML = "something different from QUnit.config.fixture"; + + QUnit.reset(); + + assert.strictEqual( myFixture.innerHTML, QUnit.config.fixture, "restores #qunit-fixture" ); +}); diff -Nru libjs-qunit-1.14.0/test/main/assert.js libjs-qunit-1.22.0/test/main/assert.js --- libjs-qunit-1.14.0/test/main/assert.js 1970-01-01 00:00:00.000000000 +0000 +++ libjs-qunit-1.22.0/test/main/assert.js 2016-02-23 15:57:56.000000000 +0000 @@ -0,0 +1,405 @@ +QUnit.module( "assert" ); + +QUnit.test( "ok", function( assert ) { + assert.ok( true ); + assert.ok( 1 ); + assert.ok( "1" ); + assert.ok( Infinity ); + assert.ok( {} ); + assert.ok( [] ); +}); + +QUnit.test( "notOk", function( assert ) { + assert.notOk( false ); + assert.notOk( 0 ); + assert.notOk( "" ); + assert.notOk( null ); + assert.notOk( undefined ); + assert.notOk( NaN ); +}); + +QUnit.test( "equal", function( assert ) { + assert.equal( 1, 1 ); + assert.equal( "foo", "foo" ); + assert.equal( "foo", [ "foo" ] ); + assert.equal( "foo", { toString: function() { return "foo"; } } ); + assert.equal( 0, [ 0 ] ); +}); + +QUnit.test( "notEqual", function( assert ) { + assert.notEqual( 1, 2 ); + assert.notEqual( "foo", "bar" ); + assert.notEqual( {}, {} ); + assert.notEqual( [], [] ); +}); + +QUnit.test( "strictEqual", function( assert ) { + assert.strictEqual( 1, 1 ); + assert.strictEqual( "foo", "foo" ); +}); + +QUnit.test( "notStrictEqual", function( assert ) { + assert.notStrictEqual( 1, 2 ); + assert.notStrictEqual( "foo", "bar" ); + assert.notStrictEqual( "foo", [ "foo" ] ); + assert.notStrictEqual( "1", 1 ); + assert.notStrictEqual( "foo", { toString: function() { return "foo"; } } ); +}); + +QUnit.test( "propEqual", function( assert ) { + assert.expect( 5 ); + var objectCreate = Object.create || function( origin ) { + function O() {} + O.prototype = origin; + var r = new O(); + return r; + }; + + function Foo( x, y, z ) { + this.x = x; + this.y = y; + this.z = z; + } + Foo.prototype.doA = function() {}; + Foo.prototype.doB = function() {}; + Foo.prototype.bar = "prototype"; + + function Bar() { + } + Bar.prototype = objectCreate( Foo.prototype ); + Bar.prototype.constructor = Bar; + + assert.propEqual( + new Foo( 1, "2", [] ), + { + x: 1, + y: "2", + z: [] + } + ); + + assert.notPropEqual( + new Foo( "1", 2, 3 ), + { + x: 1, + y: "2", + z: 3 + }, + "Primitive values are strictly compared" + ); + + assert.notPropEqual( + new Foo( 1, "2", [] ), + { + x: 1, + y: "2", + z: {} + }, + "Array type is preserved" + ); + + assert.notPropEqual( + new Foo( 1, "2", {} ), + { + x: 1, + y: "2", + z: [] + }, + "Empty array is not the same as empty object" + ); + + assert.propEqual( + new Foo( 1, "2", new Foo( [ 3 ], new Bar(), null ) ), + { + x: 1, + y: "2", + z: { + x: [ 3 ], + y: {}, + z: null + } + }, + "Complex nesting of different types, inheritance and constructors" + ); +}); + +QUnit.test( "throws", function( assert ) { + assert.expect( 16 ); + function CustomError( message ) { + this.message = message; + } + + CustomError.prototype.toString = function() { + return this.message; + }; + + assert.throws( + function() { + throw "my error"; + } + ); + + assert.throws( + function() { + throw "my error"; + }, + "simple string throw, no 'expected' value given" + ); + + // This test is for IE 7 and prior which does not properly + // implement Error.prototype.toString + assert.throws( + function() { + throw new Error( "error message" ); + }, + /error message/, + "use regexp against instance of Error" + ); + + assert.throws( + function() { + throw new TypeError(); + }, + Error, + "thrown TypeError without a message is an instance of Error" + ); + + assert.throws( + function() { + throw new TypeError(); + }, + TypeError, + "thrown TypeError without a message is an instance of TypeError" + ); + + assert.throws( + function() { + throw new TypeError( "error message" ); + }, + Error, + "thrown TypeError with a message is an instance of Error" + ); + + // This test is for IE 8 and prior which goes against the standards + // by considering that the native Error constructors, such TypeError, + // are also instances of the Error constructor. As such, the assertion + // sometimes went down the wrong path. + assert.throws( + function() { + throw new TypeError( "error message" ); + }, + TypeError, + "thrown TypeError with a message is an instance of TypeError" + ); + + assert.throws( + function() { + throw new CustomError( "some error description" ); + }, + CustomError, + "thrown error is an instance of CustomError" + ); + + assert.throws( + function() { + throw new Error( "some error description" ); + }, + /description/, + "use a regex to match against the stringified error" + ); + + assert.throws( + function() { + throw new Error( "foo" ); + }, + new Error( "foo" ), + "thrown error object is similar to the expected Error object" + ); + + assert.throws( + function() { + throw new CustomError( "some error description" ); + }, + new CustomError( "some error description" ), + "thrown error object is similar to the expected CustomError object" + ); + + assert.throws( + function() { + throw { + name: "SomeName", + message: "some message" + }; + }, + { name: "SomeName", message: "some message" }, + "thrown error object is similar to the expected plain object" + ); + + assert.throws( + function() { + throw new CustomError( "some error description" ); + }, + function( err ) { + return err instanceof CustomError && /description/.test( err ); + }, + "custom validation function" + ); + + assert.throws( + function() { + + /*jshint ignore:start */ + ( window.execScript || function( data ) { + window.eval.call( window, data ); + })( "throw 'error';" ); + + /*jshint ignore:end */ + }, + "globally-executed errors caught" + ); + + this.CustomError = CustomError; + + assert.throws( + function() { + throw new this.CustomError( "some error description" ); + }, + /description/, + "throw error from property of 'this' context" + ); + + assert.throws( + function() { + throw "some error description"; + }, + "some error description", + "handle string typed thrown errors" + ); +}); + +QUnit.test( "raises, alias for throws", function( assert ) { + assert.expect( 1 ); + assert.raises(function() { + throw "my error"; + }); +}); + +QUnit.module( "failing assertions", { + beforeEach: function( assert ) { + var originalPushResult = assert.pushResult; + assert.pushResult = function( resultInfo ) { + // inverts the result so we can test failing assertions + resultInfo.result = !resultInfo.result; + originalPushResult( resultInfo ); + }; + } +}); + +QUnit.test( "ok", function( assert ) { + assert.ok( false ); + assert.ok( 0 ); + assert.ok( "" ); + assert.ok( null ); + assert.ok( undefined ); + assert.ok( NaN ); +}); + +QUnit.test( "notOk", function( assert ) { + assert.notOk( true ); + assert.notOk( 1 ); + assert.notOk( "1" ); + assert.notOk( Infinity ); + assert.notOk( {} ); + assert.notOk( [] ); +}); + +QUnit.test( "equal", function( assert ) { + assert.equal( 1, 2 ); + assert.equal( "foo", "bar" ); + assert.equal( {}, {} ); + assert.equal( [], [] ); +}); + +QUnit.test( "notEqual", function( assert ) { + assert.notEqual( 1, 1 ); + assert.notEqual( "foo", "foo" ); + assert.notEqual( "foo", [ "foo" ] ); + assert.notEqual( "foo", { toString: function() { return "foo"; } } ); + assert.notEqual( 0, [ 0 ] ); +}); + +QUnit.test( "strictEqual", function( assert ) { + assert.strictEqual( 1, 2 ); + assert.strictEqual( "foo", "bar" ); + assert.strictEqual( "foo", [ "foo" ] ); + assert.strictEqual( "1", 1 ); + assert.strictEqual( "foo", { toString: function() { return "foo"; } } ); +}); + +QUnit.test( "notStrictEqual", function( assert ) { + assert.notStrictEqual( 1, 1 ); + assert.notStrictEqual( "foo", "foo" ); +}); + +QUnit.test( "deepEqual", function( assert ) { + assert.deepEqual( [ "foo", "bar" ], [ "foo" ] ); +}); + +QUnit.test( "notDeepEqual", function( assert ) { + assert.notDeepEqual( [ "foo", "bar" ], [ "foo", "bar" ] ); +}); + +QUnit.test( "propEqual", function( assert ) { + function Foo( x, y, z ) { + this.x = x; + this.y = y; + this.z = z; + } + Foo.prototype.baz = function() {}; + Foo.prototype.bar = "prototype"; + + assert.propEqual( + new Foo( "1", 2, 3 ), + { + x: 1, + y: "2", + z: 3 + } + ); +}); + +QUnit.test( "notPropEqual", function( assert ) { + function Foo( x, y, z ) { + this.x = x; + this.y = y; + this.z = z; + } + Foo.prototype.baz = function() {}; + Foo.prototype.bar = "prototype"; + + assert.notPropEqual( + new Foo( 1, "2", [] ), + { + x: 1, + y: "2", + z: [] + } + ); +}); + +QUnit.test( "throws", function( assert ) { + assert.throws( + function() { + return; + }, + "throws fails without a thrown error" + ); + + assert.throws( + function() { + throw "foo"; + }, + /bar/, + "throws fail when regexp doesn't match the error message" + ); +}); diff -Nru libjs-qunit-1.14.0/test/main/async.js libjs-qunit-1.22.0/test/main/async.js --- libjs-qunit-1.14.0/test/main/async.js 1970-01-01 00:00:00.000000000 +0000 +++ libjs-qunit-1.22.0/test/main/async.js 2016-02-23 15:57:56.000000000 +0000 @@ -0,0 +1,532 @@ +var globalStartError, globalStopError; + +function _setupForFailingAssertionsAfterAsyncDone( assert ) { + var errorRegex = new RegExp( "Assertion after the final `assert\\.async` " + + "was resolved" ); + + // Duck-punch to force an Error to be thrown instead of a `pushFailure` call + assert.test.pushFailure = function( msg ) { + + // Increment the semaphore, preventing post-`done` assertions from causing another failure + assert.test.semaphore++; + + throw new Error( msg ); + }; + + // Provide a wrapper for `assert.throws` to allow test to pass this post-`done` assertion + this._assertCatch = function( fn ) { + assert.throws.call( assert, fn, errorRegex ); + + // Decrement the semaphore to undo the effects of the duck-punched `test.pushFailure` above + assert.test.semaphore--; + }; +} + +QUnit.begin(function() { + try { + QUnit.start(); + } + catch ( thrownError ) { + globalStartError = thrownError.message; + } +}); + +try { + QUnit.stop(); +} +catch ( thrownError ) { + globalStopError = thrownError.message; +} + +QUnit.module( "global start/stop errors" ); + +QUnit.test( "Call start() when already started", function( assert ) { + assert.expect( 1 ); + assert.equal( globalStartError, "Called start() outside of a test context while already " + + "started" ); +}); + +QUnit.test( "Call stop() outside of test context", function( assert ) { + assert.expect( 1 ); + assert.equal( globalStopError, "Called stop() outside of a test context" ); +}); + +QUnit.module( "start/stop" ); + +QUnit.test( "parallel calls", function( assert ) { + assert.expect( 2 ); + QUnit.stop(); + setTimeout(function() { + assert.ok( true ); + QUnit.start(); + }); + QUnit.stop(); + setTimeout(function() { + assert.ok( true ); + QUnit.start(); + }); +}); + +QUnit.test( "waterfall calls", function( assert ) { + assert.expect( 2 ); + QUnit.stop(); + setTimeout(function() { + assert.ok( true, "first" ); + QUnit.start(); + QUnit.stop(); + setTimeout(function() { + assert.ok( true, "second" ); + QUnit.start(); + }); + }); +}); + +QUnit.test( "fails if start is called more than stop", function( assert ) { + assert.expect( 1 ); + + // Duck-punch to force an Error to be thrown instead of a `pushFailure` call + assert.test.pushFailure = function( msg ) { + throw new Error( msg ); + }; + assert.throws(function() { + QUnit.start(); + }, new RegExp( "Called start\\(\\) while already started \\(test's semaphore was 0 " + + "already\\)" ) ); +}); + +QUnit.test( "fails if start is called with a non-numeric argument", function( assert ) { + QUnit.stop(); + + // Duck-punch to force an Error to be thrown instead of a `pushFailure` call + assert.test.pushFailure = function( msg ) { + throw new Error( msg ); + }; + + assert.throws(function() { + QUnit.start( "ok" ); + }, /Called start\(\) with a non\-numeric decrement\./ ); +}); + +QUnit.module( "asyncTest" ); + +QUnit.asyncTest( "asyncTest", function( assert ) { + assert.expect( 1 ); + setTimeout(function() { + assert.ok( true ); + QUnit.start(); + }); +}); + +QUnit.module( "assert.async" ); + +QUnit.test( "single call", function( assert ) { + var done = assert.async(); + + assert.expect( 1 ); + setTimeout(function() { + assert.ok( true ); + done(); + }); +}); + +QUnit.test( "multiple call", function( assert ) { + var done = assert.async( 4 ); + + assert.expect( 4 ); + setTimeout(function() { + assert.ok( true ); + done(); + }); + setTimeout(function() { + assert.ok( true ); + done(); + }); + setTimeout(function() { + assert.ok( true ); + done(); + }); + setTimeout(function() { + assert.ok( true ); + done(); + }); + +}); + +QUnit.test( "parallel calls", function( assert ) { + var done1 = assert.async(), + done2 = assert.async(); + + assert.expect( 2 ); + setTimeout(function() { + assert.ok( true ); + done1(); + }); + setTimeout(function() { + assert.ok( true ); + done2(); + }); +}); + +QUnit.test( "waterfall calls", function( assert ) { + var done2, + done1 = assert.async(); + + assert.expect( 2 ); + setTimeout(function() { + assert.ok( true, "first" ); + done1(); + done2 = assert.async(); + setTimeout(function() { + assert.ok( true, "second" ); + done2(); + }); + }); +}); + +QUnit.test( "fails if callback is called more than once in test", function( assert ) { + + // Having an outer async flow in this test avoids the need to manually modify QUnit internals + // in order to avoid post-`done` assertions causing additional failures + var done = assert.async(); + + assert.expect( 1 ); + + // Duck-punch to force an Error to be thrown instead of a `pushFailure` call + assert.test.pushFailure = function( msg ) { + throw new Error( msg ); + }; + + var overDone = assert.async(); + overDone(); + + assert.throws(function() { + overDone(); + }, new RegExp( "Too many calls to the `assert.async` callback" ) ); + + done(); +}); + +QUnit.test( "fails if callback is called more than callback call count", function( assert ) { + + // Having an outer async flow in this test avoids the need to manually modify QUnit internals + // in order to avoid post-`done` assertions causing additional failures + var done = assert.async(); + + assert.expect( 1 ); + + // Duck-punch to force an Error to be thrown instead of a `pushFailure` call + assert.test.pushFailure = function( msg ) { + throw new Error( msg ); + }; + + var overDone = assert.async( 3 ); + overDone(); + overDone(); + overDone(); + + assert.throws(function() { + overDone(); + }, new RegExp( "Too many calls to the `assert.async` callback" ) ); + + done(); + +}); + +QUnit.module( "assert.async fails if callback is called more than once in", { + beforeEach: function( assert ) { + + // Having an outer async flow in this test avoids the need to manually modify QUnit + // internals in order to avoid post-`done` assertions causing additional failures + var done = assert.async(); + + assert.expect( 1 ); + + // Duck-punch to force an Error to be thrown instead of a `pushFailure` call + assert.test.pushFailure = function( msg ) { + throw new Error( msg ); + }; + + var overDone = assert.async(); + overDone(); + + assert.throws(function() { + overDone(); + }, new RegExp( "Too many calls to the `assert.async` callback" ) ); + + done(); + } +}); + +QUnit.test( "beforeEach", function( /* assert */ ) { + // noop +}); + +QUnit.module( "assert.async fails if callback is called more than once in", { + afterEach: function( assert ) { + + // Having an outer async flow in this test avoids the need to manually modify QUnit + // internals in order to avoid post-`done` assertions causing additional failures + var done = assert.async(); + + assert.expect( 1 ); + + // Duck-punch to force an Error to be thrown instead of a `pushFailure` call + assert.test.pushFailure = function( msg ) { + throw new Error( msg ); + }; + + var overDone = assert.async(); + overDone(); + + assert.throws(function() { + overDone(); + }, new RegExp( "Too many calls to the `assert.async` callback" ) ); + + done(); + } +}); + +QUnit.test( "afterEach", function( /* assert */ ) { + // noop +}); + +QUnit.module( "assert.async in beforeEach", { + beforeEach: function( assert ) { + var done = assert.async(), + testContext = this; + setTimeout(function() { + testContext.state = "beforeEach"; + done(); + }); + } +}); + +QUnit.test( "beforeEach synchronized", function( assert ) { + assert.expect( 1 ); + assert.equal( this.state, "beforeEach", "beforeEach synchronized before test callback was " + + "executed" ); +}); + +QUnit.module( "assert.async before afterEach", { + afterEach: function( assert ) { + assert.equal( this.state, "done", "test callback synchronized before afterEach was " + + "executed" ); + } +}); + +QUnit.test( "afterEach will synchronize", function( assert ) { + assert.expect( 1 ); + var done = assert.async(), + testContext = this; + setTimeout(function() { + testContext.state = "done"; + done(); + }); +}); + +QUnit.module( "assert.async in afterEach", { + afterEach: function( assert ) { + var done = assert.async(); + setTimeout(function() { + assert.ok( true, "afterEach synchronized before test was finished" ); + done(); + }); + } +}); + +QUnit.test( "afterEach will synchronize", function( assert ) { + assert.expect( 1 ); +}); + +QUnit.module( "assert.async callback event loop timing" ); + +QUnit.test( "`done` can be called synchronously", function( assert ) { + var done; + + assert.expect( 1 ); + done = assert.async(); + + assert.ok( true ); + done(); +}); + +QUnit.test( "sole `done` is called last", function( assert ) { + var done; + + assert.expect( 1 ); + done = assert.async(); + setTimeout(function() { + assert.ok( true, "should pass if called before `done`" ); + done(); + }); +}); + +QUnit.test( "multiple `done` calls, no assertions after final `done`", function( assert ) { + var done1, done2; + + assert.expect( 2 ); + done1 = assert.async(); + done2 = assert.async(); + setTimeout(function() { + done1(); + assert.ok( true, "should pass if called after this `done` but before final `done`" ); + }); + setTimeout(function() { + assert.ok( true, "should pass if called before final `done`" ); + done2(); + }); +}); + +QUnit.module( "assertions after final assert.async callback in test callback fail", { + beforeEach: function( assert ) { + _setupForFailingAssertionsAfterAsyncDone.call( this, assert ); + } +}); + +QUnit.test( "sole `done` is called synchronously BEFORE passing assertion", function( assert ) { + assert.expect( 1 ); + + assert.async()(); + + this._assertCatch(function() { + + // FAIL!!! (with duck-punch to force an Error to be thrown instead of a `pushFailure` call) + assert.ok( true, "should fail with a special `done`-related error message if called " + + "after `done` even if result is passing" ); + }); +}); + +QUnit.test( "sole `done` is called BEFORE assertion", function( assert ) { + var testContext = this, + done = assert.async(); + + assert.expect( 1 ); + + setTimeout(function() { + done(); + + testContext._assertCatch(function() { + + // FAIL!!! (with duck-punch to force an Error to be thrown instead of `pushFailure`) + assert.ok( true, "should fail with a special `done`-related error message if called " + + "after `done` even if result is passing" ); + }); + }); +}); + +QUnit.test( "multiple `done` calls, final `done` is called BEFORE assertion", function( assert ) { + var testContext = this, + done1 = assert.async(), + done2 = assert.async(); + + assert.expect( 2 ); + setTimeout(function() { + done1(); + assert.ok( true, "should pass as this is not after the final `done`" ); + }); + setTimeout(function() { + done2(); + + testContext._assertCatch(function() { + + // FAIL!!! (with duck-punch to force an Error to be thrown instead of `pushFailure`) + assert.ok( true, "should fail with a special `done`-related error message if called " + + "after final `done` even if result is passing" ); + }); + }); +}); + +QUnit.test( "cannot allow assertions between first `done` call and second `assert.async` call", + function( assert ) { + var done2, + testContext = this, + done1 = assert.async(); + + assert.expect( 1 ); + setTimeout(function() { + done1(); + + testContext._assertCatch(function() { + + // FAIL!!! (with duck-punch to force an Error to be thrown instead of `pushFailure`) + assert.ok( true, "should fail with a special `done`-related error message if called " + + "after final `done` even if result is passing" ); + + done2 = assert.async(); + setTimeout(function() { + assert.ok( false, "Should never reach this point anyway" ); + done2(); + }); + }); + }); +}); + +QUnit.module( "assert after last done in beforeEach fail, but allow other phases to run", { + beforeEach: function( assert ) { + _setupForFailingAssertionsAfterAsyncDone.call( this, assert ); + + // THIS IS THE ACTUAL TEST! + assert.expect( 3 ); + this._assertCatch(function() { + assert.async()(); + + // FAIL!!! (with duck-punch to force an Error to be thrown instead of `pushFailure`) + assert.ok( true, "should fail with a special `done`-related error message if called " + + "after final `done` even if result is passing" ); + }); + }, + + afterEach: function( assert ) { + assert.ok( true, "This assertion should still run in afterEach" ); + } +}); + +QUnit.test( "beforeEach will fail but test and afterEach will still run", function( assert ) { + assert.ok( true, "This assertion should still run in the test callback" ); +}); + +QUnit.module( "assert after last done in test fail, but allow other phases to run", { + beforeEach: function( assert ) { + _setupForFailingAssertionsAfterAsyncDone.call( this, assert ); + + assert.expect( 3 ); + assert.ok( true, "This assertion should still run in beforeEach" ); + }, + + afterEach: function( assert ) { + assert.ok( true, "This assertion should still run in afterEach" ); + } +}); + +QUnit.test( "test will fail, but beforeEach and afterEach will still run", function( assert ) { + this._assertCatch(function() { + assert.async()(); + + // FAIL!!! (with duck-punch to force an Error to be thrown instead of `pushFailure`) + assert.ok( true, "should fail with a special `done`-related error message if called " + + "after final `done` even if result is passing" ); + }); +}); + +QUnit.module( "assert after last done in afterEach fail, but allow other phases to run", { + beforeEach: function( assert ) { + _setupForFailingAssertionsAfterAsyncDone.call( this, assert ); + + assert.expect( 3 ); + assert.ok( true, "This assertion should still run in beforeEach" ); + }, + + afterEach: function( assert ) { + this._assertCatch(function() { + assert.async()(); + + // FAIL!!! (with duck-punch to force an Error to be thrown instead of `pushFailure`) + assert.ok( true, "should fail with a special `done`-related error message if called " + + "after final `done` even if result is passing" ); + }); + } +}); + +QUnit.test( "afterEach will fail but beforeEach and test will still run", function( assert ) { + assert.ok( true, "This assertion should still run in the test callback" ); +}); diff -Nru libjs-qunit-1.14.0/test/main/deepEqual.js libjs-qunit-1.22.0/test/main/deepEqual.js --- libjs-qunit-1.14.0/test/main/deepEqual.js 1970-01-01 00:00:00.000000000 +0000 +++ libjs-qunit-1.22.0/test/main/deepEqual.js 2016-02-23 15:57:56.000000000 +0000 @@ -0,0 +1,1878 @@ +/* globals Set:false, Map:false, Symbol:false */ + +QUnit.module( "equiv" ); + +QUnit.test( "Primitive types and constants", function( assert ) { + assert.equal( QUnit.equiv( null, null ), true, "null" ); + assert.equal( QUnit.equiv( null, {} ), false, "null" ); + assert.equal( QUnit.equiv( null, undefined ), false, "null" ); + assert.equal( QUnit.equiv( null, 0 ), false, "null" ); + assert.equal( QUnit.equiv( null, false ), false, "null" ); + assert.equal( QUnit.equiv( null, "" ), false, "null" ); + assert.equal( QUnit.equiv( null, [] ), false, "null" ); + + assert.equal( QUnit.equiv( undefined, undefined ), true, "undefined" ); + assert.equal( QUnit.equiv( undefined, null ), false, "undefined" ); + assert.equal( QUnit.equiv( undefined, 0 ), false, "undefined" ); + assert.equal( QUnit.equiv( undefined, false ), false, "undefined" ); + assert.equal( QUnit.equiv( undefined, {} ), false, "undefined" ); + assert.equal( QUnit.equiv( undefined, [] ), false, "undefined" ); + assert.equal( QUnit.equiv( undefined, "" ), false, "undefined" ); + + // Nan usually doest not equal to Nan using the '==' operator. + // Only isNaN() is able to do it. + assert.equal( QUnit.equiv( 0 / 0, 0 / 0 ), true, "NaN" ); // NaN VS NaN + assert.equal( QUnit.equiv( 1 / 0, 2 / 0 ), true, "Infinity" ); // Infinity VS Infinity + assert.equal( QUnit.equiv( -1 / 0, 2 / 0 ), false, "-Infinity, Infinity" ); // -Infinity VS Infinity + assert.equal( QUnit.equiv( -1 / 0, -2 / 0 ), true, "-Infinity, -Infinity" ); // -Infinity VS -Infinity + assert.equal( QUnit.equiv( 0 / 0, 1 / 0 ), false, "NaN, Infinity" ); // Nan VS Infinity + assert.equal( QUnit.equiv( 1 / 0, 0 / 0 ), false, "NaN, Infinity" ); // Nan VS Infinity + assert.equal( QUnit.equiv( 0 / 0, null ), false, "NaN" ); + assert.equal( QUnit.equiv( 0 / 0, undefined ), false, "NaN" ); + assert.equal( QUnit.equiv( 0 / 0, 0 ), false, "NaN" ); + assert.equal( QUnit.equiv( 0 / 0, false ), false, "NaN" ); + assert.equal( QUnit.equiv( 0 / 0, function() {} ), false, "NaN" ); + assert.equal( QUnit.equiv( 1 / 0, null ), false, "NaN, Infinity" ); + assert.equal( QUnit.equiv( 1 / 0, undefined ), false, "NaN, Infinity" ); + assert.equal( QUnit.equiv( 1 / 0, 0 ), false, "NaN, Infinity" ); + assert.equal( QUnit.equiv( 1 / 0, 1 ), false, "NaN, Infinity" ); + assert.equal( QUnit.equiv( 1 / 0, false ), false, "NaN, Infinity" ); + assert.equal( QUnit.equiv( 1 / 0, true ), false, "NaN, Infinity" ); + assert.equal( QUnit.equiv( 1 / 0, function() {} ), false, "NaN, Infinity" ); + + assert.equal( QUnit.equiv( 0, 0 ), true, "number" ); + assert.equal( QUnit.equiv( 0, 1 ), false, "number" ); + assert.equal( QUnit.equiv( 1, 0 ), false, "number" ); + assert.equal( QUnit.equiv( 1, 1 ), true, "number" ); + assert.equal( QUnit.equiv( 1.1, 1.1 ), true, "number" ); + assert.equal( QUnit.equiv( 0.0000005, 0.0000005 ), true, "number" ); + assert.equal( QUnit.equiv( 0, "" ), false, "number" ); + assert.equal( QUnit.equiv( 0, "0" ), false, "number" ); + assert.equal( QUnit.equiv( 1, "1" ), false, "number" ); + assert.equal( QUnit.equiv( 0, false ), false, "number" ); + assert.equal( QUnit.equiv( 1, true ), false, "number" ); + + assert.equal( QUnit.equiv( true, true ), true, "boolean" ); + assert.equal( QUnit.equiv( true, false ), false, "boolean" ); + assert.equal( QUnit.equiv( false, true ), false, "boolean" ); + assert.equal( QUnit.equiv( false, 0 ), false, "boolean" ); + assert.equal( QUnit.equiv( false, null ), false, "boolean" ); + assert.equal( QUnit.equiv( false, undefined ), false, "boolean" ); + assert.equal( QUnit.equiv( true, 1 ), false, "boolean" ); + assert.equal( QUnit.equiv( true, null ), false, "boolean" ); + assert.equal( QUnit.equiv( true, undefined ), false, "boolean" ); + + assert.equal( QUnit.equiv( "", "" ), true, "string" ); + assert.equal( QUnit.equiv( "a", "a" ), true, "string" ); + assert.equal( QUnit.equiv( "foobar", "foobar" ), true, "string" ); + assert.equal( QUnit.equiv( "foobar", "foo" ), false, "string" ); + assert.equal( QUnit.equiv( "", 0 ), false, "string" ); + assert.equal( QUnit.equiv( "", false ), false, "string" ); + assert.equal( QUnit.equiv( "", null ), false, "string" ); + assert.equal( QUnit.equiv( "", undefined ), false, "string" ); + + // Rename for lint validation. + // We know this is bad, we are asserting whether we can coop with bad code like this. + var SafeNumber = Number, + SafeString = String, + SafeBoolean = Boolean, + SafeObject = Object; + + // primitives vs. objects + + assert.equal( QUnit.equiv( 0, new SafeNumber() ), true, "number 0 primitive vs. object" ); + assert.equal( QUnit.equiv( new SafeNumber(), 0 ), true, "number 0 object vs. primitive" ); + assert.equal( QUnit.equiv( new SafeNumber(), new SafeNumber() ), true, "empty number objects" ); + assert.equal( QUnit.equiv( 1, new SafeNumber( 1 ) ), true, "number 1 primitive vs. object" ); + assert.equal( QUnit.equiv( new SafeNumber( 1 ), 1 ), true, "number 1 object vs. primitive" ); + assert.equal( QUnit.equiv( new SafeNumber( 1 ), new SafeNumber( 1 ) ), true, "number 1 objects" ); + assert.equal( QUnit.equiv( 0, new SafeNumber( 1 ) ), false, "differing number primitive vs. object" ); + assert.equal( QUnit.equiv( new SafeNumber( 0 ), 1 ), false, "differing number object vs. primitive" ); + + assert.equal( QUnit.equiv( "", new SafeString() ), true, "empty string primitive vs. object" ); + assert.equal( QUnit.equiv( new SafeString(), "" ), true, "empty string object vs. primitive" ); + assert.equal( QUnit.equiv( new SafeString(), new SafeString() ), true, "empty string objects" ); + assert.equal( QUnit.equiv( "My String", new SafeString( "My String" ) ), true, "nonempty string primitive vs. object" ); + assert.equal( QUnit.equiv( new SafeString( "My String" ), "My String" ), true, "nonempty string object vs. primitive" ); + assert.equal( QUnit.equiv( new SafeString( "My String" ), new SafeString( "My String" ) ), true, "nonempty string objects" ); + assert.equal( QUnit.equiv( "Bad String", new SafeString( "My String" ) ), false, "differing string primitive vs. object" ); + assert.equal( QUnit.equiv( new SafeString( "Bad String" ), "My String" ), false, "differing string object vs. primitive" ); + + assert.equal( QUnit.equiv( false, new SafeBoolean() ), true, "boolean false primitive vs. object" ); + assert.equal( QUnit.equiv( new SafeBoolean(), false ), true, "boolean empty object vs. primitive" ); + assert.equal( QUnit.equiv( new SafeBoolean(), new SafeBoolean() ), true, "empty boolean objects" ); + assert.equal( QUnit.equiv( true, new SafeBoolean( true ) ), true, "boolean true primitive vs. object" ); + assert.equal( QUnit.equiv( new SafeBoolean( true ), true ), true, "boolean true object vs. primitive" ); + assert.equal( QUnit.equiv( new SafeBoolean( true ), new SafeBoolean( true ) ), true, "boolean true objects" ); + assert.equal( QUnit.equiv( true, new SafeBoolean( 1 ) ), true, "boolean true primitive vs. truthy object" ); + assert.equal( QUnit.equiv( false, new SafeBoolean( false ) ), true, "boolean false primitive vs. false object" ); + assert.equal( QUnit.equiv( new SafeBoolean( false ), false ), true, "boolean false object vs. primitive" ); + assert.equal( QUnit.equiv( new SafeBoolean( false ), new SafeBoolean( false ) ), true, "boolean false objects" ); + assert.equal( QUnit.equiv( false, new SafeBoolean( 0 ) ), true, "boolean false primitive vs. 0 object" ); + assert.equal( QUnit.equiv( true, new SafeBoolean( false ) ), false, "differing boolean primitive vs. object" ); + assert.equal( QUnit.equiv( new SafeBoolean( false ), true ), false, "differing boolean object vs. primitive" ); + + assert.equal( QUnit.equiv( new SafeObject(), {} ), true, "empty object instantiation vs. literal" ); + assert.equal( QUnit.equiv( {}, new SafeObject() ), true, "empty object literal vs. instantiation" ); + assert.equal( QUnit.equiv( new SafeObject(), { a: 1 } ), false, "empty object instantiation vs. nonempty literal" ); + assert.equal( QUnit.equiv( { a: 1 }, new SafeObject() ), false, "nonempty object literal vs. empty instantiation" ); + assert.equal( QUnit.equiv( { a: undefined }, new SafeObject() ), false, "other nonempty object literal vs. empty instantiation" ); + assert.equal( QUnit.equiv( new SafeObject(), { a: undefined } ), false, "empty object instantiation vs. other nonempty literal" ); +}); + +QUnit.test( "Objects basics", function( assert ) { + assert.equal( QUnit.equiv( {}, {} ), true ); + assert.equal( QUnit.equiv( {}, null ), false ); + assert.equal( QUnit.equiv( {}, undefined ), false ); + assert.equal( QUnit.equiv( {}, 0 ), false ); + assert.equal( QUnit.equiv( {}, false ), false ); + + // This test is a hard one, it is very important + // REASONS: + // 1) They are of the same type "object" + // 2) [] instanceof Object is true + // 3) Their properties are the same (doesn't exists) + assert.equal( QUnit.equiv( {}, [] ), false ); + + assert.equal( QUnit.equiv( { a: 1 }, { a: 1 } ), true ); + assert.equal( QUnit.equiv( { a: 1 }, { a: "1" } ), false ); + assert.equal( QUnit.equiv( { a: [] }, { a: [] } ), true ); + assert.equal( QUnit.equiv( { a: {} }, { a: null } ), false ); + assert.equal( QUnit.equiv( { a: 1 }, {} ), false ); + assert.equal( QUnit.equiv( {}, { a: 1 } ), false ); + + // Hard ones + assert.equal( QUnit.equiv( { a: undefined }, {} ), false ); + assert.equal( QUnit.equiv( {}, { a: undefined } ), false ); + assert.equal( QUnit.equiv( + { + a: [ { bar: undefined } ] + }, + { + a: [ { bat: undefined } ] + } + ), false ); + +}); + +QUnit[ typeof Object.create === "function" ? "test" : "skip" ]( + "Objects with null prototypes", function( assert ) { + + var nonEmptyWithNoProto; + + // Objects with no prototype, created via Object.create(null), are used + // e.g. as dictionaries. + // Being able to test equivalence against object literals is quite useful. + assert.equal( + QUnit.equiv( Object.create( null ), {} ), + true, + "empty object without prototype VS empty object" + ); + + assert.equal( + QUnit.equiv( {}, Object.create( null ) ), + true, + "empty object VS empty object without prototype" + ); + + nonEmptyWithNoProto = Object.create( null ); + nonEmptyWithNoProto.foo = "bar"; + + assert.equal( + QUnit.equiv( nonEmptyWithNoProto, { foo: "bar" } ), + true, + "object without prototype VS object" + ); + + assert.equal( + QUnit.equiv( { foo: "bar" }, nonEmptyWithNoProto ), + true, + "object VS object without prototype" + ); +}); + +// Ref #851 +QUnit[ typeof Object.create === "function" ? "test" : "skip" ]( + "Object prototype constructor is null", function( assert ) { + + // Ref #851 + // Unfortunately, in practice `Object.create(null)` is fairly costly. + // To mitigate this cost a specialized NullObject can be used. This + // Object has similar safe characteristics, but with dramatically + // reduced allocation costs. + function NullObject() {} + NullObject.prototype = Object.create( null, { + constructor: { + value: null + } + }); + + var a = new NullObject(); + a.foo = 1; + var b = { foo: 1 }; + + assert.ok( QUnit.equiv( a, b ) ); + assert.ok( QUnit.equiv( b, a ) ); +}); + +QUnit.test( "Arrays basics", function( assert ) { + + assert.equal( QUnit.equiv( [], [] ), true ); + + // May be a hard one, can invoke a crash at execution. + // because their types are both "object" but null isn't + // like a true object, it doesn't have any property at all. + assert.equal( QUnit.equiv( [], null ), false ); + + assert.equal( QUnit.equiv( [], undefined ), false ); + assert.equal( QUnit.equiv( [], false ), false ); + assert.equal( QUnit.equiv( [], 0 ), false ); + assert.equal( QUnit.equiv( [], "" ), false ); + + // May be a hard one, but less hard + // than {} with [] (note the order) + assert.equal( QUnit.equiv( [], {} ), false ); + + assert.equal( QUnit.equiv( [ null ], [] ), false ); + assert.equal( QUnit.equiv( [ undefined ], [] ), false ); + assert.equal( QUnit.equiv( [], [ null ] ), false ); + assert.equal( QUnit.equiv( [], [ undefined ] ), false ); + assert.equal( QUnit.equiv( [ null ], [ undefined ]), false ); + assert.equal( QUnit.equiv( [ [] ], [ [] ] ), true ); + assert.equal( QUnit.equiv( [ [], [], [] ], [ [], [], [] ] ), true ); + assert.equal( QUnit.equiv( + [[],[],[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]], + [[],[],[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]), + true ); + assert.equal( QUnit.equiv( + [[],[],[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]], + [[],[],[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]), // shorter + false ); + assert.equal( QUnit.equiv( + [[],[],[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ {} ]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]], + [[],[],[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]), // deepest element not an array + false ); + + // same multidimensional + assert.equal( QUnit.equiv( + [ 1,2,3,4,5,6,7,8,9, [ + 1,2,3,4,5,6,7,8,9, [ + 1,2,3,4,5,[ + [ 6,7,8,9, [ + [ + 1,2,3,4,[ + 2,3,4,[ + 1,2,[ + 1,2,3,4,[ + 1,2,3,4,5,6,7,8,9,[ + 0 + ],1,2,3,4,5,6,7,8,9 + ],5,6,7,8,9 + ],4,5,6,7,8,9 + ],5,6,7,8,9 + ],5,6,7 + ] + ] + ] + ] + ]]], + [ 1,2,3,4,5,6,7,8,9, [ + 1,2,3,4,5,6,7,8,9, [ + 1,2,3,4,5,[ + [ 6,7,8,9, [ + [ + 1,2,3,4,[ + 2,3,4,[ + 1,2,[ + 1,2,3,4,[ + 1,2,3,4,5,6,7,8,9,[ + 0 + ],1,2,3,4,5,6,7,8,9 + ],5,6,7,8,9 + ],4,5,6,7,8,9 + ],5,6,7,8,9 + ],5,6,7 + ] + ] + ] + ] + ]]]), + true, "Multidimensional" ); + + // different multidimensional + assert.equal( QUnit.equiv( + [ 1,2,3,4,5,6,7,8,9, [ + 1,2,3,4,5,6,7,8,9, [ + 1,2,3,4,5,[ + [ 6,7,8,9, [ + [ + 1,2,3,4,[ + 2,3,4,[ + 1,2,[ + 1,2,3,4,[ + 1,2,3,4,5,6,7,8,9,[ + 0 + ],1,2,3,4,5,6,7,8,9 + ],5,6,7,8,9 + ],4,5,6,7,8,9 + ],5,6,7,8,9 + ],5,6,7 + ] + ] + ] + ] + ]]], + [ 1,2,3,4,5,6,7,8,9, [ + 1,2,3,4,5,6,7,8,9, [ + 1,2,3,4,5,[ + [ 6,7,8,9, [ + [ + 1,2,3,4,[ + 2,3,4,[ + 1,2,[ + "1",2,3,4,[ // string instead of number + 1,2,3,4,5,6,7,8,9,[ + 0 + ],1,2,3,4,5,6,7,8,9 + ],5,6,7,8,9 + ],4,5,6,7,8,9 + ],5,6,7,8,9 + ],5,6,7 + ] + ] + ] + ] + ]]]), + false, "Multidimensional" ); + + // different multidimensional + assert.equal( QUnit.equiv( + [ 1,2,3,4,5,6,7,8,9, [ + 1,2,3,4,5,6,7,8,9, [ + 1,2,3,4,5,[ + [ 6,7,8,9, [ + [ + 1,2,3,4,[ + 2,3,4,[ + 1,2,[ + 1,2,3,4,[ + 1,2,3,4,5,6,7,8,9,[ + 0 + ],1,2,3,4,5,6,7,8,9 + ],5,6,7,8,9 + ],4,5,6,7,8,9 + ],5,6,7,8,9 + ],5,6,7 + ] + ] + ] + ] + ]]], + [ 1,2,3,4,5,6,7,8,9, [ + 1,2,3,4,5,6,7,8,9, [ + 1,2,3,4,5,[ + [ 6,7,8,9, [ + [ + 1,2,3,4,[ + 2,3,[ // missing an element (4) + 1,2,[ + 1,2,3,4,[ + 1,2,3,4,5,6,7,8,9,[ + 0 + ],1,2,3,4,5,6,7,8,9 + ],5,6,7,8,9 + ],4,5,6,7,8,9 + ],5,6,7,8,9 + ],5,6,7 + ] + ] + ] + ] + ]]]), + false, "Multidimensional" ); +}); + +QUnit.test( "Functions", function( assert ) { + var f0 = function() {}, + f1 = function() {}, + + // f2 and f3 have the same code, formatted differently + f2 = function() { return 0; }, + f3 = function() { + + /* jshint asi:true */ + return 0 // this comment and no semicoma as difference + }; + + assert.equal( QUnit.equiv( function() {}, function() {} ), false, "Anonymous functions" ); // exact source code + assert.equal( QUnit.equiv(function() {}, function() { + return true; + }), false, "Anonymous functions" ); + + assert.equal( QUnit.equiv( f0, f0 ), true, "Function references" ); // same references + assert.equal( QUnit.equiv( f0, f1 ), false, "Function references" ); // exact source code, different references + assert.equal( QUnit.equiv( f2, f3 ), false, "Function references" ); // equivalent source code, different references + assert.equal( QUnit.equiv( f1, f2 ), false, "Function references" ); // different source code, different references + assert.equal( QUnit.equiv( function() {}, true ), false ); + assert.equal( QUnit.equiv( function() {}, undefined ), false ); + assert.equal( QUnit.equiv( function() {}, null ), false ); + assert.equal( QUnit.equiv( function() {}, {} ), false ); +}); + +QUnit.test( "Date instances", function( assert ) { + + // Date, we don't need to test Date.parse() because it returns a number. + // Only test the Date instances by setting them a fix date. + // The date use is midnight January 1, 1970 + var d1, d2, d3; + + d1 = new Date(); + d1.setTime( 0 ); // fix the date + + d2 = new Date(); + d2.setTime( 0 ); // fix the date + + d3 = new Date(); // The very now + + // Anyway their types differs, just in case the code fails in the order in which it deals with date + assert.equal( QUnit.equiv( d1, 0 ), false ); // d1.valueOf() returns 0, but d1 and 0 are different + + // test same values date and different instances equality + assert.equal( QUnit.equiv( d1, d2 ), true ); + + // test different date and different instances difference + assert.equal( QUnit.equiv( d1, d3 ), false ); +}); + +QUnit.test( "RegExp", function( assert ) { + // Must test cases that imply those traps: + // var a = /./; + // a instanceof Object; // Oops + // a instanceof RegExp; // Oops + // typeof a === "function"; // Oops, false in IE and Opera, true in FF and Safari ("object") + + // Tests same regex with same modifiers in different order + var regex1, regex2, regex3, r3a, r3b, ru1, ru2, + r1 = /foo/, + r2 = /foo/gim, + r3 = /foo/gmi, + r4 = /foo/igm, + r5 = /foo/img, + r6 = /foo/mig, + r7 = /foo/mgi, + ri1 = /foo/i, + ri2 = /foo/i, + rm1 = /foo/m, + rm2 = /foo/m, + rg1 = /foo/g, + rg2 = /foo/g; + + assert.equal( QUnit.equiv( r2, r3 ), true, "Modifier order" ); + assert.equal( QUnit.equiv( r2, r4 ), true, "Modifier order" ); + assert.equal( QUnit.equiv( r2, r5 ), true, "Modifier order" ); + assert.equal( QUnit.equiv( r2, r6 ), true, "Modifier order" ); + assert.equal( QUnit.equiv( r2, r7 ), true, "Modifier order" ); + assert.equal( QUnit.equiv( r1, r2 ), false, "Modifier" ); + + assert.equal( QUnit.equiv( ri1, ri2 ), true, "Modifier" ); + assert.equal( QUnit.equiv( r1, ri1 ), false, "Modifier" ); + assert.equal( QUnit.equiv( ri1, rm1 ), false, "Modifier" ); + assert.equal( QUnit.equiv( r1, rm1 ), false, "Modifier" ); + assert.equal( QUnit.equiv( rm1, ri1 ), false, "Modifier" ); + assert.equal( QUnit.equiv( rm1, rm2 ), true, "Modifier" ); + assert.equal( QUnit.equiv( rg1, rm1 ), false, "Modifier" ); + assert.equal( QUnit.equiv( rm1, rg1 ), false, "Modifier" ); + assert.equal( QUnit.equiv( rg1, rg2 ), true, "Modifier" ); + + // Compare unicode modifier + try { + r2 = new RegExp( "foo", "umig" ); + r3 = new RegExp( "foo", "mgiu" ); + assert.equal( QUnit.equiv( r2, r3 ), true, "Modifier order" ); + assert.equal( QUnit.equiv( r1, r2 ), false, "Modifier" ); + + ru1 = new RegExp( "/u{1D306}", "u" ); + ru2 = new RegExp( "/u{1D306}", "u" ); + assert.equal( QUnit.equiv( ru1, rg1 ), false, "Modifier" ); + assert.equal( QUnit.equiv( rg1, ru1 ), false, "Modifier" ); + assert.equal( QUnit.equiv( ru1, ru2 ), true, "Modifier" ); + } catch ( e ) {} + + // Different regex, same modifiers + r1 = /[a-z]/gi; + r2 = /[0-9]/gi; // oops! different + assert.equal( QUnit.equiv( r1, r2 ), false, "Regex pattern" ); + + r1 = /0/ig; + r2 = /"0"/ig; // oops! different + assert.equal( QUnit.equiv( r1, r2 ), false, "Regex pattern" ); + + r1 = /[\n\r\u2028\u2029]/g; + r2 = /[\n\r\u2028\u2029]/g; + r3 = /[\n\r\u2028\u2028]/g; // differs from r1 + r4 = /[\n\r\u2028\u2029]/; // differs from r1 + + assert.equal( QUnit.equiv( r1, r2 ), true, "Regex pattern" ); + assert.equal( QUnit.equiv( r1, r3 ), false, "Regex pattern" ); + assert.equal( QUnit.equiv( r1, r4 ), false, "Regex pattern" ); + + // More complex regex + regex1 = "^[-_.a-z0-9]+@([-_a-z0-9]+\\.)+([A-Za-z][A-Za-z]|[A-Za-z][A-Za-z][A-Za-z])|(([0-9][0-9]?|[0-1][0-9][0-9]|[2][0-4][0-9]|[2][5][0-5]))$"; + regex2 = "^[-_.a-z0-9]+@([-_a-z0-9]+\\.)+([A-Za-z][A-Za-z]|[A-Za-z][A-Za-z][A-Za-z])|(([0-9][0-9]?|[0-1][0-9][0-9]|[2][0-4][0-9]|[2][5][0-5]))$"; + // regex 3 is different: '.' not escaped + regex3 = "^[-_.a-z0-9]+@([-_a-z0-9]+.)+([A-Za-z][A-Za-z]|[A-Za-z][A-Za-z][A-Za-z])|(([0-9][0-9]?|[0-1][0-9][0-9]|[2][0-4][0-9]|[2][5][0-5]))$"; + + r1 = new RegExp( regex1 ); + r2 = new RegExp( regex2 ); + r3 = new RegExp( regex3 ); // diff from r21, not same pattern + r3a = new RegExp( regex3, "gi" ); // diff from r23, not same modifier + r3b = new RegExp( regex3, "ig" ); // same as r23a + + assert.equal( QUnit.equiv( r1, r2 ), true, "Complex Regex" ); + assert.equal( QUnit.equiv( r1, r3 ), false, "Complex Regex" ); + assert.equal( QUnit.equiv( r3, r3a ), false, "Complex Regex" ); + assert.equal( QUnit.equiv( r3a, r3b ), true, "Complex Regex" ); + + // typeof r1 is "function" in some browsers and "object" in others so we must cover this test + r1 = / /; + assert.equal( QUnit.equiv( r1, function() {} ), false, "Regex internal" ); + assert.equal( QUnit.equiv( r1, {} ), false, "Regex internal" ); +}); + +QUnit.test( "Complex objects", function( assert ) { + + function fn1() { + return "fn1"; + } + function fn2() { + return "fn2"; + } + + // Try to invert the order of some properties to make sure it is covered. + // It can failed when properties are compared between unsorted arrays. + assert.equal( QUnit.equiv( + { + a: 1, + b: null, + c: [ {} ], + d: { + a: 3.14159, + b: false, + c: { + e: fn1, + f: [[[]]], + g: { + j: { + k: { + n: { + r: "r", + s: [ 1, 2, 3 ], + t: undefined, + u: 0, + v: { + w: { + x: { + y: "Yahoo!", + z: null + } + } + } + }, + q: [], + p: 1 / 0, + o: 99 + }, + l: undefined, + m: null + } + }, + d: 0, + i: true, + h: "false" + } + }, + e: undefined, + g: "", + h: "h", + f: {}, + i: [] + }, + { + a: 1, + b: null, + c: [ {} ], + d: { + b: false, + a: 3.14159, + c: { + d: 0, + e: fn1, + f: [[[]]], + g: { + j: { + k: { + n: { + r: "r", + t: undefined, + u: 0, + s: [ 1, 2, 3 ], + v: { + w: { + x: { + z: null, + y: "Yahoo!" + } + } + } + }, + o: 99, + p: 1 / 0, + q: [] + }, + l: undefined, + m: null + } + }, + i: true, + h: "false" + } + }, + e: undefined, + g: "", + f: {}, + h: "h", + i: [] + } + ), true); + + assert.equal( QUnit.equiv( + { + a: 1, + b: null, + c: [ {} ], + d: { + a: 3.14159, + b: false, + c: { + d: 0, + e: fn1, + f: [[[]]], + g: { + j: { + k: { + n: { + //r: "r", // different: missing a property + s: [ 1, 2, 3 ], + t: undefined, + u: 0, + v: { + w: { + x: { + y: "Yahoo!", + z: null + } + } + } + }, + o: 99, + p: 1 / 0, + q: [] + }, + l: undefined, + m: null + } + }, + h: "false", + i: true + } + }, + e: undefined, + f: {}, + g: "", + h: "h", + i: [] + }, + { + a: 1, + b: null, + c: [ {} ], + d: { + a: 3.14159, + b: false, + c: { + d: 0, + e: fn1, + f: [[[]]], + g: { + j: { + k: { + n: { + r: "r", + s: [ 1, 2, 3 ], + t: undefined, + u: 0, + v: { + w: { + x: { + y: "Yahoo!", + z: null + } + } + } + }, + o: 99, + p: 1 / 0, + q: [] + }, + l: undefined, + m: null + } + }, + h: "false", + i: true + } + }, + e: undefined, + f: {}, + g: "", + h: "h", + i: [] + } + ), false); + + assert.equal( QUnit.equiv( + { + a: 1, + b: null, + c: [ {} ], + d: { + a: 3.14159, + b: false, + c: { + d: 0, + e: fn1, + f: [[[]]], + g: { + j: { + k: { + n: { + r: "r", + s: [ 1, 2, 3 ], + t: undefined, + u: 0, + v: { + w: { + x: { + y: "Yahoo!", + z: null + } + } + } + }, + o: 99, + p: 1 / 0, + q: [] + }, + l: undefined, + m: null + } + }, + h: "false", + i: true + } + }, + e: undefined, + f: {}, + g: "", + h: "h", + i: [] + }, + { + a: 1, + b: null, + c: [ {} ], + d: { + a: 3.14159, + b: false, + c: { + d: 0, + e: fn1, + f: [[[]]], + g: { + j: { + k: { + n: { + r: "r", + s: [ 1, 2, 3 ], + //t: undefined, // different: missing a property with an undefined value + u: 0, + v: { + w: { + x: { + y: "Yahoo!", + z: null + } + } + } + }, + o: 99, + p: 1 / 0, + q: [] + }, + l: undefined, + m: null + } + }, + h: "false", + i: true + } + }, + e: undefined, + f: {}, + g: "", + h: "h", + i: [] + } + ), false); + + assert.equal( QUnit.equiv( + { + a: 1, + b: null, + c: [ {} ], + d: { + a: 3.14159, + b: false, + c: { + d: 0, + e: fn1, + f: [[[]]], + g: { + j: { + k: { + n: { + r: "r", + s: [ 1, 2, 3 ], + t: undefined, + u: 0, + v: { + w: { + x: { + y: "Yahoo!", + z: null + } + } + } + }, + o: 99, + p: 1 / 0, + q: [] + }, + l: undefined, + m: null + } + }, + h: "false", + i: true + } + }, + e: undefined, + f: {}, + g: "", + h: "h", + i: [] + }, + { + a: 1, + b: null, + c: [ {} ], + d: { + a: 3.14159, + b: false, + c: { + d: 0, + e: fn1, + f: [[[]]], + g: { + j: { + k: { + n: { + r: "r", + s: [ 1, 2, 3 ], + t: undefined, + u: 0, + v: { + w: { + x: { + y: "Yahoo!", + z: null + } + } + } + }, + o: 99, + p: 1 / 0, + q: {} // different was [] + }, + l: undefined, + m: null + } + }, + h: "false", + i: true + } + }, + e: undefined, + f: {}, + g: "", + h: "h", + i: [] + } + ), false ); + + var same1 = { + a: [ + "string", null, 0, "1", 1, { + prop: null, + foo: [ 1, 2, null, {}, [], [ 1, 2, 3 ] ], + bar: undefined + }, 3, "Hey!", "Κάνε πάντα γνωÏίζουμε ας των, μηχανής επιδιόÏθωσης επιδιοÏθώσεις ÏŽÏ‚ μια. Κλπ ας" + ], + unicode: "è€ æ±‰è¯ä¸å˜åœ¨ 港澳和海外的åŽäººåœˆä¸ 贵州 我去了书店 现在尚有争", + b: "b", + c: fn1 + }, + + same2 = { + a: [ + "string", null, 0, "1", 1, { + prop: null, + foo: [ 1, 2, null, {}, [], [ 1, 2, 3 ] ], + bar: undefined + }, 3, "Hey!", "Κάνε πάντα γνωÏίζουμε ας των, μηχανής επιδιόÏθωσης επιδιοÏθώσεις ÏŽÏ‚ μια. Κλπ ας" + ], + unicode: "è€ æ±‰è¯ä¸å˜åœ¨ 港澳和海外的åŽäººåœˆä¸ 贵州 我去了书店 现在尚有争", + b: "b", + c: fn1 + }, + + diff1 = { + a: [ + "string", null, 0, "1", 1, { + prop: null, + foo: [ 1, 2, null, {}, [], [ 1, 2, 3, 4 ] ], // different: 4 was add to the array + bar: undefined + }, 3, "Hey!", "Κάνε πάντα γνωÏίζουμε ας των, μηχανής επιδιόÏθωσης επιδιοÏθώσεις ÏŽÏ‚ μια. Κλπ ας" + ], + unicode: "è€ æ±‰è¯ä¸å˜åœ¨ 港澳和海外的åŽäººåœˆä¸ 贵州 我去了书店 现在尚有争", + b: "b", + c: fn1 + }, + + diff2 = { + a: [ + "string", null, 0, "1", 1, { + prop: null, + foo: [ 1, 2, null, {}, [], [ 1, 2, 3 ] ], + newprop: undefined, // different: newprop was added + bar: undefined + }, 3, "Hey!", "Κάνε πάντα γνωÏίζουμε ας των, μηχανής επιδιόÏθωσης επιδιοÏθώσεις ÏŽÏ‚ μια. Κλπ ας" + ], + unicode: "è€ æ±‰è¯ä¸å˜åœ¨ 港澳和海外的åŽäººåœˆä¸ 贵州 我去了书店 现在尚有争", + b: "b", + c: fn1 + }, + + diff3 = { + a: [ + "string", null, 0, "1", 1, { + prop: null, + foo: [ 1, 2, null, {}, [], [ 1, 2, 3 ] ], + bar: undefined + }, 3, "Hey!", "Κάνε πάντα γνωÏίζουμε ας των, μηχανής επιδιόÏθωσης επιδιοÏθώσεις ÏŽÏ‚ μια. Κλπ α" // different: missing last char + ], + unicode: "è€ æ±‰è¯ä¸å˜åœ¨ 港澳和海外的åŽäººåœˆä¸ 贵州 我去了书店 现在尚有争", + b: "b", + c: fn1 + }, + + diff4 = { + a: [ + "string", null, 0, "1", 1, { + prop: null, + foo: [ 1,2,undefined,{}, [], [ 1, 2, 3 ] ], // different: undefined instead of null + bar: undefined + }, 3, "Hey!", "Κάνε πάντα γνωÏίζουμε ας των, μηχανής επιδιόÏθωσης επιδιοÏθώσεις ÏŽÏ‚ μια. Κλπ ας" + ], + unicode: "è€ æ±‰è¯ä¸å˜åœ¨ 港澳和海外的åŽäººåœˆä¸ 贵州 我去了书店 现在尚有争", + b: "b", + c: fn1 + }, + + diff5 = { + a: [ + "string", null, 0, "1", 1, { + prop: null, + foo: [ 1, 2, null, {}, [], [ 1, 2, 3 ] ], + bat: undefined // different: property name not "bar" + }, 3, "Hey!", "Κάνε πάντα γνωÏίζουμε ας των, μηχανής επιδιόÏθωσης επιδιοÏθώσεις ÏŽÏ‚ μια. Κλπ ας" + ], + unicode: "è€ æ±‰è¯ä¸å˜åœ¨ 港澳和海外的åŽäººåœˆä¸ 贵州 我去了书店 现在尚有争", + b: "b", + c: fn1 + }, + + diff6 = { + a: [ + "string", null, 0, "1", 1, { + prop: null, + foo: [ 1, 2, null, {}, [], [ 1, 2, 3 ] ], + bar: undefined + }, 3, "Hey!", "Κάνε πάντα γνωÏίζουμε ας των, μηχανής επιδιόÏθωσης επιδιοÏθώσεις ÏŽÏ‚ μια. Κλπ ας" + ], + unicode: "è€ æ±‰è¯ä¸å˜åœ¨ 港澳和海外的åŽäººåœˆä¸ 贵州 我去了书店 现在尚有争", + b: "b", + c: fn2 // different: fn2 instead of fn1 + }; + + assert.equal( QUnit.equiv( same1, same2 ), true ); + assert.equal( QUnit.equiv( same2, same1 ), true ); + assert.equal( QUnit.equiv( same2, diff1 ), false ); + assert.equal( QUnit.equiv( diff1, same2 ), false ); + + assert.equal( QUnit.equiv( same1, diff1 ), false ); + assert.equal( QUnit.equiv( same1, diff2 ), false ); + assert.equal( QUnit.equiv( same1, diff3 ), false ); + assert.equal( QUnit.equiv( same1, diff3 ), false ); + assert.equal( QUnit.equiv( same1, diff4 ), false ); + assert.equal( QUnit.equiv( same1, diff5 ), false ); + assert.equal( QUnit.equiv( same1, diff6 ), false ); + assert.equal( QUnit.equiv( diff5, diff1 ), false ); +}); + +QUnit.test( "Complex Arrays", function( assert ) { + + function fn() {} + + assert.equal( QUnit.equiv( + [ 1, 2, 3, true, {}, null, [ + { + a: [ "", "1", 0 ] + }, + 5, 6, 7 + ], "foo" ], + [ 1, 2, 3, true, {}, null, [ + { + a: [ "", "1", 0 ] + }, + 5, 6, 7 + ], "foo" ] ), + true ); + + assert.equal( QUnit.equiv( + [ 1, 2, 3, true, {}, null, [ + { + a: [ "", "1", 0 ] + }, + 5, 6, 7 + ], "foo" ], + [ 1, 2, 3, true, {}, null, [ + { + b: [ "", "1", 0 ] // not same property name + }, + 5, 6, 7 + ], "foo" ] ), + false ); + + var a = [ { + b: fn, + c: false, + "do": "reserved word", + "for": { + ar: [ 3, 5, 9, "hey!", [], { + ar: [ 1,[ + 3,4,6,9, null, [], [] + ] ], + e: fn, + f: undefined + } ] + }, + e: 0.43445 + }, 5, "string", 0, fn, false, null, undefined, 0, [ + 4,5,6,7,8,9,11,22,33,44,55,"66", null, [], [[[[[ 3 ]]]], "3" ], {}, 1 / 0 + ], [], [ [ [], "foo", null, { + n: 1 / 0, + z: { + a: [ 3, 4, 5, 6, "yep!", undefined, undefined ], + b: {} + } + }, {} ] ] ]; + + assert.equal( QUnit.equiv( a, + [ { + b: fn, + c: false, + "do": "reserved word", + "for": { + ar: [ 3, 5, 9, "hey!", [], { + ar: [ 1, [ + 3,4,6,9, null, [], [] + ]], + e: fn, + f: undefined + } ] + }, + e: 0.43445 + }, 5, "string", 0, fn, false, null, undefined, 0, [ + 4,5,6,7,8,9,11,22,33,44,55,"66", null, [], [[[[[ 3 ]]]], "3" ], {}, 1 / 0 + ], [], [[[], "foo", null, { + n: 1 / 0, + z: { + a: [ 3, 4, 5, 6, "yep!", undefined, undefined ], + b: {} + } + }, {}]]]), true); + + assert.equal( QUnit.equiv( a, + [ { + b: fn, + c: false, + "do": "reserved word", + "for": { + ar: [ 3, 5, 9, "hey!", [], { + ar: [ 1, [ + 3,4,6,9, null, [], [] + ]], + e: fn, + f: undefined + } ] + }, + e: 0.43445 + }, 5, "string", 0, fn, false, null, undefined, 0, [ + 4,5,6,7,8,9,11,22,33,44,55,"66", null, [], [[[[[ 2 ]]]], "3"], {}, 1 / 0 // different: [[[[[2]]]]] instead of [[[[[3]]]]] + ], [], [[[], "foo", null, { + n: 1 / 0, + z: { + a: [ 3, 4, 5, 6, "yep!", undefined, undefined ], + b: {} + } + }, {}]]]), false); + + assert.equal( QUnit.equiv( a, + [ { + b: fn, + c: false, + "do": "reserved word", + "for": { + ar: [ 3, 5, 9, "hey!", [], { + ar: [ 1, [ + 3,4,6,9, null, [], [] + ]], + e: fn, + f: undefined + } ] + }, + e: 0.43445 + }, 5, "string", 0, fn, false, null, undefined, 0, [ + 4,5,6,7,8,9,11,22,33,44,55,"66", null, [], [[[[[ 3 ]]]], "3" ], {}, 1 / 0 + ], [], [[[], "foo", null, { + n: -1 / 0, // different, -Infinity instead of Infinity + z: { + a: [ 3, 4, 5, 6, "yep!", undefined, undefined ], + b: {} + } + }, {}]]]), false); + + assert.equal( QUnit.equiv( a, + [ { + b: fn, + c: false, + "do": "reserved word", + "for": { + ar: [ 3, 5, 9, "hey!", [], { + ar: [ 1, [ + 3,4,6,9, null, [], [] + ]], + e: fn, + f: undefined + } ] + }, + e: 0.43445 + }, 5, "string", 0, fn, false, null, undefined, 0, [ + 4,5,6,7,8,9,11,22,33,44,55,"66", null, [], [[[[[ 3 ]]]], "3" ], {}, 1 / 0 + ], [], [[[], "foo", { // different: null is missing + n: 1 / 0, + z: { + a: [ 3, 4, 5, 6, "yep!", undefined, undefined ], + b: {} + } + }, {}]]]), false); + + assert.equal( QUnit.equiv( a, + [ { + b: fn, + c: false, + "do": "reserved word", + "for": { + ar: [ 3, 5, 9, "hey!", [], { + ar: [ 1, [ + 3,4,6,9, null, [], [] + ]], + e: fn + // different: missing property f: undefined + } ] + }, + e: 0.43445 + }, 5, "string", 0, fn, false, null, undefined, 0, [ + 4,5,6,7,8,9,11,22,33,44,55,"66", null, [], [[[[[ 3 ]]]], "3" ], {}, 1 / 0 + ], [], [[[], "foo", null, { + n: 1 / 0, + z: { + a: [ 3, 4, 5, 6, "yep!", undefined, undefined ], + b: {} + } + }, {}]]]), false); +}); + +QUnit.test( "Prototypal inheritance", function( assert ) { + function Gizmo( id ) { + this.id = id; + } + + function Hoozit( id ) { + this.id = id; + } + Hoozit.prototype = new Gizmo(); + + var gizmo = new Gizmo( "ok" ), + hoozit = new Hoozit( "ok" ); + + // Try this test many times after test on instances that hold function + // to make sure that our code does not mess with last object constructor memoization. + assert.equal( QUnit.equiv( function() {}, function() {} ), false ); + + // Hoozit inherit from Gizmo + // hoozit instanceof Hoozit; // true + // hoozit instanceof Gizmo; // true + assert.equal( QUnit.equiv( hoozit, gizmo ), true ); + + Gizmo.prototype.bar = true; // not a function just in case we skip them + + // Hoozit inherit from Gizmo + // They are equivalent + assert.equal( QUnit.equiv( hoozit, gizmo ), true ); + + // Make sure this is still true !important + // The reason for this is that I forgot to reset the last + // caller to where it were called from. + assert.equal( QUnit.equiv( function() {}, function() {} ), false ); + + // Make sure this is still true !important + assert.equal( QUnit.equiv( hoozit, gizmo ), true ); + + Hoozit.prototype.foo = true; // not a function just in case we skip them + + // Gizmo does not inherit from Hoozit + // gizmo instanceof Gizmo; // true + // gizmo instanceof Hoozit; // false + // They are not equivalent + assert.equal( QUnit.equiv( hoozit, gizmo ), false ); + + // Make sure this is still true !important + assert.equal( QUnit.equiv( function() {}, function() {} ), false ); +}); + +QUnit.test( "Instances", function( assert ) { + var a1, a2, b1, b2, car, carSame, carDiff, human; + + function A() {} + a1 = new A(); + a2 = new A(); + + function B() { + this.fn = function() {}; + } + b1 = new B(); + b2 = new B(); + + assert.equal( QUnit.equiv( a1, a2 ), true, "Same property, same constructor" ); + + // b1.fn and b2.fn are functions but they are different references + // But we decided to skip function for instances. + assert.equal( QUnit.equiv( b1, b2 ), true, "Same property, same constructor" ); + + // failed + assert.equal( QUnit.equiv( a1, b1 ), false, "Same properties but different constructor" ); + + function Car( year ) { + var privateVar = 0; + this.year = year; + this.isOld = function() { + return privateVar > 10; + }; + } + + function Human( year ) { + var privateVar = 1; + this.year = year; + this.isOld = function() { + return privateVar > 80; + }; + } + + car = new Car( 30 ); + carSame = new Car( 30 ); + carDiff = new Car( 10 ); + human = new Human( 30 ); + + /** + * difference: + * - year: 30 + * same: + * - year: 30, + * - isOld: function () {} + */ + + assert.equal( QUnit.equiv( car, car ), true ); + assert.equal( QUnit.equiv( car, carDiff ), false ); + assert.equal( QUnit.equiv( car, carSame ), true ); + assert.equal( QUnit.equiv( car, human ), false ); +}); + +QUnit.test( + "Complex instance nesting (with function values in literals and/or in nested instances)", + function( assert ) { + var a1, a2, b1, b2, c1, c2, d1, d2, e1, e2; + + function A( fn ) { + this.a = {}; + this.fn = fn; + this.b = { a: [] }; + this.o = {}; + this.fn1 = fn; + } + function B( fn ) { + this.fn = fn; + this.fn1 = function() {}; + this.a = new A(function() {}); + } + + function fnOutside() { + } + + function C( fn ) { + function fnInside() { + } + this.x = 10; + this.fn = fn; + this.fn1 = function() {}; + this.fn2 = fnInside; + this.fn3 = { + a: true, + b: fnOutside // ok make reference to a function in all instances scope + }; + this.o1 = {}; + + // This function will be ignored. + // Even if it is not visible for all instances (e.g. locked in a closures), + // it is from a property that makes part of an instance (e.g. from the C constructor) + this.b1 = new B(function() {}); + this.b2 = new B({ + x: { + b2: new B(function() {}) + } + }); + } + + function D( fn ) { + function fnInside() { + } + this.x = 10; + this.fn = fn; + this.fn1 = function() {}; + this.fn2 = fnInside; + this.fn3 = { + a: true, + b: fnOutside, // ok make reference to a function in all instances scope + + // This function won't be ignored. + // It isn't visible for all C instances + // and it is not in a property of an instance. + // (in an Object instances e.g. the object literal) + c: fnInside + }; + this.o1 = {}; + + // This function will be ignored. + // Even if it is not visible for all instances (e.g. locked in a closures), + // it is from a property that makes part of an instance (e.g. from the C constructor) + this.b1 = new B(function() {}); + this.b2 = new B({ + x: { + b2: new B(function() {}) + } + }); + } + + function E( fn ) { + function fnInside() { + } + this.x = 10; + this.fn = fn; + this.fn1 = function() {}; + this.fn2 = fnInside; + this.fn3 = { + a: true, + b: fnOutside // ok make reference to a function in all instances scope + }; + this.o1 = {}; + + // This function will be ignored. + // Even if it is not visible for all instances (e.g. locked in a closures), + // it is from a property that makes part of an instance (e.g. from the C constructor) + this.b1 = new B(function() {}); + this.b2 = new B({ + x: { + b1: new B( { a: function() {} } ), + b2: new B(function() {}) + } + }); + } + + a1 = new A(function() {}); + a2 = new A(function() {}); + assert.equal( QUnit.equiv( a1, a2 ), true ); + + assert.equal( QUnit.equiv( a1, a2 ), true ); // different instances + + b1 = new B(function() {}); + b2 = new B(function() {}); + assert.equal( QUnit.equiv( b1, b2 ), true ); + + c1 = new C(function() {}); + c2 = new C(function() {}); + assert.equal( QUnit.equiv( c1, c2 ), true ); + + d1 = new D(function() {}); + d2 = new D(function() {}); + assert.equal( QUnit.equiv( d1, d2 ), false ); + + e1 = new E(function() {}); + e2 = new E(function() {}); + assert.equal( QUnit.equiv( e1, e2 ), false ); + } +); + +QUnit.test( "Object with circular references", function( assert ) { + var circularA = { + abc: null + }, + circularB = { + abc: null + }; + circularA.abc = circularA; + circularB.abc = circularB; + assert.equal( QUnit.equiv( circularA, circularB ), true, + "Should not repeat test on object (ambiguous test)" + ); + + circularA.def = 1; + circularB.def = 1; + assert.equal( QUnit.equiv( circularA, circularB ), true, + "Should not repeat test on object (ambiguous test)" + ); + + circularA.def = 1; + circularB.def = 0; + assert.equal( QUnit.equiv( circularA, circularB ), false, + "Should not repeat test on object (unambiguous test)" + ); +}); + +QUnit.test( "Array with circular references", function( assert ) { + var circularA = [], + circularB = []; + circularA.push( circularA ); + circularB.push( circularB ); + assert.equal( QUnit.equiv( circularA, circularB ), true, + "Should not repeat test on array (ambiguous test)" + ); + + circularA.push( "abc" ); + circularB.push( "abc" ); + assert.equal( QUnit.equiv( circularA, circularB ), true, + "Should not repeat test on array (ambiguous test)" + ); + + circularA.push( "hello" ); + circularB.push( "goodbye" ); + assert.equal( QUnit.equiv( circularA, circularB ), false, + "Should not repeat test on array (unambiguous test)" + ); +}); + +QUnit.test( "Mixed object/array with references to self wont loop", function( assert ) { + var circularA = [ { + abc: null + } ], + circularB = [ { + abc: null + } ]; + circularA[ 0 ].abc = circularA; + circularB[ 0 ].abc = circularB; + + circularA.push( circularA ); + circularB.push( circularB ); + assert.equal( QUnit.equiv( circularA, circularB ), true, + "Should not repeat test on object/array (ambiguous test)" + ); + + circularA[ 0 ].def = 1; + circularB[ 0 ].def = 1; + assert.equal( QUnit.equiv( circularA, circularB ), true, + "Should not repeat test on object/array (ambiguous test)" + ); + + circularA[ 0 ].def = 1; + circularB[ 0 ].def = 0; + assert.equal( QUnit.equiv( circularA, circularB ), false, + "Should not repeat test on object/array (unambiguous test)" + ); +}); + +QUnit.test( "Compare self-referent to tree", function( assert ) { + var temp, + circularA = [ 0 ], + treeA = [ 0, null ], + circularO = {}, + treeO = { + o: null + }; + + circularA[ 1 ] = circularA; + circularO.o = circularO; + + assert.equal( QUnit.equiv( circularA, treeA ), false, + "Array: Should not consider circular equal to tree" + ); + assert.equal( QUnit.equiv( circularO, treeO ), false, + "Object: Should not consider circular equal to tree" + ); + + temp = [ 0, circularA ]; + assert.equal( QUnit.equiv( circularA, temp ), true, + "Array: Reference is circular for one, but equal on other" + ); + assert.equal( QUnit.equiv( temp, circularA ), true, + "Array: Reference is circular for one, but equal on other" + ); + + temp = { + o: circularO + }; + assert.equal( QUnit.equiv( circularO, temp ), true, + "Object: Reference is circular for one, but equal on other" + ); + assert.equal( QUnit.equiv( temp, circularO ), true, + "Object: Reference is circular for one, but equal on other" + ); +}); + +QUnit.test( "Test that must be done at the end because they extend some primitive's prototype", + function( assert ) { + + // Try that a function looks like our regular expression. + // This tests if we check that a and b are really both instance of RegExp + Function.prototype.global = true; + Function.prototype.multiline = true; + Function.prototype.ignoreCase = false; + Function.prototype.source = "my regex"; + var re = /my regex/gm; + assert.equal( QUnit.equiv( re, function() {}), false, + "A function that looks that a regex isn't a regex" + ); + + // This test will ensures it works in both ways, + // and ALSO especially that we can make differences + // between RegExp and Function constructor because + // typeof on a RegExpt instance is "function" + assert.equal( QUnit.equiv(function() {}, re ), false, + "Same conversely, but ensures that function and regexp are distinct because their constructor are different" + ); + } +); + +QUnit.module( "equiv Object-wrapped primitives" ); + +QUnit.test( "Number", function( assert ) { + var SafeNumber = Number; + + assert.ok( QUnit.equiv( new SafeNumber( 1 ), new SafeNumber( 1 ) ), + "Number objects with same values are equivalent." + ); + assert.ok( QUnit.equiv( new SafeNumber( 0 / 0 ), new SafeNumber( 0 / 0 ) ), + "NaN Number objects are equivalent." + ); + assert.ok( QUnit.equiv( new SafeNumber( 1 / 0 ), new SafeNumber( 2 / 0 ) ), + "Infinite Number objects are equivalent." + ); + + assert.notOk( QUnit.equiv( new SafeNumber( 1 ), new SafeNumber( 2 ) ), + "Number objects with different values are not equivalent." + ); + assert.notOk( QUnit.equiv( new SafeNumber( 0 / 0 ), new SafeNumber( 1 / 0 ) ), + "NaN Number objects and infinite Number objects are not equivalent." + ); + assert.notOk( QUnit.equiv( new SafeNumber( 1 / 0 ), new SafeNumber( -1 / 0 ) ), + "Positive and negative infinite Number objects are not equivalent." + ); +}); + +QUnit.test( "String", function( assert ) { + var SafeString = String; + + assert.ok( QUnit.equiv( new SafeString( "foo" ), new SafeString( "foo" ) ), + "String objects with same values are equivalent." + ); + assert.ok( QUnit.equiv( new SafeString( "" ), new SafeString( "" ) ), + "Empty String objects are equivalent." + ); + + assert.notOk( QUnit.equiv( new SafeString( "foo" ), new SafeString( "bar" ) ), + "String objects with different values are not equivalent." + ); + assert.notOk( QUnit.equiv( new SafeString( "" ), new SafeString( "foo" ) ), + "Empty and nonempty String objects are not equivalent." + ); +}); + +QUnit.test( "Boolean", function( assert ) { + var SafeBoolean = Boolean; + + assert.ok( QUnit.equiv( new SafeBoolean( true ), new SafeBoolean( true ) ), + "True Boolean objects are equivalent." + ); + assert.ok( QUnit.equiv( new SafeBoolean( false ), new SafeBoolean( false ) ), + "False Boolean objects are equivalent." + ); + + assert.notOk( QUnit.equiv( new SafeBoolean( true ), new SafeBoolean( false ) ), + "Boolean objects with different values are not equivalent." + ); +}); + +QUnit.module( "equiv Maps and Sets" ); + +var hasES6Set = ( function() { + if ( typeof Set !== "function" ) { + return false; + } + + try { + // some platforms don't support iterables in Set constructors + var s = new Set( [ 1, 2, 3 ] ); + if ( s.size !== 3 || !s.has( 2 ) ) { + return false; + } + + // in IE 11, QUnit.objectType( new Set() ) === "object" + return ( QUnit.objectType( s ) === "set" ); + } + catch ( e ) { + return false; + } +} )(); + +var hasES6Map = ( function() { + if ( typeof Map !== "function" ) { + return false; + } + + try { + // some platforms don't support array-like iterables in Map constructors + var m = new Map( [ [ 1, 2 ] ] ); + if ( m.size !== 2 || !m.has( 1 ) ) { + return false; + } + + // in IE 11, QUnit.objectType( new Map() ) === "object" + return ( QUnit.objectType( m ) === "map" ); + } + catch ( e ) { + return false; + } +} )(); + +QUnit[ hasES6Set ? "test" : "skip" ]( "Sets", function ( assert ) { + var s1, s2, s3, s4, o1, o2, o3, m1, m2, m3; + + // Empty sets + s1 = new Set(); + s2 = new Set( [] ); + assert.equal( QUnit.equiv( s1, s2 ), true, "Empty sets" ); + + // Simple cases + s1 = new Set( [ 1 ] ); + s2 = new Set( [ 1 ] ); + s3 = new Set( [ 3 ] ); + assert.equal( QUnit.equiv( s1, s2 ), true, "Single element sets [1] vs [1]" ); + assert.equal( QUnit.equiv( s1, s3 ), false, "Single element sets [1] vs [3]" ); + + // Tricky values + s1 = new Set( [ undefined, null, false, 0, NaN, Infinity, -Infinity ] ); + s2 = new Set( [ undefined, null, false, 0, NaN, Infinity, -Infinity ] ); + assert.equal( QUnit.equiv( s1, s2 ), true, "Multiple-element sets of tricky values" ); + + // Sets Containing objects + o1 = { foo: 0, bar: true }; + o2 = { foo: 0, bar: true }; + o3 = { foo: 1, bar: true }; + s1 = new Set( [ o1, o3 ] ); + s2 = new Set( [ o1, o3 ] ); + assert.equal( QUnit.equiv( s1, s2 ), true, "Sets containing same objects" ); + s1 = new Set( [ o1 ] ); + s2 = new Set( [ o2 ] ); + assert.equal( QUnit.equiv( s1, s2 ), true, "Sets containing deeply-equal objects" ); + s1 = new Set( [ o1 ] ); + s2 = new Set( [ o3 ] ); + assert.equal( QUnit.equiv( s1, s2 ), false, "Sets containing different objects" ); + + // Sets containing sets + s1 = new Set( [ 1, 2, 3 ] ); + s2 = new Set( [ 1, 2, 3 ] ); + s3 = new Set( [ s1 ] ); + s4 = new Set( [ s2 ] ); + assert.equal( QUnit.equiv( s3, s4 ), true, "Sets containing deeply-equal sets" ); + + // Sets containing different sets + s1 = new Set( [ 1, 2, 3 ] ); + s2 = new Set( [ 1, 2, 3, 4 ] ); + s3 = new Set( [ s1 ] ); + s4 = new Set( [ s2 ] ); + assert.equal( QUnit.equiv( s3, s4 ), false, "Sets containing different sets" ); + + // Sets containing maps + m1 = new Map( [ [ 1, 1 ] ] ); + m2 = new Map( [ [ 1, 1 ] ] ); + m3 = new Map( [ [ 1, 3 ] ] ); + s3 = new Set( [ m1 ] ); + s4 = new Set( [ m2 ] ); + assert.equal( QUnit.equiv( s3, s4 ), true, "Sets containing different but deeply-equal maps" ); + s3 = new Set( [ m1 ] ); + s4 = new Set( [ m3 ] ); + assert.equal( QUnit.equiv( s3, s4 ), false, "Sets containing different maps" ); +}); + +QUnit[ hasES6Map ? "test" : "skip" ]( "Maps", function ( assert ) { + var m1, m2, m3, m4, o1, o2, o3, s1, s2, s3; + + // Empty maps + m1 = new Map(); + m2 = new Map( [] ); + assert.equal( QUnit.equiv( m1, m2 ), true, "Empty maps" ); + + // Simple cases + m1 = new Map( [ [ 1, 1 ] ] ); + m2 = new Map( [ [ 1, 1 ] ] ); + m3 = new Map( [ [ 1, 3 ] ] ); + assert.equal( QUnit.equiv( m1, m2 ), true, "Single element maps [1,1] vs [1,1]" ); + assert.equal( QUnit.equiv( m1, m3 ), false, "Single element maps [1,1] vs [1,3]" ); + + // Tricky values + m1 = new Map( [ + [ undefined, undefined ], + [ null, null ], + [ false, false ], + [ 0, 0 ], + [ NaN, NaN ], + [ Infinity, Infinity ], + [ -Infinity, -Infinity ] + ] ); + m2 = new Map( [ + [ undefined, undefined ], + [ null, null ], + [ false, false ], + [ 0, 0 ], + [ NaN, NaN ], + [ Infinity, Infinity ], + [ -Infinity, -Infinity ] + ] ); + assert.equal( QUnit.equiv( m1, m2 ), true, "Multiple-element maps of tricky values" ); + + // Same keys, different values + m1 = new Map( [ + [ 1, "one" ], + [ 2, "two" ] + ] ); + m2 = new Map( [ + [ 1, 1 ], + [ 2, 2 ] + ] ); + assert.equal( QUnit.equiv( m1, m2 ), false, "Maps with same keys, different values" ); + + // Maps Containing objects + o1 = { foo: 0, bar: true }; + o2 = { foo: 0, bar: true }; + o3 = { foo: 1, bar: true }; + m1 = new Map( [ + [ 1, o1 ], + [ 2, o3 ] + ] ); + m2 = new Map( [ + [ 1, o1 ], + [ 2, o3 ] + ] ); + assert.equal( QUnit.equiv( m1, m2 ), true, "Maps containing same objects" ); + m1 = new Map( [ [ 1, o1 ] ] ); + m2 = new Map( [ [ 1, o2 ] ] ); + assert.equal( QUnit.equiv( m1, m2 ), true, "Maps containing different but deeply-equal objects" ); + + // Maps containing different objects + m1 = new Map( [ [ 1, o1 ] ] ); + m2 = new Map( [ [ 1, o3 ] ] ); + assert.equal( QUnit.equiv( m1, m2 ), false, "Maps containing different objects" ); + + // Maps containing maps + m1 = new Map( [ [ 1, 1 ] ] ); + m2 = new Map( [ [ 1, 1 ] ] ); + m3 = new Map( [ [ "myMap", m1 ] ] ); + m4 = new Map( [ [ "myMap", m2 ] ] ); + assert.equal( QUnit.equiv( m3, m4 ), true, "Maps containing deeply-equal maps" ); + + // Maps containing different maps + m1 = new Map( [ [ 1, 1 ] ] ); + m2 = new Map( [ [ 1, 2 ] ] ); + m3 = new Map( [ [ "myMap", m1 ] ] ); + m4 = new Map( [ [ "myMap", m2 ] ] ); + assert.equal( QUnit.equiv( m3, m4 ), false, "Maps containing different maps" ); + + // Maps containing sets + s1 = new Set( [ 1, 2, 3 ] ); + s2 = new Set( [ 1, 2, 3 ] ); + s3 = new Set( [ 1, 2, 3, 4 ] ); + m1 = new Map( [ [ 1, s1 ] ] ); + m2 = new Map( [ [ 1, s2 ] ] ); + assert.equal( QUnit.equiv( m1, m2 ), true, "Maps containing different but deeply-equal sets" ); + + // Maps containing different sets + m1 = new Map( [ [ 1, s1 ] ] ); + m2 = new Map( [ [ 1, s3 ] ] ); + assert.equal( QUnit.equiv( m1, m2 ), false, "Maps containing different sets" ); +}); + +QUnit.module( "equiv Symbols" ); + +var hasES6Symbol = ( function() { + return typeof Symbol === "function"; +} )(); + +QUnit[ hasES6Symbol ? "test" : "skip" ]( "regular checks", function ( assert ) { + var a = Symbol( 1 ); + var b = Symbol( 1 ); + + assert.equal( QUnit.equiv( a, a ), true, "Same symbol is equivalent" ); + assert.equal( + QUnit.equiv( a, b ), false, + "Not equivalent to another similar symbol built build on the same token" + ); +} ); diff -Nru libjs-qunit-1.14.0/test/main/dump.js libjs-qunit-1.22.0/test/main/dump.js --- libjs-qunit-1.14.0/test/main/dump.js 1970-01-01 00:00:00.000000000 +0000 +++ libjs-qunit-1.22.0/test/main/dump.js 2016-02-23 15:57:56.000000000 +0000 @@ -0,0 +1,226 @@ +QUnit.module( "dump", { + teardown: function() { + QUnit.dump.maxDepth = null; + } +}); + +QUnit.test( "dump output", function( assert ) { + assert.equal( QUnit.dump.parse( [ 1, 2 ] ), "[\n 1,\n 2\n]" ); + assert.equal( QUnit.dump.parse( { top: 5, left: 0 } ), "{\n \"left\": 0,\n \"top\": 5\n}" ); + if ( typeof document !== "undefined" && document.getElementById( "qunit-header" ) ) { + assert.equal( + QUnit.dump.parse( document.getElementById( "qunit-header" ) ), + "

              " + ); + assert.equal( + QUnit.dump.parse( document.getElementsByTagName( "h1" ) ), + "[\n

              \n]" + ); + } +}); + +QUnit.test( "dump output, shallow", function( assert ) { + var obj = { + top: { + middle: { + bottom: 0 + } + }, + left: 0 + }; + assert.expect( 4 ); + QUnit.dump.maxDepth = 1; + assert.equal( QUnit.dump.parse( obj ), "{\n \"left\": 0,\n \"top\": [object Object]\n}" ); + + QUnit.dump.maxDepth = 2; + assert.equal( + QUnit.dump.parse( obj ), + "{\n \"left\": 0,\n \"top\": {\n \"middle\": [object Object]\n }\n}" + ); + + QUnit.dump.maxDepth = 3; + assert.equal( + QUnit.dump.parse( obj ), + "{\n \"left\": 0,\n \"top\": {\n \"middle\": {\n \"bottom\": 0\n }\n }\n}" + ); + + QUnit.dump.maxDepth = 5; + assert.equal( + QUnit.dump.parse( obj ), + "{\n \"left\": 0,\n \"top\": {\n \"middle\": {\n \"bottom\": 0\n }\n }\n}" + ); +}); + +QUnit.test( "dump, TypeError properties", function( assert ) { + function CustomError( message ) { + this.message = message; + } + + CustomError.prototype.toString = function() { + return this.message; + }; + var customError = new CustomError( "sad puppy" ), + typeError = new TypeError( "crying kitten" ), + expectedCustomMessage = "\"message\": \"sad puppy\"", + expectedTypeMessage = "\"message\": \"crying kitten\"", + expectedTypeName = "\"name\": \"TypeError\"", + + dumpedCustomError = QUnit.dump.parse( customError ), + dumpedTypeError = QUnit.dump.parse( typeError ), + + dumpedTypeErrorWithEnumerable; + + // Test when object has some enumerable properties by adding one + typeError.hasCheeseburger = true; + + dumpedTypeErrorWithEnumerable = QUnit.dump.parse( typeError ); + + assert.pushResult( { + result: dumpedCustomError.indexOf(expectedCustomMessage) >= 0, + actual: dumpedCustomError, + expected: expectedCustomMessage, + message: "custom error contains message field" + } ); + assert.pushResult( { + result: dumpedTypeError.indexOf(expectedTypeMessage) >= 0, + actual: dumpedTypeError, + expected: expectedTypeMessage, + message: "type error contains message field" + } ); + assert.pushResult( { + result: dumpedTypeError.indexOf(expectedTypeName) >= 0, + actual: dumpedTypeError, + expected: expectedTypeName, + message: "type error contains name field" + } ); + assert.pushResult( { + result: dumpedTypeErrorWithEnumerable.indexOf(expectedTypeMessage) >= 0, + actual: dumpedTypeErrorWithEnumerable, + expected: expectedTypeMessage, + message: "type error with enumerable field contains message field" + } ); +}); + +QUnit.module( "dump, recursions", { + Wrap: function( x ) { + this.wrap = x; + if ( x === undefined ) { + this.first = true; + } + }, + chainwrap: function( depth, first, prev ) { + depth = depth || 0; + var last = prev || new this.Wrap(); + first = first || last; + + if ( depth === 1 ) { + first.wrap = last; + } + if ( depth > 1 ) { + last = this.chainwrap( depth - 1, first, new this.Wrap( last ) ); + } + + return last; + } +}); + +QUnit.test( "Check dump recursion", function( assert ) { + assert.expect( 4 ); + + var noref, nodump, selfref, selfdump, parentref, parentdump, circref, circdump; + + noref = this.chainwrap( 0 ); + nodump = QUnit.dump.parse( noref ); + assert.equal( nodump, "{\n \"first\": true,\n \"wrap\": undefined\n}" ); + + selfref = this.chainwrap( 1 ); + selfdump = QUnit.dump.parse( selfref ); + assert.equal( selfdump, "{\n \"first\": true,\n \"wrap\": recursion(-1)\n}" ); + + parentref = this.chainwrap( 2 ); + parentdump = QUnit.dump.parse( parentref ); + assert.equal( parentdump, + "{\n \"wrap\": {\n \"first\": true,\n \"wrap\": recursion(-2)\n }\n}" + ); + + circref = this.chainwrap( 10 ); + circdump = QUnit.dump.parse( circref ); + assert.ok( new RegExp( "recursion\\(-10\\)" ).test( circdump ), + "(" + circdump + ") should show -10 recursion level" + ); +}); + +QUnit.test( "Check equal/deepEqual recursion", function( assert ) { + var noRecursion, selfref, circref; + + noRecursion = this.chainwrap( 0 ); + assert.equal( noRecursion, noRecursion, "I should be equal to me." ); + assert.deepEqual( noRecursion, noRecursion, "... and so in depth." ); + + selfref = this.chainwrap( 1 ); + assert.equal( selfref, selfref, "Even so if I nest myself." ); + assert.deepEqual( selfref, selfref, "... into the depth." ); + + circref = this.chainwrap( 10 ); + assert.equal( circref, circref, "Or hide that through some levels of indirection." ); + assert.deepEqual( circref, circref, "... and checked on all levels!" ); +}); + +QUnit.test( "Circular reference with arrays", function( assert ) { + var arr, arrdump, obj, childarr, objdump, childarrdump; + + // pure array self-ref + arr = []; + arr.push( arr ); + + arrdump = QUnit.dump.parse( arr ); + + assert.equal( arrdump, "[\n recursion(-1)\n]" ); + assert.equal( arr, arr[ 0 ], "no endless stack when trying to dump arrays with circular ref" ); + + // mix obj-arr circular ref + obj = {}; + childarr = [ obj ]; + obj.childarr = childarr; + + objdump = QUnit.dump.parse( obj ); + childarrdump = QUnit.dump.parse( childarr ); + + assert.equal( objdump, "{\n \"childarr\": [\n recursion(-2)\n ]\n}" ); + assert.equal( childarrdump, "[\n {\n \"childarr\": recursion(-2)\n }\n]" ); + + assert.equal( obj.childarr, childarr, + "no endless stack when trying to dump array/object mix with circular ref" + ); + assert.equal( childarr[ 0 ], obj, + "no endless stack when trying to dump array/object mix with circular ref" + ); + +}); + +QUnit.test( "Circular reference - test reported by soniciq in #105", function( assert ) { + var a, b, barr, + MyObject = function() {}; + MyObject.prototype.parent = function( obj ) { + if ( obj === undefined ) { + return this._parent; + } + this._parent = obj; + }; + MyObject.prototype.children = function( obj ) { + if ( obj === undefined ) { + return this._children; + } + this._children = obj; + }; + + a = new MyObject(); + b = new MyObject(); + + barr = [ b ]; + a.children( barr ); + b.parent( a ); + + assert.equal( a.children(), barr ); + assert.deepEqual( a.children(), [ b ] ); +}); diff -Nru libjs-qunit-1.14.0/test/main/globals.js libjs-qunit-1.22.0/test/main/globals.js --- libjs-qunit-1.14.0/test/main/globals.js 1970-01-01 00:00:00.000000000 +0000 +++ libjs-qunit-1.22.0/test/main/globals.js 2016-02-23 15:57:56.000000000 +0000 @@ -0,0 +1,77 @@ +/*global ok: false, equal: false, throws: false, raises: false */ +(function( window ) { + +QUnit.module( "globals" ); + +function checkExported( assert, methods, isAssertion ) { + var i, l, method; + + for ( i = 0, l = methods.length; i < l; i++ ) { + method = methods[ i ]; + + assert.strictEqual( typeof( window[ method ] ), "function", "global " + method ); + + assert.strictEqual( + window[ method ], + QUnit[ method ], + "QUnit exports QUnit." + method + " to the global scope" + ); + + if ( isAssertion ) { + assert.strictEqual( + window[ method ], + assert[ method ], + "Global " + method + " is the same of assert." + method + ); + } + } +} + +QUnit.test( "QUnit exported methods", function( assert ) { + var globals = [ + "test", "asyncTest", "module", + "start", "stop" + ]; + + // 2 assertions per item on checkExported + assert.expect( globals.length * 2 ); + + checkExported( assert, globals ); +}); + +// Test deprecated exported Assert methods +QUnit.test( "Exported assertions", function() { + QUnit.expect( 12 ); + + QUnit.ok( true ); + QUnit.equal( 2, 2 ); + QUnit.throws(function() { + throw "error"; + }); + QUnit.raises(function() { + throw "error"; + }); + + ok( true ); + equal( 2, 2 ); + throws(function() { + throw "error"; + }); + raises(function() { + throw "error"; + }); + + QUnit.assert.ok( true ); + QUnit.assert.equal( 2, 2 ); + QUnit.assert.throws(function() { + throw "error"; + }); + QUnit.assert.raises(function() { + throw "error"; + }); +}); + +// Get a reference to the global object, like window in browsers +}( (function() { + return this; +}.call()) )); diff -Nru libjs-qunit-1.14.0/test/main/modules.js libjs-qunit-1.22.0/test/main/modules.js --- libjs-qunit-1.14.0/test/main/modules.js 1970-01-01 00:00:00.000000000 +0000 +++ libjs-qunit-1.22.0/test/main/modules.js 2016-02-23 15:57:56.000000000 +0000 @@ -0,0 +1,296 @@ +QUnit.module( "beforeEach/afterEach", { + beforeEach: function() { + this.lastHook = "module-beforeEach"; + }, + afterEach: function( assert ) { + if ( this.hooksTest ) { + assert.strictEqual( this.lastHook, "test-block", + "Module's afterEach runs after current test block" ); + this.lastHook = "module-afterEach"; + } + } +}); + +QUnit.test( "hooks order", function( assert ) { + assert.expect( 2 ); + + // This will trigger an assertion on the global and one on the module's afterEach + this.hooksTest = true; + + assert.strictEqual( this.lastHook, "module-beforeEach", + "Module's beforeEach runs before current test block" ); + this.lastHook = "test-block"; +}); + +QUnit.module( "Test context object", { + beforeEach: function( assert ) { + var key, + keys = []; + + for ( key in this ) { + keys.push( key ); + } + assert.deepEqual( keys, [ "helper" ] ); + }, + afterEach: function() {}, + helper: function() {} +}); + +QUnit.test( "keys", function( assert ) { + assert.expect( 1 ); + this.contextTest = true; +}); + +QUnit.module( "afterEach and QUnit.stop", { + beforeEach: function() { + this.state = false; + }, + afterEach: function( assert ) { + assert.strictEqual( this.state, true, "Test afterEach." ); + } +}); + +QUnit.test( "afterEach must be called after test ended", function( assert ) { + var testContext = this; + assert.expect( 1 ); + QUnit.stop(); + setTimeout(function() { + testContext.state = true; + QUnit.start(); + }); +}); + +QUnit.test( "parameter passed to stop increments semaphore n times", function( assert ) { + var testContext = this; + assert.expect( 1 ); + QUnit.stop( 3 ); + setTimeout(function() { + QUnit.start(); + QUnit.start(); + }); + setTimeout(function() { + testContext.state = true; + QUnit.start(); + }, 1 ); +}); + +QUnit.test( "parameter passed to start decrements semaphore n times", function( assert ) { + var testContext = this; + assert.expect( 1 ); + QUnit.stop(); + QUnit.stop(); + QUnit.stop(); + setTimeout(function() { + testContext.state = true; + QUnit.start( 3 ); + }); +}); + +QUnit.module( "async beforeEach test", { + beforeEach: function( assert ) { + QUnit.stop(); + setTimeout(function() { + assert.ok( true ); + QUnit.start(); + }); + } +}); + +QUnit.asyncTest( "module with async beforeEach", function( assert ) { + assert.expect( 2 ); + assert.ok( true ); + QUnit.start(); +}); + +QUnit.module( "async afterEach test", { + afterEach: function( assert ) { + QUnit.stop(); + setTimeout(function() { + assert.ok( true ); + QUnit.start(); + }); + } +}); + +QUnit.asyncTest( "module with async afterEach", function( assert ) { + assert.expect( 2 ); + assert.ok( true ); + QUnit.start(); +}); + +QUnit.module( "save scope", { + foo: "foo", + beforeEach: function( assert ) { + assert.deepEqual( this.foo, "foo" ); + this.foo = "bar"; + }, + afterEach: function( assert ) { + assert.deepEqual( this.foo, "foobar" ); + } +}); + +QUnit.test( "scope check", function( assert ) { + assert.expect( 3 ); + assert.deepEqual( this.foo, "bar" ); + this.foo = "foobar"; +}); + +QUnit.module( "Deprecated setup/teardown", { + setup: function() { + this.deprecatedSetup = true; + }, + teardown: function( assert ) { + assert.ok( this.deprecatedSetup ); + } +}); + +QUnit.test( "before/after order", function( assert ) { + assert.expect( 1 ); +}); + +QUnit.module( "pre-nested modules"); + +QUnit.module( "nested modules", function() { + QUnit.module( "first outer", { + afterEach: function( assert ) { + assert.ok( true, "first outer module afterEach called" ); + }, + beforeEach: function( assert ) { + assert.ok( true, "first outer beforeEach called" ); + } + }, + function() { + QUnit.module( "first inner", { + afterEach: function( assert ) { + assert.ok( true, "first inner module afterEach called" ); + }, + beforeEach: function( assert ) { + assert.ok( true, "first inner module beforeEach called" ); + } + }, + function() { + QUnit.test( "in module, before- and afterEach called in out-in-out " + + "order", + function( assert ) { + var module = assert.test.module; + assert.equal( module.name, + "nested modules > first outer > first inner" ); + assert.expect( 5 ); + }); + }); + QUnit.test( "test after nested module is processed", function( assert ) { + var module = assert.test.module; + assert.equal( module.name, "nested modules > first outer" ); + assert.expect( 3 ); + }); + QUnit.module( "second inner" ); + QUnit.test( "test after non-nesting module declared", function( assert ) { + var module = assert.test.module; + assert.equal( module.name, "nested modules > first outer > second inner" ); + assert.expect( 3 ); + }); + }); + QUnit.module( "second outer" ); + QUnit.test( "test after all nesting modules processed and new module declared", + function( assert ) { + var module = assert.test.module; + assert.equal( module.name, "nested modules > second outer" ); + }); +}); + +QUnit.test( "modules with nested functions does not spread beyond", function( assert ) { + assert.equal( assert.test.module.name, "pre-nested modules" ); +}); + +QUnit.module( "contained suite arguments", function( hooks ) { + QUnit.test( "hook functions", function( assert ) { + assert.strictEqual( typeof hooks.beforeEach, "function" ); + assert.strictEqual( typeof hooks.afterEach, "function" ); + } ); + + QUnit.module( "outer hooks", function( hooks ) { + var beforeEach = hooks.beforeEach; + var afterEach = hooks.afterEach; + + beforeEach( function( assert ) { + assert.ok( true, "beforeEach called" ); + } ); + + afterEach( function( assert ) { + assert.ok( true, "afterEach called" ); + } ); + + QUnit.test( "call hooks", function( assert ) { + assert.expect( 2 ); + } ); + + QUnit.module( "stacked inner hooks", function( hooks ) { + var beforeEach = hooks.beforeEach; + var afterEach = hooks.afterEach; + + beforeEach( function( assert ) { + assert.ok( true, "nested beforeEach called" ); + } ); + + afterEach( function( assert ) { + assert.ok( true, "nested afterEach called" ); + } ); + + QUnit.test( "call hooks", function( assert ) { + assert.expect( 4 ); + } ); + } ); + } ); +} ); + +QUnit.module( "contained suite `this`", function( hooks ) { + this.outer = 1; + + hooks.beforeEach( function() { + this.outer++; + } ); + + hooks.afterEach( function( assert ) { + assert.equal( + this.outer, 42, + "in-test environment modifications are visible by afterEach callbacks" + ); + } ); + + QUnit.test( "`this` is shared from modules to the tests", function( assert ) { + assert.equal( this.outer, 2 ); + this.outer = 42; + } ); + + QUnit.test( "sibling tests don't share environments", function( assert ) { + assert.equal( this.outer, 2 ); + this.outer = 42; + } ); + + QUnit.module( "nested suite `this`", function( hooks ) { + this.inner = true; + + hooks.beforeEach( function( assert ) { + assert.ok( this.outer ); + assert.ok( this.inner ); + } ); + + hooks.afterEach( function( assert ) { + assert.ok( this.outer ); + assert.ok( this.inner ); + + // This change affects the outermodule afterEach assertion. + this.outer = 42; + } ); + + QUnit.test( "inner modules share outer environments", function( assert ) { + assert.ok( this.outer ); + assert.ok( this.inner ); + } ); + } ); + + QUnit.test( "tests can't see environments from nested modules", function( assert ) { + assert.strictEqual( this.inner, undefined ); + this.outer = 42; + } ); +} ); diff -Nru libjs-qunit-1.14.0/test/main/promise.js libjs-qunit-1.22.0/test/main/promise.js --- libjs-qunit-1.14.0/test/main/promise.js 1970-01-01 00:00:00.000000000 +0000 +++ libjs-qunit-1.22.0/test/main/promise.js 2016-02-23 15:57:56.000000000 +0000 @@ -0,0 +1,88 @@ +// NOTE: Adds 1 assertion +function createMockPromise( assert ) { + + // Return a mock self-fulfilling Promise ("thenable") + var thenable = { + then: function( fulfilledCallback /*, rejectedCallback */ ) { + assert.strictEqual( this, thenable, "`then` was invoked with the Promise as the " + + "context" ); + setTimeout( function() { + return fulfilledCallback.call( thenable, {} ); + }, 13 ); + } + }; + return thenable; +} + +QUnit.module( "Module with Promise-aware beforeEach", { + beforeEach: function( assert ) { + assert.ok( true ); + return {}; + } +}); + +QUnit.test( "non-Promise", function( assert ) { + assert.expect( 1 ); +}); + +QUnit.module( "Module with Promise-aware beforeEach", { + beforeEach: function( assert ) { + + // Adds 1 assertion + return createMockPromise( assert ); + } +}); + +QUnit.test( "fulfilled Promise", function( assert ) { + assert.expect( 1 ); +}); + +QUnit.module( "Module with Promise-aware afterEach", { + afterEach: function( assert ) { + assert.ok( true ); + return {}; + } +}); + +QUnit.test( "non-Promise", function( assert ) { + assert.expect( 1 ); +}); + +QUnit.module( "Module with Promise-aware afterEach", { + afterEach: function( assert ) { + + // Adds 1 assertion + return createMockPromise( assert ); + } +}); + +QUnit.test( "fulfilled Promise", function( assert ) { + assert.expect( 1 ); +}); + +QUnit.module( "Promise-aware return values without beforeEach/afterEach" ); + +QUnit.test( "non-Promise", function( assert ) { + assert.expect( 0 ); + return {}; +}); + +QUnit.test( "fulfilled Promise", function( assert ) { + assert.expect( 1 ); + + // Adds 1 assertion + return createMockPromise( assert ); +}); + +QUnit.test( "fulfilled Promise with non-Promise async assertion", function( assert ) { + assert.expect( 2 ); + + var done = assert.async(); + setTimeout( function() { + assert.ok( true ); + done(); + }, 100 ); + + // Adds 1 assertion + return createMockPromise( assert ); +}); diff -Nru libjs-qunit-1.14.0/test/main/stack.js libjs-qunit-1.22.0/test/main/stack.js --- libjs-qunit-1.14.0/test/main/stack.js 1970-01-01 00:00:00.000000000 +0000 +++ libjs-qunit-1.22.0/test/main/stack.js 2016-02-23 15:57:56.000000000 +0000 @@ -0,0 +1,14 @@ +( function() { + var stack = QUnit.stack(); + + QUnit.module( "QUnit.stack" ); + + // Flag this test as skipped on browsers that doesn't support stack trace + QUnit[ stack ? "test" : "skip" ]( "returns the proper stack line", function( assert ) { + assert.ok( /\/test\/main\/stack\.js/.test( stack ) ); + + stack = QUnit.stack( 2 ); + assert.ok( stack, "can use offset argument to return a different stacktrace line" ); + assert.notOk( /\/test\/main\/stack\.js/.test( stack ), "stack with offset argument" ); + } ); +} )(); diff -Nru libjs-qunit-1.14.0/test/main/test.js libjs-qunit-1.22.0/test/main/test.js --- libjs-qunit-1.14.0/test/main/test.js 1970-01-01 00:00:00.000000000 +0000 +++ libjs-qunit-1.22.0/test/main/test.js 2016-02-23 15:57:56.000000000 +0000 @@ -0,0 +1,87 @@ +QUnit.test( "expect query and multiple issue", function( assert ) { + assert.expect( 2 ); + assert.ok( true ); + var expected = assert.expect(); + assert.equal( expected, 2 ); + assert.expect( expected + 1 ); + assert.ok( true ); +}); + +if ( typeof document !== "undefined" ) { + +QUnit.module( "fixture" ); + +QUnit.test( "setup", function( assert ) { + assert.expect( 0 ); + document.getElementById( "qunit-fixture" ).innerHTML = "foobar"; +}); + +QUnit.test( "basics", function( assert ) { + assert.equal( + document.getElementById( "qunit-fixture" ).innerHTML, + "test markup", + "automatically reset" + ); +}); + +} + +QUnit.module( "custom assertions" ); + +QUnit.assert.mod2 = function( value, expected, message ) { + var actual = value % 2; + this.pushResult( { + result: actual === expected, + actual: actual, + expected: expected, + message: message + } ); +}; + +QUnit.assert.testForPush = function( value, expected, message ) { + this.push( true, value, expected, message, false ); +}; + +QUnit.test( "mod2", function( assert ) { + assert.expect( 2 ); + + assert.mod2( 2, 0, "2 % 2 == 0" ); + assert.mod2( 3, 1, "3 % 2 == 1" ); +}); + +QUnit.test( "testForPush", function( assert ) { + assert.expect( 6 ); + + QUnit.log( function( detail ) { + if ( detail.message === "should be call pushResult" ) { + assert.equal( detail.result, true ); + assert.equal( detail.actual, 1 ); + assert.equal( detail.expected, 1 ); + assert.equal( detail.message, "should be call pushResult" ); + assert.equal( detail.negative, false ); + } + } ); + assert.testForPush( 1, 1, "should be call pushResult" ); +}); + +QUnit.module( "QUnit.skip", { + beforeEach: function( assert ) { + + // skip test hooks for skipped tests + assert.ok( false, "skipped function" ); + throw "Error"; + }, + afterEach: function( assert ) { + assert.ok( false, "skipped function" ); + throw "Error"; + } +}); + +QUnit.skip( "test blocks are skipped", function( assert ) { + + // this test callback won't run, even with broken code + assert.expect( 1000 ); + throw "Error"; +}); + +QUnit.skip( "no function" ); diff -Nru libjs-qunit-1.14.0/test/narwhal-test.js libjs-qunit-1.22.0/test/narwhal-test.js --- libjs-qunit-1.14.0/test/narwhal-test.js 2014-01-31 16:41:24.000000000 +0000 +++ libjs-qunit-1.22.0/test/narwhal-test.js 1970-01-01 00:00:00.000000000 +0000 @@ -1,27 +0,0 @@ -/*jshint node:true, undef:false */ -/*globals QUnit:true */ -// Run with: $ narwhal test/narwhal-test.js -var QUnit = require("../dist/qunit"); - -QUnit.log(function(details) { - if (!details.result) { - var output = "FAILED: " + (details.message ? details.message + ", " : ""); - if (details.actual) { - output += "expected: " + details.expected + ", actual: " + details.actual; - } - if (details.source) { - output += ", " + details.source; - } - print(output); - } -}); - -QUnit.test("fail twice with stacktrace", function(assert) { - /*jshint expr:true */ - assert.equal(true, false); - assert.equal(true, false, "gotta fail"); - // Throws ReferenceError - x.y.z; -}); - -QUnit.load(); diff -Nru libjs-qunit-1.14.0/test/node-test.js libjs-qunit-1.22.0/test/node-test.js --- libjs-qunit-1.14.0/test/node-test.js 2014-01-31 16:41:24.000000000 +0000 +++ libjs-qunit-1.22.0/test/node-test.js 1970-01-01 00:00:00.000000000 +0000 @@ -1,27 +0,0 @@ -/*jshint node:true, undef:false */ -/*globals QUnit:true */ -// Run with: $ node test/node-test.js -var QUnit = require("../dist/qunit"); - -QUnit.log(function(details) { - if (!details.result) { - var output = "FAILED: " + (details.message ? details.message + ", " : ""); - if (details.actual) { - output += "expected: " + details.expected + ", actual: " + details.actual; - } - if (details.source) { - output += ", " + details.source; - } - console.log(output); - } -}); - -QUnit.test("fail twice with stacktrace", function(assert) { - /*jshint expr:true */ - assert.equal(true, false); - assert.equal(true, false, "gotta fail"); - // Throws ReferenceError - x.y.z; -}); - -QUnit.load(); diff -Nru libjs-qunit-1.14.0/test/only.html libjs-qunit-1.22.0/test/only.html --- libjs-qunit-1.14.0/test/only.html 1970-01-01 00:00:00.000000000 +0000 +++ libjs-qunit-1.22.0/test/only.html 2016-02-23 15:57:56.000000000 +0000 @@ -0,0 +1,13 @@ + + + + + QUnit Only Test Suite + + + + + +
              + + diff -Nru libjs-qunit-1.14.0/test/only.js libjs-qunit-1.22.0/test/only.js --- libjs-qunit-1.14.0/test/only.js 1970-01-01 00:00:00.000000000 +0000 +++ libjs-qunit-1.22.0/test/only.js 2016-02-23 15:57:56.000000000 +0000 @@ -0,0 +1,17 @@ +QUnit.module( "QUnit.only" ); + +QUnit.test( "implicitly skipped test", function( assert ) { + assert.ok( false, "test should be skipped" ); +}); + +QUnit.only( "only run this test", function( assert ) { + assert.ok( true, "only this test should run" ); +}); + +QUnit.test( "another implicitly skipped test", function( assert ) { + assert.ok( false, "test should be skipped" ); +}); + +QUnit.only( "ignore the subsequent calls to only", function( assert ) { + assert.ok( false, "this test should be skipped" ); +}); diff -Nru libjs-qunit-1.14.0/test/regex-exclude-filter.html libjs-qunit-1.22.0/test/regex-exclude-filter.html --- libjs-qunit-1.14.0/test/regex-exclude-filter.html 1970-01-01 00:00:00.000000000 +0000 +++ libjs-qunit-1.22.0/test/regex-exclude-filter.html 2016-02-23 15:57:56.000000000 +0000 @@ -0,0 +1,16 @@ + + + + + QUnit Regex Exclude Filtering Test Suite + + + + + + +
              + + diff -Nru libjs-qunit-1.14.0/test/regex-exclude-filter.js libjs-qunit-1.22.0/test/regex-exclude-filter.js --- libjs-qunit-1.14.0/test/regex-exclude-filter.js 1970-01-01 00:00:00.000000000 +0000 +++ libjs-qunit-1.22.0/test/regex-exclude-filter.js 2016-02-23 15:57:56.000000000 +0000 @@ -0,0 +1,17 @@ +QUnit.module( "QUnit.config.filter with excluding, case-sensitive regular expression" ); + +QUnit.test( "foo test should be run", function( assert ) { + assert.ok( true, "foo test should be run" ); +}); + +QUnit.test( "Foo test should not be run", function( assert ) { + assert.ok( false, "Foo test should not be run" ); +}); + +QUnit.test( "Bar test should be run", function( assert ) { + assert.ok( true, "Bar test should be run" ); +}); + +QUnit.test( "bar test should not be run", function( assert ) { + assert.ok( false, "bar test should not be run" ); +}); diff -Nru libjs-qunit-1.14.0/test/regex-filter.html libjs-qunit-1.22.0/test/regex-filter.html --- libjs-qunit-1.14.0/test/regex-filter.html 1970-01-01 00:00:00.000000000 +0000 +++ libjs-qunit-1.22.0/test/regex-filter.html 2016-02-23 15:57:56.000000000 +0000 @@ -0,0 +1,16 @@ + + + + + QUnit Regex Filtering Test Suite + + + + + + +
              + + diff -Nru libjs-qunit-1.14.0/test/regex-filter.js libjs-qunit-1.22.0/test/regex-filter.js --- libjs-qunit-1.14.0/test/regex-filter.js 1970-01-01 00:00:00.000000000 +0000 +++ libjs-qunit-1.22.0/test/regex-filter.js 2016-02-23 15:57:56.000000000 +0000 @@ -0,0 +1,17 @@ +QUnit.module( "QUnit.config.filter with case-insensitive regular expression" ); + +QUnit.test( "foo test should be run", function( assert ) { + assert.ok( true, "foo test should be run" ); +}); + +QUnit.test( "boo test should not be run", function( assert ) { + assert.ok( false, "boo test should not be run" ); +}); + +QUnit.test( "bar test should be run", function( assert ) { + assert.ok( true, "bar test should be run" ); +}); + +QUnit.test( "baz test should not be run", function( assert ) { + assert.ok( false, "baz test should not be run" ); +}); diff -Nru libjs-qunit-1.14.0/test/reorderError1.html libjs-qunit-1.22.0/test/reorderError1.html --- libjs-qunit-1.14.0/test/reorderError1.html 1970-01-01 00:00:00.000000000 +0000 +++ libjs-qunit-1.22.0/test/reorderError1.html 2016-02-23 15:57:56.000000000 +0000 @@ -0,0 +1,52 @@ + + + + + QUnit - Asserts it does not skip tests after reordering + + + + + + + + + +
              + + diff -Nru libjs-qunit-1.14.0/test/reorderError1.js libjs-qunit-1.22.0/test/reorderError1.js --- libjs-qunit-1.14.0/test/reorderError1.js 1970-01-01 00:00:00.000000000 +0000 +++ libjs-qunit-1.22.0/test/reorderError1.js 2016-02-23 15:57:56.000000000 +0000 @@ -0,0 +1,7 @@ +QUnit.module( "Test call count - first case" ); +QUnit[ window.sessionStorage ? "test" : "skip" ]( + "does not skip tests after reordering", + function( assert ) { + assert.equal( window.totalCount, 3 ); + } +); diff -Nru libjs-qunit-1.14.0/test/reorderError2.html libjs-qunit-1.22.0/test/reorderError2.html --- libjs-qunit-1.14.0/test/reorderError2.html 1970-01-01 00:00:00.000000000 +0000 +++ libjs-qunit-1.22.0/test/reorderError2.html 2016-02-23 15:57:56.000000000 +0000 @@ -0,0 +1,49 @@ + + + + + QUnit - Asserts it does not skip tests after reordering + + + + + + + + + +
              + + diff -Nru libjs-qunit-1.14.0/test/reorderError2.js libjs-qunit-1.22.0/test/reorderError2.js --- libjs-qunit-1.14.0/test/reorderError2.js 1970-01-01 00:00:00.000000000 +0000 +++ libjs-qunit-1.22.0/test/reorderError2.js 2016-02-23 15:57:56.000000000 +0000 @@ -0,0 +1,7 @@ +QUnit.module( "Test call count - second case" ); +QUnit[ window.sessionStorage ? "test" : "skip" ]( + "does not skip tests after reordering", + function( assert ) { + assert.equal( window.totalCount, 2 ); + } +); diff -Nru libjs-qunit-1.14.0/test/reporter-html/diff.js libjs-qunit-1.22.0/test/reporter-html/diff.js --- libjs-qunit-1.14.0/test/reporter-html/diff.js 1970-01-01 00:00:00.000000000 +0000 +++ libjs-qunit-1.22.0/test/reporter-html/diff.js 2016-02-23 15:57:56.000000000 +0000 @@ -0,0 +1,148 @@ +QUnit.module( "diff" ); + +QUnit.test( "throws if arguments are not strings", function( assert ) { + assert.throws(function() { QUnit.diff( {}, "" ); }); + assert.throws(function() { QUnit.diff( "", {} ); }); +}); + +QUnit.test( "different strings", function( assert ) { + var a = "abcd"; + var b = "xkcd"; + + assert.equal( + QUnit.diff( a, b ), + "abxkcd", + "QUnit.diff( 'abcd', 'xkcd' )" + ); + assert.equal( + QUnit.diff( b, a ), + "xkabcd", + "QUnit.diff( 'xkcd', 'abcd' )" + ); + + assert.equal( + QUnit.diff( a, "" ), + "abcd", + "QUnit.diff( 'abcd', '' )" + ); + assert.equal( + QUnit.diff( "", a ), + "abcd", + "QUnit.diff( '', 'abcd' )" + ); + + assert.equal( + QUnit.diff( "false", "true" ), + "falstrue", + "QUnit.diff( 'false', 'true' )" + ); + + assert.equal( + QUnit.diff( "true", "false" ), + "trufalse", + "QUnit.diff( 'true', 'false' )" + ); +}); + +QUnit.test( "additions", function( assert ) { + var a = "do less!"; + var b = "do less, write more!"; + + assert.equal( + QUnit.diff( a, b ), + "do less, write more!", + "QUnit.diff( 'do less!', 'do less, write more!' )" + ); +}); + +QUnit.test( "removals", function( assert ) { + var a = "do less, write more!"; + var b = "do less!"; + + assert.equal( + QUnit.diff( a, b ), + "do less, write more!", + "QUnit.diff( 'do less, write more!', 'do less!' )" + ); +}); + +QUnit.test( "equality shifts", function( assert ) { + + // ABAC -> ABAC + var a = "AC"; + var b = "ABAC"; + + assert.equal( + QUnit.diff( a, b ), "ABAC" + ); +}); + +QUnit.test( "test with line mode on long strings", function( assert ) { + var a = "QUnit is a powerful, easy-to-use JavaScript unit testing framework. " + + "It's used by the jQuery, jQuery UI and jQuery Mobile projects and is " + + "capable of testing any generic JavaScript code, including itself!"; + + var b = "QUnit is a very powerful, easy-to-use JavaScript unit testing framework. " + + "It's used by the jQuery Core, jQuery UI and jQuery Mobile projects and is " + + "capable of testing any JavaScript code, including itself!" + + "QUnit was originally developed by John Resig as part of jQuery. In 2008 " + + "it got its own home, name and API documentation, allowing others to use it " + + "for their unit testing as well. At the time it still depended on jQuery. " + + "A rewrite in 2009 fixed that, and now QUnit runs completely standalone. "; + + assert.equal( + QUnit.diff( a, b ), + "QUnit is a very powerful, easy-to-use " + + "JavaScript unit testing framework. It's used by the jQuery " + + "Core, jQuery UI and jQuery Mobile projects and is capable of" + + " testing any generic JavaScript code, including " + + "itself!" + + "QUnit was originally developed by John Resig as part of jQuery. In " + + "2008 it got its own home, name and API documentation, allowing others to" + + " use it for their unit testing as well. At the time it still depended on" + + " jQuery. A rewrite in 2009 fixed that, and now QUnit runs completely " + + "standalone. " + ); +}); + +QUnit.test( "simplified diffs", function( assert ) { + assert.equal( + QUnit.diff( "BXYD", "AXYC" ), + "BXYDAXYC", + "return is not BAXYDC" + ); + + assert.equal( + QUnit.diff( "XD", "AXC" ), + "XDAXC", + "return is not AXDC" + ); + + assert.equal( + QUnit.diff( "A BC ", " B" ), + "A BC ", + "Swap insertions for deletions if diff is reversed" + ); + + assert.equal( + QUnit.diff( "abcxxx", "xxxdef" ), + "abcxxxdef" + ); + + assert.equal( + QUnit.diff( "xxxabc", "defxxx" ), + "defxxxabc" + ); +}); + +QUnit.test( "equal values", function( assert ) { + assert.equal( + QUnit.diff( "abc", "abc" ), + "abc" + ); + + assert.equal( + QUnit.diff( "", "" ), + "" + ); +}); diff -Nru libjs-qunit-1.14.0/test/reporter-html/legacy-markup.html libjs-qunit-1.22.0/test/reporter-html/legacy-markup.html --- libjs-qunit-1.14.0/test/reporter-html/legacy-markup.html 1970-01-01 00:00:00.000000000 +0000 +++ libjs-qunit-1.22.0/test/reporter-html/legacy-markup.html 2016-02-23 15:57:56.000000000 +0000 @@ -0,0 +1,17 @@ + + + + + QUnit HTML Reporter - Legacy Markup + + + + + +

              Tests

              +

              +
              +

              +
                + + diff -Nru libjs-qunit-1.14.0/test/reporter-html/no-qunit-element.html libjs-qunit-1.22.0/test/reporter-html/no-qunit-element.html --- libjs-qunit-1.14.0/test/reporter-html/no-qunit-element.html 1970-01-01 00:00:00.000000000 +0000 +++ libjs-qunit-1.22.0/test/reporter-html/no-qunit-element.html 2016-02-23 15:57:56.000000000 +0000 @@ -0,0 +1,26 @@ + + + + + QUnit HTML Reporter - No Markup + + + + + + diff -Nru libjs-qunit-1.14.0/test/reporter-html/reporter-html.js libjs-qunit-1.22.0/test/reporter-html/reporter-html.js --- libjs-qunit-1.14.0/test/reporter-html/reporter-html.js 1970-01-01 00:00:00.000000000 +0000 +++ libjs-qunit-1.22.0/test/reporter-html/reporter-html.js 2016-02-23 15:57:56.000000000 +0000 @@ -0,0 +1,131 @@ +// The following tests need to run on their respective order +QUnit.config.reorder = false; + +QUnit.module( "", { + beforeEach: function() { + }, + afterEach: function( assert ) { + + // We can't use ok(false) inside script tags since some browsers + // don't evaluate script tags inserted through innerHTML after domready. + // Counting them before/after doesn't cover everything either as qunit-modulefilter + // is created before any test is ran. So use ids instead. + if ( document.getElementById( "qunit-unescaped-module" ) ) { + + // This can either be from in #qunit-modulefilter or #qunit-testresult + assert.ok( false, "Unescaped module name" ); + } + if ( document.getElementById( "qunit-unescaped-test" ) ) { + assert.ok( false, "Unescaped test name" ); + } + if ( document.getElementById( "qunit-unescaped-assertion" ) ) { + assert.ok( false, "Unescaped test name" ); + } + } +}); + +QUnit.test( "", function( assert ) { + assert.expect( 1 ); + assert.ok( true, "" ); +}); + +QUnit.module( "display test info" ); + +QUnit.test( "running test name displayed", function( assert ) { + assert.expect( 2 ); + + var displaying = document.getElementById( "qunit-testresult" ); + + assert.ok( /running test name displayed/.test( displaying.innerHTML ), + "Expect test name to be found in displayed text" + ); + assert.ok( /display test info/.test( displaying.innerHTML ), + "Expect module name to be found in displayed text" + ); +}); + +QUnit.module( "timing", { + getPreviousTest: function( assert ) { + return document.getElementById( "qunit-test-output-" + assert.test.testId ) + .previousSibling; + }, + filterClass: function( elements ) { + var i; + for ( i = 0; i < elements.length; i++ ) { + if ( /(^| )runtime( |$)/.test( elements[ i ].className ) ) { + return elements[ i ]; + } + } + }, + afterEach: function( assert ) { + var done; + if ( this.delayNextSetup ) { + this.delayNextSetup = false; + done = assert.async(); + setTimeout(function() { + done(); + }, 101 ); + } + } +}); + +QUnit.test( "setup", function( assert ) { + assert.expect( 0 ); + this.delayNextSetup = true; +}); + +QUnit.test( "basics", function( assert ) { + assert.expect( 1 ); + var previous = this.getPreviousTest( assert ), + runtime = this.filterClass( previous.getElementsByTagName( "span" ) ); + + assert.ok( /^\d+ ms$/.test( runtime.innerHTML ), "Runtime reported in ms" ); +}); + +QUnit.test( "values", function( assert ) { + assert.expect( 2 ); + + var basics = this.getPreviousTest( assert ), + setup = basics.previousSibling; + + basics = this.filterClass( basics.getElementsByTagName( "span" ) ); + setup = this.filterClass( setup.getElementsByTagName( "span" ) ); + + assert.ok( parseInt( basics.innerHTML, 10 ) < 100, + "Fast runtime for trivial test" + ); + assert.ok( parseInt( setup.innerHTML, 10 ) > 100, + "Runtime includes beforeEach" + ); +}); + +QUnit.module( "source" ); + +QUnit.test( "setup", function( assert ) { + assert.expect( 0 ); +}); + +QUnit.test( "logs location", function( assert ) { + var previous = document.getElementById( "qunit-test-output-" + assert.test.testId ) + .previousSibling; + var source = previous.lastChild; + var stack = QUnit.stack(); + + // Verify QUnit supported stack trace + if ( !stack ) { + assert.equal( + /(^| )qunit-source( |$)/.test( source.className ), + false, + "Don't add source information on unsupported environments" + ); + return; + } + + assert.ok( /(^| )qunit-source( |$)/.test( source.className ), "Source element exists" ); + assert.equal( source.firstChild.innerHTML, "Source: " ); + + // test/reporter-html/reporter-html.js is a direct reference to this test file + assert.ok( /\/test\/reporter-html\/reporter-html\.js\:\d+/.test( source.innerHTML ), + "Source references to the current file and line number" + ); +}); diff -Nru libjs-qunit-1.14.0/test/reporter-html/single-testid.html libjs-qunit-1.22.0/test/reporter-html/single-testid.html --- libjs-qunit-1.14.0/test/reporter-html/single-testid.html 1970-01-01 00:00:00.000000000 +0000 +++ libjs-qunit-1.22.0/test/reporter-html/single-testid.html 2016-02-23 15:57:56.000000000 +0000 @@ -0,0 +1,13 @@ + + + + + QUnit Main Test Suite + + + + + +
                + + diff -Nru libjs-qunit-1.14.0/test/reporter-html/single-testid.js libjs-qunit-1.22.0/test/reporter-html/single-testid.js --- libjs-qunit-1.14.0/test/reporter-html/single-testid.js 1970-01-01 00:00:00.000000000 +0000 +++ libjs-qunit-1.22.0/test/reporter-html/single-testid.js 2016-02-23 15:57:56.000000000 +0000 @@ -0,0 +1,12 @@ +QUnit.config.testId = [ "2e48c6fa", "9ccf6855" ]; + +QUnit.test( "Check for changed header after running filtered test", function( assert ) { + var html = document.getElementById( "qunit-filteredTest" ).innerHTML; + var result = html.match( /Rerunning selected tests\: 2e48c6fa\, 9ccf6855/ ); + assert.ok( result ); +}); + +QUnit.test( "Check for link to clear filter", function( assert ) { + var html = document.getElementById( "qunit-clearFilter" ).innerHTML; + assert.equal( html, "Run all tests" ); +}); diff -Nru libjs-qunit-1.14.0/test/setTimeout.html libjs-qunit-1.22.0/test/setTimeout.html --- libjs-qunit-1.14.0/test/setTimeout.html 2014-01-31 16:41:24.000000000 +0000 +++ libjs-qunit-1.22.0/test/setTimeout.html 2016-02-23 15:57:56.000000000 +0000 @@ -9,6 +9,5 @@
                - diff -Nru libjs-qunit-1.14.0/test/setTimeout.js libjs-qunit-1.22.0/test/setTimeout.js --- libjs-qunit-1.14.0/test/setTimeout.js 2014-01-31 16:41:24.000000000 +0000 +++ libjs-qunit-1.22.0/test/setTimeout.js 2016-02-23 15:57:56.000000000 +0000 @@ -1,19 +1,24 @@ -QUnit.config.updateRate = 1; +(function( window ) { -module( "Module that mucks with time", { - setup: function() { +QUnit.module( "Module that mucks with time", { + beforeEach: function() { this.setTimeout = window.setTimeout; window.setTimeout = function() {}; }, - teardown: function() { + afterEach: function() { window.setTimeout = this.setTimeout; } }); -test( "just a test", function( assert ) { - assert.ok(true); +QUnit.test( "just a test", function( assert ) { + assert.ok( true ); }); -test( "just a test", function( assert ) { - assert.ok(true); + +QUnit.test( "just a test", function( assert ) { + assert.ok( true ); }); + +}( (function() { + return this; +})() )); diff -Nru libjs-qunit-1.14.0/test/stack-errors.html libjs-qunit-1.22.0/test/stack-errors.html --- libjs-qunit-1.14.0/test/stack-errors.html 1970-01-01 00:00:00.000000000 +0000 +++ libjs-qunit-1.22.0/test/stack-errors.html 2016-02-23 15:57:56.000000000 +0000 @@ -0,0 +1,13 @@ + + + + + QUnit Main Test Suite + + + + + +
                + + diff -Nru libjs-qunit-1.14.0/test/stack-errors.js libjs-qunit-1.22.0/test/stack-errors.js --- libjs-qunit-1.14.0/test/stack-errors.js 1970-01-01 00:00:00.000000000 +0000 +++ libjs-qunit-1.22.0/test/stack-errors.js 2016-02-23 15:57:56.000000000 +0000 @@ -0,0 +1,100 @@ +/* globals polluteGlobal: true */ + +// No pollution +QUnit.test( "globals", function( assert ) { + QUnit.config.noglobals = true; + polluteGlobal = 1; + assert.expect( 0 ); +}); + +// Failing test +QUnit.test( "failing", function( assert ) { + assert.equal( "foo", "bar" ); +}); + +// No assertions fail +QUnit.test( "no assertions", function() { + // nothing +}); + +// start error inside of a test context +QUnit.test( "QUnit.start()", function() { + QUnit.start(); +}); + +// Died on test +QUnit.test( "dies on test", function() { + throw new Error( "foo" ); +}); + +// beforeEach die +QUnit.module( "beforeEach fail", { + beforeEach: function() { + throw new Error( "foo" ); + } +}); +QUnit.test( "module fails", function() { + // ... +}); + +// afterEach die +QUnit.module( "afterEach fail", { + afterEach: function() { + throw new Error( "bar" ); + } +}); +QUnit.test( "module fails", function() { + // ... +}); + +// assert.async post-resolution assertions fail +QUnit.module( "assertions fail after assert.async flows are resolved" ); + +QUnit.test( "assert.ok", function( assert ) { + assert.async()(); + assert.ok( true, "This assertion should pass but have a failure logged before it" ); +}); + +QUnit.test( "assert.equal", function( assert ) { + assert.async()(); + assert.equal( 1, 1, "This assertion should pass but have a failure logged before it" ); +}); + +QUnit.test( "assert.throws", function( assert ) { + assert.async()(); + assert.throws(function() { + throw new Error( "foo" ); + }, "This assertion should pass but have a failure logged before it" ); +}); + +QUnit.module( "globals" ); + +// start error outside of a test context +setTimeout(function() { + QUnit.start(); +}, 0 ); + +// pushFailure outside of a test context +setTimeout(function() { + QUnit.pushFailure( true ); +}, 0 ); + +// Assertion outside of a test context +setTimeout(function() { + QUnit.ok( true ); +}, 0 ); + +// Trigger window.onerror +setTimeout(function() { + throw new Error( "foo" ); +}, 0 ); + +// DEPRECATED: To be removed in QUnit 2.0.0 +// Trigger warnings by replacing the logging callbacks +QUnit.begin = function() {}; +QUnit.done = function() {}; +QUnit.log = function() {}; +QUnit.testStart = function() {}; +QUnit.testDone = function() {}; +QUnit.moduleStart = function() {}; +QUnit.moduleDone = function() {}; diff -Nru libjs-qunit-1.14.0/test/startError.html libjs-qunit-1.22.0/test/startError.html --- libjs-qunit-1.14.0/test/startError.html 1970-01-01 00:00:00.000000000 +0000 +++ libjs-qunit-1.22.0/test/startError.html 2016-02-23 15:57:56.000000000 +0000 @@ -0,0 +1,35 @@ + + + + + QUnit Start Error Test Suite + + + +
                + + + + + + + + + diff -Nru libjs-qunit-1.14.0/test/startError.js libjs-qunit-1.22.0/test/startError.js --- libjs-qunit-1.14.0/test/startError.js 1970-01-01 00:00:00.000000000 +0000 +++ libjs-qunit-1.22.0/test/startError.js 2016-02-23 15:57:56.000000000 +0000 @@ -0,0 +1,16 @@ +/*global autostartStartError, tooManyStartsError */ + +QUnit.module( "global start unrecoverable errors" ); + +QUnit.test( "start() throws when QUnit.config.autostart === true", function( assert ) { + assert.expect( 1 ); + assert.equal( autostartStartError.message, + "Called start() outside of a test context when QUnit.config.autostart was true" ); +}); + +QUnit.test( "Throws after calling start() too many times outside of a test context", + function( assert ) { + assert.expect( 1 ); + assert.equal( tooManyStartsError.message, + "Called start() outside of a test context too many times" ); +}); diff -Nru libjs-qunit-1.14.0/test/string-filter.html libjs-qunit-1.22.0/test/string-filter.html --- libjs-qunit-1.14.0/test/string-filter.html 1970-01-01 00:00:00.000000000 +0000 +++ libjs-qunit-1.22.0/test/string-filter.html 2016-02-23 15:57:56.000000000 +0000 @@ -0,0 +1,16 @@ + + + + + QUnit String Filtering Test Suite + + + + + + +
                + + diff -Nru libjs-qunit-1.14.0/test/string-filter.js libjs-qunit-1.22.0/test/string-filter.js --- libjs-qunit-1.14.0/test/string-filter.js 1970-01-01 00:00:00.000000000 +0000 +++ libjs-qunit-1.22.0/test/string-filter.js 2016-02-23 15:57:56.000000000 +0000 @@ -0,0 +1,13 @@ +QUnit.module( "QUnit.config.filter" ); + +QUnit.test( "foo test should be run", function( assert ) { + assert.ok( true, "foo test should be run" ); +}); + +QUnit.test( "bar test should be run", function( assert ) { + assert.ok( true, "bar test should be run" ); +}); + +QUnit.test( "foo|bar test should not be run", function( assert ) { + assert.ok( false, "baz test should not be run" ); +}); diff -Nru libjs-qunit-1.14.0/test/swarminject.js libjs-qunit-1.22.0/test/swarminject.js --- libjs-qunit-1.14.0/test/swarminject.js 2014-01-31 16:41:24.000000000 +0000 +++ libjs-qunit-1.22.0/test/swarminject.js 1970-01-01 00:00:00.000000000 +0000 @@ -1,10 +0,0 @@ -// load testswarm agent -(function() { - var url = window.location.search; - url = decodeURIComponent( url.slice( url.indexOf("swarmURL=") + 9 ) ); - if ( !url || url.indexOf("http") !== 0 ) { - return; - } - /*jshint evil:true */ - document.write(""); -})(); diff -Nru libjs-qunit-1.14.0/test/test.js libjs-qunit-1.22.0/test/test.js --- libjs-qunit-1.14.0/test/test.js 2014-01-31 16:41:24.000000000 +0000 +++ libjs-qunit-1.22.0/test/test.js 1970-01-01 00:00:00.000000000 +0000 @@ -1,851 +0,0 @@ -(function( window ) { - -function getPreviousTests( rTestName, rModuleName ) { - var testSpan, moduleSpan, - matches = [], - i = 0, - rModule = /(^| )module-name( |$)/, - testNames = typeof document.getElementsByClassName !== "undefined" ? - document.getElementsByClassName("test-name") : - (function( spans ) { - var span, - tests = [], - i = 0, - rTest = /(^| )test-name( |$)/; - for ( ; (span = spans[i]); i++ ) { - if ( rTest.test( span.className ) ) { - tests.push( span ); - } - } - return tests; - })( document.getElementsByTagName("span") ); - - for ( ; (testSpan = testNames[i]); i++ ) { - moduleSpan = testSpan; - while ( (moduleSpan = moduleSpan.previousSibling) ) { - if ( rModule.test( moduleSpan.className ) ) { - break; - } - } - if ( (!rTestName || rTestName.test( testSpan.innerHTML )) && - (!rModuleName || moduleSpan && rModuleName.test( moduleSpan.innerHTML )) ) { - - while ( (testSpan = testSpan.parentNode) ) { - if ( testSpan.nodeName.toLowerCase() === "li" ) { - matches.push( testSpan ); - } - } - } - } - return matches; -} - -test("module without setup/teardown (default)", function( assert ) { - expect(1); - assert.ok(true); -}); - -test("expect in test", function( assert ) { - expect( 3 ); - assert.ok(true); - assert.ok(true); - assert.ok(true); -}); - -test("expect in test", function( assert ) { - expect( 1 ); - assert.ok(true); -}); - -test("expect query and multiple issue", function( assert ) { - expect(2); - assert.ok(true); - var expected = expect(); - assert.equal(expected, 2); - expect(expected + 1); - assert.ok(true); -}); - -QUnit.module( "assertion helpers" ); - -QUnit.test( "QUnit.assert compatibility", function( assert ) { - expect( 5 ); - assert.ok( true, "Calling method on `assert` argument to test() callback" ); - - // Should also work, although discouraged and not documented - QUnit.assert.ok( true, "Calling method on QUnit.assert object" ); - - // Test compatibility aliases - QUnit.ok( true, "Calling aliased method in QUnit root object" ); - assert.ok( true, "Calling aliased function in global namespace" ); - - // Regression fix for #341 - // The assert-context way of testing discouraged global variables, - // it doesn't make sense of it itself to be a global variable. - // Only allows for mistakes (e.g. forgetting to list 'assert' as parameter) - assert.notStrictEqual( window.assert, QUnit.assert, "Assert does not get exposed as a global variable" ); -}); - -QUnit.module( "setup test", { - setup: function( assert ) { - assert.ok(true); - } -}); - -test("module with setup", function( assert ) { - expect(2); - assert.ok(true); -}); - -test("module with setup, expect in test call", function( assert ) { - expect( 2 ); - assert.ok(true); -}); - -// TODO: More to the html-reporter test once we have that. -if ( typeof document !== "undefined" ) { - - QUnit.module( "", { - setup: function() { - }, - teardown: function( assert ) { - - // We can't use ok(false) inside script tags since some browsers - // don't evaluate script tags inserted through innerHTML after domready. - // Counting them before/after doesn't cover everything either as qunit-modulefilter - // is created before any test is ran. So use ids instead. - if ( document.getElementById( "qunit-unescaped-module" ) ) { - - // This can either be from in #qunit-modulefilter or #qunit-testresult - assert.ok( false, "Unescaped module name" ); - } - if ( document.getElementById( "qunit-unescaped-test" ) ) { - assert.ok( false, "Unescaped test name" ); - } - if ( document.getElementById( "qunit-unescaped-assertion" ) ) { - assert.ok( false, "Unescaped test name" ); - } - } - }); - - test( "", function( assert ) { - expect( 1 ); - assert.ok( true, "" ); - }); - -} - -var state; - -QUnit.module( "setup/teardown test", { - setup: function( assert ) { - state = true; - assert.ok(true); - - // Assert that we can introduce and delete globals in setup/teardown - // without noglobals sounding any alarm. - - // Using an implied global variable instead of explicit window property - // because there is no way to delete a window.property in IE6-8 - // `delete x` only works for `x = 1, and `delete window.x` throws exception. - // No one-code fits all solution possible afaic. Resort to @cc. - - /*@cc_on - @if (@_jscript_version < 9) - x = 1; - @else @*/ - window.x = 1; - /*@end - @*/ - }, - teardown: function( assert ) { - assert.ok(true); - - /*@cc_on - @if (@_jscript_version < 9) - delete x; - @else @*/ - delete window.x; - /*@end - @*/ - } -}); - -test("module with setup/teardown", function( assert ) { - expect(3); - assert.ok(true); -}); - -QUnit.module( "setup/teardown test 2" ); - -test("module without setup/teardown", function( assert ) { - expect(1); - assert.ok(true); -}); - -var OrgDate; - -QUnit.module( "Date test", { - setup: function( assert ) { - OrgDate = Date; - window.Date = function () { - assert.ok( false, 'QUnit should internally be independent from Date-related manipulation and testing' ); - return new OrgDate(); - }; - }, - teardown: function() { - window.Date = OrgDate; - } -}); - -test("sample test for Date test", function ( assert ) { - expect(1); - assert.ok(true); -}); - -if (typeof setTimeout !== 'undefined') { -state = 'fail'; - -QUnit.module( "teardown and stop", { - teardown: function( assert ) { - assert.equal(state, "done", "Test teardown."); - } -}); - -test("teardown must be called after test ended", function() { - expect(1); - stop(); - setTimeout(function() { - state = "done"; - start(); - }, 13); -}); - -test("parameter passed to stop increments semaphore n times", function() { - expect(1); - stop(3); - setTimeout(function() { - state = "not enough starts"; - start(); - start(); - }, 13); - setTimeout(function() { - state = "done"; - start(); - }, 15); -}); - -test("parameter passed to start decrements semaphore n times", function() { - expect(1); - stop(); - stop(); - stop(); - setTimeout(function() { - state = "done"; - start(3); - }, 18); -}); - -QUnit.module( "async setup test", { - setup: function( assert ) { - stop(); - setTimeout(function() { - assert.ok(true); - start(); - }, 500); - } -}); - -asyncTest("module with async setup", function( assert ) { - expect(2); - assert.ok(true); - start(); -}); - -QUnit.module( "async teardown test", { - teardown: function( assert ) { - stop(); - setTimeout(function() { - assert.ok(true); - start(); - }, 500); - } -}); - -asyncTest("module with async teardown", function( assert ) { - expect(2); - assert.ok(true); - start(); -}); - -QUnit.module( "asyncTest" ); - -asyncTest("asyncTest", function( assert ) { - expect( 2 ); - assert.ok(true); - setTimeout(function() { - state = "done"; - assert.ok(true); - start(); - }, 13); -}); - -asyncTest("asyncTest with expect()", function( assert ) { - expect(2); - assert.ok(true); - setTimeout(function() { - state = "done"; - assert.ok(true); - start(); - }, 13); -}); - -test("sync", function( assert ) { - expect( 2 ); - stop(); - setTimeout(function() { - assert.ok(true); - start(); - }, 13); - stop(); - setTimeout(function() { - assert.ok(true); - start(); - }, 125); -}); - -test("test synchronous calls to stop", function( assert ) { - expect( 2 ); - stop(); - setTimeout(function() { - assert.ok(true, 'first'); - start(); - stop(); - setTimeout(function() { - assert.ok(true, 'second'); - start(); - }, 150); - }, 150); -}); -} - -QUnit.module( "save scope", { - setup: function() { - this.foo = "bar"; - }, - teardown: function( assert ) { - assert.deepEqual(this.foo, "bar"); - } -}); -test("scope check", function( assert ) { - expect(2); - assert.deepEqual(this.foo, "bar"); -}); - -QUnit.module( "simple testEnvironment setup", { - foo: "bar", - // example of meta-data - bugid: "#5311" -}); -test("scope check", function( assert ) { - assert.deepEqual(this.foo, "bar"); -}); -test("modify testEnvironment", function() { - expect(0); - this.foo = "hamster"; -}); -test("testEnvironment reset for next test", function( assert ) { - assert.deepEqual(this.foo, "bar"); -}); - -QUnit.module( "testEnvironment with object", { - options: { - recipe: "soup", - ingredients: ["hamster", "onions"] - } -}); -test("scope check", function( assert ) { - assert.deepEqual(this.options, { - recipe: "soup", - ingredients: ["hamster", "onions"] - }); -}); -test("modify testEnvironment",function() { - expect(0); - // since we only do a shallow copy, nested children of testEnvironment can be modified - // and survice - this.options.ingredients.push("carrots"); -}); -test("testEnvironment reset for next test",function( assert ) { - assert.deepEqual(this.options, { - recipe: "soup", - ingredients: ["hamster", "onions", "carrots"] - }, "Is this a bug or a feature? Could do a deep copy") ; -}); - - -QUnit.module( "testEnvironment tests" ); - -function makeurl() { - var testEnv = QUnit.config.current.testEnvironment; - var url = testEnv.url || 'http://example.com/search'; - var q = testEnv.q || 'a search test'; - return url + '?q='+encodeURIComponent(q); -} - -test("makeurl working", function( assert ) { - expect( 2 ); - assert.equal( QUnit.config.current.testEnvironment, this, 'The current testEnvironment QUnit.config'); - assert.equal( makeurl(), 'http://example.com/search?q=a%20search%20test', 'makeurl returns a default url if nothing specified in the testEnvironment'); -}); - -QUnit.module( "testEnvironment with makeurl settings", { - url: 'http://google.com/', - q: 'another_search_test' -}); -test("makeurl working with settings from testEnvironment", function( assert ) { - assert.equal( makeurl(), 'http://google.com/?q=another_search_test', 'rather than passing arguments, we use test metadata to from the url'); -}); - -QUnit.module( "jsDump" ); -test("jsDump output", function( assert ) { - assert.equal( QUnit.jsDump.parse([1, 2]), "[\n 1,\n 2\n]" ); - assert.equal( QUnit.jsDump.parse({top: 5, left: 0}), "{\n \"left\": 0,\n \"top\": 5\n}" ); - if (typeof document !== 'undefined' && document.getElementById("qunit-header")) { - assert.equal( QUnit.jsDump.parse(document.getElementById("qunit-header")), "

                " ); - assert.equal( QUnit.jsDump.parse(document.getElementsByTagName("h1")), "[\n

                \n]" ); - } -}); - -QUnit.module( "assertions" ); - -test("propEqual", function( assert ) { - expect( 5 ); - var objectCreate = Object.create || function ( origin ) { - function O() {} - O.prototype = origin; - var r = new O(); - return r; - }; - - function Foo( x, y, z ) { - this.x = x; - this.y = y; - this.z = z; - } - Foo.prototype.doA = function () {}; - Foo.prototype.doB = function () {}; - Foo.prototype.bar = 'prototype'; - - function Bar() { - } - Bar.prototype = objectCreate( Foo.prototype ); - Bar.prototype.constructor = Bar; - - assert.propEqual( - new Foo( 1, '2', [] ), - { - x: 1, - y: '2', - z: [] - } - ); - - assert.notPropEqual( - new Foo( '1', 2, 3 ), - { - x: 1, - y: '2', - z: 3 - }, - 'Primitive values are strictly compared' - ); - - assert.notPropEqual( - new Foo( 1, '2', [] ), - { - x: 1, - y: '2', - z: {} - }, - 'Array type is preserved' - ); - - assert.notPropEqual( - new Foo( 1, '2', {} ), - { - x: 1, - y: '2', - z: [] - }, - 'Empty array is not the same as empty object' - ); - - assert.propEqual( - new Foo( 1, '2', new Foo( [ 3 ], new Bar(), null ) ), - { - x: 1, - y: '2', - z: { - x: [ 3 ], - y: {}, - z: null - } - }, - 'Complex nesting of different types, inheritance and constructors' - ); -}); - -test("throws", function( assert ) { - expect(10); - function CustomError( message ) { - this.message = message; - } - - CustomError.prototype.toString = function() { - return this.message; - }; - - assert.throws( - function() { - throw "my error"; - } - ); - - assert.throws( - function() { - throw "my error"; - }, - "simple string throw, no 'expected' value given" - ); - - // This test is for IE 7 and prior which does not properly - // implement Error.prototype.toString - assert.throws( - function() { - throw new Error("error message"); - }, - /error message/, - "use regexp against instance of Error" - ); - - assert.throws( - function() { - throw new CustomError(); - }, - CustomError, - 'thrown error is an instance of CustomError' - ); - - assert.throws( - function() { - throw new CustomError("some error description"); - }, - /description/, - "use a regex to match against the stringified error" - ); - - assert.throws( - function() { - throw new CustomError("some error description"); - }, - function( err ) { - if ( (err instanceof CustomError) && /description/.test(err) ) { - return true; - } - }, - "custom validation function" - ); - - assert.throws( - function() { - /*jshint evil:true */ - ( window.execScript || function( data ) { - window["eval"].call( window, data ); - })( "throw 'error';" ); - }, - 'globally-executed errors caught' - ); - - this.CustomError = CustomError; - - assert.throws( - function() { - throw new this.CustomError("some error description"); - }, - /description/, - "throw error from property of 'this' context" - ); - - assert.throws( - function() { - throw "some error description"; - }, - "some error description", - "handle string typed thrown errors" - ); - - assert.throws( - function() { - throw new Error( "foo" ); - }, - new Error( "foo" ), - "assert when a function throws an 'Error' object" - ); -}); - -if (typeof document !== "undefined") { - -QUnit.module( "fixture" ); -test("setup", function() { - expect(0); - document.getElementById("qunit-fixture").innerHTML = "foobar"; -}); - -test("basics", function( assert ) { - assert.equal( document.getElementById("qunit-fixture").innerHTML, "test markup", "automatically reset" ); -}); - -test("running test name displayed", function( assert ) { - expect(2); - - var displaying = document.getElementById("qunit-testresult"); - - assert.ok( /running test name displayed/.test(displaying.innerHTML), "Expect test name to be found in displayed text" ); - assert.ok( /fixture/.test(displaying.innerHTML), "Expect module name to be found in displayed text" ); -}); - -(function() { - var delayNextSetup, - sleep = function( n ) { - stop(); - setTimeout( function() { start(); }, n ); - }; - - QUnit.module( "timing", { - setup: function() { - if ( delayNextSetup ) { - delayNextSetup = false; - sleep( 250 ); - } - } - }); - - test("setup", function() { - expect( 0 ); - delayNextSetup = true; - }); - - test("basics", function( assert ) { - expect( 2 ); - var previous = getPreviousTests(/^setup$/, /^timing$/)[0], - runtime = previous.lastChild.previousSibling; - assert.ok( /(^| )runtime( |$)/.test( runtime.className ), "Runtime element exists" ); - assert.ok( /^\d+ ms$/.test( runtime.innerHTML ), "Runtime reported in ms" ); - }); - - test("values", function( assert ) { - expect( 2 ); - var basics = getPreviousTests(/^setup$/, /^timing$/)[0], - slow = getPreviousTests(/^basics$/, /^timing$/)[0]; - assert.ok( parseInt( basics.lastChild.previousSibling.innerHTML, 10 ) < 50, "Fast runtime for trivial test" ); - assert.ok( parseInt( slow.lastChild.previousSibling.innerHTML, 10 ) > 250, "Runtime includes setup" ); - }); -})(); - -} - -QUnit.module( "custom assertions" ); -(function() { - QUnit.assert.mod2 = function( value, expected, message ) { - var actual = value % 2; - QUnit.push(actual === expected, actual, expected, message); - }; - test("mod2", function( assert ) { - expect( 2 ); - assert.mod2(2, 0, "2 % 2 == 0"); - assert.mod2(3, 1, "3 % 2 == 1"); - }); -})(); - - -QUnit.module( "recursions" ); - -function Wrap(x) { - this.wrap = x; - if (x === undefined) { - this.first = true; - } -} - -function chainwrap(depth, first, prev) { - depth = depth || 0; - var last = prev || new Wrap(); - first = first || last; - - if (depth === 1) { - first.wrap = last; - } - if (depth > 1) { - last = chainwrap(depth-1, first, new Wrap(last)); - } - - return last; -} - -test("Check jsDump recursion", function( assert ) { - expect(4); - - var noref = chainwrap(0); - var nodump = QUnit.jsDump.parse(noref); - assert.equal(nodump, '{\n "first": true,\n "wrap": undefined\n}'); - - var selfref = chainwrap(1); - var selfdump = QUnit.jsDump.parse(selfref); - assert.equal(selfdump, '{\n "first": true,\n "wrap": recursion(-1)\n}'); - - var parentref = chainwrap(2); - var parentdump = QUnit.jsDump.parse(parentref); - assert.equal(parentdump, '{\n "wrap": {\n "first": true,\n "wrap": recursion(-2)\n }\n}'); - - var circref = chainwrap(10); - var circdump = QUnit.jsDump.parse(circref); - assert.ok(new RegExp("recursion\\(-10\\)").test(circdump), "(" +circdump + ") should show -10 recursion level"); -}); - -test("Check equal/deepEqual recursion", function( assert ) { - var noRecursion = chainwrap(0); - assert.equal(noRecursion, noRecursion, "I should be equal to me."); - assert.deepEqual(noRecursion, noRecursion, "... and so in depth."); - - var selfref = chainwrap(1); - assert.equal(selfref, selfref, "Even so if I nest myself."); - assert.deepEqual(selfref, selfref, "... into the depth."); - - var circref = chainwrap(10); - assert.equal(circref, circref, "Or hide that through some levels of indirection."); - assert.deepEqual(circref, circref, "... and checked on all levels!"); -}); - - -test("Circular reference with arrays", function( assert ) { - - // pure array self-ref - var arr = []; - arr.push(arr); - - var arrdump = QUnit.jsDump.parse(arr); - - assert.equal(arrdump, '[\n recursion(-1)\n]'); - assert.equal(arr, arr[0], 'no endless stack when trying to dump arrays with circular ref'); - - - // mix obj-arr circular ref - var obj = {}; - var childarr = [obj]; - obj.childarr = childarr; - - var objdump = QUnit.jsDump.parse(obj); - var childarrdump = QUnit.jsDump.parse(childarr); - - assert.equal(objdump, '{\n "childarr": [\n recursion(-2)\n ]\n}'); - assert.equal(childarrdump, '[\n {\n "childarr": recursion(-2)\n }\n]'); - - assert.equal(obj.childarr, childarr, 'no endless stack when trying to dump array/object mix with circular ref'); - assert.equal(childarr[0], obj, 'no endless stack when trying to dump array/object mix with circular ref'); - -}); - - -test("Circular reference - test reported by soniciq in #105", function( assert ) { - var MyObject = function() {}; - MyObject.prototype.parent = function(obj) { - if (obj === undefined) { return this._parent; } - this._parent = obj; - }; - MyObject.prototype.children = function(obj) { - if (obj === undefined) { return this._children; } - this._children = obj; - }; - - var a = new MyObject(), - b = new MyObject(); - - var barr = [b]; - a.children(barr); - b.parent(a); - - assert.equal(a.children(), barr); - assert.deepEqual(a.children(), [b]); -}); - -(function() { - var reset = QUnit.reset; - QUnit.module( "reset" ); - test("reset runs assertions", function( assert ) { - expect(0); - QUnit.reset = function() { - assert.ok( false, "reset should not modify test status" ); - reset.apply( this, arguments ); - }; - }); - test("reset runs assertions, cleanup", function() { - expect(0); - QUnit.reset = reset; - }); -})(); - -function testAfterDone() { - var testName = "ensure has correct number of assertions"; - - function secondAfterDoneTest() { - QUnit.config.done = []; - // Because when this does happen, the assertion count parameter doesn't actually - // work we use this test to check the assertion count. - QUnit.module( "check previous test's assertion counts" ); - test('count previous two test\'s assertions', function ( assert ) { - var tests = getPreviousTests(/^ensure has correct number of assertions/, /^Synchronous test after load of page$/); - - assert.equal(tests[0].firstChild.lastChild.getElementsByTagName("b")[1].innerHTML, "99"); - assert.equal(tests[1].firstChild.lastChild.getElementsByTagName("b")[1].innerHTML, "99"); - }); - } - QUnit.config.done = []; - QUnit.done(secondAfterDoneTest); - - QUnit.module( "Synchronous test after load of page" ); - - asyncTest('Async test', function( assert ) { - start(); - for (var i = 1; i < 100; i++) { - assert.ok(i); - } - }); - - test(testName, function( assert ) { - expect( 99 ); - for (var i = 1; i < 100; i++) { - assert.ok(i); - } - }); - - // We need two of these types of tests in order to ensure that assertions - // don't move between tests. - test(testName + ' 2', function( assert ) { - expect( 99 ); - for (var i = 1; i < 100; i++) { - assert.ok(i); - } - }); - -} - -if (typeof setTimeout !== 'undefined') { - QUnit.done(testAfterDone); -} - - -// Get a reference to the global object, like window in browsers -}( (function() { - return this; -}.call()) )); diff -Nru libjs-qunit-1.14.0/.travis.yml libjs-qunit-1.22.0/.travis.yml --- libjs-qunit-1.14.0/.travis.yml 2014-01-31 16:41:24.000000000 +0000 +++ libjs-qunit-1.22.0/.travis.yml 2016-02-23 15:57:56.000000000 +0000 @@ -1,5 +1,18 @@ +sudo: false language: node_js node_js: - - "0.10" + - "4" before_install: - - npm install -g grunt-cli + - true && `base64 --decode <<< ZXhwb3J0IEJST1dTRVJTVEFDS19VU0VSTkFNRT1icm93c2Vyc3RhY2txdW5pMQo=` + - true && `base64 --decode <<< ZXhwb3J0IEJST1dTRVJTVEFDS19LRVk9SllzeHJrVWk5aGJGVndkdW44ZUsK=` +script: + - npm run-script ci +cache: + directories: + - node_modules +notifications: + irc: + channels: + - "chat.freenode.net#jquery-dev" + template: + - "%{repository}#%{build_number} (%{branch} - %{commit} %{author}): %{message} - %{build_url}"