You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

392 lines
16 KiB

'use strict';
var chai = require('chai');
var _ = require('lodash');
var q = require('q');
var zeit = require('../');
var assert = chai.assert;
chai.use(require('chai-timers'));
assert.momentEql = function (expected, actual, msg) {
assert.equal(expected.toString(), actual.toString(), msg);
};
var ZERO_MILLIS = 0;
var REPEAT_OF_ONE_SECOND = 1000;
var AFTER_INTERVAL = 1234;
function EventCapture(emitter) {
var events = {};
return {
listenTo: function () {
_.each(arguments, function (name) {
events[name] = [];
emitter.on(name, function (event) {
events[name].push(event);
});
});
return this;
},
captured: events
};
}
function PromiseToExecute(actualFn) {
var invocationsCounter = [];
var f = function () {
invocationsCounter.push(0);
return actualFn();
};
f.invocations = function () {
return invocationsCounter.length;
};
return f;
}
function setUpTest(ClockCtr, returnValueFn) {
return function () {
var clock = new ClockCtr();
var scheduler = new zeit.Scheduler(clock);
var promise = new PromiseToExecute(returnValueFn);
var events = new EventCapture(scheduler).listenTo('start', 'finish', 'error');
return {
startTime: clock.now(),
scheduler: scheduler,
clock: clock,
executionCountIsLessThan: function (expected) {
return function () {
return promise.invocations() < expected;
}
},
executionCountIs: function (expected) {
return function () {
return promise.invocations() === expected;
}
},
startSchedule: function (modify) {
return modify(scheduler.execute(promise).named('my function'), clock).start();
},
scheduleFor: function (scheduleId) {
return scheduler.activeSchedule(scheduleId);
},
clockTimeoutFor: function (scheduleId) {
return clock.timeouts()[this.scheduleFor(scheduleId).clockId];
},
assertInvocationCount: function (expected) {
assert.equal(promise.invocations(), expected);
},
assertEventCounts: function (started, finished, errors) {
assert.deepEqual(events.captured['start'].length, started);
assert.deepEqual(events.captured['finish'].length, finished);
assert.deepEqual(events.captured['error'].length, errors);
},
triggerAllClockSchedulesAndAssertExecuted: function (expectedScheduleIds) {
var expectedClockIds = _.map(expectedScheduleIds, function (scheduleId) {
return scheduler.activeSchedule(scheduleId).clockId;
});
assert.deepEqual(clock.triggerAll(), expectedClockIds);
},
assertThereIsNoActiveScheduleFor: function (scheduleId) {
assert.equal(this.scheduleFor(scheduleId), undefined);
}
};
};
}
function describeRepetitionScenariosFor(name, expectedRepeatInterval, testFn, scheduleBuilderFn) {
describe(name, function () {
describe('', function () {
var t = testFn();
var scheduleId = t.startSchedule(scheduleBuilderFn);
it('has a 0ms initial delay', function () {
assert.deepEqual(t.clockTimeoutFor(scheduleId), t.clock.numberOfMillisecondsAsDuration(0));
assert.momentEql(t.scheduleFor(scheduleId).nextTriggerTime, t.clock.lastKnownTime());
});
});
describe('combined with after()', function () {
var t = testFn();
var startDelay = t.clock.numberOfMillisecondsAsDuration(AFTER_INTERVAL);
var scheduleId = t.startSchedule(function (s, clock) {
return scheduleBuilderFn(s, clock).after(startDelay);
});
it('initially is scheduled with the defined delay', function () {
assert.deepEqual(t.clockTimeoutFor(scheduleId), startDelay);
assert.momentEql(t.scheduleFor(scheduleId).nextTriggerTime, t.clock.timeIn(startDelay));
});
it('triggers when executed', function () {
t.triggerAllClockSchedulesAndAssertExecuted([scheduleId]);
t.assertInvocationCount(1);
});
it('is rescheduled at the defined interval', function () {
assert.deepEqual(t.clockTimeoutFor(scheduleId), t.clock.numberOfMillisecondsAsDuration(expectedRepeatInterval));
assert.momentEql(t.scheduleFor(scheduleId).nextTriggerTime, t.clock.timeIn(t.clock.numberOfMillisecondsAsDuration(expectedRepeatInterval)));
});
});
describe('combined with at()', function () {
var t = testFn();
var startDelay = t.clock.numberOfMillisecondsAsDuration(AFTER_INTERVAL);
var scheduleId = t.startSchedule(function (s, clock) {
return scheduleBuilderFn(s, clock).at(clock.timeIn(startDelay));
});
it('initially is scheduled with the defined delay', function () {
assert.deepEqual(t.clockTimeoutFor(scheduleId), startDelay);
assert.momentEql(t.scheduleFor(scheduleId).nextTriggerTime, t.clock.timeIn(startDelay));
});
it('triggers when executed', function () {
t.triggerAllClockSchedulesAndAssertExecuted([scheduleId]);
t.assertInvocationCount(1);
});
it('is rescheduled at the defined interval', function () {
assert.deepEqual(t.clockTimeoutFor(scheduleId), t.clock.numberOfMillisecondsAsDuration(expectedRepeatInterval));
assert.momentEql(t.scheduleFor(scheduleId).nextTriggerTime, t.clock.timeIn(t.clock.numberOfMillisecondsAsDuration(expectedRepeatInterval)));
});
});
describe('combined with whilst()', function () {
var t = testFn();
var scheduleId = t.startSchedule(function (s, clock) {
return scheduleBuilderFn(s, clock).whilst(t.executionCountIsLessThan(2));
});
it('triggers when executed', function () {
t.triggerAllClockSchedulesAndAssertExecuted([scheduleId]);
t.assertInvocationCount(1);
});
it('is rescheduled at the defined interval', function () {
assert.deepEqual(t.clockTimeoutFor(scheduleId), t.clock.numberOfMillisecondsAsDuration(expectedRepeatInterval));
assert.momentEql(t.scheduleFor(scheduleId).nextTriggerTime, t.clock.timeIn(t.clock.numberOfMillisecondsAsDuration(expectedRepeatInterval)));
});
it('triggers when executed again', function () {
t.triggerAllClockSchedulesAndAssertExecuted([scheduleId]);
t.assertInvocationCount(2);
});
it('is now not rescheduled', function () {
t.assertThereIsNoActiveScheduleFor(scheduleId);
});
});
describe('combined with until()', function () {
var t = testFn();
var scheduleId = t.startSchedule(function (s, clock) {
return scheduleBuilderFn(s, clock).until(t.executionCountIs(2));
});
it('triggers when executed', function () {
t.triggerAllClockSchedulesAndAssertExecuted([scheduleId]);
t.assertInvocationCount(1);
});
it('is rescheduled at the defined interval', function () {
assert.deepEqual(t.clockTimeoutFor(scheduleId), t.clock.numberOfMillisecondsAsDuration(expectedRepeatInterval));
assert.momentEql(t.scheduleFor(scheduleId).nextTriggerTime, t.clock.timeIn(t.clock.numberOfMillisecondsAsDuration(expectedRepeatInterval)));
});
it('triggers when executed again', function () {
t.triggerAllClockSchedulesAndAssertExecuted([scheduleId]);
t.assertInvocationCount(2);
});
it('is now not rescheduled', function () {
t.assertThereIsNoActiveScheduleFor(scheduleId);
});
});
});
}
function describeNoRepetitionScenariosFor(name, testFn, scheduleBuilderFn) {
describe(name, function () {
describe('', function () {
var t = testFn();
var scheduleId = t.startSchedule(scheduleBuilderFn);
it('has a 0ms initial delay', function () {
assert.deepEqual(t.clockTimeoutFor(scheduleId), t.clock.numberOfMillisecondsAsDuration(0));
assert.momentEql(t.scheduleFor(scheduleId).nextTriggerTime, t.clock.lastKnownTime());
});
});
describe('after() sets a custom initial delay', function () {
var t = testFn();
var startDelay = t.clock.numberOfMillisecondsAsDuration(AFTER_INTERVAL);
var scheduleId = t.startSchedule(function (s, clock) {
return scheduleBuilderFn(s, clock).after(startDelay);
});
it('initially is scheduled with the defined delay', function () {
assert.deepEqual(t.clockTimeoutFor(scheduleId), startDelay);
assert.momentEql(t.scheduleFor(scheduleId).nextTriggerTime, t.clock.timeIn(startDelay));
});
it('triggers when executed', function () {
t.triggerAllClockSchedulesAndAssertExecuted([scheduleId]);
t.assertInvocationCount(1);
});
it('is now not rescheduled', function () {
t.assertThereIsNoActiveScheduleFor(scheduleId);
});
});
describe('at() sets a initial delay at to a specific time', function () {
var t = testFn();
var startDelay = t.clock.numberOfMillisecondsAsDuration(AFTER_INTERVAL);
var scheduleId = t.startSchedule(function (s, clock) {
return scheduleBuilderFn(s, clock).at(clock.timeIn(startDelay));
});
it('initially is scheduled with the defined delay', function () {
assert.deepEqual(t.clockTimeoutFor(scheduleId), startDelay);
assert.momentEql(t.scheduleFor(scheduleId).nextTriggerTime, t.clock.timeIn(startDelay));
});
it('triggers when executed', function () {
t.triggerAllClockSchedulesAndAssertExecuted([scheduleId]);
t.assertInvocationCount(1);
});
it('is now not rescheduled', function () {
t.assertThereIsNoActiveScheduleFor(scheduleId);
});
});
describe('whilst() sets a pre-condition for execution', function () {
var t = testFn();
var scheduleId = t.startSchedule(function (s, clock) {
return scheduleBuilderFn(s, clock).whilst(t.executionCountIsLessThan(0));
});
it('triggers when executed, but does not invoke the promise', function () {
t.triggerAllClockSchedulesAndAssertExecuted([scheduleId]);
t.assertInvocationCount(0);
});
it('is now not rescheduled', function () {
t.assertThereIsNoActiveScheduleFor(scheduleId);
});
});
describe('until cannot be scheduled without a repeat', function () {
var t = testFn();
it('blows up as cannot have a post-condition for a one-off execution', function () {
try {
t.startSchedule(function (s, clock) {
return scheduleBuilderFn(s, clock).until(t.executionCountIs(2));
});
assert.fail(name);
} catch (err) {
assert.equal(err.message, 'Cannot specify an until() without repeating!', 'error message');
}
});
});
});
}
function describeSchedulerWhenCallback(name, testFn, expectedStartEvents, expectedFinishEvents, expectedErrorEvents) {
describe(name + ',', function () {
describe('correct events are emitted', function () {
var t = testFn();
var scheduleId = t.startSchedule(_.identity);
it('triggers when executed', function () {
t.triggerAllClockSchedulesAndAssertExecuted([scheduleId]);
t.assertInvocationCount(1);
});
it('emits then correct events', function () {
t.assertEventCounts(expectedStartEvents, expectedFinishEvents, expectedErrorEvents);
});
});
describe('cancelling a schedule', function () {
var t = testFn();
var scheduleId = t.startSchedule(_.identity);
it('returns the cancelled details' ,function() {
assert.equal(t.scheduler.cancel(scheduleId).id, scheduleId);
});
it('is now not scheduled', function () {
t.assertThereIsNoActiveScheduleFor(scheduleId);
});
});
describe('cancelling all schedules', function () {
var t = testFn();
var scheduleId = t.startSchedule(_.identity);
it('returns all cancelled details' ,function() {
var cancelled = t.scheduler.cancelAll();
assert.equal(_.size(cancelled), 1);
assert.equal(cancelled[scheduleId].id, scheduleId);
});
it('is now not scheduled', function () {
t.assertThereIsNoActiveScheduleFor(scheduleId);
});
});
describeNoRepetitionScenariosFor('one-off', testFn, _.identity);
describeNoRepetitionScenariosFor('once()', testFn, function (scheduler) {
return scheduler.once();
});
describeRepetitionScenariosFor('exactly()', ZERO_MILLIS, testFn, function (scheduler) {
return scheduler.exactly(3);
});
describeRepetitionScenariosFor('andRepeatAfter(1000ms)', REPEAT_OF_ONE_SECOND, testFn, function (scheduler, clock) {
return scheduler.andRepeatAfter(clock.numberOfMillisecondsAsDuration(REPEAT_OF_ONE_SECOND));
});
describeRepetitionScenariosFor('andRepeatAfter(0ms)', ZERO_MILLIS, testFn, function (scheduler, clock) {
return scheduler.andRepeatAfter(clock.numberOfMillisecondsAsDuration(ZERO_MILLIS));
});
describeRepetitionScenariosFor('atFixedIntervalOf(1000ms)', REPEAT_OF_ONE_SECOND, testFn, function (scheduler, clock) {
return scheduler.atFixedIntervalOf(clock.numberOfMillisecondsAsDuration(REPEAT_OF_ONE_SECOND));
});
describeRepetitionScenariosFor('atFixedIntervalOf(0ms)', ZERO_MILLIS, testFn, function (scheduler, clock) {
return scheduler.atFixedIntervalOf(clock.numberOfMillisecondsAsDuration(ZERO_MILLIS));
});
});
}
function describeSchedulerUsing(name, ClockCtr) {
describe('(' + name + ' clock): when', function () {
describeSchedulerWhenCallback('returns a promise which is resolved', setUpTest(ClockCtr, function() {
return q.resolve('ok value');
}), 1, 1, 0);
describeSchedulerWhenCallback('returns a promise which is then rejected', setUpTest(ClockCtr, function() {
return q.reject('err value');
}), 1, 0, 1);
describeSchedulerWhenCallback('callback succeeds', setUpTest(ClockCtr, function() {
return 'ok value';
}), 1, 1, 0);
describeSchedulerWhenCallback('exception is thrown', setUpTest(ClockCtr, function() {
throw 'err value';
}), 1, 0, 1);
});
}
describe('Scheduler', function () {
describeSchedulerUsing('date-based', zeit.StubDateClock);
describeSchedulerUsing('moment-based', zeit.StubMomentClock);
});