808fde106d
The same AST sweep over specs found fixtures maintained in parallel across suites that have no reason to know about each other. Twenty-one specs each mounted useMessageStream themselves and ten of the harnesses were byte-identical; twenty now take renderMessageStream, with overrides for the seams that genuinely vary. The SessionInfo builder was spelled out field-by-field in seven specs, so a new backend field broke seven files instead of one. Twenty specs carried their own inert ResizeObserver and eleven repeated the animation-frame, CSS.escape, scrollTo and WAAPI stubs the transcript needs to mount at all — split by scope into src/test/jsdom for what any component might need and the assistant-ui folder's own kit for the transcript. Plus the window-state bridge, deferred, the external-store thread runtime, the manual createRoot harness, and the per-folder caret, env-var, provider and session fixtures. Left alone on purpose: the store suites' makePrimary, where the vi.mock harness around it is the actual duplication and cannot be hoisted out of a hoisted factory; electron's deferred, where reaching into src/ from the main process would invert the layering for eight lines; and the two suites that compose another hook alongside the stream.
45 lines
1.3 KiB
TypeScript
45 lines
1.3 KiB
TypeScript
import { act, type ReactNode } from 'react'
|
|
import { createRoot, type Root } from 'react-dom/client'
|
|
|
|
/** A React root in a container attached to the document, for the handful of
|
|
* suites that cannot use Testing Library's `render`.
|
|
*
|
|
* They need the component mounted in the real document — a canvas that must
|
|
* paint, an element measured by its parent, a portal that reads
|
|
* `document.body` — and they need to unmount inside `act` mid-test to assert
|
|
* on teardown. Cleanup is idempotent so `afterEach` can call it after a test
|
|
* already did. */
|
|
export function reactRoot() {
|
|
let root: Root | null = null
|
|
let container: HTMLDivElement | null = null
|
|
|
|
return {
|
|
get container() {
|
|
return container
|
|
},
|
|
/** The root itself, for a re-render that has to share an `act` with
|
|
* something else — a mutation-observer callback, a timer flush. */
|
|
get root() {
|
|
return root
|
|
},
|
|
render(ui: ReactNode) {
|
|
container = document.createElement('div')
|
|
document.body.append(container)
|
|
root = createRoot(container)
|
|
|
|
act(() => {
|
|
root!.render(ui)
|
|
})
|
|
},
|
|
unmount() {
|
|
if (root) {
|
|
act(() => root!.unmount())
|
|
}
|
|
|
|
container?.remove()
|
|
root = null
|
|
container = null
|
|
}
|
|
}
|
|
}
|