import { describe, it, expect, afterEach, vi } from 'vitest'; import { cleanup, render } from 'vitest-browser-svelte'; import { page } from 'vitest/browser'; import DropZone from './DropZone.svelte'; // vi.hoisted lets the mock fn reference survive vi.mock's hoisting so tests // can assert on it from below while the factory remains self-contained. const { invalidateAllMock } = vi.hoisted(() => ({ invalidateAllMock: vi.fn(async () => {}) })); vi.mock('$app/navigation', () => ({ invalidateAll: invalidateAllMock })); afterEach(() => { cleanup(); vi.restoreAllMocks(); }); function stubXhrWith(responseBody: string) { class FakeXhr { upload = { addEventListener: vi.fn() }; status = 200; responseText = responseBody; private loadHandler: (() => void) | null = null; open = vi.fn(); addEventListener = vi.fn((event: string, handler: () => void) => { if (event === 'load') this.loadHandler = handler; }); send = vi.fn(() => { queueMicrotask(() => this.loadHandler?.()); }); } vi.stubGlobal('XMLHttpRequest', FakeXhr); } describe('DropZone onUploadComplete', () => { it('invokes callback with created.length after a successful upload', async () => { stubXhrWith(JSON.stringify({ created: [{ id: 'd1' }, { id: 'd2' }], updated: [], errors: [] })); const onUploadComplete = vi.fn(); render(DropZone, { onUploadComplete }); const input = document.querySelector('input[type="file"]') as HTMLInputElement | null; expect(input).not.toBeNull(); const file = new File(['%PDF-1.4'], 'test.pdf', { type: 'application/pdf' }); const dataTransfer = new DataTransfer(); dataTransfer.items.add(file); input!.files = dataTransfer.files; input!.dispatchEvent(new Event('change', { bubbles: true })); await vi.waitFor(() => { expect(onUploadComplete).toHaveBeenCalledTimes(1); }); expect(onUploadComplete).toHaveBeenCalledWith(2); }); it('does not invoke callback when no files were created', async () => { stubXhrWith(JSON.stringify({ created: [], updated: [], errors: [] })); const onUploadComplete = vi.fn(); render(DropZone, { onUploadComplete }); const input = document.querySelector('input[type="file"]') as HTMLInputElement; const file = new File(['%PDF-1.4'], 'dupe.pdf', { type: 'application/pdf' }); const dt = new DataTransfer(); dt.items.add(file); input.files = dt.files; input.dispatchEvent(new Event('change', { bubbles: true })); // invalidateAll is the last async step of the upload handler — once it // has been called, the callback decision has already been made. await vi.waitFor(() => { expect(invalidateAllMock).toHaveBeenCalled(); }); expect(onUploadComplete).not.toHaveBeenCalled(); }); it('works when the onUploadComplete prop is not supplied', async () => { stubXhrWith(JSON.stringify({ created: [{ id: 'x' }], updated: [], errors: [] })); render(DropZone, {}); const input = document.querySelector('input[type="file"]') as HTMLInputElement; const file = new File(['%PDF-1.4'], 'x.pdf', { type: 'application/pdf' }); const dt = new DataTransfer(); dt.items.add(file); // Should not throw when the optional callback is absent. input.files = dt.files; input.dispatchEvent(new Event('change', { bubbles: true })); await expect.element(page.getByText(/1 Dokument/)).toBeInTheDocument(); }); });