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, ): 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 ): 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) => void; trapEmberOnerror: typeof setupOnerror; trapConsoleError: (setup: typeof console.error) => void; }) => Promise | void, ): Promise { 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()); } }