Skip to content

Commit 3cd4057

Browse files
committed
test(tui): fix geometry invariant and add input-required zone coverage
The collapse-order assertion predates the input_required zone; it now pins worker_wait, then input_required, then prompt. New coverage verifies the zone requests, collapses, and restores heights that sum to the terminal, and that a 24-row narrow terminal keeps both strips in bounds with no overlap.
1 parent 0c3fb27 commit 3cd4057

1 file changed

Lines changed: 80 additions & 0 deletions

File tree

‎src/tui/geometry.test.ts‎

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
resolveGeometry,
1717
type GeometryInput,
1818
} from "./geometry/index.js";
19+
import type { Rect } from "./geometry/resolve.js";
1920

2021
function idle80x24(overrides: Partial<GeometryInput> = {}) {
2122
return resolveGeometry({
@@ -48,6 +49,9 @@ describe("worker wait zone", () => {
4849

4950
test("is the last optional row cut before prompt growth is reclaimed", () => {
5051
expect(COLLAPSE_ORDER.indexOf("worker_wait")).toBe(
52+
COLLAPSE_ORDER.indexOf("input_required") - 1,
53+
);
54+
expect(COLLAPSE_ORDER.indexOf("input_required")).toBe(
5155
COLLAPSE_ORDER.indexOf("prompt") - 1,
5256
);
5357
const layout = resolveGeometry({
@@ -60,6 +64,82 @@ describe("worker wait zone", () => {
6064
});
6165
});
6266

67+
describe("input required zone", () => {
68+
test("requests one row directly on the prompt box without shrinking it", () => {
69+
const idle = idle80x24();
70+
const layout = idle80x24({ visibility: { inputRequired: true } });
71+
const strip = defined(layout.regions.input_required, "input_required");
72+
const prompt = defined(layout.regions.prompt, "prompt");
73+
expect(strip.height).toBe(1);
74+
expect(strip.y + strip.height).toBe(prompt.y);
75+
expect(layout.heights.prompt).toBe(idle.heights.prompt);
76+
expect(layout.transcriptHeight).toBe(idle.transcriptHeight - 1);
77+
expect(
78+
layout.chromeHeight + layout.overlayHeight + layout.transcriptHeight,
79+
).toBe(24);
80+
});
81+
82+
test("collapses before prompt growth and restores to zero after settlement", () => {
83+
const layout = resolveGeometry({
84+
terminal: { columns: 80, rows: 18 },
85+
visibility: {
86+
workerWait: true,
87+
inputRequired: true,
88+
notice: true,
89+
pending: 2,
90+
},
91+
});
92+
// worker_wait is cut first: input_required is the newest optional strip
93+
// and survives whenever one optional row can be retained.
94+
expect(layout.heights.notice).toBe(0);
95+
expect(layout.heights.pending).toBe(0);
96+
expect(layout.heights.worker_wait).toBe(0);
97+
expect(layout.heights.input_required).toBe(1);
98+
expect(
99+
layout.chromeHeight + layout.overlayHeight + layout.transcriptHeight,
100+
).toBe(18);
101+
// A settled strip asks for nothing and adds no region.
102+
const restored = idle80x24();
103+
expect(restored.heights.input_required).toBe(0);
104+
expect(restored.regions.input_required).toBeUndefined();
105+
});
106+
107+
test("a 24-row narrow terminal keeps both strips in bounds with no overlap", () => {
108+
const layout = resolveGeometry({
109+
terminal: { columns: 40, rows: 24 },
110+
visibility: { workerWait: true, inputRequired: true },
111+
});
112+
const regions = Object.values(layout.regions).filter(
113+
(rect): rect is Rect => rect !== undefined,
114+
);
115+
expect(regions.length).toBeGreaterThan(0);
116+
for (const rect of regions) {
117+
expect(rect.y).toBeGreaterThanOrEqual(0);
118+
expect(rect.y + rect.height).toBeLessThanOrEqual(24);
119+
expect(rect.x).toBeGreaterThanOrEqual(0);
120+
expect(rect.x + rect.width).toBeLessThanOrEqual(40);
121+
}
122+
for (let i = 0; i < regions.length; i++) {
123+
const a = regions[i];
124+
if (a === undefined) continue;
125+
for (let j = i + 1; j < regions.length; j++) {
126+
const b = regions[j];
127+
if (b === undefined) continue;
128+
const overlapY = a.y < b.y + b.height && b.y < a.y + a.height;
129+
expect(overlapY).toBe(false);
130+
}
131+
}
132+
// Vertical order: worker_wait above input_required above prompt.
133+
const worker = defined(layout.regions.worker_wait, "worker_wait");
134+
const strip = defined(layout.regions.input_required, "input_required");
135+
const prompt = defined(layout.regions.prompt, "prompt");
136+
expect(worker.y).toBeLessThan(strip.y);
137+
expect(strip.y + strip.height).toBe(prompt.y);
138+
expect(strip.x).toBe(layout.sideMargin);
139+
expect(strip.width).toBe(layout.contentWidth);
140+
});
141+
});
142+
63143
describe("resolveGeometry — 80×24 idle floor", () => {
64144
test("idle default chrome yields transcript ≥ 12", () => {
65145
const layout = idle80x24();

0 commit comments

Comments
 (0)