/* @vitest-environment jsdom */ import { act, render, screen, waitFor } from '@testing-library/react'; import { useEffect, useRef, useState } from 'react'; import { expect, test, vi } from 'vitest'; import { usePlatformCreationAgentFlowController } from './usePlatformCreationAgentFlowController'; type TestSession = { sessionId: string; messages: Array<{ id: string; role: string; kind?: string; text: string; createdAt?: string; }>; }; type TestMessagePayload = { clientMessageId: string; text: string; }; function TestHarness({ streamMessage, }: { streamMessage: ( sessionId: string, payload: TestMessagePayload, options?: { onUpdate?: (text: string) => void }, ) => Promise; }) { const hasOpenedRef = useRef(false); const flow = usePlatformCreationAgentFlowController< TestSession, Record, { session: TestSession }, TestMessagePayload, { action: string }, { session: TestSession } >({ client: { createSession: async () => ({ session: { sessionId: 'session-1', messages: [], }, }), getSession: async () => ({ session: { sessionId: 'session-1', messages: [], }, }), streamMessage, executeAction: async () => ({ session: { sessionId: 'session-1', messages: [], }, }), selectSession: (response) => response.session, }, createPayload: {}, workspaceStage: 'match3d-agent-workspace', resultStage: 'match3d-result', platformStage: 'platform', isCompileAction: () => false, resolveErrorMessage: (error, fallback) => error instanceof Error ? error.message : fallback, errorMessages: { open: '打开失败', restoreMissingSession: '缺少会话', restore: '恢复失败', submit: '发送失败', execute: '执行失败', }, enterCreateTab: () => {}, setSelectionStage: () => {}, }); useEffect(() => { if (hasOpenedRef.current) { return; } hasOpenedRef.current = true; void flow.openWorkspace({}); }, [flow]); return (
{flow.session?.messages.map((message) => (
{`${message.role}:${message.kind}:${message.text}`}
))}
{flow.error ?
{flow.error}
: null}
); } type ActionTestSession = TestSession & { draft?: { profileId: string } | null; }; function ActionErrorHarness({ onActionError, }: { onActionError: (params: { session: ActionTestSession; setSession: (session: ActionTestSession) => void; }) => void; }) { const [stage, setStage] = useState('platform'); const hasOpenedRef = useRef(false); const flow = usePlatformCreationAgentFlowController< ActionTestSession, Record, { session: ActionTestSession }, TestMessagePayload, { action: string }, { session: ActionTestSession } >({ client: { createSession: async () => ({ session: { sessionId: 'session-1', messages: [], draft: { profileId: 'profile-draft-1' }, }, }), getSession: async () => ({ session: { sessionId: 'session-1', messages: [], draft: { profileId: 'profile-draft-1' }, }, }), streamMessage: async () => ({ sessionId: 'session-1', messages: [], draft: { profileId: 'profile-draft-1' }, }), executeAction: async () => { throw new Error('模型生成失败'); }, selectSession: (response) => response.session, }, createPayload: {}, workspaceStage: 'match3d-agent-workspace', resultStage: 'match3d-result', platformStage: 'platform', isCompileAction: () => true, resolveErrorMessage: (error, fallback) => error instanceof Error ? error.message : fallback, errorMessages: { open: '打开失败', restoreMissingSession: '缺少会话', restore: '恢复失败', submit: '发送失败', execute: '执行失败', }, enterCreateTab: () => {}, setSelectionStage: setStage, onActionError: ({ session, setSession }) => { onActionError({ session, setSession }); }, }); useEffect(() => { if (hasOpenedRef.current) { return; } hasOpenedRef.current = true; void flow.openWorkspace({}); }, [flow]); return (
{stage}
{flow.session?.draft?.profileId ?? 'missing'}
{flow.error ?
{flow.error}
: null}
); } function ActionCompleteHarness({ onActionComplete, }: { onActionComplete: () => { openResult?: boolean } | void; }) { const [stage, setStage] = useState('platform'); const hasOpenedRef = useRef(false); const flow = usePlatformCreationAgentFlowController< ActionTestSession, Record, { session: ActionTestSession }, TestMessagePayload, { action: string }, { session: ActionTestSession } >({ client: { createSession: async () => ({ session: { sessionId: 'session-1', messages: [], draft: { profileId: 'profile-draft-1' }, }, }), getSession: async () => ({ session: { sessionId: 'session-1', messages: [], draft: { profileId: 'profile-draft-1' }, }, }), streamMessage: async () => ({ sessionId: 'session-1', messages: [], draft: { profileId: 'profile-draft-1' }, }), executeAction: async () => ({ session: { sessionId: 'session-1', messages: [], draft: { profileId: 'profile-ready-1' }, }, }), selectSession: (response) => response.session, }, createPayload: {}, workspaceStage: 'match3d-agent-workspace', resultStage: 'match3d-result', platformStage: 'platform', isCompileAction: () => true, resolveErrorMessage: (error, fallback) => error instanceof Error ? error.message : fallback, errorMessages: { open: '打开失败', restoreMissingSession: '缺少会话', restore: '恢复失败', submit: '发送失败', execute: '执行失败', }, enterCreateTab: () => {}, setSelectionStage: setStage, onActionComplete: ({ setSession, response }) => { setSession(response.session); return onActionComplete(); }, }); useEffect(() => { if (hasOpenedRef.current) { return; } hasOpenedRef.current = true; void flow.openWorkspace({}); }, [flow]); return (
{stage}
{flow.session?.draft?.profileId ?? 'missing'}
); } test('creation agent flow preserves streamed assistant text when stream fails', async () => { const streamMessage = vi.fn(async (_sessionId, _payload, options) => { options?.onUpdate?.('先把方洞万能的反差定住。'); throw new Error('方洞挑战聊天生成失败:LLM 请求超时'); }); render(); await waitFor(() => { expect(screen.getByRole('button', { name: '发送' })).toBeTruthy(); }); await act(async () => { screen.getByRole('button', { name: '发送' }).click(); }); await waitFor(() => { expect(screen.getByText('方洞挑战聊天生成失败:LLM 请求超时')).toBeTruthy(); }); expect(screen.getByTestId('messages').textContent).toContain( 'user:chat:做一个办公室文具方洞挑战', ); expect(screen.getByTestId('messages').textContent).toContain( 'assistant:warning:先把方洞万能的反差定住。', ); }); test('creation agent flow exposes session setter after compile action fails', async () => { const onActionError = vi.fn( ({ setSession, }: { session: ActionTestSession; setSession: (session: ActionTestSession) => void; }) => { setSession({ sessionId: 'session-1', messages: [], draft: { profileId: 'profile-after-failure' }, }); }, ); render(); await waitFor(() => { expect(screen.getByRole('button', { name: '执行' })).toBeTruthy(); }); await act(async () => { screen.getByRole('button', { name: '执行' }).click(); }); await waitFor(() => { expect(screen.getByText('模型生成失败')).toBeTruthy(); }); expect(onActionError).toHaveBeenCalledTimes(1); expect(screen.getByTestId('profile').textContent).toBe( 'profile-after-failure', ); expect(screen.getByTestId('stage').textContent).toBe( 'match3d-agent-workspace', ); }); test('creation agent flow suppresses compile result stage for background completion', async () => { const onActionComplete = vi.fn(() => ({ openResult: false })); render(); await waitFor(() => { expect(screen.getByTestId('stage').textContent).toBe( 'match3d-agent-workspace', ); }); await act(async () => { screen.getByRole('button', { name: '执行' }).click(); }); await waitFor(() => { expect(onActionComplete).toHaveBeenCalledTimes(1); }); expect(screen.getByTestId('profile').textContent).toBe('profile-ready-1'); expect(screen.getByTestId('stage').textContent).toBe( 'match3d-agent-workspace', ); });