├── client
├── deadbolts.js
├── index.html
├── deadbolts_test.js
└── qunit
│ ├── qunit.css
│ └── qunit.js
├── LICENSE
├── README.md
└── src
└── deadbolts.js
/client/deadbolts.js:
--------------------------------------------------------------------------------
1 | ../src/deadbolts.js
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | This Source Code Form is subject to the terms of the Mozilla Public
2 | License, v. 2.0. If a copy of the MPL was not distributed with this
3 | file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 |
--------------------------------------------------------------------------------
/client/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Test!
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
Deadbolts Test Suite
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Deadbolts
2 | A deadbolt is kind of like a promise, useful in asynchronous programming where several actions that could be running concurrently must complete before a single action is called.
3 |
4 | The inspiration for the name came from remembering cheesy 70s movies where there are people in apartments in NYC with 4 or 5 locks on their doors, each of them needed to be unlocked before the door can be opened.
5 |
6 | ## Purpose
7 | Useful any time several actions must complete before another action can be called. An example of this is when several concurrent AJAX calls, each fetching a different set of data, must complete before a set of results can be computed from the returned data.
8 |
9 | ## Example - AJAX requests.
10 |
11 | ```
12 | var labels, issues;
13 | var computeResults = function() {
14 | // compute result set with both issues and labels.
15 | };
16 |
17 | var deadbolts = new Deadbolts(computeResults);
18 | $.get("/api/issues", deadbolts.bolt(function(data) {
19 | issues = data;
20 | }));
21 |
22 | $.get("/api/labels", deadbolts.bolt(function(data) {
23 | labels = data;
24 | }));
25 | ```
26 |
27 | ## License:
28 | Mozilla MPL 2.0
29 |
30 | ## Author
31 | * Shane Tomlinson
32 | * @shane_tomlinson
33 | * set117@yahoo.com
34 | * stomlinson@mozilla.com
35 | * http://shanetomlinson.com
36 |
37 |
--------------------------------------------------------------------------------
/src/deadbolts.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This Source Code Form is subject to the terms of the Mozilla Public
3 | * License, v. 2.0. If a copy of the MPL was not distributed with this
4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 | */
6 | window.Deadbolts = (function() {
7 | "use strict";
8 |
9 | function Deadbolts(complete) {
10 | this.complete = complete;
11 | this.locked_count_bolts = {};
12 | this.curr_id = 0;
13 | this.locked_count = 0;
14 | }
15 | Deadbolts.prototype.bolt = function(onunlock) {
16 | var self = this,
17 | id = self.curr_id,
18 | locked_bolts = self.locked_count_bolts;
19 |
20 | // Each deadbolt has an id.
21 | self.curr_id++;
22 |
23 | // Keep track of the number of bolts that are locked.
24 | self.locked_count++;
25 |
26 | // The current bolt is locked.
27 | locked_bolts[id] = true;
28 |
29 | return function() {
30 | // If the bolt being opened is locked, open it.
31 | if(locked_bolts[id]) {
32 | // Cannot re-open an open bolt.
33 | delete locked_bolts[id];
34 | self.locked_count--;
35 |
36 | onunlock && onunlock.apply(null, arguments);
37 |
38 | // If there are no more bolts to unlock, complete
39 | if(!self.locked_count) {
40 | self.complete();
41 | }
42 | }
43 | }
44 |
45 | };
46 |
47 |
48 | return Deadbolts;
49 | }());
50 |
--------------------------------------------------------------------------------
/client/deadbolts_test.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | "use strict";
3 |
4 | var Deadbolts = window.Deadbolts;
5 |
6 | module("Deadbolts");
7 |
8 | asyncTest("a single bolt - completes after first bolt unlocks", function() {
9 | function complete() {
10 | ok(true, "single bolt unlocked");
11 | start();
12 | }
13 |
14 | var deadbolts = new Deadbolts(complete);
15 |
16 | // create an unlock.
17 | var unlock = deadbolts.bolt();
18 |
19 | // unlock the bolt
20 | unlock();
21 | });
22 |
23 | asyncTest("two deadbolts - completes after second bolt unlocks", function() {
24 | function complete() {
25 | ok(true, "two deadbolts unlocked");
26 | start();
27 | }
28 |
29 | var deadbolts = new Deadbolts(complete);
30 |
31 | var unlock1 = deadbolts.bolt();
32 | var unlock2 = deadbolts.bolt();
33 |
34 | // Can unlock in any order
35 | unlock2();
36 | unlock1();
37 | });
38 |
39 | asyncTest("two deadbolts - completes after second bolt, two calls to one bolt has not effect", function() {
40 | var count = 0;
41 | function complete() {
42 | count++;
43 | equal(count, 1, "complete only called once");
44 | start();
45 | }
46 |
47 | var deadbolts = new Deadbolts(complete);
48 |
49 | var unlock1 = deadbolts.bolt();
50 | var unlock2 = deadbolts.bolt();
51 |
52 | // unlocking a single bolt twice has no effect.
53 | unlock1();
54 | unlock1();
55 |
56 | // second bolt must be unlocked.
57 | unlock2();
58 | unlock2();
59 | });
60 |
61 | asyncTest("a single bolt with unlock callback - call the unlock callback before complete", function() {
62 | var completeCalled = false, onunlockCalled = false;
63 |
64 | function complete() {
65 | completeCalled = true;
66 | start();
67 | }
68 |
69 | var deadbolts = new Deadbolts(complete);
70 |
71 | // create a bolt
72 | var unlock = deadbolts.bolt(function() {
73 | equal(completeCalled, false, "complete not yet called");
74 | onunlockCalled = true;
75 | });
76 |
77 | // unlock the bolt
78 | unlock();
79 | equal(onunlockCalled, true, "bolt was called");
80 | equal(completeCalled, true, "complete was called");
81 | });
82 |
83 |
84 | }());
85 |
86 |
--------------------------------------------------------------------------------
/client/qunit/qunit.css:
--------------------------------------------------------------------------------
1 | /**
2 | * QUnit - A JavaScript Unit Testing Framework
3 | *
4 | * http://docs.jquery.com/QUnit
5 | *
6 | * Copyright (c) 2011 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: 15px 15px 0 0;
42 | -moz-border-radius: 15px 15px 0 0;
43 | -webkit-border-top-right-radius: 15px;
44 | -webkit-border-top-left-radius: 15px;
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-banner {
58 | height: 5px;
59 | }
60 |
61 | #qunit-testrunner-toolbar {
62 | padding: 0.5em 0 0.5em 2em;
63 | color: #5E740B;
64 | background-color: #eee;
65 | }
66 |
67 | #qunit-userAgent {
68 | padding: 0.5em 0 0.5em 2.5em;
69 | background-color: #2b81af;
70 | color: #fff;
71 | text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
72 | }
73 |
74 |
75 | /** Tests: Pass/Fail */
76 |
77 | #qunit-tests {
78 | list-style-position: inside;
79 | }
80 |
81 | #qunit-tests li {
82 | padding: 0.4em 0.5em 0.4em 2.5em;
83 | border-bottom: 1px solid #fff;
84 | list-style-position: inside;
85 | }
86 |
87 | #qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running {
88 | display: none;
89 | }
90 |
91 | #qunit-tests li strong {
92 | cursor: pointer;
93 | }
94 |
95 | #qunit-tests li a {
96 | padding: 0.5em;
97 | color: #c2ccd1;
98 | text-decoration: none;
99 | }
100 | #qunit-tests li a:hover,
101 | #qunit-tests li a:focus {
102 | color: #000;
103 | }
104 |
105 | #qunit-tests ol {
106 | margin-top: 0.5em;
107 | padding: 0.5em;
108 |
109 | background-color: #fff;
110 |
111 | border-radius: 15px;
112 | -moz-border-radius: 15px;
113 | -webkit-border-radius: 15px;
114 |
115 | box-shadow: inset 0px 2px 13px #999;
116 | -moz-box-shadow: inset 0px 2px 13px #999;
117 | -webkit-box-shadow: inset 0px 2px 13px #999;
118 | }
119 |
120 | #qunit-tests table {
121 | border-collapse: collapse;
122 | margin-top: .2em;
123 | }
124 |
125 | #qunit-tests th {
126 | text-align: right;
127 | vertical-align: top;
128 | padding: 0 .5em 0 0;
129 | }
130 |
131 | #qunit-tests td {
132 | vertical-align: top;
133 | }
134 |
135 | #qunit-tests pre {
136 | margin: 0;
137 | white-space: pre-wrap;
138 | word-wrap: break-word;
139 | }
140 |
141 | #qunit-tests del {
142 | background-color: #e0f2be;
143 | color: #374e0c;
144 | text-decoration: none;
145 | }
146 |
147 | #qunit-tests ins {
148 | background-color: #ffcaca;
149 | color: #500;
150 | text-decoration: none;
151 | }
152 |
153 | /*** Test Counts */
154 |
155 | #qunit-tests b.counts { color: black; }
156 | #qunit-tests b.passed { color: #5E740B; }
157 | #qunit-tests b.failed { color: #710909; }
158 |
159 | #qunit-tests li li {
160 | margin: 0.5em;
161 | padding: 0.4em 0.5em 0.4em 0.5em;
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: #5E740B;
171 | background-color: #fff;
172 | border-left: 26px 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: 26px solid #EE5757;
189 | }
190 |
191 | #qunit-tests > li:last-child {
192 | border-radius: 0 0 15px 15px;
193 | -moz-border-radius: 0 0 15px 15px;
194 | -webkit-border-bottom-right-radius: 15px;
195 | -webkit-border-bottom-left-radius: 15px;
196 | }
197 |
198 | #qunit-tests .fail { color: #000000; background-color: #EE5757; }
199 | #qunit-tests .fail .test-name,
200 | #qunit-tests .fail .module-name { color: #000000; }
201 |
202 | #qunit-tests .fail .test-actual { color: #EE5757; }
203 | #qunit-tests .fail .test-expected { color: green; }
204 |
205 | #qunit-banner.qunit-fail { background-color: #EE5757; }
206 |
207 |
208 | /** Result */
209 |
210 | #qunit-testresult {
211 | padding: 0.5em 0.5em 0.5em 2.5em;
212 |
213 | color: #2b81af;
214 | background-color: #D2E0E6;
215 |
216 | border-bottom: 1px solid white;
217 | }
218 |
219 | /** Fixture */
220 |
221 | #qunit-fixture {
222 | position: absolute;
223 | top: -10000px;
224 | left: -10000px;
225 | }
226 |
--------------------------------------------------------------------------------
/client/qunit/qunit.js:
--------------------------------------------------------------------------------
1 | /**
2 | * QUnit - A JavaScript Unit Testing Framework
3 | *
4 | * http://docs.jquery.com/QUnit
5 | *
6 | * Copyright (c) 2011 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 defined = {
14 | setTimeout: typeof window.setTimeout !== "undefined",
15 | sessionStorage: (function() {
16 | try {
17 | return !!sessionStorage.getItem;
18 | } catch(e){
19 | return false;
20 | }
21 | })()
22 | };
23 |
24 | var testId = 0;
25 |
26 | var Test = function(name, testName, expected, testEnvironmentArg, async, callback) {
27 | this.name = name;
28 | this.testName = testName;
29 | this.expected = expected;
30 | this.testEnvironmentArg = testEnvironmentArg;
31 | this.async = async;
32 | this.callback = callback;
33 | this.assertions = [];
34 | };
35 | Test.prototype = {
36 | init: function() {
37 | var tests = id("qunit-tests");
38 | if (tests) {
39 | var b = document.createElement("strong");
40 | b.innerHTML = "Running " + this.name;
41 | var li = document.createElement("li");
42 | li.appendChild( b );
43 | li.className = "running";
44 | li.id = this.id = "test-output" + testId++;
45 | tests.appendChild( li );
46 | }
47 | },
48 | setup: function() {
49 | if (this.module != config.previousModule) {
50 | if ( config.previousModule ) {
51 | QUnit.moduleDone( {
52 | name: config.previousModule,
53 | failed: config.moduleStats.bad,
54 | passed: config.moduleStats.all - config.moduleStats.bad,
55 | total: config.moduleStats.all
56 | } );
57 | }
58 | config.previousModule = this.module;
59 | config.moduleStats = { all: 0, bad: 0 };
60 | QUnit.moduleStart( {
61 | name: this.module
62 | } );
63 | }
64 |
65 | config.current = this;
66 | this.testEnvironment = extend({
67 | setup: function() {},
68 | teardown: function() {}
69 | }, this.moduleTestEnvironment);
70 | if (this.testEnvironmentArg) {
71 | extend(this.testEnvironment, this.testEnvironmentArg);
72 | }
73 |
74 | QUnit.testStart( {
75 | name: this.testName
76 | } );
77 |
78 | // allow utility functions to access the current test environment
79 | // TODO why??
80 | QUnit.current_testEnvironment = this.testEnvironment;
81 |
82 | try {
83 | if ( !config.pollution ) {
84 | saveGlobal();
85 | }
86 |
87 | this.testEnvironment.setup.call(this.testEnvironment);
88 | } catch(e) {
89 | QUnit.ok( false, "Setup failed on " + this.testName + ": " + e.message );
90 | }
91 | },
92 | run: function() {
93 | if ( this.async ) {
94 | QUnit.stop();
95 | }
96 |
97 | if ( config.notrycatch ) {
98 | this.callback.call(this.testEnvironment);
99 | return;
100 | }
101 | try {
102 | this.callback.call(this.testEnvironment);
103 | } catch(e) {
104 | fail("Test " + this.testName + " died, exception and test follows", e, this.callback);
105 | QUnit.ok( false, "Died on test #" + (this.assertions.length + 1) + ": " + e.message + " - " + QUnit.jsDump.parse(e) );
106 | // else next test will carry the responsibility
107 | saveGlobal();
108 |
109 | // Restart the tests if they're blocking
110 | if ( config.blocking ) {
111 | start();
112 | }
113 | }
114 | },
115 | teardown: function() {
116 | try {
117 | this.testEnvironment.teardown.call(this.testEnvironment);
118 | checkPollution();
119 | } catch(e) {
120 | QUnit.ok( false, "Teardown failed on " + this.testName + ": " + e.message );
121 | }
122 | },
123 | finish: function() {
124 | if ( this.expected && this.expected != this.assertions.length ) {
125 | QUnit.ok( false, "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run" );
126 | }
127 |
128 | var good = 0, bad = 0,
129 | tests = id("qunit-tests");
130 |
131 | config.stats.all += this.assertions.length;
132 | config.moduleStats.all += this.assertions.length;
133 |
134 | if ( tests ) {
135 | var ol = document.createElement("ol");
136 |
137 | for ( var i = 0; i < this.assertions.length; i++ ) {
138 | var assertion = this.assertions[i];
139 |
140 | var li = document.createElement("li");
141 | li.className = assertion.result ? "pass" : "fail";
142 | li.innerHTML = assertion.message || (assertion.result ? "okay" : "failed");
143 | ol.appendChild( li );
144 |
145 | if ( assertion.result ) {
146 | good++;
147 | } else {
148 | bad++;
149 | config.stats.bad++;
150 | config.moduleStats.bad++;
151 | }
152 | }
153 |
154 | // store result when possible
155 | if ( QUnit.config.reorder && defined.sessionStorage ) {
156 | if (bad) {
157 | sessionStorage.setItem("qunit-" + this.module + "-" + this.testName, bad);
158 | } else {
159 | sessionStorage.removeItem("qunit-" + this.module + "-" + this.testName);
160 | }
161 | }
162 |
163 | if (bad == 0) {
164 | ol.style.display = "none";
165 | }
166 |
167 | var b = document.createElement("strong");
168 | b.innerHTML = this.name + " (" + bad + ", " + good + ", " + this.assertions.length + ")";
169 |
170 | var a = document.createElement("a");
171 | a.innerHTML = "Rerun";
172 | a.href = QUnit.url({ filter: getText([b]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") });
173 |
174 | addEvent(b, "click", function() {
175 | var next = b.nextSibling.nextSibling,
176 | display = next.style.display;
177 | next.style.display = display === "none" ? "block" : "none";
178 | });
179 |
180 | addEvent(b, "dblclick", function(e) {
181 | var target = e && e.target ? e.target : window.event.srcElement;
182 | if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) {
183 | target = target.parentNode;
184 | }
185 | if ( window.location && target.nodeName.toLowerCase() === "strong" ) {
186 | window.location = QUnit.url({ filter: getText([target]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") });
187 | }
188 | });
189 |
190 | var li = id(this.id);
191 | li.className = bad ? "fail" : "pass";
192 | li.removeChild( li.firstChild );
193 | li.appendChild( b );
194 | li.appendChild( a );
195 | li.appendChild( ol );
196 |
197 | } else {
198 | for ( var i = 0; i < this.assertions.length; i++ ) {
199 | if ( !this.assertions[i].result ) {
200 | bad++;
201 | config.stats.bad++;
202 | config.moduleStats.bad++;
203 | }
204 | }
205 | }
206 |
207 | try {
208 | QUnit.reset();
209 | } catch(e) {
210 | fail("reset() failed, following Test " + this.testName + ", exception and reset fn follows", e, QUnit.reset);
211 | }
212 |
213 | QUnit.testDone( {
214 | name: this.testName,
215 | failed: bad,
216 | passed: this.assertions.length - bad,
217 | total: this.assertions.length
218 | } );
219 | },
220 |
221 | queue: function() {
222 | var test = this;
223 | synchronize(function() {
224 | test.init();
225 | });
226 | function run() {
227 | // each of these can by async
228 | synchronize(function() {
229 | test.setup();
230 | });
231 | synchronize(function() {
232 | test.run();
233 | });
234 | synchronize(function() {
235 | test.teardown();
236 | });
237 | synchronize(function() {
238 | test.finish();
239 | });
240 | }
241 | // defer when previous test run passed, if storage is available
242 | var bad = QUnit.config.reorder && defined.sessionStorage && +sessionStorage.getItem("qunit-" + this.module + "-" + this.testName);
243 | if (bad) {
244 | run();
245 | } else {
246 | synchronize(run);
247 | };
248 | }
249 |
250 | };
251 |
252 | var QUnit = {
253 |
254 | // call on start of module test to prepend name to all tests
255 | module: function(name, testEnvironment) {
256 | config.currentModule = name;
257 | config.currentModuleTestEnviroment = testEnvironment;
258 | },
259 |
260 | asyncTest: function(testName, expected, callback) {
261 | if ( arguments.length === 2 ) {
262 | callback = expected;
263 | expected = 0;
264 | }
265 |
266 | QUnit.test(testName, expected, callback, true);
267 | },
268 |
269 | test: function(testName, expected, callback, async) {
270 | var name = '' + testName + '', testEnvironmentArg;
271 |
272 | if ( arguments.length === 2 ) {
273 | callback = expected;
274 | expected = null;
275 | }
276 | // is 2nd argument a testEnvironment?
277 | if ( expected && typeof expected === 'object') {
278 | testEnvironmentArg = expected;
279 | expected = null;
280 | }
281 |
282 | if ( config.currentModule ) {
283 | name = '' + config.currentModule + ": " + name;
284 | }
285 |
286 | if ( !validTest(config.currentModule + ": " + testName) ) {
287 | return;
288 | }
289 |
290 | var test = new Test(name, testName, expected, testEnvironmentArg, async, callback);
291 | test.module = config.currentModule;
292 | test.moduleTestEnvironment = config.currentModuleTestEnviroment;
293 | test.queue();
294 | },
295 |
296 | /**
297 | * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through.
298 | */
299 | expect: function(asserts) {
300 | config.current.expected = asserts;
301 | },
302 |
303 | /**
304 | * Asserts true.
305 | * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
306 | */
307 | ok: function(a, msg) {
308 | a = !!a;
309 | var details = {
310 | result: a,
311 | message: msg
312 | };
313 | msg = escapeHtml(msg);
314 | QUnit.log(details);
315 | config.current.assertions.push({
316 | result: a,
317 | message: msg
318 | });
319 | },
320 |
321 | /**
322 | * Checks that the first two arguments are equal, with an optional message.
323 | * Prints out both actual and expected values.
324 | *
325 | * Prefered to ok( actual == expected, message )
326 | *
327 | * @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." );
328 | *
329 | * @param Object actual
330 | * @param Object expected
331 | * @param String message (optional)
332 | */
333 | equal: function(actual, expected, message) {
334 | QUnit.push(expected == actual, actual, expected, message);
335 | },
336 |
337 | notEqual: function(actual, expected, message) {
338 | QUnit.push(expected != actual, actual, expected, message);
339 | },
340 |
341 | deepEqual: function(actual, expected, message) {
342 | QUnit.push(QUnit.equiv(actual, expected), actual, expected, message);
343 | },
344 |
345 | notDeepEqual: function(actual, expected, message) {
346 | QUnit.push(!QUnit.equiv(actual, expected), actual, expected, message);
347 | },
348 |
349 | strictEqual: function(actual, expected, message) {
350 | QUnit.push(expected === actual, actual, expected, message);
351 | },
352 |
353 | notStrictEqual: function(actual, expected, message) {
354 | QUnit.push(expected !== actual, actual, expected, message);
355 | },
356 |
357 | raises: function(block, expected, message) {
358 | var actual, ok = false;
359 |
360 | if (typeof expected === 'string') {
361 | message = expected;
362 | expected = null;
363 | }
364 |
365 | try {
366 | block();
367 | } catch (e) {
368 | actual = e;
369 | }
370 |
371 | if (actual) {
372 | // we don't want to validate thrown error
373 | if (!expected) {
374 | ok = true;
375 | // expected is a regexp
376 | } else if (QUnit.objectType(expected) === "regexp") {
377 | ok = expected.test(actual);
378 | // expected is a constructor
379 | } else if (actual instanceof expected) {
380 | ok = true;
381 | // expected is a validation function which returns true is validation passed
382 | } else if (expected.call({}, actual) === true) {
383 | ok = true;
384 | }
385 | }
386 |
387 | QUnit.ok(ok, message);
388 | },
389 |
390 | start: function() {
391 | config.semaphore--;
392 | if (config.semaphore > 0) {
393 | // don't start until equal number of stop-calls
394 | return;
395 | }
396 | if (config.semaphore < 0) {
397 | // ignore if start is called more often then stop
398 | config.semaphore = 0;
399 | }
400 | // A slight delay, to avoid any current callbacks
401 | if ( defined.setTimeout ) {
402 | window.setTimeout(function() {
403 | if ( config.timeout ) {
404 | clearTimeout(config.timeout);
405 | }
406 |
407 | config.blocking = false;
408 | process();
409 | }, 13);
410 | } else {
411 | config.blocking = false;
412 | process();
413 | }
414 | },
415 |
416 | stop: function(timeout) {
417 | config.semaphore++;
418 | config.blocking = true;
419 |
420 | if ( timeout && defined.setTimeout ) {
421 | clearTimeout(config.timeout);
422 | config.timeout = window.setTimeout(function() {
423 | QUnit.ok( false, "Test timed out" );
424 | QUnit.start();
425 | }, timeout);
426 | }
427 | }
428 | };
429 |
430 | // Backwards compatibility, deprecated
431 | QUnit.equals = QUnit.equal;
432 | QUnit.same = QUnit.deepEqual;
433 |
434 | // Maintain internal state
435 | var config = {
436 | // The queue of tests to run
437 | queue: [],
438 |
439 | // block until document ready
440 | blocking: true,
441 |
442 | // by default, run previously failed tests first
443 | // very useful in combination with "Hide passed tests" checked
444 | reorder: true,
445 |
446 | noglobals: false,
447 | notrycatch: false
448 | };
449 |
450 | // Load paramaters
451 | (function() {
452 | var location = window.location || { search: "", protocol: "file:" },
453 | params = location.search.slice( 1 ).split( "&" ),
454 | length = params.length,
455 | urlParams = {},
456 | current;
457 |
458 | if ( params[ 0 ] ) {
459 | for ( var i = 0; i < length; i++ ) {
460 | current = params[ i ].split( "=" );
461 | current[ 0 ] = decodeURIComponent( current[ 0 ] );
462 | // allow just a key to turn on a flag, e.g., test.html?noglobals
463 | current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true;
464 | urlParams[ current[ 0 ] ] = current[ 1 ];
465 | if ( current[ 0 ] in config ) {
466 | config[ current[ 0 ] ] = current[ 1 ];
467 | }
468 | }
469 | }
470 |
471 | QUnit.urlParams = urlParams;
472 | config.filter = urlParams.filter;
473 |
474 | // Figure out if we're running the tests from a server or not
475 | QUnit.isLocal = !!(location.protocol === 'file:');
476 | })();
477 |
478 | // Expose the API as global variables, unless an 'exports'
479 | // object exists, in that case we assume we're in CommonJS
480 | if ( typeof exports === "undefined" || typeof require === "undefined" ) {
481 | extend(window, QUnit);
482 | window.QUnit = QUnit;
483 | } else {
484 | extend(exports, QUnit);
485 | exports.QUnit = QUnit;
486 | }
487 |
488 | // define these after exposing globals to keep them in these QUnit namespace only
489 | extend(QUnit, {
490 | config: config,
491 |
492 | // Initialize the configuration options
493 | init: function() {
494 | extend(config, {
495 | stats: { all: 0, bad: 0 },
496 | moduleStats: { all: 0, bad: 0 },
497 | started: +new Date,
498 | updateRate: 1000,
499 | blocking: false,
500 | autostart: true,
501 | autorun: false,
502 | filter: "",
503 | queue: [],
504 | semaphore: 0
505 | });
506 |
507 | var tests = id( "qunit-tests" ),
508 | banner = id( "qunit-banner" ),
509 | result = id( "qunit-testresult" );
510 |
511 | if ( tests ) {
512 | tests.innerHTML = "";
513 | }
514 |
515 | if ( banner ) {
516 | banner.className = "";
517 | }
518 |
519 | if ( result ) {
520 | result.parentNode.removeChild( result );
521 | }
522 |
523 | if ( tests ) {
524 | result = document.createElement( "p" );
525 | result.id = "qunit-testresult";
526 | result.className = "result";
527 | tests.parentNode.insertBefore( result, tests );
528 | result.innerHTML = 'Running... ';
529 | }
530 | },
531 |
532 | /**
533 | * Resets the test setup. Useful for tests that modify the DOM.
534 | *
535 | * If jQuery is available, uses jQuery's html(), otherwise just innerHTML.
536 | */
537 | reset: function() {
538 | if ( window.jQuery ) {
539 | jQuery( "#qunit-fixture" ).html( config.fixture );
540 | } else {
541 | var main = id( 'qunit-fixture' );
542 | if ( main ) {
543 | main.innerHTML = config.fixture;
544 | }
545 | }
546 | },
547 |
548 | /**
549 | * Trigger an event on an element.
550 | *
551 | * @example triggerEvent( document.body, "click" );
552 | *
553 | * @param DOMElement elem
554 | * @param String type
555 | */
556 | triggerEvent: function( elem, type, event ) {
557 | if ( document.createEvent ) {
558 | event = document.createEvent("MouseEvents");
559 | event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView,
560 | 0, 0, 0, 0, 0, false, false, false, false, 0, null);
561 | elem.dispatchEvent( event );
562 |
563 | } else if ( elem.fireEvent ) {
564 | elem.fireEvent("on"+type);
565 | }
566 | },
567 |
568 | // Safe object type checking
569 | is: function( type, obj ) {
570 | return QUnit.objectType( obj ) == type;
571 | },
572 |
573 | objectType: function( obj ) {
574 | if (typeof obj === "undefined") {
575 | return "undefined";
576 |
577 | // consider: typeof null === object
578 | }
579 | if (obj === null) {
580 | return "null";
581 | }
582 |
583 | var type = Object.prototype.toString.call( obj )
584 | .match(/^\[object\s(.*)\]$/)[1] || '';
585 |
586 | switch (type) {
587 | case 'Number':
588 | if (isNaN(obj)) {
589 | return "nan";
590 | } else {
591 | return "number";
592 | }
593 | case 'String':
594 | case 'Boolean':
595 | case 'Array':
596 | case 'Date':
597 | case 'RegExp':
598 | case 'Function':
599 | return type.toLowerCase();
600 | }
601 | if (typeof obj === "object") {
602 | return "object";
603 | }
604 | return undefined;
605 | },
606 |
607 | push: function(result, actual, expected, message) {
608 | var details = {
609 | result: result,
610 | message: message,
611 | actual: actual,
612 | expected: expected
613 | };
614 |
615 | message = escapeHtml(message) || (result ? "okay" : "failed");
616 | message = '' + message + "";
617 | expected = escapeHtml(QUnit.jsDump.parse(expected));
618 | actual = escapeHtml(QUnit.jsDump.parse(actual));
619 | var output = message + '