diff --git a/packages/@glimmer/integration-tests/lib/suites/components.ts b/packages/@glimmer/integration-tests/lib/suites/components.ts index baf7877140..afe8bb805b 100644 --- a/packages/@glimmer/integration-tests/lib/suites/components.ts +++ b/packages/@glimmer/integration-tests/lib/suites/components.ts @@ -886,4 +886,52 @@ export class BasicComponents extends RenderTest { this.assertHTML('', 'destroys correctly'); } + + @test({ kind: 'fragment' }) + 'throwing an error during rendering gives a readable error stack'(assert: Assert) { + let originalConsoleError = console.error; + + console.error = (message: string) => { + this.assert.ok( + message.match(/Error occurred while rendering:(\n\nBar\n {2}Foo)?/), + 'message logged' + ); + }; + + try { + assert.expect(7); + + this.registerComponent( + 'Glimmer', + 'Foo', + 'Hello', + class extends EmberishGlimmerComponent { + constructor(args: EmberishGlimmerArgs) { + super(args); + throw new Error('something went wrong!'); + } + } + ); + + this.registerComponent('Basic', 'Bar', '
'); + + this.render('{{#if showing}}
{{/if}}', { + showing: false, + }); + + this.assert.throws(() => { + this.rerender({ showing: true }); + }, /something went wrong!/); + + this.assertHTML( + '
', + 'values rendered before the error rendered correctly' + ); + this.destroy(); + + this.assertHTML('', 'destroys correctly'); + } finally { + console.error = originalConsoleError; + } + } } diff --git a/packages/@glimmer/runtime/lib/vm/append.ts b/packages/@glimmer/runtime/lib/vm/append.ts index fb03ac0e5b..c3977a6d92 100644 --- a/packages/@glimmer/runtime/lib/vm/append.ts +++ b/packages/@glimmer/runtime/lib/vm/append.ts @@ -568,7 +568,7 @@ export default class VM implements PublicVM, InternalVM { elements.popBlock(); } - resetTracking(); + console.error(`\n\nError occurred while rendering:\n\n${resetTracking()}\n\n`); } } } else { diff --git a/packages/@glimmer/runtime/lib/vm/update.ts b/packages/@glimmer/runtime/lib/vm/update.ts index b66bf8f3fd..312ebf79a8 100644 --- a/packages/@glimmer/runtime/lib/vm/update.ts +++ b/packages/@glimmer/runtime/lib/vm/update.ts @@ -52,7 +52,7 @@ export default class UpdatingVM { hasErrored = false; } finally { if (hasErrored) { - resetTracking(); + console.error(`\n\nError occurred while rendering:\n\n${resetTracking()}\n\n`); } } } else { diff --git a/packages/@glimmer/validator/lib/debug.ts b/packages/@glimmer/validator/lib/debug.ts index ade6323855..75c6d42403 100644 --- a/packages/@glimmer/validator/lib/debug.ts +++ b/packages/@glimmer/validator/lib/debug.ts @@ -10,7 +10,7 @@ export let runInTrackingTransaction: | ((fn: () => void, debuggingContext?: string | false) => void); export let deprecateMutationsInTrackingTransaction: undefined | ((fn: () => void) => void); -export let resetTrackingTransaction: undefined | (() => void); +export let resetTrackingTransaction: undefined | (() => string); export let setTrackingTransactionEnv: | undefined | ((env: { @@ -99,8 +99,16 @@ if (DEBUG) { }; resetTrackingTransaction = () => { + let stack = ''; + + if (TRANSACTION_STACK.length > 0) { + stack = logTrackingStack!(TRANSACTION_STACK[TRANSACTION_STACK.length - 1]); + } + TRANSACTION_STACK = []; CONSUMED_TAGS = null; + + return stack; }; /** diff --git a/packages/@glimmer/validator/lib/tracking.ts b/packages/@glimmer/validator/lib/tracking.ts index be1c5ec266..ddd2f1e464 100644 --- a/packages/@glimmer/validator/lib/tracking.ts +++ b/packages/@glimmer/validator/lib/tracking.ts @@ -110,7 +110,7 @@ export function endUntrackFrame(): void { } // This function is only for handling errors and resetting to a valid state -export function resetTracking(): void { +export function resetTracking(): string | void { while (OPEN_TRACK_FRAMES.length > 0) { OPEN_TRACK_FRAMES.pop(); } @@ -118,7 +118,7 @@ export function resetTracking(): void { CURRENT_TRACKER = null; if (DEBUG) { - unwrap(resetTrackingTransaction)(); + return unwrap(resetTrackingTransaction)(); } }