feat: add ask settings modal shell

This commit is contained in:
Erik Kotsiuba
2026-04-28 22:35:43 +02:00
parent b030b90257
commit 5c0d8964fb
10 changed files with 322 additions and 142 deletions
+58
View File
@@ -0,0 +1,58 @@
import assert from "node:assert/strict";
import test from "node:test";
import { registerAskSettingsCommand } from "../src/ask-settings-command.ts";
function plainTheme() {
return {
fg(_color: string, text: string) {
return text;
},
bg(_color: string, text: string) {
return text;
},
};
}
test("registers /ask-settings and opens the shared modal overlay", async () => {
const commands = new Map<
string,
{ handler: (args: string, ctx: any) => Promise<void> }
>();
registerAskSettingsCommand({
registerCommand(
name: string,
command: { handler: (args: string, ctx: any) => Promise<void> }
) {
commands.set(name, command);
},
} as never);
assert(commands.has("ask-settings"));
const customCalls: Array<{ options: unknown; lines: string[] }> = [];
await commands.get("ask-settings")?.handler("", {
ui: {
custom(callback: any, options: unknown) {
const done = () => {
// test callback intentionally unused
};
const component = callback(undefined, plainTheme(), undefined, done);
customCalls.push({ options, lines: component.render(72) });
return Promise.resolve();
},
},
});
assert.equal(customCalls.length, 1);
assert.deepEqual(customCalls[0]?.options, {
overlay: true,
overlayOptions: {
anchor: "center",
margin: 1,
maxHeight: "90%",
minWidth: 26,
width: 72,
},
});
assert(customCalls[0]?.lines.join("\n").includes("Keymaps"));
});
+32 -14
View File
@@ -2,7 +2,7 @@ import assert from "node:assert/strict";
import test from "node:test";
import { visibleWidth } from "@mariozechner/pi-tui";
import { ASK_HELP_CLOSE_HINT, ASK_KEYMAPS } from "../src/constants/keymaps.ts";
import { AskHelpModal } from "../src/ui/help-modal.ts";
import { AskSettingsModal } from "../src/ui/settings-modal.ts";
const noop = () => {
// Test callback intentionally does nothing.
@@ -19,20 +19,23 @@ function plainTheme() {
};
}
test("help modal renders compactly on phone-width screens", () => {
const modal = new AskHelpModal(plainTheme(), noop);
test("settings modal renders compactly on phone-width screens", () => {
const modal = new AskSettingsModal(plainTheme(), noop);
const lines = modal.render(28);
assert(lines.length > ASK_KEYMAPS.length + 4);
assert(lines.every((line) => visibleWidth(line) <= 28));
assert(lines.join("\n").includes("@eko24ive/pi-ask"));
assert(lines.slice(-3, -1).join(" ").includes("Esc, Ctrl+C, or ?"));
assert(lines[1]?.includes("Keymaps"));
assert.equal(lines[2], "│ │");
assert(lines.includes("│ ? │"));
assert(lines.includes("│ Show this menu │"));
assert.equal(lines[0]?.includes("├"), false);
});
test("help modal keeps context rows on wider screens", () => {
const modal = new AskHelpModal(plainTheme(), noop);
test("settings modal keeps context rows on wider screens", () => {
const modal = new AskSettingsModal(plainTheme(), noop);
const lines = modal.render(72);
assert(lines.length > ASK_KEYMAPS.length + 4);
@@ -40,22 +43,37 @@ test("help modal keeps context rows on wider screens", () => {
assert(lines.at(-2)?.includes(ASK_HELP_CLOSE_HINT));
});
test("help modal closes from configured keyboard shortcuts", () => {
test("settings modal switches tabs and closes from configured shortcuts", () => {
let closed = 0;
const modal = new AskSettingsModal(plainTheme(), () => {
closed += 1;
});
modal.handleInput("\t");
assert(
modal.render(72).join("\n").includes("Behaviour settings coming soon.")
);
assert(modal.render(72).join("\n").includes("Behaviour"));
modal.handleInput("\u001b[Z");
assert.equal(
modal.render(72).join("\n").includes("Behaviour settings coming soon."),
false
);
for (const input of ["?", "\u001b", "\u0003"]) {
let closed = 0;
const modal = new AskHelpModal(plainTheme(), () => {
const closable = new AskSettingsModal(plainTheme(), () => {
closed += 1;
});
modal.handleInput(input);
assert.equal(closed, 1);
closable.handleInput(input);
}
assert.equal(closed, 3);
});
test("help modal ignores enter and closes idempotently on dispose", () => {
test("settings modal ignores enter and closes idempotently on dispose", () => {
let closed = 0;
const modal = new AskHelpModal(plainTheme(), () => {
const modal = new AskSettingsModal(plainTheme(), () => {
closed += 1;
});