Expose browser errors uniformly outside of GlobalErrors

This commit is contained in:
Steve Gravrock
2025-10-04 12:11:14 -07:00
parent dbcc1c924a
commit bca56032e0
7 changed files with 135 additions and 195 deletions

View File

@@ -67,14 +67,14 @@ getJasmineRequireObj().Env = function(j$) {
if (!envOptions.suppressLoadErrors) {
installGlobalErrors();
globalErrors.pushListener(function loadtimeErrorHandler(error, event) {
globalErrors.pushListener(function loadtimeErrorHandler(error) {
topSuite.result.failedExpectations.push({
passed: false,
globalErrorType: 'load',
message: error ? error.message : event.message,
stack: error && error.stack,
filename: event && event.filename,
lineno: event && event.lineno
message: error.message,
stack: error.stack,
filename: error.filename,
lineno: error.lineno
});
});
}

View File

@@ -40,13 +40,6 @@ getJasmineRequireObj().GlobalErrors = function(j$) {
this.#adapter.uninstall();
}
// The listener at the top of the stack will be called with two arguments:
// the error and the event. Either of them may be falsy.
// The error will normally be provided, but will be falsy in the case of
// some browser load-time errors. The event will normally be provided in
// browsers but will be falsy in Node.
// Listeners that are pushed after spec files have been loaded should be
// able to just use the error parameter.
pushListener(listener) {
this.#handlers.push(listener);
}
@@ -78,29 +71,23 @@ getJasmineRequireObj().GlobalErrors = function(j$) {
}
reportUnhandledRejections() {
for (const {
reason,
event
} of this.#pendingUnhandledRejections.values()) {
this.#dispatchError(reason, event);
for (const { reason } of this.#pendingUnhandledRejections.values()) {
this.#dispatchError(reason);
}
this.#pendingUnhandledRejections.clear();
}
// Either error or event may be undefined
#onUncaughtException(error, event) {
this.#dispatchError(error, event);
#onUncaughtException(error) {
this.#dispatchError(error);
}
// event or promise may be undefined
// event is passed through for backwards compatibility reasons. It's probably
// unnecessary, but user code could depend on it.
#onUnhandledRejection(reason, promise, event) {
// promise may be undefined
#onUnhandledRejection(reason, promise) {
if (this.#detectLateRejectionHandling() && promise) {
this.#pendingUnhandledRejections.set(promise, { reason, event });
this.#pendingUnhandledRejections.set(promise, { reason });
} else {
this.#dispatchError(reason, event);
this.#dispatchError(reason);
}
}
@@ -112,8 +99,7 @@ getJasmineRequireObj().GlobalErrors = function(j$) {
this.#pendingUnhandledRejections.delete(promise);
}
// Either error or event may be undefined
#dispatchError(error, event) {
#dispatchError(error) {
if (this.#overrideHandler) {
// See discussion of spyOnGlobalErrorsAsync in base.js
this.#overrideHandler(error);
@@ -123,7 +109,7 @@ getJasmineRequireObj().GlobalErrors = function(j$) {
const handler = this.#handlers[this.#handlers.length - 1];
if (handler) {
handler(error, event);
handler(error);
} else {
throw error;
}
@@ -140,7 +126,7 @@ getJasmineRequireObj().GlobalErrors = function(j$) {
constructor(global, dispatch) {
this.#global = global;
this.#dispatch = dispatch;
this.#onError = event => dispatch.onUncaughtException(event.error, event);
this.#onError = this.#errorHandler.bind(this);
this.#onUnhandledRejection = this.#unhandledRejectionHandler.bind(this);
this.#onRejectionHandled = this.#rejectionHandledHandler.bind(this);
}
@@ -169,6 +155,28 @@ getJasmineRequireObj().GlobalErrors = function(j$) {
);
}
#errorHandler(event) {
let error = event.error;
// event.error isn't guaranteed to be present in all browser load-time
// error events.
if (!error) {
error = {
message: event.message,
stack: `@${event.filename}:${event.lineno}`
};
}
if (event.filename) {
// filename and lineno can be more convenient than stack when reporting
// things like syntax errors. Pass them along.
error.filename = event.filename;
error.lineno = event.lineno;
}
this.#dispatch.onUncaughtException(error);
}
#unhandledRejectionHandler(event) {
const jasmineMessage = 'Unhandled promise rejection: ' + event.reason;
let reason;
@@ -180,7 +188,7 @@ getJasmineRequireObj().GlobalErrors = function(j$) {
reason = jasmineMessage;
}
this.#dispatch.onUnhandledRejection(reason, event.promise, event);
this.#dispatch.onUnhandledRejection(reason, event.promise);
}
#rejectionHandledHandler(event) {

View File

@@ -77,8 +77,8 @@ getJasmineRequireObj().QueueRunner = function(j$) {
}
QueueRunner.prototype.execute = function() {
this.handleFinalError = (error, event) => {
this.onException(errorOrMsgForGlobalError(error, event));
this.handleFinalError = error => {
this.onException(error);
};
this.globalErrors.pushListener(this.handleFinalError);
this.run(0);
@@ -108,8 +108,8 @@ getJasmineRequireObj().QueueRunner = function(j$) {
this.recordError_(iterativeIndex);
};
function handleError(error, event) {
onException(errorOrMsgForGlobalError(error, event));
function handleError(error) {
onException(error);
}
const cleanup = once(() => {
if (timeoutId !== void 0) {
@@ -296,16 +296,5 @@ getJasmineRequireObj().QueueRunner = function(j$) {
};
}
function errorOrMsgForGlobalError(error, event) {
// TODO: In cases where error is a string or undefined, the error message
// that gets sent to reporters will be `${message} thrown`, which could
// be improved to not say "thrown" when the cause wasn't necessarily
// an exception or to provide hints about throwing Errors rather than
// strings.
return (
error || (event && event.message) || 'Global error event with no message'
);
}
return QueueRunner;
};