f3914af793
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 14s
CI / ui (pull_request) Successful in 48s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 57s
The board only scrolled to the hint's word when the hint also toggled zoom (the zoom-out case): the recenter effect tracked `zoomed` and read `focus` untracked, so a hint taken while ALREADY zoomed in — no zoom change — never recentred and the board stayed parked where the player was looking. Add a `recenter` nonce the hint bumps; the board's scroll effect tracks it and, when it fires without a zoom toggle, pans straight to `focus` (the board width is stable, so the width-driven zoom tween has nothing to ride). Placing a 2nd+ tile or hovering a dragged tile still set `focus` without the nonce, so the board never jumps on those — the original no-jump intent is preserved. e2e: zoom into the corner, then hint (the mock plays at the centre) — the board pans toward the centre. Verified RED without the fix (both engines), GREEN with.
89 lines
3.7 KiB
TypeScript
89 lines
3.7 KiB
TypeScript
import { expect, test } from './fixtures';
|
|
|
|
// Item 5: zooming the board must enlarge the labels too (a magnifying-glass zoom).
|
|
// cqw is sized against the zoom-scaled board, so the font grows with the cells.
|
|
test('zoom enlarges the board labels with the board', async ({ page }) => {
|
|
await page.goto('/');
|
|
await page.getByRole('button', { name: /guest/i }).click();
|
|
await page.getByRole('button', { name: /Ann/ }).click();
|
|
|
|
const letter = page.locator('[data-cell] .letter').first();
|
|
await expect(letter).toBeVisible();
|
|
const base = await letter.evaluate((el) => parseFloat(getComputedStyle(el).fontSize));
|
|
|
|
// Double-tap an empty cell to zoom in (two synchronous clicks = a double-tap).
|
|
await page
|
|
.locator('[data-cell]:not(.filled)')
|
|
.nth(20)
|
|
.evaluate((el: HTMLElement) => {
|
|
el.click();
|
|
el.click();
|
|
});
|
|
await page.waitForTimeout(400); // let the width transition settle
|
|
|
|
const zoomed = await letter.evaluate((el) => parseFloat(getComputedStyle(el).fontSize));
|
|
expect(zoomed).toBeGreaterThan(base * 1.4);
|
|
});
|
|
|
|
// Item 4 (UX): the zoomed board clamps at its edge — no native rubber-band/overscroll past
|
|
// the content. We assert the CSS contract on the zoomed scroll container, which is
|
|
// deterministic across engines (the bounce gesture itself is a native compositor behaviour
|
|
// Playwright cannot reliably synthesize).
|
|
test('zoomed board clamps overscroll at the edge', async ({ page }) => {
|
|
await page.goto('/');
|
|
await page.getByRole('button', { name: /guest/i }).click();
|
|
await page.getByRole('button', { name: /Ann/ }).click();
|
|
|
|
// Double-tap an empty cell to zoom in.
|
|
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();
|
|
const ob = await viewport.evaluate((el) => {
|
|
const s = getComputedStyle(el);
|
|
return { x: s.overscrollBehaviorX, y: s.overscrollBehaviorY };
|
|
});
|
|
expect(ob.x).toBe('none');
|
|
expect(ob.y).toBe('none');
|
|
});
|
|
|
|
// A hint taken while the board is ALREADY zoomed in must still scroll to the played word — the
|
|
// zoom state does not change, so without an explicit recenter the board stays parked where the
|
|
// player was looking (the reported rough edge). The mock hint plays at the centre star (7,7), so
|
|
// zooming into the top-left corner first and then hinting must pan the board toward the centre.
|
|
test('a hint recentres an already-zoomed board on the played word', async ({ page }) => {
|
|
await page.goto('/');
|
|
await page.getByRole('button', { name: /guest/i }).click();
|
|
await page.getByRole('button', { name: /Ann/ }).click();
|
|
|
|
// Zoom into the top-left cell (far from the centre where the hint lands).
|
|
await page
|
|
.locator('[data-cell]:not(.filled)')
|
|
.first()
|
|
.evaluate((el: HTMLElement) => {
|
|
el.click();
|
|
el.click();
|
|
});
|
|
const viewport = page.locator('.viewport.zoomed');
|
|
await expect(viewport).toBeVisible();
|
|
await page.waitForTimeout(400); // let the zoom-in settle near the top-left corner
|
|
const before = await viewport.evaluate((el) => ({ left: el.scrollLeft, top: el.scrollTop }));
|
|
|
|
// Take a hint (the control confirms on a second tap), which plays at the centre.
|
|
const hint = page.getByRole('button', { name: 'Hint' });
|
|
await hint.click();
|
|
await hint.click();
|
|
await page.waitForTimeout(300); // the board pans to the hint word
|
|
|
|
const after = await viewport.evaluate((el) => ({ left: el.scrollLeft, top: el.scrollTop }));
|
|
// The board panned from the top-left toward the centre word: both offsets grew.
|
|
expect(after.left).toBeGreaterThan(before.left + 20);
|
|
expect(after.top).toBeGreaterThan(before.top + 20);
|
|
});
|