c276cd6eff
Also normalizes setting labels/descriptions and carries the package changelog entries for this batch (adjacent unreleased bullets are not splittable per commit), restoring section placement disturbed by the two prior changelog hunks.
161 lines
5.3 KiB
TypeScript
161 lines
5.3 KiB
TypeScript
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "bun:test";
|
|
import { resetSettingsForTest, Settings, settings } from "@oh-my-pi/pi-coding-agent/config/settings";
|
|
import { SettingsSelectorComponent } from "@oh-my-pi/pi-coding-agent/modes/components/settings-selector";
|
|
import { initTheme } from "@oh-my-pi/pi-coding-agent/modes/theme/theme";
|
|
|
|
beforeAll(async () => {
|
|
await initTheme();
|
|
});
|
|
|
|
beforeEach(async () => {
|
|
resetSettingsForTest();
|
|
await Settings.init({ inMemory: true });
|
|
});
|
|
|
|
afterEach(() => {
|
|
resetSettingsForTest();
|
|
});
|
|
|
|
function createSelector(onCancel: () => void = () => {}): SettingsSelectorComponent {
|
|
return new SettingsSelectorComponent(
|
|
{
|
|
availableThinkingLevels: [],
|
|
thinkingLevel: undefined,
|
|
availableThemes: ["dark"],
|
|
cwd: process.cwd(),
|
|
},
|
|
{
|
|
onChange: () => {},
|
|
onCancel,
|
|
},
|
|
);
|
|
}
|
|
|
|
/** Switch the selector to the memory tab. SETTING_TABS puts memory at index 4 (after appearance/model/interaction/context). */
|
|
function focusMemoryTab(comp: SettingsSelectorComponent): void {
|
|
for (let i = 0; i < 4; i++) {
|
|
comp.handleInput("\x1b[C");
|
|
}
|
|
}
|
|
|
|
describe("SettingsSelectorComponent memory tab", () => {
|
|
it("reveals condition-gated Hindsight rows the moment memory.backend changes via the submenu", () => {
|
|
settings.set("memory.backend", "off");
|
|
const comp = createSelector();
|
|
focusMemoryTab(comp);
|
|
// Width 70 keeps the flat single-column layout (the wide split layout
|
|
// shows only the active section's rows, covered by the sidebar test).
|
|
const before = comp.render(70).join("\n");
|
|
expect(before).toContain("Memory Backend");
|
|
expect(before).not.toContain("Hindsight API URL");
|
|
|
|
// Memory Backend is the only visible row, so it's already selected at index 0.
|
|
// Enter opens the SelectSubmenu pre-positioned on "off"; navigate to "hindsight" (index 2) and confirm.
|
|
comp.handleInput("\n");
|
|
comp.handleInput("\x1b[B");
|
|
comp.handleInput("\x1b[B");
|
|
comp.handleInput("\n");
|
|
|
|
expect(settings.get("memory.backend")).toBe("hindsight");
|
|
const after = comp.render(70).join("\n");
|
|
expect(after).toContain("Memory Backend");
|
|
expect(after).toContain("Hindsight API URL");
|
|
expect(after).toContain("Hindsight Auto Recall");
|
|
});
|
|
|
|
it("hides Hindsight rows again when the backend is switched back to off without leaving the tab", () => {
|
|
settings.set("memory.backend", "hindsight");
|
|
const comp = createSelector();
|
|
focusMemoryTab(comp);
|
|
// Width 70 keeps the flat layout so all sections' rows render inline.
|
|
expect(comp.render(70).join("\n")).toContain("Hindsight API URL");
|
|
|
|
// Open Memory Backend → SelectSubmenu pre-selects the current value
|
|
// ("hindsight" at index 2) → step up twice to reach "off" → Enter confirms.
|
|
comp.handleInput("\n");
|
|
comp.handleInput("\x1b[A");
|
|
comp.handleInput("\x1b[A");
|
|
comp.handleInput("\n");
|
|
|
|
expect(settings.get("memory.backend")).toBe("off");
|
|
const after = comp.render(70).join("\n");
|
|
expect(after).toContain("Memory Backend");
|
|
expect(after).not.toContain("Hindsight API URL");
|
|
expect(after).not.toContain("Hindsight Auto Recall");
|
|
});
|
|
|
|
it("renders group titles, suppressing groups whose items are all condition-hidden", () => {
|
|
settings.set("memory.backend", "off");
|
|
const comp = createSelector();
|
|
focusMemoryTab(comp);
|
|
|
|
const strip = (line: string): string => line.replace(/\x1b\[[0-9;]*m/g, "");
|
|
|
|
// Single visible group renders flat: the title is a standalone heading row.
|
|
// Mnemopi/Hindsight groups are fully condition-hidden and emit nothing.
|
|
const flatHeadings = comp
|
|
.render(120)
|
|
.map(line =>
|
|
strip(line)
|
|
.replace(/[█│]\s*$/, "")
|
|
.trim(),
|
|
)
|
|
.filter(line => line === "General" || line === "Mnemopi" || line === "Hindsight");
|
|
expect(flatHeadings).toEqual(["General"]);
|
|
|
|
// Switch backend to hindsight: a second group materializes, so the wide
|
|
// render switches to the split layout with section titles in the sidebar.
|
|
comp.handleInput("\n");
|
|
comp.handleInput("\x1b[B");
|
|
comp.handleInput("\x1b[B");
|
|
comp.handleInput("\n");
|
|
|
|
const sidebarTitles = comp
|
|
.render(120)
|
|
.map(strip)
|
|
.filter(line => line.includes("│"))
|
|
.map(line => line.split("│")[0].trim())
|
|
.filter(title => title.length > 0);
|
|
expect(sidebarTitles).toEqual(["General", "Hindsight"]);
|
|
});
|
|
|
|
it("clears settings search on Escape before closing the selector", () => {
|
|
let cancelCount = 0;
|
|
const comp = createSelector(() => {
|
|
cancelCount++;
|
|
});
|
|
|
|
comp.handleInput("b");
|
|
expect(comp.render(120).join("\n")).toContain("Search: b");
|
|
|
|
comp.handleInput("\x1b");
|
|
expect(cancelCount).toBe(0);
|
|
expect(comp.render(120).join("\n")).not.toContain("Search: b");
|
|
|
|
comp.handleInput("\x1b");
|
|
expect(cancelCount).toBe(1);
|
|
});
|
|
|
|
it("delegates Escape to an open settings submenu before closing the selector", () => {
|
|
let cancelCount = 0;
|
|
settings.set("memory.backend", "off");
|
|
const comp = createSelector(() => {
|
|
cancelCount++;
|
|
});
|
|
focusMemoryTab(comp);
|
|
|
|
comp.handleInput("\n");
|
|
expect(comp.render(120).join("\n")).toContain("Esc to go back");
|
|
|
|
comp.handleInput("\x1b");
|
|
const afterBack = comp.render(120).join("\n");
|
|
expect(cancelCount).toBe(0);
|
|
expect(afterBack).toContain("Memory Backend");
|
|
expect(afterBack).toContain("Esc to cancel");
|
|
expect(afterBack).not.toContain("Esc to go back");
|
|
|
|
comp.handleInput("\x1b");
|
|
expect(cancelCount).toBe(1);
|
|
});
|
|
});
|