Apply the AGENTS.md comments guideline to every .ts under tests/: remove what the code already expresses, keep only what it cannot. Removed: spec-header coverage inventories the describe/test tree already names; comments restating the next line's assertion or its message; helper JSDoc whose body already says it; within-file duplicates; five stale claims (trendChart's "six aggregated turns" against agg.length === 8, agentGraph's "six categories" against CATS' seven, fold-timing's "cheapest call LAST" against a loop that feeds it first, fold-surface's unreachable plain-JSON note, contextView's "two turns" for a one-turn fixture). Kept and condensed: the projection-freeze class, strict wire/state-schema hazards, retention and pricing windows, pairing rules, hostile-input rationale, fixture and expectation-array ground truth, jsdom mechanics, and issue-numbered regressions. Comment lines 2604 -> 2014 (-22%); 85 module headers kept, 30 coverage inventories dropped; every surviving comment line is within the 140-column limit, with 36 wrapped lines joined onto fuller ones. No logic, string, identifier, import or directive changed: a comment-strip gate proves each file's comment-free code is byte-identical to HEAD, and a line-level proof pairs all 845 changed lines with an unchanged code part. pnpm run lint && pnpm run test (106 files / 2061 tests, per-file 100% coverage) && pnpm run build all pass.
64 lines
2.3 KiB
TypeScript
64 lines
2.3 KiB
TypeScript
// React logs caught errors to console and replays the failed render through a
|
|
// fake DOM event — silenced with a window-error preventer and a spy, never mocked.
|
|
|
|
import { createElement as h } from 'react'
|
|
import assert from 'node:assert/strict'
|
|
import { afterEach, describe, test, vi } from 'vitest'
|
|
import { makeErrorBoundary } from '../../../src/client/components/errorBoundary'
|
|
import { click, makeKit, mount, query, silenceWindowErrors, text } from '../helpers/kit'
|
|
|
|
const kit = makeKit()
|
|
const ErrorBoundary = makeErrorBoundary(kit.t)
|
|
|
|
let consoleSpy: ReturnType<typeof vi.spyOn> | null = null
|
|
let silenceErrors: (() => void) | null = null
|
|
|
|
afterEach(() => {
|
|
consoleSpy?.mockRestore()
|
|
consoleSpy = null
|
|
silenceErrors?.()
|
|
silenceErrors = null
|
|
})
|
|
|
|
function silenceRenderErrors(): void {
|
|
consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
silenceErrors = silenceWindowErrors()
|
|
}
|
|
|
|
describe('ErrorBoundary', () => {
|
|
test('passes children through when nothing throws', async () => {
|
|
const m = await mount(h(ErrorBoundary, {}, h('div', { className: 'healthy' }, 'all good')))
|
|
assert.equal(text(m.container), 'all good')
|
|
await m.unmount()
|
|
})
|
|
|
|
test('a render error degrades to the error card; Retry resumes a healthy child', async () => {
|
|
silenceRenderErrors()
|
|
let shouldThrow = true
|
|
function Bomb() {
|
|
if (shouldThrow) throw new Error('kaboom')
|
|
return h('div', { className: 'healthy' }, 'recovered')
|
|
}
|
|
const m = await mount(h(ErrorBoundary, {}, h(Bomb, {})))
|
|
const card = query(m.container, '.lc-error')
|
|
assert.ok(text(card).includes('Failed to read context data:'))
|
|
assert.equal(query(card, '.lc-error-msg').textContent, 'kaboom')
|
|
const retry = query(card, '.lc-error-retry')
|
|
assert.equal(retry.textContent, 'Retry')
|
|
shouldThrow = false
|
|
await click(retry)
|
|
assert.equal(text(m.container), 'recovered')
|
|
await m.unmount()
|
|
})
|
|
|
|
test('a non-Error throw is stringified into the card', async () => {
|
|
silenceRenderErrors()
|
|
function Bomb(): never {
|
|
throw 'string failure' // deliberate non-Error throw
|
|
}
|
|
const m = await mount(h(ErrorBoundary, {}, h(Bomb, {})))
|
|
assert.equal(query(m.container, '.lc-error-msg').textContent, 'string failure')
|
|
await m.unmount()
|
|
})
|
|
})
|