fix(chess): global Esc handler + stable test selectors for layouts e2e

- LayoutEditor: move Esc handling to a window keydown listener so
  the modal closes regardless of where focus currently sits (the
  prior onKeyDown on the modal div only fired when the root div
  itself held focus).
- LayoutPicker: add data-testid on the description <p> so e2e
  assertions can target it unambiguously instead of relying on
  free-text matches that also select the hidden <option> labels.
- e2e: use the new testid.

All 12 layouts e2e tests pass; existing multiplayer + full-flow
e2e suites still green.
This commit is contained in:
Joey Yakimowich-Payne 2026-04-18 20:23:07 -06:00
commit 89c22d6bd5
No known key found for this signature in database
3 changed files with 21 additions and 3 deletions

View file

@ -52,7 +52,9 @@ test.describe('LayoutPicker (lobby)', () => {
test('selecting Dunsany updates the description', async ({ page }) => {
const picker = page.getByTestId('layout-picker');
await picker.selectOption('dunsany');
await expect(page.locator('text=/Dunsany/i').first()).toBeVisible();
await expect(
page.getByTestId('layout-picker-description'),
).toContainText(/pawn tide/i);
});
test('?layoutId=dunsany query param pre-selects the layout', async ({ page }) => {

View file

@ -29,7 +29,7 @@
* incrementally simpler and avoids ambiguity about which source
* of truth wins).
*/
import { useMemo, useState, type ReactNode } from 'react';
import { useEffect, useMemo, useState, type ReactNode } from 'react';
import { toast } from 'sonner';
import {
fromFen,
@ -92,6 +92,19 @@ export function LayoutEditor({
const [libraryOpen, setLibraryOpen] = useState(false);
const [libraryVersion, setLibraryVersion] = useState(0); // bump to force reload
// Global Esc listener: close the editor regardless of where focus
// currently is. We can't rely on the modal div's onKeyDown because
// the user might have focused a textarea / button inside a nested
// panel, and keyboard events on those don't bubble to the modal
// root in a way that works universally.
useEffect(() => {
const handler = (e: KeyboardEvent) => {
if (e.key === 'Escape') onClose();
};
window.addEventListener('keydown', handler);
return () => window.removeEventListener('keydown', handler);
}, [onClose]);
// Derived FEN whenever placements change — keeps the readonly view
// in sync. The user's in-flight fenDraft is a SEPARATE state so
// typing doesn't get clobbered by a re-render.

View file

@ -116,7 +116,10 @@ export function LayoutPicker({
</svg>
</div>
</div>
<p className="text-xs text-neutral-500 leading-relaxed">
<p
data-testid="layout-picker-description"
className="text-xs text-neutral-500 leading-relaxed"
>
{isCustom ? 'Custom layout loaded from your editor.' : value.description}
</p>
{value.suggestedPresets !== undefined &&