rollback optimistic message on send failure
This commit is contained in:
+63
@@ -446,6 +446,69 @@ describe('EditorAgentConversationPanelView', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('restores the draft and selected attachments when sending fails', async () => {
|
||||
const client = createClient();
|
||||
vi.mocked(client.sendMessage).mockRejectedValueOnce(
|
||||
new Error('Network error'),
|
||||
);
|
||||
|
||||
render(
|
||||
<EditorAgentConversationPanelView
|
||||
open
|
||||
onToggleOpen={vi.fn()}
|
||||
client={client}
|
||||
layers={[
|
||||
{
|
||||
id: 'layer-1',
|
||||
resourceId: 'resource-1',
|
||||
title: '角色图层',
|
||||
src: '/generated/role.png',
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 512,
|
||||
height: 512,
|
||||
originalWidth: 512,
|
||||
originalHeight: 512,
|
||||
zIndex: 1,
|
||||
sourceType: 'generated',
|
||||
},
|
||||
]}
|
||||
/>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('已经看到画布内容')).toBeTruthy();
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '添加附件' }));
|
||||
const attachmentDialog = screen.getByRole('dialog', {
|
||||
name: '选择图片附件',
|
||||
});
|
||||
fireEvent.click(
|
||||
within(attachmentDialog).getByRole('checkbox', {
|
||||
name: '选择画布图片 角色图层',
|
||||
}),
|
||||
);
|
||||
fireEvent.click(
|
||||
within(attachmentDialog).getByRole('button', { name: '应用' }),
|
||||
);
|
||||
fireEvent.change(screen.getByLabelText('发送给画布 Agent'), {
|
||||
target: { value: '失败后恢复这条草稿' },
|
||||
});
|
||||
fireEvent.click(screen.getByRole('button', { name: '发送' }));
|
||||
|
||||
expect(await screen.findByText('Network error')).toBeTruthy();
|
||||
expect(
|
||||
(screen.getByLabelText('发送给画布 Agent') as HTMLTextAreaElement).value,
|
||||
).toBe('失败后恢复这条草稿');
|
||||
expect(screen.getByText('角色图层')).toBeTruthy();
|
||||
expect(
|
||||
within(screen.getByRole('log', { name: '画布 Agent 消息流' })).queryByText(
|
||||
'失败后恢复这条草稿',
|
||||
),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it('preserves messages when the panel is collapsed and reopened', async () => {
|
||||
const client = createClient();
|
||||
vi.mocked(client.sendMessage).mockResolvedValue({
|
||||
|
||||
+5
-2
@@ -553,7 +553,7 @@ describe('useEditorAgentConversation', () => {
|
||||
expect(onCanvasRefreshRequested).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('handles fetch error as failed stage', async () => {
|
||||
it('rethrows fetch errors and rolls back the optimistic message', async () => {
|
||||
const client = createClient();
|
||||
vi.mocked(client.sendMessage).mockRejectedValue(new Error('Network error'));
|
||||
const { result } = renderHook(() =>
|
||||
@@ -567,10 +567,13 @@ describe('useEditorAgentConversation', () => {
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await expect(result.current.sendMessage('test')).resolves.toBeUndefined();
|
||||
await expect(result.current.sendMessage('test')).rejects.toThrow(
|
||||
'Network error',
|
||||
);
|
||||
});
|
||||
|
||||
expect(result.current.errorMessage).toBe('Network error');
|
||||
expect(result.current.messages).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('aborts the active request and resets state on stopCurrentTurn', async () => {
|
||||
|
||||
@@ -355,13 +355,14 @@ export function useEditorAgentConversation({
|
||||
activeRequestAbortControllerRef.current = abortController;
|
||||
setErrorMessage(null);
|
||||
setIsWaiting(true);
|
||||
const optimisticMessage = createLocalUserMessage({
|
||||
id: -1,
|
||||
text,
|
||||
attachments,
|
||||
});
|
||||
setMessages((currentMessages) => [
|
||||
...currentMessages,
|
||||
createLocalUserMessage({
|
||||
id: -1, // does not care
|
||||
text,
|
||||
attachments,
|
||||
}),
|
||||
optimisticMessage,
|
||||
]);
|
||||
|
||||
try {
|
||||
@@ -394,6 +395,10 @@ export function useEditorAgentConversation({
|
||||
const message =
|
||||
error instanceof Error ? error.message : '发送画布 Agent 消息失败';
|
||||
setErrorMessage(message);
|
||||
setMessages((currentMessages) =>
|
||||
currentMessages.filter((message) => message !== optimisticMessage),
|
||||
);
|
||||
throw error;
|
||||
} finally {
|
||||
if (activeRequestAbortControllerRef.current === abortController) {
|
||||
activeRequestAbortControllerRef.current = null;
|
||||
|
||||
Reference in New Issue
Block a user