├── .gitignore ├── README.md ├── jquery.$$.min.js ├── package.json ├── src └── jquery.$$.js └── test ├── index.html ├── qunit ├── qunit.css └── qunit.js └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [jQuery Selector Cache](https://raw.github.com/farzher/jQuery-Selector-Cache/master/jquery.$$.min.js) 2 | 3 | Cache your selectors, without messy code. 4 | 5 | 6 | 7 | 8 | 9 | 10 | ## Installation 11 | 12 | This library is so tiny, just copy paste its whole source after your copy of jQuery (its faster than the browser downloading another file). 13 | 14 | ```html 15 | 16 | 17 | ``` 18 | 19 | 20 | 21 | 22 | 23 | 24 | ## Usage 25 | 26 | ```js 27 | // Instead of 28 | $('div') 29 | 30 | // Use 31 | $$('div') 32 | ``` 33 | 34 | The next time you call `$$('div')` it will be instantly fetched from the cache. 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | ## Caching? Why should I care 43 | 44 | jQuery itself does no caching. The DOM is slow. You shouldn't look for something that you've found before. Using this code might give you free performance. 45 | 46 | ```js 47 | $('.thing') // Slow 48 | $('.thing') // Just as slow 49 | 50 | $$('.thing') // Slow 51 | $$('.thing') // Instant 52 | ``` 53 | 54 | 55 | 56 | ## Benchmarks https://jsperf.com/jquery-selector-cache-benchmark/1 57 | 58 | Over 100% faster than jQuery with no cache. 59 | 60 | 61 | 62 | 63 | 64 | 65 | ## Documentation 66 | 67 | - `$$('div')` The next time you call `$$('div')` it will be fetched from the cache. 68 | - `$$clear('div')` Invalidates the cache. The next time you call `$$('div')` It will return fresh results. 69 | - `$$fresh('div')` Shortcut for `$$clear('div')` `$$('div')` 70 | 71 | ### Advanced usage 72 | - `$$('div', '#context')` Find within a context 73 | - `$$clear('div', '#context')` Invalidates query on the context 74 | - `$$clear('#context')` Invalidates the cache, and all queries on the context 75 | - `$$clear()` Invalidates all of the cache 76 | - `$$fresh('div', '#context')` Shortcut for `$$clear('div', '#context')` `$$('div', '#context')` 77 | - OOP syntax `$$('p').$$('a')` `$$('p').$$clear('a')` `$$('p').$$fresh('a')` 78 | 79 | 80 | 81 | 82 | 83 | 84 | ## Alternatives 85 | 86 | [jQache](https://github.com/danwit/jQache) is more popular, and does more. Although, if this works for you and you're hardcore about performance, this plugin is less heavy (because it's more simple). 87 | 88 | Or just don't use a library: `let $mybutton = $('#mybutton'); $mybutton.click(); $mybutton.hide()` 89 | -------------------------------------------------------------------------------- /jquery.$$.min.js: -------------------------------------------------------------------------------- 1 | !function($,n){var r,e,t={},c={};$$=function(f,u){return u?((r=u.selector)&&(u=r),e=c[u],e===n&&(e=c[u]={}),r=e[f],r!==n?r:e[f]=$(f,$$(u))):(r=t[f],r!==n?r:t[f]=$(f))},$$clear=function($,e){e?((r=e.selector)&&(e=r),$&&(r=c[e])&&(r[$]=n),c[e]=n):$?(t[$]=n,c[$]=n):(t={},c={})},$$fresh=function($,n){return $$clear($,n),$$($,n)},$.fn.$$=function($){return $$($,this)},$.fn.$$clear=function($){$$clear($,this)},$.fn.$$fresh=function($){return $$fresh($,this)}}(jQuery); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jQuery-Selector-Cache", 3 | "version": "2.0.0", 4 | "description": "Cache your selectors, without messy code.", 5 | "main": "jquery.$$.min.js", 6 | "directories": { 7 | "example": "examples", 8 | "test": "test" 9 | }, 10 | "scripts": { 11 | "test": "echo \"Error: no test specified\" && exit 1", 12 | "build": "uglifyjs src/jquery.$$.js -m -c -o jquery.$$.min.js" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "https://farzher@github.com/farzher/jQuery-Selector-Cache.git" 17 | }, 18 | "author": "Stephen Kamenar", 19 | "license": "BSD-2-Clause", 20 | "bugs": { 21 | "url": "https://github.com/farzher/jQuery-Selector-Cache/issues" 22 | }, 23 | "devDependencies": { 24 | "eslint": "^3.2.2", 25 | "eslint-config-airbnb": "^10.0.0", 26 | "eslint-plugin-import": "^1.12.0", 27 | "eslint-plugin-jsx-a11y": "^2.0.1", 28 | "eslint-plugin-react": "^6.0.0", 29 | "uglify-js": "^2.7.0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/jquery.$$.js: -------------------------------------------------------------------------------- 1 | /* 2 | * $$ Selector Cache 3 | * Cache your selectors, without messy code. 4 | * @author Stephen Kamenar 5 | */ 6 | (function ($, undefined) { 7 | // '#a': $('#a') 8 | var cache = {} 9 | 10 | // '#context': (a cache object for the element) 11 | var cacheByContext = {} 12 | 13 | // Here for performance/minification 14 | var tmp, tmp2 15 | 16 | $$ = function(selector, context) { 17 | if(context) { 18 | if(tmp=context.selector) context = tmp 19 | 20 | // tmp2 is contextCache 21 | tmp2 = cacheByContext[context] 22 | if(tmp2 === undefined) { 23 | tmp2 = cacheByContext[context] = {} 24 | } 25 | 26 | tmp = tmp2[selector] 27 | if(tmp !== undefined) return tmp 28 | 29 | return tmp2[selector] = $(selector, $$(context)) 30 | } 31 | 32 | tmp = cache[selector] 33 | if(tmp !== undefined) return tmp 34 | 35 | return cache[selector] = $(selector) 36 | } 37 | 38 | $$clear = function(selector, context) { 39 | if(context) { 40 | if(tmp=context.selector) context = tmp 41 | 42 | if(selector) { 43 | if(tmp = cacheByContext[context]) tmp[selector] = undefined 44 | } 45 | cacheByContext[context] = undefined 46 | } else { 47 | if(selector) { 48 | cache[selector] = undefined 49 | cacheByContext[selector] = undefined 50 | } else { 51 | cache = {} 52 | cacheByContext = {} 53 | } 54 | } 55 | } 56 | 57 | $$fresh = function(selector, context) { 58 | $$clear(selector, context) 59 | return $$(selector, context) 60 | } 61 | 62 | $.fn.$$ = function(selector) { 63 | return $$(selector, this) 64 | } 65 | $.fn.$$clear = function(selector) { 66 | $$clear(selector, this) 67 | } 68 | $.fn.$$fresh = function(selector) { 69 | return $$fresh(selector, this) 70 | } 71 | })(jQuery) 72 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | QUnit Test Suite 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 |
test markup
16 | 17 | 18 | -------------------------------------------------------------------------------- /test/qunit/qunit.css: -------------------------------------------------------------------------------- 1 | /** 2 | * QUnit v1.11.0pre - A JavaScript Unit Testing Framework 3 | * 4 | * http://qunitjs.com 5 | * 6 | * Copyright 2012 jQuery Foundation and other contributors 7 | * Released under the MIT license. 8 | * http://jquery.org/license 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-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter { 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 | overflow: hidden; 71 | } 72 | 73 | #qunit-userAgent { 74 | padding: 0.5em 0 0.5em 2.5em; 75 | background-color: #2b81af; 76 | color: #fff; 77 | text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; 78 | } 79 | 80 | #qunit-modulefilter-container { 81 | float: right; 82 | } 83 | 84 | /** Tests: Pass/Fail */ 85 | 86 | #qunit-tests { 87 | list-style-position: inside; 88 | } 89 | 90 | #qunit-tests li { 91 | padding: 0.4em 0.5em 0.4em 2.5em; 92 | border-bottom: 1px solid #fff; 93 | list-style-position: inside; 94 | } 95 | 96 | #qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running { 97 | display: none; 98 | } 99 | 100 | #qunit-tests li strong { 101 | cursor: pointer; 102 | } 103 | 104 | #qunit-tests li a { 105 | padding: 0.5em; 106 | color: #c2ccd1; 107 | text-decoration: none; 108 | } 109 | #qunit-tests li a:hover, 110 | #qunit-tests li a:focus { 111 | color: #000; 112 | } 113 | 114 | .qunit-assert-list { 115 | margin-top: 0.5em; 116 | padding: 0.5em; 117 | 118 | background-color: #fff; 119 | 120 | border-radius: 5px; 121 | -moz-border-radius: 5px; 122 | -webkit-border-radius: 5px; 123 | } 124 | 125 | .qunit-collapsed { 126 | display: none; 127 | } 128 | 129 | #qunit-tests table { 130 | border-collapse: collapse; 131 | margin-top: .2em; 132 | } 133 | 134 | #qunit-tests th { 135 | text-align: right; 136 | vertical-align: top; 137 | padding: 0 .5em 0 0; 138 | } 139 | 140 | #qunit-tests td { 141 | vertical-align: top; 142 | } 143 | 144 | #qunit-tests pre { 145 | margin: 0; 146 | white-space: pre-wrap; 147 | word-wrap: break-word; 148 | } 149 | 150 | #qunit-tests del { 151 | background-color: #e0f2be; 152 | color: #374e0c; 153 | text-decoration: none; 154 | } 155 | 156 | #qunit-tests ins { 157 | background-color: #ffcaca; 158 | color: #500; 159 | text-decoration: none; 160 | } 161 | 162 | /*** Test Counts */ 163 | 164 | #qunit-tests b.counts { color: black; } 165 | #qunit-tests b.passed { color: #5E740B; } 166 | #qunit-tests b.failed { color: #710909; } 167 | 168 | #qunit-tests li li { 169 | padding: 5px; 170 | background-color: #fff; 171 | border-bottom: none; 172 | list-style-position: inside; 173 | } 174 | 175 | /*** Passing Styles */ 176 | 177 | #qunit-tests li li.pass { 178 | color: #3c510c; 179 | background-color: #fff; 180 | border-left: 10px solid #C6E746; 181 | } 182 | 183 | #qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } 184 | #qunit-tests .pass .test-name { color: #366097; } 185 | 186 | #qunit-tests .pass .test-actual, 187 | #qunit-tests .pass .test-expected { color: #999999; } 188 | 189 | #qunit-banner.qunit-pass { background-color: #C6E746; } 190 | 191 | /*** Failing Styles */ 192 | 193 | #qunit-tests li li.fail { 194 | color: #710909; 195 | background-color: #fff; 196 | border-left: 10px solid #EE5757; 197 | white-space: pre; 198 | } 199 | 200 | #qunit-tests > li:last-child { 201 | border-radius: 0 0 5px 5px; 202 | -moz-border-radius: 0 0 5px 5px; 203 | -webkit-border-bottom-right-radius: 5px; 204 | -webkit-border-bottom-left-radius: 5px; 205 | } 206 | 207 | #qunit-tests .fail { color: #000000; background-color: #EE5757; } 208 | #qunit-tests .fail .test-name, 209 | #qunit-tests .fail .module-name { color: #000000; } 210 | 211 | #qunit-tests .fail .test-actual { color: #EE5757; } 212 | #qunit-tests .fail .test-expected { color: green; } 213 | 214 | #qunit-banner.qunit-fail { background-color: #EE5757; } 215 | 216 | 217 | /** Result */ 218 | 219 | #qunit-testresult { 220 | padding: 0.5em 0.5em 0.5em 2.5em; 221 | 222 | color: #2b81af; 223 | background-color: #D2E0E6; 224 | 225 | border-bottom: 1px solid white; 226 | } 227 | #qunit-testresult .module-name { 228 | font-weight: bold; 229 | } 230 | 231 | /** Fixture */ 232 | 233 | #qunit-fixture { 234 | position: absolute; 235 | top: -10000px; 236 | left: -10000px; 237 | width: 1000px; 238 | height: 1000px; 239 | } 240 | -------------------------------------------------------------------------------- /test/qunit/qunit.js: -------------------------------------------------------------------------------- 1 | /** 2 | * QUnit v1.11.0pre - A JavaScript Unit Testing Framework 3 | * 4 | * http://qunitjs.com 5 | * 6 | * Copyright 2012 jQuery Foundation and other contributors 7 | * Released under the MIT license. 8 | * http://jquery.org/license 9 | */ 10 | 11 | (function( window ) { 12 | 13 | var QUnit, 14 | assert, 15 | config, 16 | onErrorFnPrev, 17 | testId = 0, 18 | fileName = (sourceFromStacktrace( 0 ) || "" ).replace(/(:\d+)+\)?/, "").replace(/.+\//, ""), 19 | toString = Object.prototype.toString, 20 | hasOwn = Object.prototype.hasOwnProperty, 21 | // Keep a local reference to Date (GH-283) 22 | Date = window.Date, 23 | defined = { 24 | setTimeout: typeof window.setTimeout !== "undefined", 25 | sessionStorage: (function() { 26 | var x = "qunit-test-string"; 27 | try { 28 | sessionStorage.setItem( x, x ); 29 | sessionStorage.removeItem( x ); 30 | return true; 31 | } catch( e ) { 32 | return false; 33 | } 34 | }()) 35 | }; 36 | 37 | function Test( settings ) { 38 | extend( this, settings ); 39 | this.assertions = []; 40 | this.testNumber = ++Test.count; 41 | } 42 | 43 | Test.count = 0; 44 | 45 | Test.prototype = { 46 | init: function() { 47 | var a, b, li, 48 | tests = id( "qunit-tests" ); 49 | 50 | if ( tests ) { 51 | b = document.createElement( "strong" ); 52 | b.innerHTML = this.name; 53 | 54 | // `a` initialized at top of scope 55 | a = document.createElement( "a" ); 56 | a.innerHTML = "Rerun"; 57 | a.href = QUnit.url({ testNumber: this.testNumber }); 58 | 59 | li = document.createElement( "li" ); 60 | li.appendChild( b ); 61 | li.appendChild( a ); 62 | li.className = "running"; 63 | li.id = this.id = "qunit-test-output" + testId++; 64 | 65 | tests.appendChild( li ); 66 | } 67 | }, 68 | setup: function() { 69 | if ( this.module !== config.previousModule ) { 70 | if ( config.previousModule ) { 71 | runLoggingCallbacks( "moduleDone", QUnit, { 72 | name: config.previousModule, 73 | failed: config.moduleStats.bad, 74 | passed: config.moduleStats.all - config.moduleStats.bad, 75 | total: config.moduleStats.all 76 | }); 77 | } 78 | config.previousModule = this.module; 79 | config.moduleStats = { all: 0, bad: 0 }; 80 | runLoggingCallbacks( "moduleStart", QUnit, { 81 | name: this.module 82 | }); 83 | } else if ( config.autorun ) { 84 | runLoggingCallbacks( "moduleStart", QUnit, { 85 | name: this.module 86 | }); 87 | } 88 | 89 | config.current = this; 90 | 91 | this.testEnvironment = extend({ 92 | setup: function() {}, 93 | teardown: function() {} 94 | }, this.moduleTestEnvironment ); 95 | 96 | runLoggingCallbacks( "testStart", QUnit, { 97 | name: this.testName, 98 | module: this.module 99 | }); 100 | 101 | // allow utility functions to access the current test environment 102 | // TODO why?? 103 | QUnit.current_testEnvironment = this.testEnvironment; 104 | 105 | if ( !config.pollution ) { 106 | saveGlobal(); 107 | } 108 | if ( config.notrycatch ) { 109 | this.testEnvironment.setup.call( this.testEnvironment ); 110 | return; 111 | } 112 | try { 113 | this.testEnvironment.setup.call( this.testEnvironment ); 114 | } catch( e ) { 115 | QUnit.pushFailure( "Setup failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 1 ) ); 116 | } 117 | }, 118 | run: function() { 119 | config.current = this; 120 | 121 | var running = id( "qunit-testresult" ); 122 | 123 | if ( running ) { 124 | running.innerHTML = "Running:
" + this.name; 125 | } 126 | 127 | if ( this.async ) { 128 | QUnit.stop(); 129 | } 130 | 131 | if ( config.notrycatch ) { 132 | this.callback.call( this.testEnvironment, QUnit.assert ); 133 | return; 134 | } 135 | 136 | try { 137 | this.callback.call( this.testEnvironment, QUnit.assert ); 138 | } catch( e ) { 139 | QUnit.pushFailure( "Died on test #" + (this.assertions.length + 1) + " " + this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) ); 140 | // else next test will carry the responsibility 141 | saveGlobal(); 142 | 143 | // Restart the tests if they're blocking 144 | if ( config.blocking ) { 145 | QUnit.start(); 146 | } 147 | } 148 | }, 149 | teardown: function() { 150 | config.current = this; 151 | if ( config.notrycatch ) { 152 | this.testEnvironment.teardown.call( this.testEnvironment ); 153 | return; 154 | } else { 155 | try { 156 | this.testEnvironment.teardown.call( this.testEnvironment ); 157 | } catch( e ) { 158 | QUnit.pushFailure( "Teardown failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 1 ) ); 159 | } 160 | } 161 | checkPollution(); 162 | }, 163 | finish: function() { 164 | config.current = this; 165 | if ( config.requireExpects && this.expected === null ) { 166 | QUnit.pushFailure( "Expected number of assertions to be defined, but expect() was not called.", this.stack ); 167 | } else if ( this.expected !== null && this.expected !== this.assertions.length ) { 168 | QUnit.pushFailure( "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run", this.stack ); 169 | } else if ( this.expected === null && !this.assertions.length ) { 170 | QUnit.pushFailure( "Expected at least one assertion, but none were run - call expect(0) to accept zero assertions.", this.stack ); 171 | } 172 | 173 | var assertion, a, b, i, li, ol, 174 | test = this, 175 | good = 0, 176 | bad = 0, 177 | tests = id( "qunit-tests" ); 178 | 179 | config.stats.all += this.assertions.length; 180 | config.moduleStats.all += this.assertions.length; 181 | 182 | if ( tests ) { 183 | ol = document.createElement( "ol" ); 184 | ol.className = "qunit-assert-list"; 185 | 186 | for ( i = 0; i < this.assertions.length; i++ ) { 187 | assertion = this.assertions[i]; 188 | 189 | li = document.createElement( "li" ); 190 | li.className = assertion.result ? "pass" : "fail"; 191 | li.innerHTML = assertion.message || ( assertion.result ? "okay" : "failed" ); 192 | ol.appendChild( li ); 193 | 194 | if ( assertion.result ) { 195 | good++; 196 | } else { 197 | bad++; 198 | config.stats.bad++; 199 | config.moduleStats.bad++; 200 | } 201 | } 202 | 203 | // store result when possible 204 | if ( QUnit.config.reorder && defined.sessionStorage ) { 205 | if ( bad ) { 206 | sessionStorage.setItem( "qunit-test-" + this.module + "-" + this.testName, bad ); 207 | } else { 208 | sessionStorage.removeItem( "qunit-test-" + this.module + "-" + this.testName ); 209 | } 210 | } 211 | 212 | if ( bad === 0 ) { 213 | addClass( ol, "qunit-collapsed" ); 214 | } 215 | 216 | // `b` initialized at top of scope 217 | b = document.createElement( "strong" ); 218 | b.innerHTML = this.name + " (" + bad + ", " + good + ", " + this.assertions.length + ")"; 219 | 220 | addEvent(b, "click", function() { 221 | var next = b.nextSibling.nextSibling, 222 | collapsed = hasClass( next, "qunit-collapsed" ); 223 | ( collapsed ? removeClass : addClass )( next, "qunit-collapsed" ); 224 | }); 225 | 226 | addEvent(b, "dblclick", function( e ) { 227 | var target = e && e.target ? e.target : window.event.srcElement; 228 | if ( target.nodeName.toLowerCase() === "span" || target.nodeName.toLowerCase() === "b" ) { 229 | target = target.parentNode; 230 | } 231 | if ( window.location && target.nodeName.toLowerCase() === "strong" ) { 232 | window.location = QUnit.url({ testNumber: test.testNumber }); 233 | } 234 | }); 235 | 236 | // `li` initialized at top of scope 237 | li = id( this.id ); 238 | li.className = bad ? "fail" : "pass"; 239 | li.removeChild( li.firstChild ); 240 | a = li.firstChild; 241 | li.appendChild( b ); 242 | li.appendChild ( a ); 243 | li.appendChild( ol ); 244 | 245 | } else { 246 | for ( i = 0; i < this.assertions.length; i++ ) { 247 | if ( !this.assertions[i].result ) { 248 | bad++; 249 | config.stats.bad++; 250 | config.moduleStats.bad++; 251 | } 252 | } 253 | } 254 | 255 | runLoggingCallbacks( "testDone", QUnit, { 256 | name: this.testName, 257 | module: this.module, 258 | failed: bad, 259 | passed: this.assertions.length - bad, 260 | total: this.assertions.length 261 | }); 262 | 263 | QUnit.reset(); 264 | 265 | config.current = undefined; 266 | }, 267 | 268 | queue: function() { 269 | var bad, 270 | test = this; 271 | 272 | synchronize(function() { 273 | test.init(); 274 | }); 275 | function run() { 276 | // each of these can by async 277 | synchronize(function() { 278 | test.setup(); 279 | }); 280 | synchronize(function() { 281 | test.run(); 282 | }); 283 | synchronize(function() { 284 | test.teardown(); 285 | }); 286 | synchronize(function() { 287 | test.finish(); 288 | }); 289 | } 290 | 291 | // `bad` initialized at top of scope 292 | // defer when previous test run passed, if storage is available 293 | bad = QUnit.config.reorder && defined.sessionStorage && 294 | +sessionStorage.getItem( "qunit-test-" + this.module + "-" + this.testName ); 295 | 296 | if ( bad ) { 297 | run(); 298 | } else { 299 | synchronize( run, true ); 300 | } 301 | } 302 | }; 303 | 304 | // Root QUnit object. 305 | // `QUnit` initialized at top of scope 306 | QUnit = { 307 | 308 | // call on start of module test to prepend name to all tests 309 | module: function( name, testEnvironment ) { 310 | config.currentModule = name; 311 | config.currentModuleTestEnvironment = testEnvironment; 312 | config.modules[name] = true; 313 | }, 314 | 315 | asyncTest: function( testName, expected, callback ) { 316 | if ( arguments.length === 2 ) { 317 | callback = expected; 318 | expected = null; 319 | } 320 | 321 | QUnit.test( testName, expected, callback, true ); 322 | }, 323 | 324 | test: function( testName, expected, callback, async ) { 325 | var test, 326 | name = "" + escapeInnerText( testName ) + ""; 327 | 328 | if ( arguments.length === 2 ) { 329 | callback = expected; 330 | expected = null; 331 | } 332 | 333 | if ( config.currentModule ) { 334 | name = "" + config.currentModule + ": " + name; 335 | } 336 | 337 | test = new Test({ 338 | name: name, 339 | testName: testName, 340 | expected: expected, 341 | async: async, 342 | callback: callback, 343 | module: config.currentModule, 344 | moduleTestEnvironment: config.currentModuleTestEnvironment, 345 | stack: sourceFromStacktrace( 2 ) 346 | }); 347 | 348 | if ( !validTest( test ) ) { 349 | return; 350 | } 351 | 352 | test.queue(); 353 | }, 354 | 355 | // Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through. 356 | expect: function( asserts ) { 357 | if (arguments.length === 1) { 358 | config.current.expected = asserts; 359 | } else { 360 | return config.current.expected; 361 | } 362 | }, 363 | 364 | start: function( count ) { 365 | config.semaphore -= count || 1; 366 | // don't start until equal number of stop-calls 367 | if ( config.semaphore > 0 ) { 368 | return; 369 | } 370 | // ignore if start is called more often then stop 371 | if ( config.semaphore < 0 ) { 372 | config.semaphore = 0; 373 | QUnit.pushFailure( "Called start() while already started (QUnit.config.semaphore was 0 already)", null, sourceFromStacktrace(2) ); 374 | return; 375 | } 376 | // A slight delay, to avoid any current callbacks 377 | if ( defined.setTimeout ) { 378 | window.setTimeout(function() { 379 | if ( config.semaphore > 0 ) { 380 | return; 381 | } 382 | if ( config.timeout ) { 383 | clearTimeout( config.timeout ); 384 | } 385 | 386 | config.blocking = false; 387 | process( true ); 388 | }, 13); 389 | } else { 390 | config.blocking = false; 391 | process( true ); 392 | } 393 | }, 394 | 395 | stop: function( count ) { 396 | config.semaphore += count || 1; 397 | config.blocking = true; 398 | 399 | if ( config.testTimeout && defined.setTimeout ) { 400 | clearTimeout( config.timeout ); 401 | config.timeout = window.setTimeout(function() { 402 | QUnit.ok( false, "Test timed out" ); 403 | config.semaphore = 1; 404 | QUnit.start(); 405 | }, config.testTimeout ); 406 | } 407 | } 408 | }; 409 | 410 | // `assert` initialized at top of scope 411 | // Asssert helpers 412 | // All of these must either call QUnit.push() or manually do: 413 | // - runLoggingCallbacks( "log", .. ); 414 | // - config.current.assertions.push({ .. }); 415 | // We attach it to the QUnit object *after* we expose the public API, 416 | // otherwise `assert` will become a global variable in browsers (#341). 417 | assert = { 418 | /** 419 | * Asserts rough true-ish result. 420 | * @name ok 421 | * @function 422 | * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); 423 | */ 424 | ok: function( result, msg ) { 425 | if ( !config.current ) { 426 | throw new Error( "ok() assertion outside test context, was " + sourceFromStacktrace(2) ); 427 | } 428 | result = !!result; 429 | 430 | var source, 431 | details = { 432 | module: config.current.module, 433 | name: config.current.testName, 434 | result: result, 435 | message: msg 436 | }; 437 | 438 | msg = escapeInnerText( msg || (result ? "okay" : "failed" ) ); 439 | msg = "" + msg + ""; 440 | 441 | if ( !result ) { 442 | source = sourceFromStacktrace( 2 ); 443 | if ( source ) { 444 | details.source = source; 445 | msg += "
Source:
" + escapeInnerText( source ) + "
"; 446 | } 447 | } 448 | runLoggingCallbacks( "log", QUnit, details ); 449 | config.current.assertions.push({ 450 | result: result, 451 | message: msg 452 | }); 453 | }, 454 | 455 | /** 456 | * Assert that the first two arguments are equal, with an optional message. 457 | * Prints out both actual and expected values. 458 | * @name equal 459 | * @function 460 | * @example equal( format( "Received {0} bytes.", 2), "Received 2 bytes.", "format() replaces {0} with next argument" ); 461 | */ 462 | equal: function( actual, expected, message ) { 463 | /*jshint eqeqeq:false */ 464 | QUnit.push( expected == actual, actual, expected, message ); 465 | }, 466 | 467 | /** 468 | * @name notEqual 469 | * @function 470 | */ 471 | notEqual: function( actual, expected, message ) { 472 | /*jshint eqeqeq:false */ 473 | QUnit.push( expected != actual, actual, expected, message ); 474 | }, 475 | 476 | /** 477 | * @name deepEqual 478 | * @function 479 | */ 480 | deepEqual: function( actual, expected, message ) { 481 | QUnit.push( QUnit.equiv(actual, expected), actual, expected, message ); 482 | }, 483 | 484 | /** 485 | * @name notDeepEqual 486 | * @function 487 | */ 488 | notDeepEqual: function( actual, expected, message ) { 489 | QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message ); 490 | }, 491 | 492 | /** 493 | * @name strictEqual 494 | * @function 495 | */ 496 | strictEqual: function( actual, expected, message ) { 497 | QUnit.push( expected === actual, actual, expected, message ); 498 | }, 499 | 500 | /** 501 | * @name notStrictEqual 502 | * @function 503 | */ 504 | notStrictEqual: function( actual, expected, message ) { 505 | QUnit.push( expected !== actual, actual, expected, message ); 506 | }, 507 | 508 | "throws": function( block, expected, message ) { 509 | var actual, 510 | expectedOutput = expected, 511 | ok = false; 512 | 513 | // 'expected' is optional 514 | if ( typeof expected === "string" ) { 515 | message = expected; 516 | expected = null; 517 | } 518 | 519 | config.current.ignoreGlobalErrors = true; 520 | try { 521 | block.call( config.current.testEnvironment ); 522 | } catch (e) { 523 | actual = e; 524 | } 525 | config.current.ignoreGlobalErrors = false; 526 | 527 | if ( actual ) { 528 | // we don't want to validate thrown error 529 | if ( !expected ) { 530 | ok = true; 531 | expectedOutput = null; 532 | // expected is a regexp 533 | } else if ( QUnit.objectType( expected ) === "regexp" ) { 534 | ok = expected.test( actual ); 535 | // expected is a constructor 536 | } else if ( actual instanceof expected ) { 537 | ok = true; 538 | // expected is a validation function which returns true is validation passed 539 | } else if ( expected.call( {}, actual ) === true ) { 540 | expectedOutput = null; 541 | ok = true; 542 | } 543 | 544 | QUnit.push( ok, actual, expectedOutput, message ); 545 | } else { 546 | QUnit.pushFailure( message, null, 'No exception was thrown.' ); 547 | } 548 | } 549 | }; 550 | 551 | /** 552 | * @deprecate since 1.8.0 553 | * Kept assertion helpers in root for backwards compatibility. 554 | */ 555 | extend( QUnit, assert ); 556 | 557 | /** 558 | * @deprecated since 1.9.0 559 | * Kept root "raises()" for backwards compatibility. 560 | * (Note that we don't introduce assert.raises). 561 | */ 562 | QUnit.raises = assert[ "throws" ]; 563 | 564 | /** 565 | * @deprecated since 1.0.0, replaced with error pushes since 1.3.0 566 | * Kept to avoid TypeErrors for undefined methods. 567 | */ 568 | QUnit.equals = function() { 569 | QUnit.push( false, false, false, "QUnit.equals has been deprecated since 2009 (e88049a0), use QUnit.equal instead" ); 570 | }; 571 | QUnit.same = function() { 572 | QUnit.push( false, false, false, "QUnit.same has been deprecated since 2009 (e88049a0), use QUnit.deepEqual instead" ); 573 | }; 574 | 575 | // We want access to the constructor's prototype 576 | (function() { 577 | function F() {} 578 | F.prototype = QUnit; 579 | QUnit = new F(); 580 | // Make F QUnit's constructor so that we can add to the prototype later 581 | QUnit.constructor = F; 582 | }()); 583 | 584 | /** 585 | * Config object: Maintain internal state 586 | * Later exposed as QUnit.config 587 | * `config` initialized at top of scope 588 | */ 589 | config = { 590 | // The queue of tests to run 591 | queue: [], 592 | 593 | // block until document ready 594 | blocking: true, 595 | 596 | // when enabled, show only failing tests 597 | // gets persisted through sessionStorage and can be changed in UI via checkbox 598 | hidepassed: false, 599 | 600 | // by default, run previously failed tests first 601 | // very useful in combination with "Hide passed tests" checked 602 | reorder: true, 603 | 604 | // by default, modify document.title when suite is done 605 | altertitle: true, 606 | 607 | // when enabled, all tests must call expect() 608 | requireExpects: false, 609 | 610 | // add checkboxes that are persisted in the query-string 611 | // when enabled, the id is set to `true` as a `QUnit.config` property 612 | urlConfig: [ 613 | { 614 | id: "noglobals", 615 | label: "Check for Globals", 616 | tooltip: "Enabling this will test if any test introduces new properties on the `window` object. Stored as query-strings." 617 | }, 618 | { 619 | id: "notrycatch", 620 | label: "No try-catch", 621 | tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging exceptions in IE reasonable. Stored as query-strings." 622 | } 623 | ], 624 | 625 | // Set of all modules. 626 | modules: {}, 627 | 628 | // logging callback queues 629 | begin: [], 630 | done: [], 631 | log: [], 632 | testStart: [], 633 | testDone: [], 634 | moduleStart: [], 635 | moduleDone: [] 636 | }; 637 | 638 | // Initialize more QUnit.config and QUnit.urlParams 639 | (function() { 640 | var i, 641 | location = window.location || { search: "", protocol: "file:" }, 642 | params = location.search.slice( 1 ).split( "&" ), 643 | length = params.length, 644 | urlParams = {}, 645 | current; 646 | 647 | if ( params[ 0 ] ) { 648 | for ( i = 0; i < length; i++ ) { 649 | current = params[ i ].split( "=" ); 650 | current[ 0 ] = decodeURIComponent( current[ 0 ] ); 651 | // allow just a key to turn on a flag, e.g., test.html?noglobals 652 | current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true; 653 | urlParams[ current[ 0 ] ] = current[ 1 ]; 654 | } 655 | } 656 | 657 | QUnit.urlParams = urlParams; 658 | 659 | // String search anywhere in moduleName+testName 660 | config.filter = urlParams.filter; 661 | 662 | // Exact match of the module name 663 | config.module = urlParams.module; 664 | 665 | config.testNumber = parseInt( urlParams.testNumber, 10 ) || null; 666 | 667 | // Figure out if we're running the tests from a server or not 668 | QUnit.isLocal = location.protocol === "file:"; 669 | }()); 670 | 671 | // Export global variables, unless an 'exports' object exists, 672 | // in that case we assume we're in CommonJS (dealt with on the bottom of the script) 673 | if ( typeof exports === "undefined" ) { 674 | extend( window, QUnit ); 675 | 676 | // Expose QUnit object 677 | window.QUnit = QUnit; 678 | } 679 | 680 | // Extend QUnit object, 681 | // these after set here because they should not be exposed as global functions 682 | extend( QUnit, { 683 | assert: assert, 684 | 685 | config: config, 686 | 687 | // Initialize the configuration options 688 | init: function() { 689 | extend( config, { 690 | stats: { all: 0, bad: 0 }, 691 | moduleStats: { all: 0, bad: 0 }, 692 | started: +new Date(), 693 | updateRate: 1000, 694 | blocking: false, 695 | autostart: true, 696 | autorun: false, 697 | filter: "", 698 | queue: [], 699 | semaphore: 1 700 | }); 701 | 702 | var tests, banner, result, 703 | qunit = id( "qunit" ); 704 | 705 | if ( qunit ) { 706 | qunit.innerHTML = 707 | "

" + escapeInnerText( document.title ) + "

" + 708 | "

" + 709 | "
" + 710 | "

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

    ') 34 | deepEqual($$('#testContext p').length, 1) 35 | deepEqual($$('p', '#testContext').length, 1) 36 | $$('#tmp').append('

    ') 37 | $$clear('#testContext') 38 | deepEqual($$('#testContext p').length, 1, 'wasn\'t cleared') 39 | deepEqual($$('p', '#testContext').length, 2) 40 | deepEqual($$('#testContext').$$('p').length, 2) 41 | $$('#testContext #tmp').remove() 42 | }) 43 | 44 | test('fresh', function() { 45 | deepEqual($$('#testContext div').length, 1, 'There should still be 1 div in cache') 46 | deepEqual($$('div', '#testContext').length, 1, 'There should still be 1 div in cache') 47 | $$('#testContext div').remove() 48 | deepEqual($$('#testContext div').length, 1, 'div has been removed, but it should still be in cache') 49 | deepEqual($$fresh('#testContext div').length, 0, 'Doing a fresh call should find nothing') 50 | deepEqual($$('div', '#testContext').length, 1, 'div has been removed, but it should still be in cache') 51 | deepEqual($$fresh('div', '#testContext').length, 0, 'Doing a fresh call should find nothing') 52 | }) 53 | }) 54 | --------------------------------------------------------------------------------