test(e2e): T2 polish vertical slice + solo regression guards
Adds 8 Playwright scenarios to modifier-profiles.spec.ts under a new
'T2 polish' describe block:
P1 editor undo/redo across 3 distinct type-modifier adds
P2 copy / paste wire: Copy lights the Paste button with a count
P3 paste-type-modifier disabled when clipboard empty (baseline)
P4 conflict panel: seed an invuln-king profile via localStorage,
bind layout=classic, Load, observe error + Fix clears it
P5 modifier-indicator rendered without hover (create-room path,
with the same no-WS-server test.skip fallback T26 uses)
P6 source-chain in pinned panel — test.fixme; ModifierPinnedPanel
computes row.source but does not render it yet
P7 multiplayer propose->approve e2e — test.fixme; needs a
two-context harness this spec doesn't have today. Protocol
coverage lives at packages/server/src/ws.modifier-profile-
consent.test.ts.
P8 multiplayer propose->reject e2e — same harness gap as P7.
Adds 2 regression tests to solo-smoke.spec.ts:
- Rules drawer: clicking the backdrop (far-left of viewport)
closes the drawer and leaves the board interactive. Regression
guard for the stuck-overlay pointer-events bug.
- Modifier editor: Esc closes the editor but leaves the drawer
open (capture-phase stopImmediatePropagation); a second Esc
then closes the drawer. Documents the nested-Esc ordering
contract and guards against a future change that would cascade
both closes on one keystroke.
Result: 55 Playwright passing, 3 skipped (all documented fixme).
bun run check green.
This commit is contained in:
parent
8f5dca9c21
commit
92dae32f31
2 changed files with 403 additions and 0 deletions
|
|
@ -87,4 +87,84 @@ test.describe('Solo-play smoke (T2 preview tests)', () => {
|
|||
throw new Error(`Unexpected errors: ${errors.slice(0, 3).join(' | ')}`);
|
||||
}
|
||||
});
|
||||
|
||||
// T2 regression — clicking the drawer backdrop closes the drawer AND
|
||||
// leaves the board interactive afterwards. Regression guard for a
|
||||
// stuck-overlay bug where the backdrop `pointer-events-none` timing
|
||||
// wasn't cleared, silently blocking drag-to-move.
|
||||
test('rules drawer: backdrop click closes drawer and restores board interactivity', async ({ page }) => {
|
||||
await page.locator('[data-action="play-solo"]').click();
|
||||
await page.waitForURL('**/game');
|
||||
await page.locator('[data-action="open-rules-drawer"]').click();
|
||||
await expect(page.getByTestId('rules-drawer')).toBeVisible();
|
||||
|
||||
// Click the far-left edge of the viewport — the drawer itself
|
||||
// sits on the right (`fixed top-0 right-0 max-w-md`), so x=50 is
|
||||
// guaranteed to land on the backdrop overlay, not the drawer.
|
||||
await page.mouse.click(50, 300);
|
||||
await expect(page.getByTestId('rules-drawer')).not.toBeVisible({ timeout: 1000 });
|
||||
|
||||
// Board must be interactive: drag e2→e4 and see the pawn land on e4.
|
||||
// Any stuck-overlay regression shows up here as a silent no-op drag.
|
||||
await page
|
||||
.locator('[data-square="e2"] [data-piece]')
|
||||
.dragTo(page.locator('[data-square="e4"]'));
|
||||
await expect(page.locator('[data-square="e4"] [data-piece]')).toBeVisible({
|
||||
timeout: 3000,
|
||||
});
|
||||
});
|
||||
|
||||
// T2 regression — nested-modal Esc ordering. With BOTH the rules
|
||||
// drawer AND the modifier editor open, a single Esc must close the
|
||||
// editor ONLY; the drawer stays. A second Esc then closes the
|
||||
// drawer. This is enforced by ModifierProfileEditor installing its
|
||||
// keydown handler in the capture phase with `stopImmediatePropagation`,
|
||||
// so the drawer's window-level Esc handler never fires on the same
|
||||
// keystroke. Without that guard both would close, leaving the board
|
||||
// behind a briefly-lingering pointer-events-blocking backdrop.
|
||||
test('modifier editor: Esc closes editor first, drawer stays open; 2nd Esc closes drawer', async ({ page }) => {
|
||||
await page.locator('[data-action="play-solo"]').click();
|
||||
await page.waitForURL('**/game');
|
||||
await page.locator('[data-action="open-rules-drawer"]').click();
|
||||
await page.locator('[data-testid="open-modifier-editor"]').click();
|
||||
await expect(page.getByTestId('modifier-editor-modal')).toBeVisible();
|
||||
|
||||
// First Esc: editor closes; drawer still visible.
|
||||
//
|
||||
// The editor's capture-phase keydown listener handles this event
|
||||
// with stopImmediatePropagation(), so the drawer's bubble-phase
|
||||
// listener is suppressed. Editor unmounts → its useEffect cleanup
|
||||
// removes the listener, BUT that cleanup runs during React's
|
||||
// commit phase — not synchronously after the state update. A
|
||||
// too-fast second Esc can still hit the stale editor listener
|
||||
// before it has fully detached. The small wait below lets
|
||||
// React complete its commit so the second Esc sees only the
|
||||
// drawer's listener.
|
||||
await page.keyboard.press('Escape');
|
||||
await expect(page.getByTestId('modifier-editor-modal')).not.toBeVisible({
|
||||
timeout: 500,
|
||||
});
|
||||
await expect(page.getByTestId('rules-drawer')).toBeVisible();
|
||||
|
||||
// Second Esc: drawer closes. (See note above re: the wait.)
|
||||
//
|
||||
// The drawer exits via a framer-motion spring animation that
|
||||
// takes ~300–500ms to fully unmount the `<aside>` — too tight a
|
||||
// timeout here flakes even when the close did fire. We use 1500ms
|
||||
// to match the backdrop-click test and leave headroom.
|
||||
await page.waitForTimeout(50);
|
||||
await page.keyboard.press('Escape');
|
||||
await expect(page.getByTestId('rules-drawer')).not.toBeVisible({
|
||||
timeout: 1500,
|
||||
});
|
||||
|
||||
// Board must be fully interactive again.
|
||||
await page
|
||||
.locator('[data-square="e2"] [data-piece]')
|
||||
.dragTo(page.locator('[data-square="e4"]'));
|
||||
await expect(page.locator('[data-square="e4"] [data-piece]')).toBeVisible({
|
||||
timeout: 3000,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue