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.
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
19 * @param {remoting.Xhr.Params} params
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;
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.
44 var normalizeParams = function(params
) {
46 method
: params
.method
,
48 urlParams
: typeof params
.urlParams
== 'object' ?
49 base
.copyWithoutNullFields(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
)
64 * Psuedo-override from remoting.Xhr.
67 remoting
.MockXhr
.prototype.abort = function() {
72 * Psuedo-override from remoting.Xhr.
73 * @return {!Promise<!remoting.Xhr.Response>}
75 remoting
.MockXhr
.prototype.start = function() {
76 runMatchingHandler(this);
77 if (!this.deferred_
) {
78 this.deferred_
= new base
.Deferred();
81 return this.deferred_
.promise();
85 * Tells this object to send an empty response to the current or next
87 * @param {number} status The HTTP status code to respond with.
89 remoting
.MockXhr
.prototype.setEmptyResponse = function(status
) {
90 this.setResponse_(new remoting
.Xhr
.Response(
92 'mock status text from setEmptyResponse',
99 * Tells this object to send a text/plain response to the current or
101 * @param {number} status The HTTP status code to respond with.
102 * @param {string} body The content to respond with.
104 remoting
.MockXhr
.prototype.setTextResponse = function(status
, body
) {
105 this.setResponse_(new remoting
.Xhr
.Response(
107 'mock status text from setTextResponse',
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.
119 remoting
.MockXhr
.prototype.setJsonResponse = function(status
, body
) {
120 if (!this.params
.acceptJson
) {
121 throw new Error('client does not want JSON response');
123 this.setResponse_(new remoting
.Xhr
.Response(
125 'mock status text from setJsonResponse',
127 JSON
.stringify(body
),
132 * Sets the response to be used for the current or next request.
133 * @param {!remoting.Xhr.Response} response
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.
147 remoting
.MockXhr
.prototype.maybeRespond_ = function() {
148 if (this.deferred_
&& this.response_
&& !this.aborted_
) {
149 this.deferred_
.resolve(this.response_
);
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}
161 * @type {!Array<remoting.MockXhr.UrlHandler>}
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
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
183 remoting
.MockXhr
.setResponseFor = function(
184 method
, urlPattern
, callback
, opt_reuse
) {
187 urlPattern
: urlPattern
,
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
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
);
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
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
);
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
234 * @param {boolean=} opt_reuse
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
);
245 * Runs the most first handler for a given method and URL.
246 * @param {!remoting.MockXhr} xhr
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
) {
254 if (handler
.urlPattern
== null) {
255 // Let the handler run.
256 } else if (typeof handler
.urlPattern
== 'string') {
257 if (xhr
.params
.url
!= handler
.urlPattern
) {
261 var regexp
= /** @type {RegExp} */ (handler
.urlPattern
);
262 if (!regexp
.test(xhr
.params
.url
)) {
266 if (!handler
.reuse
) {
267 handlers
.splice(i
, 1);
269 handler
.callback(xhr
);
273 'No handler registered for ' + xhr
.params
.method
+
274 ' to '+ xhr
.params
.url
);
278 * Activates this mock.
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|.
290 remoting
.MockXhr
.restore = function() {
291 console
.assert(origXhr
!= null, 'Xhr mocking not active');
292 remoting
['Xhr'] = origXhr
;
299 // Can't put put typedefs inside a function :-(
303 * urlPattern:(?string|RegExp),
304 * callback:function(!remoting.MockXhr):void,
308 remoting
.MockXhr
.UrlHandler
;