refactor(catalog): derive desktop sort options from SORT_FIELD_LABELS #14
@@ -1,4 +1,5 @@
|
|||||||
import { test, expect, type Page } from '@playwright/test';
|
import { test, expect, type Page } from '@playwright/test';
|
||||||
|
import { SORT_FIELD_LABELS } from '../src/lib/mangaSort';
|
||||||
|
|
||||||
// These E2E tests run against the SvelteKit dev server, which proxies /api
|
// These E2E tests run against the SvelteKit dev server, which proxies /api
|
||||||
// to the backend. Playwright starts vite via `webServer` (see
|
// to the backend. Playwright starts vite via `webServer` (see
|
||||||
@@ -44,6 +45,35 @@ test('home page renders the Mangalord heading and search input', async ({ page }
|
|||||||
await expect(page.getByTestId('empty')).toContainText('No mangas yet');
|
await expect(page.getByTestId('empty')).toContainText('No mangas yet');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// would silently diverge the two surfaces. Comparing the rendered options to
|
||||||
|
// the single source of truth makes that divergence fail loudly.
|
||||||
|
test('desktop sort options stay in sync with SORT_FIELD_LABELS', async ({ page }) => {
|
||||||
|
await mockAnonymous(page);
|
||||||
|
await page.route('**/api/v1/mangas*', async (route) => {
|
||||||
|
await route.fulfill({
|
||||||
|
status: 200,
|
||||||
|
contentType: 'application/json',
|
||||||
|
body: JSON.stringify(emptyPage)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.goto('/');
|
||||||
|
const select = page.getByTestId('sort-select');
|
||||||
|
await expect(select).toBeVisible();
|
||||||
|
|
||||||
|
const options = select.locator('option');
|
||||||
|
const labels = (await options.allTextContents()).map((s) => s.trim());
|
||||||
|
const values = await options.evaluateAll((opts) =>
|
||||||
|
opts.map((o) => (o as HTMLOptionElement).value)
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(values).toEqual(Object.keys(SORT_FIELD_LABELS));
|
||||||
|
expect(labels).toEqual(Object.values(SORT_FIELD_LABELS));
|
||||||
|
});
|
||||||
|
|
||||||
test('search updates the manga list', async ({ page }) => {
|
test('search updates the manga list', async ({ page }) => {
|
||||||
await mockAnonymous(page);
|
await mockAnonymous(page);
|
||||||
let lastSearch: string | null = null;
|
let lastSearch: string | null = null;
|
||||||
|
|||||||
@@ -538,10 +538,11 @@
|
|||||||
<label class="sort">
|
<label class="sort">
|
||||||
<span>Sort</span>
|
<span>Sort</span>
|
||||||
<select bind:value={sort} onchange={onSortChange} data-testid="sort-select">
|
<select bind:value={sort} onchange={onSortChange} data-testid="sort-select">
|
||||||
<option value="updated">Last updated</option>
|
<!-- Options derive from SORT_FIELD_LABELS — the same source the
|
||||||
<option value="created">Date added</option>
|
mobile sort sheet uses — so the two surfaces can't drift. -->
|
||||||
<option value="title">Title</option>
|
{#each Object.entries(SORT_FIELD_LABELS) as [value, label] (value)}
|
||||||
<option value="author">Author</option>
|
<option {value}>{label}</option>
|
||||||
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
<div class="sort">
|
<div class="sort">
|
||||||
|
|||||||
Reference in New Issue
Block a user