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