pretender-rails 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 1203f9df04b877d6a7ad3cc32daca9b01a1fd844
4
+ data.tar.gz: 723f9bc1e3792b40e644dcb043ae20e2b7826939
5
+ SHA512:
6
+ metadata.gz: 0f946042676557f164c3f72d722cb0bff9fb30c134adb2b23557785dbd414199dc803332d729318d32a26bb1ee7faf1cffae22bc679d993a544986619517d687
7
+ data.tar.gz: 3a6a9d11965883cc5eb568f466c0cfb528c60846b3dabfa4c95f3bbd3e48254f482af6420a4fa093442ad9b2b19126acf3bed354f87f78b610e06ddf79334308
data/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in pretender-rails.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Chase McCarthy
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,33 @@
1
+ # Pretender Rails
2
+
3
+ Stub clientside calls to external services.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'pretender-rails'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install pretender-rails
20
+
21
+ ## Usage
22
+
23
+ ```ruby
24
+ Pretender.server.stub('get', 'https://api.github.com/repos/code0100fun/pretender-rails', [200, {}, {name: "mock-repo"}.to_json])
25
+ ```
26
+
27
+ ## Contributing
28
+
29
+ 1. Fork it ( https://github.com/[my-github-username]/pretender-rails/fork )
30
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
31
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
32
+ 4. Push to the branch (`git push origin my-new-feature`)
33
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,10 @@
1
+ require "pretender/rails/version"
2
+ require "pretender/rails/server"
3
+ require "pretender/rails/middleware"
4
+ require "pretender/rails/railtie"
5
+ require "pretender/rails/engine"
6
+
7
+ module Pretender
8
+ module Rails
9
+ end
10
+ end
@@ -0,0 +1,18 @@
1
+ require 'cucumber'
2
+ require 'capybara/cucumber'
3
+
4
+ module Pretender
5
+ module Rails
6
+ module CucumberHelper
7
+ def stub(method, route, response)
8
+ Pretender.server.stub(method, route, response)
9
+ end
10
+ end
11
+ end
12
+ end
13
+
14
+ World(Pretender::Rails::CucumberHelper)
15
+
16
+ After do
17
+ Pretender.server.shutdown(page)
18
+ end
@@ -0,0 +1,6 @@
1
+ module Pretender
2
+ module Rails
3
+ class Engine < ::Rails::Engine
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,20 @@
1
+ module Pretender
2
+ module Rails
3
+ class Middleware
4
+ def initialize(app)
5
+ @app = app
6
+ end
7
+
8
+ def call(env)
9
+ status, headers, env = @app.call(env)
10
+ if headers['Content-Type'].to_s.include?("text/html")
11
+ body = Pretender.server.inject(env.body)
12
+ headers['Content-Length'] = Rack::Utils.bytesize(body.to_s).to_s
13
+ return [status, headers, [body]]
14
+ end
15
+
16
+ [status, headers, env]
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,9 @@
1
+ module Pretender
2
+ module Rails
3
+ class Railtie < ::Rails::Railtie
4
+ initializer "pretender.configure_rails_initialization" do
5
+ ::Rails.application.middleware.use Pretender::Rails::Middleware
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,75 @@
1
+ module Pretender
2
+ def self.server
3
+ @_server ||= ::Pretender::Rails::Server.new
4
+ end
5
+
6
+ module Rails
7
+ class Server
8
+ Stub = Struct.new(:method, :route, :response)
9
+ attr_accessor :session
10
+
11
+ def inject(body)
12
+ unless stubs.empty?
13
+ head_pos = body.index('</head>')
14
+ body.insert(head_pos, scripts) if head_pos
15
+ end
16
+ body
17
+ end
18
+
19
+ def shutdown(page)
20
+ stubs.clear
21
+ page.execute_script('typeof(server) !== "undefined" && server.shutdown();')
22
+ end
23
+
24
+ def stub(method, route, response)
25
+ stub = Stub.new(method, route, response)
26
+ stubs.unshift stub
27
+ stub
28
+ end
29
+
30
+ private
31
+
32
+ def stubs
33
+ @_stubs ||= []
34
+ end
35
+
36
+ def routes
37
+ stubs.map do |stub|
38
+ "this.#{stub.method}('#{stub.route}', function(request) { return #{stub.response.to_json}; });"
39
+ end.join("\n")
40
+ end
41
+
42
+ def load_script(path)
43
+ open(path).read
44
+ end
45
+
46
+ def scripts
47
+ [dependencies_script, server_script].join("\n")
48
+ end
49
+
50
+ def dependencies_script
51
+ [javascript_include_tag('fake_xml_http_request.js'),
52
+ javascript_include_tag('route-recognizer.js'),
53
+ javascript_include_tag('pretender.js')].join("\n")
54
+ end
55
+
56
+ def javascript_include_tag(url)
57
+ "<script src=\"/assets/#{url}\"></script>"
58
+ end
59
+
60
+ def server_script
61
+ <<-JS
62
+ <script>
63
+ var server = new Pretender(function(){
64
+ #{routes}
65
+ this.get('*any', this.passthrough);
66
+ this.post('*any', this.passthrough);
67
+ this.put('*any', this.passthrough);
68
+ this.delete('*any', this.passthrough);
69
+ });
70
+ </script>
71
+ JS
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,5 @@
1
+ module Pretender
2
+ module Rails
3
+ VERSION = "0.0.2"
4
+ end
5
+ end
@@ -0,0 +1,23 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'pretender/rails/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "pretender-rails"
8
+ spec.version = Pretender::Rails::VERSION
9
+ spec.authors = ["Chase McCarthy"]
10
+ spec.email = ["chase@code0100fun.com"]
11
+ spec.summary = %q{Stub clientside ajax requests using pretender}
12
+ spec.description = %q{Injects pretender into all HTML requests when a route has been stubbed}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.7"
22
+ spec.add_development_dependency "rake", "~> 10.0"
23
+ end
@@ -0,0 +1,497 @@
1
+ (function (global, factory) {
2
+ typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
3
+ typeof define === 'function' && define.amd ? define(factory) :
4
+ global.FakeXMLHttpRequest = factory()
5
+ }(this, function () { 'use strict';
6
+
7
+ /**
8
+ * Minimal Event interface implementation
9
+ *
10
+ * Original implementation by Sven Fuchs: https://gist.github.com/995028
11
+ * Modifications and tests by Christian Johansen.
12
+ *
13
+ * @author Sven Fuchs (svenfuchs@artweb-design.de)
14
+ * @author Christian Johansen (christian@cjohansen.no)
15
+ * @license BSD
16
+ *
17
+ * Copyright (c) 2011 Sven Fuchs, Christian Johansen
18
+ */
19
+
20
+ var _Event = function Event(type, bubbles, cancelable, target) {
21
+ this.type = type;
22
+ this.bubbles = bubbles;
23
+ this.cancelable = cancelable;
24
+ this.target = target;
25
+ };
26
+
27
+ _Event.prototype = {
28
+ stopPropagation: function () {},
29
+ preventDefault: function () {
30
+ this.defaultPrevented = true;
31
+ }
32
+ };
33
+
34
+ /*
35
+ Used to set the statusText property of an xhr object
36
+ */
37
+ var httpStatusCodes = {
38
+ 100: "Continue",
39
+ 101: "Switching Protocols",
40
+ 200: "OK",
41
+ 201: "Created",
42
+ 202: "Accepted",
43
+ 203: "Non-Authoritative Information",
44
+ 204: "No Content",
45
+ 205: "Reset Content",
46
+ 206: "Partial Content",
47
+ 300: "Multiple Choice",
48
+ 301: "Moved Permanently",
49
+ 302: "Found",
50
+ 303: "See Other",
51
+ 304: "Not Modified",
52
+ 305: "Use Proxy",
53
+ 307: "Temporary Redirect",
54
+ 400: "Bad Request",
55
+ 401: "Unauthorized",
56
+ 402: "Payment Required",
57
+ 403: "Forbidden",
58
+ 404: "Not Found",
59
+ 405: "Method Not Allowed",
60
+ 406: "Not Acceptable",
61
+ 407: "Proxy Authentication Required",
62
+ 408: "Request Timeout",
63
+ 409: "Conflict",
64
+ 410: "Gone",
65
+ 411: "Length Required",
66
+ 412: "Precondition Failed",
67
+ 413: "Request Entity Too Large",
68
+ 414: "Request-URI Too Long",
69
+ 415: "Unsupported Media Type",
70
+ 416: "Requested Range Not Satisfiable",
71
+ 417: "Expectation Failed",
72
+ 422: "Unprocessable Entity",
73
+ 500: "Internal Server Error",
74
+ 501: "Not Implemented",
75
+ 502: "Bad Gateway",
76
+ 503: "Service Unavailable",
77
+ 504: "Gateway Timeout",
78
+ 505: "HTTP Version Not Supported"
79
+ };
80
+
81
+
82
+ /*
83
+ Cross-browser XML parsing. Used to turn
84
+ XML responses into Document objects
85
+ Borrowed from JSpec
86
+ */
87
+ function parseXML(text) {
88
+ var xmlDoc;
89
+
90
+ if (typeof DOMParser != "undefined") {
91
+ var parser = new DOMParser();
92
+ xmlDoc = parser.parseFromString(text, "text/xml");
93
+ } else {
94
+ xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
95
+ xmlDoc.async = "false";
96
+ xmlDoc.loadXML(text);
97
+ }
98
+
99
+ return xmlDoc;
100
+ }
101
+
102
+ /*
103
+ Without mocking, the native XMLHttpRequest object will throw
104
+ an error when attempting to set these headers. We match this behavior.
105
+ */
106
+ var unsafeHeaders = {
107
+ "Accept-Charset": true,
108
+ "Accept-Encoding": true,
109
+ "Connection": true,
110
+ "Content-Length": true,
111
+ "Cookie": true,
112
+ "Cookie2": true,
113
+ "Content-Transfer-Encoding": true,
114
+ "Date": true,
115
+ "Expect": true,
116
+ "Host": true,
117
+ "Keep-Alive": true,
118
+ "Referer": true,
119
+ "TE": true,
120
+ "Trailer": true,
121
+ "Transfer-Encoding": true,
122
+ "Upgrade": true,
123
+ "User-Agent": true,
124
+ "Via": true
125
+ };
126
+
127
+ /*
128
+ Adds an "event" onto the fake xhr object
129
+ that just calls the same-named method. This is
130
+ in case a library adds callbacks for these events.
131
+ */
132
+ function _addEventListener(eventName, xhr){
133
+ xhr.addEventListener(eventName, function (event) {
134
+ var listener = xhr["on" + eventName];
135
+
136
+ if (listener && typeof listener == "function") {
137
+ listener(event);
138
+ }
139
+ });
140
+ }
141
+
142
+ function EventedObject() {
143
+ this._eventListeners = {};
144
+ var events = ["loadstart", "progress", "load", "abort", "loadend"];
145
+ for (var i = events.length - 1; i >= 0; i--) {
146
+ _addEventListener(events[i], this);
147
+ }
148
+ };
149
+
150
+ EventedObject.prototype = {
151
+ /*
152
+ Duplicates the behavior of native XMLHttpRequest's addEventListener function
153
+ */
154
+ addEventListener: function addEventListener(event, listener) {
155
+ this._eventListeners[event] = this._eventListeners[event] || [];
156
+ this._eventListeners[event].push(listener);
157
+ },
158
+
159
+ /*
160
+ Duplicates the behavior of native XMLHttpRequest's removeEventListener function
161
+ */
162
+ removeEventListener: function removeEventListener(event, listener) {
163
+ var listeners = this._eventListeners[event] || [];
164
+
165
+ for (var i = 0, l = listeners.length; i < l; ++i) {
166
+ if (listeners[i] == listener) {
167
+ return listeners.splice(i, 1);
168
+ }
169
+ }
170
+ },
171
+
172
+ /*
173
+ Duplicates the behavior of native XMLHttpRequest's dispatchEvent function
174
+ */
175
+ dispatchEvent: function dispatchEvent(event) {
176
+ var type = event.type;
177
+ var listeners = this._eventListeners[type] || [];
178
+
179
+ for (var i = 0; i < listeners.length; i++) {
180
+ if (typeof listeners[i] == "function") {
181
+ listeners[i].call(this, event);
182
+ } else {
183
+ listeners[i].handleEvent(event);
184
+ }
185
+ }
186
+
187
+ return !!event.defaultPrevented;
188
+ },
189
+
190
+ /*
191
+ Triggers an `onprogress` event with the given parameters.
192
+ */
193
+ _progress: function _progress(lengthComputable, loaded, total) {
194
+ var event = new _Event('progress');
195
+ event.target = this;
196
+ event.lengthComputable = lengthComputable;
197
+ event.loaded = loaded;
198
+ event.total = total;
199
+ this.dispatchEvent(event);
200
+ }
201
+ }
202
+
203
+ /*
204
+ Constructor for a fake window.XMLHttpRequest
205
+ */
206
+ function FakeXMLHttpRequest() {
207
+ EventedObject.call(this);
208
+ this.readyState = FakeXMLHttpRequest.UNSENT;
209
+ this.requestHeaders = {};
210
+ this.requestBody = null;
211
+ this.status = 0;
212
+ this.statusText = "";
213
+ this.upload = new EventedObject();
214
+ }
215
+
216
+ FakeXMLHttpRequest.prototype = new EventedObject();
217
+
218
+ // These status codes are available on the native XMLHttpRequest
219
+ // object, so we match that here in case a library is relying on them.
220
+ FakeXMLHttpRequest.UNSENT = 0;
221
+ FakeXMLHttpRequest.OPENED = 1;
222
+ FakeXMLHttpRequest.HEADERS_RECEIVED = 2;
223
+ FakeXMLHttpRequest.LOADING = 3;
224
+ FakeXMLHttpRequest.DONE = 4;
225
+
226
+ var FakeXMLHttpRequestProto = {
227
+ UNSENT: 0,
228
+ OPENED: 1,
229
+ HEADERS_RECEIVED: 2,
230
+ LOADING: 3,
231
+ DONE: 4,
232
+ async: true,
233
+
234
+ /*
235
+ Duplicates the behavior of native XMLHttpRequest's open function
236
+ */
237
+ open: function open(method, url, async, username, password) {
238
+ this.method = method;
239
+ this.url = url;
240
+ this.async = typeof async == "boolean" ? async : true;
241
+ this.username = username;
242
+ this.password = password;
243
+ this.responseText = null;
244
+ this.responseXML = null;
245
+ this.requestHeaders = {};
246
+ this.sendFlag = false;
247
+ this._readyStateChange(FakeXMLHttpRequest.OPENED);
248
+ },
249
+
250
+ /*
251
+ Duplicates the behavior of native XMLHttpRequest's setRequestHeader function
252
+ */
253
+ setRequestHeader: function setRequestHeader(header, value) {
254
+ verifyState(this);
255
+
256
+ if (unsafeHeaders[header] || /^(Sec-|Proxy-)/.test(header)) {
257
+ throw new Error("Refused to set unsafe header \"" + header + "\"");
258
+ }
259
+
260
+ if (this.requestHeaders[header]) {
261
+ this.requestHeaders[header] += "," + value;
262
+ } else {
263
+ this.requestHeaders[header] = value;
264
+ }
265
+ },
266
+
267
+ /*
268
+ Duplicates the behavior of native XMLHttpRequest's send function
269
+ */
270
+ send: function send(data) {
271
+ verifyState(this);
272
+
273
+ if (!/^(get|head)$/i.test(this.method)) {
274
+ if (this.requestHeaders["Content-Type"]) {
275
+ var value = this.requestHeaders["Content-Type"].split(";");
276
+ this.requestHeaders["Content-Type"] = value[0] + ";charset=utf-8";
277
+ } else {
278
+ this.requestHeaders["Content-Type"] = "text/plain;charset=utf-8";
279
+ }
280
+
281
+ this.requestBody = data;
282
+ }
283
+
284
+ this.errorFlag = false;
285
+ this.sendFlag = this.async;
286
+ this._readyStateChange(FakeXMLHttpRequest.OPENED);
287
+
288
+ if (typeof this.onSend == "function") {
289
+ this.onSend(this);
290
+ }
291
+
292
+ this.dispatchEvent(new _Event("loadstart", false, false, this));
293
+ },
294
+
295
+ /*
296
+ Duplicates the behavior of native XMLHttpRequest's abort function
297
+ */
298
+ abort: function abort() {
299
+ this.aborted = true;
300
+ this.responseText = null;
301
+ this.errorFlag = true;
302
+ this.requestHeaders = {};
303
+
304
+ if (this.readyState > FakeXMLHttpRequest.UNSENT && this.sendFlag) {
305
+ this._readyStateChange(FakeXMLHttpRequest.DONE);
306
+ this.sendFlag = false;
307
+ }
308
+
309
+ this.readyState = FakeXMLHttpRequest.UNSENT;
310
+
311
+ this.dispatchEvent(new _Event("abort", false, false, this));
312
+ if (typeof this.onerror === "function") {
313
+ this.onerror();
314
+ }
315
+ },
316
+
317
+ /*
318
+ Duplicates the behavior of native XMLHttpRequest's getResponseHeader function
319
+ */
320
+ getResponseHeader: function getResponseHeader(header) {
321
+ if (this.readyState < FakeXMLHttpRequest.HEADERS_RECEIVED) {
322
+ return null;
323
+ }
324
+
325
+ if (/^Set-Cookie2?$/i.test(header)) {
326
+ return null;
327
+ }
328
+
329
+ header = header.toLowerCase();
330
+
331
+ for (var h in this.responseHeaders) {
332
+ if (h.toLowerCase() == header) {
333
+ return this.responseHeaders[h];
334
+ }
335
+ }
336
+
337
+ return null;
338
+ },
339
+
340
+ /*
341
+ Duplicates the behavior of native XMLHttpRequest's getAllResponseHeaders function
342
+ */
343
+ getAllResponseHeaders: function getAllResponseHeaders() {
344
+ if (this.readyState < FakeXMLHttpRequest.HEADERS_RECEIVED) {
345
+ return "";
346
+ }
347
+
348
+ var headers = "";
349
+
350
+ for (var header in this.responseHeaders) {
351
+ if (this.responseHeaders.hasOwnProperty(header) && !/^Set-Cookie2?$/i.test(header)) {
352
+ headers += header + ": " + this.responseHeaders[header] + "\r\n";
353
+ }
354
+ }
355
+
356
+ return headers;
357
+ },
358
+
359
+ /*
360
+ Places a FakeXMLHttpRequest object into the passed
361
+ state.
362
+ */
363
+ _readyStateChange: function _readyStateChange(state) {
364
+ this.readyState = state;
365
+
366
+ if (typeof this.onreadystatechange == "function") {
367
+ this.onreadystatechange();
368
+ }
369
+
370
+ this.dispatchEvent(new _Event("readystatechange"));
371
+
372
+ if (this.readyState == FakeXMLHttpRequest.DONE) {
373
+ this.dispatchEvent(new _Event("load", false, false, this));
374
+ this.dispatchEvent(new _Event("loadend", false, false, this));
375
+ }
376
+ },
377
+
378
+
379
+ /*
380
+ Sets the FakeXMLHttpRequest object's response headers and
381
+ places the object into readyState 2
382
+ */
383
+ _setResponseHeaders: function _setResponseHeaders(headers) {
384
+ this.responseHeaders = {};
385
+
386
+ for (var header in headers) {
387
+ if (headers.hasOwnProperty(header)) {
388
+ this.responseHeaders[header] = headers[header];
389
+ }
390
+ }
391
+
392
+ if (this.async) {
393
+ this._readyStateChange(FakeXMLHttpRequest.HEADERS_RECEIVED);
394
+ } else {
395
+ this.readyState = FakeXMLHttpRequest.HEADERS_RECEIVED;
396
+ }
397
+ },
398
+
399
+ /*
400
+ Sets the FakeXMLHttpRequest object's response body and
401
+ if body text is XML, sets responseXML to parsed document
402
+ object
403
+ */
404
+ _setResponseBody: function _setResponseBody(body) {
405
+ verifyRequestSent(this);
406
+ verifyHeadersReceived(this);
407
+ verifyResponseBodyType(body);
408
+
409
+ var chunkSize = this.chunkSize || 10;
410
+ var index = 0;
411
+ this.responseText = "";
412
+
413
+ do {
414
+ if (this.async) {
415
+ this._readyStateChange(FakeXMLHttpRequest.LOADING);
416
+ }
417
+
418
+ this.responseText += body.substring(index, index + chunkSize);
419
+ index += chunkSize;
420
+ } while (index < body.length);
421
+
422
+ var type = this.getResponseHeader("Content-Type");
423
+
424
+ if (this.responseText && (!type || /(text\/xml)|(application\/xml)|(\+xml)/.test(type))) {
425
+ try {
426
+ this.responseXML = parseXML(this.responseText);
427
+ } catch (e) {
428
+ // Unable to parse XML - no biggie
429
+ }
430
+ }
431
+
432
+ if (this.async) {
433
+ this._readyStateChange(FakeXMLHttpRequest.DONE);
434
+ } else {
435
+ this.readyState = FakeXMLHttpRequest.DONE;
436
+ }
437
+ },
438
+
439
+ /*
440
+ Forces a response on to the FakeXMLHttpRequest object.
441
+
442
+ This is the public API for faking responses. This function
443
+ takes a number status, headers object, and string body:
444
+
445
+ ```
446
+ xhr.respond(404, {Content-Type: 'text/plain'}, "Sorry. This object was not found.")
447
+
448
+ ```
449
+ */
450
+ respond: function respond(status, headers, body) {
451
+ this._setResponseHeaders(headers || {});
452
+ this.status = typeof status == "number" ? status : 200;
453
+ this.statusText = httpStatusCodes[this.status];
454
+ this._setResponseBody(body || "");
455
+ }
456
+ };
457
+
458
+ for (var property in FakeXMLHttpRequestProto) {
459
+ FakeXMLHttpRequest.prototype[property] = FakeXMLHttpRequestProto[property];
460
+ }
461
+
462
+ function verifyState(xhr) {
463
+ if (xhr.readyState !== FakeXMLHttpRequest.OPENED) {
464
+ throw new Error("INVALID_STATE_ERR");
465
+ }
466
+
467
+ if (xhr.sendFlag) {
468
+ throw new Error("INVALID_STATE_ERR");
469
+ }
470
+ }
471
+
472
+
473
+ function verifyRequestSent(xhr) {
474
+ if (xhr.readyState == FakeXMLHttpRequest.DONE) {
475
+ throw new Error("Request done");
476
+ }
477
+ }
478
+
479
+ function verifyHeadersReceived(xhr) {
480
+ if (xhr.async && xhr.readyState != FakeXMLHttpRequest.HEADERS_RECEIVED) {
481
+ throw new Error("No headers received");
482
+ }
483
+ }
484
+
485
+ function verifyResponseBodyType(body) {
486
+ if (typeof body != "string") {
487
+ var error = new Error("Attempted to respond to fake XMLHttpRequest with " +
488
+ body + ", which is not a string.");
489
+ error.name = "InvalidBodyException";
490
+ throw error;
491
+ }
492
+ }
493
+ var fake_xml_http_request = FakeXMLHttpRequest;
494
+
495
+ return fake_xml_http_request;
496
+
497
+ }));