Commit 58e816f0 authored by jrw's avatar jrw Committed by Commit bot

Added mock_xhr module w/ unit tests.

Also ported gcd_client_unittests to use mock_xhr as a proof of concept,
but left original test as a supplement to xhr_unittests.

BUG=471928

Review URL: https://codereview.chromium.org/1055313002

Cr-Commit-Position: refs/heads/master@{#324095}
parent 01041e84
...@@ -75,10 +75,12 @@ ...@@ -75,10 +75,12 @@
'webapp/crd/js/error_unittest.js', 'webapp/crd/js/error_unittest.js',
'webapp/crd/js/fallback_signal_strategy_unittest.js', 'webapp/crd/js/fallback_signal_strategy_unittest.js',
'webapp/crd/js/gcd_client_unittest.js', 'webapp/crd/js/gcd_client_unittest.js',
'webapp/crd/js/gcd_client_with_mock_xhr_unittest.js',
'webapp/crd/js/host_table_entry_unittest.js', 'webapp/crd/js/host_table_entry_unittest.js',
'webapp/crd/js/identity_unittest.js', 'webapp/crd/js/identity_unittest.js',
'webapp/crd/js/l10n_unittest.js', 'webapp/crd/js/l10n_unittest.js',
'webapp/crd/js/menu_button_unittest.js', 'webapp/crd/js/menu_button_unittest.js',
'webapp/crd/js/mock_xhr_unittest.js',
'webapp/crd/js/typecheck_unittest.js', 'webapp/crd/js/typecheck_unittest.js',
'webapp/crd/js/xhr_unittest.js', 'webapp/crd/js/xhr_unittest.js',
'webapp/crd/js/xmpp_connection_unittest.js', 'webapp/crd/js/xmpp_connection_unittest.js',
...@@ -92,6 +94,7 @@ ...@@ -92,6 +94,7 @@
'webapp/js_proto/chrome_proto.js', 'webapp/js_proto/chrome_proto.js',
'webapp/js_proto/chrome_mocks.js', 'webapp/js_proto/chrome_mocks.js',
'webapp/unittests/sinon_helpers.js', 'webapp/unittests/sinon_helpers.js',
'webapp/crd/js/mock_xhr.js',
], ],
# Prototypes for objects that are not mocked. # Prototypes for objects that are not mocked.
'remoting_webapp_unittests_js_proto_files': [ 'remoting_webapp_unittests_js_proto_files': [
......
...@@ -214,6 +214,28 @@ base.deepCopy = function(value) { ...@@ -214,6 +214,28 @@ base.deepCopy = function(value) {
return null; return null;
}; };
/**
* Returns a copy of the input object with all null/undefined fields
* removed. Returns an empty object for a null/undefined input.
*
* @param {Object<string,?T>|undefined} input
* @return {!Object<string,T>}
* @template T
*/
base.copyWithoutNullFields = function(input) {
/** @const {!Object} */
var result = {};
if (input) {
for (var field in input) {
var value = /** @type {*} */ (input[field]);
if (value != null) {
result[field] = value;
}
}
}
return result;
};
/** /**
* @type {boolean|undefined} * @type {boolean|undefined}
* @private * @private
......
...@@ -54,6 +54,53 @@ QUnit.test('deepCopy(obj) should copy primitive types recursively', ...@@ -54,6 +54,53 @@ QUnit.test('deepCopy(obj) should copy primitive types recursively',
assert.deepEqual(base.deepCopy([1, [2, [3]]]), [1, [2, [3]]]); assert.deepEqual(base.deepCopy([1, [2, [3]]]), [1, [2, [3]]]);
}); });
QUnit.test('copyWithoutNullFields returns a new object',
function(assert) {
var obj = {
a: 'foo',
b: 42
};
var copy = base.copyWithoutNullFields(obj);
assert.notEqual(obj, copy);
assert.deepEqual(obj, copy);
});
QUnit.test('copyWithoutNullFields removes null and undefined fields',
function(assert) {
/** @const */
var obj = {
a: 'foo',
b: 42,
zero: 0,
emptyString: '',
nullField: null,
undefinedField: undefined
};
var copy = base.copyWithoutNullFields(obj);
assert.equal(copy['a'], obj['a']);
assert.equal(copy['b'], obj['b']);
assert.equal(copy['zero'], 0);
assert.equal(copy['emptyString'], '');
assert.ok(!('nullField' in copy));
assert.ok(!('undefinedField' in copy));
});
QUnit.test('copyWithoutNullFields(null) returns a new empty bject',
function(assert) {
assert.deepEqual(
base.copyWithoutNullFields(null),
{});
assert.notEqual(
base.copyWithoutNullFields(null),
base.copyWithoutNullFields(null));
assert.deepEqual(
base.copyWithoutNullFields(undefined),
{});
assert.notEqual(
base.copyWithoutNullFields(undefined),
base.copyWithoutNullFields(undefined));
});
QUnit.test('modify the original after deepCopy(obj) should not affect the copy', QUnit.test('modify the original after deepCopy(obj) should not affect the copy',
function(assert) { function(assert) {
var original = [1, 2, 3, 4]; var original = [1, 2, 3, 4];
......
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
(function() {
'use strict';
/** @type {remoting.gcd.Client} */
var client;
/** @const */
var FAKE_REGISTRATION_TICKET = {
kind: 'clouddevices#registrationTicket',
id: 'fake_ticket_id',
robotAccountEmail: 'fake@robotaccounts.com',
robotAccountAuthorizationCode: 'fake_robot_auth_code',
deviceDraft: {
id: 'fake_device_id'
}
};
/** @const */
var FAKE_DEVICE = {
kind: 'clouddevices#device',
id: 'fake_device_id'
};
/** @const */
var FAKE_DEVICE_PATCH = {
fake_patch: true
};
/** @const */
var FAKE_DEVICE_LIST = {
kind: 'clouddevices#devicesListResponse',
devices: [FAKE_DEVICE]
};
QUnit.module('gcd_client with mock_xhr', {
beforeEach: function() {
remoting.MockXhr.activate();
remoting.identity = new remoting.Identity();
chromeMocks.activate(['identity']);
chromeMocks.identity.mock$setToken('fake_token');
client = new remoting.gcd.Client({
apiBaseUrl: 'https://fake.api',
apiKey: 'fake_key',
oauthClientId: 'fake_client_id'
});
},
afterEach: function() {
chromeMocks.restore();
remoting.identity = null;
remoting.MockXhr.restore();
}
});
QUnit.test('insertRegistrationTicket', function(assert) {
remoting.MockXhr.setResponseFor(
'POST', 'https://fake.api/registrationTickets',
function(/** remoting.MockXhr */ xhr) {
assert.equal(xhr.params.useIdentity, true);
assert.deepEqual(
xhr.params.jsonContent,
{ userEmail: 'me' });
xhr.setJsonResponse(200, FAKE_REGISTRATION_TICKET);
});
return client.insertRegistrationTicket().then(function(ticket) {
assert.deepEqual(ticket, FAKE_REGISTRATION_TICKET);
});
});
QUnit.test('patchRegistrationTicket', function(assert) {
remoting.MockXhr.setResponseFor(
'POST', 'https://fake.api/registrationTickets/fake_ticket_id',
function(/** remoting.MockXhr */ xhr) {
assert.equal(xhr.params.useIdentity, true);
assert.deepEqual(
xhr.params.jsonContent, {
deviceDraft: { 'fake_device_draft': true },
oauthClientId: 'fake_client_id'
});
xhr.setJsonResponse(200, FAKE_REGISTRATION_TICKET);
});
return client.patchRegistrationTicket('fake_ticket_id', {
'fake_device_draft': true
}).then(function(ticket) {
assert.deepEqual(ticket, FAKE_REGISTRATION_TICKET);
});
});
QUnit.test('finalizeRegistrationTicket', function(assert) {
remoting.MockXhr.setResponseFor(
'POST', 'https://fake.api/registrationTickets/fake_ticket_id/finalize',
function(/** remoting.MockXhr */ xhr) {
assert.equal(xhr.params.urlParams['key'], 'fake_key');
assert.equal(xhr.params.useIdentity, false);
xhr.setJsonResponse(200, FAKE_REGISTRATION_TICKET);
});
return client.finalizeRegistrationTicket('fake_ticket_id').
then(function(ticket) {
assert.deepEqual(ticket, FAKE_REGISTRATION_TICKET);
});
});
QUnit.test('listDevices', function(assert) {
remoting.MockXhr.setResponseFor(
'GET', 'https://fake.api/devices',
function(/** remoting.MockXhr */ xhr) {
assert.equal(xhr.params.useIdentity, true);
xhr.setJsonResponse(200, FAKE_DEVICE_LIST);
});
return client.listDevices().then(function(devices) {
assert.deepEqual(devices, [FAKE_DEVICE]);
});
});
QUnit.test('patchDevice', function(assert) {
remoting.MockXhr.setResponseFor(
'PATCH', 'https://fake.api/devices/fake_device_id',
function(/** remoting.MockXhr */ xhr) {
assert.equal(xhr.params.useIdentity, true);
assert.deepEqual(xhr.params.jsonContent, FAKE_DEVICE_PATCH);
xhr.setJsonResponse(200, FAKE_DEVICE);
});
return client.patchDevice('fake_device_id', FAKE_DEVICE_PATCH).
then(function(device) {
assert.deepEqual(device, FAKE_DEVICE);
});
});
})();
// Copyright (c) 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @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 === undefined ? undefined :
base.copyWithoutNullFields(params.formContent),
headers: base.copyWithoutNullFields(params.headers),
withCredentials: Boolean(params.withCredentials),
oauthToken: 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_);
}
};
/**
* The original value of the remoting.Xhr constructor. The JSDoc type
* is that of the remoting.Xhr constructor function.
* @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. The handler should generally call one of
* |set{Test,Json,Empty}Response|
*
* @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 handler
* 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
});
};
/**
* Installs a 204 (no content) response. See |setResponseFor| for
* more details on how the parameters work.
*
* @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);
};
/**
* Installs a 200 response with text content. See |setResponseFor|
* for more details on how the parameters work.
*
* @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);
};
/**
* Installs a 200 response with JSON content. See |setResponseFor|
* for more details on how the parameters work.
*
* @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;
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
(function() {
'use strict';
/** @type {boolean} */
var oldThrowOnAssert;
QUnit.module('mock_xhr', {
beforeEach: function() {
oldThrowOnAssert = base.debug.throwOnAssert;
base.debug.throwOnAssert = true;
remoting.MockXhr.activate();
},
afterEach: function() {
remoting.MockXhr.restore();
base.debug.throwOnAssert = oldThrowOnAssert;
}
});
QUnit.test('multiple calls to activate() fail', function(assert) {
assert.throws(function() {
remoting.MockXhr.activate();
});
});
QUnit.test('restore() without activate() fails', function(assert) {
remoting.MockXhr.restore();
assert.throws(function() {
remoting.MockXhr.restore();
});
remoting.MockXhr.activate();
});
/**
* @param {string=} opt_url The URL to request.
* @return {!Promise<!remoting.Xhr.Response>}
*/
function sendRequest(opt_url) {
return new remoting.Xhr({
method: 'GET',
url: opt_url || 'http://foo.com'
}).start();
};
/**
* @return {!Promise<!remoting.Xhr.Response>}
*/
function sendRequestForJson() {
return new remoting.Xhr({
method: 'GET',
url: 'http://foo.com',
acceptJson: true
}).start();
}
QUnit.test('unhandled requests fail', function(assert) {
assert.throws(sendRequest);
});
QUnit.test('setEmptyResponseFor', function(assert) {
remoting.MockXhr.setEmptyResponseFor('GET', 'http://foo.com');
var promise = sendRequest();
assert.throws(sendRequest);
return promise.then(function(/** remoting.Xhr.Response */ result) {
assert.equal(result.status, 204);
assert.equal(result.getText(), '');
assert.throws(result.getJson.bind(result));
});
});
QUnit.test('setEmptyResponseFor with repeat', function(assert) {
remoting.MockXhr.setEmptyResponseFor('GET', 'http://foo.com', true);
var promise1 = sendRequest();
var promise2 = sendRequest();
return promise1.then(function(/** remoting.Xhr.Response */ result) {
assert.equal(result.status, 204);
assert.equal(result.getText(), '');
assert.throws(result.getJson.bind(result));
return promise2;
}).then(function(/** remoting.Xhr.Response */ result) {
assert.equal(result.status, 204);
assert.equal(result.getText(), '');
assert.throws(result.getJson.bind(result));
});
});
QUnit.test('setEmptyResponseFor with RegExp', function(assert) {
remoting.MockXhr.setEmptyResponseFor('GET', /foo/);
var promise = sendRequest();
assert.throws(sendRequest);
return promise.then(function(/** remoting.Xhr.Response */ result) {
assert.equal(result.status, 204);
assert.equal(result.getText(), '');
assert.throws(result.getJson.bind(result));
});
});
QUnit.test('setTextResponseFor', function(assert) {
remoting.MockXhr.setTextResponseFor('GET', /foo/, 'first');
remoting.MockXhr.setTextResponseFor('GET', /foo/, 'second');
var promise1 = sendRequest();
var promise2 = sendRequest();
return promise1.then(function(/** remoting.Xhr.Response */ result) {
assert.equal(result.status, 200);
assert.equal(result.getText(), 'first');
assert.throws(result.getJson.bind(result));
return promise2;
}).then(function(/** remoting.Xhr.Response */ result) {
assert.equal(result.status, 200);
assert.equal(result.getText(), 'second');
assert.throws(result.getJson.bind(result));
});
});
QUnit.test('setTextResponseFor with different URLs', function(assert) {
remoting.MockXhr.setTextResponseFor('GET', /foo/, 'first');
remoting.MockXhr.setTextResponseFor('GET', /bar/, 'second');
var promise1 = sendRequest('http://bar.com');
var promise2 = sendRequest();
return promise1.then(function(/** remoting.Xhr.Response */ result) {
assert.equal(result.status, 200);
assert.equal(result.getText(), 'second');
assert.throws(result.getJson.bind(result));
return promise2;
}).then(function(/** remoting.Xhr.Response */ result) {
assert.equal(result.status, 200);
assert.equal(result.getText(), 'first');
assert.throws(result.getJson.bind(result));
});
});
QUnit.test('setTextResponseFor with default', function(assert) {
remoting.MockXhr.setTextResponseFor('GET', /foo/, 'specific');
remoting.MockXhr.setTextResponseFor('GET', null, 'default', true);
var promise1 = sendRequest('http://bar.com');
var promise2 = sendRequest();
var promise3 = sendRequest();
return promise1.then(function(/** remoting.Xhr.Response */ result) {
assert.equal(result.status, 200);
assert.equal(result.getText(), 'default');
assert.throws(result.getJson.bind(result));
return promise2;
}).then(function(/** remoting.Xhr.Response */ result) {
assert.equal(result.status, 200);
assert.equal(result.getText(), 'specific');
assert.throws(result.getJson.bind(result));
return promise3;
}).then(function(/** remoting.Xhr.Response */ result) {
assert.equal(result.status, 200);
assert.equal(result.getText(), 'default');
assert.throws(result.getJson.bind(result));
});
});
QUnit.test('setJsonResponseFor', function(assert) {
remoting.MockXhr.setJsonResponseFor('GET', 'http://foo.com', 'foo');
var promise = sendRequestForJson();
assert.throws(sendRequestForJson);
return promise.then(function(/** remoting.Xhr.Response */ result) {
assert.equal(result.status, 200);
assert.equal(JSON.parse(result.getText()), 'foo');
assert.equal(result.getJson(), 'foo');
});
});
})();
\ No newline at end of file
...@@ -5,6 +5,8 @@ ...@@ -5,6 +5,8 @@
/** /**
* @fileoverview * @fileoverview
* Utility class for making XHRs more pleasant. * Utility class for making XHRs more pleasant.
*
* Note: a mock version of this API exists in mock_xhr.js.
*/ */
'use strict'; 'use strict';
...@@ -26,7 +28,7 @@ remoting.Xhr = function(params) { ...@@ -26,7 +28,7 @@ remoting.Xhr = function(params) {
parameterString = params.urlParams; parameterString = params.urlParams;
} else if (typeof(params.urlParams) === 'object') { } else if (typeof(params.urlParams) === 'object') {
parameterString = remoting.Xhr.urlencodeParamHash( parameterString = remoting.Xhr.urlencodeParamHash(
remoting.Xhr.removeNullFields_(params.urlParams)); base.copyWithoutNullFields(params.urlParams));
} }
if (parameterString) { if (parameterString) {
url += '?' + parameterString; url += '?' + parameterString;
...@@ -34,7 +36,7 @@ remoting.Xhr = function(params) { ...@@ -34,7 +36,7 @@ remoting.Xhr = function(params) {
// Prepare the build modified headers. // Prepare the build modified headers.
/** @const */ /** @const */
this.headers_ = remoting.Xhr.removeNullFields_(params.headers); this.headers_ = base.copyWithoutNullFields(params.headers);
// Convert the content fields to a single text content variable. // Convert the content fields to a single text content variable.
/** @private {?string} */ /** @private {?string} */
...@@ -160,6 +162,7 @@ remoting.Xhr.prototype.start = function() { ...@@ -160,6 +162,7 @@ remoting.Xhr.prototype.start = function() {
/** /**
* @param {remoting.Xhr.Params} params * @param {remoting.Xhr.Params} params
* @throws {Error} if params are invalid * @throws {Error} if params are invalid
* @private
*/ */
remoting.Xhr.checkParams_ = function(params) { remoting.Xhr.checkParams_ = function(params) {
if (params.urlParams) { if (params.urlParams) {
...@@ -234,7 +237,8 @@ remoting.Xhr.prototype.maybeSetHeader_ = function(key, value) { ...@@ -234,7 +237,8 @@ remoting.Xhr.prototype.maybeSetHeader_ = function(key, value) {
/** @private */ /** @private */
remoting.Xhr.prototype.sendXhr_ = function() { remoting.Xhr.prototype.sendXhr_ = function() {
for (var key in this.headers_) { for (var key in this.headers_) {
this.nativeXhr_.setRequestHeader(key, this.headers_[key]); this.nativeXhr_.setRequestHeader(
key, /** @type {string} */ (this.headers_[key]));
} }
this.nativeXhr_.send(this.content_); this.nativeXhr_.send(this.content_);
this.content_ = null; // for gc this.content_ = null; // for gc
...@@ -247,7 +251,7 @@ remoting.Xhr.prototype.onReadyStateChange_ = function() { ...@@ -247,7 +251,7 @@ remoting.Xhr.prototype.onReadyStateChange_ = function() {
var xhr = this.nativeXhr_; var xhr = this.nativeXhr_;
if (xhr.readyState == 4) { if (xhr.readyState == 4) {
// See comments at remoting.Xhr.Response. // See comments at remoting.Xhr.Response.
this.deferred_.resolve(new remoting.Xhr.Response( this.deferred_.resolve(remoting.Xhr.Response.fromXhr_(
xhr, this.acceptJson_)); xhr, this.acceptJson_));
} }
}; };
...@@ -261,38 +265,56 @@ remoting.Xhr.prototype.onReadyStateChange_ = function() { ...@@ -261,38 +265,56 @@ remoting.Xhr.prototype.onReadyStateChange_ = function() {
* API. * API.
* *
* @constructor * @constructor
* @param {!XMLHttpRequest} xhr * @param {number} status
* @param {string} statusText
* @param {?string} url
* @param {string} text
* @param {boolean} allowJson * @param {boolean} allowJson
*/ */
remoting.Xhr.Response = function(xhr, allowJson) { remoting.Xhr.Response = function(
/** @private @const */ status, statusText, url, text, allowJson) {
this.allowJson_ = allowJson;
/** /**
* The HTTP status code. * The HTTP status code.
* @const {number} * @const {number}
*/ */
this.status = xhr.status; this.status = status;
/** /**
* The HTTP status description. * The HTTP status description.
* @const {string} * @const {string}
*/ */
this.statusText = xhr.statusText; this.statusText = statusText;
/** /**
* The response URL, if any. * The response URL, if any.
* @const {?string} * @const {?string}
*/ */
this.url = xhr.responseURL; this.url = url;
/** @private {string} */ /** @private {string} */
this.text_ = xhr.responseText || ''; this.text_ = text;
/** @private @const */
this.allowJson_ = allowJson;
/** @private {*|undefined} */ /** @private {*|undefined} */
this.json_ = undefined; this.json_ = undefined;
}; };
/**
* @param {!XMLHttpRequest} xhr
* @param {boolean} allowJson
* @return {!remoting.Xhr.Response}
*/
remoting.Xhr.Response.fromXhr_ = function(xhr, allowJson) {
return new remoting.Xhr.Response(
xhr.status,
xhr.statusText,
xhr.responseURL,
xhr.responseText || '',
allowJson);
};
/** /**
* @return {boolean} True if the response code is outside the 200-299 * @return {boolean} True if the response code is outside the 200-299
* range (i.e. success as defined by the HTTP protocol). * range (i.e. success as defined by the HTTP protocol).
...@@ -321,28 +343,6 @@ remoting.Xhr.Response.prototype.getJson = function() { ...@@ -321,28 +343,6 @@ remoting.Xhr.Response.prototype.getJson = function() {
return this.json_; return this.json_;
}; };
/**
* Returns a copy of the input object with all null or undefined
* fields removed.
*
* @param {Object<string,?string>|undefined} input
* @return {!Object<string,string>}
* @private
*/
remoting.Xhr.removeNullFields_ = function(input) {
/** @type {!Object<string,string>} */
var result = {};
if (input) {
for (var field in input) {
var value = input[field];
if (value != null) {
result[field] = value;
}
}
}
return result;
};
/** /**
* Takes an associative array of parameters and urlencodes it. * Takes an associative array of parameters and urlencodes it.
* *
...@@ -352,8 +352,11 @@ remoting.Xhr.removeNullFields_ = function(input) { ...@@ -352,8 +352,11 @@ remoting.Xhr.removeNullFields_ = function(input) {
remoting.Xhr.urlencodeParamHash = function(paramHash) { remoting.Xhr.urlencodeParamHash = function(paramHash) {
var paramArray = []; var paramArray = [];
for (var key in paramHash) { for (var key in paramHash) {
paramArray.push(encodeURIComponent(key) + var value = paramHash[key];
'=' + encodeURIComponent(paramHash[key])); if (value != null) {
paramArray.push(encodeURIComponent(key) +
'=' + encodeURIComponent(value));
}
} }
if (paramArray.length > 0) { if (paramArray.length > 0) {
return paramArray.join('&'); return paramArray.join('&');
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment