Last active
August 20, 2025 01:13
-
-
Save sukima/2378fa36cd407821cab1abfe00f98e10 to your computer and use it in GitHub Desktop.
Micro util helpers for trapping errors in (Ember) tests
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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