Files
jasmine/src/core/QueueRunner.js
Ferdinand Prantl 3a7fc63879 Pick the error instance to pass to error handlers in QueueRunner
The first number is the error message in HTML5 browser, which does not include
the call stack. The error instance allows logging the complete call stack in
reporters.
2019-08-11 09:32:31 +02:00

214 lines
5.8 KiB
JavaScript

getJasmineRequireObj().QueueRunner = function(j$) {
function StopExecutionError() {}
StopExecutionError.prototype = new Error();
j$.StopExecutionError = StopExecutionError;
function once(fn) {
var called = false;
return function(arg) {
if (!called) {
called = true;
// Direct call using single parameter, because cleanup/next does not need more
fn(arg);
}
return null;
};
}
function emptyFn() {}
function QueueRunner(attrs) {
var queueableFns = attrs.queueableFns || [];
this.queueableFns = queueableFns.concat(attrs.cleanupFns || []);
this.firstCleanupIx = queueableFns.length;
this.onComplete = attrs.onComplete || emptyFn;
this.clearStack =
attrs.clearStack ||
function(fn) {
fn();
};
this.onException = attrs.onException || emptyFn;
this.userContext = attrs.userContext || new j$.UserContext();
this.timeout = attrs.timeout || {
setTimeout: setTimeout,
clearTimeout: clearTimeout
};
this.fail = attrs.fail || emptyFn;
this.globalErrors = attrs.globalErrors || {
pushListener: emptyFn,
popListener: emptyFn
};
this.completeOnFirstError = !!attrs.completeOnFirstError;
this.errored = false;
if (typeof this.onComplete !== 'function') {
throw new Error('invalid onComplete ' + JSON.stringify(this.onComplete));
}
this.deprecated = attrs.deprecated;
}
QueueRunner.prototype.execute = function() {
var self = this;
this.handleFinalError = function(message, source, lineno, colno, error) {
// Older browsers would send the error as the first parameter. HTML5
// specifies the the five parameters above. The error instance should
// be preffered, otherwise the call stack would get lost.
self.onException(error || message);
};
this.globalErrors.pushListener(this.handleFinalError);
this.run(0);
};
QueueRunner.prototype.skipToCleanup = function(lastRanIndex) {
if (lastRanIndex < this.firstCleanupIx) {
this.run(this.firstCleanupIx);
} else {
this.run(lastRanIndex + 1);
}
};
QueueRunner.prototype.clearTimeout = function(timeoutId) {
Function.prototype.apply.apply(this.timeout.clearTimeout, [
j$.getGlobal(),
[timeoutId]
]);
};
QueueRunner.prototype.setTimeout = function(fn, timeout) {
return Function.prototype.apply.apply(this.timeout.setTimeout, [
j$.getGlobal(),
[fn, timeout]
]);
};
QueueRunner.prototype.attempt = function attempt(iterativeIndex) {
var self = this,
completedSynchronously = true,
handleError = function handleError(error) {
onException(error);
next(error);
},
cleanup = once(function cleanup() {
if (timeoutId !== void 0) {
self.clearTimeout(timeoutId);
}
self.globalErrors.popListener(handleError);
}),
next = once(function next(err) {
cleanup();
if (j$.isError_(err)) {
if (!(err instanceof StopExecutionError) && !err.jasmineMessage) {
self.fail(err);
}
self.errored = errored = true;
}
function runNext() {
if (self.completeOnFirstError && errored) {
self.skipToCleanup(iterativeIndex);
} else {
self.run(iterativeIndex + 1);
}
}
if (completedSynchronously) {
self.setTimeout(runNext);
} else {
runNext();
}
}),
errored = false,
queueableFn = self.queueableFns[iterativeIndex],
timeoutId;
next.fail = function nextFail() {
self.fail.apply(null, arguments);
self.errored = errored = true;
next();
};
self.globalErrors.pushListener(handleError);
if (queueableFn.timeout !== undefined) {
var timeoutInterval = queueableFn.timeout || j$.DEFAULT_TIMEOUT_INTERVAL;
timeoutId = self.setTimeout(function() {
var error = new Error(
'Timeout - Async callback was not invoked within ' +
timeoutInterval +
'ms ' +
(queueableFn.timeout
? '(custom timeout)'
: '(set by jasmine.DEFAULT_TIMEOUT_INTERVAL)')
);
onException(error);
next();
}, timeoutInterval);
}
try {
if (queueableFn.fn.length === 0) {
var maybeThenable = queueableFn.fn.call(self.userContext);
if (maybeThenable && j$.isFunction_(maybeThenable.then)) {
maybeThenable.then(next, onPromiseRejection);
completedSynchronously = false;
return { completedSynchronously: false };
}
} else {
queueableFn.fn.call(self.userContext, next);
completedSynchronously = false;
return { completedSynchronously: false };
}
} catch (e) {
onException(e);
self.errored = errored = true;
}
cleanup();
return { completedSynchronously: true, errored: errored };
function onException(e) {
self.onException(e);
self.errored = errored = true;
}
function onPromiseRejection(e) {
onException(e);
next();
}
};
QueueRunner.prototype.run = function(recursiveIndex) {
var length = this.queueableFns.length,
self = this,
iterativeIndex;
for (
iterativeIndex = recursiveIndex;
iterativeIndex < length;
iterativeIndex++
) {
var result = this.attempt(iterativeIndex);
if (!result.completedSynchronously) {
return;
}
self.errored = self.errored || result.errored;
if (this.completeOnFirstError && result.errored) {
this.skipToCleanup(iterativeIndex);
return;
}
}
this.clearStack(function() {
self.globalErrors.popListener(self.handleFinalError);
self.onComplete(self.errored && new StopExecutionError());
});
};
return QueueRunner;
};