This makes the specs green and appears to work for most cases. I have a
number of concerns about the implementation and would appreciate
ideas/feedback.
- Suite#addExpecationResult infers if it is coming from an afterAll fn
based on if the first child of the suite is finished. This assumes
that the first child of the suite is a spec (this appears to be true
as long as there is at least one spec in the suite)
- Suites behave like unfinished specs. Because suites will propagate
expectation failures to their children suites, the afterAll
expectation reporting appears to work for suites without specs
unless you have:
1) An otherwise empty suite with an afterAll
2) An afterAll'd suite whose first suite is empty (or whose first
suite's first suite is empty (and so on))
- Changed afterAllError to afterAllEvent, so it can accommodate both
errors and expectation failures. The reporter now receives a string
instead of the actual error object. The loss of the object doesn't
affect our reporters, but may be a nice-to-have for other reporters/
the future.
- The gap between the expectations caught in Suite and QueueRunner (who
triggers reporting via an injected callback) is an array injected into
QR by the Suite. The array is then flushed at some point (currently
after the attempt… functions). This works, but is a bit goofy.
[#73741654]
291 lines
10 KiB
JavaScript
291 lines
10 KiB
JavaScript
describe("QueueRunner", function() {
|
|
|
|
it("runs all the functions it's passed", function() {
|
|
var calls = [],
|
|
queueableFn1 = { fn: jasmine.createSpy('fn1') },
|
|
queueableFn2 = { fn: jasmine.createSpy('fn2') },
|
|
queueRunner = new j$.QueueRunner({
|
|
queueableFns: [queueableFn1, queueableFn2]
|
|
});
|
|
queueableFn1.fn.and.callFake(function() {
|
|
calls.push('fn1');
|
|
});
|
|
queueableFn2.fn.and.callFake(function() {
|
|
calls.push('fn2');
|
|
});
|
|
|
|
queueRunner.execute();
|
|
|
|
expect(calls).toEqual(['fn1', 'fn2']);
|
|
});
|
|
|
|
it("calls each function with a consistent 'this'-- an empty object", function() {
|
|
var queueableFn1 = { fn: jasmine.createSpy('fn1') },
|
|
queueableFn2 = { fn: jasmine.createSpy('fn2') },
|
|
queueableFn3 = { fn: function(done) { asyncContext = this; done(); } },
|
|
queueRunner = new j$.QueueRunner({
|
|
queueableFns: [queueableFn1, queueableFn2, queueableFn3]
|
|
}),
|
|
asyncContext;
|
|
|
|
queueRunner.execute();
|
|
|
|
var context = queueableFn1.fn.calls.first().object;
|
|
expect(context).toEqual({});
|
|
expect(queueableFn2.fn.calls.first().object).toBe(context);
|
|
expect(asyncContext).toBe(context);
|
|
});
|
|
|
|
describe("with an asynchronous function", function() {
|
|
beforeEach(function() {
|
|
jasmine.clock().install();
|
|
});
|
|
|
|
afterEach(function() {
|
|
jasmine.clock().uninstall();
|
|
});
|
|
|
|
it("supports asynchronous functions, only advancing to next function after a done() callback", function() {
|
|
//TODO: it would be nice if spy arity could match the fake, so we could do something like:
|
|
//createSpy('asyncfn').and.callFake(function(done) {});
|
|
|
|
var onComplete = jasmine.createSpy('onComplete'),
|
|
beforeCallback = jasmine.createSpy('beforeCallback'),
|
|
fnCallback = jasmine.createSpy('fnCallback'),
|
|
afterCallback = jasmine.createSpy('afterCallback'),
|
|
queueableFn1 = { fn: function(done) {
|
|
beforeCallback();
|
|
setTimeout(done, 100);
|
|
} },
|
|
queueableFn2 = { fn: function(done) {
|
|
fnCallback();
|
|
setTimeout(done, 100);
|
|
} },
|
|
queueableFn3 = { fn: function(done) {
|
|
afterCallback();
|
|
setTimeout(done, 100);
|
|
} },
|
|
queueRunner = new j$.QueueRunner({
|
|
queueableFns: [queueableFn1, queueableFn2, queueableFn3],
|
|
onComplete: onComplete
|
|
});
|
|
|
|
queueRunner.execute();
|
|
|
|
expect(beforeCallback).toHaveBeenCalled();
|
|
expect(fnCallback).not.toHaveBeenCalled();
|
|
expect(afterCallback).not.toHaveBeenCalled();
|
|
expect(onComplete).not.toHaveBeenCalled();
|
|
|
|
jasmine.clock().tick(100);
|
|
|
|
expect(fnCallback).toHaveBeenCalled();
|
|
expect(afterCallback).not.toHaveBeenCalled();
|
|
expect(onComplete).not.toHaveBeenCalled();
|
|
|
|
jasmine.clock().tick(100);
|
|
|
|
expect(afterCallback).toHaveBeenCalled();
|
|
expect(onComplete).not.toHaveBeenCalled();
|
|
|
|
jasmine.clock().tick(100);
|
|
|
|
expect(onComplete).toHaveBeenCalled();
|
|
});
|
|
|
|
it("sets a timeout if requested for asynchronous functions so they don't go on forever", function() {
|
|
var timeout = 3,
|
|
beforeFn = { fn: function(done) { }, type: 'before', timeout: function() { return timeout; } },
|
|
queueableFn = { fn: jasmine.createSpy('fn'), type: 'queueable' },
|
|
onComplete = jasmine.createSpy('onComplete'),
|
|
reportException = jasmine.createSpy('reportException'),
|
|
onException = jasmine.createSpy('onException'),
|
|
queueRunner = new j$.QueueRunner({
|
|
queueableFns: [beforeFn, queueableFn],
|
|
onComplete: onComplete,
|
|
reportException: reportException,
|
|
onException: onException
|
|
});
|
|
|
|
queueRunner.execute();
|
|
expect(queueableFn.fn).not.toHaveBeenCalled();
|
|
|
|
jasmine.clock().tick(timeout);
|
|
|
|
expect(reportException).toHaveBeenCalledWith(jasmine.any(Error), 'before');
|
|
expect(onException).toHaveBeenCalledWith(jasmine.any(Error));
|
|
expect(queueableFn.fn).toHaveBeenCalled();
|
|
expect(onComplete).toHaveBeenCalled();
|
|
});
|
|
|
|
it("by default does not set a timeout for asynchronous functions", function() {
|
|
var beforeFn = { fn: function(done) { } },
|
|
queueableFn = { fn: jasmine.createSpy('fn') },
|
|
onComplete = jasmine.createSpy('onComplete'),
|
|
reportException = jasmine.createSpy('reportException'),
|
|
onException = jasmine.createSpy('onException'),
|
|
queueRunner = new j$.QueueRunner({
|
|
queueableFns: [beforeFn, queueableFn],
|
|
onComplete: onComplete,
|
|
reportException: reportException,
|
|
onException: onException,
|
|
});
|
|
|
|
queueRunner.execute();
|
|
expect(queueableFn.fn).not.toHaveBeenCalled();
|
|
|
|
jasmine.clock().tick(j$.DEFAULT_TIMEOUT_INTERVAL);
|
|
|
|
expect(reportException).not.toHaveBeenCalled();
|
|
expect(onException).not.toHaveBeenCalled();
|
|
expect(queueableFn.fn).not.toHaveBeenCalled();
|
|
expect(onComplete).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("clears the timeout when an async function throws an exception, to prevent additional exception reporting", function() {
|
|
var queueableFn = { fn: function(done) { throw new Error("error!"); } },
|
|
onComplete = jasmine.createSpy('onComplete'),
|
|
reportException = jasmine.createSpy('reportException'),
|
|
onException = jasmine.createSpy('onException'),
|
|
queueRunner = new j$.QueueRunner({
|
|
queueableFns: [queueableFn],
|
|
onComplete: onComplete,
|
|
reportException: reportException,
|
|
onException: onException
|
|
});
|
|
|
|
queueRunner.execute();
|
|
|
|
expect(onComplete).toHaveBeenCalled();
|
|
expect(reportException).toHaveBeenCalled();
|
|
expect(onException).toHaveBeenCalled();
|
|
|
|
jasmine.clock().tick(j$.DEFAULT_TIMEOUT_INTERVAL);
|
|
expect(reportException.calls.count()).toEqual(1);
|
|
expect(onException.calls.count()).toEqual(1);
|
|
});
|
|
|
|
it("clears the timeout when the done callback is called", function() {
|
|
var queueableFn = { fn: function(done) { done(); } },
|
|
onComplete = jasmine.createSpy('onComplete'),
|
|
reportException = jasmine.createSpy('reportException'),
|
|
onException = jasmine.createSpy('onException'),
|
|
queueRunner = new j$.QueueRunner({
|
|
queueableFns: [queueableFn],
|
|
onComplete: onComplete,
|
|
reportException: reportException,
|
|
onException: onException
|
|
});
|
|
|
|
queueRunner.execute();
|
|
|
|
expect(onComplete).toHaveBeenCalled();
|
|
|
|
jasmine.clock().tick(j$.DEFAULT_TIMEOUT_INTERVAL);
|
|
expect(reportException).not.toHaveBeenCalled();
|
|
expect(onException).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("only moves to the next spec the first time you call done", function() {
|
|
var queueableFn = { fn: function(done) {done(); done();} },
|
|
nextQueueableFn = { fn: jasmine.createSpy('nextFn') };
|
|
queueRunner = new j$.QueueRunner({
|
|
queueableFns: [queueableFn, nextQueueableFn]
|
|
});
|
|
|
|
queueRunner.execute();
|
|
expect(nextQueueableFn.fn.calls.count()).toEqual(1);
|
|
});
|
|
|
|
it("does not move to the next spec if done is called after an exception has ended the spec", function() {
|
|
var queueableFn = { fn: function(done) {
|
|
setTimeout(done, 1);
|
|
throw new Error('error!');
|
|
} },
|
|
nextQueueableFn = { fn: jasmine.createSpy('nextFn') };
|
|
queueRunner = new j$.QueueRunner({
|
|
queueableFns: [queueableFn, nextQueueableFn]
|
|
});
|
|
|
|
queueRunner.execute();
|
|
jasmine.clock().tick(1);
|
|
expect(nextQueueableFn.fn.calls.count()).toEqual(1);
|
|
});
|
|
});
|
|
|
|
it("calls exception handlers when an exception is thrown in a fn", function() {
|
|
var queueableFn = { type: 'queueable',
|
|
fn: function() {
|
|
throw new Error('fake error');
|
|
} },
|
|
exceptionCallback = jasmine.createSpy('exception callback'),
|
|
onExceptionCallback = jasmine.createSpy('on exception callback'),
|
|
queueRunner = new j$.QueueRunner({
|
|
queueableFns: [queueableFn],
|
|
reportException: exceptionCallback,
|
|
onException: onExceptionCallback
|
|
});
|
|
|
|
queueRunner.execute();
|
|
|
|
expect(exceptionCallback).toHaveBeenCalledWith(jasmine.any(Error), 'queueable');
|
|
expect(onExceptionCallback).toHaveBeenCalledWith(jasmine.any(Error));
|
|
});
|
|
|
|
it("rethrows an exception if told to", function() {
|
|
var queueableFn = { fn: function() {
|
|
throw new Error('fake error');
|
|
} },
|
|
queueRunner = new j$.QueueRunner({
|
|
queueableFns: [queueableFn],
|
|
catchException: function(e) { return false; }
|
|
});
|
|
|
|
expect(function() {
|
|
queueRunner.execute();
|
|
}).toThrowError('fake error');
|
|
});
|
|
|
|
it("continues running the functions even after an exception is thrown in an async spec", function() {
|
|
var queueableFn = { fn: function(done) { throw new Error("error"); } },
|
|
nextQueueableFn = { fn: jasmine.createSpy("nextFunction") },
|
|
queueRunner = new j$.QueueRunner({
|
|
queueableFns: [queueableFn, nextQueueableFn]
|
|
});
|
|
|
|
queueRunner.execute();
|
|
expect(nextQueueableFn.fn).toHaveBeenCalled();
|
|
});
|
|
|
|
it("calls a provided complete callback when done", function() {
|
|
var queueableFn = { fn: jasmine.createSpy('fn') },
|
|
completeCallback = jasmine.createSpy('completeCallback'),
|
|
queueRunner = new j$.QueueRunner({
|
|
queueableFns: [queueableFn],
|
|
onComplete: completeCallback
|
|
});
|
|
|
|
queueRunner.execute();
|
|
|
|
expect(completeCallback).toHaveBeenCalled();
|
|
});
|
|
|
|
it("calls a provided stack clearing function when done", function() {
|
|
var asyncFn = { fn: function(done) { done() } },
|
|
afterFn = { fn: jasmine.createSpy('afterFn') },
|
|
completeCallback = jasmine.createSpy('completeCallback'),
|
|
clearStack = jasmine.createSpy('clearStack'),
|
|
queueRunner = new j$.QueueRunner({
|
|
queueableFns: [asyncFn, afterFn],
|
|
clearStack: clearStack,
|
|
onComplete: completeCallback
|
|
});
|
|
|
|
clearStack.and.callFake(function(fn) { fn(); });
|
|
|
|
queueRunner.execute();
|
|
expect(afterFn.fn).toHaveBeenCalled();
|
|
expect(clearStack).toHaveBeenCalledWith(completeCallback);
|
|
});
|
|
});
|