Phase 2: add settings tests — defaults, merge, effective helpers.
Covers layered settings merge, incremental global file writes, and delivery-mode resolution per SPEC §6.5.3. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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<string, unknown>;
|
||||
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<string, unknown>;
|
||||
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");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user