From dd7774f9adeb539a6033f225d47314d5a8012b26 Mon Sep 17 00:00:00 2001 From: Aamer Akhter Date: Sat, 10 Oct 2026 22:59:54 -0400 Subject: [PATCH] test(mobile): give the touch-scrollback case real local history "keeps terminal touch drag available for scrollback with the visible textarea enabled" built a Codex session on a blank terminal. With Codex in transcriptPageKeys that pane is hollow, so the drag is correctly consumed by the page-key fallback and never reaches scrollLines; the fixture no longer described local scrollback. The case now writes 200 lines into xterm (awaiting the write), turns the visible CJK textarea on explicitly, and asserts the scenario before the drag: normal buffer, baseY > 0, viewport at the bottom, not hollow, wheel forwarding off, cjk-input-visible set. A real touch drag back into history must then call scrollLines with negative lines, move the viewport up, and send nothing to the CLI. Hollow Codex paging stays covered by terminal-touch-paging.browser.test.ts. --- test/mobile/keyboard.test.ts | 50 +++++++++++++++++++++++++++++++----- 1 file changed, 43 insertions(+), 7 deletions(-) diff --git a/test/mobile/keyboard.test.ts b/test/mobile/keyboard.test.ts index 91cf4000..a6ddb765 100644 --- a/test/mobile/keyboard.test.ts +++ b/test/mobile/keyboard.test.ts @@ -1254,7 +1254,11 @@ describe('Virtual Keyboard', () => { }); it('keeps terminal touch drag available for scrollback with the visible textarea enabled', async () => { - const calls = await page.evaluate(async () => { + // A Codex pane with REAL local scrollback (an older inline Codex, or any pane + // whose output scrolled lines into xterm's normal buffer). A blank pane is + // hollow and correctly pages the CLI instead (terminal-touch-paging.browser + // test), so the history is written first and checked before the drag. + const result = await page.evaluate(async () => { app.activeSessionId = 'mobile-touch-scroll-test'; app.sessions.set('mobile-touch-scroll-test', { id: 'mobile-touch-scroll-test', @@ -1262,22 +1266,42 @@ describe('Virtual Keyboard', () => { status: 'running', }); app.hideWelcome(); + const settings = app.loadAppSettingsFromStorage(); + settings.cjkInputEnabled = true; + app.saveAppSettingsToStorage(settings); + app._serverCjkOverride = true; app._updateCjkInputState(); + const history = Array.from({ length: 200 }, (_, i) => `history line ${i}`).join('\r\n') + '\r\n'; + await new Promise((resolve) => app.terminal.write(history, resolve)); + app.terminal.scrollToBottom(); + const buffer = app.terminal.buffer.active; + const before = { + type: buffer.type, + baseY: buffer.baseY, + viewportY: buffer.viewportY, + hollow: app._localScrollbackIsHollow(), + forwards: app._shouldForwardWheelToApp({ shiftKey: false }), + cjkVisible: document.body.classList.contains('cjk-input-visible'), + }; + const originalScrollLines = app.terminal.scrollLines.bind(app.terminal); const scrollCalls: number[] = []; app.terminal.scrollLines = (lines: number) => { scrollCalls.push(lines); return originalScrollLines(lines); }; + const sentToCli: string[] = []; + app._sendInputEphemeral = (_id: string, data: string) => sentToCli.push(data); const target = document.querySelector('#terminalContainer .xterm-screen') ?? document.getElementById('terminalContainer'); - if (!target) return scrollCalls; + if (!target) return { before, scrollCalls, sentToCli, viewportAfter: -1 }; const rect = target.getBoundingClientRect(); const x = rect.left + rect.width / 2; - const startY = rect.top + Math.min(180, rect.height - 20); - const endY = startY - 120; + // The finger moves DOWN the screen, so the content follows it back into history. + const startY = rect.top + 20; + const endY = startY + 120; function createTouch(y: number) { return new Touch({ @@ -1315,11 +1339,23 @@ describe('Virtual Keyboard', () => { }) ); await new Promise((resolve) => setTimeout(resolve, 50)); - return scrollCalls; + app._flushWheelSgrQueue?.(); + return { before, scrollCalls, sentToCli, viewportAfter: app.terminal.buffer.active.viewportY }; }); - expect(calls.length).toBeGreaterThan(0); - expect(calls.some((lines) => lines !== 0)).toBe(true); + // The scenario is real local scrollback with the visible textarea on. + expect(result.before.cjkVisible).toBe(true); + expect(result.before.type).toBe('normal'); + expect(result.before.baseY).toBeGreaterThan(0); + expect(result.before.viewportY).toBe(result.before.baseY); + expect(result.before.hollow).toBe(false); + expect(result.before.forwards).toBe(false); + + // The drag scrolled xterm's own history upward, and sent nothing to the CLI. + expect(result.scrollCalls.length).toBeGreaterThan(0); + expect(result.scrollCalls.some((lines) => lines < 0)).toBe(true); + expect(result.viewportAfter).toBeLessThan(result.before.viewportY); + expect(result.sentToCli).toEqual([]); }); it('keeps typed phone text in the terminal local echo path', async () => {