fix: await concurrent attachment uploads

This commit is contained in:
2026-08-24 01:54:25 +08:00
parent 1ca0249e69
commit 1fe8863e69
3 changed files with 20 additions and 9 deletions

View File

@@ -365,7 +365,7 @@ describe('CodingChatPanel first Conversation', () => {
expect(maxActiveUploads).toBeLessThanOrEqual(4);
});
it('locks attachment removal while an upload is still pending', async () => {
it('keeps attachment removal locked until every upload settles after one fails', async () => {
const upload = deferred<{ attachmentId: string; mime: string; byteLength: number }>();
projectApi.list.mockResolvedValue({ projects: [project], activeProjectId: project.id });
projectApi.config.mockResolvedValue({ project, config });
@@ -383,7 +383,11 @@ describe('CodingChatPanel first Conversation', () => {
runId: 'run-1',
mode: input.mode,
}));
attachmentApi.upload.mockReturnValue(upload.promise);
attachmentApi.upload.mockImplementation((file: File) => (
file.name === 'failed.png'
? Promise.reject(new Error('图片附件无效。'))
: upload.promise
));
const { CodingChatPanel } = await import('@/pages/Chat/CodingChatPanel');
const { createLocalConversationSnapshot } = await import('@/pages/Chat/coding-chat-snapshot');
const { codingConversationStore } = await import('@/stores/coding-conversations');
@@ -393,21 +397,24 @@ describe('CodingChatPanel first Conversation', () => {
await waitFor(() => expect(codingConversationStore.getState().selectedConversationId)
.toBe(conversation.id));
const file = new File(['image'], 'held.png', { type: 'image/png' });
const failed = new File(['failed'], 'failed.png', { type: 'image/png' });
const held = new File(['held'], 'held.png', { type: 'image/png' });
fireEvent.change(screen.getByTestId('coding-file-attachment-input'), {
target: { files: [file] },
target: { files: [failed, held] },
});
fireEvent.click(await screen.findByRole('button', { name: '发送' }));
await waitFor(() => expect(attachmentApi.upload).toHaveBeenCalledOnce());
await waitFor(() => expect(attachmentApi.upload).toHaveBeenCalledTimes(2));
const remove = screen.getByRole('button', { name: '移除图片 held.png' });
expect(remove).toBeDisabled();
fireEvent.click(remove);
expect(screen.getByAltText('held.png')).toBeInTheDocument();
expect(screen.queryByText('图片附件无效。')).not.toBeInTheDocument();
upload.resolve({ attachmentId: 'attachment-held', mime: 'image/png', byteLength: 5 });
await waitFor(() => expect(conversationApi.submit).toHaveBeenCalledOnce());
await waitFor(() => expect(screen.queryByAltText('held.png')).not.toBeInTheDocument());
expect(await screen.findByText('图片附件无效。')).toBeInTheDocument();
expect(remove).toBeEnabled();
expect(conversationApi.submit).not.toHaveBeenCalled();
});
it('keeps next-message images out of a submission while its 202 response is pending', async () => {