From 218b03ceb7a8f728f4262e46db80c6c67ed88673 Mon Sep 17 00:00:00 2001 From: Codeman maintainer Date: Wed, 7 Oct 2026 04:09:04 +0200 Subject: [PATCH] test(tiles): the load-queue test drives TerminalTile on the shared fakes tile-grid-load-queue carried its own FakeSocket, FakeFit and FakeTerminal, near-copies of terminal-tile-input's. It now imports test/mocks/terminal-tile-fakes.ts, which gains what only it used: FakeSocket.drop(code), the terminal's scrollToLine / scrollToTop, and the replay-pace extension (an opt-in `holdParse` that keeps write callbacks from running, as on a disposed xterm, and empty writes left out of `writes`, since the replay queues one only to hear it was parsed). One definition serves both files with no per-file switch: terminal-tile-input passes unchanged with the extension in place, the shared fit resizes to the default 80x24 the tile already has, and FakeSocket.OPEN is the real value. Every assertion is unchanged. Mutation-checked through the shared fakes: dropping destroy()'s replay settle fails the destroy-while-parsing case, a queue that runs two loads at once fails eleven cases, and a tile that never registers its input socket fails six in terminal-tile-input. Co-Authored-By: Claude Opus 5.5 (1M context) --- test/mocks/terminal-tile-fakes.ts | 15 +++++- test/tile-grid-load-queue.test.ts | 78 +------------------------------ 2 files changed, 15 insertions(+), 78 deletions(-) diff --git a/test/mocks/terminal-tile-fakes.ts b/test/mocks/terminal-tile-fakes.ts index d4081cc4..63eb82b9 100644 --- a/test/mocks/terminal-tile-fakes.ts +++ b/test/mocks/terminal-tile-fakes.ts @@ -37,6 +37,11 @@ export class FakeSocket { inputFrames() { return this.sent.filter((f) => f.t === 'i'); } + /** The connection drops: closed, and the tile hears `code`. */ + drop(code = 1006) { + this.readyState = 3; + this.onclose?.({ code }); + } } /** The fit addon: proposes `FakeFit.proposed` and, like the real one, resizes to it (NaN = hidden pane). */ @@ -90,9 +95,13 @@ export class FakeTerminal { } registerLinkProvider() {} writes: string[] = []; + /** Set by a test: write callbacks never run, as on a disposed xterm. */ + holdParse = false; write(data: string, cb?: () => void) { - this.writes.push(data); - cb?.(); + // An empty write puts nothing on screen; the replay queues one only to hear + // (its callback) that everything before it has been parsed. + if (data) this.writes.push(data); + if (!this.holdParse) cb?.(); } clear() { this.writes.push(''); @@ -103,6 +112,8 @@ export class FakeTerminal { this.cols = cols; this.rows = rows; } + scrollToLine() {} + scrollToTop() {} dispose() {} type(data: string) { this.dataCb?.(data); diff --git a/test/tile-grid-load-queue.test.ts b/test/tile-grid-load-queue.test.ts index 59cea824..69ff3ae0 100644 --- a/test/tile-grid-load-queue.test.ts +++ b/test/tile-grid-load-queue.test.ts @@ -23,88 +23,14 @@ * * Real code under test: constants.js + app.js + terminal-ui.js + * terminal-tile.js in one `vm` context; xterm, the fit addon and WebSocket are - * fakes. Port: N/A. + * fakes (test/mocks/terminal-tile-fakes.ts). Port: N/A. */ import { readFileSync } from 'node:fs'; import { performance } from 'node:perf_hooks'; import { resolve } from 'node:path'; import vm from 'node:vm'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; - -class FakeSocket { - static instances: FakeSocket[] = []; - readyState = 0; - sent: Array> = []; - onopen: (() => void) | null = null; - onmessage: ((ev: { data: string }) => void) | null = null; - onclose: ((ev?: { code: number }) => void) | null = null; - onerror: (() => void) | null = null; - constructor(public url: string) { - FakeSocket.instances.push(this); - } - send(data: string) { - this.sent.push(JSON.parse(data)); - } - close = vi.fn(() => { - this.readyState = 3; - }); - open() { - this.readyState = 1; - this.onopen?.(); - } - receive(msg: object) { - this.onmessage?.({ data: JSON.stringify(msg) }); - } - drop(code = 1006) { - this.readyState = 3; - this.onclose?.({ code }); - } -} - -class FakeFit { - term: FakeTerminal | null = null; - fit() {} - proposeDimensions() { - return { cols: 80, rows: 24 }; - } -} - -class FakeTerminal { - options: Record; - cols = 80; - rows = 24; - buffer = { active: { type: 'normal', viewportY: 0, length: 24 } }; - writes: string[] = []; - constructor(options: Record) { - this.options = { ...options }; - } - loadAddon(addon: FakeFit) { - addon.term = this; - } - open() {} - onData() {} - attachCustomKeyEventHandler() {} - registerLinkProvider() {} - textarea = { addEventListener() {}, removeEventListener() {} }; - /** Set by a test: write callbacks never run, as on a disposed xterm. */ - holdParse = false; - write(data: string, cb?: () => void) { - // An empty write puts nothing on screen; the replay queues one only to hear - // (its callback) that everything before it has been parsed. - if (data) this.writes.push(data); - if (!this.holdParse) cb?.(); - } - clear() { - this.writes.push(''); - } - resize(cols: number, rows: number) { - this.cols = cols; - this.rows = rows; - } - scrollToLine() {} - scrollToTop() {} - dispose() {} -} +import { FakeFit, FakeSocket, FakeTerminal } from './mocks/terminal-tile-fakes.js'; /** One `/terminal` fetch the test answers (or lets hang) by hand. */ type Capture = { url: string; settled: boolean; aborted: boolean; answer(body: string): void };