import { describe, expect, it } from "vitest"; import { extractVariables, parseTemplate } from "./parser.js"; function vars(entries: ReadonlyArray<[string, string | null]>): Map { return new Map(entries); } describe("parser", () => { describe("variable insertion", () => { it("simple variable insertion", () => { // 1. [system:time] with value → inserts it expect(parseTemplate("[system:time]", vars([["system:time", "12:00"]]))).toBe("12:00"); }); it("unknown variable → blank", () => { // 2. [unknown:foo] not in map → "" expect(parseTemplate("[unknown:foo]", vars([]))).toBe(""); }); it("null variable → blank", () => { // 3. [file:missing.md] with value null → "" expect(parseTemplate("[file:missing.md]", vars([["file:missing.md", null]]))).toBe(""); }); it("inserts value mid-text", () => { expect(parseTemplate("cwd is [prompt:cwd]!", vars([["prompt:cwd", "/proj"]]))).toBe( "cwd is /proj!", ); }); it("non-tag brackets stay literal", () => { expect(parseTemplate("[not a tag] done", vars([]))).toBe("[not a tag] done"); }); it("unclosed bracket stays literal", () => { expect(parseTemplate("[file:x", vars([]))).toBe("[file:x"); }); }); describe("conditionals", () => { it("if block renders when variable exists", () => { // 4. [if file:AGENTS.md]YES[endif] with value → "YES" expect( parseTemplate("[if file:AGENTS.md]YES[endif]", vars([["file:AGENTS.md", "content"]])), ).toBe("YES"); }); it("if block skipped when variable is null", () => { // 5. same with null → "" expect(parseTemplate("[if file:AGENTS.md]YES[endif]", vars([["file:AGENTS.md", null]]))).toBe( "", ); }); it("if block skipped when variable absent", () => { expect(parseTemplate("[if file:AGENTS.md]YES[endif]", vars([]))).toBe(""); }); it("if/else renders fallback when null", () => { // 6. [if file:X]A[else]B[endif] with null → "B" expect(parseTemplate("[if file:X]A[else]B[endif]", vars([["file:X", null]]))).toBe("B"); }); it("if/else renders then-branch when exists", () => { expect(parseTemplate("[if file:X]A[else]B[endif]", vars([["file:X", "v"]]))).toBe("A"); }); it("negated if renders when variable is null", () => { // 7. [if !file:X]A[endif] with null → "A" expect(parseTemplate("[if !file:X]A[endif]", vars([["file:X", null]]))).toBe("A"); }); it("negated if skipped when variable exists", () => { expect(parseTemplate("[if !file:X]A[endif]", vars([["file:X", "v"]]))).toBe(""); }); it("negated if renders when variable absent", () => { expect(parseTemplate("[if !file:X]A[endif]", vars([]))).toBe("A"); }); it("nested if — inner skipped when its var is null", () => { // 8. [if system:os][if file:X]A[endif][endif] with os=set, file=null → "" expect( parseTemplate( "[if system:os][if file:X]A[endif][endif]", vars([ ["system:os", "linux"], ["file:X", null], ]), ), ).toBe(""); }); it("nested if — inner renders when both exist", () => { expect( parseTemplate( "[if system:os][if file:X]A[endif][endif]", vars([ ["system:os", "linux"], ["file:X", "v"], ]), ), ).toBe("A"); }); it("nested if/else", () => { expect( parseTemplate( "[if system:os]os[if file:X]A[else]B[endif][endif]", vars([ ["system:os", "linux"], ["file:X", null], ]), ), ).toBe("osB"); }); it("unmatched if → literal text", () => { // 9. [if file:X]text (no endif) → "[if file:X]text" expect(parseTemplate("[if file:X]text", vars([["file:X", "v"]]))).toBe("[if file:X]text"); }); it("unmatched if with null var still emits literal tag", () => { expect(parseTemplate("[if file:X]text", vars([["file:X", null]]))).toBe("[if file:X]text"); }); it("stray endif → literal text", () => { expect(parseTemplate("a[endif]b", vars([]))).toBe("a[endif]b"); }); it("stray else → literal text", () => { expect(parseTemplate("a[else]b", vars([]))).toBe("a[else]b"); }); it("multi-line content renders correctly", () => { // 10. if block spanning multiple lines const template = "[if file:AGENTS.md]line1\nline2\nline3[endif]"; expect(parseTemplate(template, vars([["file:AGENTS.md", "c"]]))).toBe("line1\nline2\nline3"); }); it("multi-line if/else block", () => { const template = "[if file:X]\nA\n[else]\nB\n[endif]"; expect(parseTemplate(template, vars([["file:X", null]]))).toBe("\nB\n"); }); it("default-template-like structure renders", () => { const template = "You are a helpful coding assistant.\n\n[if file:AGENTS.md]\n[file:AGENTS.md]\n[endif]\n\nThe current working directory is [prompt:cwd].\n"; expect( parseTemplate( template, vars([ ["file:AGENTS.md", "RULES"], ["prompt:cwd", "/proj"], ]), ), ).toBe( "You are a helpful coding assistant.\n\n\nRULES\n\n\nThe current working directory is /proj.\n", ); }); it("default-template-like structure without AGENTS.md", () => { const template = "[if file:AGENTS.md]\n[file:AGENTS.md]\n[endif]\nThe cwd is [prompt:cwd]."; expect(parseTemplate(template, vars([["prompt:cwd", "/proj"]]))).toBe("\nThe cwd is /proj."); }); }); describe("extractVariables", () => { it("collects insertion + condition keys", () => { const template = "[system:time] [if file:AGENTS.md][file:AGENTS.md][endif] [if !git:branch]"; expect(extractVariables(template)).toEqual(["system:time", "file:AGENTS.md", "git:branch"]); }); it("deduplicates keys", () => { expect(extractVariables("[file:X][if file:X][file:X]")).toEqual(["file:X"]); }); it("returns empty for plain text", () => { expect(extractVariables("no variables here")).toEqual([]); }); it("ignores unmatched tags", () => { expect(extractVariables("[if file:X]no endif")).toEqual(["file:X"]); }); }); });