diff --git a/test/settings.test.ts b/test/settings.test.ts new file mode 100644 index 0000000..fe505d9 --- /dev/null +++ b/test/settings.test.ts @@ -0,0 +1,161 @@ +import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "fs"; +import { tmpdir } from "os"; +import { join } from "path"; +import { afterEach, describe, expect, it } from "vitest"; +import { + DEFAULT_SKILL_REINJECT_SETTINGS, + SKILL_REINJECT_SETTINGS_KEY, + createDefaultSettings, + effectiveEnabled, + effectiveIntegration, + mergeSkillReinjectIntoSettingsFile, + mergeSkillReinjectSettings, + parseSkillReinjectPartial, +} from "../src/settings"; + +const tempDirs: string[] = []; + +afterEach(() => { + for (const dir of tempDirs.splice(0)) { + rmSync(dir, { recursive: true, force: true }); + } +}); + +function tempSettingsPath(): string { + const dir = mkdtempSync(join(tmpdir(), "pi-skill-reinject-settings-")); + tempDirs.push(dir); + return join(dir, "settings.json"); +} + +describe("createDefaultSettings", () => { + it("matches SPEC ยง7.3 defaults", () => { + expect(createDefaultSettings()).toEqual(DEFAULT_SKILL_REINJECT_SETTINGS); + }); +}); + +describe("parseSkillReinjectPartial", () => { + it("ignores invalid shapes and fields", () => { + expect(parseSkillReinjectPartial(null)).toEqual({}); + expect(parseSkillReinjectPartial([])).toEqual({}); + expect( + parseSkillReinjectPartial({ + enabled: "yes", + autoCompactIntegration: "bogus", + extra: true, + }), + ).toEqual({}); + }); + + it("keeps valid fields only", () => { + expect( + parseSkillReinjectPartial({ + enabled: true, + suffix: "custom", + }), + ).toEqual({ enabled: true, suffix: "custom" }); + }); +}); + +describe("mergeSkillReinjectSettings", () => { + it("applies global then project overrides on defaults", () => { + expect( + mergeSkillReinjectSettings({ enabled: true }, { trackReadPaths: false }), + ).toEqual({ + ...DEFAULT_SKILL_REINJECT_SETTINGS, + enabled: true, + trackReadPaths: false, + }); + }); + + it("lets project win over global for the same field", () => { + expect( + mergeSkillReinjectSettings({ enabled: true }, { enabled: false }), + ).toEqual({ + ...DEFAULT_SKILL_REINJECT_SETTINGS, + enabled: false, + }); + }); +}); + +describe("mergeSkillReinjectIntoSettingsFile", () => { + it("merges skillReinject fields across successive writes", () => { + const path = tempSettingsPath(); + mergeSkillReinjectIntoSettingsFile(path, { enabled: true }); + mergeSkillReinjectIntoSettingsFile(path, { triggerTurn: true }); + + const saved = JSON.parse(readFileSync(path, "utf8")) as Record; + expect(saved[SKILL_REINJECT_SETTINGS_KEY]).toEqual({ + enabled: true, + triggerTurn: true, + }); + }); + + it("preserves existing top-level keys", () => { + const path = tempSettingsPath(); + writeFileSync( + path, + `${JSON.stringify({ theme: "light", compaction: { enabled: false } }, null, 2)}\n`, + "utf8", + ); + mergeSkillReinjectIntoSettingsFile(path, { enabled: true, suffix: "x" }); + + const saved = JSON.parse(readFileSync(path, "utf8")) as Record; + expect(saved.theme).toBe("light"); + expect(saved.compaction).toEqual({ enabled: false }); + expect(saved[SKILL_REINJECT_SETTINGS_KEY]).toEqual({ + enabled: true, + suffix: "x", + }); + }); +}); + +describe("effectiveEnabled", () => { + const settings = createDefaultSettings(); + + it("uses session override when set", () => { + expect(effectiveEnabled(true, settings)).toBe(true); + expect(effectiveEnabled(false, { ...settings, enabled: true })).toBe(false); + }); + + it("falls back to global enabled when override is null", () => { + expect(effectiveEnabled(null, settings)).toBe(false); + expect(effectiveEnabled(null, { ...settings, enabled: true })).toBe(true); + }); +}); + +describe("effectiveIntegration", () => { + const base = createDefaultSettings(); + + it("honors explicit defer and immediate", () => { + expect( + effectiveIntegration({ ...base, autoCompactIntegration: "defer" }, false), + ).toBe("defer"); + expect( + effectiveIntegration({ ...base, autoCompactIntegration: "immediate" }, true), + ).toBe("immediate"); + }); + + it("uses triggerTurn when integration is off", () => { + expect( + effectiveIntegration({ ...base, autoCompactIntegration: "off", triggerTurn: false }, true), + ).toBe("defer"); + expect( + effectiveIntegration({ ...base, autoCompactIntegration: "off", triggerTurn: true }, true), + ).toBe("immediate"); + }); + + it("defers when auto mode detects pi-auto-compact", () => { + expect(effectiveIntegration(base, true)).toBe("defer"); + }); + + it("uses triggerTurn in auto mode without pi-auto-compact", () => { + expect(effectiveIntegration({ ...base, triggerTurn: false }, false)).toBe("defer"); + expect(effectiveIntegration({ ...base, triggerTurn: true }, false)).toBe("immediate"); + }); + + it("prefers session integration override", () => { + expect( + effectiveIntegration({ ...base, autoCompactIntegration: "immediate" }, false, "defer"), + ).toBe("defer"); + }); +});