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 |