Files
scrabble-game/ui/e2e/landscape.spec.ts
T
Ilia Denisov 02ef31c464
CI / changes (pull_request) Successful in 1s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Successful in 45s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m13s
feat(ui): landscape iteration — reorder left panel, enable board zoom
Left panel order is now score / history / status / rack / controls (the history
fills the middle and scrolls). Board zoom is re-enabled in landscape with the
same gestures as portrait, but height-driven: the viewport is the full right
pane, the board fits by height as a centred square when zoomed out, and on
zoom-in it magnifies past the pane and pans within it (focus-centred scroll that
rides the magnify transition), occupying the full width up to the left panel.
Board.svelte gains a landscape prop; portrait stays width-driven and unchanged.
landscape.spec.ts now asserts zoom works (the zoomed board overflows the pane).
2026-06-14 19:39:06 +02:00

56 lines
2.8 KiB
TypeScript

import { expect, test, type Page } from './fixtures';
// The landscape (wide) layout: when the viewport is wider than tall, the game switches to a
// two-column layout — the board fills the right side fitted to the height (no zoom), while the
// rack, status, scores, the always-open history and the controls stack in a left panel. These
// lock the key differences from the portrait layout so a future edit surfaces as a failing
// assertion. The rest of the suite runs portrait (see playwright.config.ts); this spec overrides
// the viewport to a wide, short one.
test.use({ viewport: { width: 1100, height: 640 } });
async function openGame(page: Page): Promise<void> {
await page.goto('/');
await page.getByRole('button', { name: /guest/i }).click();
await page.getByRole('button', { name: /Ann/ }).click(); // the seeded active game vs Ann
await expect(page.locator('[data-cell]').first()).toBeVisible();
}
test('landscape lays the game in two columns with the history docked open', async ({ page }) => {
await openGame(page);
// The two-column landscape wrapper is mounted instead of the portrait stacking.
await expect(page.locator('.game-land')).toBeVisible();
// The history is shown without tapping the score bar — it is docked open in the left panel,
// not a slide-down drawer.
await expect(page.locator('.history')).toBeVisible();
// The controls live in the left panel (no bottom tab bar in landscape).
await expect(page.locator('.leftpane .tabbar')).toBeVisible();
});
test('landscape zooms the board on a double-tap and the zoomed board overflows the pane', async ({ page }) => {
await openGame(page);
// Double-tap an empty cell zooms in, same as portrait. The board is height-driven here, so the
// zoomed square grows past the wide pane and becomes pannable (scrollWidth exceeds the viewport).
await page
.locator('[data-cell]:not(.filled)')
.nth(20)
.evaluate((el: HTMLElement) => {
el.click();
el.click();
});
const viewport = page.locator('.viewport.zoomed');
await expect(viewport).toBeVisible();
await page.waitForTimeout(350); // let the width/height magnify settle
const overflows = await viewport.evaluate((el) => el.scrollWidth > el.clientWidth + 5);
expect(overflows).toBe(true);
});
test('landscape still places a tile and commits via the make button', async ({ page }) => {
await openGame(page);
// Placement works the same in landscape: tap a rack tile, tap an empty cell adjacent to the
// seeded word, and the make (✅) control appears next to the rack.
await page.locator('.rack .tile').first().click();
await page.locator('[data-cell][data-row="9"][data-col="6"]').click();
await expect(page.locator('.cell.pending')).toHaveCount(1);
await expect(page.locator('.make')).toBeVisible();
});