fix(coding): reconcile snapshot after abort

This commit is contained in:
2026-09-01 15:17:45 +08:00
parent 143aaec3d6
commit d2ef37bc4d
6 changed files with 165 additions and 13 deletions

View File

@@ -695,6 +695,45 @@ describe('CodingChatPanel first Conversation', () => {
expect(screen.getByRole('button', { name: '发送' })).toBeEnabled();
});
it('refreshes the authoritative Snapshot after abort when the terminal SSE patch was missed', async () => {
projectApi.list.mockResolvedValue({ projects: [project], activeProjectId: project.id });
projectApi.config.mockResolvedValue({ project, config });
projectApi.conversations.mockResolvedValue([conversation]);
conversationApi.events.mockResolvedValue(new FakeEventSource() as unknown as EventSource);
conversationApi.recover.mockResolvedValue(undefined);
conversationApi.abort.mockResolvedValue(undefined);
const { CodingChatPanel } = await import('@/pages/Chat/CodingChatPanel');
const { createLocalConversationSnapshot } = await import('@/pages/Chat/coding-chat-snapshot');
const runningSnapshot: ConversationSnapshot = {
...createLocalConversationSnapshot(project.id, conversation),
worker: { status: 'ready', generation: 1 },
run: { status: 'running', runId: 'run-stale', mode: 'prompt' },
cursor: { workerGeneration: 1, seq: 7 },
};
const settledSnapshot: ConversationSnapshot = {
...runningSnapshot,
run: {
status: 'idle',
runId: 'run-stale',
mode: 'prompt',
settledAt: 12_000,
terminalReason: 'completed',
},
cursor: { workerGeneration: 1, seq: 8 },
};
conversationApi.snapshot
.mockResolvedValueOnce(runningSnapshot)
.mockResolvedValueOnce(settledSnapshot);
render(<CodingChatPanel />);
const abortButton = await screen.findByRole('button', { name: '中止生成' });
fireEvent.click(abortButton);
await waitFor(() => expect(conversationApi.abort).toHaveBeenCalledWith(conversation.id));
await waitFor(() => expect(conversationApi.snapshot).toHaveBeenCalledTimes(2));
expect(await screen.findByRole('button', { name: '发送' })).toBeInTheDocument();
});
it('caps one message at 16 images and uploads at most four concurrently', async () => {
projectApi.list.mockResolvedValue({ projects: [project], activeProjectId: project.id });
projectApi.config.mockResolvedValue({ project, config });