Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / remoting / webapp / crd / js / mock_xhr.js
blobda446887bcff14170f6a55e5e9d762c99f52f0f8
1 // Copyright (c) 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 /**
6  * @fileoverview A mock version of remoting.Xhr.  Compared to
7  * sinon.useMockXhr, this allows unit tests to be written at a higher
8  * level, and it eliminates a fair amount of boilerplate involved in
9  * making the sinon mocks work with asynchronous calls
10  * (cf. gcd_client_unittest.js vs. gcd_client_with_mock_xhr.js for an
11  * example).
12  */
14 (function() {
15 'use strict';
17 /**
18  * @constructor
19  * @param {remoting.Xhr.Params} params
20  */
21 remoting.MockXhr = function(params) {
22   origXhr['checkParams_'](params);
24   /** @const {remoting.Xhr.Params} */
25   this.params = normalizeParams(params);
27   /** @private {base.Deferred<!remoting.Xhr.Response>} */
28   this.deferred_ = null;
30   /** @type {remoting.Xhr.Response} */
31   this.response_ = null;
33   /** @type {boolean} */
34   this.aborted_ = false;
37 /**
38  * Converts constuctor parameters to a normalized form that hides
39  * details of how the constructor is called.
40  * @param {remoting.Xhr.Params} params
41  * @return {remoting.Xhr.Params} The argument with all missing fields
42  *     filled in with default values.
43  */
44 var normalizeParams = function(params) {
45   return {
46     method: params.method,
47     url: params.url,
48     urlParams: typeof params.urlParams == 'object' ?
49         base.copyWithoutNullFields(params.urlParams) :
50         params.urlParams,
51     textContent: params.textContent,
52     jsonContent: params.jsonContent,
53     formContent: params.formContent === undefined ? undefined :
54         base.copyWithoutNullFields(params.formContent),
55     headers: base.copyWithoutNullFields(params.headers),
56     withCredentials: Boolean(params.withCredentials),
57     oauthToken: params.oauthToken,
58     useIdentity: Boolean(params.useIdentity),
59     acceptJson: Boolean(params.acceptJson)
60   };
63 /**
64  * Psuedo-override from remoting.Xhr.
65  * @return {void}
66  */
67 remoting.MockXhr.prototype.abort = function() {
68   this.aborted_ = true;
71 /**
72  * Psuedo-override from remoting.Xhr.
73  * @return {!Promise<!remoting.Xhr.Response>}
74  */
75 remoting.MockXhr.prototype.start = function() {
76   runMatchingHandler(this);
77   if (!this.deferred_) {
78     this.deferred_ = new base.Deferred();
79     this.maybeRespond_();
80   }
81   return this.deferred_.promise();
84 /**
85  * Tells this object to send an empty response to the current or next
86  * request.
87  * @param {number} status The HTTP status code to respond with.
88  */
89 remoting.MockXhr.prototype.setEmptyResponse = function(status) {
90   this.setResponse_(new remoting.Xhr.Response(
91       status,
92       'mock status text from setEmptyResponse',
93       null,
94       '',
95       false));
98 /**
99  * Tells this object to send a text/plain response to the current or
100  * next request.
101  * @param {number} status The HTTP status code to respond with.
102  * @param {string} body The content to respond with.
103  */
104 remoting.MockXhr.prototype.setTextResponse = function(status, body) {
105   this.setResponse_(new remoting.Xhr.Response(
106       status,
107       'mock status text from setTextResponse',
108       null,
109       body || '',
110       false));
114  * Tells this object to send an application/json response to the
115  * current or next request.
116  * @param {number} status The HTTP status code to respond with.
117  * @param {*} body The content to respond with.
118  */
119 remoting.MockXhr.prototype.setJsonResponse = function(status, body) {
120   if (!this.params.acceptJson) {
121     throw new Error('client does not want JSON response');
122   }
123   this.setResponse_(new remoting.Xhr.Response(
124       status,
125       'mock status text from setJsonResponse',
126       null,
127       JSON.stringify(body),
128       true));
132  * Sets the response to be used for the current or next request.
133  * @param {!remoting.Xhr.Response} response
134  * @private
135  */
136 remoting.MockXhr.prototype.setResponse_ = function(response) {
137   console.assert(this.response_ == null,
138                  'Duplicate setResponse_() invocation.');
139   this.response_ = response;
140   this.maybeRespond_();
144  * Sends a response if one is available.
145  * @private
146  */
147 remoting.MockXhr.prototype.maybeRespond_ = function() {
148   if (this.deferred_ && this.response_ && !this.aborted_) {
149     this.deferred_.resolve(this.response_);
150   }
154  * The original value of the remoting.Xhr constructor.  The JSDoc type
155  * is that of the remoting.Xhr constructor function.
156  * @type {?function(this: remoting.Xhr, remoting.Xhr.Params):void}
157  */
158 var origXhr = null;
161  * @type {!Array<remoting.MockXhr.UrlHandler>}
162  */
163 var handlers = [];
166  * Registers a handler for a given method and URL.  The |urlPattern|
167  * argument may either be a string, which must equal a URL to match
168  * it, or a RegExp.
170  * Matching handlers are run when a FakeXhr's |start| method is
171  * called.  The handler should generally call one of
172  * |set{Test,Json,Empty}Response|
174  * @param {?string} method The HTTP method to respond to, or null to
175  *     respond to any method.
176  * @param {?string|!RegExp} urlPattern The URL or pattern to respond
177  *     to, or null to match any URL.
178  * @param {function(!remoting.MockXhr):void} callback The handler
179  *     function to call when a matching XHR is started.
180  * @param {boolean=} opt_reuse If true, the response can be used for
181  *     multiple requests.
182  */
183 remoting.MockXhr.setResponseFor = function(
184     method, urlPattern, callback, opt_reuse) {
185   handlers.push({
186     method: method,
187     urlPattern: urlPattern,
188     callback: callback,
189     reuse: !!opt_reuse
190   });
194  * Installs a response with no content.  See |setResponseFor| for
195  * more details on how the parameters work.
197  * @param {?string} method
198  * @param {?string|!RegExp} urlPattern
199  * @param {number=} opt_status The status code to return.
200  * @param {boolean=} opt_reuse
201  */
202 remoting.MockXhr.setEmptyResponseFor = function(
203     method, urlPattern, opt_status, opt_reuse) {
204   remoting.MockXhr.setResponseFor(
205       method, urlPattern, function(/** remoting.MockXhr */ xhr) {
206         xhr.setEmptyResponse(opt_status === undefined ? 204 : opt_status);
207       }, opt_reuse);
211  * Installs a 200 response with text content.  See |setResponseFor|
212  * for more details on how the parameters work.
214  * @param {?string} method
215  * @param {?string|!RegExp} urlPattern
216  * @param {string} content
217  * @param {boolean=} opt_reuse
218  */
219 remoting.MockXhr.setTextResponseFor = function(
220     method, urlPattern, content, opt_reuse) {
221   remoting.MockXhr.setResponseFor(
222       method, urlPattern, function(/** remoting.MockXhr */ xhr) {
223         xhr.setTextResponse(200, content);
224       }, opt_reuse);
228  * Installs a 200 response with JSON content.  See |setResponseFor|
229  * for more details on how the parameters work.
231  * @param {?string} method
232  * @param {?string|!RegExp} urlPattern
233  * @param {*} content
234  * @param {boolean=} opt_reuse
235  */
236 remoting.MockXhr.setJsonResponseFor = function(
237     method, urlPattern, content, opt_reuse) {
238   remoting.MockXhr.setResponseFor(
239       method, urlPattern, function(/** remoting.MockXhr */ xhr) {
240         xhr.setJsonResponse(200, content);
241       }, opt_reuse);
245  * Runs the most first handler for a given method and URL.
246  * @param {!remoting.MockXhr} xhr
247  */
248 var runMatchingHandler = function(xhr) {
249   for (var i = 0; i < handlers.length; i++) {
250     var handler = handlers[i];
251     if (handler.method == null || handler.method != xhr.params.method) {
252       continue;
253     }
254     if (handler.urlPattern == null) {
255       // Let the handler run.
256     } else if (typeof handler.urlPattern == 'string') {
257       if (xhr.params.url != handler.urlPattern) {
258         continue;
259       }
260     } else {
261       var regexp = /** @type {RegExp} */ (handler.urlPattern);
262       if (!regexp.test(xhr.params.url)) {
263         continue;
264       }
265     }
266     if (!handler.reuse) {
267       handlers.splice(i, 1);
268     }
269     handler.callback(xhr);
270     return;
271   };
272   throw new Error(
273       'No handler registered for ' + xhr.params.method +
274       ' to '+ xhr.params.url);
278  * Activates this mock.
279  */
280 remoting.MockXhr.activate = function() {
281   console.assert(origXhr == null, 'Xhr mocking already active');
282   origXhr = remoting.Xhr;
283   remoting.MockXhr.Response = remoting.Xhr.Response;
284   remoting['Xhr'] = remoting.MockXhr;
288  * Restores the original definiton of |remoting.Xhr|.
289  */
290 remoting.MockXhr.restore = function() {
291   console.assert(origXhr != null, 'Xhr mocking not active');
292   remoting['Xhr'] = origXhr;
293   origXhr = null;
294   handlers = [];
297 })();
299 // Can't put put typedefs inside a function :-(
301  * @typedef {{
302  *   method:?string,
303  *   urlPattern:(?string|RegExp),
304  *   callback:function(!remoting.MockXhr):void,
305  *   reuse:boolean
306  * }}
307  */
308 remoting.MockXhr.UrlHandler;