test(coverage): drive browser tests to 80% on all metrics (#496) #505
@@ -9,18 +9,21 @@ describe('admin/system page', () => {
|
||||
let fetchSpy: ReturnType<typeof vi.spyOn>;
|
||||
|
||||
beforeEach(() => {
|
||||
fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue(
|
||||
new Response(
|
||||
JSON.stringify({
|
||||
state: 'IDLE',
|
||||
message: '',
|
||||
total: 0,
|
||||
processed: 0,
|
||||
skipped: 0,
|
||||
failed: 0
|
||||
}),
|
||||
{ status: 200, headers: { 'Content-Type': 'application/json' } }
|
||||
)
|
||||
// mockImplementation (not mockResolvedValue) returns a fresh Response per call so the
|
||||
// body stream isn't already-consumed after the first read.
|
||||
fetchSpy = vi.spyOn(globalThis, 'fetch').mockImplementation(
|
||||
async () =>
|
||||
new Response(
|
||||
JSON.stringify({
|
||||
state: 'IDLE',
|
||||
message: '',
|
||||
total: 0,
|
||||
processed: 0,
|
||||
skipped: 0,
|
||||
failed: 0
|
||||
}),
|
||||
{ status: 200, headers: { 'Content-Type': 'application/json' } }
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
@@ -79,36 +82,33 @@ describe('admin/system page', () => {
|
||||
|
||||
render(AdminSystemPage, { props: {} });
|
||||
|
||||
const btn = (await page
|
||||
.getByRole('button', { name: /jetzt auffüllen/i })
|
||||
.element()) as HTMLButtonElement;
|
||||
btn.click();
|
||||
await page.getByRole('button', { name: /jetzt auffüllen/i }).click();
|
||||
|
||||
await new Promise((r) => setTimeout(r, 50));
|
||||
const calls = fetchSpy.mock.calls.map((c) => c[0].toString());
|
||||
expect(calls.some((c) => c.includes('backfill-versions'))).toBe(true);
|
||||
await vi.waitFor(() => {
|
||||
const calls = fetchSpy.mock.calls.map((c) => c[0].toString());
|
||||
expect(calls.some((c) => c.includes('backfill-versions'))).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('triggers file-hashes backfill when its button is clicked', async () => {
|
||||
render(AdminSystemPage, { props: {} });
|
||||
|
||||
const btn = (await page
|
||||
.getByRole('button', { name: /datei-hashes berechnen/i })
|
||||
.element()) as HTMLButtonElement;
|
||||
btn.click();
|
||||
await page.getByRole('button', { name: /datei-hashes berechnen/i }).click();
|
||||
|
||||
await new Promise((r) => setTimeout(r, 50));
|
||||
const calls = fetchSpy.mock.calls.map((c) => c[0].toString());
|
||||
expect(calls.some((c) => c.includes('backfill-file-hashes'))).toBe(true);
|
||||
await vi.waitFor(() => {
|
||||
const calls = fetchSpy.mock.calls.map((c) => c[0].toString());
|
||||
expect(calls.some((c) => c.includes('backfill-file-hashes'))).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('initial fetch loads import-status and thumbnail-status', async () => {
|
||||
render(AdminSystemPage, { props: {} });
|
||||
|
||||
await new Promise((r) => setTimeout(r, 50));
|
||||
const calls = fetchSpy.mock.calls.map((c) => c[0].toString());
|
||||
expect(calls.some((c) => c.includes('import-status'))).toBe(true);
|
||||
expect(calls.some((c) => c.includes('thumbnail-status'))).toBe(true);
|
||||
await vi.waitFor(() => {
|
||||
const calls = fetchSpy.mock.calls.map((c) => c[0].toString());
|
||||
expect(calls.some((c) => c.includes('import-status'))).toBe(true);
|
||||
expect(calls.some((c) => c.includes('thumbnail-status'))).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('shows the success banner after backfill versions completes', async () => {
|
||||
@@ -128,14 +128,11 @@ describe('admin/system page', () => {
|
||||
|
||||
render(AdminSystemPage, { props: {} });
|
||||
|
||||
const btn = (await page
|
||||
.getByRole('button', { name: /jetzt auffüllen/i })
|
||||
.element()) as HTMLButtonElement;
|
||||
btn.click();
|
||||
await page.getByRole('button', { name: /jetzt auffüllen/i }).click();
|
||||
|
||||
await new Promise((r) => setTimeout(r, 100));
|
||||
const banner = document.querySelector('.bg-green-50');
|
||||
expect(banner).not.toBeNull();
|
||||
await vi.waitFor(() => {
|
||||
expect(document.querySelector('.bg-green-50')).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
it('renders the running state for import-status', async () => {
|
||||
@@ -155,8 +152,9 @@ describe('admin/system page', () => {
|
||||
|
||||
render(AdminSystemPage, { props: {} });
|
||||
|
||||
await new Promise((r) => setTimeout(r, 100));
|
||||
expect(document.body.textContent).toMatch(/läuft|wird ausgeführt/i);
|
||||
await vi.waitFor(() => {
|
||||
expect(document.body.textContent).toMatch(/läuft|wird ausgeführt/i);
|
||||
});
|
||||
});
|
||||
|
||||
it('renders the DONE state with processed count for import-status', async () => {
|
||||
@@ -176,8 +174,9 @@ describe('admin/system page', () => {
|
||||
|
||||
render(AdminSystemPage, { props: {} });
|
||||
|
||||
await new Promise((r) => setTimeout(r, 100));
|
||||
expect(document.body.textContent).toContain('99');
|
||||
await vi.waitFor(() => {
|
||||
expect(document.body.textContent).toContain('99');
|
||||
});
|
||||
});
|
||||
|
||||
it('renders the FAILED state with the error message for thumbnail-status', async () => {
|
||||
@@ -205,8 +204,9 @@ describe('admin/system page', () => {
|
||||
|
||||
render(AdminSystemPage, { props: {} });
|
||||
|
||||
await new Promise((r) => setTimeout(r, 100));
|
||||
expect(document.body.textContent).toContain('connection refused');
|
||||
await vi.waitFor(() => {
|
||||
expect(document.body.textContent).toContain('connection refused');
|
||||
});
|
||||
});
|
||||
|
||||
it('renders the DONE state for thumbnail-status with retry button', async () => {
|
||||
@@ -234,9 +234,9 @@ describe('admin/system page', () => {
|
||||
|
||||
render(AdminSystemPage, { props: {} });
|
||||
|
||||
await new Promise((r) => setTimeout(r, 100));
|
||||
const banner = document.querySelector('[data-testid="thumbnails-status-done"]');
|
||||
expect(banner).not.toBeNull();
|
||||
await vi.waitFor(() => {
|
||||
expect(document.querySelector('[data-testid="thumbnails-status-done"]')).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
it('renders the FAILED state for import-status with retry button', async () => {
|
||||
@@ -261,8 +261,9 @@ describe('admin/system page', () => {
|
||||
|
||||
render(AdminSystemPage, { props: {} });
|
||||
|
||||
await new Promise((r) => setTimeout(r, 100));
|
||||
expect(document.body.textContent).toContain('database error');
|
||||
await vi.waitFor(() => {
|
||||
expect(document.body.textContent).toContain('database error');
|
||||
});
|
||||
});
|
||||
|
||||
it('renders the running thumbnail status with progress count', async () => {
|
||||
@@ -290,33 +291,40 @@ describe('admin/system page', () => {
|
||||
|
||||
render(AdminSystemPage, { props: {} });
|
||||
|
||||
await new Promise((r) => setTimeout(r, 100));
|
||||
// Total 100, processed+skipped+failed = 36
|
||||
expect(document.body.textContent).toMatch(/36|100/);
|
||||
await vi.waitFor(() => {
|
||||
// Total 100, processed+skipped+failed = 36 — at least one of these surfaces.
|
||||
expect(document.body.textContent).toMatch(/36|100/);
|
||||
});
|
||||
});
|
||||
|
||||
it('triggers thumbnail backfill when its button is clicked', async () => {
|
||||
render(AdminSystemPage, { props: {} });
|
||||
|
||||
await new Promise((r) => setTimeout(r, 50));
|
||||
await vi.waitFor(() => {
|
||||
expect(document.querySelector('[data-thumbnails-trigger]')).not.toBeNull();
|
||||
});
|
||||
const btns = Array.from(document.querySelectorAll('[data-thumbnails-trigger]'));
|
||||
(btns[0] as HTMLButtonElement)?.click();
|
||||
(btns[0] as HTMLButtonElement).click();
|
||||
|
||||
await new Promise((r) => setTimeout(r, 50));
|
||||
const calls = fetchSpy.mock.calls.map((c) => c[0].toString());
|
||||
expect(calls.some((c) => c.includes('generate-thumbnails'))).toBe(true);
|
||||
await vi.waitFor(() => {
|
||||
const calls = fetchSpy.mock.calls.map((c) => c[0].toString());
|
||||
expect(calls.some((c) => c.includes('generate-thumbnails'))).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('triggers import when import button is clicked from idle state', async () => {
|
||||
render(AdminSystemPage, { props: {} });
|
||||
|
||||
await new Promise((r) => setTimeout(r, 50));
|
||||
await vi.waitFor(() => {
|
||||
expect(document.querySelector('[data-import-trigger]')).not.toBeNull();
|
||||
});
|
||||
const btns = Array.from(document.querySelectorAll('[data-import-trigger]'));
|
||||
(btns[0] as HTMLButtonElement)?.click();
|
||||
(btns[0] as HTMLButtonElement).click();
|
||||
|
||||
await new Promise((r) => setTimeout(r, 50));
|
||||
const calls = fetchSpy.mock.calls.map((c) => c[0].toString());
|
||||
expect(calls.some((c) => c.includes('trigger-import'))).toBe(true);
|
||||
await vi.waitFor(() => {
|
||||
const calls = fetchSpy.mock.calls.map((c) => c[0].toString());
|
||||
expect(calls.some((c) => c.includes('trigger-import'))).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('renders the running thumbnail status without progress when total is 0', async () => {
|
||||
@@ -344,8 +352,8 @@ describe('admin/system page', () => {
|
||||
|
||||
render(AdminSystemPage, { props: {} });
|
||||
|
||||
await new Promise((r) => setTimeout(r, 100));
|
||||
// running state shown but no progress count when total=0
|
||||
expect(document.body.textContent).toMatch(/läuft|wird|generier/i);
|
||||
await vi.waitFor(() => {
|
||||
expect(document.body.textContent).toMatch(/läuft|wird|generier/i);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user