修复澄清自由输入把整页打进 error boundary
澄清卡片的自由回答输入框在 setAnswers 的 updater 里读 event.currentTarget.value。React 在事件派发结束后会把 currentTarget 置空,而 updater 要等渲染阶段才跑,读到的就是 null,抛出 "Cannot read properties of null (reading 'value')"。异常出在渲染阶段, 被 AuthenticatedClient 的 error boundary 接住,整页被换成 "客户端页面加载失败"。 手动输入、以及先选中选项再删除填充文本,走的都是这一个 onChange。 改成在事件里先取出 value 再交给 updater。新增的回归测试在 StrictMode 下渲染卡片(与 main.tsx 一致),覆盖这两条路径。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -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,
|
||||
}));
|
||||
}}
|
||||
/>
|
||||
</fieldset>
|
||||
);
|
||||
|
||||
@@ -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(
|
||||
<StrictMode>
|
||||
<AgentRuntimeUserInputCard
|
||||
request={clarificationRequest()}
|
||||
controlBusy={false}
|
||||
/>
|
||||
</StrictMode>,
|
||||
);
|
||||
|
||||
const textarea = screen.getByLabelText('第1轮·关键决定 其他回答');
|
||||
fireEvent.change(textarea, { target: { value: '玩家自己写的答案' } });
|
||||
|
||||
expect((textarea as HTMLTextAreaElement).value).toBe('玩家自己写的答案');
|
||||
});
|
||||
|
||||
it('先选中选项再清空填充文本不会让页面崩溃', () => {
|
||||
render(
|
||||
<StrictMode>
|
||||
<AgentRuntimeUserInputCard
|
||||
request={clarificationRequest()}
|
||||
controlBusy={false}
|
||||
/>
|
||||
</StrictMode>,
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByText('分数驱动'));
|
||||
const textarea = screen.getByLabelText(
|
||||
'第1轮·关键决定 其他回答',
|
||||
) as HTMLTextAreaElement;
|
||||
expect(textarea.value).toBe('分数驱动');
|
||||
|
||||
fireEvent.change(textarea, { target: { value: '' } });
|
||||
expect(textarea.value).toBe('');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user