feat: skeleton loading state on the home catalog

Replaces the plain "Loading…" text with a content-shaped MangaGridSkeleton
so the catalog grid's layout is reserved while it loads and swaps in without
a shift. Keeps the data-testid="loading" wrapper (now role="status") for
accessibility and selector stability.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-07-06 19:40:28 +02:00
parent 09f12c8959
commit def97d3087
5 changed files with 52 additions and 4 deletions

View File

@@ -63,6 +63,51 @@ test('home page renders the Mangalord heading and search input', async ({ page }
await expect(page.getByTestId('empty')).toContainText('No mangas yet');
});
test('shows a skeleton grid while the catalog is loading, then the real grid', async ({ page }) => {
await mockAnonymous(page);
// Hold the catalog fetch open so the loading state is observable.
let release: () => void = () => {};
const gate = new Promise<void>((resolve) => {
release = resolve;
});
await page.route('**/api/v1/mangas*', async (route) => {
await gate;
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
items: [
{
id: 'm1',
title: 'Berserk',
status: 'ongoing',
alt_titles: [],
description: null,
cover_image_path: null,
created_at: '2026-01-01T00:00:00Z',
updated_at: '2026-01-01T00:00:00Z',
authors: [],
genres: []
}
],
page: { limit: 50, offset: 0, total: 1 }
})
});
});
await page.goto('/');
// Skeleton stands in for the grid while the fetch is pending.
await expect(page.getByTestId('manga-grid-skeleton')).toBeVisible();
await expect(page.getByTestId('manga-list')).toHaveCount(0);
// Once data lands, the real grid replaces the skeleton.
release();
await expect(page.getByTestId('manga-list')).toContainText('Berserk');
await expect(page.getByTestId('manga-grid-skeleton')).toHaveCount(0);
});
// Anti-drift guard: the desktop sort <select> and the mobile sort sheet must
// both enumerate exactly SORT_FIELD_LABELS, in the same order. The desktop
// select used to hard-code its <option>s, so a label rename in mangaSort.ts