├── README.md ├── package.json ├── test ├── specs │ ├── username.test.js │ ├── password.test.js │ ├── zipCode.test.js │ ├── postalCode.test.js │ ├── email.test.js │ └── url.test.js ├── index.html └── qunit │ ├── qunit.css │ └── qunit.js ├── grunt.js └── regex.js /README.md: -------------------------------------------------------------------------------- 1 | A collection of useful regex patterns. The regex file is not intended to be used as is. It is meant as a reference/cheat sheet. 2 | 3 | ## License 4 | 5 | RegexCollection is licensed under MIT http://www.opensource.org/licenses/MIT 6 | 7 | ### Copyright 8 | 9 | Copyright (c) 2012, Fabien Doiron 10 | , [@fabien_doiron](http://twitter.com/fabien_doiron) -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "RegexCollection", 3 | "description" : "A collection of useful regex patterns", 4 | "version" : "0.0.1", 5 | "homepage" : "https://github.com/fabien-d/RegexCollection", 6 | "author" : { 7 | "name" : "Fabien Doiron", 8 | "email" : "fabien.doiron@gmail.com" 9 | }, 10 | "licenses" : [{ 11 | "type" : "MIT", 12 | "url" : "http://opensource.org/licenses/mit-license.php" 13 | }] 14 | } -------------------------------------------------------------------------------- /test/specs/username.test.js: -------------------------------------------------------------------------------- 1 | test("username validation", function() { 2 | expect(6); 3 | var u = USERNAME; 4 | // testing invalid patterns 5 | deepEqual(u.test("fd"), false, "username must be minimum of 4 characters"); 6 | deepEqual(u.test("thisisareallylongusername"), false, "username must be maximum of 12 characters"); 7 | // testing valid patterns 8 | deepEqual(u.test("fabiend"), true, "valid username"); 9 | deepEqual(u.test("fabiend12"), true, "valid username"); 10 | deepEqual(u.test("fabien-d"), true, "valid username with dash"); 11 | deepEqual(u.test("fabien_doiron"), true, "valid username with underscore"); 12 | }); -------------------------------------------------------------------------------- /test/specs/password.test.js: -------------------------------------------------------------------------------- 1 | test("password validation", function() { 2 | expect(6); 3 | var p = PASSWORD; 4 | // testing invalid patterns 5 | deepEqual(p.test("ab"), false, "must be at least 6 characters"); 6 | deepEqual(p.test("abcdefghijklmnop"), false, "must be no more than 12 characters"); 7 | deepEqual(p.test("1bcdegf"), false, "must contain at least 1 uppercase"); 8 | deepEqual(p.test("Abcdefg"), false, "must contain at least 1 number"); 9 | // testing valid patterns 10 | deepEqual(p.test("Abcdefgh1"), true, "valid password with base requirements"); 11 | deepEqual(p.test("@Bc!2$deF"), true, "valid password with special characters"); 12 | }); 13 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Regex Test Suite 6 | 7 | 8 | 9 | 10 |
11 |
test markup
12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /test/specs/zipCode.test.js: -------------------------------------------------------------------------------- 1 | test("zip code validation", function() { 2 | expect(9); 3 | var z = ZIP_CODE; 4 | // testing invalid patterns 5 | deepEqual(z.test("2341"), false, "insufficent numbers, must be exactly 5 or 9 digits"); 6 | deepEqual(z.test("23412536"), false, "insufficent numbers, if over 5, must be 9"); 7 | deepEqual(z.test("2A341"), false, "cannot include characters"); 8 | deepEqual(z.test("21323441092"), false, "too many numbers"); 9 | deepEqual(z.test("2A341 2341"), false, "cannot include multiple spaces"); 10 | // testing valid patterns 11 | deepEqual(z.test("90210"), true, "valid zip code"); 12 | deepEqual(z.test("90210-1234"), true, "valid zip code + 4"); 13 | deepEqual(z.test("902101234"), true, "valid zip code + 4 no space"); 14 | deepEqual(z.test("90210 1234"), true, "valid zip code + 4 with space"); 15 | }); 16 | -------------------------------------------------------------------------------- /test/specs/postalCode.test.js: -------------------------------------------------------------------------------- 1 | test("canadian postal code validation", function() { 2 | expect(9); 3 | var p = POSTAL_CODE; 4 | // testing invalid patterns 5 | deepEqual(p.test("DDDDDD"), false, "postal code format must be A0A0A0"); 6 | deepEqual(p.test("W0A0A0"), false, "W cannot be the first character"); 7 | deepEqual(p.test("Z0A0A0"), false, "Z cannot be the first character"); 8 | deepEqual(p.test("D0F0I0"), false, "postal code cannot include D, F, I, O, Q or U"); 9 | deepEqual(p.test("K0F0I0"), false, "postal code cannot include D, F, I, O, Q or U"); 10 | deepEqual(p.test("K0K0I0"), false, "postal code cannot include D, F, I, O, Q or U"); 11 | deepEqual(p.test("K1A 0B1"), false, "can only include 1 space"); 12 | // testing valid patterns 13 | deepEqual(p.test("K1A0B1"), true, "very simple valid postal code"); 14 | deepEqual(p.test("K1A 0B1"), true, "optional space at 4th character"); 15 | }); -------------------------------------------------------------------------------- /test/specs/email.test.js: -------------------------------------------------------------------------------- 1 | test("email validation", function() { 2 | expect(11); 3 | var e = EMAIL; 4 | // testing invalid patterns 5 | deepEqual(e.test(".mail@abc.com"), false, "local part: period cannot be the first character"); 6 | deepEqual(e.test("mail.@abc.com"), false, "local part: period cannot be the last character"); 7 | deepEqual(e.test("ma..l@abc.com"), false, "local part: period cannot appear twice in a row"); 8 | deepEqual(e.test("email.abc.com"), false, "@ character must separate local and domain parts"); 9 | deepEqual(e.test("email@a@bc.com"), false, "@ character must only appear once outside of quotes"); 10 | // testing valid patterns 11 | deepEqual(e.test("email@abc.com"), true, "very simple valid email"); 12 | deepEqual(e.test("first.last@abc.com"), true, "acceptable period in email"); 13 | deepEqual(e.test("first.middle.last@abc.com"), true, "acceptable period in email"); 14 | deepEqual(e.test("first.middle.last+@abc.com"), true, "+ character in email"); 15 | deepEqual(e.test("user@[IPv6:2001:db8:1ff::a0b:dbd0]"), true, "testing IPv6 domain"); 16 | deepEqual(e.test("\"much.more unusual\"@example.com"), true, "testing quotes"); 17 | }); -------------------------------------------------------------------------------- /grunt.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | "use strict"; 3 | 4 | // Project configuration. 5 | grunt.initConfig({ 6 | pkg : "", 7 | meta : { 8 | banner : "/**\n" + 9 | " * <%= pkg.name %>\n" + 10 | " * <%= pkg.description %>\n" + 11 | " *\n" + 12 | " * @author <%= pkg.author.name %> <<%= pkg.author.email %>>\n" + 13 | " * @copyright <%= pkg.author.name %> <%= grunt.template.today('yyyy') %>\n" + 14 | " * @license <%= pkg.licenses[0].type %> <<%= pkg.licenses[0].url %>>\n" + 15 | " * @link <%= pkg.homepage %>\n" + 16 | " * @module <%= pkg.name %>\n" + 17 | " * @version <%= pkg.version %>\n" + 18 | " */" 19 | }, 20 | lint : { 21 | all : ["grunt.js", "regex.js"] 22 | }, 23 | qunit : { 24 | files : ["test/index.html"] 25 | }, 26 | watch : { 27 | scripts: { 28 | files : '', 29 | tasks : 'default' 30 | } 31 | }, 32 | jshint : { 33 | options: { 34 | curly : false, 35 | eqeqeq : true, 36 | immed : true, 37 | latedef : true, 38 | noempty : true, 39 | newcap : true, 40 | noarg : true, 41 | sub : true, 42 | undef : true, 43 | boss : true, 44 | eqnull : true, 45 | node : true, 46 | smarttabs : true, 47 | es5 : true 48 | }, 49 | globals : { 50 | exports : true, 51 | define : true 52 | } 53 | } 54 | }); 55 | 56 | // Default task. 57 | grunt.registerTask("default", "lint qunit"); 58 | }; -------------------------------------------------------------------------------- /test/specs/url.test.js: -------------------------------------------------------------------------------- 1 | test("URL validation", function() { 2 | expect(19); 3 | var u = URL; 4 | // testing invalid patterns 5 | deepEqual(u.test("-example.com"), false, "URL cannot begin with a dash"); 6 | deepEqual(u.test("http://example.com:"), false, "URL must have a number for a port"); 7 | deepEqual(u.test("http://example.com:123123"), false, "URL port must be no more than 5 digits"); 8 | deepEqual(u.test("htp://example.com"), false, "URL scheme is wrong, should be http"); 9 | deepEqual(u.test("ftp;//example.com"), false, "URL scheme is wrong, cannot be a semicolon"); 10 | deepEqual(u.test("http:/example.com"), false, "URL scheme is wrong, should have 2 slashes"); 11 | deepEqual(u.test("http://www.example.a"), false, "URL top-level domain must be between 2 and 6 characters"); 12 | // testing valid patterns 13 | deepEqual(u.test("http://www.example.com"), true, "valid URL with http scheme"); 14 | deepEqual(u.test("https://www.example.com"), true, "valid URL with https scheme"); 15 | deepEqual(u.test("ftp://www.example.com"), true, "valid URL with ftp scheme"); 16 | deepEqual(u.test("git://github.com"), true, "valid URL with git scheme"); 17 | deepEqual(u.test("http://www.exa-mple.com"), true, "valid URL with a dash"); 18 | deepEqual(u.test("www.3xampl3.com"), true, "valid URL with numbers"); 19 | deepEqual(u.test("example.com:8080"), true, "valid URL with port"); 20 | deepEqual(u.test("http://example.com:80"), true, "valid URL with port"); 21 | deepEqual(u.test("http://www.example.com/"), true, "valid URL with trailing slash"); 22 | deepEqual(u.test("http://www.example.com/path_name/page.html"), true, "valid URL with path"); 23 | deepEqual(u.test("http://www.example.com/path_name/page.html?foo=1&bar=2"), true, "valid URL with query string"); 24 | deepEqual(u.test("http://www.example.com/path_name/page.html?foo=1&bar=2#anchor"), true, "valid URL with anchor"); 25 | }); 26 | -------------------------------------------------------------------------------- /regex.js: -------------------------------------------------------------------------------- 1 | var 2 | /** 3 | * Email Pattern 4 | * Validating email is only 100% sure by attempting to send an email 5 | * This pattern will catch the most common errors 6 | * 7 | * ^ start of line 8 | * (?!.*([.])\1) prevent duplicate period character 9 | * [^.] cannot start with a period 10 | * [^@]+ any character except @ 11 | * [^.] last character before @ cannot be a period 12 | * @ @ symbol 13 | * [^\s@]+ one or more non-space or @ symbol 14 | * $ end of line 15 | * 16 | * @type {RegExp} 17 | */ 18 | EMAIL = /^(?!.*([.])\1)[^.][^@]+[^.]@[^\s@]+$/, 19 | /** 20 | * Password Pattern 21 | * This pattern is a starting point and should be modified 22 | * to match the need of the application. 23 | * 24 | * Base pattern requirements are: 25 | * 1 Uppercase 26 | * 1 Number 27 | * Be between 6 and 12 characters 28 | * 29 | * Accepts: 30 | * Uppercase characters 31 | * Lowercase characters 32 | * Numbers 33 | * Special Characters !, @, #, $, % 34 | * 35 | * ^ start of line 36 | * (?=.*[A-Z]) ensure at least 1 uppercase 37 | * (?=.*[0-9]) ensure at least 1 digit 38 | * [a-zA-Z0-9!@#$%] match characters, digits and a few special characters 39 | * {6,12} between 6 and 12 characters 40 | * $ end of line 41 | * 42 | * @type {RegExp} 43 | */ 44 | PASSWORD = /^(?=.*[A-Z])(?=.*[0-9])[a-zA-Z0-9!@#$%]{6,12}$/, 45 | /** 46 | * Canadian Postal Code Pattern 47 | * Validating canadian postal code 48 | * 49 | * ^ start of line 50 | * [a-ceghj-nprstvxy] any characters except D, F, I, O, Q, U, W, Z 51 | * \d any digit 52 | * [a-ceghj-nprstv-z] any characters expect D, F, I, O, Q, U 53 | * ? optional space 54 | * \d any digit 55 | * [a-ceghj-nprstv-z] any characters expect D, F, I, O, Q, U 56 | * \d any digit 57 | * $ end of line 58 | * 59 | * @type {RegExp} 60 | */ 61 | POSTAL_CODE = /^[a-ceghj-nprstvxy]\d[a-ceghj-nprstv-z] ?\d[a-ceghj-nprstv-z]\d$/i, 62 | /** 63 | * URL Pattern 64 | * Matches ftp, git and http(s) patterns as below 65 | * scheme://domain:port/path?query_string#fragment_id 66 | * 67 | * ^ start of line 68 | * ((ftp|git|https?):\/\/)? optional scheme: ftp, git or http(s) followed by :// 69 | * [a-z0-9] any character or digit (URL cannot start with a period or dash) 70 | * [a-z0-9\.\-]+ any character, digit, period and dash 71 | * \. period 72 | * [a-z]{2,6} 2 to 6 top-level domain 73 | * (:\d{1,5})? optional 1 to 5 digit port 74 | * (\/([\w\.\/\-&\?=#]?)+)? optional character, period, &, ?, =, # preceeded by a / 75 | * $ end of line 76 | * 77 | * @type {RegExp} 78 | */ 79 | URL = /^((ftp|git|https?):\/\/)?[a-z0-9][a-z0-9\.\-]+\.[a-z]{2,6}(:\d{1,5})?(\/([\w\.\/\-&\?=#]?)+)?$/i, 80 | /** 81 | * Username Pattern 82 | * This pattern is a starting point and should be modified 83 | * to match the need of the application. 84 | * Base pattern requirements are: characters, numbers, -, _ 85 | * and between 4 and 18 characters. 86 | * 87 | * ^ start of line 88 | * [\w\-] matches word characters and underscore (same as a-zA-Z0-9_) 89 | * {4,18} between 4 and 18 characters 90 | * $ end of line 91 | * 92 | * @type {RegExp} 93 | */ 94 | USERNAME = /^[\w\-]{4,18}$/, 95 | /** 96 | * US Zip Code Pattern 97 | * Matches both the 5 and 9 (10 with space or dash) digits (zip + 4) 98 | * 99 | * ^ start of line 100 | * \d{5} any 5 digits 101 | * ( 102 | * [ \-]? optional space of dash 103 | * \d{4} any 4 digits 104 | * )? last 4 digits are optional 105 | * $ end of line 106 | * 107 | * @type {RegExp} 108 | */ 109 | ZIP_CODE = /^\d{5}([ \-]?\d{4})?$/; 110 | -------------------------------------------------------------------------------- /test/qunit/qunit.css: -------------------------------------------------------------------------------- 1 | /** 2 | * QUnit v1.9.0 - A JavaScript Unit Testing Framework 3 | * 4 | * http://docs.jquery.com/QUnit 5 | * 6 | * Copyright (c) 2012 John Resig, Jörn Zaefferer 7 | * Dual licensed under the MIT (MIT-LICENSE.txt) 8 | * or GPL (GPL-LICENSE.txt) licenses. 9 | */ 10 | 11 | /** Font Family and Sizes */ 12 | 13 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult { 14 | font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif; 15 | } 16 | 17 | #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } 18 | #qunit-tests { font-size: smaller; } 19 | 20 | 21 | /** Resets */ 22 | 23 | #qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult { 24 | margin: 0; 25 | padding: 0; 26 | } 27 | 28 | 29 | /** Header */ 30 | 31 | #qunit-header { 32 | padding: 0.5em 0 0.5em 1em; 33 | 34 | color: #8699a4; 35 | background-color: #0d3349; 36 | 37 | font-size: 1.5em; 38 | line-height: 1em; 39 | font-weight: normal; 40 | 41 | border-radius: 5px 5px 0 0; 42 | -moz-border-radius: 5px 5px 0 0; 43 | -webkit-border-top-right-radius: 5px; 44 | -webkit-border-top-left-radius: 5px; 45 | } 46 | 47 | #qunit-header a { 48 | text-decoration: none; 49 | color: #c2ccd1; 50 | } 51 | 52 | #qunit-header a:hover, 53 | #qunit-header a:focus { 54 | color: #fff; 55 | } 56 | 57 | #qunit-testrunner-toolbar label { 58 | display: inline-block; 59 | padding: 0 .5em 0 .1em; 60 | } 61 | 62 | #qunit-banner { 63 | height: 5px; 64 | } 65 | 66 | #qunit-testrunner-toolbar { 67 | padding: 0.5em 0 0.5em 2em; 68 | color: #5E740B; 69 | background-color: #eee; 70 | } 71 | 72 | #qunit-userAgent { 73 | padding: 0.5em 0 0.5em 2.5em; 74 | background-color: #2b81af; 75 | color: #fff; 76 | text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; 77 | } 78 | 79 | 80 | /** Tests: Pass/Fail */ 81 | 82 | #qunit-tests { 83 | list-style-position: inside; 84 | } 85 | 86 | #qunit-tests li { 87 | padding: 0.4em 0.5em 0.4em 2.5em; 88 | border-bottom: 1px solid #fff; 89 | list-style-position: inside; 90 | } 91 | 92 | #qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running { 93 | display: none; 94 | } 95 | 96 | #qunit-tests li strong { 97 | cursor: pointer; 98 | } 99 | 100 | #qunit-tests li a { 101 | padding: 0.5em; 102 | color: #c2ccd1; 103 | text-decoration: none; 104 | } 105 | #qunit-tests li a:hover, 106 | #qunit-tests li a:focus { 107 | color: #000; 108 | } 109 | 110 | #qunit-tests ol { 111 | margin-top: 0.5em; 112 | padding: 0.5em; 113 | 114 | background-color: #fff; 115 | 116 | border-radius: 5px; 117 | -moz-border-radius: 5px; 118 | -webkit-border-radius: 5px; 119 | } 120 | 121 | #qunit-tests table { 122 | border-collapse: collapse; 123 | margin-top: .2em; 124 | } 125 | 126 | #qunit-tests th { 127 | text-align: right; 128 | vertical-align: top; 129 | padding: 0 .5em 0 0; 130 | } 131 | 132 | #qunit-tests td { 133 | vertical-align: top; 134 | } 135 | 136 | #qunit-tests pre { 137 | margin: 0; 138 | white-space: pre-wrap; 139 | word-wrap: break-word; 140 | } 141 | 142 | #qunit-tests del { 143 | background-color: #e0f2be; 144 | color: #374e0c; 145 | text-decoration: none; 146 | } 147 | 148 | #qunit-tests ins { 149 | background-color: #ffcaca; 150 | color: #500; 151 | text-decoration: none; 152 | } 153 | 154 | /*** Test Counts */ 155 | 156 | #qunit-tests b.counts { color: black; } 157 | #qunit-tests b.passed { color: #5E740B; } 158 | #qunit-tests b.failed { color: #710909; } 159 | 160 | #qunit-tests li li { 161 | padding: 5px; 162 | background-color: #fff; 163 | border-bottom: none; 164 | list-style-position: inside; 165 | } 166 | 167 | /*** Passing Styles */ 168 | 169 | #qunit-tests li li.pass { 170 | color: #3c510c; 171 | background-color: #fff; 172 | border-left: 10px solid #C6E746; 173 | } 174 | 175 | #qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } 176 | #qunit-tests .pass .test-name { color: #366097; } 177 | 178 | #qunit-tests .pass .test-actual, 179 | #qunit-tests .pass .test-expected { color: #999999; } 180 | 181 | #qunit-banner.qunit-pass { background-color: #C6E746; } 182 | 183 | /*** Failing Styles */ 184 | 185 | #qunit-tests li li.fail { 186 | color: #710909; 187 | background-color: #fff; 188 | border-left: 10px solid #EE5757; 189 | white-space: pre; 190 | } 191 | 192 | #qunit-tests > li:last-child { 193 | border-radius: 0 0 5px 5px; 194 | -moz-border-radius: 0 0 5px 5px; 195 | -webkit-border-bottom-right-radius: 5px; 196 | -webkit-border-bottom-left-radius: 5px; 197 | } 198 | 199 | #qunit-tests .fail { color: #000000; background-color: #EE5757; } 200 | #qunit-tests .fail .test-name, 201 | #qunit-tests .fail .module-name { color: #000000; } 202 | 203 | #qunit-tests .fail .test-actual { color: #EE5757; } 204 | #qunit-tests .fail .test-expected { color: green; } 205 | 206 | #qunit-banner.qunit-fail { background-color: #EE5757; } 207 | 208 | 209 | /** Result */ 210 | 211 | #qunit-testresult { 212 | padding: 0.5em 0.5em 0.5em 2.5em; 213 | 214 | color: #2b81af; 215 | background-color: #D2E0E6; 216 | 217 | border-bottom: 1px solid white; 218 | } 219 | #qunit-testresult .module-name { 220 | font-weight: bold; 221 | } 222 | 223 | /** Fixture */ 224 | 225 | #qunit-fixture { 226 | position: absolute; 227 | top: -10000px; 228 | left: -10000px; 229 | width: 1000px; 230 | height: 1000px; 231 | } -------------------------------------------------------------------------------- /test/qunit/qunit.js: -------------------------------------------------------------------------------- 1 | /** 2 | * QUnit v1.9.0 - A JavaScript Unit Testing Framework 3 | * 4 | * http://docs.jquery.com/QUnit 5 | * 6 | * Copyright (c) 2012 John Resig, Jörn Zaefferer 7 | * Dual licensed under the MIT (MIT-LICENSE.txt) 8 | * or GPL (GPL-LICENSE.txt) licenses. 9 | */ 10 | 11 | (function( window ) { 12 | 13 | var QUnit, 14 | config, 15 | onErrorFnPrev, 16 | testId = 0, 17 | fileName = (sourceFromStacktrace( 0 ) || "" ).replace(/(:\d+)+\)?/, "").replace(/.+\//, ""), 18 | toString = Object.prototype.toString, 19 | hasOwn = Object.prototype.hasOwnProperty, 20 | defined = { 21 | setTimeout: typeof window.setTimeout !== "undefined", 22 | sessionStorage: (function() { 23 | var x = "qunit-test-string"; 24 | try { 25 | sessionStorage.setItem( x, x ); 26 | sessionStorage.removeItem( x ); 27 | return true; 28 | } catch( e ) { 29 | return false; 30 | } 31 | }()) 32 | }; 33 | 34 | function Test( settings ) { 35 | extend( this, settings ); 36 | this.assertions = []; 37 | this.testNumber = ++Test.count; 38 | } 39 | 40 | Test.count = 0; 41 | 42 | Test.prototype = { 43 | init: function() { 44 | var a, b, li, 45 | tests = id( "qunit-tests" ); 46 | 47 | if ( tests ) { 48 | b = document.createElement( "strong" ); 49 | b.innerHTML = this.name; 50 | 51 | // `a` initialized at top of scope 52 | a = document.createElement( "a" ); 53 | a.innerHTML = "Rerun"; 54 | a.href = QUnit.url({ testNumber: this.testNumber }); 55 | 56 | li = document.createElement( "li" ); 57 | li.appendChild( b ); 58 | li.appendChild( a ); 59 | li.className = "running"; 60 | li.id = this.id = "qunit-test-output" + testId++; 61 | 62 | tests.appendChild( li ); 63 | } 64 | }, 65 | setup: function() { 66 | if ( this.module !== config.previousModule ) { 67 | if ( config.previousModule ) { 68 | runLoggingCallbacks( "moduleDone", QUnit, { 69 | name: config.previousModule, 70 | failed: config.moduleStats.bad, 71 | passed: config.moduleStats.all - config.moduleStats.bad, 72 | total: config.moduleStats.all 73 | }); 74 | } 75 | config.previousModule = this.module; 76 | config.moduleStats = { all: 0, bad: 0 }; 77 | runLoggingCallbacks( "moduleStart", QUnit, { 78 | name: this.module 79 | }); 80 | } else if ( config.autorun ) { 81 | runLoggingCallbacks( "moduleStart", QUnit, { 82 | name: this.module 83 | }); 84 | } 85 | 86 | config.current = this; 87 | 88 | this.testEnvironment = extend({ 89 | setup: function() {}, 90 | teardown: function() {} 91 | }, this.moduleTestEnvironment ); 92 | 93 | runLoggingCallbacks( "testStart", QUnit, { 94 | name: this.testName, 95 | module: this.module 96 | }); 97 | 98 | // allow utility functions to access the current test environment 99 | // TODO why?? 100 | QUnit.current_testEnvironment = this.testEnvironment; 101 | 102 | if ( !config.pollution ) { 103 | saveGlobal(); 104 | } 105 | if ( config.notrycatch ) { 106 | this.testEnvironment.setup.call( this.testEnvironment ); 107 | return; 108 | } 109 | try { 110 | this.testEnvironment.setup.call( this.testEnvironment ); 111 | } catch( e ) { 112 | QUnit.pushFailure( "Setup failed on " + this.testName + ": " + e.message, extractStacktrace( e, 1 ) ); 113 | } 114 | }, 115 | run: function() { 116 | config.current = this; 117 | 118 | var running = id( "qunit-testresult" ); 119 | 120 | if ( running ) { 121 | running.innerHTML = "Running:
" + this.name; 122 | } 123 | 124 | if ( this.async ) { 125 | QUnit.stop(); 126 | } 127 | 128 | if ( config.notrycatch ) { 129 | this.callback.call( this.testEnvironment, QUnit.assert ); 130 | return; 131 | } 132 | 133 | try { 134 | this.callback.call( this.testEnvironment, QUnit.assert ); 135 | } catch( e ) { 136 | QUnit.pushFailure( "Died on test #" + (this.assertions.length + 1) + " " + this.stack + ": " + e.message, extractStacktrace( e, 0 ) ); 137 | // else next test will carry the responsibility 138 | saveGlobal(); 139 | 140 | // Restart the tests if they're blocking 141 | if ( config.blocking ) { 142 | QUnit.start(); 143 | } 144 | } 145 | }, 146 | teardown: function() { 147 | config.current = this; 148 | if ( config.notrycatch ) { 149 | this.testEnvironment.teardown.call( this.testEnvironment ); 150 | return; 151 | } else { 152 | try { 153 | this.testEnvironment.teardown.call( this.testEnvironment ); 154 | } catch( e ) { 155 | QUnit.pushFailure( "Teardown failed on " + this.testName + ": " + e.message, extractStacktrace( e, 1 ) ); 156 | } 157 | } 158 | checkPollution(); 159 | }, 160 | finish: function() { 161 | config.current = this; 162 | if ( config.requireExpects && this.expected == null ) { 163 | QUnit.pushFailure( "Expected number of assertions to be defined, but expect() was not called.", this.stack ); 164 | } else if ( this.expected != null && this.expected != this.assertions.length ) { 165 | QUnit.pushFailure( "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run", this.stack ); 166 | } else if ( this.expected == null && !this.assertions.length ) { 167 | QUnit.pushFailure( "Expected at least one assertion, but none were run - call expect(0) to accept zero assertions.", this.stack ); 168 | } 169 | 170 | var assertion, a, b, i, li, ol, 171 | test = this, 172 | good = 0, 173 | bad = 0, 174 | tests = id( "qunit-tests" ); 175 | 176 | config.stats.all += this.assertions.length; 177 | config.moduleStats.all += this.assertions.length; 178 | 179 | if ( tests ) { 180 | ol = document.createElement( "ol" ); 181 | 182 | for ( i = 0; i < this.assertions.length; i++ ) { 183 | assertion = this.assertions[i]; 184 | 185 | li = document.createElement( "li" ); 186 | li.className = assertion.result ? "pass" : "fail"; 187 | li.innerHTML = assertion.message || ( assertion.result ? "okay" : "failed" ); 188 | ol.appendChild( li ); 189 | 190 | if ( assertion.result ) { 191 | good++; 192 | } else { 193 | bad++; 194 | config.stats.bad++; 195 | config.moduleStats.bad++; 196 | } 197 | } 198 | 199 | // store result when possible 200 | if ( QUnit.config.reorder && defined.sessionStorage ) { 201 | if ( bad ) { 202 | sessionStorage.setItem( "qunit-test-" + this.module + "-" + this.testName, bad ); 203 | } else { 204 | sessionStorage.removeItem( "qunit-test-" + this.module + "-" + this.testName ); 205 | } 206 | } 207 | 208 | if ( bad === 0 ) { 209 | ol.style.display = "none"; 210 | } 211 | 212 | // `b` initialized at top of scope 213 | b = document.createElement( "strong" ); 214 | b.innerHTML = this.name + " (" + bad + ", " + good + ", " + this.assertions.length + ")"; 215 | 216 | addEvent(b, "click", function() { 217 | var next = b.nextSibling.nextSibling, 218 | display = next.style.display; 219 | next.style.display = display === "none" ? "block" : "none"; 220 | }); 221 | 222 | addEvent(b, "dblclick", function( e ) { 223 | var target = e && e.target ? e.target : window.event.srcElement; 224 | if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) { 225 | target = target.parentNode; 226 | } 227 | if ( window.location && target.nodeName.toLowerCase() === "strong" ) { 228 | window.location = QUnit.url({ testNumber: test.testNumber }); 229 | } 230 | }); 231 | 232 | // `li` initialized at top of scope 233 | li = id( this.id ); 234 | li.className = bad ? "fail" : "pass"; 235 | li.removeChild( li.firstChild ); 236 | a = li.firstChild; 237 | li.appendChild( b ); 238 | li.appendChild ( a ); 239 | li.appendChild( ol ); 240 | 241 | } else { 242 | for ( i = 0; i < this.assertions.length; i++ ) { 243 | if ( !this.assertions[i].result ) { 244 | bad++; 245 | config.stats.bad++; 246 | config.moduleStats.bad++; 247 | } 248 | } 249 | } 250 | 251 | runLoggingCallbacks( "testDone", QUnit, { 252 | name: this.testName, 253 | module: this.module, 254 | failed: bad, 255 | passed: this.assertions.length - bad, 256 | total: this.assertions.length 257 | }); 258 | 259 | QUnit.reset(); 260 | 261 | config.current = undefined; 262 | }, 263 | 264 | queue: function() { 265 | var bad, 266 | test = this; 267 | 268 | synchronize(function() { 269 | test.init(); 270 | }); 271 | function run() { 272 | // each of these can by async 273 | synchronize(function() { 274 | test.setup(); 275 | }); 276 | synchronize(function() { 277 | test.run(); 278 | }); 279 | synchronize(function() { 280 | test.teardown(); 281 | }); 282 | synchronize(function() { 283 | test.finish(); 284 | }); 285 | } 286 | 287 | // `bad` initialized at top of scope 288 | // defer when previous test run passed, if storage is available 289 | bad = QUnit.config.reorder && defined.sessionStorage && 290 | +sessionStorage.getItem( "qunit-test-" + this.module + "-" + this.testName ); 291 | 292 | if ( bad ) { 293 | run(); 294 | } else { 295 | synchronize( run, true ); 296 | } 297 | } 298 | }; 299 | 300 | // Root QUnit object. 301 | // `QUnit` initialized at top of scope 302 | QUnit = { 303 | 304 | // call on start of module test to prepend name to all tests 305 | module: function( name, testEnvironment ) { 306 | config.currentModule = name; 307 | config.currentModuleTestEnviroment = testEnvironment; 308 | }, 309 | 310 | asyncTest: function( testName, expected, callback ) { 311 | if ( arguments.length === 2 ) { 312 | callback = expected; 313 | expected = null; 314 | } 315 | 316 | QUnit.test( testName, expected, callback, true ); 317 | }, 318 | 319 | test: function( testName, expected, callback, async ) { 320 | var test, 321 | name = "" + escapeInnerText( testName ) + ""; 322 | 323 | if ( arguments.length === 2 ) { 324 | callback = expected; 325 | expected = null; 326 | } 327 | 328 | if ( config.currentModule ) { 329 | name = "" + config.currentModule + ": " + name; 330 | } 331 | 332 | test = new Test({ 333 | name: name, 334 | testName: testName, 335 | expected: expected, 336 | async: async, 337 | callback: callback, 338 | module: config.currentModule, 339 | moduleTestEnvironment: config.currentModuleTestEnviroment, 340 | stack: sourceFromStacktrace( 2 ) 341 | }); 342 | 343 | if ( !validTest( test ) ) { 344 | return; 345 | } 346 | 347 | test.queue(); 348 | }, 349 | 350 | // Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through. 351 | expect: function( asserts ) { 352 | config.current.expected = asserts; 353 | }, 354 | 355 | start: function( count ) { 356 | config.semaphore -= count || 1; 357 | // don't start until equal number of stop-calls 358 | if ( config.semaphore > 0 ) { 359 | return; 360 | } 361 | // ignore if start is called more often then stop 362 | if ( config.semaphore < 0 ) { 363 | config.semaphore = 0; 364 | } 365 | // A slight delay, to avoid any current callbacks 366 | if ( defined.setTimeout ) { 367 | window.setTimeout(function() { 368 | if ( config.semaphore > 0 ) { 369 | return; 370 | } 371 | if ( config.timeout ) { 372 | clearTimeout( config.timeout ); 373 | } 374 | 375 | config.blocking = false; 376 | process( true ); 377 | }, 13); 378 | } else { 379 | config.blocking = false; 380 | process( true ); 381 | } 382 | }, 383 | 384 | stop: function( count ) { 385 | config.semaphore += count || 1; 386 | config.blocking = true; 387 | 388 | if ( config.testTimeout && defined.setTimeout ) { 389 | clearTimeout( config.timeout ); 390 | config.timeout = window.setTimeout(function() { 391 | QUnit.ok( false, "Test timed out" ); 392 | config.semaphore = 1; 393 | QUnit.start(); 394 | }, config.testTimeout ); 395 | } 396 | } 397 | }; 398 | 399 | // Asssert helpers 400 | // All of these must call either QUnit.push() or manually do: 401 | // - runLoggingCallbacks( "log", .. ); 402 | // - config.current.assertions.push({ .. }); 403 | QUnit.assert = { 404 | /** 405 | * Asserts rough true-ish result. 406 | * @name ok 407 | * @function 408 | * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); 409 | */ 410 | ok: function( result, msg ) { 411 | if ( !config.current ) { 412 | throw new Error( "ok() assertion outside test context, was " + sourceFromStacktrace(2) ); 413 | } 414 | result = !!result; 415 | 416 | var source, 417 | details = { 418 | result: result, 419 | message: msg 420 | }; 421 | 422 | msg = escapeInnerText( msg || (result ? "okay" : "failed" ) ); 423 | msg = "" + msg + ""; 424 | 425 | if ( !result ) { 426 | source = sourceFromStacktrace( 2 ); 427 | if ( source ) { 428 | details.source = source; 429 | msg += "
Source:
" + escapeInnerText( source ) + "
"; 430 | } 431 | } 432 | runLoggingCallbacks( "log", QUnit, details ); 433 | config.current.assertions.push({ 434 | result: result, 435 | message: msg 436 | }); 437 | }, 438 | 439 | /** 440 | * Assert that the first two arguments are equal, with an optional message. 441 | * Prints out both actual and expected values. 442 | * @name equal 443 | * @function 444 | * @example equal( format( "Received {0} bytes.", 2), "Received 2 bytes.", "format() replaces {0} with next argument" ); 445 | */ 446 | equal: function( actual, expected, message ) { 447 | QUnit.push( expected == actual, actual, expected, message ); 448 | }, 449 | 450 | /** 451 | * @name notEqual 452 | * @function 453 | */ 454 | notEqual: function( actual, expected, message ) { 455 | QUnit.push( expected != actual, actual, expected, message ); 456 | }, 457 | 458 | /** 459 | * @name deepEqual 460 | * @function 461 | */ 462 | deepEqual: function( actual, expected, message ) { 463 | QUnit.push( QUnit.equiv(actual, expected), actual, expected, message ); 464 | }, 465 | 466 | /** 467 | * @name notDeepEqual 468 | * @function 469 | */ 470 | notDeepEqual: function( actual, expected, message ) { 471 | QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message ); 472 | }, 473 | 474 | /** 475 | * @name strictEqual 476 | * @function 477 | */ 478 | strictEqual: function( actual, expected, message ) { 479 | QUnit.push( expected === actual, actual, expected, message ); 480 | }, 481 | 482 | /** 483 | * @name notStrictEqual 484 | * @function 485 | */ 486 | notStrictEqual: function( actual, expected, message ) { 487 | QUnit.push( expected !== actual, actual, expected, message ); 488 | }, 489 | 490 | throws: function( block, expected, message ) { 491 | var actual, 492 | ok = false; 493 | 494 | // 'expected' is optional 495 | if ( typeof expected === "string" ) { 496 | message = expected; 497 | expected = null; 498 | } 499 | 500 | config.current.ignoreGlobalErrors = true; 501 | try { 502 | block.call( config.current.testEnvironment ); 503 | } catch (e) { 504 | actual = e; 505 | } 506 | config.current.ignoreGlobalErrors = false; 507 | 508 | if ( actual ) { 509 | // we don't want to validate thrown error 510 | if ( !expected ) { 511 | ok = true; 512 | // expected is a regexp 513 | } else if ( QUnit.objectType( expected ) === "regexp" ) { 514 | ok = expected.test( actual ); 515 | // expected is a constructor 516 | } else if ( actual instanceof expected ) { 517 | ok = true; 518 | // expected is a validation function which returns true is validation passed 519 | } else if ( expected.call( {}, actual ) === true ) { 520 | ok = true; 521 | } 522 | 523 | QUnit.push( ok, actual, null, message ); 524 | } else { 525 | QUnit.pushFailure( message, null, 'No exception was thrown.' ); 526 | } 527 | } 528 | }; 529 | 530 | /** 531 | * @deprecate since 1.8.0 532 | * Kept assertion helpers in root for backwards compatibility 533 | */ 534 | extend( QUnit, QUnit.assert ); 535 | 536 | /** 537 | * @deprecated since 1.9.0 538 | * Kept global "raises()" for backwards compatibility 539 | */ 540 | QUnit.raises = QUnit.assert.throws; 541 | 542 | /** 543 | * @deprecated since 1.0.0, replaced with error pushes since 1.3.0 544 | * Kept to avoid TypeErrors for undefined methods. 545 | */ 546 | QUnit.equals = function() { 547 | QUnit.push( false, false, false, "QUnit.equals has been deprecated since 2009 (e88049a0), use QUnit.equal instead" ); 548 | }; 549 | QUnit.same = function() { 550 | QUnit.push( false, false, false, "QUnit.same has been deprecated since 2009 (e88049a0), use QUnit.deepEqual instead" ); 551 | }; 552 | 553 | // We want access to the constructor's prototype 554 | (function() { 555 | function F() {} 556 | F.prototype = QUnit; 557 | QUnit = new F(); 558 | // Make F QUnit's constructor so that we can add to the prototype later 559 | QUnit.constructor = F; 560 | }()); 561 | 562 | /** 563 | * Config object: Maintain internal state 564 | * Later exposed as QUnit.config 565 | * `config` initialized at top of scope 566 | */ 567 | config = { 568 | // The queue of tests to run 569 | queue: [], 570 | 571 | // block until document ready 572 | blocking: true, 573 | 574 | // when enabled, show only failing tests 575 | // gets persisted through sessionStorage and can be changed in UI via checkbox 576 | hidepassed: false, 577 | 578 | // by default, run previously failed tests first 579 | // very useful in combination with "Hide passed tests" checked 580 | reorder: true, 581 | 582 | // by default, modify document.title when suite is done 583 | altertitle: true, 584 | 585 | // when enabled, all tests must call expect() 586 | requireExpects: false, 587 | 588 | // add checkboxes that are persisted in the query-string 589 | // when enabled, the id is set to `true` as a `QUnit.config` property 590 | urlConfig: [ 591 | { 592 | id: "noglobals", 593 | label: "Check for Globals", 594 | tooltip: "Enabling this will test if any test introduces new properties on the `window` object. Stored as query-strings." 595 | }, 596 | { 597 | id: "notrycatch", 598 | label: "No try-catch", 599 | tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging exceptions in IE reasonable. Stored as query-strings." 600 | } 601 | ], 602 | 603 | // logging callback queues 604 | begin: [], 605 | done: [], 606 | log: [], 607 | testStart: [], 608 | testDone: [], 609 | moduleStart: [], 610 | moduleDone: [] 611 | }; 612 | 613 | // Initialize more QUnit.config and QUnit.urlParams 614 | (function() { 615 | var i, 616 | location = window.location || { search: "", protocol: "file:" }, 617 | params = location.search.slice( 1 ).split( "&" ), 618 | length = params.length, 619 | urlParams = {}, 620 | current; 621 | 622 | if ( params[ 0 ] ) { 623 | for ( i = 0; i < length; i++ ) { 624 | current = params[ i ].split( "=" ); 625 | current[ 0 ] = decodeURIComponent( current[ 0 ] ); 626 | // allow just a key to turn on a flag, e.g., test.html?noglobals 627 | current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true; 628 | urlParams[ current[ 0 ] ] = current[ 1 ]; 629 | } 630 | } 631 | 632 | QUnit.urlParams = urlParams; 633 | 634 | // String search anywhere in moduleName+testName 635 | config.filter = urlParams.filter; 636 | 637 | // Exact match of the module name 638 | config.module = urlParams.module; 639 | 640 | config.testNumber = parseInt( urlParams.testNumber, 10 ) || null; 641 | 642 | // Figure out if we're running the tests from a server or not 643 | QUnit.isLocal = location.protocol === "file:"; 644 | }()); 645 | 646 | // Export global variables, unless an 'exports' object exists, 647 | // in that case we assume we're in CommonJS (dealt with on the bottom of the script) 648 | if ( typeof exports === "undefined" ) { 649 | extend( window, QUnit ); 650 | 651 | // Expose QUnit object 652 | window.QUnit = QUnit; 653 | } 654 | 655 | // Extend QUnit object, 656 | // these after set here because they should not be exposed as global functions 657 | extend( QUnit, { 658 | config: config, 659 | 660 | // Initialize the configuration options 661 | init: function() { 662 | extend( config, { 663 | stats: { all: 0, bad: 0 }, 664 | moduleStats: { all: 0, bad: 0 }, 665 | started: +new Date(), 666 | updateRate: 1000, 667 | blocking: false, 668 | autostart: true, 669 | autorun: false, 670 | filter: "", 671 | queue: [], 672 | semaphore: 0 673 | }); 674 | 675 | var tests, banner, result, 676 | qunit = id( "qunit" ); 677 | 678 | if ( qunit ) { 679 | qunit.innerHTML = 680 | "

" + escapeInnerText( document.title ) + "

" + 681 | "

" + 682 | "
" + 683 | "

" + 684 | "
    "; 685 | } 686 | 687 | tests = id( "qunit-tests" ); 688 | banner = id( "qunit-banner" ); 689 | result = id( "qunit-testresult" ); 690 | 691 | if ( tests ) { 692 | tests.innerHTML = ""; 693 | } 694 | 695 | if ( banner ) { 696 | banner.className = ""; 697 | } 698 | 699 | if ( result ) { 700 | result.parentNode.removeChild( result ); 701 | } 702 | 703 | if ( tests ) { 704 | result = document.createElement( "p" ); 705 | result.id = "qunit-testresult"; 706 | result.className = "result"; 707 | tests.parentNode.insertBefore( result, tests ); 708 | result.innerHTML = "Running...
     "; 709 | } 710 | }, 711 | 712 | // Resets the test setup. Useful for tests that modify the DOM. 713 | // If jQuery is available, uses jQuery's html(), otherwise just innerHTML. 714 | reset: function() { 715 | var fixture; 716 | 717 | if ( window.jQuery ) { 718 | jQuery( "#qunit-fixture" ).html( config.fixture ); 719 | } else { 720 | fixture = id( "qunit-fixture" ); 721 | if ( fixture ) { 722 | fixture.innerHTML = config.fixture; 723 | } 724 | } 725 | }, 726 | 727 | // Trigger an event on an element. 728 | // @example triggerEvent( document.body, "click" ); 729 | triggerEvent: function( elem, type, event ) { 730 | if ( document.createEvent ) { 731 | event = document.createEvent( "MouseEvents" ); 732 | event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView, 733 | 0, 0, 0, 0, 0, false, false, false, false, 0, null); 734 | 735 | elem.dispatchEvent( event ); 736 | } else if ( elem.fireEvent ) { 737 | elem.fireEvent( "on" + type ); 738 | } 739 | }, 740 | 741 | // Safe object type checking 742 | is: function( type, obj ) { 743 | return QUnit.objectType( obj ) == type; 744 | }, 745 | 746 | objectType: function( obj ) { 747 | if ( typeof obj === "undefined" ) { 748 | return "undefined"; 749 | // consider: typeof null === object 750 | } 751 | if ( obj === null ) { 752 | return "null"; 753 | } 754 | 755 | var type = toString.call( obj ).match(/^\[object\s(.*)\]$/)[1] || ""; 756 | 757 | switch ( type ) { 758 | case "Number": 759 | if ( isNaN(obj) ) { 760 | return "nan"; 761 | } 762 | return "number"; 763 | case "String": 764 | case "Boolean": 765 | case "Array": 766 | case "Date": 767 | case "RegExp": 768 | case "Function": 769 | return type.toLowerCase(); 770 | } 771 | if ( typeof obj === "object" ) { 772 | return "object"; 773 | } 774 | return undefined; 775 | }, 776 | 777 | push: function( result, actual, expected, message ) { 778 | if ( !config.current ) { 779 | throw new Error( "assertion outside test context, was " + sourceFromStacktrace() ); 780 | } 781 | 782 | var output, source, 783 | details = { 784 | result: result, 785 | message: message, 786 | actual: actual, 787 | expected: expected 788 | }; 789 | 790 | message = escapeInnerText( message ) || ( result ? "okay" : "failed" ); 791 | message = "" + message + ""; 792 | output = message; 793 | 794 | if ( !result ) { 795 | expected = escapeInnerText( QUnit.jsDump.parse(expected) ); 796 | actual = escapeInnerText( QUnit.jsDump.parse(actual) ); 797 | output += ""; 798 | 799 | if ( actual != expected ) { 800 | output += ""; 801 | output += ""; 802 | } 803 | 804 | source = sourceFromStacktrace(); 805 | 806 | if ( source ) { 807 | details.source = source; 808 | output += ""; 809 | } 810 | 811 | output += "
    Expected:
    " + expected + "
    Result:
    " + actual + "
    Diff:
    " + QUnit.diff( expected, actual ) + "
    Source:
    " + escapeInnerText( source ) + "
    "; 812 | } 813 | 814 | runLoggingCallbacks( "log", QUnit, details ); 815 | 816 | config.current.assertions.push({ 817 | result: !!result, 818 | message: output 819 | }); 820 | }, 821 | 822 | pushFailure: function( message, source, actual ) { 823 | if ( !config.current ) { 824 | throw new Error( "pushFailure() assertion outside test context, was " + sourceFromStacktrace(2) ); 825 | } 826 | 827 | var output, 828 | details = { 829 | result: false, 830 | message: message 831 | }; 832 | 833 | message = escapeInnerText( message ) || "error"; 834 | message = "" + message + ""; 835 | output = message; 836 | 837 | output += ""; 838 | 839 | if ( actual ) { 840 | output += ""; 841 | } 842 | 843 | if ( source ) { 844 | details.source = source; 845 | output += ""; 846 | } 847 | 848 | output += "
    Result:
    " + escapeInnerText( actual ) + "
    Source:
    " + escapeInnerText( source ) + "
    "; 849 | 850 | runLoggingCallbacks( "log", QUnit, details ); 851 | 852 | config.current.assertions.push({ 853 | result: false, 854 | message: output 855 | }); 856 | }, 857 | 858 | url: function( params ) { 859 | params = extend( extend( {}, QUnit.urlParams ), params ); 860 | var key, 861 | querystring = "?"; 862 | 863 | for ( key in params ) { 864 | if ( !hasOwn.call( params, key ) ) { 865 | continue; 866 | } 867 | querystring += encodeURIComponent( key ) + "=" + 868 | encodeURIComponent( params[ key ] ) + "&"; 869 | } 870 | return window.location.pathname + querystring.slice( 0, -1 ); 871 | }, 872 | 873 | extend: extend, 874 | id: id, 875 | addEvent: addEvent 876 | // load, equiv, jsDump, diff: Attached later 877 | }); 878 | 879 | /** 880 | * @deprecated: Created for backwards compatibility with test runner that set the hook function 881 | * into QUnit.{hook}, instead of invoking it and passing the hook function. 882 | * QUnit.constructor is set to the empty F() above so that we can add to it's prototype here. 883 | * Doing this allows us to tell if the following methods have been overwritten on the actual 884 | * QUnit object. 885 | */ 886 | extend( QUnit.constructor.prototype, { 887 | 888 | // Logging callbacks; all receive a single argument with the listed properties 889 | // run test/logs.html for any related changes 890 | begin: registerLoggingCallback( "begin" ), 891 | 892 | // done: { failed, passed, total, runtime } 893 | done: registerLoggingCallback( "done" ), 894 | 895 | // log: { result, actual, expected, message } 896 | log: registerLoggingCallback( "log" ), 897 | 898 | // testStart: { name } 899 | testStart: registerLoggingCallback( "testStart" ), 900 | 901 | // testDone: { name, failed, passed, total } 902 | testDone: registerLoggingCallback( "testDone" ), 903 | 904 | // moduleStart: { name } 905 | moduleStart: registerLoggingCallback( "moduleStart" ), 906 | 907 | // moduleDone: { name, failed, passed, total } 908 | moduleDone: registerLoggingCallback( "moduleDone" ) 909 | }); 910 | 911 | if ( typeof document === "undefined" || document.readyState === "complete" ) { 912 | config.autorun = true; 913 | } 914 | 915 | QUnit.load = function() { 916 | runLoggingCallbacks( "begin", QUnit, {} ); 917 | 918 | // Initialize the config, saving the execution queue 919 | var banner, filter, i, label, len, main, ol, toolbar, userAgent, val, urlConfigCheckboxes, 920 | urlConfigHtml = "", 921 | oldconfig = extend( {}, config ); 922 | 923 | QUnit.init(); 924 | extend(config, oldconfig); 925 | 926 | config.blocking = false; 927 | 928 | len = config.urlConfig.length; 929 | 930 | for ( i = 0; i < len; i++ ) { 931 | val = config.urlConfig[i]; 932 | if ( typeof val === "string" ) { 933 | val = { 934 | id: val, 935 | label: val, 936 | tooltip: "[no tooltip available]" 937 | }; 938 | } 939 | config[ val.id ] = QUnit.urlParams[ val.id ]; 940 | urlConfigHtml += ""; 941 | } 942 | 943 | // `userAgent` initialized at top of scope 944 | userAgent = id( "qunit-userAgent" ); 945 | if ( userAgent ) { 946 | userAgent.innerHTML = navigator.userAgent; 947 | } 948 | 949 | // `banner` initialized at top of scope 950 | banner = id( "qunit-header" ); 951 | if ( banner ) { 952 | banner.innerHTML = "" + banner.innerHTML + " "; 953 | } 954 | 955 | // `toolbar` initialized at top of scope 956 | toolbar = id( "qunit-testrunner-toolbar" ); 957 | if ( toolbar ) { 958 | // `filter` initialized at top of scope 959 | filter = document.createElement( "input" ); 960 | filter.type = "checkbox"; 961 | filter.id = "qunit-filter-pass"; 962 | 963 | addEvent( filter, "click", function() { 964 | var tmp, 965 | ol = document.getElementById( "qunit-tests" ); 966 | 967 | if ( filter.checked ) { 968 | ol.className = ol.className + " hidepass"; 969 | } else { 970 | tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " "; 971 | ol.className = tmp.replace( / hidepass /, " " ); 972 | } 973 | if ( defined.sessionStorage ) { 974 | if (filter.checked) { 975 | sessionStorage.setItem( "qunit-filter-passed-tests", "true" ); 976 | } else { 977 | sessionStorage.removeItem( "qunit-filter-passed-tests" ); 978 | } 979 | } 980 | }); 981 | 982 | if ( config.hidepassed || defined.sessionStorage && sessionStorage.getItem( "qunit-filter-passed-tests" ) ) { 983 | filter.checked = true; 984 | // `ol` initialized at top of scope 985 | ol = document.getElementById( "qunit-tests" ); 986 | ol.className = ol.className + " hidepass"; 987 | } 988 | toolbar.appendChild( filter ); 989 | 990 | // `label` initialized at top of scope 991 | label = document.createElement( "label" ); 992 | label.setAttribute( "for", "qunit-filter-pass" ); 993 | label.setAttribute( "title", "Only show tests and assertons that fail. Stored in sessionStorage." ); 994 | label.innerHTML = "Hide passed tests"; 995 | toolbar.appendChild( label ); 996 | 997 | urlConfigCheckboxes = document.createElement( 'span' ); 998 | urlConfigCheckboxes.innerHTML = urlConfigHtml; 999 | addEvent( urlConfigCheckboxes, "change", function( event ) { 1000 | var params = {}; 1001 | params[ event.target.name ] = event.target.checked ? true : undefined; 1002 | window.location = QUnit.url( params ); 1003 | }); 1004 | toolbar.appendChild( urlConfigCheckboxes ); 1005 | } 1006 | 1007 | // `main` initialized at top of scope 1008 | main = id( "qunit-fixture" ); 1009 | if ( main ) { 1010 | config.fixture = main.innerHTML; 1011 | } 1012 | 1013 | if ( config.autostart ) { 1014 | QUnit.start(); 1015 | } 1016 | }; 1017 | 1018 | addEvent( window, "load", QUnit.load ); 1019 | 1020 | // `onErrorFnPrev` initialized at top of scope 1021 | // Preserve other handlers 1022 | onErrorFnPrev = window.onerror; 1023 | 1024 | // Cover uncaught exceptions 1025 | // Returning true will surpress the default browser handler, 1026 | // returning false will let it run. 1027 | window.onerror = function ( error, filePath, linerNr ) { 1028 | var ret = false; 1029 | if ( onErrorFnPrev ) { 1030 | ret = onErrorFnPrev( error, filePath, linerNr ); 1031 | } 1032 | 1033 | // Treat return value as window.onerror itself does, 1034 | // Only do our handling if not surpressed. 1035 | if ( ret !== true ) { 1036 | if ( QUnit.config.current ) { 1037 | if ( QUnit.config.current.ignoreGlobalErrors ) { 1038 | return true; 1039 | } 1040 | QUnit.pushFailure( error, filePath + ":" + linerNr ); 1041 | } else { 1042 | QUnit.test( "global failure", function() { 1043 | QUnit.pushFailure( error, filePath + ":" + linerNr ); 1044 | }); 1045 | } 1046 | return false; 1047 | } 1048 | 1049 | return ret; 1050 | }; 1051 | 1052 | function done() { 1053 | config.autorun = true; 1054 | 1055 | // Log the last module results 1056 | if ( config.currentModule ) { 1057 | runLoggingCallbacks( "moduleDone", QUnit, { 1058 | name: config.currentModule, 1059 | failed: config.moduleStats.bad, 1060 | passed: config.moduleStats.all - config.moduleStats.bad, 1061 | total: config.moduleStats.all 1062 | }); 1063 | } 1064 | 1065 | var i, key, 1066 | banner = id( "qunit-banner" ), 1067 | tests = id( "qunit-tests" ), 1068 | runtime = +new Date() - config.started, 1069 | passed = config.stats.all - config.stats.bad, 1070 | html = [ 1071 | "Tests completed in ", 1072 | runtime, 1073 | " milliseconds.
    ", 1074 | "", 1075 | passed, 1076 | " tests of ", 1077 | config.stats.all, 1078 | " passed, ", 1079 | config.stats.bad, 1080 | " failed." 1081 | ].join( "" ); 1082 | 1083 | if ( banner ) { 1084 | banner.className = ( config.stats.bad ? "qunit-fail" : "qunit-pass" ); 1085 | } 1086 | 1087 | if ( tests ) { 1088 | id( "qunit-testresult" ).innerHTML = html; 1089 | } 1090 | 1091 | if ( config.altertitle && typeof document !== "undefined" && document.title ) { 1092 | // show ✖ for good, ✔ for bad suite result in title 1093 | // use escape sequences in case file gets loaded with non-utf-8-charset 1094 | document.title = [ 1095 | ( config.stats.bad ? "\u2716" : "\u2714" ), 1096 | document.title.replace( /^[\u2714\u2716] /i, "" ) 1097 | ].join( " " ); 1098 | } 1099 | 1100 | // clear own sessionStorage items if all tests passed 1101 | if ( config.reorder && defined.sessionStorage && config.stats.bad === 0 ) { 1102 | // `key` & `i` initialized at top of scope 1103 | for ( i = 0; i < sessionStorage.length; i++ ) { 1104 | key = sessionStorage.key( i++ ); 1105 | if ( key.indexOf( "qunit-test-" ) === 0 ) { 1106 | sessionStorage.removeItem( key ); 1107 | } 1108 | } 1109 | } 1110 | 1111 | runLoggingCallbacks( "done", QUnit, { 1112 | failed: config.stats.bad, 1113 | passed: passed, 1114 | total: config.stats.all, 1115 | runtime: runtime 1116 | }); 1117 | } 1118 | 1119 | /** @return Boolean: true if this test should be ran */ 1120 | function validTest( test ) { 1121 | var include, 1122 | filter = config.filter && config.filter.toLowerCase(), 1123 | module = config.module && config.module.toLowerCase(), 1124 | fullName = (test.module + ": " + test.testName).toLowerCase(); 1125 | 1126 | if ( config.testNumber ) { 1127 | return test.testNumber === config.testNumber; 1128 | } 1129 | 1130 | if ( module && ( !test.module || test.module.toLowerCase() !== module ) ) { 1131 | return false; 1132 | } 1133 | 1134 | if ( !filter ) { 1135 | return true; 1136 | } 1137 | 1138 | include = filter.charAt( 0 ) !== "!"; 1139 | if ( !include ) { 1140 | filter = filter.slice( 1 ); 1141 | } 1142 | 1143 | // If the filter matches, we need to honour include 1144 | if ( fullName.indexOf( filter ) !== -1 ) { 1145 | return include; 1146 | } 1147 | 1148 | // Otherwise, do the opposite 1149 | return !include; 1150 | } 1151 | 1152 | // so far supports only Firefox, Chrome and Opera (buggy), Safari (for real exceptions) 1153 | // Later Safari and IE10 are supposed to support error.stack as well 1154 | // See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack 1155 | function extractStacktrace( e, offset ) { 1156 | offset = offset === undefined ? 3 : offset; 1157 | 1158 | var stack, include, i, regex; 1159 | 1160 | if ( e.stacktrace ) { 1161 | // Opera 1162 | return e.stacktrace.split( "\n" )[ offset + 3 ]; 1163 | } else if ( e.stack ) { 1164 | // Firefox, Chrome 1165 | stack = e.stack.split( "\n" ); 1166 | if (/^error$/i.test( stack[0] ) ) { 1167 | stack.shift(); 1168 | } 1169 | if ( fileName ) { 1170 | include = []; 1171 | for ( i = offset; i < stack.length; i++ ) { 1172 | if ( stack[ i ].indexOf( fileName ) != -1 ) { 1173 | break; 1174 | } 1175 | include.push( stack[ i ] ); 1176 | } 1177 | if ( include.length ) { 1178 | return include.join( "\n" ); 1179 | } 1180 | } 1181 | return stack[ offset ]; 1182 | } else if ( e.sourceURL ) { 1183 | // Safari, PhantomJS 1184 | // hopefully one day Safari provides actual stacktraces 1185 | // exclude useless self-reference for generated Error objects 1186 | if ( /qunit.js$/.test( e.sourceURL ) ) { 1187 | return; 1188 | } 1189 | // for actual exceptions, this is useful 1190 | return e.sourceURL + ":" + e.line; 1191 | } 1192 | } 1193 | function sourceFromStacktrace( offset ) { 1194 | try { 1195 | throw new Error(); 1196 | } catch ( e ) { 1197 | return extractStacktrace( e, offset ); 1198 | } 1199 | } 1200 | 1201 | function escapeInnerText( s ) { 1202 | if ( !s ) { 1203 | return ""; 1204 | } 1205 | s = s + ""; 1206 | return s.replace( /[\&<>]/g, function( s ) { 1207 | switch( s ) { 1208 | case "&": return "&"; 1209 | case "<": return "<"; 1210 | case ">": return ">"; 1211 | default: return s; 1212 | } 1213 | }); 1214 | } 1215 | 1216 | function synchronize( callback, last ) { 1217 | config.queue.push( callback ); 1218 | 1219 | if ( config.autorun && !config.blocking ) { 1220 | process( last ); 1221 | } 1222 | } 1223 | 1224 | function process( last ) { 1225 | function next() { 1226 | process( last ); 1227 | } 1228 | var start = new Date().getTime(); 1229 | config.depth = config.depth ? config.depth + 1 : 1; 1230 | 1231 | while ( config.queue.length && !config.blocking ) { 1232 | if ( !defined.setTimeout || config.updateRate <= 0 || ( ( new Date().getTime() - start ) < config.updateRate ) ) { 1233 | config.queue.shift()(); 1234 | } else { 1235 | window.setTimeout( next, 13 ); 1236 | break; 1237 | } 1238 | } 1239 | config.depth--; 1240 | if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) { 1241 | done(); 1242 | } 1243 | } 1244 | 1245 | function saveGlobal() { 1246 | config.pollution = []; 1247 | 1248 | if ( config.noglobals ) { 1249 | for ( var key in window ) { 1250 | // in Opera sometimes DOM element ids show up here, ignore them 1251 | if ( !hasOwn.call( window, key ) || /^qunit-test-output/.test( key ) ) { 1252 | continue; 1253 | } 1254 | config.pollution.push( key ); 1255 | } 1256 | } 1257 | } 1258 | 1259 | function checkPollution( name ) { 1260 | var newGlobals, 1261 | deletedGlobals, 1262 | old = config.pollution; 1263 | 1264 | saveGlobal(); 1265 | 1266 | newGlobals = diff( config.pollution, old ); 1267 | if ( newGlobals.length > 0 ) { 1268 | QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join(", ") ); 1269 | } 1270 | 1271 | deletedGlobals = diff( old, config.pollution ); 1272 | if ( deletedGlobals.length > 0 ) { 1273 | QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join(", ") ); 1274 | } 1275 | } 1276 | 1277 | // returns a new Array with the elements that are in a but not in b 1278 | function diff( a, b ) { 1279 | var i, j, 1280 | result = a.slice(); 1281 | 1282 | for ( i = 0; i < result.length; i++ ) { 1283 | for ( j = 0; j < b.length; j++ ) { 1284 | if ( result[i] === b[j] ) { 1285 | result.splice( i, 1 ); 1286 | i--; 1287 | break; 1288 | } 1289 | } 1290 | } 1291 | return result; 1292 | } 1293 | 1294 | function extend( a, b ) { 1295 | for ( var prop in b ) { 1296 | if ( b[ prop ] === undefined ) { 1297 | delete a[ prop ]; 1298 | 1299 | // Avoid "Member not found" error in IE8 caused by setting window.constructor 1300 | } else if ( prop !== "constructor" || a !== window ) { 1301 | a[ prop ] = b[ prop ]; 1302 | } 1303 | } 1304 | 1305 | return a; 1306 | } 1307 | 1308 | function addEvent( elem, type, fn ) { 1309 | if ( elem.addEventListener ) { 1310 | elem.addEventListener( type, fn, false ); 1311 | } else if ( elem.attachEvent ) { 1312 | elem.attachEvent( "on" + type, fn ); 1313 | } else { 1314 | fn(); 1315 | } 1316 | } 1317 | 1318 | function id( name ) { 1319 | return !!( typeof document !== "undefined" && document && document.getElementById ) && 1320 | document.getElementById( name ); 1321 | } 1322 | 1323 | function registerLoggingCallback( key ) { 1324 | return function( callback ) { 1325 | config[key].push( callback ); 1326 | }; 1327 | } 1328 | 1329 | // Supports deprecated method of completely overwriting logging callbacks 1330 | function runLoggingCallbacks( key, scope, args ) { 1331 | //debugger; 1332 | var i, callbacks; 1333 | if ( QUnit.hasOwnProperty( key ) ) { 1334 | QUnit[ key ].call(scope, args ); 1335 | } else { 1336 | callbacks = config[ key ]; 1337 | for ( i = 0; i < callbacks.length; i++ ) { 1338 | callbacks[ i ].call( scope, args ); 1339 | } 1340 | } 1341 | } 1342 | 1343 | // Test for equality any JavaScript type. 1344 | // Author: Philippe Rathé 1345 | QUnit.equiv = (function() { 1346 | 1347 | // Call the o related callback with the given arguments. 1348 | function bindCallbacks( o, callbacks, args ) { 1349 | var prop = QUnit.objectType( o ); 1350 | if ( prop ) { 1351 | if ( QUnit.objectType( callbacks[ prop ] ) === "function" ) { 1352 | return callbacks[ prop ].apply( callbacks, args ); 1353 | } else { 1354 | return callbacks[ prop ]; // or undefined 1355 | } 1356 | } 1357 | } 1358 | 1359 | // the real equiv function 1360 | var innerEquiv, 1361 | // stack to decide between skip/abort functions 1362 | callers = [], 1363 | // stack to avoiding loops from circular referencing 1364 | parents = [], 1365 | 1366 | getProto = Object.getPrototypeOf || function ( obj ) { 1367 | return obj.__proto__; 1368 | }, 1369 | callbacks = (function () { 1370 | 1371 | // for string, boolean, number and null 1372 | function useStrictEquality( b, a ) { 1373 | if ( b instanceof a.constructor || a instanceof b.constructor ) { 1374 | // to catch short annotaion VS 'new' annotation of a 1375 | // declaration 1376 | // e.g. var i = 1; 1377 | // var j = new Number(1); 1378 | return a == b; 1379 | } else { 1380 | return a === b; 1381 | } 1382 | } 1383 | 1384 | return { 1385 | "string": useStrictEquality, 1386 | "boolean": useStrictEquality, 1387 | "number": useStrictEquality, 1388 | "null": useStrictEquality, 1389 | "undefined": useStrictEquality, 1390 | 1391 | "nan": function( b ) { 1392 | return isNaN( b ); 1393 | }, 1394 | 1395 | "date": function( b, a ) { 1396 | return QUnit.objectType( b ) === "date" && a.valueOf() === b.valueOf(); 1397 | }, 1398 | 1399 | "regexp": function( b, a ) { 1400 | return QUnit.objectType( b ) === "regexp" && 1401 | // the regex itself 1402 | a.source === b.source && 1403 | // and its modifers 1404 | a.global === b.global && 1405 | // (gmi) ... 1406 | a.ignoreCase === b.ignoreCase && 1407 | a.multiline === b.multiline; 1408 | }, 1409 | 1410 | // - skip when the property is a method of an instance (OOP) 1411 | // - abort otherwise, 1412 | // initial === would have catch identical references anyway 1413 | "function": function() { 1414 | var caller = callers[callers.length - 1]; 1415 | return caller !== Object && typeof caller !== "undefined"; 1416 | }, 1417 | 1418 | "array": function( b, a ) { 1419 | var i, j, len, loop; 1420 | 1421 | // b could be an object literal here 1422 | if ( QUnit.objectType( b ) !== "array" ) { 1423 | return false; 1424 | } 1425 | 1426 | len = a.length; 1427 | if ( len !== b.length ) { 1428 | // safe and faster 1429 | return false; 1430 | } 1431 | 1432 | // track reference to avoid circular references 1433 | parents.push( a ); 1434 | for ( i = 0; i < len; i++ ) { 1435 | loop = false; 1436 | for ( j = 0; j < parents.length; j++ ) { 1437 | if ( parents[j] === a[i] ) { 1438 | loop = true;// dont rewalk array 1439 | } 1440 | } 1441 | if ( !loop && !innerEquiv(a[i], b[i]) ) { 1442 | parents.pop(); 1443 | return false; 1444 | } 1445 | } 1446 | parents.pop(); 1447 | return true; 1448 | }, 1449 | 1450 | "object": function( b, a ) { 1451 | var i, j, loop, 1452 | // Default to true 1453 | eq = true, 1454 | aProperties = [], 1455 | bProperties = []; 1456 | 1457 | // comparing constructors is more strict than using 1458 | // instanceof 1459 | if ( a.constructor !== b.constructor ) { 1460 | // Allow objects with no prototype to be equivalent to 1461 | // objects with Object as their constructor. 1462 | if ( !(( getProto(a) === null && getProto(b) === Object.prototype ) || 1463 | ( getProto(b) === null && getProto(a) === Object.prototype ) ) ) { 1464 | return false; 1465 | } 1466 | } 1467 | 1468 | // stack constructor before traversing properties 1469 | callers.push( a.constructor ); 1470 | // track reference to avoid circular references 1471 | parents.push( a ); 1472 | 1473 | for ( i in a ) { // be strict: don't ensures hasOwnProperty 1474 | // and go deep 1475 | loop = false; 1476 | for ( j = 0; j < parents.length; j++ ) { 1477 | if ( parents[j] === a[i] ) { 1478 | // don't go down the same path twice 1479 | loop = true; 1480 | } 1481 | } 1482 | aProperties.push(i); // collect a's properties 1483 | 1484 | if (!loop && !innerEquiv( a[i], b[i] ) ) { 1485 | eq = false; 1486 | break; 1487 | } 1488 | } 1489 | 1490 | callers.pop(); // unstack, we are done 1491 | parents.pop(); 1492 | 1493 | for ( i in b ) { 1494 | bProperties.push( i ); // collect b's properties 1495 | } 1496 | 1497 | // Ensures identical properties name 1498 | return eq && innerEquiv( aProperties.sort(), bProperties.sort() ); 1499 | } 1500 | }; 1501 | }()); 1502 | 1503 | innerEquiv = function() { // can take multiple arguments 1504 | var args = [].slice.apply( arguments ); 1505 | if ( args.length < 2 ) { 1506 | return true; // end transition 1507 | } 1508 | 1509 | return (function( a, b ) { 1510 | if ( a === b ) { 1511 | return true; // catch the most you can 1512 | } else if ( a === null || b === null || typeof a === "undefined" || 1513 | typeof b === "undefined" || 1514 | QUnit.objectType(a) !== QUnit.objectType(b) ) { 1515 | return false; // don't lose time with error prone cases 1516 | } else { 1517 | return bindCallbacks(a, callbacks, [ b, a ]); 1518 | } 1519 | 1520 | // apply transition with (1..n) arguments 1521 | }( args[0], args[1] ) && arguments.callee.apply( this, args.splice(1, args.length - 1 )) ); 1522 | }; 1523 | 1524 | return innerEquiv; 1525 | }()); 1526 | 1527 | /** 1528 | * jsDump Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | 1529 | * http://flesler.blogspot.com Licensed under BSD 1530 | * (http://www.opensource.org/licenses/bsd-license.php) Date: 5/15/2008 1531 | * 1532 | * @projectDescription Advanced and extensible data dumping for Javascript. 1533 | * @version 1.0.0 1534 | * @author Ariel Flesler 1535 | * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html} 1536 | */ 1537 | QUnit.jsDump = (function() { 1538 | function quote( str ) { 1539 | return '"' + str.toString().replace( /"/g, '\\"' ) + '"'; 1540 | } 1541 | function literal( o ) { 1542 | return o + ""; 1543 | } 1544 | function join( pre, arr, post ) { 1545 | var s = jsDump.separator(), 1546 | base = jsDump.indent(), 1547 | inner = jsDump.indent(1); 1548 | if ( arr.join ) { 1549 | arr = arr.join( "," + s + inner ); 1550 | } 1551 | if ( !arr ) { 1552 | return pre + post; 1553 | } 1554 | return [ pre, inner + arr, base + post ].join(s); 1555 | } 1556 | function array( arr, stack ) { 1557 | var i = arr.length, ret = new Array(i); 1558 | this.up(); 1559 | while ( i-- ) { 1560 | ret[i] = this.parse( arr[i] , undefined , stack); 1561 | } 1562 | this.down(); 1563 | return join( "[", ret, "]" ); 1564 | } 1565 | 1566 | var reName = /^function (\w+)/, 1567 | jsDump = { 1568 | parse: function( obj, type, stack ) { //type is used mostly internally, you can fix a (custom)type in advance 1569 | stack = stack || [ ]; 1570 | var inStack, res, 1571 | parser = this.parsers[ type || this.typeOf(obj) ]; 1572 | 1573 | type = typeof parser; 1574 | inStack = inArray( obj, stack ); 1575 | 1576 | if ( inStack != -1 ) { 1577 | return "recursion(" + (inStack - stack.length) + ")"; 1578 | } 1579 | //else 1580 | if ( type == "function" ) { 1581 | stack.push( obj ); 1582 | res = parser.call( this, obj, stack ); 1583 | stack.pop(); 1584 | return res; 1585 | } 1586 | // else 1587 | return ( type == "string" ) ? parser : this.parsers.error; 1588 | }, 1589 | typeOf: function( obj ) { 1590 | var type; 1591 | if ( obj === null ) { 1592 | type = "null"; 1593 | } else if ( typeof obj === "undefined" ) { 1594 | type = "undefined"; 1595 | } else if ( QUnit.is( "regexp", obj) ) { 1596 | type = "regexp"; 1597 | } else if ( QUnit.is( "date", obj) ) { 1598 | type = "date"; 1599 | } else if ( QUnit.is( "function", obj) ) { 1600 | type = "function"; 1601 | } else if ( typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined" ) { 1602 | type = "window"; 1603 | } else if ( obj.nodeType === 9 ) { 1604 | type = "document"; 1605 | } else if ( obj.nodeType ) { 1606 | type = "node"; 1607 | } else if ( 1608 | // native arrays 1609 | toString.call( obj ) === "[object Array]" || 1610 | // NodeList objects 1611 | ( typeof obj.length === "number" && typeof obj.item !== "undefined" && ( obj.length ? obj.item(0) === obj[0] : ( obj.item( 0 ) === null && typeof obj[0] === "undefined" ) ) ) 1612 | ) { 1613 | type = "array"; 1614 | } else { 1615 | type = typeof obj; 1616 | } 1617 | return type; 1618 | }, 1619 | separator: function() { 1620 | return this.multiline ? this.HTML ? "
    " : "\n" : this.HTML ? " " : " "; 1621 | }, 1622 | indent: function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing 1623 | if ( !this.multiline ) { 1624 | return ""; 1625 | } 1626 | var chr = this.indentChar; 1627 | if ( this.HTML ) { 1628 | chr = chr.replace( /\t/g, " " ).replace( / /g, " " ); 1629 | } 1630 | return new Array( this._depth_ + (extra||0) ).join(chr); 1631 | }, 1632 | up: function( a ) { 1633 | this._depth_ += a || 1; 1634 | }, 1635 | down: function( a ) { 1636 | this._depth_ -= a || 1; 1637 | }, 1638 | setParser: function( name, parser ) { 1639 | this.parsers[name] = parser; 1640 | }, 1641 | // The next 3 are exposed so you can use them 1642 | quote: quote, 1643 | literal: literal, 1644 | join: join, 1645 | // 1646 | _depth_: 1, 1647 | // This is the list of parsers, to modify them, use jsDump.setParser 1648 | parsers: { 1649 | window: "[Window]", 1650 | document: "[Document]", 1651 | error: "[ERROR]", //when no parser is found, shouldn"t happen 1652 | unknown: "[Unknown]", 1653 | "null": "null", 1654 | "undefined": "undefined", 1655 | "function": function( fn ) { 1656 | var ret = "function", 1657 | name = "name" in fn ? fn.name : (reName.exec(fn) || [])[1];//functions never have name in IE 1658 | 1659 | if ( name ) { 1660 | ret += " " + name; 1661 | } 1662 | ret += "( "; 1663 | 1664 | ret = [ ret, QUnit.jsDump.parse( fn, "functionArgs" ), "){" ].join( "" ); 1665 | return join( ret, QUnit.jsDump.parse(fn,"functionCode" ), "}" ); 1666 | }, 1667 | array: array, 1668 | nodelist: array, 1669 | "arguments": array, 1670 | object: function( map, stack ) { 1671 | var ret = [ ], keys, key, val, i; 1672 | QUnit.jsDump.up(); 1673 | if ( Object.keys ) { 1674 | keys = Object.keys( map ); 1675 | } else { 1676 | keys = []; 1677 | for ( key in map ) { 1678 | keys.push( key ); 1679 | } 1680 | } 1681 | keys.sort(); 1682 | for ( i = 0; i < keys.length; i++ ) { 1683 | key = keys[ i ]; 1684 | val = map[ key ]; 1685 | ret.push( QUnit.jsDump.parse( key, "key" ) + ": " + QUnit.jsDump.parse( val, undefined, stack ) ); 1686 | } 1687 | QUnit.jsDump.down(); 1688 | return join( "{", ret, "}" ); 1689 | }, 1690 | node: function( node ) { 1691 | var a, val, 1692 | open = QUnit.jsDump.HTML ? "<" : "<", 1693 | close = QUnit.jsDump.HTML ? ">" : ">", 1694 | tag = node.nodeName.toLowerCase(), 1695 | ret = open + tag; 1696 | 1697 | for ( a in QUnit.jsDump.DOMAttrs ) { 1698 | val = node[ QUnit.jsDump.DOMAttrs[a] ]; 1699 | if ( val ) { 1700 | ret += " " + a + "=" + QUnit.jsDump.parse( val, "attribute" ); 1701 | } 1702 | } 1703 | return ret + close + open + "/" + tag + close; 1704 | }, 1705 | functionArgs: function( fn ) {//function calls it internally, it's the arguments part of the function 1706 | var args, 1707 | l = fn.length; 1708 | 1709 | if ( !l ) { 1710 | return ""; 1711 | } 1712 | 1713 | args = new Array(l); 1714 | while ( l-- ) { 1715 | args[l] = String.fromCharCode(97+l);//97 is 'a' 1716 | } 1717 | return " " + args.join( ", " ) + " "; 1718 | }, 1719 | key: quote, //object calls it internally, the key part of an item in a map 1720 | functionCode: "[code]", //function calls it internally, it's the content of the function 1721 | attribute: quote, //node calls it internally, it's an html attribute value 1722 | string: quote, 1723 | date: quote, 1724 | regexp: literal, //regex 1725 | number: literal, 1726 | "boolean": literal 1727 | }, 1728 | DOMAttrs: { 1729 | //attributes to dump from nodes, name=>realName 1730 | id: "id", 1731 | name: "name", 1732 | "class": "className" 1733 | }, 1734 | HTML: false,//if true, entities are escaped ( <, >, \t, space and \n ) 1735 | indentChar: " ",//indentation unit 1736 | multiline: true //if true, items in a collection, are separated by a \n, else just a space. 1737 | }; 1738 | 1739 | return jsDump; 1740 | }()); 1741 | 1742 | // from Sizzle.js 1743 | function getText( elems ) { 1744 | var i, elem, 1745 | ret = ""; 1746 | 1747 | for ( i = 0; elems[i]; i++ ) { 1748 | elem = elems[i]; 1749 | 1750 | // Get the text from text nodes and CDATA nodes 1751 | if ( elem.nodeType === 3 || elem.nodeType === 4 ) { 1752 | ret += elem.nodeValue; 1753 | 1754 | // Traverse everything else, except comment nodes 1755 | } else if ( elem.nodeType !== 8 ) { 1756 | ret += getText( elem.childNodes ); 1757 | } 1758 | } 1759 | 1760 | return ret; 1761 | } 1762 | 1763 | // from jquery.js 1764 | function inArray( elem, array ) { 1765 | if ( array.indexOf ) { 1766 | return array.indexOf( elem ); 1767 | } 1768 | 1769 | for ( var i = 0, length = array.length; i < length; i++ ) { 1770 | if ( array[ i ] === elem ) { 1771 | return i; 1772 | } 1773 | } 1774 | 1775 | return -1; 1776 | } 1777 | 1778 | /* 1779 | * Javascript Diff Algorithm 1780 | * By John Resig (http://ejohn.org/) 1781 | * Modified by Chu Alan "sprite" 1782 | * 1783 | * Released under the MIT license. 1784 | * 1785 | * More Info: 1786 | * http://ejohn.org/projects/javascript-diff-algorithm/ 1787 | * 1788 | * Usage: QUnit.diff(expected, actual) 1789 | * 1790 | * QUnit.diff( "the quick brown fox jumped over", "the quick fox jumps over" ) == "the quick brown fox jumped jumps over" 1791 | */ 1792 | QUnit.diff = (function() { 1793 | function diff( o, n ) { 1794 | var i, 1795 | ns = {}, 1796 | os = {}; 1797 | 1798 | for ( i = 0; i < n.length; i++ ) { 1799 | if ( ns[ n[i] ] == null ) { 1800 | ns[ n[i] ] = { 1801 | rows: [], 1802 | o: null 1803 | }; 1804 | } 1805 | ns[ n[i] ].rows.push( i ); 1806 | } 1807 | 1808 | for ( i = 0; i < o.length; i++ ) { 1809 | if ( os[ o[i] ] == null ) { 1810 | os[ o[i] ] = { 1811 | rows: [], 1812 | n: null 1813 | }; 1814 | } 1815 | os[ o[i] ].rows.push( i ); 1816 | } 1817 | 1818 | for ( i in ns ) { 1819 | if ( !hasOwn.call( ns, i ) ) { 1820 | continue; 1821 | } 1822 | if ( ns[i].rows.length == 1 && typeof os[i] != "undefined" && os[i].rows.length == 1 ) { 1823 | n[ ns[i].rows[0] ] = { 1824 | text: n[ ns[i].rows[0] ], 1825 | row: os[i].rows[0] 1826 | }; 1827 | o[ os[i].rows[0] ] = { 1828 | text: o[ os[i].rows[0] ], 1829 | row: ns[i].rows[0] 1830 | }; 1831 | } 1832 | } 1833 | 1834 | for ( i = 0; i < n.length - 1; i++ ) { 1835 | if ( n[i].text != null && n[ i + 1 ].text == null && n[i].row + 1 < o.length && o[ n[i].row + 1 ].text == null && 1836 | n[ i + 1 ] == o[ n[i].row + 1 ] ) { 1837 | 1838 | n[ i + 1 ] = { 1839 | text: n[ i + 1 ], 1840 | row: n[i].row + 1 1841 | }; 1842 | o[ n[i].row + 1 ] = { 1843 | text: o[ n[i].row + 1 ], 1844 | row: i + 1 1845 | }; 1846 | } 1847 | } 1848 | 1849 | for ( i = n.length - 1; i > 0; i-- ) { 1850 | if ( n[i].text != null && n[ i - 1 ].text == null && n[i].row > 0 && o[ n[i].row - 1 ].text == null && 1851 | n[ i - 1 ] == o[ n[i].row - 1 ]) { 1852 | 1853 | n[ i - 1 ] = { 1854 | text: n[ i - 1 ], 1855 | row: n[i].row - 1 1856 | }; 1857 | o[ n[i].row - 1 ] = { 1858 | text: o[ n[i].row - 1 ], 1859 | row: i - 1 1860 | }; 1861 | } 1862 | } 1863 | 1864 | return { 1865 | o: o, 1866 | n: n 1867 | }; 1868 | } 1869 | 1870 | return function( o, n ) { 1871 | o = o.replace( /\s+$/, "" ); 1872 | n = n.replace( /\s+$/, "" ); 1873 | 1874 | var i, pre, 1875 | str = "", 1876 | out = diff( o === "" ? [] : o.split(/\s+/), n === "" ? [] : n.split(/\s+/) ), 1877 | oSpace = o.match(/\s+/g), 1878 | nSpace = n.match(/\s+/g); 1879 | 1880 | if ( oSpace == null ) { 1881 | oSpace = [ " " ]; 1882 | } 1883 | else { 1884 | oSpace.push( " " ); 1885 | } 1886 | 1887 | if ( nSpace == null ) { 1888 | nSpace = [ " " ]; 1889 | } 1890 | else { 1891 | nSpace.push( " " ); 1892 | } 1893 | 1894 | if ( out.n.length === 0 ) { 1895 | for ( i = 0; i < out.o.length; i++ ) { 1896 | str += "" + out.o[i] + oSpace[i] + ""; 1897 | } 1898 | } 1899 | else { 1900 | if ( out.n[0].text == null ) { 1901 | for ( n = 0; n < out.o.length && out.o[n].text == null; n++ ) { 1902 | str += "" + out.o[n] + oSpace[n] + ""; 1903 | } 1904 | } 1905 | 1906 | for ( i = 0; i < out.n.length; i++ ) { 1907 | if (out.n[i].text == null) { 1908 | str += "" + out.n[i] + nSpace[i] + ""; 1909 | } 1910 | else { 1911 | // `pre` initialized at top of scope 1912 | pre = ""; 1913 | 1914 | for ( n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++ ) { 1915 | pre += "" + out.o[n] + oSpace[n] + ""; 1916 | } 1917 | str += " " + out.n[i].text + nSpace[i] + pre; 1918 | } 1919 | } 1920 | } 1921 | 1922 | return str; 1923 | }; 1924 | }()); 1925 | 1926 | // for CommonJS enviroments, export everything 1927 | if ( typeof exports !== "undefined" ) { 1928 | extend(exports, QUnit); 1929 | } 1930 | 1931 | // get at whatever the global object is, like window in browsers 1932 | }( (function() {return this;}.call()) )); --------------------------------------------------------------------------------