// @vitest-environment jsdom import { act } from 'react'; import { createRoot, type Root } from 'react-dom/client'; import { afterEach, describe, expect, it, vi } from 'vitest'; import type { DaemonTranscriptBlock } from '@qwen-code/sdk/daemon'; import { useAnimationFrameTranscriptBlocks } from './useAnimationFrameTranscriptBlocks'; Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true }); const testStore = vi.hoisted(() => { let blocks: readonly DaemonTranscriptBlock[] = []; const listeners = new Set<() => void>(); return { getSnapshot: () => ({ blocks }), subscribe: (listener: () => void) => { listeners.add(listener); return () => listeners.delete(listener); }, update(nextBlocks: readonly DaemonTranscriptBlock[]) { blocks = nextBlocks; listeners.forEach((listener) => listener()); }, reset() { blocks = []; listeners.clear(); }, }; }); vi.mock('@qwen-code/webui/daemon-react-sdk', () => ({ useTranscriptStore: () => testStore, })); let root: Root | null = null; let container: HTMLDivElement | null = null; let renderCount = 0; let latestBlocks: readonly DaemonTranscriptBlock[] = []; function Harness() { latestBlocks = useAnimationFrameTranscriptBlocks(); renderCount += 1; return null; } afterEach(() => { act(() => root?.unmount()); container?.remove(); root = null; container = null; renderCount = 0; latestBlocks = []; testStore.reset(); vi.restoreAllMocks(); }); describe('useAnimationFrameTranscriptBlocks', () => { it('coalesces transcript notifications into one render per frame', () => { let pendingFrame: FrameRequestCallback | null = null; vi.spyOn(window, 'requestAnimationFrame').mockImplementation((callback) => { pendingFrame = callback; return 1; }); container = document.createElement('div'); document.body.append(container); root = createRoot(container); act(() => root!.render()); const initialRenderCount = renderCount; act(() => { for (let index = 1; index <= 100; index++) { testStore.update( Array.from({ length: index }, () => ({}) as DaemonTranscriptBlock), ); } }); expect(renderCount).toBe(initialRenderCount); expect(pendingFrame).not.toBeNull(); act(() => { pendingFrame?.(performance.now()); }); expect(renderCount).toBe(initialRenderCount + 1); expect(latestBlocks).toHaveLength(100); }); it('cancels a pending frame on unmount', () => { vi.spyOn(window, 'requestAnimationFrame').mockReturnValue(7); const cancelFrame = vi.spyOn(window, 'cancelAnimationFrame'); container = document.createElement('div'); document.body.append(container); root = createRoot(container); act(() => root!.render()); act(() => testStore.update([{} as DaemonTranscriptBlock])); act(() => root!.unmount()); root = null; expect(cancelFrame).toHaveBeenCalledWith(7); }); });