├── .gitignore
├── CONTRIBUTORS.md
├── Gruntfile.js
├── LICENSE-MIT
├── README.md
├── ajax-retry.jquery.json
├── dist
├── jquery.ajax-retry.js
└── jquery.ajax-retry.min.js
├── libs
├── jquery
│ └── jquery.js
├── qunit
│ ├── qunit.css
│ └── qunit.js
└── sinon
│ └── sinon.js
├── package.json
├── src
└── jquery.ajax-retry.js
└── test
├── jquery.ajax-retry.html
└── jquery.ajax-retry_test.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # gcc coverage testing tool files
2 |
3 | *.gcno
4 | *.gcda
5 | *.gcov
6 |
7 | # npm modules
8 | node_modules
9 |
--------------------------------------------------------------------------------
/CONTRIBUTORS.md:
--------------------------------------------------------------------------------
1 | [John K. Paul](http://twitter.com/johnkpaul)
2 | [Corey Frang](http://twitter.com/gnarf37)
3 | [Oleg Slobodskoi](http://twitter.com/oleg008)
4 |
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | /*global module:false*/
2 | module.exports = function(grunt) {
3 |
4 | require('load-grunt-tasks')(grunt);
5 |
6 | // Project configuration.
7 | grunt.initConfig({
8 | pkg: grunt.file.readJSON('package.json'),
9 | concat: {
10 | dist: {
11 | src: ['src/<%= pkg.name %>.js'],
12 | dest: 'dist/<%= pkg.name %>.js'
13 | }
14 | },
15 | uglify: {
16 | dist: {
17 | src: ['<%= concat.dist.dest %>'],
18 | dest: 'dist/<%= pkg.name %>.min.js'
19 | }
20 | },
21 | qunit: {
22 | files: ['test/**/*.html']
23 | },
24 | jshint: {
25 | files: ['Gruntfile.js', 'src/**/*.js', 'test/**/*.js'],
26 | options: {
27 | curly: true,
28 | eqeqeq: true,
29 | immed: true,
30 | latedef: true,
31 | newcap: true,
32 | noarg: true,
33 | sub: true,
34 | undef: true,
35 | boss: true,
36 | eqnull: true,
37 | browser: true,
38 | globals: {
39 | jQuery: true,
40 | define: true,
41 | ok: true,
42 | require: true
43 | }
44 | }
45 | }
46 | });
47 |
48 | // Default task.
49 | grunt.registerTask('default', ['jshint','qunit', 'concat', 'uglify', 'manifest']);
50 |
51 | grunt.registerTask( "manifest", function() {
52 | var pkg = grunt.config( "pkg" );
53 | grunt.file.write( "ajax-retry.jquery.json", JSON.stringify({
54 | name: "ajax-retry",
55 | title: pkg.title,
56 | description: pkg.description,
57 | keywords: pkg.keywords,
58 | version: pkg.version,
59 | author: pkg.author,
60 | maintainers: pkg.maintainers,
61 | licenses: pkg.licenses.map(function( license ) {
62 | license.url = license.url.replace( "master", pkg.version );
63 | return license;
64 | }),
65 | bugs: pkg.bugs,
66 | homepage: pkg.homepage,
67 | docs: pkg.homepage,
68 | dependencies: {
69 | jquery: ">=1.5"
70 | }
71 | }, null, " " ) );
72 | });
73 |
74 | };
75 |
--------------------------------------------------------------------------------
/LICENSE-MIT:
--------------------------------------------------------------------------------
1 | Copyright (c) 2012 John Paul
2 |
3 | Permission is hereby granted, free of charge, to any person
4 | obtaining a copy of this software and associated documentation
5 | files (the "Software"), to deal in the Software without
6 | restriction, including without limitation the rights to use,
7 | copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the
9 | Software is furnished to do so, subject to the following
10 | conditions:
11 |
12 | The above copyright notice and this permission notice shall be
13 | included in all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # jQuery Ajax Retry
2 |
3 | Retry ajax calls using the deferred API
4 |
5 | ## Getting Started
6 | Download the [production version][min] or the [development version][max].
7 |
8 | [min]: https://raw.github.com/johnkpaul/jquery-ajax-retry/master/dist/jquery.ajax-retry.min.js
9 | [max]: https://raw.github.com/johnkpaul/jquery-ajax-retry/master/dist/jquery.ajax-retry.js
10 |
11 | In your web page:
12 |
13 | ```html
14 |
15 |
16 |
37 | ```
38 |
39 | ## Contributing
40 | In lieu of a formal styleguide, take care to maintain the existing coding style. Add unit tests for any new or changed functionality. Lint and test your code using [grunt](https://github.com/cowboy/grunt).
41 |
42 | _Also, please don't edit files in the "dist" subdirectory as they are generated via grunt. You'll find source code in the "src" subdirectory!_
43 |
44 | ## Release History
45 | _(Nothing yet)_
46 |
47 | ## License
48 | Copyright (c) 2012 John Paul
49 | Licensed under the MIT license.
50 |
--------------------------------------------------------------------------------
/ajax-retry.jquery.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ajax-retry",
3 | "title": "jQuery Ajax Retry",
4 | "description": "Retry ajax calls using the deferred API",
5 | "keywords": [
6 | "ajax",
7 | "retry"
8 | ],
9 | "version": "1.0.0",
10 | "author": {
11 | "name": "John Paul",
12 | "email": "john@johnkpaul.com",
13 | "url": "http://www.johnkpaul.com"
14 | },
15 | "maintainers": [
16 | {
17 | "name": "John Paul",
18 | "email": "john@johnkpaul.com",
19 | "url": "http://www.johnkpaul.com"
20 | }
21 | ],
22 | "licenses": [
23 | {
24 | "type": "MIT",
25 | "url": "https://github.com/johnkpaul/jquery-ajax-retry/blob/1.0.0/LICENSE-MIT"
26 | }
27 | ],
28 | "bugs": {
29 | "url": "https://github.com/johnkpaul/jquery-ajax-retry/issues"
30 | },
31 | "homepage": "https://github.com/johnkpaul/jquery-ajax-retry",
32 | "docs": "https://github.com/johnkpaul/jquery-ajax-retry",
33 | "dependencies": {
34 | "jquery": ">=1.5"
35 | }
36 | }
--------------------------------------------------------------------------------
/dist/jquery.ajax-retry.js:
--------------------------------------------------------------------------------
1 | /*
2 | * jquery.ajax-retry
3 | * https://github.com/johnkpaul/jquery-ajax-retry
4 | *
5 | * Copyright (c) 2012 John Paul
6 | * Licensed under the MIT license.
7 | */
8 | (function(factory) {
9 | if (typeof define === 'function' && define.amd) {
10 | // AMD. Register as an anonymous module.
11 | define(['jquery'], factory);
12 | } else if (typeof exports === 'object') {
13 | // Node/CommonJS
14 | factory(require('jquery'));
15 | } else {
16 | // Browser globals
17 | factory(jQuery);
18 | }
19 | })(function($) {
20 |
21 | // enhance all ajax requests with our retry API
22 | $.ajaxPrefilter(function(options, originalOptions, jqXHR) {
23 | jqXHR.retry = function(opts) {
24 | if(opts.timeout) {
25 | this.timeout = opts.timeout;
26 | }
27 | if (opts.statusCodes) {
28 | this.statusCodes = opts.statusCodes;
29 | }
30 | return this.pipe(null, pipeFailRetry(this, opts));
31 | };
32 | });
33 |
34 | // generates a fail pipe function that will retry `jqXHR` `times` more times
35 | function pipeFailRetry(jqXHR, opts) {
36 | var times = opts.times;
37 | var timeout = jqXHR.timeout;
38 |
39 | // takes failure data as input, returns a new deferred
40 | return function(input, status, msg) {
41 | var ajaxOptions = this;
42 | var output = new $.Deferred();
43 | var retryAfter = jqXHR.getResponseHeader('Retry-After');
44 |
45 | // whenever we do make this request, pipe its output to our deferred
46 | function nextRequest() {
47 | $.ajax(ajaxOptions)
48 | .retry({times: times - 1, timeout: opts.timeout, statusCodes: opts.statusCodes})
49 | .pipe(output.resolve, output.reject);
50 | }
51 |
52 | if (times > 1 && (!jqXHR.statusCodes || $.inArray(input.status, jqXHR.statusCodes) > -1)) {
53 | // implement Retry-After rfc
54 | // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.37
55 | if (retryAfter) {
56 | // it must be a date
57 | if (isNaN(retryAfter)) {
58 | timeout = new Date(retryAfter).getTime() - $.now();
59 | // its a number in seconds
60 | } else {
61 | timeout = parseInt(retryAfter, 10) * 1000;
62 | }
63 | // ensure timeout is a positive number
64 | if (isNaN(timeout) || timeout < 0) {
65 | timeout = jqXHR.timeout;
66 | }
67 | }
68 |
69 | if (timeout !== undefined){
70 | setTimeout(nextRequest, timeout);
71 | } else {
72 | nextRequest();
73 | }
74 | } else {
75 | // no times left, reject our deferred with the current arguments
76 | output.rejectWith(this, arguments);
77 | }
78 |
79 | return output;
80 | };
81 | }
82 |
83 | });
84 |
--------------------------------------------------------------------------------
/dist/jquery.ajax-retry.min.js:
--------------------------------------------------------------------------------
1 | !function(a){"function"==typeof define&&define.amd?define(["jquery"],a):a("object"==typeof exports?require("jquery"):jQuery)}(function(a){function b(b,c){var d=c.times,e=b.timeout;return function(f){function g(){a.ajax(h).retry({times:d-1,timeout:c.timeout,statusCodes:c.statusCodes}).pipe(i.resolve,i.reject)}var h=this,i=new a.Deferred,j=b.getResponseHeader("Retry-After");return d>1&&(!b.statusCodes||a.inArray(f.status,b.statusCodes)>-1)?(j&&(e=isNaN(j)?new Date(j).getTime()-a.now():1e3*parseInt(j,10),(isNaN(e)||0>e)&&(e=b.timeout)),void 0!==e?setTimeout(g,e):g()):i.rejectWith(this,arguments),i}}a.ajaxPrefilter(function(a,c,d){d.retry=function(a){return a.timeout&&(this.timeout=a.timeout),a.statusCodes&&(this.statusCodes=a.statusCodes),this.pipe(null,b(this,a))}})});
--------------------------------------------------------------------------------
/libs/qunit/qunit.css:
--------------------------------------------------------------------------------
1 | /**
2 | * QUnit v1.4.0 - A JavaScript Unit Testing Framework
3 | *
4 | * http://docs.jquery.com/QUnit
5 | *
6 | * Copyright (c) 2012 John Resig, Jörn Zaefferer
7 | * Dual licensed under the MIT (MIT-LICENSE.txt)
8 | * or GPL (GPL-LICENSE.txt) licenses.
9 | */
10 |
11 | /** Font Family and Sizes */
12 |
13 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult {
14 | font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif;
15 | }
16 |
17 | #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; }
18 | #qunit-tests { font-size: smaller; }
19 |
20 |
21 | /** Resets */
22 |
23 | #qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult {
24 | margin: 0;
25 | padding: 0;
26 | }
27 |
28 |
29 | /** Header */
30 |
31 | #qunit-header {
32 | padding: 0.5em 0 0.5em 1em;
33 |
34 | color: #8699a4;
35 | background-color: #0d3349;
36 |
37 | font-size: 1.5em;
38 | line-height: 1em;
39 | font-weight: normal;
40 |
41 | border-radius: 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-header label {
58 | display: inline-block;
59 | }
60 |
61 | #qunit-banner {
62 | height: 5px;
63 | }
64 |
65 | #qunit-testrunner-toolbar {
66 | padding: 0.5em 0 0.5em 2em;
67 | color: #5E740B;
68 | background-color: #eee;
69 | }
70 |
71 | #qunit-userAgent {
72 | padding: 0.5em 0 0.5em 2.5em;
73 | background-color: #2b81af;
74 | color: #fff;
75 | text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
76 | }
77 |
78 |
79 | /** Tests: Pass/Fail */
80 |
81 | #qunit-tests {
82 | list-style-position: inside;
83 | }
84 |
85 | #qunit-tests li {
86 | padding: 0.4em 0.5em 0.4em 2.5em;
87 | border-bottom: 1px solid #fff;
88 | list-style-position: inside;
89 | }
90 |
91 | #qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running {
92 | display: none;
93 | }
94 |
95 | #qunit-tests li strong {
96 | cursor: pointer;
97 | }
98 |
99 | #qunit-tests li a {
100 | padding: 0.5em;
101 | color: #c2ccd1;
102 | text-decoration: none;
103 | }
104 | #qunit-tests li a:hover,
105 | #qunit-tests li a:focus {
106 | color: #000;
107 | }
108 |
109 | #qunit-tests ol {
110 | margin-top: 0.5em;
111 | padding: 0.5em;
112 |
113 | background-color: #fff;
114 |
115 | border-radius: 15px;
116 | -moz-border-radius: 15px;
117 | -webkit-border-radius: 15px;
118 |
119 | box-shadow: inset 0px 2px 13px #999;
120 | -moz-box-shadow: inset 0px 2px 13px #999;
121 | -webkit-box-shadow: inset 0px 2px 13px #999;
122 | }
123 |
124 | #qunit-tests table {
125 | border-collapse: collapse;
126 | margin-top: .2em;
127 | }
128 |
129 | #qunit-tests th {
130 | text-align: right;
131 | vertical-align: top;
132 | padding: 0 .5em 0 0;
133 | }
134 |
135 | #qunit-tests td {
136 | vertical-align: top;
137 | }
138 |
139 | #qunit-tests pre {
140 | margin: 0;
141 | white-space: pre-wrap;
142 | word-wrap: break-word;
143 | }
144 |
145 | #qunit-tests del {
146 | background-color: #e0f2be;
147 | color: #374e0c;
148 | text-decoration: none;
149 | }
150 |
151 | #qunit-tests ins {
152 | background-color: #ffcaca;
153 | color: #500;
154 | text-decoration: none;
155 | }
156 |
157 | /*** Test Counts */
158 |
159 | #qunit-tests b.counts { color: black; }
160 | #qunit-tests b.passed { color: #5E740B; }
161 | #qunit-tests b.failed { color: #710909; }
162 |
163 | #qunit-tests li li {
164 | margin: 0.5em;
165 | padding: 0.4em 0.5em 0.4em 0.5em;
166 | background-color: #fff;
167 | border-bottom: none;
168 | list-style-position: inside;
169 | }
170 |
171 | /*** Passing Styles */
172 |
173 | #qunit-tests li li.pass {
174 | color: #5E740B;
175 | background-color: #fff;
176 | border-left: 26px solid #C6E746;
177 | }
178 |
179 | #qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; }
180 | #qunit-tests .pass .test-name { color: #366097; }
181 |
182 | #qunit-tests .pass .test-actual,
183 | #qunit-tests .pass .test-expected { color: #999999; }
184 |
185 | #qunit-banner.qunit-pass { background-color: #C6E746; }
186 |
187 | /*** Failing Styles */
188 |
189 | #qunit-tests li li.fail {
190 | color: #710909;
191 | background-color: #fff;
192 | border-left: 26px solid #EE5757;
193 | white-space: pre;
194 | }
195 |
196 | #qunit-tests > li:last-child {
197 | border-radius: 0 0 15px 15px;
198 | -moz-border-radius: 0 0 15px 15px;
199 | -webkit-border-bottom-right-radius: 15px;
200 | -webkit-border-bottom-left-radius: 15px;
201 | }
202 |
203 | #qunit-tests .fail { color: #000000; background-color: #EE5757; }
204 | #qunit-tests .fail .test-name,
205 | #qunit-tests .fail .module-name { color: #000000; }
206 |
207 | #qunit-tests .fail .test-actual { color: #EE5757; }
208 | #qunit-tests .fail .test-expected { color: green; }
209 |
210 | #qunit-banner.qunit-fail { background-color: #EE5757; }
211 |
212 |
213 | /** Result */
214 |
215 | #qunit-testresult {
216 | padding: 0.5em 0.5em 0.5em 2.5em;
217 |
218 | color: #2b81af;
219 | background-color: #D2E0E6;
220 |
221 | border-bottom: 1px solid white;
222 | }
223 |
224 | /** Fixture */
225 |
226 | #qunit-fixture {
227 | position: absolute;
228 | top: -10000px;
229 | left: -10000px;
230 | width: 1000px;
231 | height: 1000px;
232 | }
233 |
--------------------------------------------------------------------------------
/libs/qunit/qunit.js:
--------------------------------------------------------------------------------
1 | /**
2 | * QUnit v1.4.0 - A JavaScript Unit Testing Framework
3 | *
4 | * http://docs.jquery.com/QUnit
5 | *
6 | * Copyright (c) 2012 John Resig, Jörn Zaefferer
7 | * Dual licensed under the MIT (MIT-LICENSE.txt)
8 | * or GPL (GPL-LICENSE.txt) licenses.
9 | */
10 |
11 | (function(window) {
12 |
13 | var defined = {
14 | setTimeout: typeof window.setTimeout !== "undefined",
15 | sessionStorage: (function() {
16 | var x = "qunit-test-string";
17 | try {
18 | sessionStorage.setItem(x, x);
19 | sessionStorage.removeItem(x);
20 | return true;
21 | } catch(e) {
22 | return false;
23 | }
24 | }())
25 | };
26 |
27 | var testId = 0,
28 | toString = Object.prototype.toString,
29 | hasOwn = Object.prototype.hasOwnProperty;
30 |
31 | var Test = function(name, testName, expected, async, callback) {
32 | this.name = name;
33 | this.testName = testName;
34 | this.expected = expected;
35 | this.async = async;
36 | this.callback = callback;
37 | this.assertions = [];
38 | };
39 | Test.prototype = {
40 | init: function() {
41 | var tests = id("qunit-tests");
42 | if (tests) {
43 | var b = document.createElement("strong");
44 | b.innerHTML = "Running " + this.name;
45 | var li = document.createElement("li");
46 | li.appendChild( b );
47 | li.className = "running";
48 | li.id = this.id = "test-output" + testId++;
49 | tests.appendChild( li );
50 | }
51 | },
52 | setup: function() {
53 | if (this.module != config.previousModule) {
54 | if ( config.previousModule ) {
55 | runLoggingCallbacks('moduleDone', QUnit, {
56 | name: config.previousModule,
57 | failed: config.moduleStats.bad,
58 | passed: config.moduleStats.all - config.moduleStats.bad,
59 | total: config.moduleStats.all
60 | } );
61 | }
62 | config.previousModule = this.module;
63 | config.moduleStats = { all: 0, bad: 0 };
64 | runLoggingCallbacks( 'moduleStart', QUnit, {
65 | name: this.module
66 | } );
67 | } else if (config.autorun) {
68 | runLoggingCallbacks( 'moduleStart', QUnit, {
69 | name: this.module
70 | } );
71 | }
72 |
73 | config.current = this;
74 | this.testEnvironment = extend({
75 | setup: function() {},
76 | teardown: function() {}
77 | }, this.moduleTestEnvironment);
78 |
79 | runLoggingCallbacks( 'testStart', QUnit, {
80 | name: this.testName,
81 | module: this.module
82 | });
83 |
84 | // allow utility functions to access the current test environment
85 | // TODO why??
86 | QUnit.current_testEnvironment = this.testEnvironment;
87 |
88 | if ( !config.pollution ) {
89 | saveGlobal();
90 | }
91 | if ( config.notrycatch ) {
92 | this.testEnvironment.setup.call(this.testEnvironment);
93 | return;
94 | }
95 | try {
96 | this.testEnvironment.setup.call(this.testEnvironment);
97 | } catch(e) {
98 | QUnit.pushFailure( "Setup failed on " + this.testName + ": " + e.message, extractStacktrace( e, 1 ) );
99 | }
100 | },
101 | run: function() {
102 | config.current = this;
103 | if ( this.async ) {
104 | QUnit.stop();
105 | }
106 |
107 | if ( config.notrycatch ) {
108 | this.callback.call(this.testEnvironment);
109 | return;
110 | }
111 | try {
112 | this.callback.call(this.testEnvironment);
113 | } catch(e) {
114 | QUnit.pushFailure( "Died on test #" + (this.assertions.length + 1) + ": " + e.message, extractStacktrace( e, 1 ) );
115 | // else next test will carry the responsibility
116 | saveGlobal();
117 |
118 | // Restart the tests if they're blocking
119 | if ( config.blocking ) {
120 | QUnit.start();
121 | }
122 | }
123 | },
124 | teardown: function() {
125 | config.current = this;
126 | if ( config.notrycatch ) {
127 | this.testEnvironment.teardown.call(this.testEnvironment);
128 | return;
129 | } else {
130 | try {
131 | this.testEnvironment.teardown.call(this.testEnvironment);
132 | } catch(e) {
133 | QUnit.pushFailure( "Teardown failed on " + this.testName + ": " + e.message, extractStacktrace( e, 1 ) );
134 | }
135 | }
136 | checkPollution();
137 | },
138 | finish: function() {
139 | config.current = this;
140 | if ( this.expected != null && this.expected != this.assertions.length ) {
141 | QUnit.pushFailure( "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run" );
142 | } else if ( this.expected == null && !this.assertions.length ) {
143 | QUnit.pushFailure( "Expected at least one assertion, but none were run - call expect(0) to accept zero assertions." );
144 | }
145 |
146 | var good = 0, bad = 0,
147 | li, i,
148 | tests = id("qunit-tests");
149 |
150 | config.stats.all += this.assertions.length;
151 | config.moduleStats.all += this.assertions.length;
152 |
153 | if ( tests ) {
154 | var ol = document.createElement("ol");
155 |
156 | for ( i = 0; i < this.assertions.length; i++ ) {
157 | var assertion = this.assertions[i];
158 |
159 | li = document.createElement("li");
160 | li.className = assertion.result ? "pass" : "fail";
161 | li.innerHTML = assertion.message || (assertion.result ? "okay" : "failed");
162 | ol.appendChild( li );
163 |
164 | if ( assertion.result ) {
165 | good++;
166 | } else {
167 | bad++;
168 | config.stats.bad++;
169 | config.moduleStats.bad++;
170 | }
171 | }
172 |
173 | // store result when possible
174 | if ( QUnit.config.reorder && defined.sessionStorage ) {
175 | if (bad) {
176 | sessionStorage.setItem("qunit-test-" + this.module + "-" + this.testName, bad);
177 | } else {
178 | sessionStorage.removeItem("qunit-test-" + this.module + "-" + this.testName);
179 | }
180 | }
181 |
182 | if (bad === 0) {
183 | ol.style.display = "none";
184 | }
185 |
186 | var b = document.createElement("strong");
187 | b.innerHTML = this.name + " (" + bad + ", " + good + ", " + this.assertions.length + ")";
188 |
189 | var a = document.createElement("a");
190 | a.innerHTML = "Rerun";
191 | a.href = QUnit.url({ filter: getText([b]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") });
192 |
193 | addEvent(b, "click", function() {
194 | var next = b.nextSibling.nextSibling,
195 | display = next.style.display;
196 | next.style.display = display === "none" ? "block" : "none";
197 | });
198 |
199 | addEvent(b, "dblclick", function(e) {
200 | var target = e && e.target ? e.target : window.event.srcElement;
201 | if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) {
202 | target = target.parentNode;
203 | }
204 | if ( window.location && target.nodeName.toLowerCase() === "strong" ) {
205 | window.location = QUnit.url({ filter: getText([target]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") });
206 | }
207 | });
208 |
209 | li = id(this.id);
210 | li.className = bad ? "fail" : "pass";
211 | li.removeChild( li.firstChild );
212 | li.appendChild( b );
213 | li.appendChild( a );
214 | li.appendChild( ol );
215 |
216 | } else {
217 | for ( i = 0; i < this.assertions.length; i++ ) {
218 | if ( !this.assertions[i].result ) {
219 | bad++;
220 | config.stats.bad++;
221 | config.moduleStats.bad++;
222 | }
223 | }
224 | }
225 |
226 | QUnit.reset();
227 |
228 | runLoggingCallbacks( 'testDone', QUnit, {
229 | name: this.testName,
230 | module: this.module,
231 | failed: bad,
232 | passed: this.assertions.length - bad,
233 | total: this.assertions.length
234 | } );
235 | },
236 |
237 | queue: function() {
238 | var test = this;
239 | synchronize(function() {
240 | test.init();
241 | });
242 | function run() {
243 | // each of these can by async
244 | synchronize(function() {
245 | test.setup();
246 | });
247 | synchronize(function() {
248 | test.run();
249 | });
250 | synchronize(function() {
251 | test.teardown();
252 | });
253 | synchronize(function() {
254 | test.finish();
255 | });
256 | }
257 | // defer when previous test run passed, if storage is available
258 | var bad = QUnit.config.reorder && defined.sessionStorage && +sessionStorage.getItem("qunit-test-" + this.module + "-" + this.testName);
259 | if (bad) {
260 | run();
261 | } else {
262 | synchronize(run, true);
263 | }
264 | }
265 |
266 | };
267 |
268 | var QUnit = {
269 |
270 | // call on start of module test to prepend name to all tests
271 | module: function(name, testEnvironment) {
272 | config.currentModule = name;
273 | config.currentModuleTestEnviroment = testEnvironment;
274 | },
275 |
276 | asyncTest: function(testName, expected, callback) {
277 | if ( arguments.length === 2 ) {
278 | callback = expected;
279 | expected = null;
280 | }
281 |
282 | QUnit.test(testName, expected, callback, true);
283 | },
284 |
285 | test: function(testName, expected, callback, async) {
286 | var name = '' + escapeInnerText(testName) + '';
287 |
288 | if ( arguments.length === 2 ) {
289 | callback = expected;
290 | expected = null;
291 | }
292 |
293 | if ( config.currentModule ) {
294 | name = '' + config.currentModule + ": " + name;
295 | }
296 |
297 | if ( !validTest(config.currentModule + ": " + testName) ) {
298 | return;
299 | }
300 |
301 | var test = new Test(name, testName, expected, async, callback);
302 | test.module = config.currentModule;
303 | test.moduleTestEnvironment = config.currentModuleTestEnviroment;
304 | test.queue();
305 | },
306 |
307 | // Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through.
308 | expect: function(asserts) {
309 | config.current.expected = asserts;
310 | },
311 |
312 | // Asserts true.
313 | // @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
314 | ok: function(result, msg) {
315 | if (!config.current) {
316 | throw new Error("ok() assertion outside test context, was " + sourceFromStacktrace(2));
317 | }
318 | result = !!result;
319 | var details = {
320 | result: result,
321 | message: msg
322 | };
323 | msg = escapeInnerText(msg || (result ? "okay" : "failed"));
324 | if ( !result ) {
325 | var source = sourceFromStacktrace(2);
326 | if (source) {
327 | details.source = source;
328 | msg += '
Source: | ' + escapeInnerText(source) + ' |
---|
';
329 | }
330 | }
331 | runLoggingCallbacks( 'log', QUnit, details );
332 | config.current.assertions.push({
333 | result: result,
334 | message: msg
335 | });
336 | },
337 |
338 | // Checks that the first two arguments are equal, with an optional message. Prints out both actual and expected values.
339 | // @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." );
340 | equal: function(actual, expected, message) {
341 | QUnit.push(expected == actual, actual, expected, message);
342 | },
343 |
344 | notEqual: function(actual, expected, message) {
345 | QUnit.push(expected != actual, actual, expected, message);
346 | },
347 |
348 | deepEqual: function(actual, expected, message) {
349 | QUnit.push(QUnit.equiv(actual, expected), actual, expected, message);
350 | },
351 |
352 | notDeepEqual: function(actual, expected, message) {
353 | QUnit.push(!QUnit.equiv(actual, expected), actual, expected, message);
354 | },
355 |
356 | strictEqual: function(actual, expected, message) {
357 | QUnit.push(expected === actual, actual, expected, message);
358 | },
359 |
360 | notStrictEqual: function(actual, expected, message) {
361 | QUnit.push(expected !== actual, actual, expected, message);
362 | },
363 |
364 | raises: function(block, expected, message) {
365 | var actual, ok = false;
366 |
367 | if (typeof expected === 'string') {
368 | message = expected;
369 | expected = null;
370 | }
371 |
372 | try {
373 | block();
374 | } catch (e) {
375 | actual = e;
376 | }
377 |
378 | if (actual) {
379 | // we don't want to validate thrown error
380 | if (!expected) {
381 | ok = true;
382 | // expected is a regexp
383 | } else if (QUnit.objectType(expected) === "regexp") {
384 | ok = expected.test(actual);
385 | // expected is a constructor
386 | } else if (actual instanceof expected) {
387 | ok = true;
388 | // expected is a validation function which returns true is validation passed
389 | } else if (expected.call({}, actual) === true) {
390 | ok = true;
391 | }
392 | }
393 |
394 | QUnit.ok(ok, message);
395 | },
396 |
397 | start: function(count) {
398 | config.semaphore -= count || 1;
399 | if (config.semaphore > 0) {
400 | // don't start until equal number of stop-calls
401 | return;
402 | }
403 | if (config.semaphore < 0) {
404 | // ignore if start is called more often then stop
405 | config.semaphore = 0;
406 | }
407 | // A slight delay, to avoid any current callbacks
408 | if ( defined.setTimeout ) {
409 | window.setTimeout(function() {
410 | if (config.semaphore > 0) {
411 | return;
412 | }
413 | if ( config.timeout ) {
414 | clearTimeout(config.timeout);
415 | }
416 |
417 | config.blocking = false;
418 | process(true);
419 | }, 13);
420 | } else {
421 | config.blocking = false;
422 | process(true);
423 | }
424 | },
425 |
426 | stop: function(count) {
427 | config.semaphore += count || 1;
428 | config.blocking = true;
429 |
430 | if ( config.testTimeout && defined.setTimeout ) {
431 | clearTimeout(config.timeout);
432 | config.timeout = window.setTimeout(function() {
433 | QUnit.ok( false, "Test timed out" );
434 | config.semaphore = 1;
435 | QUnit.start();
436 | }, config.testTimeout);
437 | }
438 | }
439 | };
440 |
441 | //We want access to the constructor's prototype
442 | (function() {
443 | function F(){}
444 | F.prototype = QUnit;
445 | QUnit = new F();
446 | //Make F QUnit's constructor so that we can add to the prototype later
447 | QUnit.constructor = F;
448 | }());
449 |
450 | // deprecated; still export them to window to provide clear error messages
451 | // next step: remove entirely
452 | QUnit.equals = function() {
453 | QUnit.push(false, false, false, "QUnit.equals has been deprecated since 2009 (e88049a0), use QUnit.equal instead");
454 | };
455 | QUnit.same = function() {
456 | QUnit.push(false, false, false, "QUnit.same has been deprecated since 2009 (e88049a0), use QUnit.deepEqual instead");
457 | };
458 |
459 | // Maintain internal state
460 | var config = {
461 | // The queue of tests to run
462 | queue: [],
463 |
464 | // block until document ready
465 | blocking: true,
466 |
467 | // when enabled, show only failing tests
468 | // gets persisted through sessionStorage and can be changed in UI via checkbox
469 | hidepassed: false,
470 |
471 | // by default, run previously failed tests first
472 | // very useful in combination with "Hide passed tests" checked
473 | reorder: true,
474 |
475 | // by default, modify document.title when suite is done
476 | altertitle: true,
477 |
478 | urlConfig: ['noglobals', 'notrycatch'],
479 |
480 | //logging callback queues
481 | begin: [],
482 | done: [],
483 | log: [],
484 | testStart: [],
485 | testDone: [],
486 | moduleStart: [],
487 | moduleDone: []
488 | };
489 |
490 | // Load paramaters
491 | (function() {
492 | var location = window.location || { search: "", protocol: "file:" },
493 | params = location.search.slice( 1 ).split( "&" ),
494 | length = params.length,
495 | urlParams = {},
496 | current;
497 |
498 | if ( params[ 0 ] ) {
499 | for ( var i = 0; i < length; i++ ) {
500 | current = params[ i ].split( "=" );
501 | current[ 0 ] = decodeURIComponent( current[ 0 ] );
502 | // allow just a key to turn on a flag, e.g., test.html?noglobals
503 | current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true;
504 | urlParams[ current[ 0 ] ] = current[ 1 ];
505 | }
506 | }
507 |
508 | QUnit.urlParams = urlParams;
509 | config.filter = urlParams.filter;
510 |
511 | // Figure out if we're running the tests from a server or not
512 | QUnit.isLocal = location.protocol === 'file:';
513 | }());
514 |
515 | // Expose the API as global variables, unless an 'exports'
516 | // object exists, in that case we assume we're in CommonJS - export everything at the end
517 | if ( typeof exports === "undefined" || typeof require === "undefined" ) {
518 | extend(window, QUnit);
519 | window.QUnit = QUnit;
520 | }
521 |
522 | // define these after exposing globals to keep them in these QUnit namespace only
523 | extend(QUnit, {
524 | config: config,
525 |
526 | // Initialize the configuration options
527 | init: function() {
528 | extend(config, {
529 | stats: { all: 0, bad: 0 },
530 | moduleStats: { all: 0, bad: 0 },
531 | started: +new Date(),
532 | updateRate: 1000,
533 | blocking: false,
534 | autostart: true,
535 | autorun: false,
536 | filter: "",
537 | queue: [],
538 | semaphore: 0
539 | });
540 |
541 | var qunit = id( "qunit" );
542 | if ( qunit ) {
543 | qunit.innerHTML =
544 | '' +
545 | '' +
546 | '' +
547 | '' +
548 | '
';
549 | }
550 |
551 | var tests = id( "qunit-tests" ),
552 | banner = id( "qunit-banner" ),
553 | result = id( "qunit-testresult" );
554 |
555 | if ( tests ) {
556 | tests.innerHTML = "";
557 | }
558 |
559 | if ( banner ) {
560 | banner.className = "";
561 | }
562 |
563 | if ( result ) {
564 | result.parentNode.removeChild( result );
565 | }
566 |
567 | if ( tests ) {
568 | result = document.createElement( "p" );
569 | result.id = "qunit-testresult";
570 | result.className = "result";
571 | tests.parentNode.insertBefore( result, tests );
572 | result.innerHTML = 'Running...
';
573 | }
574 | },
575 |
576 | // Resets the test setup. Useful for tests that modify the DOM.
577 | // If jQuery is available, uses jQuery's html(), otherwise just innerHTML.
578 | reset: function() {
579 | if ( window.jQuery ) {
580 | jQuery( "#qunit-fixture" ).html( config.fixture );
581 | } else {
582 | var main = id( 'qunit-fixture' );
583 | if ( main ) {
584 | main.innerHTML = config.fixture;
585 | }
586 | }
587 | },
588 |
589 | // Trigger an event on an element.
590 | // @example triggerEvent( document.body, "click" );
591 | triggerEvent: function( elem, type, event ) {
592 | if ( document.createEvent ) {
593 | event = document.createEvent("MouseEvents");
594 | event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView,
595 | 0, 0, 0, 0, 0, false, false, false, false, 0, null);
596 | elem.dispatchEvent( event );
597 |
598 | } else if ( elem.fireEvent ) {
599 | elem.fireEvent("on"+type);
600 | }
601 | },
602 |
603 | // Safe object type checking
604 | is: function( type, obj ) {
605 | return QUnit.objectType( obj ) == type;
606 | },
607 |
608 | objectType: function( obj ) {
609 | if (typeof obj === "undefined") {
610 | return "undefined";
611 |
612 | // consider: typeof null === object
613 | }
614 | if (obj === null) {
615 | return "null";
616 | }
617 |
618 | var type = toString.call( obj ).match(/^\[object\s(.*)\]$/)[1] || '';
619 |
620 | switch (type) {
621 | case 'Number':
622 | if (isNaN(obj)) {
623 | return "nan";
624 | }
625 | return "number";
626 | case 'String':
627 | case 'Boolean':
628 | case 'Array':
629 | case 'Date':
630 | case 'RegExp':
631 | case 'Function':
632 | return type.toLowerCase();
633 | }
634 | if (typeof obj === "object") {
635 | return "object";
636 | }
637 | return undefined;
638 | },
639 |
640 | push: function(result, actual, expected, message) {
641 | if (!config.current) {
642 | throw new Error("assertion outside test context, was " + sourceFromStacktrace());
643 | }
644 | var details = {
645 | result: result,
646 | message: message,
647 | actual: actual,
648 | expected: expected
649 | };
650 |
651 | message = escapeInnerText(message) || (result ? "okay" : "failed");
652 | message = '' + message + "";
653 | var output = message;
654 | if (!result) {
655 | expected = escapeInnerText(QUnit.jsDump.parse(expected));
656 | actual = escapeInnerText(QUnit.jsDump.parse(actual));
657 | output += 'Expected: | ' + expected + ' |
';
658 | if (actual != expected) {
659 | output += 'Result: | ' + actual + ' |
';
660 | output += 'Diff: | ' + QUnit.diff(expected, actual) +' |
';
661 | }
662 | var source = sourceFromStacktrace();
663 | if (source) {
664 | details.source = source;
665 | output += 'Source: | ' + escapeInnerText(source) + ' |
';
666 | }
667 | output += "
";
668 | }
669 |
670 | runLoggingCallbacks( 'log', QUnit, details );
671 |
672 | config.current.assertions.push({
673 | result: !!result,
674 | message: output
675 | });
676 | },
677 |
678 | pushFailure: function(message, source) {
679 | var details = {
680 | result: false,
681 | message: message
682 | };
683 | var output = escapeInnerText(message);
684 | if (source) {
685 | details.source = source;
686 | output += 'Source: | ' + escapeInnerText(source) + ' |
---|
';
687 | }
688 | runLoggingCallbacks( 'log', QUnit, details );
689 | config.current.assertions.push({
690 | result: false,
691 | message: output
692 | });
693 | },
694 |
695 | url: function( params ) {
696 | params = extend( extend( {}, QUnit.urlParams ), params );
697 | var querystring = "?",
698 | key;
699 | for ( key in params ) {
700 | if ( !hasOwn.call( params, key ) ) {
701 | continue;
702 | }
703 | querystring += encodeURIComponent( key ) + "=" +
704 | encodeURIComponent( params[ key ] ) + "&";
705 | }
706 | return window.location.pathname + querystring.slice( 0, -1 );
707 | },
708 |
709 | extend: extend,
710 | id: id,
711 | addEvent: addEvent
712 | });
713 |
714 | //QUnit.constructor is set to the empty F() above so that we can add to it's prototype later
715 | //Doing this allows us to tell if the following methods have been overwritten on the actual
716 | //QUnit object, which is a deprecated way of using the callbacks.
717 | extend(QUnit.constructor.prototype, {
718 | // Logging callbacks; all receive a single argument with the listed properties
719 | // run test/logs.html for any related changes
720 | begin: registerLoggingCallback('begin'),
721 | // done: { failed, passed, total, runtime }
722 | done: registerLoggingCallback('done'),
723 | // log: { result, actual, expected, message }
724 | log: registerLoggingCallback('log'),
725 | // testStart: { name }
726 | testStart: registerLoggingCallback('testStart'),
727 | // testDone: { name, failed, passed, total }
728 | testDone: registerLoggingCallback('testDone'),
729 | // moduleStart: { name }
730 | moduleStart: registerLoggingCallback('moduleStart'),
731 | // moduleDone: { name, failed, passed, total }
732 | moduleDone: registerLoggingCallback('moduleDone')
733 | });
734 |
735 | if ( typeof document === "undefined" || document.readyState === "complete" ) {
736 | config.autorun = true;
737 | }
738 |
739 | QUnit.load = function() {
740 | runLoggingCallbacks( 'begin', QUnit, {} );
741 |
742 | // Initialize the config, saving the execution queue
743 | var oldconfig = extend({}, config);
744 | QUnit.init();
745 | extend(config, oldconfig);
746 |
747 | config.blocking = false;
748 |
749 | var urlConfigHtml = '', len = config.urlConfig.length;
750 | for ( var i = 0, val; i < len; i++ ) {
751 | val = config.urlConfig[i];
752 | config[val] = QUnit.urlParams[val];
753 | urlConfigHtml += '';
754 | }
755 |
756 | var userAgent = id("qunit-userAgent");
757 | if ( userAgent ) {
758 | userAgent.innerHTML = navigator.userAgent;
759 | }
760 | var banner = id("qunit-header");
761 | if ( banner ) {
762 | banner.innerHTML = ' ' + banner.innerHTML + ' ' + urlConfigHtml;
763 | addEvent( banner, "change", function( event ) {
764 | var params = {};
765 | params[ event.target.name ] = event.target.checked ? true : undefined;
766 | window.location = QUnit.url( params );
767 | });
768 | }
769 |
770 | var toolbar = id("qunit-testrunner-toolbar");
771 | if ( toolbar ) {
772 | var filter = document.createElement("input");
773 | filter.type = "checkbox";
774 | filter.id = "qunit-filter-pass";
775 | addEvent( filter, "click", function() {
776 | var ol = document.getElementById("qunit-tests");
777 | if ( filter.checked ) {
778 | ol.className = ol.className + " hidepass";
779 | } else {
780 | var tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " ";
781 | ol.className = tmp.replace(/ hidepass /, " ");
782 | }
783 | if ( defined.sessionStorage ) {
784 | if (filter.checked) {
785 | sessionStorage.setItem("qunit-filter-passed-tests", "true");
786 | } else {
787 | sessionStorage.removeItem("qunit-filter-passed-tests");
788 | }
789 | }
790 | });
791 | if ( config.hidepassed || defined.sessionStorage && sessionStorage.getItem("qunit-filter-passed-tests") ) {
792 | filter.checked = true;
793 | var ol = document.getElementById("qunit-tests");
794 | ol.className = ol.className + " hidepass";
795 | }
796 | toolbar.appendChild( filter );
797 |
798 | var label = document.createElement("label");
799 | label.setAttribute("for", "qunit-filter-pass");
800 | label.innerHTML = "Hide passed tests";
801 | toolbar.appendChild( label );
802 | }
803 |
804 | var main = id('qunit-fixture');
805 | if ( main ) {
806 | config.fixture = main.innerHTML;
807 | }
808 |
809 | if (config.autostart) {
810 | QUnit.start();
811 | }
812 | };
813 |
814 | addEvent(window, "load", QUnit.load);
815 |
816 | // addEvent(window, "error") gives us a useless event object
817 | window.onerror = function( message, file, line ) {
818 | if ( QUnit.config.current ) {
819 | QUnit.pushFailure( message, file + ":" + line );
820 | } else {
821 | QUnit.test( "global failure", function() {
822 | QUnit.pushFailure( message, file + ":" + line );
823 | });
824 | }
825 | };
826 |
827 | function done() {
828 | config.autorun = true;
829 |
830 | // Log the last module results
831 | if ( config.currentModule ) {
832 | runLoggingCallbacks( 'moduleDone', QUnit, {
833 | name: config.currentModule,
834 | failed: config.moduleStats.bad,
835 | passed: config.moduleStats.all - config.moduleStats.bad,
836 | total: config.moduleStats.all
837 | } );
838 | }
839 |
840 | var banner = id("qunit-banner"),
841 | tests = id("qunit-tests"),
842 | runtime = +new Date() - config.started,
843 | passed = config.stats.all - config.stats.bad,
844 | html = [
845 | 'Tests completed in ',
846 | runtime,
847 | ' milliseconds.
',
848 | '',
849 | passed,
850 | ' tests of ',
851 | config.stats.all,
852 | ' passed, ',
853 | config.stats.bad,
854 | ' failed.'
855 | ].join('');
856 |
857 | if ( banner ) {
858 | banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass");
859 | }
860 |
861 | if ( tests ) {
862 | id( "qunit-testresult" ).innerHTML = html;
863 | }
864 |
865 | if ( config.altertitle && typeof document !== "undefined" && document.title ) {
866 | // show ✖ for good, ✔ for bad suite result in title
867 | // use escape sequences in case file gets loaded with non-utf-8-charset
868 | document.title = [
869 | (config.stats.bad ? "\u2716" : "\u2714"),
870 | document.title.replace(/^[\u2714\u2716] /i, "")
871 | ].join(" ");
872 | }
873 |
874 | // clear own sessionStorage items if all tests passed
875 | if ( config.reorder && defined.sessionStorage && config.stats.bad === 0 ) {
876 | for (var key in sessionStorage) {
877 | if (sessionStorage.hasOwnProperty(key) && key.indexOf("qunit-test-") === 0 ) {
878 | sessionStorage.removeItem(key);
879 | }
880 | }
881 | }
882 |
883 | runLoggingCallbacks( 'done', QUnit, {
884 | failed: config.stats.bad,
885 | passed: passed,
886 | total: config.stats.all,
887 | runtime: runtime
888 | } );
889 | }
890 |
891 | function validTest( name ) {
892 | var filter = config.filter,
893 | run = false;
894 |
895 | if ( !filter ) {
896 | return true;
897 | }
898 |
899 | var not = filter.charAt( 0 ) === "!";
900 | if ( not ) {
901 | filter = filter.slice( 1 );
902 | }
903 |
904 | if ( name.indexOf( filter ) !== -1 ) {
905 | return !not;
906 | }
907 |
908 | if ( not ) {
909 | run = true;
910 | }
911 |
912 | return run;
913 | }
914 |
915 | // so far supports only Firefox, Chrome and Opera (buggy)
916 | // could be extended in the future to use something like https://github.com/csnover/TraceKit
917 | function extractStacktrace( e, offset ) {
918 | offset = offset || 3;
919 | if (e.stacktrace) {
920 | // Opera
921 | return e.stacktrace.split("\n")[offset + 3];
922 | } else if (e.stack) {
923 | // Firefox, Chrome
924 | var stack = e.stack.split("\n");
925 | if (/^error$/i.test(stack[0])) {
926 | stack.shift();
927 | }
928 | return stack[offset];
929 | } else if (e.sourceURL) {
930 | // Safari, PhantomJS
931 | // hopefully one day Safari provides actual stacktraces
932 | // exclude useless self-reference for generated Error objects
933 | if ( /qunit.js$/.test( e.sourceURL ) ) {
934 | return;
935 | }
936 | // for actual exceptions, this is useful
937 | return e.sourceURL + ":" + e.line;
938 | }
939 | }
940 | function sourceFromStacktrace(offset) {
941 | try {
942 | throw new Error();
943 | } catch ( e ) {
944 | return extractStacktrace( e, offset );
945 | }
946 | }
947 |
948 | function escapeInnerText(s) {
949 | if (!s) {
950 | return "";
951 | }
952 | s = s + "";
953 | return s.replace(/[\&<>]/g, function(s) {
954 | switch(s) {
955 | case "&": return "&";
956 | case "<": return "<";
957 | case ">": return ">";
958 | default: return s;
959 | }
960 | });
961 | }
962 |
963 | function synchronize( callback, last ) {
964 | config.queue.push( callback );
965 |
966 | if ( config.autorun && !config.blocking ) {
967 | process(last);
968 | }
969 | }
970 |
971 | function process( last ) {
972 | function next() {
973 | process( last );
974 | }
975 | var start = new Date().getTime();
976 | config.depth = config.depth ? config.depth + 1 : 1;
977 |
978 | while ( config.queue.length && !config.blocking ) {
979 | if ( !defined.setTimeout || config.updateRate <= 0 || ( ( new Date().getTime() - start ) < config.updateRate ) ) {
980 | config.queue.shift()();
981 | } else {
982 | window.setTimeout( next, 13 );
983 | break;
984 | }
985 | }
986 | config.depth--;
987 | if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) {
988 | done();
989 | }
990 | }
991 |
992 | function saveGlobal() {
993 | config.pollution = [];
994 |
995 | if ( config.noglobals ) {
996 | for ( var key in window ) {
997 | if ( !hasOwn.call( window, key ) ) {
998 | continue;
999 | }
1000 | config.pollution.push( key );
1001 | }
1002 | }
1003 | }
1004 |
1005 | function checkPollution( name ) {
1006 | var old = config.pollution;
1007 | saveGlobal();
1008 |
1009 | var newGlobals = diff( config.pollution, old );
1010 | if ( newGlobals.length > 0 ) {
1011 | QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join(", ") );
1012 | }
1013 |
1014 | var deletedGlobals = diff( old, config.pollution );
1015 | if ( deletedGlobals.length > 0 ) {
1016 | QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join(", ") );
1017 | }
1018 | }
1019 |
1020 | // returns a new Array with the elements that are in a but not in b
1021 | function diff( a, b ) {
1022 | var result = a.slice();
1023 | for ( var i = 0; i < result.length; i++ ) {
1024 | for ( var j = 0; j < b.length; j++ ) {
1025 | if ( result[i] === b[j] ) {
1026 | result.splice(i, 1);
1027 | i--;
1028 | break;
1029 | }
1030 | }
1031 | }
1032 | return result;
1033 | }
1034 |
1035 | function extend(a, b) {
1036 | for ( var prop in b ) {
1037 | if ( b[prop] === undefined ) {
1038 | delete a[prop];
1039 |
1040 | // Avoid "Member not found" error in IE8 caused by setting window.constructor
1041 | } else if ( prop !== "constructor" || a !== window ) {
1042 | a[prop] = b[prop];
1043 | }
1044 | }
1045 |
1046 | return a;
1047 | }
1048 |
1049 | function addEvent(elem, type, fn) {
1050 | if ( elem.addEventListener ) {
1051 | elem.addEventListener( type, fn, false );
1052 | } else if ( elem.attachEvent ) {
1053 | elem.attachEvent( "on" + type, fn );
1054 | } else {
1055 | fn();
1056 | }
1057 | }
1058 |
1059 | function id(name) {
1060 | return !!(typeof document !== "undefined" && document && document.getElementById) &&
1061 | document.getElementById( name );
1062 | }
1063 |
1064 | function registerLoggingCallback(key){
1065 | return function(callback){
1066 | config[key].push( callback );
1067 | };
1068 | }
1069 |
1070 | // Supports deprecated method of completely overwriting logging callbacks
1071 | function runLoggingCallbacks(key, scope, args) {
1072 | //debugger;
1073 | var callbacks;
1074 | if ( QUnit.hasOwnProperty(key) ) {
1075 | QUnit[key].call(scope, args);
1076 | } else {
1077 | callbacks = config[key];
1078 | for( var i = 0; i < callbacks.length; i++ ) {
1079 | callbacks[i].call( scope, args );
1080 | }
1081 | }
1082 | }
1083 |
1084 | // Test for equality any JavaScript type.
1085 | // Author: Philippe Rathé
1086 | QUnit.equiv = (function() {
1087 |
1088 | var innerEquiv; // the real equiv function
1089 | var callers = []; // stack to decide between skip/abort functions
1090 | var parents = []; // stack to avoiding loops from circular referencing
1091 |
1092 | // Call the o related callback with the given arguments.
1093 | function bindCallbacks(o, callbacks, args) {
1094 | var prop = QUnit.objectType(o);
1095 | if (prop) {
1096 | if (QUnit.objectType(callbacks[prop]) === "function") {
1097 | return callbacks[prop].apply(callbacks, args);
1098 | } else {
1099 | return callbacks[prop]; // or undefined
1100 | }
1101 | }
1102 | }
1103 |
1104 | var getProto = Object.getPrototypeOf || function (obj) {
1105 | return obj.__proto__;
1106 | };
1107 |
1108 | var callbacks = (function () {
1109 |
1110 | // for string, boolean, number and null
1111 | function useStrictEquality(b, a) {
1112 | if (b instanceof a.constructor || a instanceof b.constructor) {
1113 | // to catch short annotaion VS 'new' annotation of a
1114 | // declaration
1115 | // e.g. var i = 1;
1116 | // var j = new Number(1);
1117 | return a == b;
1118 | } else {
1119 | return a === b;
1120 | }
1121 | }
1122 |
1123 | return {
1124 | "string" : useStrictEquality,
1125 | "boolean" : useStrictEquality,
1126 | "number" : useStrictEquality,
1127 | "null" : useStrictEquality,
1128 | "undefined" : useStrictEquality,
1129 |
1130 | "nan" : function(b) {
1131 | return isNaN(b);
1132 | },
1133 |
1134 | "date" : function(b, a) {
1135 | return QUnit.objectType(b) === "date" && a.valueOf() === b.valueOf();
1136 | },
1137 |
1138 | "regexp" : function(b, a) {
1139 | return QUnit.objectType(b) === "regexp" &&
1140 | // the regex itself
1141 | a.source === b.source &&
1142 | // and its modifers
1143 | a.global === b.global &&
1144 | // (gmi) ...
1145 | a.ignoreCase === b.ignoreCase &&
1146 | a.multiline === b.multiline;
1147 | },
1148 |
1149 | // - skip when the property is a method of an instance (OOP)
1150 | // - abort otherwise,
1151 | // initial === would have catch identical references anyway
1152 | "function" : function() {
1153 | var caller = callers[callers.length - 1];
1154 | return caller !== Object && typeof caller !== "undefined";
1155 | },
1156 |
1157 | "array" : function(b, a) {
1158 | var i, j, loop;
1159 | var len;
1160 |
1161 | // b could be an object literal here
1162 | if (QUnit.objectType(b) !== "array") {
1163 | return false;
1164 | }
1165 |
1166 | len = a.length;
1167 | if (len !== b.length) { // safe and faster
1168 | return false;
1169 | }
1170 |
1171 | // track reference to avoid circular references
1172 | parents.push(a);
1173 | for (i = 0; i < len; i++) {
1174 | loop = false;
1175 | for (j = 0; j < parents.length; j++) {
1176 | if (parents[j] === a[i]) {
1177 | loop = true;// dont rewalk array
1178 | }
1179 | }
1180 | if (!loop && !innerEquiv(a[i], b[i])) {
1181 | parents.pop();
1182 | return false;
1183 | }
1184 | }
1185 | parents.pop();
1186 | return true;
1187 | },
1188 |
1189 | "object" : function(b, a) {
1190 | var i, j, loop;
1191 | var eq = true; // unless we can proove it
1192 | var aProperties = [], bProperties = []; // collection of
1193 | // strings
1194 |
1195 | // comparing constructors is more strict than using
1196 | // instanceof
1197 | if (a.constructor !== b.constructor) {
1198 | // Allow objects with no prototype to be equivalent to
1199 | // objects with Object as their constructor.
1200 | if (!((getProto(a) === null && getProto(b) === Object.prototype) ||
1201 | (getProto(b) === null && getProto(a) === Object.prototype)))
1202 | {
1203 | return false;
1204 | }
1205 | }
1206 |
1207 | // stack constructor before traversing properties
1208 | callers.push(a.constructor);
1209 | // track reference to avoid circular references
1210 | parents.push(a);
1211 |
1212 | for (i in a) { // be strict: don't ensures hasOwnProperty
1213 | // and go deep
1214 | loop = false;
1215 | for (j = 0; j < parents.length; j++) {
1216 | if (parents[j] === a[i]) {
1217 | // don't go down the same path twice
1218 | loop = true;
1219 | }
1220 | }
1221 | aProperties.push(i); // collect a's properties
1222 |
1223 | if (!loop && !innerEquiv(a[i], b[i])) {
1224 | eq = false;
1225 | break;
1226 | }
1227 | }
1228 |
1229 | callers.pop(); // unstack, we are done
1230 | parents.pop();
1231 |
1232 | for (i in b) {
1233 | bProperties.push(i); // collect b's properties
1234 | }
1235 |
1236 | // Ensures identical properties name
1237 | return eq && innerEquiv(aProperties.sort(), bProperties.sort());
1238 | }
1239 | };
1240 | }());
1241 |
1242 | innerEquiv = function() { // can take multiple arguments
1243 | var args = Array.prototype.slice.apply(arguments);
1244 | if (args.length < 2) {
1245 | return true; // end transition
1246 | }
1247 |
1248 | return (function(a, b) {
1249 | if (a === b) {
1250 | return true; // catch the most you can
1251 | } else if (a === null || b === null || typeof a === "undefined" ||
1252 | typeof b === "undefined" ||
1253 | QUnit.objectType(a) !== QUnit.objectType(b)) {
1254 | return false; // don't lose time with error prone cases
1255 | } else {
1256 | return bindCallbacks(a, callbacks, [ b, a ]);
1257 | }
1258 |
1259 | // apply transition with (1..n) arguments
1260 | }(args[0], args[1]) && arguments.callee.apply(this, args.splice(1, args.length - 1)));
1261 | };
1262 |
1263 | return innerEquiv;
1264 |
1265 | }());
1266 |
1267 | /**
1268 | * jsDump Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com |
1269 | * http://flesler.blogspot.com Licensed under BSD
1270 | * (http://www.opensource.org/licenses/bsd-license.php) Date: 5/15/2008
1271 | *
1272 | * @projectDescription Advanced and extensible data dumping for Javascript.
1273 | * @version 1.0.0
1274 | * @author Ariel Flesler
1275 | * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html}
1276 | */
1277 | QUnit.jsDump = (function() {
1278 | function quote( str ) {
1279 | return '"' + str.toString().replace(/"/g, '\\"') + '"';
1280 | }
1281 | function literal( o ) {
1282 | return o + '';
1283 | }
1284 | function join( pre, arr, post ) {
1285 | var s = jsDump.separator(),
1286 | base = jsDump.indent(),
1287 | inner = jsDump.indent(1);
1288 | if ( arr.join ) {
1289 | arr = arr.join( ',' + s + inner );
1290 | }
1291 | if ( !arr ) {
1292 | return pre + post;
1293 | }
1294 | return [ pre, inner + arr, base + post ].join(s);
1295 | }
1296 | function array( arr, stack ) {
1297 | var i = arr.length, ret = new Array(i);
1298 | this.up();
1299 | while ( i-- ) {
1300 | ret[i] = this.parse( arr[i] , undefined , stack);
1301 | }
1302 | this.down();
1303 | return join( '[', ret, ']' );
1304 | }
1305 |
1306 | var reName = /^function (\w+)/;
1307 |
1308 | var jsDump = {
1309 | parse: function( obj, type, stack ) { //type is used mostly internally, you can fix a (custom)type in advance
1310 | stack = stack || [ ];
1311 | var parser = this.parsers[ type || this.typeOf(obj) ];
1312 | type = typeof parser;
1313 | var inStack = inArray(obj, stack);
1314 | if (inStack != -1) {
1315 | return 'recursion('+(inStack - stack.length)+')';
1316 | }
1317 | //else
1318 | if (type == 'function') {
1319 | stack.push(obj);
1320 | var res = parser.call( this, obj, stack );
1321 | stack.pop();
1322 | return res;
1323 | }
1324 | // else
1325 | return (type == 'string') ? parser : this.parsers.error;
1326 | },
1327 | typeOf: function( obj ) {
1328 | var type;
1329 | if ( obj === null ) {
1330 | type = "null";
1331 | } else if (typeof obj === "undefined") {
1332 | type = "undefined";
1333 | } else if (QUnit.is("RegExp", obj)) {
1334 | type = "regexp";
1335 | } else if (QUnit.is("Date", obj)) {
1336 | type = "date";
1337 | } else if (QUnit.is("Function", obj)) {
1338 | type = "function";
1339 | } else if (typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined") {
1340 | type = "window";
1341 | } else if (obj.nodeType === 9) {
1342 | type = "document";
1343 | } else if (obj.nodeType) {
1344 | type = "node";
1345 | } else if (
1346 | // native arrays
1347 | toString.call( obj ) === "[object Array]" ||
1348 | // NodeList objects
1349 | ( typeof obj.length === "number" && typeof obj.item !== "undefined" && ( obj.length ? obj.item(0) === obj[0] : ( obj.item( 0 ) === null && typeof obj[0] === "undefined" ) ) )
1350 | ) {
1351 | type = "array";
1352 | } else {
1353 | type = typeof obj;
1354 | }
1355 | return type;
1356 | },
1357 | separator: function() {
1358 | return this.multiline ? this.HTML ? '
' : '\n' : this.HTML ? ' ' : ' ';
1359 | },
1360 | indent: function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing
1361 | if ( !this.multiline ) {
1362 | return '';
1363 | }
1364 | var chr = this.indentChar;
1365 | if ( this.HTML ) {
1366 | chr = chr.replace(/\t/g,' ').replace(/ /g,' ');
1367 | }
1368 | return new Array( this._depth_ + (extra||0) ).join(chr);
1369 | },
1370 | up: function( a ) {
1371 | this._depth_ += a || 1;
1372 | },
1373 | down: function( a ) {
1374 | this._depth_ -= a || 1;
1375 | },
1376 | setParser: function( name, parser ) {
1377 | this.parsers[name] = parser;
1378 | },
1379 | // The next 3 are exposed so you can use them
1380 | quote: quote,
1381 | literal: literal,
1382 | join: join,
1383 | //
1384 | _depth_: 1,
1385 | // This is the list of parsers, to modify them, use jsDump.setParser
1386 | parsers: {
1387 | window: '[Window]',
1388 | document: '[Document]',
1389 | error: '[ERROR]', //when no parser is found, shouldn't happen
1390 | unknown: '[Unknown]',
1391 | 'null': 'null',
1392 | 'undefined': 'undefined',
1393 | 'function': function( fn ) {
1394 | var ret = 'function',
1395 | name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE
1396 | if ( name ) {
1397 | ret += ' ' + name;
1398 | }
1399 | ret += '(';
1400 |
1401 | ret = [ ret, QUnit.jsDump.parse( fn, 'functionArgs' ), '){'].join('');
1402 | return join( ret, QUnit.jsDump.parse(fn,'functionCode'), '}' );
1403 | },
1404 | array: array,
1405 | nodelist: array,
1406 | 'arguments': array,
1407 | object: function( map, stack ) {
1408 | var ret = [ ], keys, key, val, i;
1409 | QUnit.jsDump.up();
1410 | if (Object.keys) {
1411 | keys = Object.keys( map );
1412 | } else {
1413 | keys = [];
1414 | for (key in map) { keys.push( key ); }
1415 | }
1416 | keys.sort();
1417 | for (i = 0; i < keys.length; i++) {
1418 | key = keys[ i ];
1419 | val = map[ key ];
1420 | ret.push( QUnit.jsDump.parse( key, 'key' ) + ': ' + QUnit.jsDump.parse( val, undefined, stack ) );
1421 | }
1422 | QUnit.jsDump.down();
1423 | return join( '{', ret, '}' );
1424 | },
1425 | node: function( node ) {
1426 | var open = QUnit.jsDump.HTML ? '<' : '<',
1427 | close = QUnit.jsDump.HTML ? '>' : '>';
1428 |
1429 | var tag = node.nodeName.toLowerCase(),
1430 | ret = open + tag;
1431 |
1432 | for ( var a in QUnit.jsDump.DOMAttrs ) {
1433 | var val = node[QUnit.jsDump.DOMAttrs[a]];
1434 | if ( val ) {
1435 | ret += ' ' + a + '=' + QUnit.jsDump.parse( val, 'attribute' );
1436 | }
1437 | }
1438 | return ret + close + open + '/' + tag + close;
1439 | },
1440 | functionArgs: function( fn ) {//function calls it internally, it's the arguments part of the function
1441 | var l = fn.length;
1442 | if ( !l ) {
1443 | return '';
1444 | }
1445 |
1446 | var args = new Array(l);
1447 | while ( l-- ) {
1448 | args[l] = String.fromCharCode(97+l);//97 is 'a'
1449 | }
1450 | return ' ' + args.join(', ') + ' ';
1451 | },
1452 | key: quote, //object calls it internally, the key part of an item in a map
1453 | functionCode: '[code]', //function calls it internally, it's the content of the function
1454 | attribute: quote, //node calls it internally, it's an html attribute value
1455 | string: quote,
1456 | date: quote,
1457 | regexp: literal, //regex
1458 | number: literal,
1459 | 'boolean': literal
1460 | },
1461 | DOMAttrs:{//attributes to dump from nodes, name=>realName
1462 | id:'id',
1463 | name:'name',
1464 | 'class':'className'
1465 | },
1466 | HTML:false,//if true, entities are escaped ( <, >, \t, space and \n )
1467 | indentChar:' ',//indentation unit
1468 | multiline:true //if true, items in a collection, are separated by a \n, else just a space.
1469 | };
1470 |
1471 | return jsDump;
1472 | }());
1473 |
1474 | // from Sizzle.js
1475 | function getText( elems ) {
1476 | var ret = "", elem;
1477 |
1478 | for ( var i = 0; elems[i]; i++ ) {
1479 | elem = elems[i];
1480 |
1481 | // Get the text from text nodes and CDATA nodes
1482 | if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
1483 | ret += elem.nodeValue;
1484 |
1485 | // Traverse everything else, except comment nodes
1486 | } else if ( elem.nodeType !== 8 ) {
1487 | ret += getText( elem.childNodes );
1488 | }
1489 | }
1490 |
1491 | return ret;
1492 | }
1493 |
1494 | //from jquery.js
1495 | function inArray( elem, array ) {
1496 | if ( array.indexOf ) {
1497 | return array.indexOf( elem );
1498 | }
1499 |
1500 | for ( var i = 0, length = array.length; i < length; i++ ) {
1501 | if ( array[ i ] === elem ) {
1502 | return i;
1503 | }
1504 | }
1505 |
1506 | return -1;
1507 | }
1508 |
1509 | /*
1510 | * Javascript Diff Algorithm
1511 | * By John Resig (http://ejohn.org/)
1512 | * Modified by Chu Alan "sprite"
1513 | *
1514 | * Released under the MIT license.
1515 | *
1516 | * More Info:
1517 | * http://ejohn.org/projects/javascript-diff-algorithm/
1518 | *
1519 | * Usage: QUnit.diff(expected, actual)
1520 | *
1521 | * QUnit.diff("the quick brown fox jumped over", "the quick fox jumps over") == "the quick brown fox jumped jumps over"
1522 | */
1523 | QUnit.diff = (function() {
1524 | function diff(o, n) {
1525 | var ns = {};
1526 | var os = {};
1527 | var i;
1528 |
1529 | for (i = 0; i < n.length; i++) {
1530 | if (ns[n[i]] == null) {
1531 | ns[n[i]] = {
1532 | rows: [],
1533 | o: null
1534 | };
1535 | }
1536 | ns[n[i]].rows.push(i);
1537 | }
1538 |
1539 | for (i = 0; i < o.length; i++) {
1540 | if (os[o[i]] == null) {
1541 | os[o[i]] = {
1542 | rows: [],
1543 | n: null
1544 | };
1545 | }
1546 | os[o[i]].rows.push(i);
1547 | }
1548 |
1549 | for (i in ns) {
1550 | if ( !hasOwn.call( ns, i ) ) {
1551 | continue;
1552 | }
1553 | if (ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1) {
1554 | n[ns[i].rows[0]] = {
1555 | text: n[ns[i].rows[0]],
1556 | row: os[i].rows[0]
1557 | };
1558 | o[os[i].rows[0]] = {
1559 | text: o[os[i].rows[0]],
1560 | row: ns[i].rows[0]
1561 | };
1562 | }
1563 | }
1564 |
1565 | for (i = 0; i < n.length - 1; i++) {
1566 | if (n[i].text != null && n[i + 1].text == null && n[i].row + 1 < o.length && o[n[i].row + 1].text == null &&
1567 | n[i + 1] == o[n[i].row + 1]) {
1568 | n[i + 1] = {
1569 | text: n[i + 1],
1570 | row: n[i].row + 1
1571 | };
1572 | o[n[i].row + 1] = {
1573 | text: o[n[i].row + 1],
1574 | row: i + 1
1575 | };
1576 | }
1577 | }
1578 |
1579 | for (i = n.length - 1; i > 0; i--) {
1580 | if (n[i].text != null && n[i - 1].text == null && n[i].row > 0 && o[n[i].row - 1].text == null &&
1581 | n[i - 1] == o[n[i].row - 1]) {
1582 | n[i - 1] = {
1583 | text: n[i - 1],
1584 | row: n[i].row - 1
1585 | };
1586 | o[n[i].row - 1] = {
1587 | text: o[n[i].row - 1],
1588 | row: i - 1
1589 | };
1590 | }
1591 | }
1592 |
1593 | return {
1594 | o: o,
1595 | n: n
1596 | };
1597 | }
1598 |
1599 | return function(o, n) {
1600 | o = o.replace(/\s+$/, '');
1601 | n = n.replace(/\s+$/, '');
1602 | var out = diff(o === "" ? [] : o.split(/\s+/), n === "" ? [] : n.split(/\s+/));
1603 |
1604 | var str = "";
1605 | var i;
1606 |
1607 | var oSpace = o.match(/\s+/g);
1608 | if (oSpace == null) {
1609 | oSpace = [" "];
1610 | }
1611 | else {
1612 | oSpace.push(" ");
1613 | }
1614 | var nSpace = n.match(/\s+/g);
1615 | if (nSpace == null) {
1616 | nSpace = [" "];
1617 | }
1618 | else {
1619 | nSpace.push(" ");
1620 | }
1621 |
1622 | if (out.n.length === 0) {
1623 | for (i = 0; i < out.o.length; i++) {
1624 | str += '' + out.o[i] + oSpace[i] + "";
1625 | }
1626 | }
1627 | else {
1628 | if (out.n[0].text == null) {
1629 | for (n = 0; n < out.o.length && out.o[n].text == null; n++) {
1630 | str += '' + out.o[n] + oSpace[n] + "";
1631 | }
1632 | }
1633 |
1634 | for (i = 0; i < out.n.length; i++) {
1635 | if (out.n[i].text == null) {
1636 | str += '' + out.n[i] + nSpace[i] + "";
1637 | }
1638 | else {
1639 | var pre = "";
1640 |
1641 | for (n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++) {
1642 | pre += '' + out.o[n] + oSpace[n] + "";
1643 | }
1644 | str += " " + out.n[i].text + nSpace[i] + pre;
1645 | }
1646 | }
1647 | }
1648 |
1649 | return str;
1650 | };
1651 | }());
1652 |
1653 | // for CommonJS enviroments, export everything
1654 | if ( typeof exports !== "undefined" || typeof require !== "undefined" ) {
1655 | extend(exports, QUnit);
1656 | }
1657 |
1658 | // get at whatever the global object is, like window in browsers
1659 | }( (function() {return this;}.call()) ));
1660 |
--------------------------------------------------------------------------------
/libs/sinon/sinon.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Sinon.JS 1.3.4, 2012/04/16
3 | *
4 | * @author Christian Johansen (christian@cjohansen.no)
5 | *
6 | * (The BSD License)
7 | *
8 | * Copyright (c) 2010-2011, Christian Johansen, christian@cjohansen.no
9 | * All rights reserved.
10 | *
11 | * Redistribution and use in source and binary forms, with or without modification,
12 | * are permitted provided that the following conditions are met:
13 | *
14 | * * Redistributions of source code must retain the above copyright notice,
15 | * this list of conditions and the following disclaimer.
16 | * * Redistributions in binary form must reproduce the above copyright notice,
17 | * this list of conditions and the following disclaimer in the documentation
18 | * and/or other materials provided with the distribution.
19 | * * Neither the name of Christian Johansen nor the names of his contributors
20 | * may be used to endorse or promote products derived from this software
21 | * without specific prior written permission.
22 | *
23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
24 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
25 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
26 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
27 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
29 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
30 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
31 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
32 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33 | */
34 |
35 | "use strict";
36 | var sinon = (function () {
37 | var buster = (function (buster, setTimeout) {
38 | function extend(target) {
39 | if (!target) {
40 | return;
41 | }
42 |
43 | for (var i = 1, l = arguments.length, prop; i < l; ++i) {
44 | for (prop in arguments[i]) {
45 | target[prop] = arguments[i][prop];
46 | }
47 | }
48 |
49 | return target;
50 | }
51 |
52 | var div = typeof document != "undefined" && document.createElement("div");
53 |
54 | return extend(buster, {
55 | bind: function (obj, methOrProp) {
56 | var method = typeof methOrProp == "string" ? obj[methOrProp] : methOrProp;
57 | var args = Array.prototype.slice.call(arguments, 2);
58 |
59 | return function () {
60 | var allArgs = args.concat(Array.prototype.slice.call(arguments));
61 | return method.apply(obj, allArgs);
62 | };
63 | },
64 |
65 | create: (function () {
66 | function F() {}
67 |
68 | return function create(object) {
69 | F.prototype = object;
70 | return new F();
71 | }
72 | }()),
73 |
74 | extend: extend,
75 |
76 | nextTick: function (callback) {
77 | if (typeof process != "undefined" && process.nextTick) {
78 | return process.nextTick(callback);
79 | }
80 |
81 | setTimeout(callback, 0);
82 | },
83 |
84 | functionName: function (func) {
85 | if (!func) return "";
86 | if (func.displayName) return func.displayName;
87 | if (func.name) return func.name;
88 |
89 | var matches = func.toString().match(/function\s+([^\(]+)/m);
90 | return matches && matches[1] || "";
91 | },
92 |
93 | isNode: function (obj) {
94 | if (!div) return false;
95 |
96 | try {
97 | obj.appendChild(div);
98 | obj.removeChild(div);
99 | } catch (e) {
100 | return false;
101 | }
102 |
103 | return true;
104 | },
105 |
106 | isElement: function (obj) {
107 | return obj && buster.isNode(obj) && obj.nodeType === 1;
108 | }
109 | });
110 | }(buster || {}, setTimeout));
111 |
112 | if (typeof module == "object" && typeof require == "function") {
113 | module.exports = buster;
114 | buster.eventEmitter = require("./buster-event-emitter");
115 |
116 | Object.defineProperty(buster, "defineVersionGetter", {
117 | get: function () {
118 | return require("./define-version-getter");
119 | }
120 | });
121 | }
122 | if (typeof buster === "undefined") {
123 | var buster = {};
124 | }
125 |
126 | if (typeof module === "object" && typeof require === "function") {
127 | buster = require("buster-core");
128 | }
129 |
130 | buster.format = buster.format || {};
131 | buster.format.excludeConstructors = ["Object", /^.$/];
132 | buster.format.quoteStrings = true;
133 |
134 | buster.format.ascii = (function () {
135 |
136 | function keys(object) {
137 | var k = Object.keys && Object.keys(object) || [];
138 |
139 | if (k.length == 0) {
140 | for (var prop in object) {
141 | if (object.hasOwnProperty(prop)) {
142 | k.push(prop);
143 | }
144 | }
145 | }
146 |
147 | return k.sort();
148 | }
149 |
150 | function isCircular(object, objects) {
151 | if (typeof object != "object") {
152 | return false;
153 | }
154 |
155 | for (var i = 0, l = objects.length; i < l; ++i) {
156 | if (objects[i] === object) {
157 | return true;
158 | }
159 | }
160 |
161 | return false;
162 | }
163 |
164 | function ascii(object, processed, indent) {
165 | if (typeof object == "string") {
166 | var quote = typeof this.quoteStrings != "boolean" || this.quoteStrings;
167 | return processed || quote ? '"' + object + '"' : object;
168 | }
169 |
170 | if (typeof object == "function" && !(object instanceof RegExp)) {
171 | return ascii.func(object);
172 | }
173 |
174 | processed = processed || [];
175 |
176 | if (isCircular(object, processed)) {
177 | return "[Circular]";
178 | }
179 |
180 | if (Object.prototype.toString.call(object) == "[object Array]") {
181 | return ascii.array.call(this, object);
182 | }
183 |
184 | if (!object) {
185 | return "" + object;
186 | }
187 |
188 | if (buster.isElement(object)) {
189 | return ascii.element(object);
190 | }
191 |
192 | if (typeof object.toString == "function" &&
193 | object.toString !== Object.prototype.toString) {
194 | return object.toString();
195 | }
196 |
197 | return ascii.object.call(this, object, processed, indent);
198 | }
199 |
200 | ascii.func = function (func) {
201 | return "function " + buster.functionName(func) + "() {}";
202 | };
203 |
204 | ascii.array = function (array, processed) {
205 | processed = processed || [];
206 | processed.push(array);
207 | var pieces = [];
208 |
209 | for (var i = 0, l = array.length; i < l; ++i) {
210 | pieces.push(ascii.call(this, array[i], processed));
211 | }
212 |
213 | return "[" + pieces.join(", ") + "]";
214 | };
215 |
216 | ascii.object = function (object, processed, indent) {
217 | processed = processed || [];
218 | processed.push(object);
219 | indent = indent || 0;
220 | var pieces = [], properties = keys(object), prop, str, obj;
221 | var is = "";
222 | var length = 3;
223 |
224 | for (var i = 0, l = indent; i < l; ++i) {
225 | is += " ";
226 | }
227 |
228 | for (i = 0, l = properties.length; i < l; ++i) {
229 | prop = properties[i];
230 | obj = object[prop];
231 |
232 | if (isCircular(obj, processed)) {
233 | str = "[Circular]";
234 | } else {
235 | str = ascii.call(this, obj, processed, indent + 2);
236 | }
237 |
238 | str = (/\s/.test(prop) ? '"' + prop + '"' : prop) + ": " + str;
239 | length += str.length;
240 | pieces.push(str);
241 | }
242 |
243 | var cons = ascii.constructorName.call(this, object);
244 | var prefix = cons ? "[" + cons + "] " : ""
245 |
246 | return (length + indent) > 80 ?
247 | prefix + "{\n " + is + pieces.join(",\n " + is) + "\n" + is + "}" :
248 | prefix + "{ " + pieces.join(", ") + " }";
249 | };
250 |
251 | ascii.element = function (element) {
252 | var tagName = element.tagName.toLowerCase();
253 | var attrs = element.attributes, attribute, pairs = [], attrName;
254 |
255 | for (var i = 0, l = attrs.length; i < l; ++i) {
256 | attribute = attrs.item(i);
257 | attrName = attribute.nodeName.toLowerCase().replace("html:", "");
258 |
259 | if (attrName == "contenteditable" && attribute.nodeValue == "inherit") {
260 | continue;
261 | }
262 |
263 | if (!!attribute.nodeValue) {
264 | pairs.push(attrName + "=\"" + attribute.nodeValue + "\"");
265 | }
266 | }
267 |
268 | var formatted = "<" + tagName + (pairs.length > 0 ? " " : "");
269 | var content = element.innerHTML;
270 |
271 | if (content.length > 20) {
272 | content = content.substr(0, 20) + "[...]";
273 | }
274 |
275 | var res = formatted + pairs.join(" ") + ">" + content + "" + tagName + ">";
276 |
277 | return res.replace(/ contentEditable="inherit"/, "");
278 | };
279 |
280 | ascii.constructorName = function (object) {
281 | var name = buster.functionName(object && object.constructor);
282 | var excludes = this.excludeConstructors || buster.format.excludeConstructors || [];
283 |
284 | for (var i = 0, l = excludes.length; i < l; ++i) {
285 | if (typeof excludes[i] == "string" && excludes[i] == name) {
286 | return "";
287 | } else if (excludes[i].test && excludes[i].test(name)) {
288 | return "";
289 | }
290 | }
291 |
292 | return name;
293 | };
294 |
295 | return ascii;
296 | }());
297 |
298 | if (typeof module != "undefined") {
299 | module.exports = buster.format;
300 | }
301 | /*jslint eqeqeq: false, onevar: false, forin: true, nomen: false, regexp: false, plusplus: false*/
302 | /*global module, require, __dirname, document*/
303 | /**
304 | * Sinon core utilities. For internal use only.
305 | *
306 | * @author Christian Johansen (christian@cjohansen.no)
307 | * @license BSD
308 | *
309 | * Copyright (c) 2010-2011 Christian Johansen
310 | */
311 |
312 | var sinon = (function (buster) {
313 | var div = typeof document != "undefined" && document.createElement("div");
314 | var hasOwn = Object.prototype.hasOwnProperty;
315 |
316 | function isDOMNode(obj) {
317 | var success = false;
318 |
319 | try {
320 | obj.appendChild(div);
321 | success = div.parentNode == obj;
322 | } catch (e) {
323 | return false;
324 | } finally {
325 | try {
326 | obj.removeChild(div);
327 | } catch (e) {
328 | // Remove failed, not much we can do about that
329 | }
330 | }
331 |
332 | return success;
333 | }
334 |
335 | function isElement(obj) {
336 | return div && obj && obj.nodeType === 1 && isDOMNode(obj);
337 | }
338 |
339 | function isFunction(obj) {
340 | return !!(obj && obj.constructor && obj.call && obj.apply);
341 | }
342 |
343 | function mirrorProperties(target, source) {
344 | for (var prop in source) {
345 | if (!hasOwn.call(target, prop)) {
346 | target[prop] = source[prop];
347 | }
348 | }
349 | }
350 |
351 | var sinon = {
352 | wrapMethod: function wrapMethod(object, property, method) {
353 | if (!object) {
354 | throw new TypeError("Should wrap property of object");
355 | }
356 |
357 | if (typeof method != "function") {
358 | throw new TypeError("Method wrapper should be function");
359 | }
360 |
361 | var wrappedMethod = object[property];
362 |
363 | if (!isFunction(wrappedMethod)) {
364 | throw new TypeError("Attempted to wrap " + (typeof wrappedMethod) + " property " +
365 | property + " as function");
366 | }
367 |
368 | if (wrappedMethod.restore && wrappedMethod.restore.sinon) {
369 | throw new TypeError("Attempted to wrap " + property + " which is already wrapped");
370 | }
371 |
372 | if (wrappedMethod.calledBefore) {
373 | var verb = !!wrappedMethod.returns ? "stubbed" : "spied on";
374 | throw new TypeError("Attempted to wrap " + property + " which is already " + verb);
375 | }
376 |
377 | // IE 8 does not support hasOwnProperty on the window object.
378 | var owned = hasOwn.call(object, property);
379 | object[property] = method;
380 | method.displayName = property;
381 |
382 | method.restore = function () {
383 | if(owned) {
384 | object[property] = wrappedMethod;
385 | } else {
386 | delete object[property];
387 | }
388 | };
389 |
390 | method.restore.sinon = true;
391 | mirrorProperties(method, wrappedMethod);
392 |
393 | return method;
394 | },
395 |
396 | extend: function extend(target) {
397 | for (var i = 1, l = arguments.length; i < l; i += 1) {
398 | for (var prop in arguments[i]) {
399 | if (arguments[i].hasOwnProperty(prop)) {
400 | target[prop] = arguments[i][prop];
401 | }
402 |
403 | // DONT ENUM bug, only care about toString
404 | if (arguments[i].hasOwnProperty("toString") &&
405 | arguments[i].toString != target.toString) {
406 | target.toString = arguments[i].toString;
407 | }
408 | }
409 | }
410 |
411 | return target;
412 | },
413 |
414 | create: function create(proto) {
415 | var F = function () {};
416 | F.prototype = proto;
417 | return new F();
418 | },
419 |
420 | deepEqual: function deepEqual(a, b) {
421 | if (typeof a != "object" || typeof b != "object") {
422 | return a === b;
423 | }
424 |
425 | if (isElement(a) || isElement(b)) {
426 | return a === b;
427 | }
428 |
429 | if (a === b) {
430 | return true;
431 | }
432 |
433 | var aString = Object.prototype.toString.call(a);
434 | if (aString != Object.prototype.toString.call(b)) {
435 | return false;
436 | }
437 |
438 | if (aString == "[object Array]") {
439 | if (a.length !== b.length) {
440 | return false;
441 | }
442 |
443 | for (var i = 0, l = a.length; i < l; i += 1) {
444 | if (!deepEqual(a[i], b[i])) {
445 | return false;
446 | }
447 | }
448 |
449 | return true;
450 | }
451 |
452 | var prop, aLength = 0, bLength = 0;
453 |
454 | for (prop in a) {
455 | aLength += 1;
456 |
457 | if (!deepEqual(a[prop], b[prop])) {
458 | return false;
459 | }
460 | }
461 |
462 | for (prop in b) {
463 | bLength += 1;
464 | }
465 |
466 | if (aLength != bLength) {
467 | return false;
468 | }
469 |
470 | return true;
471 | },
472 |
473 | functionName: function functionName(func) {
474 | var name = func.displayName || func.name;
475 |
476 | // Use function decomposition as a last resort to get function
477 | // name. Does not rely on function decomposition to work - if it
478 | // doesn't debugging will be slightly less informative
479 | // (i.e. toString will say 'spy' rather than 'myFunc').
480 | if (!name) {
481 | var matches = func.toString().match(/function ([^\s\(]+)/);
482 | name = matches && matches[1];
483 | }
484 |
485 | return name;
486 | },
487 |
488 | functionToString: function toString() {
489 | if (this.getCall && this.callCount) {
490 | var thisValue, prop, i = this.callCount;
491 |
492 | while (i--) {
493 | thisValue = this.getCall(i).thisValue;
494 |
495 | for (prop in thisValue) {
496 | if (thisValue[prop] === this) {
497 | return prop;
498 | }
499 | }
500 | }
501 | }
502 |
503 | return this.displayName || "sinon fake";
504 | },
505 |
506 | getConfig: function (custom) {
507 | var config = {};
508 | custom = custom || {};
509 | var defaults = sinon.defaultConfig;
510 |
511 | for (var prop in defaults) {
512 | if (defaults.hasOwnProperty(prop)) {
513 | config[prop] = custom.hasOwnProperty(prop) ? custom[prop] : defaults[prop];
514 | }
515 | }
516 |
517 | return config;
518 | },
519 |
520 | format: function (val) {
521 | return "" + val;
522 | },
523 |
524 | defaultConfig: {
525 | injectIntoThis: true,
526 | injectInto: null,
527 | properties: ["spy", "stub", "mock", "clock", "server", "requests"],
528 | useFakeTimers: true,
529 | useFakeServer: true
530 | },
531 |
532 | timesInWords: function timesInWords(count) {
533 | return count == 1 && "once" ||
534 | count == 2 && "twice" ||
535 | count == 3 && "thrice" ||
536 | (count || 0) + " times";
537 | },
538 |
539 | calledInOrder: function (spies) {
540 | for (var i = 1, l = spies.length; i < l; i++) {
541 | if (!spies[i - 1].calledBefore(spies[i])) {
542 | return false;
543 | }
544 | }
545 |
546 | return true;
547 | },
548 |
549 | orderByFirstCall: function (spies) {
550 | return spies.sort(function (a, b) {
551 | // uuid, won't ever be equal
552 | var aCall = a.getCall(0);
553 | var bCall = b.getCall(0);
554 | var aId = aCall && aCall.callId || -1;
555 | var bId = bCall && bCall.callId || -1;
556 |
557 | return aId < bId ? -1 : 1;
558 | });
559 | },
560 |
561 | log: function () {},
562 |
563 | logError: function (label, err) {
564 | var msg = label + " threw exception: "
565 | sinon.log(msg + "[" + err.name + "] " + err.message);
566 | if (err.stack) { sinon.log(err.stack); }
567 |
568 | setTimeout(function () {
569 | err.message = msg + err.message;
570 | throw err;
571 | }, 0);
572 | }
573 | };
574 |
575 | var isNode = typeof module == "object" && typeof require == "function";
576 |
577 | if (isNode) {
578 | try {
579 | buster = { format: require("buster-format") };
580 | } catch (e) {}
581 | module.exports = sinon;
582 | module.exports.spy = require("./sinon/spy");
583 | module.exports.stub = require("./sinon/stub");
584 | module.exports.mock = require("./sinon/mock");
585 | module.exports.collection = require("./sinon/collection");
586 | module.exports.assert = require("./sinon/assert");
587 | module.exports.sandbox = require("./sinon/sandbox");
588 | module.exports.test = require("./sinon/test");
589 | module.exports.testCase = require("./sinon/test_case");
590 | module.exports.assert = require("./sinon/assert");
591 | }
592 |
593 | if (buster) {
594 | var formatter = sinon.create(buster.format);
595 | formatter.quoteStrings = false;
596 | sinon.format = function () {
597 | return formatter.ascii.apply(formatter, arguments);
598 | };
599 | } else if (isNode) {
600 | try {
601 | var util = require("util");
602 | sinon.format = function (value) {
603 | return typeof value == "object" ? util.inspect(value) : value;
604 | };
605 | } catch (e) {
606 | /* Node, but no util module - would be very old, but better safe than
607 | sorry */
608 | }
609 | }
610 |
611 | return sinon;
612 | }(typeof buster == "object" && buster));
613 |
614 | /* @depend ../sinon.js */
615 | /*jslint eqeqeq: false, onevar: false, plusplus: false*/
616 | /*global module, require, sinon*/
617 | /**
618 | * Spy functions
619 | *
620 | * @author Christian Johansen (christian@cjohansen.no)
621 | * @license BSD
622 | *
623 | * Copyright (c) 2010-2011 Christian Johansen
624 | */
625 |
626 | (function (sinon) {
627 | var commonJSModule = typeof module == "object" && typeof require == "function";
628 | var spyCall;
629 | var callId = 0;
630 | var push = [].push;
631 | var slice = Array.prototype.slice;
632 |
633 | if (!sinon && commonJSModule) {
634 | sinon = require("../sinon");
635 | }
636 |
637 | if (!sinon) {
638 | return;
639 | }
640 |
641 | function spy(object, property) {
642 | if (!property && typeof object == "function") {
643 | return spy.create(object);
644 | }
645 |
646 | if (!object || !property) {
647 | return spy.create(function () {});
648 | }
649 |
650 | var method = object[property];
651 | return sinon.wrapMethod(object, property, spy.create(method));
652 | }
653 |
654 | sinon.extend(spy, (function () {
655 |
656 | function delegateToCalls(api, method, matchAny, actual, notCalled) {
657 | api[method] = function () {
658 | if (!this.called) {
659 | if (notCalled) {
660 | return notCalled.apply(this, arguments);
661 | }
662 | return false;
663 | }
664 |
665 | var currentCall;
666 | var matches = 0;
667 |
668 | for (var i = 0, l = this.callCount; i < l; i += 1) {
669 | currentCall = this.getCall(i);
670 |
671 | if (currentCall[actual || method].apply(currentCall, arguments)) {
672 | matches += 1;
673 |
674 | if (matchAny) {
675 | return true;
676 | }
677 | }
678 | }
679 |
680 | return matches === this.callCount;
681 | };
682 | }
683 |
684 | function matchingFake(fakes, args, strict) {
685 | if (!fakes) {
686 | return;
687 | }
688 |
689 | var alen = args.length;
690 |
691 | for (var i = 0, l = fakes.length; i < l; i++) {
692 | if (fakes[i].matches(args, strict)) {
693 | return fakes[i];
694 | }
695 | }
696 | }
697 |
698 | function incrementCallCount() {
699 | this.called = true;
700 | this.callCount += 1;
701 | this.calledOnce = this.callCount == 1;
702 | this.calledTwice = this.callCount == 2;
703 | this.calledThrice = this.callCount == 3;
704 | }
705 |
706 | function createCallProperties() {
707 | this.firstCall = this.getCall(0);
708 | this.secondCall = this.getCall(1);
709 | this.thirdCall = this.getCall(2);
710 | this.lastCall = this.getCall(this.callCount - 1);
711 | }
712 |
713 | var uuid = 0;
714 |
715 | // Public API
716 | var spyApi = {
717 | reset: function () {
718 | this.called = false;
719 | this.calledOnce = false;
720 | this.calledTwice = false;
721 | this.calledThrice = false;
722 | this.callCount = 0;
723 | this.firstCall = null;
724 | this.secondCall = null;
725 | this.thirdCall = null;
726 | this.lastCall = null;
727 | this.args = [];
728 | this.returnValues = [];
729 | this.thisValues = [];
730 | this.exceptions = [];
731 | this.callIds = [];
732 | },
733 |
734 | create: function create(func) {
735 | var name;
736 |
737 | if (typeof func != "function") {
738 | func = function () {};
739 | } else {
740 | name = sinon.functionName(func);
741 | }
742 |
743 | function proxy() {
744 | return proxy.invoke(func, this, slice.call(arguments));
745 | }
746 |
747 | sinon.extend(proxy, spy);
748 | delete proxy.create;
749 | sinon.extend(proxy, func);
750 |
751 | proxy.reset();
752 | proxy.prototype = func.prototype;
753 | proxy.displayName = name || "spy";
754 | proxy.toString = sinon.functionToString;
755 | proxy._create = sinon.spy.create;
756 | proxy.id = "spy#" + uuid++;
757 |
758 | return proxy;
759 | },
760 |
761 | invoke: function invoke(func, thisValue, args) {
762 | var matching = matchingFake(this.fakes, args);
763 | var exception, returnValue;
764 |
765 | incrementCallCount.call(this);
766 | push.call(this.thisValues, thisValue);
767 | push.call(this.args, args);
768 | push.call(this.callIds, callId++);
769 |
770 | try {
771 | if (matching) {
772 | returnValue = matching.invoke(func, thisValue, args);
773 | } else {
774 | returnValue = (this.func || func).apply(thisValue, args);
775 | }
776 | } catch (e) {
777 | push.call(this.returnValues, undefined);
778 | exception = e;
779 | throw e;
780 | } finally {
781 | push.call(this.exceptions, exception);
782 | }
783 |
784 | push.call(this.returnValues, returnValue);
785 |
786 | createCallProperties.call(this);
787 |
788 | return returnValue;
789 | },
790 |
791 | getCall: function getCall(i) {
792 | if (i < 0 || i >= this.callCount) {
793 | return null;
794 | }
795 |
796 | return spyCall.create(this, this.thisValues[i], this.args[i],
797 | this.returnValues[i], this.exceptions[i],
798 | this.callIds[i]);
799 | },
800 |
801 | calledBefore: function calledBefore(spyFn) {
802 | if (!this.called) {
803 | return false;
804 | }
805 |
806 | if (!spyFn.called) {
807 | return true;
808 | }
809 |
810 | return this.callIds[0] < spyFn.callIds[spyFn.callIds.length - 1];
811 | },
812 |
813 | calledAfter: function calledAfter(spyFn) {
814 | if (!this.called || !spyFn.called) {
815 | return false;
816 | }
817 |
818 | return this.callIds[this.callCount - 1] > spyFn.callIds[spyFn.callCount - 1];
819 | },
820 |
821 | withArgs: function () {
822 | var args = slice.call(arguments);
823 |
824 | if (this.fakes) {
825 | var match = matchingFake(this.fakes, args, true);
826 |
827 | if (match) {
828 | return match;
829 | }
830 | } else {
831 | this.fakes = [];
832 | }
833 |
834 | var original = this;
835 | var fake = this._create();
836 | fake.matchingAguments = args;
837 | push.call(this.fakes, fake);
838 |
839 | fake.withArgs = function () {
840 | return original.withArgs.apply(original, arguments);
841 | };
842 |
843 | for (var i = 0; i < this.args.length; i++) {
844 | if (fake.matches(this.args[i])) {
845 | incrementCallCount.call(fake);
846 | push.call(fake.thisValues, this.thisValues[i]);
847 | push.call(fake.args, this.args[i]);
848 | push.call(fake.returnValues, this.returnValues[i]);
849 | push.call(fake.exceptions, this.exceptions[i]);
850 | push.call(fake.callIds, this.callIds[i]);
851 | }
852 | }
853 | createCallProperties.call(fake);
854 |
855 | return fake;
856 | },
857 |
858 | matches: function (args, strict) {
859 | var margs = this.matchingAguments;
860 |
861 | if (margs.length <= args.length &&
862 | sinon.deepEqual(margs, args.slice(0, margs.length))) {
863 | return !strict || margs.length == args.length;
864 | }
865 | },
866 |
867 | printf: function (format) {
868 | var spy = this;
869 | var args = slice.call(arguments, 1);
870 | var formatter;
871 |
872 | return (format || "").replace(/%(.)/g, function (match, specifyer) {
873 | formatter = spyApi.formatters[specifyer];
874 |
875 | if (typeof formatter == "function") {
876 | return formatter.call(null, spy, args);
877 | } else if (!isNaN(parseInt(specifyer), 10)) {
878 | return sinon.format(args[specifyer - 1]);
879 | }
880 |
881 | return "%" + specifyer;
882 | });
883 | }
884 | };
885 |
886 | delegateToCalls(spyApi, "calledOn", true);
887 | delegateToCalls(spyApi, "alwaysCalledOn", false, "calledOn");
888 | delegateToCalls(spyApi, "calledWith", true);
889 | delegateToCalls(spyApi, "alwaysCalledWith", false, "calledWith");
890 | delegateToCalls(spyApi, "calledWithExactly", true);
891 | delegateToCalls(spyApi, "alwaysCalledWithExactly", false, "calledWithExactly");
892 | delegateToCalls(spyApi, "neverCalledWith", false, "notCalledWith",
893 | function () { return true; });
894 | delegateToCalls(spyApi, "threw", true);
895 | delegateToCalls(spyApi, "alwaysThrew", false, "threw");
896 | delegateToCalls(spyApi, "returned", true);
897 | delegateToCalls(spyApi, "alwaysReturned", false, "returned");
898 | delegateToCalls(spyApi, "calledWithNew", true);
899 | delegateToCalls(spyApi, "alwaysCalledWithNew", false, "calledWithNew");
900 | delegateToCalls(spyApi, "callArg", false, "callArgWith", function () {
901 | throw new Error(this.toString() + " cannot call arg since it was not yet invoked.");
902 | });
903 | spyApi.callArgWith = spyApi.callArg;
904 | delegateToCalls(spyApi, "yield", false, "yield", function () {
905 | throw new Error(this.toString() + " cannot yield since it was not yet invoked.");
906 | });
907 | // "invokeCallback" is an alias for "yield" since "yield" is invalid in strict mode.
908 | spyApi.invokeCallback = spyApi.yield;
909 | delegateToCalls(spyApi, "yieldTo", false, "yieldTo", function (property) {
910 | throw new Error(this.toString() + " cannot yield to '" + property +
911 | "' since it was not yet invoked.");
912 | });
913 |
914 | spyApi.formatters = {
915 | "c": function (spy) {
916 | return sinon.timesInWords(spy.callCount);
917 | },
918 |
919 | "n": function (spy) {
920 | return spy.toString();
921 | },
922 |
923 | "C": function (spy) {
924 | var calls = [];
925 |
926 | for (var i = 0, l = spy.callCount; i < l; ++i) {
927 | push.call(calls, " " + spy.getCall(i).toString());
928 | }
929 |
930 | return calls.length > 0 ? "\n" + calls.join("\n") : "";
931 | },
932 |
933 | "t": function (spy) {
934 | var objects = [];
935 |
936 | for (var i = 0, l = spy.callCount; i < l; ++i) {
937 | push.call(objects, sinon.format(spy.thisValues[i]));
938 | }
939 |
940 | return objects.join(", ");
941 | },
942 |
943 | "*": function (spy, args) {
944 | return args.join(", ");
945 | }
946 | };
947 |
948 | return spyApi;
949 | }()));
950 |
951 | spyCall = (function () {
952 |
953 | function throwYieldError(proxy, text, args) {
954 | var msg = sinon.functionName(proxy) + text;
955 | if (args.length) {
956 | msg += " Received [" + slice.call(args).join(", ") + "]";
957 | }
958 | throw new Error(msg);
959 | }
960 |
961 | return {
962 | create: function create(spy, thisValue, args, returnValue, exception, id) {
963 | var proxyCall = sinon.create(spyCall);
964 | delete proxyCall.create;
965 | proxyCall.proxy = spy;
966 | proxyCall.thisValue = thisValue;
967 | proxyCall.args = args;
968 | proxyCall.returnValue = returnValue;
969 | proxyCall.exception = exception;
970 | proxyCall.callId = typeof id == "number" && id || callId++;
971 |
972 | return proxyCall;
973 | },
974 |
975 | calledOn: function calledOn(thisValue) {
976 | return this.thisValue === thisValue;
977 | },
978 |
979 | calledWith: function calledWith() {
980 | for (var i = 0, l = arguments.length; i < l; i += 1) {
981 | if (!sinon.deepEqual(arguments[i], this.args[i])) {
982 | return false;
983 | }
984 | }
985 |
986 | return true;
987 | },
988 |
989 | calledWithExactly: function calledWithExactly() {
990 | return arguments.length == this.args.length &&
991 | this.calledWith.apply(this, arguments);
992 | },
993 |
994 | notCalledWith: function notCalledWith() {
995 | for (var i = 0, l = arguments.length; i < l; i += 1) {
996 | if (!sinon.deepEqual(arguments[i], this.args[i])) {
997 | return true;
998 | }
999 | }
1000 | return false;
1001 | },
1002 |
1003 | returned: function returned(value) {
1004 | return this.returnValue === value;
1005 | },
1006 |
1007 | threw: function threw(error) {
1008 | if (typeof error == "undefined" || !this.exception) {
1009 | return !!this.exception;
1010 | }
1011 |
1012 | if (typeof error == "string") {
1013 | return this.exception.name == error;
1014 | }
1015 |
1016 | return this.exception === error;
1017 | },
1018 |
1019 | calledWithNew: function calledWithNew(thisValue) {
1020 | return this.thisValue instanceof this.proxy;
1021 | },
1022 |
1023 | calledBefore: function (other) {
1024 | return this.callId < other.callId;
1025 | },
1026 |
1027 | calledAfter: function (other) {
1028 | return this.callId > other.callId;
1029 | },
1030 |
1031 | callArg: function (pos) {
1032 | this.args[pos]();
1033 | },
1034 |
1035 | callArgWith: function (pos) {
1036 | var args = slice.call(arguments, 1);
1037 | this.args[pos].apply(null, args);
1038 | },
1039 |
1040 | "yield": function () {
1041 | var args = this.args;
1042 | for (var i = 0, l = args.length; i < l; ++i) {
1043 | if (typeof args[i] === "function") {
1044 | args[i].apply(null, slice.call(arguments));
1045 | return;
1046 | }
1047 | }
1048 | throwYieldError(this.proxy, " cannot yield since no callback was passed.", args);
1049 | },
1050 |
1051 | yieldTo: function (prop) {
1052 | var args = this.args;
1053 | for (var i = 0, l = args.length; i < l; ++i) {
1054 | if (args[i] && typeof args[i][prop] === "function") {
1055 | args[i][prop].apply(null, slice.call(arguments, 1));
1056 | return;
1057 | }
1058 | }
1059 | throwYieldError(this.proxy, " cannot yield to '" + prop +
1060 | "' since no callback was passed.", args);
1061 | },
1062 |
1063 | toString: function () {
1064 | var callStr = this.proxy.toString() + "(";
1065 | var args = [];
1066 |
1067 | for (var i = 0, l = this.args.length; i < l; ++i) {
1068 | push.call(args, sinon.format(this.args[i]));
1069 | }
1070 |
1071 | callStr = callStr + args.join(", ") + ")";
1072 |
1073 | if (typeof this.returnValue != "undefined") {
1074 | callStr += " => " + sinon.format(this.returnValue);
1075 | }
1076 |
1077 | if (this.exception) {
1078 | callStr += " !" + this.exception.name;
1079 |
1080 | if (this.exception.message) {
1081 | callStr += "(" + this.exception.message + ")";
1082 | }
1083 | }
1084 |
1085 | return callStr;
1086 | }
1087 | };
1088 | }());
1089 |
1090 | spy.spyCall = spyCall;
1091 |
1092 | // This steps outside the module sandbox and will be removed
1093 | sinon.spyCall = spyCall;
1094 |
1095 | if (commonJSModule) {
1096 | module.exports = spy;
1097 | } else {
1098 | sinon.spy = spy;
1099 | }
1100 | }(typeof sinon == "object" && sinon || null));
1101 |
1102 | /**
1103 | * @depend ../sinon.js
1104 | * @depend spy.js
1105 | */
1106 | /*jslint eqeqeq: false, onevar: false*/
1107 | /*global module, require, sinon*/
1108 | /**
1109 | * Stub functions
1110 | *
1111 | * @author Christian Johansen (christian@cjohansen.no)
1112 | * @license BSD
1113 | *
1114 | * Copyright (c) 2010-2011 Christian Johansen
1115 | */
1116 |
1117 | (function (sinon) {
1118 | var commonJSModule = typeof module == "object" && typeof require == "function";
1119 |
1120 | if (!sinon && commonJSModule) {
1121 | sinon = require("../sinon");
1122 | }
1123 |
1124 | if (!sinon) {
1125 | return;
1126 | }
1127 |
1128 | function stub(object, property, func) {
1129 | if (!!func && typeof func != "function") {
1130 | throw new TypeError("Custom stub should be function");
1131 | }
1132 |
1133 | var wrapper;
1134 |
1135 | if (func) {
1136 | wrapper = sinon.spy && sinon.spy.create ? sinon.spy.create(func) : func;
1137 | } else {
1138 | wrapper = stub.create();
1139 | }
1140 |
1141 | if (!object && !property) {
1142 | return sinon.stub.create();
1143 | }
1144 |
1145 | if (!property && !!object && typeof object == "object") {
1146 | for (var prop in object) {
1147 | if (typeof object[prop] === "function") {
1148 | stub(object, prop);
1149 | }
1150 | }
1151 |
1152 | return object;
1153 | }
1154 |
1155 | return sinon.wrapMethod(object, property, wrapper);
1156 | }
1157 |
1158 | function getCallback(stub, args) {
1159 | if (stub.callArgAt < 0) {
1160 | for (var i = 0, l = args.length; i < l; ++i) {
1161 | if (!stub.callArgProp && typeof args[i] == "function") {
1162 | return args[i];
1163 | }
1164 |
1165 | if (stub.callArgProp && args[i] &&
1166 | typeof args[i][stub.callArgProp] == "function") {
1167 | return args[i][stub.callArgProp];
1168 | }
1169 | }
1170 |
1171 | return null;
1172 | }
1173 |
1174 | return args[stub.callArgAt];
1175 | }
1176 |
1177 | var join = Array.prototype.join;
1178 |
1179 | function getCallbackError(stub, func, args) {
1180 | if (stub.callArgAt < 0) {
1181 | var msg;
1182 |
1183 | if (stub.callArgProp) {
1184 | msg = sinon.functionName(stub) +
1185 | " expected to yield to '" + stub.callArgProp +
1186 | "', but no object with such a property was passed."
1187 | } else {
1188 | msg = sinon.functionName(stub) +
1189 | " expected to yield, but no callback was passed."
1190 | }
1191 |
1192 | if (args.length > 0) {
1193 | msg += " Received [" + join.call(args, ", ") + "]";
1194 | }
1195 |
1196 | return msg;
1197 | }
1198 |
1199 | return "argument at index " + stub.callArgAt + " is not a function: " + func;
1200 | }
1201 |
1202 | function callCallback(stub, args) {
1203 | if (typeof stub.callArgAt == "number") {
1204 | var func = getCallback(stub, args);
1205 |
1206 | if (typeof func != "function") {
1207 | throw new TypeError(getCallbackError(stub, func, args));
1208 | }
1209 |
1210 | func.apply(stub.callbackContext, stub.callbackArguments);
1211 | }
1212 | }
1213 |
1214 | var uuid = 0;
1215 |
1216 | sinon.extend(stub, (function () {
1217 | var slice = Array.prototype.slice;
1218 |
1219 | function throwsException(error, message) {
1220 | if (typeof error == "string") {
1221 | this.exception = new Error(message || "");
1222 | this.exception.name = error;
1223 | } else if (!error) {
1224 | this.exception = new Error("Error");
1225 | } else {
1226 | this.exception = error;
1227 | }
1228 |
1229 | return this;
1230 | }
1231 |
1232 | return {
1233 | create: function create() {
1234 | var functionStub = function () {
1235 | if (functionStub.exception) {
1236 | throw functionStub.exception;
1237 | } else if (typeof functionStub.returnArgAt == 'number') {
1238 | return arguments[functionStub.returnArgAt];
1239 | }
1240 |
1241 | callCallback(functionStub, arguments);
1242 |
1243 | return functionStub.returnValue;
1244 | };
1245 |
1246 | functionStub.id = "stub#" + uuid++;
1247 | var orig = functionStub;
1248 | functionStub = sinon.spy.create(functionStub);
1249 | functionStub.func = orig;
1250 |
1251 | sinon.extend(functionStub, stub);
1252 | functionStub._create = sinon.stub.create;
1253 | functionStub.displayName = "stub";
1254 | functionStub.toString = sinon.functionToString;
1255 |
1256 | return functionStub;
1257 | },
1258 |
1259 | returns: function returns(value) {
1260 | this.returnValue = value;
1261 |
1262 | return this;
1263 | },
1264 |
1265 | returnsArg: function returnsArg(pos) {
1266 | if (typeof pos != "number") {
1267 | throw new TypeError("argument index is not number");
1268 | }
1269 |
1270 | this.returnArgAt = pos;
1271 |
1272 | return this;
1273 | },
1274 |
1275 | "throws": throwsException,
1276 | throwsException: throwsException,
1277 |
1278 | callsArg: function callsArg(pos) {
1279 | if (typeof pos != "number") {
1280 | throw new TypeError("argument index is not number");
1281 | }
1282 |
1283 | this.callArgAt = pos;
1284 | this.callbackArguments = [];
1285 |
1286 | return this;
1287 | },
1288 |
1289 | callsArgOn: function callsArgOn(pos, context) {
1290 | if (typeof pos != "number") {
1291 | throw new TypeError("argument index is not number");
1292 | }
1293 | if (typeof context != "object") {
1294 | throw new TypeError("argument context is not an object");
1295 | }
1296 |
1297 | this.callArgAt = pos;
1298 | this.callbackArguments = [];
1299 | this.callbackContext = context;
1300 |
1301 | return this;
1302 | },
1303 |
1304 | callsArgWith: function callsArgWith(pos) {
1305 | if (typeof pos != "number") {
1306 | throw new TypeError("argument index is not number");
1307 | }
1308 |
1309 | this.callArgAt = pos;
1310 | this.callbackArguments = slice.call(arguments, 1);
1311 |
1312 | return this;
1313 | },
1314 |
1315 | callsArgOnWith: function callsArgWith(pos, context) {
1316 | if (typeof pos != "number") {
1317 | throw new TypeError("argument index is not number");
1318 | }
1319 | if (typeof context != "object") {
1320 | throw new TypeError("argument context is not an object");
1321 | }
1322 |
1323 | this.callArgAt = pos;
1324 | this.callbackArguments = slice.call(arguments, 2);
1325 | this.callbackContext = context;
1326 |
1327 | return this;
1328 | },
1329 |
1330 | yields: function () {
1331 | this.callArgAt = -1;
1332 | this.callbackArguments = slice.call(arguments, 0);
1333 |
1334 | return this;
1335 | },
1336 |
1337 | yieldsOn: function (context) {
1338 | if (typeof context != "object") {
1339 | throw new TypeError("argument context is not an object");
1340 | }
1341 |
1342 | this.callArgAt = -1;
1343 | this.callbackArguments = slice.call(arguments, 1);
1344 | this.callbackContext = context;
1345 |
1346 | return this;
1347 | },
1348 |
1349 | yieldsTo: function (prop) {
1350 | this.callArgAt = -1;
1351 | this.callArgProp = prop;
1352 | this.callbackArguments = slice.call(arguments, 1);
1353 |
1354 | return this;
1355 | },
1356 |
1357 | yieldsToOn: function (prop, context) {
1358 | if (typeof context != "object") {
1359 | throw new TypeError("argument context is not an object");
1360 | }
1361 |
1362 | this.callArgAt = -1;
1363 | this.callArgProp = prop;
1364 | this.callbackArguments = slice.call(arguments, 2);
1365 | this.callbackContext = context;
1366 |
1367 | return this;
1368 | }
1369 | };
1370 | }()));
1371 |
1372 | if (commonJSModule) {
1373 | module.exports = stub;
1374 | } else {
1375 | sinon.stub = stub;
1376 | }
1377 | }(typeof sinon == "object" && sinon || null));
1378 |
1379 | /**
1380 | * @depend ../sinon.js
1381 | * @depend stub.js
1382 | */
1383 | /*jslint eqeqeq: false, onevar: false, nomen: false*/
1384 | /*global module, require, sinon*/
1385 | /**
1386 | * Mock functions.
1387 | *
1388 | * @author Christian Johansen (christian@cjohansen.no)
1389 | * @license BSD
1390 | *
1391 | * Copyright (c) 2010-2011 Christian Johansen
1392 | */
1393 |
1394 | (function (sinon) {
1395 | var commonJSModule = typeof module == "object" && typeof require == "function";
1396 | var push = [].push;
1397 |
1398 | if (!sinon && commonJSModule) {
1399 | sinon = require("../sinon");
1400 | }
1401 |
1402 | if (!sinon) {
1403 | return;
1404 | }
1405 |
1406 | function mock(object) {
1407 | if (!object) {
1408 | return sinon.expectation.create("Anonymous mock");
1409 | }
1410 |
1411 | return mock.create(object);
1412 | }
1413 |
1414 | sinon.mock = mock;
1415 |
1416 | sinon.extend(mock, (function () {
1417 | function each(collection, callback) {
1418 | if (!collection) {
1419 | return;
1420 | }
1421 |
1422 | for (var i = 0, l = collection.length; i < l; i += 1) {
1423 | callback(collection[i]);
1424 | }
1425 | }
1426 |
1427 | return {
1428 | create: function create(object) {
1429 | if (!object) {
1430 | throw new TypeError("object is null");
1431 | }
1432 |
1433 | var mockObject = sinon.extend({}, mock);
1434 | mockObject.object = object;
1435 | delete mockObject.create;
1436 |
1437 | return mockObject;
1438 | },
1439 |
1440 | expects: function expects(method) {
1441 | if (!method) {
1442 | throw new TypeError("method is falsy");
1443 | }
1444 |
1445 | if (!this.expectations) {
1446 | this.expectations = {};
1447 | this.proxies = [];
1448 | }
1449 |
1450 | if (!this.expectations[method]) {
1451 | this.expectations[method] = [];
1452 | var mockObject = this;
1453 |
1454 | sinon.wrapMethod(this.object, method, function () {
1455 | return mockObject.invokeMethod(method, this, arguments);
1456 | });
1457 |
1458 | push.call(this.proxies, method);
1459 | }
1460 |
1461 | var expectation = sinon.expectation.create(method);
1462 | push.call(this.expectations[method], expectation);
1463 |
1464 | return expectation;
1465 | },
1466 |
1467 | restore: function restore() {
1468 | var object = this.object;
1469 |
1470 | each(this.proxies, function (proxy) {
1471 | if (typeof object[proxy].restore == "function") {
1472 | object[proxy].restore();
1473 | }
1474 | });
1475 | },
1476 |
1477 | verify: function verify() {
1478 | var expectations = this.expectations || {};
1479 | var messages = [], met = [];
1480 |
1481 | each(this.proxies, function (proxy) {
1482 | each(expectations[proxy], function (expectation) {
1483 | if (!expectation.met()) {
1484 | push.call(messages, expectation.toString());
1485 | } else {
1486 | push.call(met, expectation.toString());
1487 | }
1488 | });
1489 | });
1490 |
1491 | this.restore();
1492 |
1493 | if (messages.length > 0) {
1494 | sinon.expectation.fail(messages.concat(met).join("\n"));
1495 | }
1496 |
1497 | return true;
1498 | },
1499 |
1500 | invokeMethod: function invokeMethod(method, thisValue, args) {
1501 | var expectations = this.expectations && this.expectations[method];
1502 | var length = expectations && expectations.length || 0;
1503 |
1504 | for (var i = 0; i < length; i += 1) {
1505 | if (!expectations[i].met() &&
1506 | expectations[i].allowsCall(thisValue, args)) {
1507 | return expectations[i].apply(thisValue, args);
1508 | }
1509 | }
1510 |
1511 | var messages = [];
1512 |
1513 | for (i = 0; i < length; i += 1) {
1514 | push.call(messages, " " + expectations[i].toString());
1515 | }
1516 |
1517 | messages.unshift("Unexpected call: " + sinon.spyCall.toString.call({
1518 | proxy: method,
1519 | args: args
1520 | }));
1521 |
1522 | sinon.expectation.fail(messages.join("\n"));
1523 | }
1524 | };
1525 | }()));
1526 |
1527 | var times = sinon.timesInWords;
1528 |
1529 | sinon.expectation = (function () {
1530 | var slice = Array.prototype.slice;
1531 | var _invoke = sinon.spy.invoke;
1532 |
1533 | function callCountInWords(callCount) {
1534 | if (callCount == 0) {
1535 | return "never called";
1536 | } else {
1537 | return "called " + times(callCount);
1538 | }
1539 | }
1540 |
1541 | function expectedCallCountInWords(expectation) {
1542 | var min = expectation.minCalls;
1543 | var max = expectation.maxCalls;
1544 |
1545 | if (typeof min == "number" && typeof max == "number") {
1546 | var str = times(min);
1547 |
1548 | if (min != max) {
1549 | str = "at least " + str + " and at most " + times(max);
1550 | }
1551 |
1552 | return str;
1553 | }
1554 |
1555 | if (typeof min == "number") {
1556 | return "at least " + times(min);
1557 | }
1558 |
1559 | return "at most " + times(max);
1560 | }
1561 |
1562 | function receivedMinCalls(expectation) {
1563 | var hasMinLimit = typeof expectation.minCalls == "number";
1564 | return !hasMinLimit || expectation.callCount >= expectation.minCalls;
1565 | }
1566 |
1567 | function receivedMaxCalls(expectation) {
1568 | if (typeof expectation.maxCalls != "number") {
1569 | return false;
1570 | }
1571 |
1572 | return expectation.callCount == expectation.maxCalls;
1573 | }
1574 |
1575 | return {
1576 | minCalls: 1,
1577 | maxCalls: 1,
1578 |
1579 | create: function create(methodName) {
1580 | var expectation = sinon.extend(sinon.stub.create(), sinon.expectation);
1581 | delete expectation.create;
1582 | expectation.method = methodName;
1583 |
1584 | return expectation;
1585 | },
1586 |
1587 | invoke: function invoke(func, thisValue, args) {
1588 | this.verifyCallAllowed(thisValue, args);
1589 |
1590 | return _invoke.apply(this, arguments);
1591 | },
1592 |
1593 | atLeast: function atLeast(num) {
1594 | if (typeof num != "number") {
1595 | throw new TypeError("'" + num + "' is not number");
1596 | }
1597 |
1598 | if (!this.limitsSet) {
1599 | this.maxCalls = null;
1600 | this.limitsSet = true;
1601 | }
1602 |
1603 | this.minCalls = num;
1604 |
1605 | return this;
1606 | },
1607 |
1608 | atMost: function atMost(num) {
1609 | if (typeof num != "number") {
1610 | throw new TypeError("'" + num + "' is not number");
1611 | }
1612 |
1613 | if (!this.limitsSet) {
1614 | this.minCalls = null;
1615 | this.limitsSet = true;
1616 | }
1617 |
1618 | this.maxCalls = num;
1619 |
1620 | return this;
1621 | },
1622 |
1623 | never: function never() {
1624 | return this.exactly(0);
1625 | },
1626 |
1627 | once: function once() {
1628 | return this.exactly(1);
1629 | },
1630 |
1631 | twice: function twice() {
1632 | return this.exactly(2);
1633 | },
1634 |
1635 | thrice: function thrice() {
1636 | return this.exactly(3);
1637 | },
1638 |
1639 | exactly: function exactly(num) {
1640 | if (typeof num != "number") {
1641 | throw new TypeError("'" + num + "' is not a number");
1642 | }
1643 |
1644 | this.atLeast(num);
1645 | return this.atMost(num);
1646 | },
1647 |
1648 | met: function met() {
1649 | return !this.failed && receivedMinCalls(this);
1650 | },
1651 |
1652 | verifyCallAllowed: function verifyCallAllowed(thisValue, args) {
1653 | if (receivedMaxCalls(this)) {
1654 | this.failed = true;
1655 | sinon.expectation.fail(this.method + " already called " + times(this.maxCalls));
1656 | }
1657 |
1658 | if ("expectedThis" in this && this.expectedThis !== thisValue) {
1659 | sinon.expectation.fail(this.method + " called with " + thisValue + " as thisValue, expected " +
1660 | this.expectedThis);
1661 | }
1662 |
1663 | if (!("expectedArguments" in this)) {
1664 | return;
1665 | }
1666 |
1667 | if (!args || args.length === 0) {
1668 | sinon.expectation.fail(this.method + " received no arguments, expected " +
1669 | this.expectedArguments.join());
1670 | }
1671 |
1672 | if (args.length < this.expectedArguments.length) {
1673 | sinon.expectation.fail(this.method + " received too few arguments (" + args.join() +
1674 | "), expected " + this.expectedArguments.join());
1675 | }
1676 |
1677 | if (this.expectsExactArgCount &&
1678 | args.length != this.expectedArguments.length) {
1679 | sinon.expectation.fail(this.method + " received too many arguments (" + args.join() +
1680 | "), expected " + this.expectedArguments.join());
1681 | }
1682 |
1683 | for (var i = 0, l = this.expectedArguments.length; i < l; i += 1) {
1684 | if (!sinon.deepEqual(this.expectedArguments[i], args[i])) {
1685 | sinon.expectation.fail(this.method + " received wrong arguments (" + args.join() +
1686 | "), expected " + this.expectedArguments.join());
1687 | }
1688 | }
1689 | },
1690 |
1691 | allowsCall: function allowsCall(thisValue, args) {
1692 | if (this.met()) {
1693 | return false;
1694 | }
1695 |
1696 | if ("expectedThis" in this && this.expectedThis !== thisValue) {
1697 | return false;
1698 | }
1699 |
1700 | if (!("expectedArguments" in this)) {
1701 | return true;
1702 | }
1703 |
1704 | args = args || [];
1705 |
1706 | if (args.length < this.expectedArguments.length) {
1707 | return false;
1708 | }
1709 |
1710 | if (this.expectsExactArgCount &&
1711 | args.length != this.expectedArguments.length) {
1712 | return false;
1713 | }
1714 |
1715 | for (var i = 0, l = this.expectedArguments.length; i < l; i += 1) {
1716 | if (!sinon.deepEqual(this.expectedArguments[i], args[i])) {
1717 | return false;
1718 | }
1719 | }
1720 |
1721 | return true;
1722 | },
1723 |
1724 | withArgs: function withArgs() {
1725 | this.expectedArguments = slice.call(arguments);
1726 | return this;
1727 | },
1728 |
1729 | withExactArgs: function withExactArgs() {
1730 | this.withArgs.apply(this, arguments);
1731 | this.expectsExactArgCount = true;
1732 | return this;
1733 | },
1734 |
1735 | on: function on(thisValue) {
1736 | this.expectedThis = thisValue;
1737 | return this;
1738 | },
1739 |
1740 | toString: function () {
1741 | var args = (this.expectedArguments || []).slice();
1742 |
1743 | if (!this.expectsExactArgCount) {
1744 | push.call(args, "[...]");
1745 | }
1746 |
1747 | var callStr = sinon.spyCall.toString.call({
1748 | proxy: this.method, args: args
1749 | });
1750 |
1751 | var message = callStr.replace(", [...", "[, ...") + " " +
1752 | expectedCallCountInWords(this);
1753 |
1754 | if (this.met()) {
1755 | return "Expectation met: " + message;
1756 | }
1757 |
1758 | return "Expected " + message + " (" +
1759 | callCountInWords(this.callCount) + ")";
1760 | },
1761 |
1762 | verify: function verify() {
1763 | if (!this.met()) {
1764 | sinon.expectation.fail(this.toString());
1765 | }
1766 |
1767 | return true;
1768 | },
1769 |
1770 | fail: function (message) {
1771 | var exception = new Error(message);
1772 | exception.name = "ExpectationError";
1773 |
1774 | throw exception;
1775 | }
1776 | };
1777 | }());
1778 |
1779 | if (commonJSModule) {
1780 | module.exports = mock;
1781 | } else {
1782 | sinon.mock = mock;
1783 | }
1784 | }(typeof sinon == "object" && sinon || null));
1785 |
1786 | /**
1787 | * @depend ../sinon.js
1788 | * @depend stub.js
1789 | * @depend mock.js
1790 | */
1791 | /*jslint eqeqeq: false, onevar: false, forin: true*/
1792 | /*global module, require, sinon*/
1793 | /**
1794 | * Collections of stubs, spies and mocks.
1795 | *
1796 | * @author Christian Johansen (christian@cjohansen.no)
1797 | * @license BSD
1798 | *
1799 | * Copyright (c) 2010-2011 Christian Johansen
1800 | */
1801 |
1802 | (function (sinon) {
1803 | var commonJSModule = typeof module == "object" && typeof require == "function";
1804 | var push = [].push;
1805 |
1806 | if (!sinon && commonJSModule) {
1807 | sinon = require("../sinon");
1808 | }
1809 |
1810 | if (!sinon) {
1811 | return;
1812 | }
1813 |
1814 | function getFakes(fakeCollection) {
1815 | if (!fakeCollection.fakes) {
1816 | fakeCollection.fakes = [];
1817 | }
1818 |
1819 | return fakeCollection.fakes;
1820 | }
1821 |
1822 | function each(fakeCollection, method) {
1823 | var fakes = getFakes(fakeCollection);
1824 |
1825 | for (var i = 0, l = fakes.length; i < l; i += 1) {
1826 | if (typeof fakes[i][method] == "function") {
1827 | fakes[i][method]();
1828 | }
1829 | }
1830 | }
1831 |
1832 | function compact(fakeCollection) {
1833 | var fakes = getFakes(fakeCollection);
1834 | var i = 0;
1835 | while (i < fakes.length) {
1836 | fakes.splice(i, 1);
1837 | }
1838 | }
1839 |
1840 | var collection = {
1841 | verify: function resolve() {
1842 | each(this, "verify");
1843 | },
1844 |
1845 | restore: function restore() {
1846 | each(this, "restore");
1847 | compact(this);
1848 | },
1849 |
1850 | verifyAndRestore: function verifyAndRestore() {
1851 | var exception;
1852 |
1853 | try {
1854 | this.verify();
1855 | } catch (e) {
1856 | exception = e;
1857 | }
1858 |
1859 | this.restore();
1860 |
1861 | if (exception) {
1862 | throw exception;
1863 | }
1864 | },
1865 |
1866 | add: function add(fake) {
1867 | push.call(getFakes(this), fake);
1868 | return fake;
1869 | },
1870 |
1871 | spy: function spy() {
1872 | return this.add(sinon.spy.apply(sinon, arguments));
1873 | },
1874 |
1875 | stub: function stub(object, property, value) {
1876 | if (property) {
1877 | var original = object[property];
1878 |
1879 | if (typeof original != "function") {
1880 | if (!object.hasOwnProperty(property)) {
1881 | throw new TypeError("Cannot stub non-existent own property " + property);
1882 | }
1883 |
1884 | object[property] = value;
1885 |
1886 | return this.add({
1887 | restore: function () {
1888 | object[property] = original;
1889 | }
1890 | });
1891 | }
1892 | }
1893 |
1894 | return this.add(sinon.stub.apply(sinon, arguments));
1895 | },
1896 |
1897 | mock: function mock() {
1898 | return this.add(sinon.mock.apply(sinon, arguments));
1899 | },
1900 |
1901 | inject: function inject(obj) {
1902 | var col = this;
1903 |
1904 | obj.spy = function () {
1905 | return col.spy.apply(col, arguments);
1906 | };
1907 |
1908 | obj.stub = function () {
1909 | return col.stub.apply(col, arguments);
1910 | };
1911 |
1912 | obj.mock = function () {
1913 | return col.mock.apply(col, arguments);
1914 | };
1915 |
1916 | return obj;
1917 | }
1918 | };
1919 |
1920 | if (commonJSModule) {
1921 | module.exports = collection;
1922 | } else {
1923 | sinon.collection = collection;
1924 | }
1925 | }(typeof sinon == "object" && sinon || null));
1926 |
1927 | /*jslint eqeqeq: false, plusplus: false, evil: true, onevar: false, browser: true, forin: false*/
1928 | /*global module, require, window*/
1929 | /**
1930 | * Fake timer API
1931 | * setTimeout
1932 | * setInterval
1933 | * clearTimeout
1934 | * clearInterval
1935 | * tick
1936 | * reset
1937 | * Date
1938 | *
1939 | * Inspired by jsUnitMockTimeOut from JsUnit
1940 | *
1941 | * @author Christian Johansen (christian@cjohansen.no)
1942 | * @license BSD
1943 | *
1944 | * Copyright (c) 2010-2011 Christian Johansen
1945 | */
1946 |
1947 | if (typeof sinon == "undefined") {
1948 | var sinon = {};
1949 | }
1950 |
1951 | (function (global) {
1952 | var id = 1;
1953 |
1954 | function addTimer(args, recurring) {
1955 | if (args.length === 0) {
1956 | throw new Error("Function requires at least 1 parameter");
1957 | }
1958 |
1959 | var toId = id++;
1960 | var delay = args[1] || 0;
1961 |
1962 | if (!this.timeouts) {
1963 | this.timeouts = {};
1964 | }
1965 |
1966 | this.timeouts[toId] = {
1967 | id: toId,
1968 | func: args[0],
1969 | callAt: this.now + delay
1970 | };
1971 |
1972 | if (recurring === true) {
1973 | this.timeouts[toId].interval = delay;
1974 | }
1975 |
1976 | return toId;
1977 | }
1978 |
1979 | function parseTime(str) {
1980 | if (!str) {
1981 | return 0;
1982 | }
1983 |
1984 | var strings = str.split(":");
1985 | var l = strings.length, i = l;
1986 | var ms = 0, parsed;
1987 |
1988 | if (l > 3 || !/^(\d\d:){0,2}\d\d?$/.test(str)) {
1989 | throw new Error("tick only understands numbers and 'h:m:s'");
1990 | }
1991 |
1992 | while (i--) {
1993 | parsed = parseInt(strings[i], 10);
1994 |
1995 | if (parsed >= 60) {
1996 | throw new Error("Invalid time " + str);
1997 | }
1998 |
1999 | ms += parsed * Math.pow(60, (l - i - 1));
2000 | }
2001 |
2002 | return ms * 1000;
2003 | }
2004 |
2005 | function createObject(object) {
2006 | var newObject;
2007 |
2008 | if (Object.create) {
2009 | newObject = Object.create(object);
2010 | } else {
2011 | var F = function () {};
2012 | F.prototype = object;
2013 | newObject = new F();
2014 | }
2015 |
2016 | newObject.Date.clock = newObject;
2017 | return newObject;
2018 | }
2019 |
2020 | sinon.clock = {
2021 | now: 0,
2022 |
2023 | create: function create(now) {
2024 | var clock = createObject(this);
2025 |
2026 | if (typeof now == "number") {
2027 | clock.now = now;
2028 | }
2029 |
2030 | if (!!now && typeof now == "object") {
2031 | throw new TypeError("now should be milliseconds since UNIX epoch");
2032 | }
2033 |
2034 | return clock;
2035 | },
2036 |
2037 | setTimeout: function setTimeout(callback, timeout) {
2038 | return addTimer.call(this, arguments, false);
2039 | },
2040 |
2041 | clearTimeout: function clearTimeout(timerId) {
2042 | if (!this.timeouts) {
2043 | this.timeouts = [];
2044 | }
2045 |
2046 | if (timerId in this.timeouts) {
2047 | delete this.timeouts[timerId];
2048 | }
2049 | },
2050 |
2051 | setInterval: function setInterval(callback, timeout) {
2052 | return addTimer.call(this, arguments, true);
2053 | },
2054 |
2055 | clearInterval: function clearInterval(timerId) {
2056 | this.clearTimeout(timerId);
2057 | },
2058 |
2059 | tick: function tick(ms) {
2060 | ms = typeof ms == "number" ? ms : parseTime(ms);
2061 | var tickFrom = this.now, tickTo = this.now + ms, previous = this.now;
2062 | var timer = this.firstTimerInRange(tickFrom, tickTo);
2063 |
2064 | var firstException;
2065 | while (timer && tickFrom <= tickTo) {
2066 | if (this.timeouts[timer.id]) {
2067 | tickFrom = this.now = timer.callAt;
2068 | try {
2069 | this.callTimer(timer);
2070 | } catch (e) {
2071 | firstException = firstException || e;
2072 | }
2073 | }
2074 |
2075 | timer = this.firstTimerInRange(previous, tickTo);
2076 | previous = tickFrom;
2077 | }
2078 |
2079 | this.now = tickTo;
2080 |
2081 | if (firstException) {
2082 | throw firstException;
2083 | }
2084 | },
2085 |
2086 | firstTimerInRange: function (from, to) {
2087 | var timer, smallest, originalTimer;
2088 |
2089 | for (var id in this.timeouts) {
2090 | if (this.timeouts.hasOwnProperty(id)) {
2091 | if (this.timeouts[id].callAt < from || this.timeouts[id].callAt > to) {
2092 | continue;
2093 | }
2094 |
2095 | if (!smallest || this.timeouts[id].callAt < smallest) {
2096 | originalTimer = this.timeouts[id];
2097 | smallest = this.timeouts[id].callAt;
2098 |
2099 | timer = {
2100 | func: this.timeouts[id].func,
2101 | callAt: this.timeouts[id].callAt,
2102 | interval: this.timeouts[id].interval,
2103 | id: this.timeouts[id].id
2104 | };
2105 | }
2106 | }
2107 | }
2108 |
2109 | return timer || null;
2110 | },
2111 |
2112 | callTimer: function (timer) {
2113 | try {
2114 | if (typeof timer.func == "function") {
2115 | timer.func.call(null);
2116 | } else {
2117 | eval(timer.func);
2118 | }
2119 | } catch (e) {
2120 | var exception = e;
2121 | }
2122 |
2123 | if (!this.timeouts[timer.id]) {
2124 | if (exception) {
2125 | throw exception;
2126 | }
2127 | return;
2128 | }
2129 |
2130 | if (typeof timer.interval == "number") {
2131 | this.timeouts[timer.id].callAt += timer.interval;
2132 | } else {
2133 | delete this.timeouts[timer.id];
2134 | }
2135 |
2136 | if (exception) {
2137 | throw exception;
2138 | }
2139 | },
2140 |
2141 | reset: function reset() {
2142 | this.timeouts = {};
2143 | },
2144 |
2145 | Date: (function () {
2146 | var NativeDate = Date;
2147 |
2148 | function ClockDate(year, month, date, hour, minute, second, ms) {
2149 | // Defensive and verbose to avoid potential harm in passing
2150 | // explicit undefined when user does not pass argument
2151 | switch (arguments.length) {
2152 | case 0:
2153 | return new NativeDate(ClockDate.clock.now);
2154 | case 1:
2155 | return new NativeDate(year);
2156 | case 2:
2157 | return new NativeDate(year, month);
2158 | case 3:
2159 | return new NativeDate(year, month, date);
2160 | case 4:
2161 | return new NativeDate(year, month, date, hour);
2162 | case 5:
2163 | return new NativeDate(year, month, date, hour, minute);
2164 | case 6:
2165 | return new NativeDate(year, month, date, hour, minute, second);
2166 | default:
2167 | return new NativeDate(year, month, date, hour, minute, second, ms);
2168 | }
2169 | }
2170 |
2171 | return mirrorDateProperties(ClockDate, NativeDate);
2172 | }())
2173 | };
2174 |
2175 | function mirrorDateProperties(target, source) {
2176 | if (source.now) {
2177 | target.now = function now() {
2178 | return target.clock.now;
2179 | };
2180 | } else {
2181 | delete target.now;
2182 | }
2183 |
2184 | if (source.toSource) {
2185 | target.toSource = function toSource() {
2186 | return source.toSource();
2187 | };
2188 | } else {
2189 | delete target.toSource;
2190 | }
2191 |
2192 | target.toString = function toString() {
2193 | return source.toString();
2194 | };
2195 |
2196 | target.prototype = source.prototype;
2197 | target.parse = source.parse;
2198 | target.UTC = source.UTC;
2199 | target.prototype.toUTCString = source.prototype.toUTCString;
2200 | return target;
2201 | }
2202 |
2203 | var methods = ["Date", "setTimeout", "setInterval",
2204 | "clearTimeout", "clearInterval"];
2205 |
2206 | function restore() {
2207 | var method;
2208 |
2209 | for (var i = 0, l = this.methods.length; i < l; i++) {
2210 | method = this.methods[i];
2211 | global[method] = this["_" + method];
2212 | }
2213 | }
2214 |
2215 | function stubGlobal(method, clock) {
2216 | clock["_" + method] = global[method];
2217 |
2218 | if (method == "Date") {
2219 | var date = mirrorDateProperties(clock[method], global[method]);
2220 | global[method] = date;
2221 | } else {
2222 | global[method] = function () {
2223 | return clock[method].apply(clock, arguments);
2224 | };
2225 |
2226 | for (var prop in clock[method]) {
2227 | if (clock[method].hasOwnProperty(prop)) {
2228 | global[method][prop] = clock[method][prop];
2229 | }
2230 | }
2231 | }
2232 |
2233 | global[method].clock = clock;
2234 | }
2235 |
2236 | sinon.useFakeTimers = function useFakeTimers(now) {
2237 | var clock = sinon.clock.create(now);
2238 | clock.restore = restore;
2239 | clock.methods = Array.prototype.slice.call(arguments,
2240 | typeof now == "number" ? 1 : 0);
2241 |
2242 | if (clock.methods.length === 0) {
2243 | clock.methods = methods;
2244 | }
2245 |
2246 | for (var i = 0, l = clock.methods.length; i < l; i++) {
2247 | stubGlobal(clock.methods[i], clock);
2248 | }
2249 |
2250 | return clock;
2251 | };
2252 | }(typeof global != "undefined" && typeof global !== "function" ? global : this));
2253 |
2254 | sinon.timers = {
2255 | setTimeout: setTimeout,
2256 | clearTimeout: clearTimeout,
2257 | setInterval: setInterval,
2258 | clearInterval: clearInterval,
2259 | Date: Date
2260 | };
2261 |
2262 | if (typeof module == "object" && typeof require == "function") {
2263 | module.exports = sinon;
2264 | }
2265 |
2266 | /*jslint eqeqeq: false, onevar: false*/
2267 | /*global sinon, module, require, ActiveXObject, XMLHttpRequest, DOMParser*/
2268 | /**
2269 | * Minimal Event interface implementation
2270 | *
2271 | * Original implementation by Sven Fuchs: https://gist.github.com/995028
2272 | * Modifications and tests by Christian Johansen.
2273 | *
2274 | * @author Sven Fuchs (svenfuchs@artweb-design.de)
2275 | * @author Christian Johansen (christian@cjohansen.no)
2276 | * @license BSD
2277 | *
2278 | * Copyright (c) 2011 Sven Fuchs, Christian Johansen
2279 | */
2280 |
2281 | if (typeof sinon == "undefined") {
2282 | this.sinon = {};
2283 | }
2284 |
2285 | (function () {
2286 | var push = [].push;
2287 |
2288 | sinon.Event = function Event(type, bubbles, cancelable) {
2289 | this.initEvent(type, bubbles, cancelable);
2290 | };
2291 |
2292 | sinon.Event.prototype = {
2293 | initEvent: function(type, bubbles, cancelable) {
2294 | this.type = type;
2295 | this.bubbles = bubbles;
2296 | this.cancelable = cancelable;
2297 | },
2298 |
2299 | stopPropagation: function () {},
2300 |
2301 | preventDefault: function () {
2302 | this.defaultPrevented = true;
2303 | }
2304 | };
2305 |
2306 | sinon.EventTarget = {
2307 | addEventListener: function addEventListener(event, listener, useCapture) {
2308 | this.eventListeners = this.eventListeners || {};
2309 | this.eventListeners[event] = this.eventListeners[event] || [];
2310 | push.call(this.eventListeners[event], listener);
2311 | },
2312 |
2313 | removeEventListener: function removeEventListener(event, listener, useCapture) {
2314 | var listeners = this.eventListeners && this.eventListeners[event] || [];
2315 |
2316 | for (var i = 0, l = listeners.length; i < l; ++i) {
2317 | if (listeners[i] == listener) {
2318 | return listeners.splice(i, 1);
2319 | }
2320 | }
2321 | },
2322 |
2323 | dispatchEvent: function dispatchEvent(event) {
2324 | var type = event.type;
2325 | var listeners = this.eventListeners && this.eventListeners[type] || [];
2326 |
2327 | for (var i = 0; i < listeners.length; i++) {
2328 | if (typeof listeners[i] == "function") {
2329 | listeners[i].call(this, event);
2330 | } else {
2331 | listeners[i].handleEvent(event);
2332 | }
2333 | }
2334 |
2335 | return !!event.defaultPrevented;
2336 | }
2337 | };
2338 | }());
2339 |
2340 | /**
2341 | * @depend event.js
2342 | */
2343 | /*jslint eqeqeq: false, onevar: false*/
2344 | /*global sinon, module, require, ActiveXObject, XMLHttpRequest, DOMParser*/
2345 | /**
2346 | * Fake XMLHttpRequest object
2347 | *
2348 | * @author Christian Johansen (christian@cjohansen.no)
2349 | * @license BSD
2350 | *
2351 | * Copyright (c) 2010-2011 Christian Johansen
2352 | */
2353 |
2354 | if (typeof sinon == "undefined") {
2355 | this.sinon = {};
2356 | }
2357 | sinon.xhr = { XMLHttpRequest: this.XMLHttpRequest };
2358 |
2359 | // wrapper for global
2360 | (function(global) {
2361 | var xhr = sinon.xhr;
2362 | xhr.GlobalXMLHttpRequest = global.XMLHttpRequest;
2363 | xhr.GlobalActiveXObject = global.ActiveXObject;
2364 | xhr.supportsActiveX = typeof xhr.GlobalActiveXObject != "undefined";
2365 | xhr.supportsXHR = typeof xhr.GlobalXMLHttpRequest != "undefined";
2366 | xhr.workingXHR = xhr.supportsXHR ? xhr.GlobalXMLHttpRequest : xhr.supportsActiveX
2367 | ? function() { return new xhr.GlobalActiveXObject("MSXML2.XMLHTTP.3.0") } : false;
2368 |
2369 | /*jsl:ignore*/
2370 | var unsafeHeaders = {
2371 | "Accept-Charset": true,
2372 | "Accept-Encoding": true,
2373 | "Connection": true,
2374 | "Content-Length": true,
2375 | "Cookie": true,
2376 | "Cookie2": true,
2377 | "Content-Transfer-Encoding": true,
2378 | "Date": true,
2379 | "Expect": true,
2380 | "Host": true,
2381 | "Keep-Alive": true,
2382 | "Referer": true,
2383 | "TE": true,
2384 | "Trailer": true,
2385 | "Transfer-Encoding": true,
2386 | "Upgrade": true,
2387 | "User-Agent": true,
2388 | "Via": true
2389 | };
2390 | /*jsl:end*/
2391 |
2392 | function FakeXMLHttpRequest() {
2393 | this.readyState = FakeXMLHttpRequest.UNSENT;
2394 | this.requestHeaders = {};
2395 | this.requestBody = null;
2396 | this.status = 0;
2397 | this.statusText = "";
2398 |
2399 | if (typeof FakeXMLHttpRequest.onCreate == "function") {
2400 | FakeXMLHttpRequest.onCreate(this);
2401 | }
2402 | }
2403 |
2404 | function verifyState(xhr) {
2405 | if (xhr.readyState !== FakeXMLHttpRequest.OPENED) {
2406 | throw new Error("INVALID_STATE_ERR");
2407 | }
2408 |
2409 | if (xhr.sendFlag) {
2410 | throw new Error("INVALID_STATE_ERR");
2411 | }
2412 | }
2413 |
2414 | // filtering to enable a white-list version of Sinon FakeXhr,
2415 | // where whitelisted requests are passed through to real XHR
2416 | function each(collection, callback) {
2417 | if (!collection) return;
2418 | for (var i = 0, l = collection.length; i < l; i += 1) {
2419 | callback(collection[i]);
2420 | }
2421 | }
2422 | function some(collection, callback) {
2423 | for (var index = 0; index < collection.length; index++) {
2424 | if(callback(collection[index]) === true) return true;
2425 | };
2426 | return false;
2427 | }
2428 | // largest arity in XHR is 5 - XHR#open
2429 | var apply = function(obj,method,args) {
2430 | switch(args.length) {
2431 | case 0: return obj[method]();
2432 | case 1: return obj[method](args[0]);
2433 | case 2: return obj[method](args[0],args[1]);
2434 | case 3: return obj[method](args[0],args[1],args[2]);
2435 | case 4: return obj[method](args[0],args[1],args[2],args[3]);
2436 | case 5: return obj[method](args[0],args[1],args[2],args[3],args[4]);
2437 | };
2438 | };
2439 |
2440 | FakeXMLHttpRequest.filters = [];
2441 | FakeXMLHttpRequest.addFilter = function(fn) {
2442 | this.filters.push(fn)
2443 | };
2444 | var IE6Re = /MSIE 6/;
2445 | FakeXMLHttpRequest.defake = function(fakeXhr,xhrArgs) {
2446 | var xhr = new sinon.xhr.workingXHR();
2447 | each(["open","setRequestHeader","send","abort","getResponseHeader",
2448 | "getAllResponseHeaders","addEventListener","overrideMimeType","removeEventListener"],
2449 | function(method) {
2450 | fakeXhr[method] = function() {
2451 | return apply(xhr,method,arguments);
2452 | };
2453 | });
2454 |
2455 | var copyAttrs = function(args) {
2456 | each(args, function(attr) {
2457 | try {
2458 | fakeXhr[attr] = xhr[attr]
2459 | } catch(e) {
2460 | if(!IE6Re.test(navigator.userAgent)) throw e;
2461 | }
2462 | });
2463 | };
2464 |
2465 | var stateChange = function() {
2466 | fakeXhr.readyState = xhr.readyState;
2467 | if(xhr.readyState >= FakeXMLHttpRequest.HEADERS_RECEIVED) {
2468 | copyAttrs(["status","statusText"]);
2469 | }
2470 | if(xhr.readyState >= FakeXMLHttpRequest.LOADING) {
2471 | copyAttrs(["responseText"]);
2472 | }
2473 | if(xhr.readyState === FakeXMLHttpRequest.DONE) {
2474 | copyAttrs(["responseXML"]);
2475 | }
2476 | if(fakeXhr.onreadystatechange) fakeXhr.onreadystatechange.call(fakeXhr);
2477 | };
2478 | if(xhr.addEventListener) {
2479 | for(var event in fakeXhr.eventListeners) {
2480 | if(fakeXhr.eventListeners.hasOwnProperty(event)) {
2481 | each(fakeXhr.eventListeners[event],function(handler) {
2482 | xhr.addEventListener(event, handler);
2483 | });
2484 | }
2485 | }
2486 | xhr.addEventListener("readystatechange",stateChange);
2487 | } else {
2488 | xhr.onreadystatechange = stateChange;
2489 | }
2490 | apply(xhr,"open",xhrArgs);
2491 | };
2492 | FakeXMLHttpRequest.useFilters = false;
2493 |
2494 | function verifyRequestSent(xhr) {
2495 | if (xhr.readyState == FakeXMLHttpRequest.DONE) {
2496 | throw new Error("Request done");
2497 | }
2498 | }
2499 |
2500 | function verifyHeadersReceived(xhr) {
2501 | if (xhr.async && xhr.readyState != FakeXMLHttpRequest.HEADERS_RECEIVED) {
2502 | throw new Error("No headers received");
2503 | }
2504 | }
2505 |
2506 | function verifyResponseBodyType(body) {
2507 | if (typeof body != "string") {
2508 | var error = new Error("Attempted to respond to fake XMLHttpRequest with " +
2509 | body + ", which is not a string.");
2510 | error.name = "InvalidBodyException";
2511 | throw error;
2512 | }
2513 | }
2514 |
2515 | sinon.extend(FakeXMLHttpRequest.prototype, sinon.EventTarget, {
2516 | async: true,
2517 |
2518 | open: function open(method, url, async, username, password) {
2519 | this.method = method;
2520 | this.url = url;
2521 | this.async = typeof async == "boolean" ? async : true;
2522 | this.username = username;
2523 | this.password = password;
2524 | this.responseText = null;
2525 | this.responseXML = null;
2526 | this.requestHeaders = {};
2527 | this.sendFlag = false;
2528 | if(sinon.FakeXMLHttpRequest.useFilters === true) {
2529 | var xhrArgs = arguments;
2530 | var defake = some(FakeXMLHttpRequest.filters,function(filter) {
2531 | return filter.apply(this,xhrArgs)
2532 | });
2533 | if (defake) {
2534 | return sinon.FakeXMLHttpRequest.defake(this,arguments);
2535 | }
2536 | }
2537 | this.readyStateChange(FakeXMLHttpRequest.OPENED);
2538 | },
2539 |
2540 | readyStateChange: function readyStateChange(state) {
2541 | this.readyState = state;
2542 |
2543 | if (typeof this.onreadystatechange == "function") {
2544 | try {
2545 | this.onreadystatechange();
2546 | } catch (e) {
2547 | sinon.logError("Fake XHR onreadystatechange handler", e);
2548 | }
2549 | }
2550 |
2551 | this.dispatchEvent(new sinon.Event("readystatechange"));
2552 | },
2553 |
2554 | setRequestHeader: function setRequestHeader(header, value) {
2555 | verifyState(this);
2556 |
2557 | if (unsafeHeaders[header] || /^(Sec-|Proxy-)/.test(header)) {
2558 | throw new Error("Refused to set unsafe header \"" + header + "\"");
2559 | }
2560 |
2561 | if (this.requestHeaders[header]) {
2562 | this.requestHeaders[header] += "," + value;
2563 | } else {
2564 | this.requestHeaders[header] = value;
2565 | }
2566 | },
2567 |
2568 | // Helps testing
2569 | setResponseHeaders: function setResponseHeaders(headers) {
2570 | this.responseHeaders = {};
2571 |
2572 | for (var header in headers) {
2573 | if (headers.hasOwnProperty(header)) {
2574 | this.responseHeaders[header] = headers[header];
2575 | }
2576 | }
2577 |
2578 | if (this.async) {
2579 | this.readyStateChange(FakeXMLHttpRequest.HEADERS_RECEIVED);
2580 | }
2581 | },
2582 |
2583 | // Currently treats ALL data as a DOMString (i.e. no Document)
2584 | send: function send(data) {
2585 | verifyState(this);
2586 |
2587 | if (!/^(get|head)$/i.test(this.method)) {
2588 | if (this.requestHeaders["Content-Type"]) {
2589 | var value = this.requestHeaders["Content-Type"].split(";");
2590 | this.requestHeaders["Content-Type"] = value[0] + ";charset=utf-8";
2591 | } else {
2592 | this.requestHeaders["Content-Type"] = "text/plain;charset=utf-8";
2593 | }
2594 |
2595 | this.requestBody = data;
2596 | }
2597 |
2598 | this.errorFlag = false;
2599 | this.sendFlag = this.async;
2600 | this.readyStateChange(FakeXMLHttpRequest.OPENED);
2601 |
2602 | if (typeof this.onSend == "function") {
2603 | this.onSend(this);
2604 | }
2605 | },
2606 |
2607 | abort: function abort() {
2608 | this.aborted = true;
2609 | this.responseText = null;
2610 | this.errorFlag = true;
2611 | this.requestHeaders = {};
2612 |
2613 | if (this.readyState > sinon.FakeXMLHttpRequest.UNSENT && this.sendFlag) {
2614 | this.readyStateChange(sinon.FakeXMLHttpRequest.DONE);
2615 | this.sendFlag = false;
2616 | }
2617 |
2618 | this.readyState = sinon.FakeXMLHttpRequest.UNSENT;
2619 | },
2620 |
2621 | getResponseHeader: function getResponseHeader(header) {
2622 | if (this.readyState < FakeXMLHttpRequest.HEADERS_RECEIVED) {
2623 | return null;
2624 | }
2625 |
2626 | if (/^Set-Cookie2?$/i.test(header)) {
2627 | return null;
2628 | }
2629 |
2630 | header = header.toLowerCase();
2631 |
2632 | for (var h in this.responseHeaders) {
2633 | if (h.toLowerCase() == header) {
2634 | return this.responseHeaders[h];
2635 | }
2636 | }
2637 |
2638 | return null;
2639 | },
2640 |
2641 | getAllResponseHeaders: function getAllResponseHeaders() {
2642 | if (this.readyState < FakeXMLHttpRequest.HEADERS_RECEIVED) {
2643 | return "";
2644 | }
2645 |
2646 | var headers = "";
2647 |
2648 | for (var header in this.responseHeaders) {
2649 | if (this.responseHeaders.hasOwnProperty(header) &&
2650 | !/^Set-Cookie2?$/i.test(header)) {
2651 | headers += header + ": " + this.responseHeaders[header] + "\r\n";
2652 | }
2653 | }
2654 |
2655 | return headers;
2656 | },
2657 |
2658 | setResponseBody: function setResponseBody(body) {
2659 | verifyRequestSent(this);
2660 | verifyHeadersReceived(this);
2661 | verifyResponseBodyType(body);
2662 |
2663 | var chunkSize = this.chunkSize || 10;
2664 | var index = 0;
2665 | this.responseText = "";
2666 |
2667 | do {
2668 | if (this.async) {
2669 | this.readyStateChange(FakeXMLHttpRequest.LOADING);
2670 | }
2671 |
2672 | this.responseText += body.substring(index, index + chunkSize);
2673 | index += chunkSize;
2674 | } while (index < body.length);
2675 |
2676 | var type = this.getResponseHeader("Content-Type");
2677 |
2678 | if (this.responseText &&
2679 | (!type || /(text\/xml)|(application\/xml)|(\+xml)/.test(type))) {
2680 | try {
2681 | this.responseXML = FakeXMLHttpRequest.parseXML(this.responseText);
2682 | } catch (e) {
2683 | // Unable to parse XML - no biggie
2684 | }
2685 | }
2686 |
2687 | if (this.async) {
2688 | this.readyStateChange(FakeXMLHttpRequest.DONE);
2689 | } else {
2690 | this.readyState = FakeXMLHttpRequest.DONE;
2691 | }
2692 | },
2693 |
2694 | respond: function respond(status, headers, body) {
2695 | this.setResponseHeaders(headers || {});
2696 | this.status = typeof status == "number" ? status : 200;
2697 | this.statusText = FakeXMLHttpRequest.statusCodes[this.status];
2698 | this.setResponseBody(body || "");
2699 | }
2700 | });
2701 |
2702 | sinon.extend(FakeXMLHttpRequest, {
2703 | UNSENT: 0,
2704 | OPENED: 1,
2705 | HEADERS_RECEIVED: 2,
2706 | LOADING: 3,
2707 | DONE: 4
2708 | });
2709 |
2710 | // Borrowed from JSpec
2711 | FakeXMLHttpRequest.parseXML = function parseXML(text) {
2712 | var xmlDoc;
2713 |
2714 | if (typeof DOMParser != "undefined") {
2715 | var parser = new DOMParser();
2716 | xmlDoc = parser.parseFromString(text, "text/xml");
2717 | } else {
2718 | xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
2719 | xmlDoc.async = "false";
2720 | xmlDoc.loadXML(text);
2721 | }
2722 |
2723 | return xmlDoc;
2724 | };
2725 |
2726 | FakeXMLHttpRequest.statusCodes = {
2727 | 100: "Continue",
2728 | 101: "Switching Protocols",
2729 | 200: "OK",
2730 | 201: "Created",
2731 | 202: "Accepted",
2732 | 203: "Non-Authoritative Information",
2733 | 204: "No Content",
2734 | 205: "Reset Content",
2735 | 206: "Partial Content",
2736 | 300: "Multiple Choice",
2737 | 301: "Moved Permanently",
2738 | 302: "Found",
2739 | 303: "See Other",
2740 | 304: "Not Modified",
2741 | 305: "Use Proxy",
2742 | 307: "Temporary Redirect",
2743 | 400: "Bad Request",
2744 | 401: "Unauthorized",
2745 | 402: "Payment Required",
2746 | 403: "Forbidden",
2747 | 404: "Not Found",
2748 | 405: "Method Not Allowed",
2749 | 406: "Not Acceptable",
2750 | 407: "Proxy Authentication Required",
2751 | 408: "Request Timeout",
2752 | 409: "Conflict",
2753 | 410: "Gone",
2754 | 411: "Length Required",
2755 | 412: "Precondition Failed",
2756 | 413: "Request Entity Too Large",
2757 | 414: "Request-URI Too Long",
2758 | 415: "Unsupported Media Type",
2759 | 416: "Requested Range Not Satisfiable",
2760 | 417: "Expectation Failed",
2761 | 422: "Unprocessable Entity",
2762 | 500: "Internal Server Error",
2763 | 501: "Not Implemented",
2764 | 502: "Bad Gateway",
2765 | 503: "Service Unavailable",
2766 | 504: "Gateway Timeout",
2767 | 505: "HTTP Version Not Supported"
2768 | };
2769 |
2770 | sinon.useFakeXMLHttpRequest = function () {
2771 | sinon.FakeXMLHttpRequest.restore = function restore(keepOnCreate) {
2772 | if (xhr.supportsXHR) {
2773 | global.XMLHttpRequest = xhr.GlobalXMLHttpRequest;
2774 | }
2775 |
2776 | if (xhr.supportsActiveX) {
2777 | global.ActiveXObject = xhr.GlobalActiveXObject;
2778 | }
2779 |
2780 | delete sinon.FakeXMLHttpRequest.restore;
2781 |
2782 | if (keepOnCreate !== true) {
2783 | delete sinon.FakeXMLHttpRequest.onCreate;
2784 | }
2785 | };
2786 | if (xhr.supportsXHR) {
2787 | global.XMLHttpRequest = sinon.FakeXMLHttpRequest;
2788 | }
2789 |
2790 | if (xhr.supportsActiveX) {
2791 | global.ActiveXObject = function ActiveXObject(objId) {
2792 | if (objId == "Microsoft.XMLHTTP" || /^Msxml2\.XMLHTTP/i.test(objId)) {
2793 |
2794 | return new sinon.FakeXMLHttpRequest();
2795 | }
2796 |
2797 | return new xhr.GlobalActiveXObject(objId);
2798 | };
2799 | }
2800 |
2801 | return sinon.FakeXMLHttpRequest;
2802 | };
2803 |
2804 | sinon.FakeXMLHttpRequest = FakeXMLHttpRequest;
2805 | })(this);
2806 |
2807 | if (typeof module == "object" && typeof require == "function") {
2808 | module.exports = sinon;
2809 | }
2810 |
2811 | /**
2812 | * @depend fake_xml_http_request.js
2813 | */
2814 | /*jslint eqeqeq: false, onevar: false, regexp: false, plusplus: false*/
2815 | /*global module, require, window*/
2816 | /**
2817 | * The Sinon "server" mimics a web server that receives requests from
2818 | * sinon.FakeXMLHttpRequest and provides an API to respond to those requests,
2819 | * both synchronously and asynchronously. To respond synchronuously, canned
2820 | * answers have to be provided upfront.
2821 | *
2822 | * @author Christian Johansen (christian@cjohansen.no)
2823 | * @license BSD
2824 | *
2825 | * Copyright (c) 2010-2011 Christian Johansen
2826 | */
2827 |
2828 | if (typeof sinon == "undefined") {
2829 | var sinon = {};
2830 | }
2831 |
2832 | sinon.fakeServer = (function () {
2833 | var push = [].push;
2834 | function F() {}
2835 |
2836 | function create(proto) {
2837 | F.prototype = proto;
2838 | return new F();
2839 | }
2840 |
2841 | function responseArray(handler) {
2842 | var response = handler;
2843 |
2844 | if (Object.prototype.toString.call(handler) != "[object Array]") {
2845 | response = [200, {}, handler];
2846 | }
2847 |
2848 | if (typeof response[2] != "string") {
2849 | throw new TypeError("Fake server response body should be string, but was " +
2850 | typeof response[2]);
2851 | }
2852 |
2853 | return response;
2854 | }
2855 |
2856 | var wloc = window.location;
2857 | var rCurrLoc = new RegExp("^" + wloc.protocol + "//" + wloc.host);
2858 |
2859 | function matchOne(response, reqMethod, reqUrl) {
2860 | var rmeth = response.method;
2861 | var matchMethod = !rmeth || rmeth.toLowerCase() == reqMethod.toLowerCase();
2862 | var url = response.url;
2863 | var matchUrl = !url || url == reqUrl || (typeof url.test == "function" && url.test(reqUrl));
2864 |
2865 | return matchMethod && matchUrl;
2866 | }
2867 |
2868 | function match(response, request) {
2869 | var requestMethod = this.getHTTPMethod(request);
2870 | var requestUrl = request.url;
2871 |
2872 | if (!/^https?:\/\//.test(requestUrl) || rCurrLoc.test(requestUrl)) {
2873 | requestUrl = requestUrl.replace(rCurrLoc, "");
2874 | }
2875 |
2876 | if (matchOne(response, this.getHTTPMethod(request), requestUrl)) {
2877 | if (typeof response.response == "function") {
2878 | var ru = response.url;
2879 | var args = [request].concat(!ru ? [] : requestUrl.match(ru).slice(1));
2880 | return response.response.apply(response, args);
2881 | }
2882 |
2883 | return true;
2884 | }
2885 |
2886 | return false;
2887 | }
2888 |
2889 | return {
2890 | create: function () {
2891 | var server = create(this);
2892 | this.xhr = sinon.useFakeXMLHttpRequest();
2893 | server.requests = [];
2894 |
2895 | this.xhr.onCreate = function (xhrObj) {
2896 | server.addRequest(xhrObj);
2897 | };
2898 |
2899 | return server;
2900 | },
2901 |
2902 | addRequest: function addRequest(xhrObj) {
2903 | var server = this;
2904 | push.call(this.requests, xhrObj);
2905 |
2906 | xhrObj.onSend = function () {
2907 | server.handleRequest(this);
2908 | };
2909 |
2910 | if (this.autoRespond && !this.responding) {
2911 | setTimeout(function () {
2912 | server.responding = false;
2913 | server.respond();
2914 | }, this.autoRespondAfter || 10);
2915 |
2916 | this.responding = true;
2917 | }
2918 | },
2919 |
2920 | getHTTPMethod: function getHTTPMethod(request) {
2921 | if (this.fakeHTTPMethods && /post/i.test(request.method)) {
2922 | var matches = (request.requestBody || "").match(/_method=([^\b;]+)/);
2923 | return !!matches ? matches[1] : request.method;
2924 | }
2925 |
2926 | return request.method;
2927 | },
2928 |
2929 | handleRequest: function handleRequest(xhr) {
2930 | if (xhr.async) {
2931 | if (!this.queue) {
2932 | this.queue = [];
2933 | }
2934 |
2935 | push.call(this.queue, xhr);
2936 | } else {
2937 | this.processRequest(xhr);
2938 | }
2939 | },
2940 |
2941 | respondWith: function respondWith(method, url, body) {
2942 | if (arguments.length == 1 && typeof method != "function") {
2943 | this.response = responseArray(method);
2944 | return;
2945 | }
2946 |
2947 | if (!this.responses) { this.responses = []; }
2948 |
2949 | if (arguments.length == 1) {
2950 | body = method;
2951 | url = method = null;
2952 | }
2953 |
2954 | if (arguments.length == 2) {
2955 | body = url;
2956 | url = method;
2957 | method = null;
2958 | }
2959 |
2960 | push.call(this.responses, {
2961 | method: method,
2962 | url: url,
2963 | response: typeof body == "function" ? body : responseArray(body)
2964 | });
2965 | },
2966 |
2967 | respond: function respond() {
2968 | if (arguments.length > 0) this.respondWith.apply(this, arguments);
2969 | var queue = this.queue || [];
2970 | var request;
2971 |
2972 | while(request = queue.shift()) {
2973 | this.processRequest(request);
2974 | }
2975 | },
2976 |
2977 | processRequest: function processRequest(request) {
2978 | try {
2979 | if (request.aborted) {
2980 | return;
2981 | }
2982 |
2983 | var response = this.response || [404, {}, ""];
2984 |
2985 | if (this.responses) {
2986 | for (var i = 0, l = this.responses.length; i < l; i++) {
2987 | if (match.call(this, this.responses[i], request)) {
2988 | response = this.responses[i].response;
2989 | break;
2990 | }
2991 | }
2992 | }
2993 |
2994 | if (request.readyState != 4) {
2995 | request.respond(response[0], response[1], response[2]);
2996 | }
2997 | } catch (e) {
2998 | sinon.logError("Fake server request processing", e);
2999 | }
3000 | },
3001 |
3002 | restore: function restore() {
3003 | return this.xhr.restore && this.xhr.restore.apply(this.xhr, arguments);
3004 | }
3005 | };
3006 | }());
3007 |
3008 | if (typeof module == "object" && typeof require == "function") {
3009 | module.exports = sinon;
3010 | }
3011 |
3012 | /**
3013 | * @depend fake_server.js
3014 | * @depend fake_timers.js
3015 | */
3016 | /*jslint browser: true, eqeqeq: false, onevar: false*/
3017 | /*global sinon*/
3018 | /**
3019 | * Add-on for sinon.fakeServer that automatically handles a fake timer along with
3020 | * the FakeXMLHttpRequest. The direct inspiration for this add-on is jQuery
3021 | * 1.3.x, which does not use xhr object's onreadystatehandler at all - instead,
3022 | * it polls the object for completion with setInterval. Dispite the direct
3023 | * motivation, there is nothing jQuery-specific in this file, so it can be used
3024 | * in any environment where the ajax implementation depends on setInterval or
3025 | * setTimeout.
3026 | *
3027 | * @author Christian Johansen (christian@cjohansen.no)
3028 | * @license BSD
3029 | *
3030 | * Copyright (c) 2010-2011 Christian Johansen
3031 | */
3032 |
3033 | (function () {
3034 | function Server() {}
3035 | Server.prototype = sinon.fakeServer;
3036 |
3037 | sinon.fakeServerWithClock = new Server();
3038 |
3039 | sinon.fakeServerWithClock.addRequest = function addRequest(xhr) {
3040 | if (xhr.async) {
3041 | if (typeof setTimeout.clock == "object") {
3042 | this.clock = setTimeout.clock;
3043 | } else {
3044 | this.clock = sinon.useFakeTimers();
3045 | this.resetClock = true;
3046 | }
3047 |
3048 | if (!this.longestTimeout) {
3049 | var clockSetTimeout = this.clock.setTimeout;
3050 | var clockSetInterval = this.clock.setInterval;
3051 | var server = this;
3052 |
3053 | this.clock.setTimeout = function (fn, timeout) {
3054 | server.longestTimeout = Math.max(timeout, server.longestTimeout || 0);
3055 |
3056 | return clockSetTimeout.apply(this, arguments);
3057 | };
3058 |
3059 | this.clock.setInterval = function (fn, timeout) {
3060 | server.longestTimeout = Math.max(timeout, server.longestTimeout || 0);
3061 |
3062 | return clockSetInterval.apply(this, arguments);
3063 | };
3064 | }
3065 | }
3066 |
3067 | return sinon.fakeServer.addRequest.call(this, xhr);
3068 | };
3069 |
3070 | sinon.fakeServerWithClock.respond = function respond() {
3071 | var returnVal = sinon.fakeServer.respond.apply(this, arguments);
3072 |
3073 | if (this.clock) {
3074 | this.clock.tick(this.longestTimeout || 0);
3075 | this.longestTimeout = 0;
3076 |
3077 | if (this.resetClock) {
3078 | this.clock.restore();
3079 | this.resetClock = false;
3080 | }
3081 | }
3082 |
3083 | return returnVal;
3084 | };
3085 |
3086 | sinon.fakeServerWithClock.restore = function restore() {
3087 | if (this.clock) {
3088 | this.clock.restore();
3089 | }
3090 |
3091 | return sinon.fakeServer.restore.apply(this, arguments);
3092 | };
3093 | }());
3094 |
3095 | /**
3096 | * @depend ../sinon.js
3097 | * @depend collection.js
3098 | * @depend util/fake_timers.js
3099 | * @depend util/fake_server_with_clock.js
3100 | */
3101 | /*jslint eqeqeq: false, onevar: false, plusplus: false*/
3102 | /*global require, module*/
3103 | /**
3104 | * Manages fake collections as well as fake utilities such as Sinon's
3105 | * timers and fake XHR implementation in one convenient object.
3106 | *
3107 | * @author Christian Johansen (christian@cjohansen.no)
3108 | * @license BSD
3109 | *
3110 | * Copyright (c) 2010-2011 Christian Johansen
3111 | */
3112 |
3113 | if (typeof module == "object" && typeof require == "function") {
3114 | var sinon = require("../sinon");
3115 | sinon.extend(sinon, require("./util/fake_timers"));
3116 | }
3117 |
3118 | (function () {
3119 | var push = [].push;
3120 |
3121 | function exposeValue(sandbox, config, key, value) {
3122 | if (!value) {
3123 | return;
3124 | }
3125 |
3126 | if (config.injectInto) {
3127 | config.injectInto[key] = value;
3128 | } else {
3129 | push.call(sandbox.args, value);
3130 | }
3131 | }
3132 |
3133 | function prepareSandboxFromConfig(config) {
3134 | var sandbox = sinon.create(sinon.sandbox);
3135 |
3136 | if (config.useFakeServer) {
3137 | if (typeof config.useFakeServer == "object") {
3138 | sandbox.serverPrototype = config.useFakeServer;
3139 | }
3140 |
3141 | sandbox.useFakeServer();
3142 | }
3143 |
3144 | if (config.useFakeTimers) {
3145 | if (typeof config.useFakeTimers == "object") {
3146 | sandbox.useFakeTimers.apply(sandbox, config.useFakeTimers);
3147 | } else {
3148 | sandbox.useFakeTimers();
3149 | }
3150 | }
3151 |
3152 | return sandbox;
3153 | }
3154 |
3155 | sinon.sandbox = sinon.extend(sinon.create(sinon.collection), {
3156 | useFakeTimers: function useFakeTimers() {
3157 | this.clock = sinon.useFakeTimers.apply(sinon, arguments);
3158 |
3159 | return this.add(this.clock);
3160 | },
3161 |
3162 | serverPrototype: sinon.fakeServer,
3163 |
3164 | useFakeServer: function useFakeServer() {
3165 | var proto = this.serverPrototype || sinon.fakeServer;
3166 |
3167 | if (!proto || !proto.create) {
3168 | return null;
3169 | }
3170 |
3171 | this.server = proto.create();
3172 | return this.add(this.server);
3173 | },
3174 |
3175 | inject: function (obj) {
3176 | sinon.collection.inject.call(this, obj);
3177 |
3178 | if (this.clock) {
3179 | obj.clock = this.clock;
3180 | }
3181 |
3182 | if (this.server) {
3183 | obj.server = this.server;
3184 | obj.requests = this.server.requests;
3185 | }
3186 |
3187 | return obj;
3188 | },
3189 |
3190 | create: function (config) {
3191 | if (!config) {
3192 | return sinon.create(sinon.sandbox);
3193 | }
3194 |
3195 | var sandbox = prepareSandboxFromConfig(config);
3196 | sandbox.args = sandbox.args || [];
3197 | var prop, value, exposed = sandbox.inject({});
3198 |
3199 | if (config.properties) {
3200 | for (var i = 0, l = config.properties.length; i < l; i++) {
3201 | prop = config.properties[i];
3202 | value = exposed[prop] || prop == "sandbox" && sandbox;
3203 | exposeValue(sandbox, config, prop, value);
3204 | }
3205 | } else {
3206 | exposeValue(sandbox, config, "sandbox", value);
3207 | }
3208 |
3209 | return sandbox;
3210 | }
3211 | });
3212 |
3213 | sinon.sandbox.useFakeXMLHttpRequest = sinon.sandbox.useFakeServer;
3214 |
3215 | if (typeof module == "object" && typeof require == "function") {
3216 | module.exports = sinon.sandbox;
3217 | }
3218 | }());
3219 |
3220 | /**
3221 | * @depend ../sinon.js
3222 | * @depend stub.js
3223 | * @depend mock.js
3224 | * @depend sandbox.js
3225 | */
3226 | /*jslint eqeqeq: false, onevar: false, forin: true, plusplus: false*/
3227 | /*global module, require, sinon*/
3228 | /**
3229 | * Test function, sandboxes fakes
3230 | *
3231 | * @author Christian Johansen (christian@cjohansen.no)
3232 | * @license BSD
3233 | *
3234 | * Copyright (c) 2010-2011 Christian Johansen
3235 | */
3236 |
3237 | (function (sinon) {
3238 | var commonJSModule = typeof module == "object" && typeof require == "function";
3239 |
3240 | if (!sinon && commonJSModule) {
3241 | sinon = require("../sinon");
3242 | }
3243 |
3244 | if (!sinon) {
3245 | return;
3246 | }
3247 |
3248 | function test(callback) {
3249 | var type = typeof callback;
3250 |
3251 | if (type != "function") {
3252 | throw new TypeError("sinon.test needs to wrap a test function, got " + type);
3253 | }
3254 |
3255 | return function () {
3256 | var config = sinon.getConfig(sinon.config);
3257 | config.injectInto = config.injectIntoThis && this || config.injectInto;
3258 | var sandbox = sinon.sandbox.create(config);
3259 | var exception, result;
3260 | var args = Array.prototype.slice.call(arguments).concat(sandbox.args);
3261 |
3262 | try {
3263 | result = callback.apply(this, args);
3264 | } finally {
3265 | sandbox.verifyAndRestore();
3266 | }
3267 |
3268 | return result;
3269 | };
3270 | }
3271 |
3272 | test.config = {
3273 | injectIntoThis: true,
3274 | injectInto: null,
3275 | properties: ["spy", "stub", "mock", "clock", "server", "requests"],
3276 | useFakeTimers: true,
3277 | useFakeServer: true
3278 | };
3279 |
3280 | if (commonJSModule) {
3281 | module.exports = test;
3282 | } else {
3283 | sinon.test = test;
3284 | }
3285 | }(typeof sinon == "object" && sinon || null));
3286 |
3287 | /**
3288 | * @depend ../sinon.js
3289 | * @depend test.js
3290 | */
3291 | /*jslint eqeqeq: false, onevar: false, eqeqeq: false*/
3292 | /*global module, require, sinon*/
3293 | /**
3294 | * Test case, sandboxes all test functions
3295 | *
3296 | * @author Christian Johansen (christian@cjohansen.no)
3297 | * @license BSD
3298 | *
3299 | * Copyright (c) 2010-2011 Christian Johansen
3300 | */
3301 |
3302 | (function (sinon) {
3303 | var commonJSModule = typeof module == "object" && typeof require == "function";
3304 |
3305 | if (!sinon && commonJSModule) {
3306 | sinon = require("../sinon");
3307 | }
3308 |
3309 | if (!sinon || !Object.prototype.hasOwnProperty) {
3310 | return;
3311 | }
3312 |
3313 | function createTest(property, setUp, tearDown) {
3314 | return function () {
3315 | if (setUp) {
3316 | setUp.apply(this, arguments);
3317 | }
3318 |
3319 | var exception, result;
3320 |
3321 | try {
3322 | result = property.apply(this, arguments);
3323 | } catch (e) {
3324 | exception = e;
3325 | }
3326 |
3327 | if (tearDown) {
3328 | tearDown.apply(this, arguments);
3329 | }
3330 |
3331 | if (exception) {
3332 | throw exception;
3333 | }
3334 |
3335 | return result;
3336 | };
3337 | }
3338 |
3339 | function testCase(tests, prefix) {
3340 | /*jsl:ignore*/
3341 | if (!tests || typeof tests != "object") {
3342 | throw new TypeError("sinon.testCase needs an object with test functions");
3343 | }
3344 | /*jsl:end*/
3345 |
3346 | prefix = prefix || "test";
3347 | var rPrefix = new RegExp("^" + prefix);
3348 | var methods = {}, testName, property, method;
3349 | var setUp = tests.setUp;
3350 | var tearDown = tests.tearDown;
3351 |
3352 | for (testName in tests) {
3353 | if (tests.hasOwnProperty(testName)) {
3354 | property = tests[testName];
3355 |
3356 | if (/^(setUp|tearDown)$/.test(testName)) {
3357 | continue;
3358 | }
3359 |
3360 | if (typeof property == "function" && rPrefix.test(testName)) {
3361 | method = property;
3362 |
3363 | if (setUp || tearDown) {
3364 | method = createTest(property, setUp, tearDown);
3365 | }
3366 |
3367 | methods[testName] = sinon.test(method);
3368 | } else {
3369 | methods[testName] = tests[testName];
3370 | }
3371 | }
3372 | }
3373 |
3374 | return methods;
3375 | }
3376 |
3377 | if (commonJSModule) {
3378 | module.exports = testCase;
3379 | } else {
3380 | sinon.testCase = testCase;
3381 | }
3382 | }(typeof sinon == "object" && sinon || null));
3383 |
3384 | /**
3385 | * @depend ../sinon.js
3386 | * @depend stub.js
3387 | */
3388 | /*jslint eqeqeq: false, onevar: false, nomen: false, plusplus: false*/
3389 | /*global module, require, sinon*/
3390 | /**
3391 | * Assertions matching the test spy retrieval interface.
3392 | *
3393 | * @author Christian Johansen (christian@cjohansen.no)
3394 | * @license BSD
3395 | *
3396 | * Copyright (c) 2010-2011 Christian Johansen
3397 | */
3398 |
3399 | (function (sinon, global) {
3400 | var commonJSModule = typeof module == "object" && typeof require == "function";
3401 | var slice = Array.prototype.slice;
3402 | var assert;
3403 |
3404 | if (!sinon && commonJSModule) {
3405 | sinon = require("../sinon");
3406 | }
3407 |
3408 | if (!sinon) {
3409 | return;
3410 | }
3411 |
3412 | function verifyIsStub() {
3413 | var method;
3414 |
3415 | for (var i = 0, l = arguments.length; i < l; ++i) {
3416 | method = arguments[i];
3417 |
3418 | if (!method) {
3419 | assert.fail("fake is not a spy");
3420 | }
3421 |
3422 | if (typeof method != "function") {
3423 | assert.fail(method + " is not a function");
3424 | }
3425 |
3426 | if (typeof method.getCall != "function") {
3427 | assert.fail(method + " is not stubbed");
3428 | }
3429 | }
3430 | }
3431 |
3432 | function failAssertion(object, msg) {
3433 | object = object || global;
3434 | var failMethod = object.fail || assert.fail;
3435 | failMethod.call(object, msg);
3436 | }
3437 |
3438 | function mirrorPropAsAssertion(name, method, message) {
3439 | if (arguments.length == 2) {
3440 | message = method;
3441 | method = name;
3442 | }
3443 |
3444 | assert[name] = function (fake) {
3445 | verifyIsStub(fake);
3446 |
3447 | var args = slice.call(arguments, 1);
3448 | var failed = false;
3449 |
3450 | if (typeof method == "function") {
3451 | failed = !method(fake);
3452 | } else {
3453 | failed = typeof fake[method] == "function" ?
3454 | !fake[method].apply(fake, args) : !fake[method];
3455 | }
3456 |
3457 | if (failed) {
3458 | failAssertion(this, fake.printf.apply(fake, [message].concat(args)));
3459 | } else {
3460 | assert.pass(name);
3461 | }
3462 | };
3463 | }
3464 |
3465 | function exposedName(prefix, prop) {
3466 | return !prefix || /^fail/.test(prop) ? prop :
3467 | prefix + prop.slice(0, 1).toUpperCase() + prop.slice(1);
3468 | };
3469 |
3470 | assert = {
3471 | failException: "AssertError",
3472 |
3473 | fail: function fail(message) {
3474 | var error = new Error(message);
3475 | error.name = this.failException || assert.failException;
3476 |
3477 | throw error;
3478 | },
3479 |
3480 | pass: function pass(assertion) {},
3481 |
3482 | callOrder: function assertCallOrder() {
3483 | verifyIsStub.apply(null, arguments);
3484 | var expected = "", actual = "";
3485 |
3486 | if (!sinon.calledInOrder(arguments)) {
3487 | try {
3488 | expected = [].join.call(arguments, ", ");
3489 | actual = sinon.orderByFirstCall(slice.call(arguments)).join(", ");
3490 | } catch (e) {
3491 | // If this fails, we'll just fall back to the blank string
3492 | }
3493 |
3494 | failAssertion(this, "expected " + expected + " to be " +
3495 | "called in order but were called as " + actual);
3496 | } else {
3497 | assert.pass("callOrder");
3498 | }
3499 | },
3500 |
3501 | callCount: function assertCallCount(method, count) {
3502 | verifyIsStub(method);
3503 |
3504 | if (method.callCount != count) {
3505 | var msg = "expected %n to be called " + sinon.timesInWords(count) +
3506 | " but was called %c%C";
3507 | failAssertion(this, method.printf(msg));
3508 | } else {
3509 | assert.pass("callCount");
3510 | }
3511 | },
3512 |
3513 | expose: function expose(target, options) {
3514 | if (!target) {
3515 | throw new TypeError("target is null or undefined");
3516 | }
3517 |
3518 | var o = options || {};
3519 | var prefix = typeof o.prefix == "undefined" && "assert" || o.prefix;
3520 | var includeFail = typeof o.includeFail == "undefined" || !!o.includeFail;
3521 |
3522 | for (var method in this) {
3523 | if (method != "export" && (includeFail || !/^(fail)/.test(method))) {
3524 | target[exposedName(prefix, method)] = this[method];
3525 | }
3526 | }
3527 |
3528 | return target;
3529 | }
3530 | };
3531 |
3532 | mirrorPropAsAssertion("called", "expected %n to have been called at least once but was never called");
3533 | mirrorPropAsAssertion("notCalled", function (spy) { return !spy.called; },
3534 | "expected %n to not have been called but was called %c%C");
3535 | mirrorPropAsAssertion("calledOnce", "expected %n to be called once but was called %c%C");
3536 | mirrorPropAsAssertion("calledTwice", "expected %n to be called twice but was called %c%C");
3537 | mirrorPropAsAssertion("calledThrice", "expected %n to be called thrice but was called %c%C");
3538 | mirrorPropAsAssertion("calledOn", "expected %n to be called with %1 as this but was called with %t");
3539 | mirrorPropAsAssertion("alwaysCalledOn", "expected %n to always be called with %1 as this but was called with %t");
3540 | mirrorPropAsAssertion("calledWith", "expected %n to be called with arguments %*%C");
3541 | mirrorPropAsAssertion("alwaysCalledWith", "expected %n to always be called with arguments %*%C");
3542 | mirrorPropAsAssertion("calledWithExactly", "expected %n to be called with exact arguments %*%C");
3543 | mirrorPropAsAssertion("alwaysCalledWithExactly", "expected %n to always be called with exact arguments %*%C");
3544 | mirrorPropAsAssertion("neverCalledWith", "expected %n to never be called with arguments %*%C");
3545 | mirrorPropAsAssertion("threw", "%n did not throw exception%C");
3546 | mirrorPropAsAssertion("alwaysThrew", "%n did not always throw exception%C");
3547 |
3548 | if (commonJSModule) {
3549 | module.exports = assert;
3550 | } else {
3551 | sinon.assert = assert;
3552 | }
3553 | }(typeof sinon == "object" && sinon || null, typeof window != "undefined" ? window : global));
3554 |
3555 | return sinon;}.call(typeof window != 'undefined' && window || {}));
3556 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jquery.ajax-retry",
3 | "title": "jQuery Ajax Retry",
4 | "description": "Retry ajax calls using the deferred API",
5 | "version": "1.0.0",
6 | "homepage": "https://github.com/johnkpaul/jquery-ajax-retry",
7 | "author": {
8 | "name": "John Paul",
9 | "email": "john@johnkpaul.com",
10 | "url": "http://www.johnkpaul.com"
11 | },
12 | "maintainers": [
13 | {
14 | "name": "John Paul",
15 | "email": "john@johnkpaul.com",
16 | "url": "http://www.johnkpaul.com"
17 | }
18 | ],
19 | "repository": {
20 | "type": "git",
21 | "url": "git://github.com/johnkpaul/jquery-ajax-retry.git"
22 | },
23 | "bugs": {
24 | "url": "https://github.com/johnkpaul/jquery-ajax-retry/issues"
25 | },
26 | "licenses": [
27 | {
28 | "type": "MIT",
29 | "url": "https://github.com/johnkpaul/jquery-ajax-retry/blob/master/LICENSE-MIT"
30 | }
31 | ],
32 | "peerDependencies": {
33 | "jquery": ">=1.5"
34 | },
35 | "dependencies": {
36 | "grunt-contrib-qunit": "~0.5.1",
37 | "grunt-contrib-concat": "~0.4.0"
38 | },
39 | "devDependencies": {
40 | "grunt": "~0.4.5",
41 | "load-grunt-tasks": "~0.5.0",
42 | "grunt-contrib-uglify": "~0.5.0",
43 | "grunt-contrib-jshint": "~0.10.0"
44 | },
45 | "keywords": [
46 | "ajax",
47 | "retry"
48 | ],
49 | "main": "src/jquery.ajax-retry.js"
50 | }
51 |
--------------------------------------------------------------------------------
/src/jquery.ajax-retry.js:
--------------------------------------------------------------------------------
1 | /*
2 | * jquery.ajax-retry
3 | * https://github.com/johnkpaul/jquery-ajax-retry
4 | *
5 | * Copyright (c) 2012 John Paul
6 | * Licensed under the MIT license.
7 | */
8 | (function(factory) {
9 | if (typeof define === 'function' && define.amd) {
10 | // AMD. Register as an anonymous module.
11 | define(['jquery'], factory);
12 | } else if (typeof exports === 'object') {
13 | // Node/CommonJS
14 | factory(require('jquery'));
15 | } else {
16 | // Browser globals
17 | factory(jQuery);
18 | }
19 | })(function($) {
20 |
21 | // enhance all ajax requests with our retry API
22 | $.ajaxPrefilter(function(options, originalOptions, jqXHR) {
23 | jqXHR.retry = function(opts) {
24 | if(opts.timeout) {
25 | this.timeout = opts.timeout;
26 | }
27 | if (opts.statusCodes) {
28 | this.statusCodes = opts.statusCodes;
29 | }
30 | return this.pipe(null, pipeFailRetry(this, opts));
31 | };
32 | });
33 |
34 | // generates a fail pipe function that will retry `jqXHR` `times` more times
35 | function pipeFailRetry(jqXHR, opts) {
36 | var times = opts.times;
37 | var timeout = jqXHR.timeout;
38 |
39 | // takes failure data as input, returns a new deferred
40 | return function(input, status, msg) {
41 | var ajaxOptions = this;
42 | var output = new $.Deferred();
43 | var retryAfter = jqXHR.getResponseHeader('Retry-After');
44 |
45 | // whenever we do make this request, pipe its output to our deferred
46 | function nextRequest() {
47 | $.ajax(ajaxOptions)
48 | .retry({times: times - 1, timeout: opts.timeout, statusCodes: opts.statusCodes})
49 | .pipe(output.resolve, output.reject);
50 | }
51 |
52 | if (times > 1 && (!jqXHR.statusCodes || $.inArray(input.status, jqXHR.statusCodes) > -1)) {
53 | // implement Retry-After rfc
54 | // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.37
55 | if (retryAfter) {
56 | // it must be a date
57 | if (isNaN(retryAfter)) {
58 | timeout = new Date(retryAfter).getTime() - $.now();
59 | // its a number in seconds
60 | } else {
61 | timeout = parseInt(retryAfter, 10) * 1000;
62 | }
63 | // ensure timeout is a positive number
64 | if (isNaN(timeout) || timeout < 0) {
65 | timeout = jqXHR.timeout;
66 | }
67 | }
68 |
69 | if (timeout !== undefined){
70 | setTimeout(nextRequest, timeout);
71 | } else {
72 | nextRequest();
73 | }
74 | } else {
75 | // no times left, reject our deferred with the current arguments
76 | output.rejectWith(this, arguments);
77 | }
78 |
79 | return output;
80 | };
81 | }
82 |
83 | });
84 |
--------------------------------------------------------------------------------
/test/jquery.ajax-retry.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | jQuery Ajax Retry Test Suite
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | lame test markup
25 | normal test markup
26 | awesome test markup
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/test/jquery.ajax-retry_test.js:
--------------------------------------------------------------------------------
1 | /*global QUnit:false, module:false, test:false, asyncTest:false, expect:false*/
2 | /*global start:false, stop:false ok:false, equal:false, notEqual:false, deepEqual:false*/
3 | /*global notDeepEqual:false, strictEqual:false, notStrictEqual:false, raises:false*/
4 | /*global sinon:true */
5 | (function($) {
6 |
7 | /*
8 | ======== A Handy Little QUnit Reference ========
9 | http://docs.jquery.com/QUnit
10 |
11 | Test methods:
12 | expect(numAssertions)
13 | stop(increment)
14 | start(decrement)
15 | Test assertions:
16 | ok(value, [message])
17 | equal(actual, expected, [message])
18 | notEqual(actual, expected, [message])
19 | deepEqual(actual, expected, [message])
20 | notDeepEqual(actual, expected, [message])
21 | strictEqual(actual, expected, [message])
22 | notStrictEqual(actual, expected, [message])
23 | raises(block, [expected], [message])
24 | */
25 |
26 | module('jQuery retry tries again', {
27 | setup: function() {
28 | this.xhr = sinon.useFakeXMLHttpRequest();
29 | var requests = this.requests = [];
30 | this.xhr.onCreate = function (xhr) {
31 | requests.push(xhr);
32 | };
33 | },
34 | teardown: function(){
35 | this.xhr.restore();
36 | }
37 | });
38 |
39 | test('ajax Deferreds have new retry method', 1, function() {
40 | var def = $.ajax({url:"/test",data:{},type:"POST"});
41 | var hasRetry = "retry" in def;
42 | ok(hasRetry);
43 | });
44 |
45 | test('ajax Deferreds have existing done and then', 1, function() {
46 | var def = $.ajax({url:"/test",data:{},type:"POST"});
47 | var hasOld = "done" in def && "then" in def;
48 | ok(hasOld);
49 | });
50 |
51 | test('ajax Deferred works as original in the case of a 200', 1, function() {
52 | var def = $.post("/test",{});
53 | this.requests[0].respond(200, { "Content-Type": "application/json" },
54 | '{ "id": 12, "comment": "Hey there" }');
55 | def.then(function(data){
56 | ok(data.id === 12);
57 | });
58 | });
59 |
60 | asyncTest('ajax Deferred tries again if needed', 1, function() {
61 | var def = $.post("/test",{});
62 | def.retry({times:2}).then(function(data){
63 | ok(data.id === 12);
64 | start();
65 | });
66 | this.requests[0].respond(400, { "Content-Type": "application/json" },
67 | '{ "id": 13, "comment": "error!" }');
68 | this.requests[1].respond(200, { "Content-Type": "application/json" },
69 | '{ "id": 12, "comment": "Hey there" }');
70 | });
71 |
72 | asyncTest('ajax Deferred tries again if needed', 1, function() {
73 | var def = $.post("/test",{});
74 | def.retry({times:3}).then(function(data){
75 | ok(data.id === 12);
76 | start();
77 | });
78 |
79 | this.requests[0].respond(400, { "Content-Type": "application/json" },
80 | '{ "id": 13, "comment": "error!" }');
81 | this.requests[1].respond(400, { "Content-Type": "application/json" },
82 | '{ "id": 13, "comment": "error!" }');
83 | this.requests[2].respond(200, { "Content-Type": "application/json" },
84 | '{ "id": 12, "comment": "Hey there" }');
85 | });
86 |
87 | asyncTest('ajax Deferred tries only once if failing', 0, function() {
88 | var def = $.post("/test",{});
89 | def.retry({times:2}).fail(function(){
90 | start();
91 | });
92 | this.requests[0].respond(400, { "Content-Type": "application/json" },
93 | '{ "id": 12, "comment": "error!" }');
94 | this.requests[1].respond(400, { "Content-Type": "application/json" },
95 | '{ "id": 12, "comment": "error!" }');
96 | });
97 |
98 | asyncTest('ajax Deferred gets correct parameters to fail callback', 1, function() {
99 | var def = $.post("/test",{});
100 | def.retry({times:2}).fail(function(deferred, status, msg){
101 | ok(status === "error");
102 | start();
103 | });
104 | this.requests[0].respond(400, { "Content-Type": "application/json" },
105 | '{ "id": 12, "comment": "error!" }');
106 | this.requests[1].respond(400, { "Content-Type": "application/json" },
107 | '{ "id": 12, "comment": "error!" }');
108 | });
109 |
110 | test('data is taken from successful response ', 1, function() {
111 | var def = $.post("/test",{});
112 |
113 | def.retry({times:2}).done(function(data) {
114 | ok(data.id === 12);
115 | });
116 |
117 | this.requests[0].respond(400, { "Content-Type": "application/json" },
118 | '{ "id": 11, "comment": "error!" }');
119 |
120 |
121 | this.requests[1].respond(200, { "Content-Type": "application/json" },
122 | '{ "id": 12, "comment": "Hey there" }');
123 | });
124 |
125 |
126 | module('jQuery retry uses timeout value', {
127 | setup: function() {
128 | this.xhr = sinon.useFakeXMLHttpRequest();
129 | var requests = this.requests = [];
130 | this.xhr.onCreate = function (xhr) {
131 | requests.push(xhr);
132 | };
133 | this.clock = sinon.useFakeTimers();
134 | },
135 | teardown: function(){
136 | this.xhr.restore();
137 | this.clock.restore();
138 | }
139 | });
140 |
141 | test('timeout is waited before next retry', 3, function() {
142 | var def = $.post("/test",{});
143 |
144 | def.retry({times:2, timeout:2000});
145 |
146 | ok(this.requests.length === 1);
147 | this.requests[0].respond(400, { "Content-Type": "application/json" },
148 | '{ "id": 11, "comment": "error!" }');
149 | ok(this.requests.length === 1);
150 |
151 | this.clock.tick(2000);
152 |
153 | ok(this.requests.length === 2);
154 | this.requests[1].respond(200, { "Content-Type": "application/json" },
155 | '{ "id": 12, "comment": "Hey there" }');
156 | });
157 |
158 | test('timeout is waited between multiple retries', 4, function() {
159 | var def = $.post("/test",{});
160 |
161 | def.retry({times:3, timeout:2000});
162 |
163 | ok(this.requests.length === 1);
164 | this.requests[0].respond(400, { "Content-Type": "application/json" },
165 | '{ "id": 11, "comment": "error!" }');
166 | ok(this.requests.length === 1);
167 |
168 | this.clock.tick(2000);
169 |
170 | ok(this.requests.length === 2);
171 |
172 | this.requests[1].respond(400, { "Content-Type": "application/json" },
173 | '{ "id": 11, "comment": "error!" }');
174 |
175 | this.clock.tick(2000);
176 |
177 | ok(this.requests.length === 3);
178 |
179 | this.requests[2].respond(200, { "Content-Type": "application/json" },
180 | '{ "id": 12, "comment": "Hey there" }');
181 | });
182 |
183 | test('retry does not happen, if timeout has not been met', 3, function() {
184 | var def = $.post("/test",{});
185 | def.retry({times:2, timeout:2000});
186 | ok(this.requests.length === 1);
187 | this.requests[0].respond(400, { "Content-Type": "application/json" },
188 | '{ "id": 12, "comment": "error!" }');
189 | ok(this.requests.length === 1);
190 | this.clock.tick(1999);
191 | ok(this.requests.length === 1);
192 | });
193 |
194 | test('data is taken from successful response when using timeout option', 1, function() {
195 | var def = $.post("/test",{});
196 | def.retry({times:2, timeout:2000}).done(function(data) {
197 | ok(data.id === 12);
198 | });
199 | this.requests[0].respond(400, { "Content-Type": "application/json" },
200 | '{ "id": 13, "comment": "error!" }');
201 | this.clock.tick(2000);
202 | this.requests[1].respond(200, { "Content-Type": "application/json" },
203 | '{ "id": 12, "comment": "error!" }');
204 | });
205 |
206 | test('retry-after http header (seconds) is used as timeout', 3, function() {
207 | var def = $.post("/test",{});
208 | def.retry({times:2, timeout:100});
209 | ok(this.requests.length === 1);
210 | this.requests[0].respond(400, {
211 | "Content-Type": "application/json",
212 | "Retry-After": "1"
213 | }, '{ "id": 12, "comment": "error!" }');
214 | this.clock.tick(200);
215 | ok(this.requests.length === 1);
216 | this.clock.tick(1001);
217 | ok(this.requests.length === 2);
218 | });
219 |
220 | test('retry-after http header (HTTP-date) is used as timeout', 3, function() {
221 | var def = $.post("/test",{});
222 | def.retry({times:2, timeout:100});
223 | ok(this.requests.length === 1);
224 | this.requests[0].respond(400, {
225 | "Content-Type": "application/json",
226 | "Retry-After": new Date($.now() + 3000)
227 | }, '{ "id": 12, "comment": "error!" }');
228 | this.clock.tick(200);
229 | ok(this.requests.length === 1);
230 | this.clock.tick(3001);
231 | ok(this.requests.length === 2);
232 | });
233 |
234 | module('jQuery retry uses retry codes', {
235 | setup: function() {
236 | this.xhr = sinon.useFakeXMLHttpRequest();
237 | var requests = this.requests = [];
238 | this.xhr.onCreate = function (xhr) {
239 | requests.push(xhr);
240 | };
241 | },
242 | teardown: function(){
243 | this.xhr.restore();
244 | }
245 | });
246 |
247 | asyncTest('retry happens on provided status code', 1, function() {
248 | var def = $.post("/test",{});
249 | def.retry({times:2, statusCodes: [503]}).then(function(data){
250 | ok(data.id === 12);
251 | start();
252 | });
253 | this.requests[0].respond(503, { "Content-Type": "application/json" },
254 | '{ "id": 13, "comment": "error!" }');
255 | this.requests[1].respond(200, { "Content-Type": "application/json" },
256 | '{ "id": 12, "comment": "Hey there" }');
257 | });
258 |
259 | asyncTest('retry does not happen if status code is different from provided', 1, function() {
260 | var def = $.post("/test",{
261 | error: function(data) {
262 | ok(true);
263 | start();
264 | }
265 | });
266 |
267 | def.retry({times:2, statusCodes:[503]});
268 |
269 | this.requests[0].respond(400, { "Content-Type": "application/json" },
270 | '{ "id": 12, "comment": "error!" }');
271 |
272 | });
273 |
274 | }(jQuery));
275 |
--------------------------------------------------------------------------------