OLD | NEW |
(Empty) | |
| 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. |
| 4 |
| 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 */ |
| 13 |
| 14 (function() { |
| 15 'use strict'; |
| 16 |
| 17 /** |
| 18 * @constructor |
| 19 * @param {remoting.Xhr.Params} params |
| 20 */ |
| 21 remoting.MockXhr = function(params) { |
| 22 origXhr['checkParams_'](params); |
| 23 |
| 24 /** @const {remoting.Xhr.Params} */ |
| 25 this.params = normalizeParams(params); |
| 26 |
| 27 /** @private {base.Deferred<!remoting.Xhr.Response>} */ |
| 28 this.deferred_ = null; |
| 29 |
| 30 /** @type {remoting.Xhr.Response} */ |
| 31 this.response_ = null; |
| 32 |
| 33 /** @type {boolean} */ |
| 34 this.aborted_ = false; |
| 35 }; |
| 36 |
| 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 }; |
| 61 }; |
| 62 |
| 63 /** |
| 64 * Psuedo-override from remoting.Xhr. |
| 65 * @return {void} |
| 66 */ |
| 67 remoting.MockXhr.prototype.abort = function() { |
| 68 this.aborted_ = true; |
| 69 }; |
| 70 |
| 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(); |
| 82 }; |
| 83 |
| 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)); |
| 96 }; |
| 97 |
| 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)); |
| 111 }; |
| 112 |
| 113 /** |
| 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)); |
| 129 }; |
| 130 |
| 131 /** |
| 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 base.debug.assert(this.response_ == null); |
| 138 this.response_ = response; |
| 139 this.maybeRespond_(); |
| 140 }; |
| 141 |
| 142 /** |
| 143 * Sends a response if one is available. |
| 144 * @private |
| 145 */ |
| 146 remoting.MockXhr.prototype.maybeRespond_ = function() { |
| 147 if (this.deferred_ && this.response_ && !this.aborted_) { |
| 148 this.deferred_.resolve(this.response_); |
| 149 } |
| 150 }; |
| 151 |
| 152 /** |
| 153 * The original value of the remoting.Xhr constructor. The JSDoc type |
| 154 * is that of the remoting.Xhr constructor function. |
| 155 * @type {?function(this: remoting.Xhr, remoting.Xhr.Params):void} |
| 156 */ |
| 157 var origXhr = null; |
| 158 |
| 159 /** |
| 160 * @type {!Array<remoting.MockXhr.UrlHandler>} |
| 161 */ |
| 162 var handlers = []; |
| 163 |
| 164 /** |
| 165 * Registers a handler for a given method and URL. The |urlPattern| |
| 166 * argument may either be a string, which must equal a URL to match |
| 167 * it, or a RegExp. |
| 168 * |
| 169 * Matching handlers are run when a FakeXhr's |start| method is |
| 170 * called. The handler should generally call one of |
| 171 * |set{Test,Json,Empty}Response| |
| 172 * |
| 173 * @param {?string} method The HTTP method to respond to, or null to |
| 174 * respond to any method. |
| 175 * @param {?string|!RegExp} urlPattern The URL or pattern to respond |
| 176 * to, or null to match any URL. |
| 177 * @param {function(!remoting.MockXhr):void} callback The handler |
| 178 * function to call when a matching XHR is started. |
| 179 * @param {boolean=} opt_reuse If true, the response can be used for |
| 180 * multiple requests. |
| 181 */ |
| 182 remoting.MockXhr.setResponseFor = function( |
| 183 method, urlPattern, callback, opt_reuse) { |
| 184 handlers.push({ |
| 185 method: method, |
| 186 urlPattern: urlPattern, |
| 187 callback: callback, |
| 188 reuse: !!opt_reuse |
| 189 }); |
| 190 }; |
| 191 |
| 192 /** |
| 193 * Installs a 204 (no content) response. See |setResponseFor| for |
| 194 * more details on how the parameters work. |
| 195 * |
| 196 * @param {?string} method |
| 197 * @param {?string|!RegExp} urlPattern |
| 198 * @param {boolean=} opt_reuse |
| 199 */ |
| 200 remoting.MockXhr.setEmptyResponseFor = function(method, urlPattern, opt_reuse) { |
| 201 remoting.MockXhr.setResponseFor( |
| 202 method, urlPattern, function(/** remoting.MockXhr */ xhr) { |
| 203 xhr.setEmptyResponse(204); |
| 204 }, opt_reuse); |
| 205 }; |
| 206 |
| 207 /** |
| 208 * Installs a 200 response with text content. See |setResponseFor| |
| 209 * for more details on how the parameters work. |
| 210 * |
| 211 * @param {?string} method |
| 212 * @param {?string|!RegExp} urlPattern |
| 213 * @param {string} content |
| 214 * @param {boolean=} opt_reuse |
| 215 */ |
| 216 remoting.MockXhr.setTextResponseFor = function( |
| 217 method, urlPattern, content, opt_reuse) { |
| 218 remoting.MockXhr.setResponseFor( |
| 219 method, urlPattern, function(/** remoting.MockXhr */ xhr) { |
| 220 xhr.setTextResponse(200, content); |
| 221 }, opt_reuse); |
| 222 }; |
| 223 |
| 224 /** |
| 225 * Installs a 200 response with JSON content. See |setResponseFor| |
| 226 * for more details on how the parameters work. |
| 227 * |
| 228 * @param {?string} method |
| 229 * @param {?string|!RegExp} urlPattern |
| 230 * @param {*} content |
| 231 * @param {boolean=} opt_reuse |
| 232 */ |
| 233 remoting.MockXhr.setJsonResponseFor = function( |
| 234 method, urlPattern, content, opt_reuse) { |
| 235 remoting.MockXhr.setResponseFor( |
| 236 method, urlPattern, function(/** remoting.MockXhr */ xhr) { |
| 237 xhr.setJsonResponse(200, content); |
| 238 }, opt_reuse); |
| 239 }; |
| 240 |
| 241 /** |
| 242 * Runs the most first handler for a given method and URL. |
| 243 * @param {!remoting.MockXhr} xhr |
| 244 */ |
| 245 var runMatchingHandler = function(xhr) { |
| 246 for (var i = 0; i < handlers.length; i++) { |
| 247 var handler = handlers[i]; |
| 248 if (handler.method == null || handler.method != xhr.params.method) { |
| 249 continue; |
| 250 } |
| 251 if (handler.urlPattern == null) { |
| 252 // Let the handler run. |
| 253 } else if (typeof handler.urlPattern == 'string') { |
| 254 if (xhr.params.url != handler.urlPattern) { |
| 255 continue; |
| 256 } |
| 257 } else { |
| 258 var regexp = /** @type {RegExp} */ (handler.urlPattern); |
| 259 if (!regexp.test(xhr.params.url)) { |
| 260 continue; |
| 261 } |
| 262 } |
| 263 if (!handler.reuse) { |
| 264 handlers.splice(i, 1); |
| 265 } |
| 266 handler.callback(xhr); |
| 267 return; |
| 268 }; |
| 269 throw new Error( |
| 270 'No handler registered for ' + xhr.params.method + |
| 271 ' to '+ xhr.params.url); |
| 272 }; |
| 273 |
| 274 /** |
| 275 * Activates this mock. |
| 276 */ |
| 277 remoting.MockXhr.activate = function() { |
| 278 base.debug.assert( |
| 279 origXhr == null, |
| 280 'Xhr mocking already active'); |
| 281 origXhr = remoting.Xhr; |
| 282 remoting.MockXhr.Response = remoting.Xhr.Response; |
| 283 remoting['Xhr'] = remoting.MockXhr; |
| 284 }; |
| 285 |
| 286 /** |
| 287 * Restores the original definiton of |remoting.Xhr|. |
| 288 */ |
| 289 remoting.MockXhr.restore = function() { |
| 290 base.debug.assert( |
| 291 origXhr != null, |
| 292 'Xhr mocking not active'); |
| 293 remoting['Xhr'] = origXhr; |
| 294 origXhr = null; |
| 295 handlers = []; |
| 296 }; |
| 297 |
| 298 })(); |
| 299 |
| 300 // Can't put put typedefs inside a function :-( |
| 301 /** |
| 302 * @typedef {{ |
| 303 * method:?string, |
| 304 * urlPattern:(?string|RegExp), |
| 305 * callback:function(!remoting.MockXhr):void, |
| 306 * reuse:boolean |
| 307 * }} |
| 308 */ |
| 309 remoting.MockXhr.UrlHandler; |
OLD | NEW |