halley
Version:
A bayeux client for modern browsers and node. Forked from Faye
626 lines (525 loc) • 16.5 kB
JavaScript
'use strict';
var promiseUtil = require('../lib/util/promise-util');
var assert = require('assert');
var Promise = require('bluebird');
describe('promise-util', function() {
describe('Synchronized', function() {
beforeEach(function() {
this.sync = new promiseUtil.Synchronized();
});
it('should synchronize access with a single item', function() {
return this.sync.sync('1', function() {
return 'a';
})
.bind(this)
.then(function(result) {
assert.deepEqual(this.sync._keys, {});
assert.strictEqual(result, 'a');
});
});
it('should propogate rejections', function() {
return this.sync.sync('1', function() {
throw new Error('Crash');
})
.bind(this)
.then(function() {
assert.ok('Expected failure');
}, function(err) {
assert.strictEqual(err.message, 'Crash');
});
});
it('should propogate on queued items', function() {
this.sync.sync('1', function() { return Promise.delay(1).return('a'); });
return this.sync.sync('1', function() {
return Promise.reject(new Error('Queued error'));
})
.bind(this)
.then(function() {
assert.ok(false, 'Expected exception');
}, function(err) {
assert.strictEqual(err.message, 'Queued error');
})
.then(function() {
assert.deepEqual(this.sync._keys, {});
});
});
it('should synchronize access with multiple items', function() {
var count = 0;
return Promise.all([
this.sync.sync('1', function() { assert.strictEqual(count++, 0); return Promise.delay(2).return('a'); }),
this.sync.sync('1', function() { assert.strictEqual(count++, 1); return 'b'; })
])
.bind(this)
.then(function(result) {
assert.strictEqual(count, 2);
assert.deepEqual(this.sync._keys, {});
assert.deepEqual(result, ['a', 'b']);
});
});
it('upstream rejections should be isolated', function() {
var count = 0;
this.sync.sync('1', function() {
return Promise.reject(new Error('Random'));
}).catch(function(err) {
assert(err.message, 'Random');
count++;
});
return this.sync.sync('1', function() { return 'b'; })
.bind(this)
.then(function(result) {
assert.strictEqual(count, 1);
assert.deepEqual(this.sync._keys, {});
assert.deepEqual(result, 'b');
});
});
it('cancellation should work', function() {
var count = 0;
var p = this.sync.sync('1', function() {
return new Promise(function(resolve, reject, onCancel) {
Promise.delay(1).then(resolve);
onCancel(function() {
count++;
});
});
});
p.cancel();
return Promise.delay(2)
.then(function() {
assert.strictEqual(count, 1);
});
});
it('upstream cancellations should be isolated', function() {
var p1 = this.sync.sync('1', function() { return Promise.delay(3).return('a'); });
var p2 = this.sync.sync('1', function() { return 'b'; });
return Promise.delay(1)
.bind(this)
.then(function() {
p1.cancel();
return p2;
})
.then(function(result) {
assert.deepEqual(result, 'b');
assert.deepEqual(this.sync._keys, {});
});
});
});
describe('cancelBarrier', function() {
it('should propogate resolve', function() {
return promiseUtil.cancelBarrier(Promise.resolve('a'))
.then(function(result) {
assert.strictEqual(result, 'a');
});
});
it('should propogate reject', function() {
var e = new Error();
return promiseUtil.cancelBarrier(Promise.reject(e))
.then(function() {
assert.ok(false);
}, function(err) {
assert.strictEqual(err, e);
});
});
it('should prevent cancellations from propogating past the barrier', function() {
var count = 0;
var resolve;
var p1 = new Promise(function(res, rej, onCancel) {
resolve = res;
onCancel(function() {
count++;
});
});
var p2 = promiseUtil.cancelBarrier(p1)
.then(function(x) {
return x;
});
p2.cancel();
resolve('a');
return p1
.then(function(result) {
assert.strictEqual(result, 'a');
});
});
});
describe('after', function() {
it('should execute after resolve', function() {
return promiseUtil.after(Promise.resolve('a'));
});
it('should execute after reject', function() {
var e = new Error();
var p = Promise.delay(1).throw(e);
p.catch(function() {
// Dangling catch to prevent bluebird warnings
});
return promiseUtil.after(p);
});
it('should propogate when the source promise is cancelled', function() {
var count = 0;
var resolve;
var p1 = new Promise(function(res, rej, onCancel) {
resolve = res;
onCancel(function() {
count++;
});
});
var p2 = promiseUtil.after(p1)
.then(function() {
assert.strictEqual(count, 1);
});
p1.cancel();
return p2;
});
it('should execute in sequence', function() {
var count = 0;
var p1 = Promise.resolve('a');
var p2 = promiseUtil.after(p1).then(function() { assert.strictEqual(count, 0); count++; });
var p3 = promiseUtil.after(p2).then(function() { assert.strictEqual(count, 1); count++; });
var p4 = promiseUtil.after(p3).then(function() { assert.strictEqual(count, 2); count++; });
return p4.then(function() {
assert.strictEqual(count, 3);
});
});
});
describe('LazySingleton', function() {
beforeEach(function() {
this.count = 0;
this.lazySingleton = new promiseUtil.LazySingleton(function() {
this.count++;
return this.singletonValue;
}.bind(this));
});
it('should return a value', function() {
this.singletonValue = Promise.resolve('a');
return this.lazySingleton.get()
.bind(this)
.then(function(a) {
assert.strictEqual(this.count, 1);
assert.strictEqual(a, 'a');
});
});
it('should cache the results', function() {
this.singletonValue = Promise.resolve('a');
return this.lazySingleton.get()
.bind(this)
.then(function(a) {
assert.strictEqual(this.count, 1);
assert.strictEqual(a, 'a');
return this.lazySingleton.get();
})
.then(function(a) {
assert.strictEqual(this.count, 1);
assert.strictEqual(a, 'a');
});
});
it('should not make multiple calls', function() {
this.singletonValue = Promise.delay(1).return('a');
return Promise.all([
this.lazySingleton.get(),
this.lazySingleton.get()
])
.bind(this)
.then(function(a) {
assert.strictEqual(this.count, 1);
assert.deepEqual(a, ['a', 'a']);
});
});
it('should handle cancellations', function() {
this.singletonValue = Promise.delay(10).return('a');
return Promise.delay(0)
.bind(this)
.then(function() {
assert(this.singletonValue.isPending());
this.singletonValue.cancel();
return promiseUtil.after(this.lazySingleton.get());
})
.then(function() {
assert.strictEqual(this.count, 1);
this.singletonValue = Promise.delay(1).return('a');
return this.lazySingleton.get();
})
.then(function(a) {
assert.strictEqual(this.count, 2);
assert.strictEqual(a, 'a');
});
});
});
describe('Throttle', function() {
beforeEach(function() {
this.count = 0;
this.throwError = false;
this.throttle = new promiseUtil.Throttle(function() {
this.count++;
if (this.throwError) throw new Error('Fail');
}.bind(this), 10);
this.slowThrottle = new promiseUtil.Throttle(function() {
this.count++;
}.bind(this), 1000000);
});
it('should throttle calls', function() {
return Promise.all([
this.throttle.fire(),
this.throttle.fire(),
this.throttle.fire()
])
.bind(this)
.then(function() {
assert.strictEqual(this.count, 1);
});
});
it('should respect fireImmediate', function() {
return Promise.all([
this.throttle.fire(),
this.throttle.fire(true),
Promise.delay(10).bind(this).then(function() {
return this.throttle.fire();
})
])
.bind(this)
.then(function() {
assert.strictEqual(this.count, 2);
});
});
it('should not wait if fireImmediate is called on the first call', function() {
return this.slowThrottle.fire(true)
.bind(this)
.then(function() {
assert.strictEqual(this.count, 1);
});
});
it('should reject on destroy', function() {
var p = this.slowThrottle.fire();
var e = new Error();
this.slowThrottle.destroy(e);
return p.then(function() {
assert.ok(false);
}, function(err) {
assert.strictEqual(err, e);
});
});
it('should handle cancellations', function() {
var p = this.throttle.fire();
return Promise.delay(1)
.bind(this)
.then(function() {
assert(p.isCancellable());
p.cancel();
return Promise.delay(15);
})
.then(function() {
assert.strictEqual(this.count, 0);
});
});
it('should isolate cancels from one another', function() {
var p = this.throttle.fire();
var p2 = this.throttle.fire();
return Promise.delay(1)
.bind(this)
.then(function() {
assert(p.isCancellable());
p.cancel();
return p2;
})
.then(function() {
assert.strictEqual(this.count, 1);
});
});
it('should cancel the trigger when all fires are cancelled', function() {
var p = this.throttle.fire();
var p2 = this.throttle.fire();
return Promise.delay(1)
.bind(this)
.then(function() {
assert(p.isCancellable());
assert(p2.isCancellable());
p.cancel();
p2.cancel();
return Promise.delay(15);
})
.then(function() {
assert.strictEqual(this.count, 0);
});
});
it('should handle rejections', function() {
this.throwError = true;
return this.throttle.fire()
.bind(this)
.then(function() {
assert.ok(false, 'Expected error');
}, function(err) {
assert.strictEqual(err.message, 'Fail');
});
});
});
describe('Batcher', function() {
beforeEach(function() {
this.count = 0;
this.batcher = new promiseUtil.Batcher(function(items) {
this.count++;
this.items = items;
return 'Hello';
}.bind(this), 10);
});
it('should call on add', function() {
return this.batcher.add(10)
.then(function(value) {
assert.strictEqual(value, 'Hello');
});
});
it('should call on add with multiple items', function() {
return Promise.all([
this.batcher.add(1),
this.batcher.add(2),
])
.spread(function(a,b) {
assert.strictEqual(a, 'Hello');
assert.strictEqual(b, 'Hello');
});
});
it('should return a value', function() {
var p1 = this.batcher.add(1);
var p2 = this.batcher.add(2);
var p3 = this.batcher.add(3);
assert(p1.isCancellable());
p1.cancel();
return this.batcher.next()
.bind(this)
.then(function() {
assert(p1.isCancelled());
assert.strictEqual(this.count, 1);
assert.deepEqual(this.items, [2, 3]);
return Promise.all([p2, p3]);
})
.spread(function(a,b) {
assert.strictEqual(a, 'Hello');
assert.strictEqual(b, 'Hello');
});
});
it('should not batch if all the items are cancelled', function() {
var p1 = this.batcher.add(1);
var p2 = this.batcher.add(2);
p1.cancel();
p2.cancel();
return Promise.delay(15)
.bind(this)
.then(function() {
assert.strictEqual(this.count, 0);
});
});
});
describe('Sequencer', function() {
beforeEach(function() {
this.inPlay = 0;
this.count = 0;
this.sequencer = new promiseUtil.Sequencer();
this.fn = function() {
this.inPlay++;
this.count++;
var count = this.count;
assert.strictEqual(this.inPlay, 1);
return Promise.delay(1)
.bind(this)
.then(function() {
this.inPlay--;
assert.strictEqual(this.inPlay, 0);
return count;
});
}.bind(this);
this.fnReject = function() {
this.count++;
return Promise.delay(1)
.bind(this)
.then(function() {
throw new Error('Fail');
});
}.bind(this);
this.fnWillBeCancelled = function() {
this.count++;
var promise = new Promise(function() {});
Promise.delay(1)
.then(function() {
promise.cancel();
});
return promise;
}.bind(this);
});
it('should sequence multiple calls', function() {
return Promise.all([
this.sequencer.chain(this.fn),
this.sequencer.chain(this.fn)
])
.bind(this)
.spread(function(a, b) {
assert.strictEqual(a, 1);
assert.strictEqual(b, 2);
assert.strictEqual(this.count, 2);
});
});
it('should handle rejections', function() {
var promises = [this.sequencer.chain(this.fnReject), this.sequencer.chain(this.fn)];
return Promise.all(promises.map(function(promise) {
return promise.reflect();
}))
.bind(this)
.spread(function(a, b) {
assert(a.isRejected());
assert.strictEqual(a.reason().message, 'Fail');
assert(b.isFulfilled());
assert.strictEqual(b.value(), 2);
assert.strictEqual(this.count, 2);
return a;
});
});
it('should handle upstream cancellations', function() {
var p1 = this.sequencer.chain(this.fnWillBeCancelled);
var p2 = this.sequencer.chain(this.fn);
return p2.bind(this)
.then(function(value) {
assert(p1.isCancelled());
assert(p2.isFulfilled());
assert.strictEqual(value, 2);
assert.strictEqual(this.count, 2);
});
});
it('should handle downstream cancellations', function() {
var count = 0;
var p1 = this.sequencer.chain(function() {
return Promise.delay(1).then(function() {
count++;
assert.ok(false);
});
});
var p2 = this.sequencer.chain(function() {
assert.strictEqual(count, 0);
});
p1.cancel();
return p2;
});
it('should handle the queue being cleared', function() {
var count = 0;
var p1 = this.sequencer.chain(function() {
return Promise.delay(1).then(function() {
count++;
return "a";
});
});
var p2 = this.sequencer.chain(function() {
return Promise.delay(1).then(function() {
count++;
});
});
p2.catch(function() {}); // Stop warnings
var err = new Error('Queue cleared');
return this.sequencer.clear(err)
.then(function() {
assert.strictEqual(count, 1);
assert(p1.isFulfilled());
assert.strictEqual(p1.value(), "a");
return p2.reflect();
})
.then(function(r) {
assert.strictEqual(count, 1);
assert(r.isRejected());
assert.strictEqual(r.reason(), err);
});
});
});
});