Round-6 follow-up: UX polish + client-IP fix
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 8s
CI / integration (pull_request) Successful in 13s
CI / ui (pull_request) Successful in 32s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m8s
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 8s
CI / integration (pull_request) Successful in 13s
CI / ui (pull_request) Successful in 32s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m8s
- Client IP: the compose caddy trusts X-Forwarded-For from private-range upstreams (trusted_proxies private_ranges), so the real client IP survives the host-caddy hop (it was logging the docker caddy hop 172.18.0.x for chat moderation and bucketing the gateway per-IP rate limiter on it). Correct and spoof-safe in both contours (prod has no host caddy); peerIP unit-tested. - Ad banner gated off behind a compile-time SHOW_AD_BANNER=false (the if-branch, the AdBanner import and banner.ts are tree-shaken out of the prod bundle). - Landing: the Telegram entry is just the 64px logo (clickable, no button/text). - TG-fullscreen header: title + menu centred as a pair (hamburger right of the title), pinned to the bottom of the TG nav band. - Edge-swipe back (Screen): a left-edge rightward drag navigates to back (touch/pen only, armed from <=24px; skipped inside Telegram). - Chat soft-keyboard: a bottom-sheet Modal lifted above the keyboard by a visualViewport-driven transform (compositor-only, no page/sheet relayout). iOS-specific, needs on-device tuning; native resize=none awaits Capacitor. - Tests: e2e for the in-game '✓ in friends' item and a board→board tile relocation; codec units for last_activity_unix + OutgoingRequestList. Deferred to the next PR (agreed): #4 enrich the your-turn/game-end push; #5 hide finished games from the lobby.
This commit is contained in:
@@ -139,6 +139,29 @@ test('dropping the game ends it and shows the result', async ({ page }) => {
|
||||
await expect(page.locator('.status .over')).toBeVisible();
|
||||
});
|
||||
|
||||
test('a placed tile drags from one board cell to another (Stage 17 relocation)', async ({ page }) => {
|
||||
await openGame(page);
|
||||
await page.locator('.rack .tile').first().click();
|
||||
await page.locator('[data-cell]:not(.filled)').nth(30).click();
|
||||
const pending = page.locator('[data-cell].pending');
|
||||
await expect(pending).toHaveCount(1);
|
||||
const from = `${await pending.first().getAttribute('data-row')},${await pending.first().getAttribute('data-col')}`;
|
||||
|
||||
const target = page.locator('[data-cell]:not(.filled):not(.pending)').nth(45);
|
||||
const fb = await pending.first().boundingBox();
|
||||
const tb = await target.boundingBox();
|
||||
// Pointer-drag the placed tile to a new cell (mouse events synthesise pointer events).
|
||||
await page.mouse.move(fb!.x + fb!.width / 2, fb!.y + fb!.height / 2);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(tb!.x + tb!.width / 2, tb!.y + tb!.height / 2, { steps: 10 });
|
||||
await page.mouse.up();
|
||||
|
||||
// Still exactly one pending tile (relocated, not duplicated), now at a different cell.
|
||||
await expect(pending).toHaveCount(1);
|
||||
const to = `${await pending.first().getAttribute('data-row')},${await pending.first().getAttribute('data-col')}`;
|
||||
expect(to).not.toBe(from);
|
||||
});
|
||||
|
||||
test('the board-label mode in Settings changes the on-board labels', async ({ page }) => {
|
||||
await openGame(page);
|
||||
// beginner (default) renders split "3× / word" labels.
|
||||
|
||||
@@ -122,6 +122,18 @@ test('game: add-to-friends flips to a disabled "request sent"', async ({ page })
|
||||
await expect(sent).toBeDisabled();
|
||||
});
|
||||
|
||||
test('game: an opponent who is already a friend shows a disabled "in friends"', async ({ page }) => {
|
||||
await loginLobby(page);
|
||||
await page.getByRole('button', { name: /Kaya/ }).click(); // the finished game vs Kaya, a seeded friend
|
||||
await page.locator('.burger').first().click();
|
||||
// The in-game friend item is derived from the server's friend list (Stage 17): a friend reads
|
||||
// a disabled "✓ in friends", not the addable "Add to friends".
|
||||
const inFriends = page.getByRole('button', { name: /in friends/i });
|
||||
await expect(inFriends).toBeVisible();
|
||||
await expect(inFriends).toBeDisabled();
|
||||
await expect(page.getByRole('button', { name: /Add to friends: Kaya/ })).toHaveCount(0);
|
||||
});
|
||||
|
||||
test('profile edit disables Save and flags an invalid display name', async ({ page }) => {
|
||||
await loginLobby(page);
|
||||
await page.locator('.burger').first().click();
|
||||
|
||||
Reference in New Issue
Block a user