a41c35d5f9
CI / changes (pull_request) Successful in 2s
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 2m10s
- Stop a two-finger pinch-out from also opening the history: the board wrapper arms its open/close pull only while a single pointer is down (a 2nd finger is a pinch, owned by Board). - Widen the edge-swipe-back activation band to the left half of the viewport (was 20%). - Align a chat nudge by sender like a bubble — your own to the right, the opponent's to the left (only the alignment changes). - Kill the iOS rubber-band inside the history drawer (overscroll-behavior: none). e2e: a two-finger pinch does not open the history; a back-swipe from the left half navigates back.
118 lines
4.8 KiB
TypeScript
118 lines
4.8 KiB
TypeScript
import { expect, test, type Page } from './fixtures';
|
||
|
||
// The redesigned move history: a ruled matrix with one column per seat (no player names, no
|
||
// running total), opened by a swipe-down on the zoom-out board. The gesture is touch-only and
|
||
// the desktop projects have no touch input, so the swipe is dispatched as PointerEvents with
|
||
// pointerType:'touch'; a phone viewport so the board fills the width.
|
||
test.use({ viewport: { width: 390, height: 844 } });
|
||
|
||
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 G1 (your turn)
|
||
await expect(page.locator('[data-cell]').first()).toBeVisible();
|
||
await expect(page.locator('.pane')).toHaveCount(1); // let the screen-slide settle
|
||
}
|
||
|
||
/** touchSwipeDown dispatches a single-finger vertical touch drag on the board wrapper. */
|
||
async function touchSwipeDown(page: Page, fromY: number, toY: number): Promise<void> {
|
||
const box = (await page.locator('.boardwrap').boundingBox())!;
|
||
const x = box.x + box.width / 2;
|
||
await page.locator('.boardwrap').evaluate(
|
||
(el, { x, fromY, toY }) => {
|
||
const fire = (type: string, y: number) =>
|
||
el.dispatchEvent(
|
||
new PointerEvent(type, { pointerType: 'touch', clientX: x, clientY: y, bubbles: true, cancelable: true }),
|
||
);
|
||
fire('pointerdown', fromY);
|
||
for (let i = 1; i <= 8; i++) fire('pointermove', fromY + ((toY - fromY) * i) / 8);
|
||
fire('pointerup', toY);
|
||
},
|
||
{ x, fromY, toY },
|
||
);
|
||
}
|
||
|
||
test('a swipe-down on the zoom-out board opens the history', async ({ page }) => {
|
||
await openGame(page);
|
||
await page.locator('.stage').evaluate((el) => (el.scrollTop = 0)); // the pull only opens at the top
|
||
const box = (await page.locator('.boardwrap').boundingBox())!;
|
||
|
||
await touchSwipeDown(page, box.y + 20, box.y + 180);
|
||
|
||
await expect(page.locator('.history')).toBeVisible();
|
||
await expect(page.locator('.boardwrap.slid')).toBeVisible();
|
||
});
|
||
|
||
test('the history is a per-seat grid with move scores but no names or running total', async ({ page }) => {
|
||
await openGame(page);
|
||
await page.locator('.scoreboard').click(); // open the history (gesture is covered separately)
|
||
|
||
const grid = page.locator('.hgrid');
|
||
await expect(grid).toBeVisible();
|
||
// G1: You [HELLO 16, RAT 3] vs Ann [WORLD 9, AND 4] — a 2×2 matrix, no empty/thinking cells.
|
||
await expect(page.locator('.hcell')).toHaveCount(4);
|
||
await expect(grid).toContainText('HELLO');
|
||
await expect(grid).toContainText('(3)'); // RAT's per-move score
|
||
// The running total (RAT's was 19) and the seat names no longer appear in the table.
|
||
await expect(grid).not.toContainText('19');
|
||
await expect(grid).not.toContainText('Ann');
|
||
});
|
||
|
||
test('a two-finger pinch does not open the history (pinch-zoom vs swipe-down)', async ({ page }) => {
|
||
await openGame(page);
|
||
await page.locator('.stage').evaluate((el) => (el.scrollTop = 0));
|
||
const box = (await page.locator('.boardwrap').boundingBox())!;
|
||
const cx = box.x + box.width / 2;
|
||
const y0 = box.y + 40;
|
||
|
||
// Two fingers land, then spread apart while their centroid drifts down — a pinch-out whose
|
||
// downward component used to also slide the history open. With the single-pointer guard the
|
||
// second finger disarms the pull, so the history stays closed.
|
||
await page.locator('.boardwrap').evaluate(
|
||
(el, { cx, y0 }) => {
|
||
const fire = (type: string, id: number, x: number, y: number) =>
|
||
el.dispatchEvent(
|
||
new PointerEvent(type, {
|
||
pointerType: 'touch',
|
||
pointerId: id,
|
||
isPrimary: id === 1,
|
||
clientX: x,
|
||
clientY: y,
|
||
bubbles: true,
|
||
cancelable: true,
|
||
}),
|
||
);
|
||
fire('pointerdown', 1, cx - 20, y0);
|
||
fire('pointerdown', 2, cx + 20, y0);
|
||
for (let i = 1; i <= 8; i++) {
|
||
const d = i * 18;
|
||
fire('pointermove', 1, cx - 20 - d, y0 + d);
|
||
fire('pointermove', 2, cx + 20 + d, y0 + d);
|
||
}
|
||
fire('pointerup', 1, cx - 164, y0 + 144);
|
||
fire('pointerup', 2, cx + 164, y0 + 144);
|
||
},
|
||
{ cx, y0 },
|
||
);
|
||
|
||
await expect(page.locator('.history')).toHaveCount(0);
|
||
});
|
||
|
||
test('a swipe-down on the zoomed-in board does not open the history (native scroll wins)', async ({ page }) => {
|
||
await openGame(page);
|
||
// 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 expect(page.locator('.viewport.zoomed')).toBeVisible();
|
||
const box = (await page.locator('.boardwrap').boundingBox())!;
|
||
|
||
await touchSwipeDown(page, box.y + 20, box.y + 180);
|
||
|
||
await expect(page.locator('.history')).toHaveCount(0);
|
||
});
|