plastiq-throttle
Version:
Throttle calls to a function, by arguments, by time, and by promise completion.
556 lines (479 loc) • 15.4 kB
JavaScript
var chai = require('chai');
var expect = chai.expect;
var throttle = require('..');
var plastiq = require('plastiq');
var h = plastiq.html;
var retry = require('trytryagain');
describe('throttle', function () {
var called;
function expectToBeCalled(fn) {
var c = called;
fn();
expect(called, "expected to be called").to.equal(c + 1);
}
function expectNotToBeCalled(fn) {
var c = called;
fn();
expect(called, "expected not to be called").to.equal(c);
}
beforeEach(function () {
called = 0;
});
describe('synchronous actions', function () {
var calledRefresh = false;
beforeEach(function () {
calledRefresh = false;
});
context('without throttling', function () {
var value;
var t;
beforeEach(function () {
t = throttle({throttle: 0}, function (n) {
value = n;
called++;
});
h.refresh = function () {
calledRefresh = true;
};
});
afterEach(function () {
expect(calledRefresh).to.be.false;
});
it('runs the first time', function () {
expectToBeCalled(function () { t(1); });
expect(value).to.equal(1);
});
it("isn't run the second time", function () {
expectToBeCalled(function () { t(1); });
expectNotToBeCalled(function () { t(1); });
expect(value).to.equal(1);
});
it("is run the second time if the value is different", function () {
expectToBeCalled(function () { t(1); });
expectToBeCalled(function () { t(2); });
expect(value).to.equal(2);
});
it("is run the second time if the value is the same and it was reset", function () {
expectToBeCalled(function () { t(1); });
t.reset();
expectToBeCalled(function () { t(1); });
expect(value).to.equal(1);
});
it("is not run the third time if the value is the same as the second", function () {
expectToBeCalled(function () { t(1); });
expectToBeCalled(function () { t(2); });
expectNotToBeCalled(function () { t(2); });
expect(value).to.equal(2);
});
it("is run the third time if the value is same as first", function () {
expectToBeCalled(function () { t(1); });
expectToBeCalled(function () { t(2); });
expectToBeCalled(function () { t(1); });
expect(value).to.equal(1);
});
});
context('with throttling', function () {
var throttleDuration = 10;
beforeEach(function () {
calledRefresh = false;
called = 0;
t = throttle({throttle: throttleDuration}, function (n) {
value = n;
called++;
});
});
it('runs the first time', function () {
expectToBeCalled(function () { t(1); });
expect(value).to.equal(1);
});
it("doesn't run the second time if the value is the same", function () {
expectToBeCalled(function () { t(1); });
expectNotToBeCalled(function () { t(1); });
return wait(throttleDuration * 2).then(function () {
expect(called).to.equal(1);
expect(calledRefresh).to.be.false;
});
});
it('runs the second time if the value is different, but delayed', function () {
return new Promise(function (done) {
h.refresh = done;
expectToBeCalled(function () { t(1); });
expectNotToBeCalled(function () { t(2); });
}).then(function () {
expect(value).to.equal(2);
});
});
it("when throttled, doesn't run intermediate values, only the last", function () {
return new Promise(function (done) {
h.refresh = done;
expectToBeCalled(function () { t(1); });
expectNotToBeCalled(function () { t(2); });
expectNotToBeCalled(function () { t(3); });
expectNotToBeCalled(function () { t(4); });
expectNotToBeCalled(function () { t(5); });
}).then(function () {
expect(value).to.equal(5);
expect(called).to.equal(2);
});
});
});
});
describe('asynchronous actions', function () {
context('without throttle', function () {
var t;
var f;
var values;
beforeEach(function () {
values = [];
t = throttle({throttle: 0}, function (n) {
called++;
values.push('starting ' + n);
return wait(10).then(function () {
values.push('finishing ' + n);
});
});
f = throttle({throttle: 0}, function (n) {
called++;
values.push('starting ' + n);
return wait(10).then(function () {
values.push('failing ' + n);
throw new Error('failed ' + n);
});
});
});
it('calls refresh when promise is done', function () {
return new Promise(function (done) {
h.refresh = done;
expectToBeCalled(function () { t(1); });
}).then(function () {
expect(values).to.eql([
'starting 1',
'finishing 1'
]);
});
});
it('calls the second time if the first one has finished', function () {
return new Promise(function (done) {
h.refresh = done;
expectToBeCalled(function () { t(1); });
}).then(function () {
return new Promise(function (done) {
expect(values).to.eql([
'starting 1',
'finishing 1'
]);
h.refresh = done;
expectToBeCalled(function () { t(2); });
});
}).then(function () {
expect(values).to.eql([
'starting 1',
'finishing 1',
'starting 2',
'finishing 2'
]);
});
});
it('calls the second time if the first one has finished, even if it was rejected', function () {
return new Promise(function (done) {
expectToBeCalled(function () { f(1); });
retry(function () {
expect(values).to.eql([
'starting 1',
'failing 1'
]);
}).then(done);
}).then(function () {
return new Promise(function (done) {
expectToBeCalled(function () { f(2); });
done();
});
}).then(function () {
return retry(function () {
expect(values).to.eql([
'starting 1',
'failing 1',
'starting 2',
'failing 2'
]);
});
});
});
it('calls the second time after the first one has finished, even if it was rejected', function () {
return new Promise(function (done) {
expectToBeCalled(function () { f(1); });
done();
}).then(function () {
return new Promise(function (done) {
expectNotToBeCalled(function () { f(2); });
done();
});
}).then(function () {
return retry(function () {
expect(values).to.eql([
'starting 1',
'failing 1',
'starting 2',
'failing 2'
]);
});
});
});
it("doesn't call second time if value is the same", function () {
return new Promise(function (done) {
h.refresh = done;
expectToBeCalled(function () { t(1); });
expectNotToBeCalled(function () { t(1); });
}).then(function () {
return wait(20);
}).then(function () {
expect(values).to.eql([
'starting 1',
'finishing 1'
]);
});
});
it("calls the second time if the value is different, but not before first promise is done", function () {
return new Promise(function (done) {
h.refresh = done;
expectToBeCalled(function () { t(1); });
expectNotToBeCalled(function () { t(2); });
}).then(function () {
return wait(20);
}).then(function () {
expect(values).to.eql([
'starting 1',
'finishing 1',
'starting 2',
'finishing 2'
]);
});
});
it("doesn't call intermediate values if waiting for a promise to finish", function () {
return new Promise(function (done) {
h.refresh = done;
expectToBeCalled(function () { t(1); });
expectNotToBeCalled(function () { t(2); });
expectNotToBeCalled(function () { t(3); });
expectNotToBeCalled(function () { t(4); });
expectNotToBeCalled(function () { t(5); });
}).then(function () {
return wait(20);
}).then(function () {
expect(values).to.eql([
'starting 1',
'finishing 1',
'starting 5',
'finishing 5'
]);
});
});
});
context('with throttling', function () {
var throttleDuration = 20;
var values;
var promise;
var promiseDuration;
beforeEach(function () {
called = 0;
values = [];
promiseDuration = 0;
t = throttle({throttle: throttleDuration}, function (n) {
called++;
values.push('starting ' + n);
return promise = wait(promiseDuration).then(function () {
values.push('finishing ' + n);
});
});
});
it('runs the first time', function () {
return new Promise(function (done) {
h.refresh = done;
expectToBeCalled(function () { t(1); });
}).then(function () {
expect(values).to.eql([
'starting 1',
'finishing 1'
]);
});
});
it("skips calls if they are within the throttle, but always runs the last", function () {
h.refresh = function () {};
expectToBeCalled(function () { t(1); });
return wait(5).then(function () {
expect(values).to.eql([
'starting 1',
'finishing 1',
]);
expectNotToBeCalled(function () { t(2); });
}).then(function () {
return wait(1);
}).then(function () {
expect(values).to.eql([
'starting 1',
'finishing 1',
]);
expectNotToBeCalled(function () { t(3); });
}).then(function () {
return wait(1);
}).then(function () {
expect(values).to.eql([
'starting 1',
'finishing 1',
]);
expectNotToBeCalled(function () { t(4); });
}).then(function () {
return wait(1);
}).then(function () {
expect(values).to.eql([
'starting 1',
'finishing 1',
]);
expectNotToBeCalled(function () { t(5); });
}).then(function () {
return wait(throttleDuration * 2);
}).then(function () {
expect(values).to.eql([
'starting 1',
'finishing 1',
'starting 5',
'finishing 5'
]);
});
});
it('for throttled calls, calls refresh at start and end of promise call', function () {
var refreshValues = [];
return new Promise(function (done) {
var refreshed = 0;
h.refresh = function () {
refreshed++;
refreshValues.push(values.slice());
if (refreshed >= 3) {
done();
}
};
t(1);
t(2);
}).then(function () {
expect(refreshValues).to.eql([
['starting 1', 'finishing 1'],
['starting 1', 'finishing 1', 'starting 2'],
['starting 1', 'finishing 1', 'starting 2', 'finishing 2']
]);
});
});
context('when the promise takes longer than the throttle', function () {
beforeEach(function () {
promiseDuration = 40;
});
it('runs each promise one after the other', function () {
return new Promise(function (done) {
h.refresh = done;
expectToBeCalled(function () { t(1); });
}).then(function () {
return promise;
}).then(function () {
return wait(1);
}).then(function () {
expect(values).to.eql([
'starting 1',
'finishing 1'
]);
expectToBeCalled(function () { t(2); });
}).then(function () {
return promise;
}).then(function () {
return wait(1);
}).then(function () {
expect(values).to.eql([
'starting 1',
'finishing 1',
'starting 2',
'finishing 2'
]);
expectToBeCalled(function () { t(3); });
}).then(function () {
return promise;
}).then(function () {
return wait(1);
}).then(function () {
expect(values).to.eql([
'starting 1',
'finishing 1',
'starting 2',
'finishing 2',
'starting 3',
'finishing 3',
]);
});
});
});
});
});
describe('equality', function () {
function expectDifferent(a, b) {
var called = 0;
var t = throttle({throttle: 0}, function () {
called++;
});
t.apply(undefined, a);
t.apply(undefined, b);
expect(called, 'expected ' + a + ' and ' + b + ' to be considered different').to.equal(2);
}
function expectSame(a, b) {
var called = 0;
var t = throttle({throttle: 0}, function () {
called++;
});
t.apply(undefined, a);
t.apply(undefined, b);
expect(called, 'expected ' + a + ' and ' + b + ' to be considered the same').to.equal(1);
}
it('numbers', function () {
expectDifferent([1], [2]);
expectSame([1], [1]);
});
it('strings', function () {
expectDifferent(["a"], ["b"]);
expectSame(["a"], ["a"]);
});
it('strings are different to numbers', function () {
expectDifferent(["1"], [1]);
});
it('booleans', function () {
expectDifferent([true], [false]);
expectSame([true], [true]);
});
it('dates', function () {
expectDifferent([new Date(2013, 1, 2)], [new Date(2013, 1, 3)]);
var d = new Date(2013, 1, 2);
expectSame([d], [d]);
});
it('objects', function () {
expectDifferent([{value: '1'}], [{value: '1'}]);
var obj = {value: '1'}
expectSame([obj], [obj]);
});
it('arrays', function () {
expectDifferent([[1]], [[1]]);
var a = [1];
expectSame([a], [a]);
});
it('multiple values', function () {
expectDifferent([1, 2], [1]);
expectDifferent([1, 2], [1, 3]);
expectSame([1, 2], [1, 2]);
});
it('no arguments are considered different', function () {
expectDifferent([], []);
});
it('undefined', function () {
expectSame([undefined], [undefined]);
});
});
});
function wait(n) {
return new Promise(function (done) {
setTimeout(done, n);
});
}