feat(web/m7-scan-progress): StageBadge + 4-card layout updates

Each of the 4 stage cards in the admin Library Scan section gains a
StageBadge in its header. The badge derives state via stageState()
from the existing ScanStatus payload — no new query, no schema
change. Active stage shows spinner + "Processing" in moss green;
done shows check + "Done" in moss green; pending and skipped show
muted text.

Card body now shows either the (possibly partial) numbers OR a
"Waiting for previous stage…" placeholder when the badge is
pending. The previous bottom-of-card "Pending…" / "Skipped."
fallback text disappears (the badge replaces it).

StageBadge.test.ts covers the four state renderings.
admin.test.ts adds one new case asserting the Processing badge
shows when scan.library is populated and in_flight: true. Existing
admin.test.ts assertions still pass (the default mock data has no
stage tallies + in_flight: false → all stages skipped, no Loader/
Check icons rendered).
This commit is contained in:
2026-05-06 21:19:53 -04:00
parent a368c74a9a
commit 3b2d1009cd
4 changed files with 104 additions and 14 deletions
+25 -2
View File
@@ -22,12 +22,13 @@ vi.mock('$lib/api/admin', async () => {
createAdminRequestsQuery: vi.fn(),
createLidarrConfigQuery: vi.fn(),
createAdminQuarantineQuery: vi.fn(),
createScanStatusQuery: () =>
createScanStatusQuery: vi.fn(() =>
readable({
data: { id: '', started_at: '', finished_at: null, in_flight: false },
isPending: false,
isError: false
}),
})
),
createCoverageQuery: () =>
readable({
data: undefined,
@@ -91,6 +92,7 @@ import {
createAdminRequestsQuery,
createLidarrConfigQuery,
createAdminQuarantineQuery,
createScanStatusQuery,
approveRequest,
rejectRequest,
resolveQuarantine,
@@ -239,4 +241,25 @@ describe('admin Overview page', () => {
'href', '/admin/requests'
);
});
test('Library scan card shows Processing badge when library tally is populated and in-flight', async () => {
const { readable } = await import('svelte/store');
(createScanStatusQuery as ReturnType<typeof vi.fn>).mockReturnValueOnce(
readable({
data: {
id: 'in-flight',
started_at: '2026-05-06T11:00:00Z',
finished_at: null,
in_flight: true,
library: { scanned: 100, added: 1, updated: 0, skipped: 99, errored: 0 }
},
isPending: false,
isError: false
})
);
setupOverview();
userState.current = { id: 'admin', username: 'admin', is_admin: true };
render(OverviewPage);
expect(await screen.findByText('Processing')).toBeInTheDocument();
});
});