de11ae46a2
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 41s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 57s
The gesture was disabled inside Telegram on the mistaken assumption it would
fight a Telegram horizontal close gesture — but Telegram's only close/minimise
gesture is vertical (disableVerticalSwipes, Bot API 7.7+). Disabling it there,
together with dropping the old left-edge swipe, removed every swipe-back in
Telegram (which that edge-swipe — deliberately enabled in TG, commit 7e34897 —
had provided).
Make shouldArm Telegram-agnostic (drop the insideTelegram guard + its now-unused
import); the gesture works in Telegram too, alongside the native BackButton.
Drop the redundant overscroll-behavior-x on .router (body already sets
overscroll-behavior: none). Regression: an e2e drives the swipe under an
injected Telegram WebApp stub and asserts it navigates back.
124 lines
4.5 KiB
TypeScript
124 lines
4.5 KiB
TypeScript
import { expect, test, type Page } from './fixtures';
|
|
|
|
// The interactive back-swipe (left-band rightward drag → parent screen). It ignores mouse
|
|
// pointers and the Playwright desktop projects have no touch input, so these specs dispatch
|
|
// PointerEvents with pointerType:'touch' straight at the window capture listener. The
|
|
// controller hit-tests via elementFromPoint, so the start coordinate picks the element under
|
|
// the press — starting on the board lets us assert the zoom-out-only rule directly.
|
|
//
|
|
// A phone-sized viewport: the gesture's left band is a fraction of the screen width, so on a
|
|
// wide desktop the centred board sits outside it. Phones are the real target and there the
|
|
// full-width board fills the band.
|
|
test.use({ viewport: { width: 390, height: 844 } });
|
|
|
|
const MID_Y = 422; // mid-board on the 844-tall viewport
|
|
const START_X = 30; // inside the left band, over the board's left column
|
|
|
|
async function openAnnGame(page: Page): Promise<void> {
|
|
await page.goto('/');
|
|
await page.getByRole('button', { name: /guest/i }).click();
|
|
await page.getByRole('button', { name: /Ann/ }).click();
|
|
await expect(page.locator('[data-cell]').first()).toBeVisible();
|
|
await page.waitForTimeout(400); // let the open slide settle before measuring/aiming
|
|
}
|
|
|
|
// A minimal Telegram WebApp stub: non-empty initData makes insideTelegram() true and drives
|
|
// the Mini App auto-auth into the lobby (the mock gateway accepts any initData).
|
|
function telegramStub() {
|
|
return {
|
|
Telegram: {
|
|
WebApp: {
|
|
initData: 'query_id=test&user=%7B%22id%22%3A1%7D&auth_date=1&hash=deadbeef',
|
|
initDataUnsafe: {},
|
|
themeParams: {},
|
|
ready() {},
|
|
expand() {},
|
|
},
|
|
},
|
|
};
|
|
}
|
|
|
|
/** touchDrag dispatches a single-finger touch drag from (x0,y0) to (x1,y1) over the window. */
|
|
async function touchDrag(page: Page, x0: number, y0: number, x1: number, y1: number): Promise<void> {
|
|
await page.evaluate(
|
|
({ x0, y0, x1, y1 }) => {
|
|
const steps = 8;
|
|
const fire = (type: string, x: number, y: number) =>
|
|
window.dispatchEvent(
|
|
new PointerEvent(type, {
|
|
pointerType: 'touch',
|
|
clientX: x,
|
|
clientY: y,
|
|
bubbles: true,
|
|
cancelable: true,
|
|
}),
|
|
);
|
|
fire('pointerdown', x0, y0);
|
|
for (let i = 1; i <= steps; i++) {
|
|
fire('pointermove', x0 + ((x1 - x0) * i) / steps, y0 + ((y1 - y0) * i) / steps);
|
|
}
|
|
fire('pointerup', x1, y1);
|
|
},
|
|
{ x0, y0, x1, y1 },
|
|
);
|
|
}
|
|
|
|
test('a left-band rightward swipe on a zoomed-out board returns to the lobby', async ({ page }) => {
|
|
await openAnnGame(page);
|
|
|
|
// Drag from the left band well past the commit threshold.
|
|
await touchDrag(page, START_X, MID_Y, 300, MID_Y);
|
|
|
|
// Back at the lobby: the URL returns to the root and the seeded game row is shown again.
|
|
await expect(page).toHaveURL(/#\/$/);
|
|
await expect(page.getByRole('button', { name: /Ann/ })).toBeVisible();
|
|
});
|
|
|
|
test('a swipe on a zoomed-in board does not navigate (board pan owns it)', async ({ page }) => {
|
|
await openAnnGame(page);
|
|
|
|
// Double-tap an empty cell to zoom in, then wait for the zoomed viewport.
|
|
await page
|
|
.locator('[data-cell]:not(.filled)')
|
|
.nth(20)
|
|
.evaluate((el: HTMLElement) => {
|
|
el.click();
|
|
el.click();
|
|
});
|
|
await expect(page.locator('.viewport.zoomed')).toBeVisible();
|
|
await page.waitForTimeout(300);
|
|
|
|
await touchDrag(page, START_X, MID_Y, 300, MID_Y);
|
|
|
|
// Still on the game: the swipe was suppressed over the zoomed board.
|
|
await page.waitForTimeout(300);
|
|
expect(page.url()).toContain('/game/');
|
|
});
|
|
|
|
test('a vertical drag in the band scrolls instead of navigating', async ({ page }) => {
|
|
await openAnnGame(page);
|
|
|
|
// A downward-dominant drag must lose to the native scroll and never go back.
|
|
await touchDrag(page, START_X, 260, START_X + 20, 700);
|
|
|
|
await page.waitForTimeout(300);
|
|
expect(page.url()).toContain('/game/');
|
|
});
|
|
|
|
test('the back-swipe also works inside Telegram (its only swipe gesture is vertical)', async ({ page }) => {
|
|
await page.addInitScript((stub) => {
|
|
Object.assign(window, stub);
|
|
}, telegramStub());
|
|
await page.goto('/');
|
|
|
|
// Telegram auto-authenticates into the lobby (no guest click), then open the seeded game.
|
|
await page.getByRole('button', { name: /Ann/ }).click();
|
|
await expect(page.locator('[data-cell]').first()).toBeVisible();
|
|
await page.waitForTimeout(400);
|
|
|
|
await touchDrag(page, START_X, MID_Y, 300, MID_Y);
|
|
|
|
await expect(page).toHaveURL(/#\/$/);
|
|
await expect(page.getByRole('button', { name: /Ann/ })).toBeVisible();
|
|
});
|