fix(bulk-upload): match error chips by filename, not by chunk position
save() was marking the first N files in a chunk as errored (where N = the error count returned by the backend), but the backend errors are keyed by filename. A failure for file[2] would incorrectly mark file[0] as the error. Now builds a Set of error filenames and matches chunk entries by file.name. Test added: save marks only the file whose filename matches the backend error. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -109,10 +109,14 @@ async function save() {
|
|||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
hadErrors = true;
|
hadErrors = true;
|
||||||
const body = await res.json().catch(() => ({ errors: [] }));
|
const body = await res.json().catch(() => ({ errors: [] }));
|
||||||
const errorCount = (body.errors ?? []).length;
|
const errorFilenames = new Set<string>(
|
||||||
for (let j = 0; j < errorCount && j < chunk.length; j++) {
|
(body.errors ?? []).map((err: { filename: string }) => err.filename)
|
||||||
const e = files.get(chunk[j].id);
|
);
|
||||||
if (e) files.set(chunk[j].id, { ...e, status: 'error' });
|
for (const entry of chunk) {
|
||||||
|
if (errorFilenames.has(entry.file.name)) {
|
||||||
|
const e = files.get(entry.id);
|
||||||
|
if (e) files.set(entry.id, { ...e, status: 'error' });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
chunkProgress = { done: i + 1, total: chunks.length };
|
chunkProgress = { done: i + 1, total: chunks.length };
|
||||||
|
|||||||
@@ -159,6 +159,34 @@ describe('BulkDocumentEditLayout', () => {
|
|||||||
expect(goto).not.toHaveBeenCalled();
|
expect(goto).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('save marks only the file whose filename matches the backend error, not adjacent files', async () => {
|
||||||
|
// backend returns error keyed to b.pdf — only b.pdf chip should get data-status="error"
|
||||||
|
const mockFetch = vi.fn().mockResolvedValue({
|
||||||
|
ok: false,
|
||||||
|
json: async () => ({ errors: [{ filename: 'b.pdf', code: 'FILE_UPLOAD_FAILED' }] })
|
||||||
|
});
|
||||||
|
vi.stubGlobal('fetch', mockFetch);
|
||||||
|
|
||||||
|
const { container } = render(BulkDocumentEditLayout, {});
|
||||||
|
await addFilesViaInput(container, [makeFile('a.pdf'), makeFile('b.pdf'), makeFile('c.pdf')]);
|
||||||
|
|
||||||
|
const saveBtn = container.querySelector(
|
||||||
|
'button[data-testid="bulk-save-btn"]'
|
||||||
|
) as HTMLButtonElement;
|
||||||
|
saveBtn.click();
|
||||||
|
|
||||||
|
await vi.waitFor(() => expect(mockFetch).toHaveBeenCalledTimes(1), { timeout: 3000 });
|
||||||
|
|
||||||
|
await vi.waitFor(
|
||||||
|
() => {
|
||||||
|
const errorChips = container.querySelectorAll('[data-chip-id][data-status="error"]');
|
||||||
|
expect(errorChips.length).toBe(1);
|
||||||
|
expect(errorChips[0].textContent).toContain('b');
|
||||||
|
},
|
||||||
|
{ timeout: 1000 }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it('discard-all resets to N=0 state and shows drop zone', async () => {
|
it('discard-all resets to N=0 state and shows drop zone', async () => {
|
||||||
const { container } = render(BulkDocumentEditLayout, {});
|
const { container } = render(BulkDocumentEditLayout, {});
|
||||||
await addFilesViaInput(container, [makeFile('a.pdf'), makeFile('b.pdf')]);
|
await addFilesViaInput(container, [makeFile('a.pdf'), makeFile('b.pdf')]);
|
||||||
|
|||||||
Reference in New Issue
Block a user