544 lines
19 KiB
TypeScript
544 lines
19 KiB
TypeScript
import { describe, it, expect, vi, afterEach } from 'vitest';
|
|
import { goto } from '$app/navigation';
|
|
import { cleanup, render } from 'vitest-browser-svelte';
|
|
import { page, userEvent } from 'vitest/browser';
|
|
import BulkDocumentEditLayout from './BulkDocumentEditLayout.svelte';
|
|
|
|
vi.mock('$app/navigation', () => ({ goto: vi.fn() }));
|
|
|
|
afterEach(() => {
|
|
cleanup();
|
|
vi.unstubAllGlobals();
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
function makeFile(name: string): File {
|
|
return new File(['content'], name, { type: 'application/pdf' });
|
|
}
|
|
|
|
async function addFilesViaInput(container: HTMLElement, files: File[]): Promise<void> {
|
|
const input = container.querySelector('input[type="file"]') as HTMLInputElement;
|
|
if (!input) throw new Error('No file input found — is BulkDropZone visible?');
|
|
await userEvent.upload(input, files);
|
|
}
|
|
|
|
describe('BulkDocumentEditLayout', () => {
|
|
it('N=0: shows BulkDropZone', async () => {
|
|
render(BulkDocumentEditLayout, {});
|
|
await expect.element(page.getByTestId('bulk-drop-zone')).toBeInTheDocument();
|
|
});
|
|
|
|
it('N=1: file-switcher-strip and per-file scope card are absent', async () => {
|
|
const { container } = render(BulkDocumentEditLayout, {});
|
|
await addFilesViaInput(container, [makeFile('doc.pdf')]);
|
|
expect(container.querySelector('[data-testid="file-switcher-strip"]')).toBeNull();
|
|
expect(container.querySelector('[data-variant="per-file"]')).toBeNull();
|
|
});
|
|
|
|
it('N=5: file-switcher-strip and per-file scope card are both present', async () => {
|
|
const { container } = render(BulkDocumentEditLayout, {});
|
|
await addFilesViaInput(container, [
|
|
makeFile('a.pdf'),
|
|
makeFile('b.pdf'),
|
|
makeFile('c.pdf'),
|
|
makeFile('d.pdf'),
|
|
makeFile('e.pdf')
|
|
]);
|
|
expect(container.querySelector('[data-testid="file-switcher-strip"]')).not.toBeNull();
|
|
expect(container.querySelector('[data-variant="per-file"]')).not.toBeNull();
|
|
});
|
|
|
|
it('removing middle file preserves order of remaining files', async () => {
|
|
const { container } = render(BulkDocumentEditLayout, {});
|
|
await addFilesViaInput(container, [
|
|
makeFile('file0.pdf'),
|
|
makeFile('file1.pdf'),
|
|
makeFile('file2.pdf')
|
|
]);
|
|
|
|
// Remove the chip for file1 via its remove button (identified by data-remove-id)
|
|
const removeButtons = container.querySelectorAll<HTMLButtonElement>(
|
|
'[data-testid="file-switcher-strip"] button[data-remove-id]'
|
|
);
|
|
expect(removeButtons.length).toBe(3);
|
|
removeButtons[1].click(); // remove file1
|
|
|
|
// Wait for Svelte to flush the DOM update
|
|
await vi.waitFor(
|
|
() => {
|
|
const chips = container.querySelectorAll(
|
|
'[data-testid="file-switcher-strip"] [data-chip-id]'
|
|
);
|
|
expect(chips.length).toBe(2);
|
|
expect(chips[0].textContent?.trim()).toContain('file0');
|
|
expect(chips[1].textContent?.trim()).toContain('file2');
|
|
},
|
|
{ timeout: 1000 }
|
|
);
|
|
});
|
|
|
|
it('save calls fetch twice for 12 files (2 chunks of 10)', async () => {
|
|
const mockFetch = vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
json: async () => ({ created: [], updated: [], errors: [] })
|
|
});
|
|
vi.stubGlobal('fetch', mockFetch);
|
|
|
|
const { container } = render(BulkDocumentEditLayout, {});
|
|
const files = Array.from({ length: 12 }, (_, i) => makeFile(`f${i}.pdf`));
|
|
await addFilesViaInput(container, files);
|
|
|
|
const saveBtn = container.querySelector(
|
|
'button[data-testid="bulk-save-btn"]'
|
|
) as HTMLButtonElement;
|
|
expect(saveBtn).not.toBeNull();
|
|
saveBtn.click();
|
|
|
|
// Wait for async save to complete
|
|
await vi.waitFor(() => expect(mockFetch).toHaveBeenCalledTimes(2), { timeout: 3000 });
|
|
});
|
|
|
|
it('save marks file as error when server returns non-ok response', async () => {
|
|
const mockFetch = vi.fn().mockResolvedValue({
|
|
ok: false,
|
|
json: async () => ({ errors: [{ filename: 'f0.pdf', code: 'FILE_UPLOAD_FAILED' }] })
|
|
});
|
|
vi.stubGlobal('fetch', mockFetch);
|
|
|
|
const { container } = render(BulkDocumentEditLayout, {});
|
|
await addFilesViaInput(container, [makeFile('f0.pdf')]);
|
|
|
|
const saveBtn = container.querySelector(
|
|
'button[data-testid="bulk-save-btn"]'
|
|
) as HTMLButtonElement;
|
|
saveBtn.click();
|
|
|
|
await vi.waitFor(() => expect(mockFetch).toHaveBeenCalledTimes(1), { timeout: 3000 });
|
|
});
|
|
|
|
it('save() includes tagNames in metadata payload', async () => {
|
|
let capturedFormData: FormData | undefined;
|
|
const mockFetch = vi.fn().mockImplementation(async (_url: string, init: RequestInit) => {
|
|
capturedFormData = init?.body as FormData;
|
|
return { ok: true, json: async () => ({ created: [], updated: [], errors: [] }) };
|
|
});
|
|
vi.stubGlobal('fetch', mockFetch);
|
|
|
|
const { container } = render(BulkDocumentEditLayout, {});
|
|
await addFilesViaInput(container, [makeFile('doc.pdf')]);
|
|
|
|
const saveBtn = container.querySelector(
|
|
'button[data-testid="bulk-save-btn"]'
|
|
) as HTMLButtonElement;
|
|
saveBtn.click();
|
|
|
|
await vi.waitFor(() => expect(mockFetch).toHaveBeenCalledTimes(1), { timeout: 3000 });
|
|
|
|
expect(capturedFormData).toBeDefined();
|
|
const metadataBlob = capturedFormData!.get('metadata') as Blob;
|
|
const metadataJson = JSON.parse(await metadataBlob.text());
|
|
expect(metadataJson).toHaveProperty('tagNames');
|
|
});
|
|
|
|
it('save() navigates to /documents when all chunks succeed', async () => {
|
|
const mockFetch = vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
json: async () => ({ created: [], updated: [], errors: [] })
|
|
});
|
|
vi.stubGlobal('fetch', mockFetch);
|
|
|
|
const { container } = render(BulkDocumentEditLayout, {});
|
|
await addFilesViaInput(container, [makeFile('doc.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(() => expect(goto).toHaveBeenCalledWith('/documents'), { timeout: 3000 });
|
|
});
|
|
|
|
it('save() does not navigate when chunk returns non-ok response', async () => {
|
|
const mockFetch = vi.fn().mockResolvedValue({
|
|
ok: false,
|
|
json: async () => ({ errors: [{ filename: 'f0.pdf', code: 'FILE_UPLOAD_FAILED' }] })
|
|
});
|
|
vi.stubGlobal('fetch', mockFetch);
|
|
|
|
const { container } = render(BulkDocumentEditLayout, {});
|
|
await addFilesViaInput(container, [makeFile('f0.pdf')]);
|
|
|
|
const saveBtn = container.querySelector(
|
|
'button[data-testid="bulk-save-btn"]'
|
|
) as HTMLButtonElement;
|
|
saveBtn.click();
|
|
|
|
await vi.waitFor(() => expect(mockFetch).toHaveBeenCalledTimes(1), { timeout: 3000 });
|
|
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('save() marks only the failed file when server returns HTTP 200 with a partial errors array', async () => {
|
|
// Backend can return 200 OK while reporting individual file failures
|
|
const mockFetch = vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
json: async () => ({
|
|
created: [{ id: '1' }],
|
|
updated: [],
|
|
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 }
|
|
);
|
|
// Navigation should be suppressed because hadErrors is true
|
|
expect(goto).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('save() marks all chunk files as errored when fetch throws a network error', async () => {
|
|
vi.stubGlobal('fetch', vi.fn().mockRejectedValue(new Error('network error')));
|
|
|
|
const { container } = render(BulkDocumentEditLayout, {});
|
|
await addFilesViaInput(container, [makeFile('a.pdf'), makeFile('b.pdf')]);
|
|
|
|
const saveBtn = container.querySelector(
|
|
'button[data-testid="bulk-save-btn"]'
|
|
) as HTMLButtonElement;
|
|
saveBtn.click();
|
|
|
|
await vi.waitFor(
|
|
() => {
|
|
const errorChips = container.querySelectorAll('[data-chip-id][data-status="error"]');
|
|
expect(errorChips.length).toBe(2);
|
|
},
|
|
{ timeout: 3000 }
|
|
);
|
|
expect(goto).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('save() does not call fetch a second time when already saving', async () => {
|
|
let resolveFirst: (() => void) | undefined;
|
|
const mockFetch = vi.fn().mockImplementation(
|
|
() =>
|
|
new Promise<Response>((resolve) => {
|
|
resolveFirst = () =>
|
|
resolve({
|
|
ok: true,
|
|
json: async () => ({ created: [], updated: [], errors: [] })
|
|
} as Response);
|
|
})
|
|
);
|
|
vi.stubGlobal('fetch', mockFetch);
|
|
|
|
const { container } = render(BulkDocumentEditLayout, {});
|
|
await addFilesViaInput(container, [makeFile('a.pdf')]);
|
|
|
|
const saveBtn = container.querySelector(
|
|
'button[data-testid="bulk-save-btn"]'
|
|
) as HTMLButtonElement;
|
|
saveBtn.click(); // first click — fetch is in-flight
|
|
saveBtn.click(); // second click — should be a no-op
|
|
|
|
resolveFirst?.();
|
|
await vi.waitFor(() => expect(mockFetch).toHaveBeenCalledTimes(1), { timeout: 3000 });
|
|
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('discard-all resets to N=0 state and shows drop zone', async () => {
|
|
const { container } = render(BulkDocumentEditLayout, {});
|
|
await addFilesViaInput(container, [makeFile('a.pdf'), makeFile('b.pdf')]);
|
|
|
|
// Confirm N=2 state — switcher is visible
|
|
expect(container.querySelector('[data-testid="file-switcher-strip"]')).not.toBeNull();
|
|
|
|
// Click the topbar discard-all button (only visible in isMulti state)
|
|
const discardBtn = container.querySelector(
|
|
'button[data-testid="discard-all-btn"]'
|
|
) as HTMLButtonElement;
|
|
expect(discardBtn).not.toBeNull();
|
|
discardBtn.click();
|
|
|
|
await vi.waitFor(
|
|
() => {
|
|
expect(container.querySelector('[data-testid="bulk-drop-zone"]')).not.toBeNull();
|
|
expect(container.querySelector('[data-testid="file-switcher-strip"]')).toBeNull();
|
|
},
|
|
{ timeout: 1000 }
|
|
);
|
|
});
|
|
});
|
|
|
|
// ─── mode="edit" ─────────────────────────────────────────────────────────────
|
|
|
|
describe('BulkDocumentEditLayout — mode="edit" discard', () => {
|
|
it('discard in edit mode clears the selection store and navigates back to /documents', async () => {
|
|
const { bulkSelectionStore } = await import('$lib/stores/bulkSelection.svelte');
|
|
bulkSelectionStore.setAll(['doc-1']);
|
|
|
|
const { container } = render(BulkDocumentEditLayout, {
|
|
mode: 'edit',
|
|
initialEditEntries: [
|
|
{ id: 'doc-1', title: 'Brief 1', pdfUrl: '/api/documents/doc-1/file' },
|
|
{ id: 'doc-2', title: 'Brief 2', pdfUrl: '/api/documents/doc-2/file' }
|
|
]
|
|
});
|
|
|
|
const discardBtn = container.querySelector(
|
|
'button[data-testid="discard-all-btn"]'
|
|
) as HTMLButtonElement;
|
|
expect(discardBtn).not.toBeNull();
|
|
discardBtn.click();
|
|
|
|
await vi.waitFor(() => expect(goto).toHaveBeenCalledWith('/documents'), { timeout: 1000 });
|
|
expect(bulkSelectionStore.size).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe('BulkDocumentEditLayout — mode="edit"', () => {
|
|
const editEntry = (i: number) => ({
|
|
id: `doc-${i}`,
|
|
title: `Brief ${i}`,
|
|
pdfUrl: `/api/documents/doc-${i}/file`
|
|
});
|
|
|
|
it('does not render the BulkDropZone in edit mode', async () => {
|
|
const { container } = render(BulkDocumentEditLayout, {
|
|
mode: 'edit',
|
|
initialEditEntries: [editEntry(1)]
|
|
});
|
|
expect(container.querySelector('[data-testid="bulk-drop-zone"]')).toBeNull();
|
|
});
|
|
|
|
it('renders the onboarding callout with role=note in edit mode', async () => {
|
|
render(BulkDocumentEditLayout, {
|
|
mode: 'edit',
|
|
initialEditEntries: [editEntry(1)]
|
|
});
|
|
const callout = page.getByTestId('bulk-edit-callout');
|
|
await expect.element(callout).toBeInTheDocument();
|
|
await expect.element(callout).toHaveAttribute('role', 'note');
|
|
});
|
|
|
|
it('renders read-only title display (no input) in edit mode', async () => {
|
|
const { container } = render(BulkDocumentEditLayout, {
|
|
mode: 'edit',
|
|
initialEditEntries: [editEntry(1)]
|
|
});
|
|
expect(container.querySelector('[data-testid="readonly-title"]')).not.toBeNull();
|
|
// Per-file ScopeCard absent at N=1 — title rendered in the single card
|
|
const titleInput = container.querySelector('input[type="text"][value="Brief 1"]');
|
|
expect(titleInput).toBeNull();
|
|
});
|
|
|
|
it('hides the date field via WhoWhenSection hideDate prop', async () => {
|
|
const { container } = render(BulkDocumentEditLayout, {
|
|
mode: 'edit',
|
|
initialEditEntries: [editEntry(1)]
|
|
});
|
|
expect(container.querySelector('[data-testid="who-when-date"]')).toBeNull();
|
|
});
|
|
|
|
it('shows additive badge next to tags label', async () => {
|
|
const { container } = render(BulkDocumentEditLayout, {
|
|
mode: 'edit',
|
|
initialEditEntries: [editEntry(1)]
|
|
});
|
|
expect(container.querySelector('[data-testid="field-label-badge-additive"]')).not.toBeNull();
|
|
});
|
|
|
|
it('shows replace badges next to sender and archive fields', async () => {
|
|
const { container } = render(BulkDocumentEditLayout, {
|
|
mode: 'edit',
|
|
initialEditEntries: [editEntry(1)]
|
|
});
|
|
const replaceBadges = container.querySelectorAll('[data-testid="field-label-badge-replace"]');
|
|
// sender + archiveBox + archiveFolder = 3
|
|
expect(replaceBadges.length).toBeGreaterThanOrEqual(3);
|
|
});
|
|
|
|
it('topbar reads "Massenbearbeitung" + "{count} werden bearbeitet" in edit mode', async () => {
|
|
// Elicit C1 fix — upload-flavoured "Mehrere Dokumente hochladen" /
|
|
// "werden erstellt" copy must not appear when mode === 'edit'.
|
|
const { container } = render(BulkDocumentEditLayout, {
|
|
mode: 'edit',
|
|
initialEditEntries: [editEntry(1), editEntry(2)]
|
|
});
|
|
// Topbar title slot
|
|
const topbar = container.querySelector('span.font-bold.text-ink');
|
|
expect(topbar?.textContent).toContain('Massenbearbeitung');
|
|
// Count pill
|
|
const pill = container.querySelector('span.bg-accent');
|
|
expect(pill?.textContent).toContain('werden bearbeitet');
|
|
// Negative: must NOT show upload-flavoured copy
|
|
expect(topbar?.textContent ?? '').not.toContain('hochladen');
|
|
expect(pill?.textContent ?? '').not.toContain('werden erstellt');
|
|
});
|
|
|
|
it('shows the archiveBox and archiveFolder bulk-only inputs', async () => {
|
|
const { container } = render(BulkDocumentEditLayout, {
|
|
mode: 'edit',
|
|
initialEditEntries: [editEntry(1)]
|
|
});
|
|
expect(container.querySelector('[data-testid="description-archive-box"]')).not.toBeNull();
|
|
expect(container.querySelector('[data-testid="description-archive-folder"]')).not.toBeNull();
|
|
});
|
|
|
|
it('save calls PATCH /api/documents/bulk in edit mode', async () => {
|
|
const mockFetch = vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
json: async () => ({ updated: 2, errors: [] })
|
|
});
|
|
vi.stubGlobal('fetch', mockFetch);
|
|
|
|
const { container } = render(BulkDocumentEditLayout, {
|
|
mode: 'edit',
|
|
initialEditEntries: [editEntry(1), editEntry(2)]
|
|
});
|
|
|
|
const saveBtn = container.querySelector(
|
|
'button[data-testid="bulk-save-btn"]'
|
|
) as HTMLButtonElement;
|
|
expect(saveBtn).not.toBeNull();
|
|
saveBtn.click();
|
|
|
|
await vi.waitFor(() => expect(mockFetch).toHaveBeenCalledTimes(1), { timeout: 3000 });
|
|
const [url, init] = mockFetch.mock.calls[0];
|
|
expect(url).toBe('/api/documents/bulk');
|
|
expect(init.method).toBe('PATCH');
|
|
const body = JSON.parse(init.body);
|
|
expect(body.documentIds).toEqual(['doc-1', 'doc-2']);
|
|
});
|
|
|
|
it('chunks IDs into 500-sized PATCH requests', async () => {
|
|
const mockFetch = vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
json: async () => ({ updated: 500, errors: [] })
|
|
});
|
|
vi.stubGlobal('fetch', mockFetch);
|
|
|
|
const entries = Array.from({ length: 1100 }, (_, i) => editEntry(i));
|
|
const { container } = render(BulkDocumentEditLayout, {
|
|
mode: 'edit',
|
|
initialEditEntries: entries
|
|
});
|
|
|
|
const saveBtn = container.querySelector(
|
|
'button[data-testid="bulk-save-btn"]'
|
|
) as HTMLButtonElement;
|
|
saveBtn.click();
|
|
|
|
await vi.waitFor(() => expect(mockFetch).toHaveBeenCalledTimes(3), { timeout: 5000 });
|
|
expect(JSON.parse(mockFetch.mock.calls[0][1].body).documentIds.length).toBe(500);
|
|
expect(JSON.parse(mockFetch.mock.calls[1][1].body).documentIds.length).toBe(500);
|
|
expect(JSON.parse(mockFetch.mock.calls[2][1].body).documentIds.length).toBe(100);
|
|
});
|
|
|
|
it('stops on chunk failure and shows the partial-failure alert with retry', async () => {
|
|
const mockFetch = vi
|
|
.fn()
|
|
.mockResolvedValueOnce({ ok: true, json: async () => ({ updated: 500, errors: [] }) })
|
|
.mockResolvedValueOnce({ ok: false, json: async () => ({ code: 'INTERNAL_ERROR' }) });
|
|
vi.stubGlobal('fetch', mockFetch);
|
|
|
|
const entries = Array.from({ length: 1100 }, (_, i) => editEntry(i));
|
|
const { container } = render(BulkDocumentEditLayout, {
|
|
mode: 'edit',
|
|
initialEditEntries: entries
|
|
});
|
|
|
|
const saveBtn = container.querySelector(
|
|
'button[data-testid="bulk-save-btn"]'
|
|
) as HTMLButtonElement;
|
|
saveBtn.click();
|
|
|
|
await vi.waitFor(
|
|
() => {
|
|
const alert = container.querySelector('[data-testid="bulk-edit-partial-failure"]');
|
|
expect(alert).not.toBeNull();
|
|
},
|
|
{ timeout: 5000 }
|
|
);
|
|
// Should have called twice — chunks 0 and 1 — but not the third.
|
|
expect(mockFetch).toHaveBeenCalledTimes(2);
|
|
expect(vi.mocked(goto)).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('marks per-document error chips when service returns errors[]', async () => {
|
|
vi.stubGlobal(
|
|
'fetch',
|
|
vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
json: async () => ({
|
|
updated: 1,
|
|
errors: [{ id: 'doc-2', message: 'Sender not found' }]
|
|
})
|
|
})
|
|
);
|
|
|
|
const { container } = render(BulkDocumentEditLayout, {
|
|
mode: 'edit',
|
|
initialEditEntries: [editEntry(1), editEntry(2)]
|
|
});
|
|
|
|
const saveBtn = container.querySelector(
|
|
'button[data-testid="bulk-save-btn"]'
|
|
) as HTMLButtonElement;
|
|
saveBtn.click();
|
|
|
|
await vi.waitFor(
|
|
() => {
|
|
const errorChip = container.querySelector(
|
|
'[data-testid="file-switcher-strip"] [data-chip-id="doc-2"][data-status="error"]'
|
|
);
|
|
expect(errorChip).not.toBeNull();
|
|
},
|
|
{ timeout: 3000 }
|
|
);
|
|
});
|
|
});
|