Skip to content

Commit bf7ec49

Browse files
committed
test(pi-ui-tweaks): cover border rendering
1 parent a45656e commit bf7ec49

1 file changed

Lines changed: 144 additions & 1 deletion

File tree

‎packages/pi-ui-tweaks/test/index.test.ts‎

Lines changed: 144 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
import assert from "node:assert/strict";
22
import { test } from "vitest";
3-
import { Input, SelectList } from "@earendil-works/pi-tui";
3+
import {
4+
Editor,
5+
Input,
6+
SelectList,
7+
TuiMainScreen,
8+
type EditorTheme,
9+
type Terminal,
10+
} from "@earendil-works/pi-tui";
11+
import { dirname, join } from "node:path";
12+
import { fileURLToPath, pathToFileURL } from "node:url";
413

514
import {
615
installAutocompletePositionPatch,
@@ -16,6 +25,8 @@ import {
1625
setBashExecPromptSpacing,
1726
type BashExecSpacingEditor,
1827
} from "../src/bash-exec-spacing.ts";
28+
import { installNeutralBorderColorPatch, setNeutralBorderColor } from "../src/border-color.ts";
29+
import { getUiTweaksPatchState } from "../src/patch-state.ts";
1930
import {
2031
getInputPromptPrefix,
2132
installInputPromptPrefixPatch,
@@ -186,6 +197,28 @@ class TestEditor implements BashExecSpacingEditor {
186197
}
187198
}
188199

200+
class FakeTerminal implements Terminal {
201+
columns = 80;
202+
rows = 24;
203+
204+
get kittyProtocolActive(): boolean {
205+
return false;
206+
}
207+
208+
start(): void {}
209+
stop(): void {}
210+
async drainInput(): Promise<void> {}
211+
write(_data: string): void {}
212+
moveBy(): void {}
213+
hideCursor(): void {}
214+
showCursor(): void {}
215+
clearLine(): void {}
216+
clearFromCursor(): void {}
217+
clearScreen(): void {}
218+
setTitle(): void {}
219+
setProgress(): void {}
220+
}
221+
189222
function createTestModelSelector(): TestModelSelector {
190223
return {
191224
addedComponents: [],
@@ -627,3 +660,113 @@ test("slash command source patch is idempotent", () => {
627660

628661
assert.equal(Reflect.get(interactiveMode, "prefixAutocompleteDescription"), patchedPrefix);
629662
});
663+
664+
test("neutral border patch renders real editor borders with the text color", async () => {
665+
const codingAgentEntry = fileURLToPath(import.meta.resolve("@earendil-works/pi-coding-agent"));
666+
const themeUrl = pathToFileURL(
667+
join(dirname(codingAgentEntry), "modes/interactive/theme/theme.js"),
668+
).href;
669+
// Pi's internal theme module is intentionally resolved from the installed runtime package.
670+
const themeModule: unknown = (await import(themeUrl)) as unknown;
671+
if (typeof themeModule !== "object" || themeModule === null) {
672+
assert.fail("missing Pi theme module");
673+
}
674+
const initTheme: unknown = Reflect.get(themeModule, "initTheme");
675+
if (typeof initTheme !== "function") {
676+
assert.fail("invalid Pi theme module");
677+
}
678+
679+
const Theme: unknown = Reflect.get(themeModule, "Theme");
680+
if ((typeof Theme !== "object" && typeof Theme !== "function") || Theme === null) {
681+
assert.fail("invalid Pi Theme constructor");
682+
}
683+
const themePrototype: unknown = Reflect.get(Theme, "prototype");
684+
if (
685+
(typeof themePrototype !== "object" && typeof themePrototype !== "function") ||
686+
themePrototype === null
687+
) {
688+
assert.fail("invalid Pi Theme prototype");
689+
}
690+
const originalFgDescriptor = Reflect.getOwnPropertyDescriptor(themePrototype, "fg");
691+
if (
692+
originalFgDescriptor === undefined ||
693+
typeof Reflect.get(themePrototype, "fg") !== "function"
694+
) {
695+
assert.fail("invalid Pi Theme.fg descriptor");
696+
}
697+
const neutralBorderPatchKey = Symbol.for("zigai.pi-ui-tweaks.neutral-border-color-patched");
698+
const originalPatchMarkerDescriptor = Reflect.getOwnPropertyDescriptor(
699+
themePrototype,
700+
neutralBorderPatchKey,
701+
);
702+
703+
const themeKey = Symbol.for("@earendil-works/pi-coding-agent:theme");
704+
const oldThemeKey = Symbol.for("@mariozechner/pi-coding-agent:theme");
705+
const hadTheme = Reflect.has(globalThis, themeKey);
706+
const hadOldTheme = Reflect.has(globalThis, oldThemeKey);
707+
const originalTheme: unknown = Reflect.get(globalThis, themeKey);
708+
const originalOldTheme: unknown = Reflect.get(globalThis, oldThemeKey);
709+
const originalNeutralBorderColor = getUiTweaksPatchState().neutralBorderColor;
710+
711+
try {
712+
Reflect.apply(initTheme, themeModule, [undefined, false]);
713+
714+
const getEditorTheme: unknown = Reflect.get(themeModule, "getEditorTheme");
715+
const theme: unknown = Reflect.get(themeModule, "theme");
716+
const themeFg: unknown = Reflect.get(theme ?? {}, "fg");
717+
if (typeof getEditorTheme !== "function" || typeof themeFg !== "function") {
718+
assert.fail("invalid Pi theme module");
719+
}
720+
721+
const editorThemeValue: unknown = Reflect.apply(getEditorTheme, themeModule, []);
722+
const textBorder: unknown = Reflect.apply(themeFg, theme, ["text", "─"]);
723+
const mutedBorder: unknown = Reflect.apply(themeFg, theme, ["borderMuted", "─"]);
724+
if (
725+
typeof editorThemeValue !== "object" ||
726+
editorThemeValue === null ||
727+
typeof textBorder !== "string" ||
728+
typeof mutedBorder !== "string"
729+
) {
730+
assert.fail("invalid Pi editor theme");
731+
}
732+
733+
await installNeutralBorderColorPatch();
734+
setNeutralBorderColor(true);
735+
// SAFETY: The runtime adapter above verifies Pi returned the editor-theme object;
736+
// the real Editor constructor exercises its complete contract below.
737+
const editor = new Editor(
738+
new TuiMainScreen(new FakeTerminal()),
739+
editorThemeValue as EditorTheme,
740+
);
741+
const neutralTopBorder = editor.render(12)[0] ?? "";
742+
743+
setNeutralBorderColor(false);
744+
const originalTopBorder = editor.render(12)[0] ?? "";
745+
746+
assert.equal(neutralTopBorder, textBorder.repeat(12));
747+
assert.equal(originalTopBorder, mutedBorder.repeat(12));
748+
assert.notEqual(neutralTopBorder, originalTopBorder);
749+
} finally {
750+
setNeutralBorderColor(originalNeutralBorderColor);
751+
Reflect.defineProperty(themePrototype, "fg", originalFgDescriptor);
752+
if (originalPatchMarkerDescriptor === undefined) {
753+
Reflect.deleteProperty(themePrototype, neutralBorderPatchKey);
754+
} else {
755+
Reflect.defineProperty(
756+
themePrototype,
757+
neutralBorderPatchKey,
758+
originalPatchMarkerDescriptor,
759+
);
760+
}
761+
if (hadTheme) {
762+
Reflect.set(globalThis, themeKey, originalTheme);
763+
} else {
764+
Reflect.deleteProperty(globalThis, themeKey);
765+
}
766+
if (hadOldTheme) {
767+
Reflect.set(globalThis, oldThemeKey, originalOldTheme);
768+
} else {
769+
Reflect.deleteProperty(globalThis, oldThemeKey);
770+
}
771+
}
772+
});

0 commit comments

Comments
 (0)