| Index: remoting/webapp/crd/js/mock_xhr.js
|
| diff --git a/remoting/webapp/crd/js/mock_xhr.js b/remoting/webapp/crd/js/mock_xhr.js
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..706badbf82fbc69d44afc39953c51a52606a9ec2
|
| --- /dev/null
|
| +++ b/remoting/webapp/crd/js/mock_xhr.js
|
| @@ -0,0 +1,296 @@
|
| +/**
|
| + * @fileoverview A mock version of remoting.Xhr. Compared to
|
| + * sinon.useMockXhr, this allows unit tests to be written at a higher
|
| + * level, and it eliminates a fair amount of boilerplate involved in
|
| + * making the sinon mocks work with asynchronous calls
|
| + * (cf. gcd_client_unittest.js vs. gcd_client_with_mock_xhr.js for an
|
| + * example).
|
| + */
|
| +
|
| +(function() {
|
| +'use strict';
|
| +
|
| +/**
|
| + * @constructor
|
| + * @param {remoting.Xhr.Params} params
|
| + */
|
| +remoting.MockXhr = function(params) {
|
| + origXhr['checkParams_'](params);
|
| +
|
| + /** @const {remoting.Xhr.Params} */
|
| + this.params = normalizeParams(params);
|
| +
|
| + /** @private {base.Deferred<!remoting.Xhr.Response>} */
|
| + this.deferred_ = null;
|
| +
|
| + /** @type {remoting.Xhr.Response} */
|
| + this.response_ = null;
|
| +
|
| + /** @type {boolean} */
|
| + this.aborted_ = false;
|
| +};
|
| +
|
| +/**
|
| + * Converts constuctor parameters to a normalized form that hides
|
| + * details of how the constructor is called.
|
| + * @param {remoting.Xhr.Params} params
|
| + * @return {remoting.Xhr.Params} The argument with all missing fields
|
| + * filled in with default values.
|
| + */
|
| +var normalizeParams = function(params) {
|
| + return {
|
| + method: params.method,
|
| + url: params.url,
|
| + urlParams: typeof params.urlParams == 'object' ?
|
| + base.copyWithoutNullFields(params.urlParams) :
|
| + params.urlParams,
|
| + textContent: params.textContent,
|
| + jsonContent: params.jsonContent,
|
| + formContent: params.formContent ||
|
| + base.copyWithoutNullFields(params.formContent),
|
| + headers: base.copyWithoutNullFields(params.headers),
|
| + withCredentials: Boolean(params.withCredentials),
|
| + oauthToken: params.oauthToken === undefined ? undefined : params.oauthToken,
|
| + useIdentity: Boolean(params.useIdentity),
|
| + acceptJson: Boolean(params.acceptJson)
|
| + };
|
| +};
|
| +
|
| +/**
|
| + * Psuedo-override from remoting.Xhr.
|
| + * @return {void}
|
| + */
|
| +remoting.MockXhr.prototype.abort = function() {
|
| + this.aborted_ = true;
|
| +};
|
| +
|
| +/**
|
| + * Psuedo-override from remoting.Xhr.
|
| + * @return {!Promise<!remoting.Xhr.Response>}
|
| + */
|
| +remoting.MockXhr.prototype.start = function() {
|
| + runMatchingHandler(this);
|
| + if (!this.deferred_) {
|
| + this.deferred_ = new base.Deferred();
|
| + this.maybeRespond_();
|
| + }
|
| + return this.deferred_.promise();
|
| +};
|
| +
|
| +/**
|
| + * Tells this object to send an empty response to the current or next
|
| + * request.
|
| + * @param {number} status The HTTP status code to respond with.
|
| + */
|
| +remoting.MockXhr.prototype.setEmptyResponse = function(status) {
|
| + this.setResponse_(new remoting.Xhr.Response(
|
| + status,
|
| + 'mock status text from setEmptyResponse',
|
| + null,
|
| + '',
|
| + false));
|
| +};
|
| +
|
| +/**
|
| + * Tells this object to send a text/plain response to the current or
|
| + * next request.
|
| + * @param {number} status The HTTP status code to respond with.
|
| + * @param {string} body The content to respond with.
|
| + */
|
| +remoting.MockXhr.prototype.setTextResponse = function(status, body) {
|
| + this.setResponse_(new remoting.Xhr.Response(
|
| + status,
|
| + 'mock status text from setTextResponse',
|
| + null,
|
| + body || '',
|
| + false));
|
| +};
|
| +
|
| +/**
|
| + * Tells this object to send an application/json response to the
|
| + * current or next request.
|
| + * @param {number} status The HTTP status code to respond with.
|
| + * @param {*} body The content to respond with.
|
| + */
|
| +remoting.MockXhr.prototype.setJsonResponse = function(status, body) {
|
| + if (!this.params.acceptJson) {
|
| + throw new Error('client does not want JSON response');
|
| + }
|
| + this.setResponse_(new remoting.Xhr.Response(
|
| + status,
|
| + 'mock status text from setJsonResponse',
|
| + null,
|
| + JSON.stringify(body),
|
| + true));
|
| +};
|
| +
|
| +/**
|
| + * Sets the response to be used for the current or next request.
|
| + * @param {!remoting.Xhr.Response} response
|
| + * @private
|
| + */
|
| +remoting.MockXhr.prototype.setResponse_ = function(response) {
|
| + base.debug.assert(this.response_ == null);
|
| + this.response_ = response;
|
| + this.maybeRespond_();
|
| +};
|
| +
|
| +/**
|
| + * Sends a response if one is available.
|
| + * @private
|
| + */
|
| +remoting.MockXhr.prototype.maybeRespond_ = function() {
|
| + if (this.deferred_ && this.response_ && !this.aborted_) {
|
| + this.deferred_.resolve(this.response_);
|
| + }
|
| +};
|
| +
|
| +/** @type {?function(this: remoting.Xhr, remoting.Xhr.Params):void} */
|
| +var origXhr = null;
|
| +
|
| +/**
|
| + * @type {!Array<remoting.MockXhr.UrlHandler>}
|
| + */
|
| +var handlers = [];
|
| +
|
| +/**
|
| + * Registers a handler for a given method and URL. The |urlPattern|
|
| + * argument may either be a string, which must equal a URL to match
|
| + * it, or a RegExp.
|
| + *
|
| + * Matching handlers are run when a FakeXhr's |start| method is called.
|
| + *
|
| + * @param {?string} method The HTTP method to respond to, or null to
|
| + * respond to any method.
|
| + * @param {?string|!RegExp} urlPattern The URL or pattern to respond
|
| + * to, or null to match any URL.
|
| + * @param {function(!remoting.MockXhr):void} callback The function to call
|
| + * when a matching XHR is started.
|
| + * @param {boolean=} opt_reuse If true, the response can be used for
|
| + * multiple requests.
|
| + */
|
| +remoting.MockXhr.setResponseFor = function(
|
| + method, urlPattern, callback, opt_reuse) {
|
| + handlers.push({
|
| + method: method,
|
| + urlPattern: urlPattern,
|
| + callback: callback,
|
| + reuse: !!opt_reuse
|
| + });
|
| +};
|
| +
|
| +/**
|
| + * Calls |setResponseFor| to add a 204 (no content) response.
|
| + *
|
| + * @param {?string} method
|
| + * @param {?string|!RegExp} urlPattern
|
| + * @param {boolean=} opt_reuse
|
| + */
|
| +remoting.MockXhr.setEmptyResponseFor = function(method, urlPattern, opt_reuse) {
|
| + remoting.MockXhr.setResponseFor(
|
| + method, urlPattern, function(/** remoting.MockXhr */ xhr) {
|
| + xhr.setEmptyResponse(204);
|
| + }, opt_reuse);
|
| +};
|
| +
|
| +/**
|
| + * Calls |setResponseFor| to add a 200 response with text content.
|
| + *
|
| + * @param {?string} method
|
| + * @param {?string|!RegExp} urlPattern
|
| + * @param {string} content
|
| + * @param {boolean=} opt_reuse
|
| + */
|
| +remoting.MockXhr.setTextResponseFor = function(
|
| + method, urlPattern, content, opt_reuse) {
|
| + remoting.MockXhr.setResponseFor(
|
| + method, urlPattern, function(/** remoting.MockXhr */ xhr) {
|
| + xhr.setTextResponse(200, content);
|
| + }, opt_reuse);
|
| +};
|
| +
|
| +/**
|
| + * Calls |setResponseFor| to add a 200 response with JSON content.
|
| + *
|
| + * @param {?string} method
|
| + * @param {?string|!RegExp} urlPattern
|
| + * @param {*} content
|
| + * @param {boolean=} opt_reuse
|
| + */
|
| +remoting.MockXhr.setJsonResponseFor = function(
|
| + method, urlPattern, content, opt_reuse) {
|
| + remoting.MockXhr.setResponseFor(
|
| + method, urlPattern, function(/** remoting.MockXhr */ xhr) {
|
| + xhr.setJsonResponse(200, content);
|
| + }, opt_reuse);
|
| +};
|
| +
|
| +/**
|
| + * Runs the most first handler for a given method and URL.
|
| + * @param {!remoting.MockXhr} xhr
|
| + */
|
| +var runMatchingHandler = function(xhr) {
|
| + for (var i = 0; i < handlers.length; i++) {
|
| + var handler = handlers[i];
|
| + if (handler.method == null || handler.method != xhr.params.method) {
|
| + continue;
|
| + }
|
| + if (handler.urlPattern == null) {
|
| + // Let the handler run.
|
| + } else if (typeof handler.urlPattern == 'string') {
|
| + if (xhr.params.url != handler.urlPattern) {
|
| + continue;
|
| + }
|
| + } else {
|
| + var regexp = /** @type {RegExp} */ (handler.urlPattern);
|
| + if (!regexp.test(xhr.params.url)) {
|
| + continue;
|
| + }
|
| + }
|
| + if (!handler.reuse) {
|
| + handlers.splice(i, 1);
|
| + }
|
| + handler.callback(xhr);
|
| + return;
|
| + };
|
| + throw new Error(
|
| + 'No handler registered for ' + xhr.params.method +
|
| + ' to '+ xhr.params.url);
|
| +};
|
| +
|
| +/**
|
| + * Activates this mock.
|
| + */
|
| +remoting.MockXhr.activate = function() {
|
| + base.debug.assert(
|
| + origXhr == null,
|
| + 'Xhr mocking already active');
|
| + origXhr = remoting.Xhr;
|
| + remoting.MockXhr.Response = remoting.Xhr.Response;
|
| + remoting['Xhr'] = remoting.MockXhr;
|
| +};
|
| +
|
| +/**
|
| + * Restores the original definiton of |remoting.Xhr|.
|
| + */
|
| +remoting.MockXhr.restore = function() {
|
| + base.debug.assert(
|
| + origXhr != null,
|
| + 'Xhr mocking not active');
|
| + remoting['Xhr'] = origXhr;
|
| + origXhr = null;
|
| + handlers = [];
|
| +};
|
| +
|
| +})();
|
| +
|
| +// Can't put put typedefs inside a function :-(
|
| +/**
|
| + * @typedef {{
|
| + * method:?string,
|
| + * urlPattern:(?string|RegExp),
|
| + * callback:function(!remoting.MockXhr):void,
|
| + * reuse:boolean
|
| + * }}
|
| + */
|
| +remoting.MockXhr.UrlHandler;
|
|
|