mesosphere-shared-reactjs
Advanced tools
Comparing version 0.0.13 to 0.0.14
{ | ||
"name": "mesosphere-shared-reactjs", | ||
"version": "0.0.13", | ||
"version": "0.0.14", | ||
"description": "Shared code from mesosphere web projects", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -9,2 +9,113 @@ jest.dontMock("../RequestUtil"); | ||
describe('#debounceOnError', function () { | ||
var successFn; | ||
var errorFn; | ||
beforeEach(function () { | ||
successFn = jest.genMockFunction(); | ||
errorFn = jest.genMockFunction(); | ||
spyOn(RequestUtil, 'json').andCallFake( | ||
function (options) { | ||
// Trigger error for url 'failRequest' | ||
if (/failRequest/.test(options.url)) { | ||
options.error(); | ||
} | ||
// Trigger success for url 'successRequest' | ||
if (/successRequest/.test(options.url)) { | ||
options.success(); | ||
} | ||
return {}; | ||
} | ||
); | ||
this.request = RequestUtil.debounceOnError( | ||
10, | ||
function (resolve, reject) { | ||
return function (url) { | ||
return RequestUtil.json({ | ||
url: url, | ||
success: function () { | ||
successFn(); | ||
resolve(); | ||
}, | ||
error: function () { | ||
errorFn(); | ||
reject(); | ||
} | ||
}); | ||
}; | ||
}, | ||
{delayAfterCount: 5} | ||
); | ||
}); | ||
it('should not debounce on the first 5 errors', function () { | ||
this.request('failRequest'); | ||
this.request('failRequest'); | ||
this.request('failRequest'); | ||
this.request('failRequest'); | ||
this.request('failRequest'); | ||
expect(errorFn.mock.calls.length).toEqual(5); | ||
}); | ||
it('should debounce on more than 5 errors', function () { | ||
// These will all be called | ||
this.request('failRequest'); | ||
this.request('failRequest'); | ||
this.request('failRequest'); | ||
this.request('failRequest'); | ||
this.request('failRequest'); | ||
// These will all be debounced | ||
this.request('failRequest'); | ||
this.request('failRequest'); | ||
this.request('failRequest'); | ||
expect(errorFn.mock.calls.length).toEqual(5); | ||
}); | ||
it('should reset debouncing after success call', function () { | ||
// These will all be called | ||
this.request('failRequest'); | ||
this.request('failRequest'); | ||
this.request('failRequest'); | ||
this.request('successRequest'); | ||
this.request('failRequest'); | ||
this.request('failRequest'); | ||
this.request('failRequest'); | ||
this.request('failRequest'); | ||
this.request('failRequest'); | ||
// This will be debounced | ||
this.request('failRequest'); | ||
this.request('failRequest'); | ||
expect(errorFn.mock.calls.length).toEqual(8); | ||
expect(successFn.mock.calls.length).toEqual(1); | ||
}); | ||
it('should return the result of the function', function () { | ||
var result = this.request('successRequest'); | ||
expect(typeof result).toEqual('object'); | ||
}); | ||
}); | ||
describe('#getErrorFromXHR', function () { | ||
it('should return the description property', function () { | ||
var json = {responseJSON: {description: 'bar'}}; | ||
expect(RequestUtil.getErrorFromXHR(json)).toEqual('bar'); | ||
}); | ||
it('should return the default error message when there is no description', | ||
function () { | ||
var json = {responseJSON: {foo: 'bar'}}; | ||
expect(RequestUtil.getErrorFromXHR(json)) | ||
.toEqual('An error has occurred.'); | ||
}); | ||
}); | ||
describe("#json", function () { | ||
@@ -27,5 +138,5 @@ | ||
it("Should use defaults for a GET json request", function () { | ||
RequestUtil.json({url: "lol"}); | ||
RequestUtil.json({url: "foo?bar"}); | ||
expect(Reqwest.reqwest).toHaveBeenCalled(); | ||
expect(Reqwest.reqwest.mostRecentCall.args[0].url).toEqual("lol"); | ||
expect(Reqwest.reqwest.mostRecentCall.args[0].url).toEqual("foo?bar"); | ||
expect(Reqwest.reqwest.mostRecentCall.args[0].contentType).toEqual("application/json; charset=utf-8"); | ||
@@ -83,5 +194,17 @@ expect(Reqwest.reqwest.mostRecentCall.args[0].type).toEqual("json"); | ||
it("appends a timestamp to the end of the url", function () { | ||
RequestUtil.json({url: "foo"}); | ||
expect(Reqwest.reqwest.calls[0].args[0].url).toMatch(/^foo\?_timestamp=[0-9]*$/); | ||
}); | ||
it("does not append a timestamp to the end of the url if it already has a query param", | ||
function () { | ||
RequestUtil.json({url: "foo?bar"}); | ||
expect(Reqwest.reqwest.calls[0].args[0].url).toEqual("foo?bar"); | ||
}); | ||
}); | ||
describe("#parseResponseBody", function () { | ||
it("should parse the object with responseText correctly", function () { | ||
@@ -112,3 +235,5 @@ var originalObject = {name: "Kenny"}; | ||
); | ||
}); | ||
}); |
var Reqwest = require("./Reqwest"); | ||
var Util = require("./Util"); | ||
var DEFAULT_ERROR_MESSAGE = "An error has occurred."; | ||
var activeRequests = {}; | ||
@@ -26,4 +28,53 @@ | ||
var RequestUtil = { | ||
debounceOnError: function (interval, promiseFn, options) { | ||
var rejectionCount = 0; | ||
var timeUntilNextCall = 0; | ||
var currentInterval = interval; | ||
options = options || {}; | ||
if (typeof options.delayAfterCount !== "number") { | ||
options.delayAfterCount = 0; | ||
} | ||
function resolveFn() { | ||
rejectionCount = 0; | ||
timeUntilNextCall = 0; | ||
currentInterval = interval; | ||
} | ||
function rejectFn() { | ||
rejectionCount++; | ||
// Only delay if after delayAfterCount requests have failed | ||
if (rejectionCount >= options.delayAfterCount) { | ||
// Exponentially increase the time till the next call | ||
currentInterval *= 2; | ||
timeUntilNextCall = Date.now() + currentInterval; | ||
} | ||
} | ||
var callback = promiseFn(resolveFn, rejectFn); | ||
return function () { | ||
if (Date.now() < timeUntilNextCall) { | ||
return; | ||
} | ||
/* eslint-disable consistent-return */ | ||
return callback.apply(options.context, arguments); | ||
/* eslint-enable consistent-return */ | ||
}; | ||
}, | ||
getErrorFromXHR: function (xhr) { | ||
var response = this.parseResponseBody(xhr); | ||
return response.description || response.message || DEFAULT_ERROR_MESSAGE; | ||
}, | ||
json: function (options) { | ||
if (options) { | ||
if (options.url && !/\?/.test(options.url)) { | ||
options.url += "?_timestamp=" + Date.now(); | ||
} | ||
if (Util.isFunction(options.hangingRequestCallback)) { | ||
@@ -80,6 +131,71 @@ var requestID = JSON.stringify(options); | ||
if (responseText) { | ||
return JSON.parse(responseText); | ||
try { | ||
return JSON.parse(responseText); | ||
} catch (e) { | ||
return { | ||
description: responseText | ||
}; | ||
} | ||
} | ||
return {}; | ||
}, | ||
/** | ||
* Allows overriding the response of an ajax request as well | ||
* as the success or failure of a request. | ||
* | ||
* @param {Object} actionsHash The Actions file configuration | ||
* @param {String} actionsHashName The actual name of the actions file | ||
* @param {String} methodName The name of the method to be stubbed | ||
* @return {Function} A function | ||
*/ | ||
stubRequest: function (actionsHash, actionsHashName, methodName) { | ||
if (!global.actionTypes) { | ||
global.actionTypes = {}; | ||
} | ||
// Cache the method we're stubbing | ||
var originalFunction = actionsHash[methodName]; | ||
function closure() { | ||
// Store original RequestUtil.json | ||
var requestUtilJSON = RequestUtil.json; | ||
// `configuration` will contain the config that | ||
// is passed to RequestUtil.json | ||
var configuration = null; | ||
// The configuration for the given method, this should be set externally | ||
var methodConfig = global.actionTypes[actionsHashName][methodName]; | ||
// Proxy calls to RequestUtil.json | ||
RequestUtil.json = function (object) { | ||
configuration = object; | ||
}; | ||
// The event type that we'll call success/error for the ajax request | ||
var eventType = methodConfig.event; | ||
// This will cause `configuration` to be set | ||
originalFunction.apply(actionsHash, arguments); | ||
// Restore RequestUtil.json | ||
RequestUtil.json = requestUtilJSON; | ||
var response = {}; | ||
if (methodConfig[eventType] && methodConfig[eventType].response) { | ||
response = methodConfig[eventType].response; | ||
} else if (eventType === "error") { | ||
response = {responseJSON: {error: "Some generic error"}}; | ||
} | ||
configuration[eventType](response); | ||
} | ||
// Return a function so that we can use setTimeout in the end. | ||
return function () { | ||
var args = arguments; | ||
setTimeout(function () { | ||
closure.apply(null, args); | ||
}, global.actionTypes.requestTimeout || 500); | ||
}; | ||
} | ||
@@ -86,0 +202,0 @@ }; |
52716
1101