fix(search): address review on the shared result rows
PR Checks / Frontend Typecheck + Tests (pull_request) Successful in 1m12s
PR Checks / Backend Smoke (pull_request) Successful in 10s
PR Checks / Build Backend (no push) (pull_request) Successful in 18s
PR Checks / Build Frontend (no push) (pull_request) Successful in 1m19s
PR Checks / Build Pipeline (no push) (pull_request) Successful in 11s
PR Checks / AI Code Review (Claude) (pull_request) Successful in 21s
PR Checks / Frontend Typecheck + Tests (pull_request) Successful in 1m12s
PR Checks / Backend Smoke (pull_request) Successful in 10s
PR Checks / Build Backend (no push) (pull_request) Successful in 18s
PR Checks / Build Frontend (no push) (pull_request) Successful in 1m19s
PR Checks / Build Pipeline (no push) (pull_request) Successful in 11s
PR Checks / AI Code Review (Claude) (pull_request) Successful in 21s
- The rows' narrow layout now switches at a 608px list, not 600px. Below 769px the page pads 1rem each side, so a 640px screen gives a 608px list: exactly the old max-width: 640px media query, where 600px left 633-640px screens on the wide layout. - rowContainerGuard.test.ts fails if anything other than HomeView renders SchoolRow or SecondarySchoolRow, or if one of HomeView's row lists loses its `results` container. Outside one the rows silently keep their wide layout on phones. (Checked: HomeView is the only importer today.) - Picking a pin from the list beside the map now works from the keyboard: each row carries a "Show <school> on the map" button, visually hidden until focused, with aria-pressed for the selected school. The row itself cannot be the button, since it holds links and buttons of its own. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
dff3e210ab
commit
ca4ddd2b12
7 files changed
+118
-12
No files matched your search
@@ -156,10 +156,19 @@ it('builds no list cards on a phone, where the pane is hidden', async () => {
|
||||
|
||||
it('draws the list view\'s own row beside the map, with the same content', async () => {
|
||||
const { container } = await renderMap();
|
||||
const beside = (container.querySelector('[data-urn="2"]') as HTMLElement).textContent;
|
||||
const beside = container.querySelector('[data-urn="2"] > [class~="row"]')!.textContent;
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'List' }));
|
||||
const row = screen.getByRole('link', { name: 'Southmead Primary School' }).closest('[class~="row"]')!;
|
||||
expect(row.parentElement?.className).toMatch(/schoolList/);
|
||||
expect(row.textContent).toBe(beside);
|
||||
});
|
||||
|
||||
it('lets a keyboard pick a pin from the list, with a real button', async () => {
|
||||
await renderMap();
|
||||
const show = screen.getByRole('button', { name: 'Show Southmead Primary School on the map' });
|
||||
expect(show).toHaveAttribute('aria-pressed', 'false');
|
||||
fireEvent.click(show);
|
||||
expect(screen.getByTestId('map')).toHaveAttribute('data-selected', '2');
|
||||
expect(show).toHaveAttribute('aria-pressed', 'true');
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
/*
|
||||
* SchoolRow and SecondarySchoolRow switch to their narrow layout with a
|
||||
* container query on a `results` container, not a media query, because the
|
||||
* same row fills the phone list and the narrow list beside the desktop map.
|
||||
* Outside a `results` container the query never matches and the row keeps its
|
||||
* wide layout on a phone, a silent regression rather than an error.
|
||||
*
|
||||
* HomeView provides the container on every list it renders the rows into.
|
||||
* Anything else that starts rendering them must do the same; this fails so
|
||||
* that the person adding it reads this first.
|
||||
*/
|
||||
|
||||
const ROOT = path.join(__dirname, '..', '..');
|
||||
const DIRS = ['app', 'components', 'lib'];
|
||||
const ROW_IMPORT = /from\s+['"][^'"]*\/(SchoolRow|SecondarySchoolRow)['"]/;
|
||||
|
||||
function sources(dir: string): string[] {
|
||||
return fs.readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
|
||||
const full = path.join(dir, entry.name);
|
||||
if (entry.isDirectory()) return entry.name === 'node_modules' ? [] : sources(full);
|
||||
return /\.tsx?$/.test(entry.name) ? [full] : [];
|
||||
});
|
||||
}
|
||||
|
||||
it('renders the results rows only where a `results` container is provided', () => {
|
||||
const importers = DIRS.flatMap((d) => sources(path.join(ROOT, d)))
|
||||
.filter((file) => ROW_IMPORT.test(fs.readFileSync(file, 'utf8')))
|
||||
.map((file) => path.relative(ROOT, file));
|
||||
expect(importers).toEqual(['components/HomeView.tsx']);
|
||||
});
|
||||
|
||||
it('gives each of HomeView\'s row lists the `results` container', () => {
|
||||
const css = fs.readFileSync(path.join(ROOT, 'components', 'HomeView.module.css'), 'utf8');
|
||||
for (const list of ['.schoolList', '.compactList', '.bottomSheet']) {
|
||||
const rule = new RegExp(`\\${list}\\s*\\{[^}]*container:\\s*results\\s*/\\s*inline-size`);
|
||||
expect({ list, provided: rule.test(css) }).toEqual({ list, provided: true });
|
||||
}
|
||||
});
|
||||
Reference in new issue
Block a user