cli: terminal app follow-up, narrow-terminal guard test, hidden-box way out and width tests (#1562)
Row 69, built by Darkwing, approved by Dewey (comment 28048) and Filbert
(comment 28055) on manifest c68e6582. Sage gate on c6711453: cli 123/0,
queue 150/0, every scripts suite green.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -191,7 +191,7 @@ test("width counts terminal cells: wide CJK and emoji 2, U+FE0F after a one-cell
|
||||
assert.equal(width(s), cells, JSON.stringify(s));
|
||||
}
|
||||
// East Asian Width W and F outside the blocks above: one from each range.
|
||||
for (const n of [0x2329, 0x232a, 0xa960, 0xfe10, 0xfe50, 0x16fe0, 0x17000, 0x1aff0, 0x1b2fb]) assert.equal(width(String.fromCodePoint(n)), 2, n.toString(16));
|
||||
for (const n of [0x2329, 0x232a, 0xa960, 0xfe10, 0xfe50, 0x16fe0, 0x17000, 0x18d08, 0x1aff0, 0x1b2fb]) assert.equal(width(String.fromCodePoint(n)), 2, n.toString(16));
|
||||
// A line break never splits ❤️ from its U+FE0F; no line is wider than asked.
|
||||
assert.deepEqual(wrapCells("abc❤\uFE0F x", 4), ["abc", "❤\uFE0F x"]);
|
||||
for (const l of wrapCells("ab ❤\uFE0F❤\uFE0F e\u0301e\u0301e\u0301", 4)) assert.ok(width(l) <= 4, JSON.stringify(l));
|
||||
@@ -342,6 +342,68 @@ test("below 40x10 the question is hidden, so every key answers it No, y included
|
||||
assert.equal(g.a.state.status, `resolved ${d.id}: yes`);
|
||||
});
|
||||
|
||||
test("under 40 columns a tall terminal shows the too-small screen even where the decide box would fit; y sends nothing (Filbert N10)", async (t) => {
|
||||
// A plain decision's box fits at cols - 2 from about 29 rows at 39
|
||||
// columns and 35 at 30, so each size here would show it if the app
|
||||
// checked only the box.
|
||||
for (const [cols, rows] of [[39, 29], [35, 31], [30, 35], [39, 33], [39, 40], [39, 60], [36, 60], [30, 80]]) {
|
||||
const s = gated(t, cols, rows, false);
|
||||
await s.a.start();
|
||||
await s.a.input("1");
|
||||
const label = `${cols}x${rows}`;
|
||||
assert.ok(s.a.state.overlay?.asking, label);
|
||||
const flat = s.a.frame.map(strip).join(" ").replace(/\s+/g, " ");
|
||||
assert.match(flat, new RegExp(`The terminal is ${cols}x${rows}; the app needs at least 40x10\\.`), label);
|
||||
assert.match(flat, /A decision question is open; any key answers No\./, label);
|
||||
assert.doesNotMatch(s.screen(), /y resolves/, label);
|
||||
await s.a.input("y");
|
||||
assert.equal(s.a.state.overlay, null, label);
|
||||
assert.equal(s.a.state.status, "not resolved", label);
|
||||
assert.ok(!s.calls.some((c) => c.verb === "decision.resolve"), label);
|
||||
}
|
||||
// Every size from 30 to 39 columns and 33 to 60 rows, short question and long.
|
||||
for (const long of [false, true]) {
|
||||
const s = gated(t, 120, 40, long);
|
||||
await s.a.start();
|
||||
await s.a.input("1");
|
||||
for (let cols = 30; cols < 40; cols++) {
|
||||
for (let rows = 33; rows <= 60; rows++) {
|
||||
const label = `${cols}x${rows}${long ? " long" : ""}`;
|
||||
assert.equal(decideShown(s.a.state.overlay, cols, rows), false, label);
|
||||
assert.match(draw(s.a.state, { cols, rows, color: false, now: NOW }).map(strip).join(" ").replace(/\s+/g, " "), /the app needs at least 40x10\./, label);
|
||||
}
|
||||
}
|
||||
s.a.quit("q");
|
||||
}
|
||||
});
|
||||
|
||||
test("a hidden decide box names the way out, and the status line under it does not promise y (Filbert N12)", async (t) => {
|
||||
const status = (s) => strip(s.a.frame.at(-2)).trim();
|
||||
const s = gated(t, 80, 24, false);
|
||||
await s.a.start();
|
||||
assert.match(status(s), /confirm with y\.$/, "the Inbox's own status, before a digit");
|
||||
await s.a.input("1");
|
||||
assert.match(s.screen(), /y resolves/);
|
||||
assert.match(status(s), /confirm with y\.$/, "true while the box shows");
|
||||
s.a.resize(80, 18);
|
||||
assert.match(s.screen(), /Too small to show this decision\.[\s\S]*Any key answers No\.[\s\S]*Resize, or use mosaic decide\./);
|
||||
assert.equal(status(s), "Too small to confirm here.");
|
||||
assert.doesNotMatch(s.screen(), /with y/);
|
||||
fits(s.a.frame, 80, 18, "hidden at 80x18");
|
||||
// Grown back: the box and the status return.
|
||||
s.a.resize(80, 24);
|
||||
assert.match(s.screen(), /y resolves/);
|
||||
assert.match(status(s), /confirm with y\.$/);
|
||||
// At the smallest size the hidden box is whole, its way out included.
|
||||
s.a.resize(40, 10);
|
||||
assert.match(s.screen(), /Any key answers No\.[\s\S]*Resize, or use mosaic decide\./);
|
||||
assert.doesNotMatch(s.screen(), /cut to fit/);
|
||||
assert.equal(status(s), "Too small to confirm here.");
|
||||
await s.a.input("y");
|
||||
assert.equal(s.a.state.status, "not resolved");
|
||||
assert.ok(!s.calls.some((c) => c.verb === "decision.resolve"));
|
||||
});
|
||||
|
||||
test("a throw while answering the question goes to crash, sends nothing and leaves no job pending", async (t) => {
|
||||
const boom = new Error("the terminal write failed");
|
||||
let fail = false;
|
||||
@@ -624,6 +686,27 @@ test("refused and failed reads keep the last list, marked stale; a refused decid
|
||||
assert.deepEqual([decide.a.state.overlay.kind, decide.a.state.overlay.message], ["error", "refused: unauthenticated"]);
|
||||
});
|
||||
|
||||
test("help or a detail box cut to fit keeps its last line, Esc included (Dewey)", async (t) => {
|
||||
const s = app(t, { cols: 80, rows: 10 });
|
||||
s.raise("git.push.protected", { target: "refactor", question: "Push the release?" });
|
||||
await s.a.start();
|
||||
// The help's last row at full height; at 80x10 it wraps "Esc closes
|
||||
// this help." and the cut keeps that row.
|
||||
const last = (frame) => frame.map(strip).filter((l) => l.includes("│")).at(-1);
|
||||
const whole = draw({ ...s.a.state, overlay: { kind: "help" } }, { cols: 80, rows: 40, color: false, now: NOW });
|
||||
assert.match(whole.map(strip).join(" ").replace(/[\s│]+/g, " "), /Esc closes this help\./);
|
||||
await s.a.input("?");
|
||||
assert.match(s.screen(), /cut to fit/);
|
||||
assert.equal(last(s.a.frame), last(whole));
|
||||
fits(s.a.frame, 80, 10, "help at 80x10");
|
||||
await s.a.input("\x1b");
|
||||
await s.a.input("\r");
|
||||
assert.equal(s.a.state.overlay?.kind, "detail");
|
||||
assert.match(s.screen(), /cut to fit; the whole text is in mosaic inbox --json/);
|
||||
assert.match(s.screen(), /│ Esc back +│/);
|
||||
fits(s.a.frame, 80, 10, "detail at 80x10");
|
||||
});
|
||||
|
||||
test("keys: views, moving, trail of a selection, the command line, help and quit", async (t) => {
|
||||
const s = app(t);
|
||||
const a = s.raise("git.push.protected", { target: "one" });
|
||||
|
||||
Reference in New Issue
Block a user