From abe28ee04bd160a7ea8b8244ecae2240a63baffa Mon Sep 17 00:00:00 2001 From: praxagent Date: Tue, 28 Jul 2026 02:13:03 +0000 Subject: [PATCH] test(mobile): the keyboard e2e tests the merged PR claimed to have MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR #59's description said the suite covered the keyboard chain — focus -> html.keyboard-open -> tab bar hidden -> padding released -> back on blur. It did not: I wrote these tests after committing the spec, ran them, quoted their results in the PR body, and never committed them. The five layout tests were in; the two keyboard tests existed only as an uncommitted local edit this checkout tripped over. So the PR claimed verification it did not carry. The tests themselves were real and passing — against the live instance, including the first-run failure where they targeted a tab with no composer — but a test that lives only in a working tree verifies nothing for anyone else. Both pass against the live instance. --- frontend/e2e/mobile.spec.ts | 64 ++++++++++++++++++++++++++++++++++--- 1 file changed, 60 insertions(+), 4 deletions(-) diff --git a/frontend/e2e/mobile.spec.ts b/frontend/e2e/mobile.spec.ts index a79c92e..0c919bf 100644 --- a/frontend/e2e/mobile.spec.ts +++ b/frontend/e2e/mobile.spec.ts @@ -18,10 +18,17 @@ * Run against a live instance: * BASE_URL=https://teamwork.your-tailnet.ts.net npx playwright test e2e/mobile.spec.ts * - * NOT covered here, and worth being explicit about: the on-screen KEYBOARD. - * Playwright cannot summon one, so `visualViewport` never shrinks and the - * composer-hidden-by-the-tab-bar class of bug stays device-only. Focus events - * fire, so the class toggle is checkable; the geometry it exists to fix is not. + * The KEYBOARD is more testable than it first appears. Playwright cannot summon + * one, so `visualViewport` never shrinks — but a keyboard only ever appears + * BECAUSE a field took focus, and focus is fully driveable. So the chain + * + * field focused -> html.keyboard-open -> tab bar hidden, padding released + * + * can be verified link by link here. What remains device-only is narrow: whether + * the visual viewport lands exactly where we expect once the keyboard is up. + * (An Android emulator would close even that, but this build machine has no + * /dev/kvm and no vmx/svm flags, so an AVD would run in software at ~10-50x + * slowdown — too slow to be a test loop.) */ import { devices, expect, test, type Page } from '@playwright/test'; @@ -138,3 +145,52 @@ test.describe('mobile layout', () => { expect(clipped, 'content is clipped with no way to scroll to it').toEqual([]); }); }); + +test.describe('keyboard', () => { + test('focusing a text field hides the tab bar and releases its padding', async ({ page }) => { + await openWorkspace(page); + + const bar = page.locator('.mobile-tab-bar'); + await expect(bar, 'the bar should be visible before typing').toBeVisible(); + + // The composer lives in Chat; a fresh context can land on another tab. + await page.locator('.mobile-tab-bar button', { hasText: 'Chat' }).click(); + const field = page.locator('textarea, input[type="text"]').first(); + await expect(field, 'no composer found in Chat').toBeVisible({ timeout: 15_000 }); + await field.focus(); + + // The class the CSS hangs on. + await expect + .poll(() => page.evaluate(() => document.documentElement.classList.contains('keyboard-open')), + { message: 'focusing a field did not mark the document' }) + .toBe(true); + + // And what it is FOR: the bar that was covering the composer. + await expect(bar, 'the tab bar still covers the composer while typing').toBeHidden(); + + // Panes reserve room for the bar; they must give it back at the same + // moment or a strip of dead space sits where it was. + const padding = await page.evaluate(() => { + const el = document.querySelector('.pb-mobile-nav') as HTMLElement | null; + return el ? getComputedStyle(el).paddingBottom : null; + }); + if (padding !== null) { + expect(padding, 'the reserved strip was not reclaimed').toBe('0px'); + } + }); + + test('blurring brings the tab bar back', async ({ page }) => { + await openWorkspace(page); + await page.locator('.mobile-tab-bar button', { hasText: 'Chat' }).click(); + const field = page.locator('textarea, input[type="text"]').first(); + await expect(field).toBeVisible({ timeout: 15_000 }); + await field.focus(); + await expect(page.locator('.mobile-tab-bar')).toBeHidden(); + + await field.blur(); + await expect( + page.locator('.mobile-tab-bar'), + 'navigation must come back when you stop typing', + ).toBeVisible(); + }); +});