// @vitest-environment jsdom import { act, cleanup, renderHook, waitFor } from '@testing-library/react'; import { afterEach, describe, expect, it, vi } from 'vitest'; import { useDirectThreadChatSubscription } from '../src/view/project-development/chat/controller/useDirectThreadChatSubscription'; import { createTauriEventFake, type TauriEventFake } from './tauriEventFake'; import { resetTauriRuntime } from './tauriRuntimeFake'; const BOOTSTRAP = { subscriptionId: 'direct-subscription-1', lastCompletedItemId: null, events: [], }; let eventFake: TauriEventFake | null = null; afterEach(() => { cleanup(); eventFake?.restore(); eventFake = null; resetTauriRuntime(); delete (window as Window & { __TAURI_INTERNALS__?: unknown }) .__TAURI_INTERNALS__; }); function installEventFake( invoke: (command: string, args?: Record) => Promise, ) { eventFake = createTauriEventFake({ invoke }); eventFake.install(); return eventFake; } describe('useDirectThreadChatSubscription', () => { it('卸载时幂等注销 native subscriber', async () => { const unsubscribeCalls: Record[] = []; const invoke = vi.fn( async (command: string, args?: Record) => { if (command === 'subscribe_direct_project_thread') return BOOTSTRAP; if (command === 'consume_direct_project_thread') return { events: [] }; if (command === 'unsubscribe_direct_project_thread') { unsubscribeCalls.push(args ?? {}); } return null; }, ); installEventFake(invoke); const { unmount } = renderHook(() => useDirectThreadChatSubscription({ enabled: true, projectPath: 'C:/projects/demo', }), ); await waitFor(() => expect(invoke).toHaveBeenCalledWith('subscribe_direct_project_thread', { projectPath: 'C:/projects/demo', }), ); unmount(); await act(async () => { await Promise.resolve(); }); expect(unsubscribeCalls).toEqual([ { subscriptionId: BOOTSTRAP.subscriptionId }, ]); }); it('bootstrap 迟到于卸载时仍注销已登记的 native subscriber', async () => { let resolveBootstrap!: (value: typeof BOOTSTRAP) => void; const unsubscribeCalls: Record[] = []; const invoke = vi.fn( async (command: string, args?: Record) => { if (command === 'subscribe_direct_project_thread') { return new Promise((resolve) => { resolveBootstrap = resolve; }); } if (command === 'unsubscribe_direct_project_thread') { unsubscribeCalls.push(args ?? {}); } return { events: [] }; }, ); installEventFake(invoke); const { unmount } = renderHook(() => useDirectThreadChatSubscription({ enabled: true, projectPath: 'C:/projects/demo', }), ); await waitFor(() => expect(invoke).toHaveBeenCalledWith('subscribe_direct_project_thread', { projectPath: 'C:/projects/demo', }), ); unmount(); await act(async () => { resolveBootstrap(BOOTSTRAP); await Promise.resolve(); }); expect(unsubscribeCalls).toEqual([ { subscriptionId: BOOTSTRAP.subscriptionId }, ]); }); });