Skip to content

Instantly share code, notes, and snippets.

@sukima
Last active August 20, 2025 01:13
Show Gist options
  • Save sukima/2378fa36cd407821cab1abfe00f98e10 to your computer and use it in GitHub Desktop.
Save sukima/2378fa36cd407821cab1abfe00f98e10 to your computer and use it in GitHub Desktop.
Micro util helpers for trapping errors in (Ember) tests
import { assert } from '@ember/debug';
import { resetOnerror, setupOnerror } from '@ember/test-helpers';
/**
* Trap window.onerror
* Be sure to use the `using` keyword.
*
* @example
* ```ts
* test('something', async function (assert) {
* using _windowOnerrorTrap = trapWindowOnerror((event) => {
* const message = event instanceof ErrorEvent
* ? event.message
* : String(event);
* assert.step(message);
* });
*
* await doSomething();
*
* assert.verifySteps(['Error: something']);
* });
* ```
*/
export function trapWindowOnerror(
setup: NonNullable<typeof window.onerror>,
): Disposable {
assert(
"trapWindowOnerror can only be used if Explicit Resouce Management is available",
Symbol.dispose,
);
const originalWindowOnerror = window.onerror;
const dispose = () => window.onerror = originalWindowOnerror;
window.onerror = setup;
return { [Symbol.dispose]: dispose };
}
/**
* Trap Ember.onerror
* Be sure to use the `using` keyword.
*
* @example
* ```ts
* test('something', async function (assert) {
* using _emberOnerrorTrap = trapEmberOnerror((error) => {
* assert.step(String(error));
* });
*
* await doSomething();
*
* assert.verifySteps(['Error: something']);
* });
* ```
*/
export function trapEmberOnerror(
...args: Parameters<typeof setupOnerror>
): Disposable {
assert(
'trapEmberOnerror can only be used if Explicit Resouce Management is available',
Symbol.dispose,
);
setupOnerror(...args);
return { [Symbol.dispose]: resetOnerror };
}
/**
* Trap console.error
* Be sure to use the `using` keyword.
*
* @example
* ```ts
* test('something', async function (assert) {
* using _consoleErrorTrap = trapConsoleError((error) => {
* assert.step(String(error));
* });
*
* await doSomething();
*
* assert.verifySteps(['Error: something']);
* });
* ```
*/
export function trapConsoleError(setup: typeof console.error): Disposable {
assert(
'trapConsoleError can only be used if Explicit Resouce Management is available',
Symbol.dispose,
);
const originalConcoleError = console.error;
const dispose = () => console.error = originalConcoleError;
console.error = setup;
return { [Symbol.dispose]: dispose };
}
/**
* Allow easy traping of common errors in Ember.
* Can trap window.onerror, Ember.onerror, and console.error
*
* Use this is your build environment does not yet suppoer Explicit Resource
* Management
*
* @example
* ```ts
* test('something', async function (assert) {
* await disposableErrorTraps(async ({
* trapWindowOnerror,
* trapEmberOnerror,
* trapConsoleError,
* }) => {
* trapWindowOnerror((event) => {
* const message = event instanceof ErrorEvent
* ? event.message
* : String(event);
* assert.step(message);
* });
* trapEmberOnerror((error) => assert.step(String(error)));
* trapConsoleError((error) => assert.step(String(error)));
*
* await doSomething();
*
* assert.verifySteps([
* 'Error: something',
* 'Error: something',
* 'Error: something',
* ]);
* });
* });
* ```
*/
export async function disposableErrorTraps(
disposableScope: (setup: {
trapWindowOnerror: (setup: NonNullable<typeof window.onerror>) => void;
trapEmberOnerror: typeof setupOnerror;
trapConsoleError: (setup: typeof console.error) => void;
}) => Promise<void> | void,
): Promise<void> {
const disposeStack: (() => void)[] = [];
const originalWindowOnerror = window.onerror;
const originalConcoleError = console.error;
try {
await disposableScope({
trapWindowOnerror: (setup) => {
window.onerror = (...args) => (setup(...args), true);
disposeStack.push(() => window.onerror = originalWindowOnerror);
},
trapEmberOnerror: (...args) => {
setupOnerror(...args);
disposeStack.push(resetOnerror);
},
trapConsoleError: (setup) => {
console.error = setup;
disposeStack.push(() => console.error = originalConcoleError);
},
});
} finally {
disposeStack.reverse().forEach((i) => i());
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment