diff --git a/frontend/taskdeck-web/src/tests/views/paper/review/ReviewDecisionRail.spec.ts b/frontend/taskdeck-web/src/tests/views/paper/review/ReviewDecisionRail.spec.ts index 787499ee5..5d09a7851 100644 --- a/frontend/taskdeck-web/src/tests/views/paper/review/ReviewDecisionRail.spec.ts +++ b/frontend/taskdeck-web/src/tests/views/paper/review/ReviewDecisionRail.spec.ts @@ -380,8 +380,26 @@ describe('ReviewDecisionRail', () => { // note id cannot resolve for a later test that expects it to be absent. const cleanups: Array<() => void> = [] + function drainCleanups(): void { + const errors: unknown[] = [] + while (cleanups.length > 0) { + const cleanup = cleanups.pop() + if (!cleanup) continue + try { + cleanup() + } catch (error: unknown) { + errors.push(error) + } + } + + if (errors.length === 1) throw errors[0] + if (errors.length > 1) { + throw new AggregateError(errors, 'One or more cleanup callbacks failed') + } + } + afterEach(() => { - for (const cleanup of cleanups.splice(0).reverse()) cleanup() + drainCleanups() }) function renderExternalNote(): void { @@ -392,6 +410,21 @@ describe('ReviewDecisionRail', () => { cleanups.push(() => note.remove()) } + it('drains every cleanup when one callback throws', () => { + const events: string[] = [] + const throwingCleanup = new Error('cleanup failed') + cleanups.push(() => events.push('oldest')) + cleanups.push(() => { + events.push('throwing') + throw throwingCleanup + }) + cleanups.push(() => events.push('newest')) + + expect(drainCleanups).toThrow(throwingCleanup) + expect(events).toEqual(['newest', 'throwing', 'oldest']) + expect(cleanups).toHaveLength(0) + }) + it('describes every disabled decision control with the external explanation', () => { renderExternalNote() const wrapper = mountRail( diff --git a/frontend/taskdeck-web/src/tests/views/paper/review/ReviewMain.spec.ts b/frontend/taskdeck-web/src/tests/views/paper/review/ReviewMain.spec.ts index e525fe432..b3909f060 100644 --- a/frontend/taskdeck-web/src/tests/views/paper/review/ReviewMain.spec.ts +++ b/frontend/taskdeck-web/src/tests/views/paper/review/ReviewMain.spec.ts @@ -257,8 +257,26 @@ describe('ReviewMain', () => { // note id cannot resolve for a later test that expects it to be absent. const cleanups: Array<() => void> = [] + function drainCleanups(): void { + const errors: unknown[] = [] + while (cleanups.length > 0) { + const cleanup = cleanups.pop() + if (!cleanup) continue + try { + cleanup() + } catch (error: unknown) { + errors.push(error) + } + } + + if (errors.length === 1) throw errors[0] + if (errors.length > 1) { + throw new AggregateError(errors, 'One or more cleanup callbacks failed') + } + } + afterEach(() => { - for (const cleanup of cleanups.splice(0).reverse()) cleanup() + drainCleanups() }) function renderLockNote(): void { @@ -269,6 +287,21 @@ describe('ReviewMain', () => { cleanups.push(() => note.remove()) } + it('drains every cleanup when one callback throws', () => { + const events: string[] = [] + const throwingCleanup = new Error('cleanup failed') + cleanups.push(() => events.push('oldest')) + cleanups.push(() => { + events.push('throwing') + throw throwingCleanup + }) + cleanups.push(() => events.push('newest')) + + expect(drainCleanups).toThrow(throwingCleanup) + expect(events).toEqual(['newest', 'throwing', 'oldest']) + expect(cleanups).toHaveLength(0) + }) + it('forwards the column description ids to every disabled decision control', () => { renderLockNote()