diff --git a/apps/ai-game-creator-shell/src/features/agent-runtime/panels.tsx b/apps/ai-game-creator-shell/src/features/agent-runtime/panels.tsx index 02ab29137..040b0b0dc 100644 --- a/apps/ai-game-creator-shell/src/features/agent-runtime/panels.tsx +++ b/apps/ai-game-creator-shell/src/features/agent-runtime/panels.tsx @@ -251,12 +251,15 @@ export function AgentRuntimeUserInputCard({ placeholder="填写其他答案" rows={2} value={answer} - onChange={(event) => + onChange={(event) => { + // React 会在事件派发结束后把 currentTarget 置空,而 setState 的 + // updater 要等到渲染阶段才跑,所以必须在这里先取出值。 + const { value } = event.currentTarget; setAnswers((current) => ({ ...current, - [question.id]: event.currentTarget.value, - })) - } + [question.id]: value, + })); + }} /> ); diff --git a/apps/ai-game-creator-shell/tests/agentRuntimeUserInputCard.test.tsx b/apps/ai-game-creator-shell/tests/agentRuntimeUserInputCard.test.tsx new file mode 100644 index 000000000..cc53b7efb --- /dev/null +++ b/apps/ai-game-creator-shell/tests/agentRuntimeUserInputCard.test.tsx @@ -0,0 +1,74 @@ +// @vitest-environment jsdom + +import { fireEvent, render, screen } from '@testing-library/react'; +import { StrictMode } from 'react'; +import { describe, expect, it } from 'vitest'; + +import type { AgentRuntimeUserInputRequest } from '../src/app/types'; +import { AgentRuntimeUserInputCard } from '../src/features/agent-runtime/panels'; + +function clarificationRequest(): AgentRuntimeUserInputRequest { + return { + schemaVersion: 'agc.user_input_request.v1', + requestId: 'req-1', + agentId: 'project-supervisor', + taskId: 'task-1', + sessionId: 'session-1', + runId: 'run-1', + actionId: 'action-1', + status: 'pending', + questions: [ + { + id: 'q1', + header: '第1轮·关键决定', + question: '这局游戏的重玩动力是什么?', + options: [ + { label: '分数驱动', description: '刷新纪录后重开' }, + { label: '成长驱动', description: '解锁新能力后重开' }, + ], + }, + ], + allowFreeform: true, + responseId: null, + requestedAt: 1, + updatedAt: 1, + }; +} + +describe('AgentRuntimeUserInputCard 澄清输入', () => { + it('手动输入自由回答不会让页面崩溃', () => { + render( + + + , + ); + + const textarea = screen.getByLabelText('第1轮·关键决定 其他回答'); + fireEvent.change(textarea, { target: { value: '玩家自己写的答案' } }); + + expect((textarea as HTMLTextAreaElement).value).toBe('玩家自己写的答案'); + }); + + it('先选中选项再清空填充文本不会让页面崩溃', () => { + render( + + + , + ); + + fireEvent.click(screen.getByText('分数驱动')); + const textarea = screen.getByLabelText( + '第1轮·关键决定 其他回答', + ) as HTMLTextAreaElement; + expect(textarea.value).toBe('分数驱动'); + + fireEvent.change(textarea, { target: { value: '' } }); + expect(textarea.value).toBe(''); + }); +});