Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(297)

Unified Diff: remoting/webapp/crd/js/mock_xhr.js

Issue 1055313002: Added mock_xhr module w/ unit tests. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: for review Created 5 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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;

Powered by Google App Engine
This is Rietveld 408576698