Files
yellowjacket/e2e/specs/settings-reach.spec.ts
T
logan c79d4d47a3 test: cover jobs in Settings, and unpick two shared selectors
The spec worth having is not that the tab is gone -- that is one line
of a table -- but that nothing became unreachable when it went. #24
promises that no action is ever unreachable at any supported size, and
deleting a destination is exactly the change that quietly breaks it.

Two existing selectors had to give. `config-section .header` is
ambiguous the moment a section holds a job, because `job-details-drawer`
carries that class too -- so `settings-reach.spec.ts` locates a
disclosure by role and name instead. And `page-header`'s and
`offline-icons`'s view lists lose an entry each.

Closes #27
2026-08-19 20:27:48 -04:00

82 lines
2.7 KiB
TypeScript

import { test, expect, navigateTo } from '../support/fixtures.js';
/**
* Plan 007 phase 5: a11y.1 and a11y.2, frozen against the real app.
*
* Every `config-section` header was a bare `<div @click>` and every
* section defaults to collapsed, so every setting in the app was behind
* a control that could not be tabbed to. Downloads' two tabs were the
* same bug one page over.
*
* A component test can see the markup; only this tier can see that the
* control is reachable *through the app's own tab order*, past the
* sidebar, the header and whatever else is on screen.
*/
test.describe('Settings is reachable without a mouse', () => {
test('every section disclosure is a button that reports its state', async ({
app,
}) => {
await app.getByTestId('nav-settings').click();
// Per *section*, not per `.header`: a section holding a
// `job-panel` (#27) also contains `job-details-drawer`, whose own
// header carries that class and is not a disclosure.
const sections = app.locator('config-page config-section');
await expect(sections.first()).toBeVisible();
const count = await sections.count();
expect(count).toBeGreaterThan(4);
for (let i = 0; i < count; i++) {
const header = sections.nth(i).locator('.header').first();
expect(await header.evaluate((el) => el.tagName)).toBe('BUTTON');
expect(['true', 'false']).toContain(
await header.getAttribute('aria-expanded'),
);
}
});
test('a collapsed section can be opened from the keyboard', async ({
app,
}) => {
await app.getByTestId('nav-settings').click();
const theme = app
.locator('config-page config-section[heading="Theme"] .header')
.first();
await expect(theme).toHaveAttribute('aria-expanded', 'false');
await theme.focus();
await app.locator('body').press('Enter');
await expect(theme).toHaveAttribute('aria-expanded', 'true');
});
});
test.describe("Downloads' tabs are tabs", () => {
test('arrow keys move the selection and swap the panel', async ({ app }) => {
// By event, not by nav item: with no download client configured
// there is no Downloads destination to click (#25).
await navigateTo(app, 'downloads');
const view = app.locator('downloads-view');
const requests = view.getByRole('tab', { name: 'Requests' });
const downloads = view.getByRole('tab', { name: 'Downloads' });
await expect(requests).toHaveAttribute('aria-selected', 'true');
await requests.focus();
await app.locator('body').press('ArrowRight');
await expect(downloads).toHaveAttribute('aria-selected', 'true');
await expect(view.locator('[role="tabpanel"]')).toHaveAttribute(
'aria-labelledby',
'tab-downloads',
);
});
});