Compare commits
2 Commits
4f3020ffab
...
aae005d5e6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aae005d5e6 | ||
|
|
9b6d8fbef1 |
@@ -68,43 +68,71 @@ test.describe('Geschichten — writer + reader journey', () => {
|
|||||||
await page.goto('/geschichten');
|
await page.goto('/geschichten');
|
||||||
await page.waitForSelector('[data-hydrated]');
|
await page.waitForSelector('[data-hydrated]');
|
||||||
|
|
||||||
// We need two persons to filter by. Open the picker and pick one whose name
|
// We need two distinct persons to filter by, but we don't want to couple this
|
||||||
// the dev seed reliably contains. Then open the picker again and pick a
|
// test to specific seed names. Strategy: type a single broadly-occurring vowel
|
||||||
// second one. Picking is via the typeahead — we type, wait for the listbox,
|
// ("e" is present in the vast majority of German names), open the listbox,
|
||||||
// click the first option.
|
// and pick whichever option matches the predicate.
|
||||||
async function pickPerson(query: string) {
|
//
|
||||||
|
// option DOM ids encode the person id as `${listboxId}-option-${personId}`,
|
||||||
|
// so we can identify the *first different* option without knowing the seed.
|
||||||
|
const PROBE = 'e';
|
||||||
|
|
||||||
|
async function openPicker() {
|
||||||
await page.getByRole('button', { name: /Person wählen/ }).click();
|
await page.getByRole('button', { name: /Person wählen/ }).click();
|
||||||
const input = page.getByRole('combobox', { name: /Person wählen/ });
|
const input = page.getByRole('combobox', { name: /Person wählen/ });
|
||||||
await input.fill(query);
|
await input.fill(PROBE);
|
||||||
// Wait for at least one option in the listbox, then click it
|
// Wait for the listbox to be populated.
|
||||||
const firstOption = page.getByRole('option').first();
|
await expect(page.getByRole('option').first()).toBeVisible();
|
||||||
await expect(firstOption).toBeVisible();
|
|
||||||
await firstOption.click();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await pickPerson('a');
|
async function pickFirstOption(): Promise<string> {
|
||||||
await page.waitForURL(/personId=/);
|
const opt = page.getByRole('option').first();
|
||||||
const firstUrl = new URL(page.url());
|
const optId = (await opt.getAttribute('id')) ?? '';
|
||||||
const firstIds = firstUrl.searchParams.getAll('personId');
|
const personId = optId.split('-option-')[1] ?? '';
|
||||||
expect(firstIds).toHaveLength(1);
|
expect(personId).not.toEqual('');
|
||||||
|
await opt.click();
|
||||||
|
return personId;
|
||||||
|
}
|
||||||
|
|
||||||
await pickPerson('b');
|
async function pickFirstOptionDifferentFrom(excludeId: string): Promise<string> {
|
||||||
|
// Iterate through visible options and return the first whose person id != excludeId.
|
||||||
|
const optionCount = await page.getByRole('option').count();
|
||||||
|
for (let i = 0; i < optionCount; i++) {
|
||||||
|
const candidate = page.getByRole('option').nth(i);
|
||||||
|
const optId = (await candidate.getAttribute('id')) ?? '';
|
||||||
|
const personId = optId.split('-option-')[1] ?? '';
|
||||||
|
if (personId && personId !== excludeId) {
|
||||||
|
await candidate.click();
|
||||||
|
return personId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Error(
|
||||||
|
`Expected at least two distinct persons matching "${PROBE}" in the seed, found only one.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await openPicker();
|
||||||
|
const firstId = await pickFirstOption();
|
||||||
|
await page.waitForURL(/personId=/);
|
||||||
|
const firstIds = new URL(page.url()).searchParams.getAll('personId');
|
||||||
|
expect(firstIds).toEqual([firstId]);
|
||||||
|
|
||||||
|
await openPicker();
|
||||||
|
const secondId = await pickFirstOptionDifferentFrom(firstId);
|
||||||
await page.waitForURL((url) => url.searchParams.getAll('personId').length === 2);
|
await page.waitForURL((url) => url.searchParams.getAll('personId').length === 2);
|
||||||
const secondUrl = new URL(page.url());
|
const secondIds = new URL(page.url()).searchParams.getAll('personId');
|
||||||
const secondIds = secondUrl.searchParams.getAll('personId');
|
expect(secondIds).toEqual([firstId, secondId]);
|
||||||
expect(secondIds).toHaveLength(2);
|
expect(secondId).not.toEqual(firstId);
|
||||||
expect(secondIds[0]).toBe(firstIds[0]); // first one persists
|
|
||||||
expect(secondIds[1]).not.toBe(firstIds[0]); // second is different
|
|
||||||
|
|
||||||
// Two chips visible — find them by their remove-aria-label pattern
|
// Two chips visible — find them by their remove-aria-label pattern
|
||||||
const chipButtons = page.getByRole('button', { name: /aus Filter entfernen/ });
|
const chipButtons = page.getByRole('button', { name: /aus Filter entfernen/ });
|
||||||
await expect(chipButtons).toHaveCount(2);
|
await expect(chipButtons).toHaveCount(2);
|
||||||
|
|
||||||
// Remove the first chip — URL drops to one param
|
// Remove the first chip — URL drops to one param, only the second id remains
|
||||||
await chipButtons.first().click();
|
await chipButtons.first().click();
|
||||||
await page.waitForURL((url) => url.searchParams.getAll('personId').length === 1);
|
await page.waitForURL((url) => url.searchParams.getAll('personId').length === 1);
|
||||||
const finalIds = new URL(page.url()).searchParams.getAll('personId');
|
const finalIds = new URL(page.url()).searchParams.getAll('personId');
|
||||||
expect(finalIds).toEqual([secondIds[1]]);
|
expect(finalIds).toEqual([secondId]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('AxeBuilder finds no critical violations on the index', async ({ page }) => {
|
test('AxeBuilder finds no critical violations on the index', async ({ page }) => {
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ function publishedAt(g: { publishedAt?: string }): string | null {
|
|||||||
type="button"
|
type="button"
|
||||||
aria-pressed={!hasFilters}
|
aria-pressed={!hasFilters}
|
||||||
onclick={clearAll}
|
onclick={clearAll}
|
||||||
class="inline-flex h-9 items-center rounded-full border border-line px-3 font-sans text-xs font-bold tracking-wider text-ink-2 uppercase hover:bg-muted aria-pressed:bg-ink aria-pressed:text-primary-fg"
|
class="inline-flex h-11 items-center rounded-full border border-line px-3 font-sans text-xs font-bold tracking-wider text-ink-2 uppercase hover:bg-muted aria-pressed:bg-ink aria-pressed:text-primary-fg"
|
||||||
>
|
>
|
||||||
{m.geschichten_filter_all_pill()}
|
{m.geschichten_filter_all_pill()}
|
||||||
</button>
|
</button>
|
||||||
@@ -81,7 +81,7 @@ function publishedAt(g: { publishedAt?: string }): string | null {
|
|||||||
aria-pressed="true"
|
aria-pressed="true"
|
||||||
aria-label={m.geschichten_filter_remove_chip({ name: p.displayName })}
|
aria-label={m.geschichten_filter_remove_chip({ name: p.displayName })}
|
||||||
onclick={() => removePerson(p.id!)}
|
onclick={() => removePerson(p.id!)}
|
||||||
class="inline-flex h-9 items-center gap-2 rounded-full bg-ink px-3 font-sans text-xs font-bold tracking-wider text-primary-fg uppercase"
|
class="inline-flex h-11 items-center gap-2 rounded-full bg-ink px-3 font-sans text-xs font-bold tracking-wider text-primary-fg uppercase"
|
||||||
>
|
>
|
||||||
{p.displayName}
|
{p.displayName}
|
||||||
<span aria-hidden="true">×</span>
|
<span aria-hidden="true">×</span>
|
||||||
@@ -92,7 +92,7 @@ function publishedAt(g: { publishedAt?: string }): string | null {
|
|||||||
type="button"
|
type="button"
|
||||||
aria-expanded={showPersonPicker}
|
aria-expanded={showPersonPicker}
|
||||||
onclick={() => (showPersonPicker = !showPersonPicker)}
|
onclick={() => (showPersonPicker = !showPersonPicker)}
|
||||||
class="inline-flex h-9 items-center rounded-full border border-line px-3 font-sans text-xs font-bold tracking-wider text-ink-2 uppercase hover:bg-muted"
|
class="inline-flex h-11 items-center rounded-full border border-line px-3 font-sans text-xs font-bold tracking-wider text-ink-2 uppercase hover:bg-muted"
|
||||||
>
|
>
|
||||||
+ {m.geschichten_filter_choose_person()}
|
+ {m.geschichten_filter_choose_person()}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -99,4 +99,25 @@ describe('geschichten page — multi-person filter chips', () => {
|
|||||||
});
|
});
|
||||||
await expect.element(page.getByRole('button', { name: /Person wählen/ })).toBeVisible();
|
await expect.element(page.getByRole('button', { name: /Person wählen/ })).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('renders all filter pills with a 44px touch target (h-11)', async () => {
|
||||||
|
render(Page, {
|
||||||
|
data: makeData({
|
||||||
|
personFilters: [person('a', 'Anna A')] as PageData['personFilters']
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// All three pill variants must use h-11 (44px) per the senior-author touch-target rule
|
||||||
|
const all = page.getByRole('button', { name: 'Alle' });
|
||||||
|
const chip = page.getByRole('button', { name: /Anna A aus Filter entfernen/ });
|
||||||
|
const add = page.getByRole('button', { name: /Person wählen/ });
|
||||||
|
|
||||||
|
const allEl = (await all.element()) as HTMLElement;
|
||||||
|
const chipEl = (await chip.element()) as HTMLElement;
|
||||||
|
const addEl = (await add.element()) as HTMLElement;
|
||||||
|
|
||||||
|
expect(allEl.className).toContain('h-11');
|
||||||
|
expect(chipEl.className).toContain('h-11');
|
||||||
|
expect(addEl.className).toContain('h-11');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user