fix(coding): reconcile snapshot after abort
This commit is contained in:
@@ -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 });
|
||||
|
||||
@@ -219,6 +219,7 @@ describe('PI-130 feature-complete Coding UI', () => {
|
||||
conversation={null}
|
||||
snapshot={null}
|
||||
onRename={vi.fn(async () => undefined)}
|
||||
onAbort={vi.fn(async () => undefined)}
|
||||
onRecover={vi.fn(async () => undefined)}
|
||||
/>
|
||||
</>,
|
||||
@@ -367,6 +368,7 @@ describe('PI-130 feature-complete Coding UI', () => {
|
||||
interactionApi.thinking.mockResolvedValue({ model: null, modelResolution: 'required' });
|
||||
const callbacks = {
|
||||
rename: vi.fn(async () => undefined),
|
||||
abort: vi.fn(async () => undefined),
|
||||
refresh: vi.fn(async () => undefined),
|
||||
recover: vi.fn(async () => undefined),
|
||||
};
|
||||
@@ -410,6 +412,7 @@ describe('PI-130 feature-complete Coding UI', () => {
|
||||
conversation={conversation}
|
||||
snapshot={snapshot}
|
||||
onRename={callbacks.rename}
|
||||
onAbort={callbacks.abort}
|
||||
onRecover={callbacks.recover}
|
||||
/>
|
||||
<CodingComposer
|
||||
@@ -635,6 +638,7 @@ describe('PI-130 feature-complete Coding UI', () => {
|
||||
conversation={conversation}
|
||||
snapshot={snapshot}
|
||||
onRename={vi.fn(async () => undefined)}
|
||||
onAbort={async () => interactionApi.abort('conversation-uncertain')}
|
||||
onRecover={recover}
|
||||
/>
|
||||
<CodingComposerRuntimeControls
|
||||
|
||||
Reference in New Issue
Block a user