import { expect, test, beforeEach, afterEach } from 'bun:test' import { initRenderer, SchemaMismatchError } from "."; import { z } from "zod" import { mkdtempSync, rmSync, existsSync, readFileSync } from "node:fs" import { join } from "node:path" import { tmpdir } from "node:os" const ifExampleSchema = z.object({ web: z.boolean(), header: z.object({ render: z.boolean(), text: z.string() }) }) let tmp: string beforeEach(() => { tmp = mkdtempSync(join(tmpdir(), "tdir-test-")) }) afterEach(() => { rmSync(tmp, { recursive: true, force: true }) }) test("createRenderer validates schema matches templates", () => { const createRenderer = initRenderer("./testdata/if_example") expect(() => createRenderer(ifExampleSchema)).not.toThrow() expect(() => createRenderer(z.object({ web: z.boolean() }))).toThrow(SchemaMismatchError) expect(() => createRenderer(z.object({ web: z.string(), header: z.object({ render: z.boolean(), text: z.string() }) }))).toThrow(SchemaMismatchError) }) test("render validates context via zod", () => { const createRenderer = initRenderer("./testdata/if_example") const render = createRenderer(ifExampleSchema) expect(() => render(tmp, {} as any)).toThrow(z.ZodError) expect(() => render(tmp, { web: "nope" } as any)).toThrow(z.ZodError) expect(() => render(tmp, { web: true, header: { render: true } } as any)).toThrow(z.ZodError) }) test("renders with web=true, header rendered", () => { const createRenderer = initRenderer("./testdata/if_example") const render = createRenderer(ifExampleSchema) render(tmp, { web: true, header: { render: true, text: "My Title" } }) const outFile = join(tmp, "web", "if_example.html") expect(existsSync(outFile)).toBe(true) const content = readFileSync(outFile, "utf-8") expect(content).toContain("My Title") expect(content).toContain("") expect(content).toContain("") }) test("wrong schema throws error",() => { const createRenderer = initRenderer("./testdata/if_example") expect(() => createRenderer(z.object({ web: z.string(), header: z.object({ render: z.string(), text: z.boolean() }) }))).toThrow(new SchemaMismatchError({message: 'expected z.boolean() but schema has z.string()', path: 'web'})) }) test("wrong schema throws error",() => { const createRenderer = initRenderer("./testdata/if_example") expect(() => createRenderer(z.object({ web: z.boolean(), header: z.object({ render: z.string(), text: z.boolean() }) }))).toThrow(new SchemaMismatchError({message: 'expected z.boolean() but schema has z.string()', path: 'header.render'})) }) test("wrong schema throws error",() => { const createRenderer = initRenderer("./testdata/if_example") expect(() => createRenderer(z.object({ web: z.boolean(), header: z.object({ render: z.boolean(), text: z.boolean() }) }))).toThrow(new SchemaMismatchError({message: 'expected z.string() but schema has z.boolean()', path: 'header.text'})) }) test("renders with web=true, header not rendered", () => { const createRenderer = initRenderer("./testdata/if_example") const render = createRenderer(ifExampleSchema) render(tmp, { web: true, header: { render: false, text: "Ignored" } }) const outFile = join(tmp, "web", "if_example.html") expect(existsSync(outFile)).toBe(true) const content = readFileSync(outFile, "utf-8") expect(content).not.toContain("Ignored") expect(content).not.toContain("") expect(content).toContain("") }) test("renders with web=false, skips directory", () => { const createRenderer = initRenderer("./testdata/if_example") const render = createRenderer(ifExampleSchema) render(tmp, { web: false, header: { render: true, text: "Hello" } }) expect(existsSync(join(tmp, "web"))).toBe(false) }) test("vars in path are added to schema",() => { const createRenderer = initRenderer("./testdata/var_in_path") expect(() => createRenderer(z.object({ web: z.boolean(), header: z.object({ render: z.boolean(), text: z.boolean() }) }))).toThrow(new SchemaMismatchError({message: 'expected z.object() but schema has z.boolean()', path: 'web'})) }) test("vars in path renders correctrly",() => { const createRenderer = initRenderer("./testdata/var_in_path") const render = createRenderer(z.object({ web: z.object({ create: z.boolean(), dir: z.string() }), header: z.object({ render: z.boolean(), text: z.string() }) })) render(tmp,{ web: { create: true, dir: "web" }, header: { render: false, text: "test" } }) expect(existsSync(join(tmp, "web"))).toBe(true) const outFile = join(tmp, "web", "var_in_path_example.html") expect(existsSync(outFile)).toBe(true) const content = readFileSync(outFile, "utf-8") expect(content).not.toContain("Ignored") expect(content).not.toContain("") expect(content).toContain("") }) test("vars in path renders correctrly",() => { const createRenderer = initRenderer("./testdata/var_in_path") const render = createRenderer(z.object({ web: z.object({ create: z.boolean(), dir: z.string() }), header: z.object({ render: z.boolean(), text: z.string() }) })) render(tmp,{ web: { create: true, dir: "test" }, header: { render: true, text: "test" } }) expect(existsSync(join(tmp, "test"))).toBe(true) const outFile = join(tmp, "test", "var_in_path_example.html") expect(existsSync(outFile)).toBe(true) const content = readFileSync(outFile, "utf-8") expect(content).toContain("test") expect(content).toContain("") expect(content).toContain("") }) test("nested if dir renders correctly",() => { const createRenderer = initRenderer("./testdata/nested_if_dir") const render = createRenderer(z.object({ web: z.boolean(), webnested: z.boolean() })) render(tmp,{ web: true, webnested: true }) expect(existsSync(join(tmp, "web"))).toBe(true) }) test("nested if dir renders correctly",() => { const createRenderer = initRenderer("./testdata/nested_if_dir_named") const render = createRenderer(z.object({ web: z.boolean(), webnested: z.boolean() })) render(tmp,{ web: true, webnested: true }) expect(existsSync(join(tmp, "nested" , "web"))).toBe(true) }) test("multiple dirs 1 var",() => { const createRenderer = initRenderer("./testdata/multi_dir_one_var") const render = createRenderer(z.object({ web: z.boolean(), })) render(tmp,{ web: true, }) expect(existsSync(join(tmp, "dir1"))).toBe(true) expect(existsSync(join(tmp, "dir2"))).toBe(true) expect(existsSync(join(tmp, "dir1", "dir1n"))).toBe(true) }) test("file if",() => { const createRenderer = initRenderer("./testdata/file_if") const render = createRenderer(z.object({ web: z.boolean(), file: z.boolean(), text: z.string() })) render(tmp,{ web: true, file: true, text: "test" }) expect(existsSync(join(tmp, "web"))).toBe(true) const outFile = join(tmp, "web", "example.txt") expect(existsSync(outFile)).toBe(true) const content = readFileSync(outFile, "utf-8") expect(content).toContain("test") }) test("no endif should throw",() => { const createRenderer = initRenderer("./testdata/no_end_if") const render = createRenderer(z.object({ test: z.boolean(), })) expect( () => render(tmp,{ test: true, })).toThrow() }) test("if elseif else",() => { const createRenderer = initRenderer("./testdata/if_elseif_else_in_file") const render = createRenderer(z.object({ test: z.boolean(), test2: z.boolean() })) render(tmp,{ test: true, test2: true }) let outFile = join(tmp, "test.txt") expect(existsSync(outFile)).toBe(true) let content = readFileSync(outFile, "utf-8") expect(content).toContain("test") render(tmp,{ test: false, test2: true }) outFile = join(tmp, "test.txt") expect(existsSync(outFile)).toBe(true) content = readFileSync(outFile, "utf-8") expect(content).toContain("test2") render(tmp,{ test: false, test2: false }) outFile = join(tmp, "test.txt") expect(existsSync(outFile)).toBe(true) content = readFileSync(outFile, "utf-8") expect(content).toContain("test3") }) test("if eq in path", () => { const createRenderer = initRenderer("./testdata/eq_in_path") const render = createRenderer(z.object({test: z.string()})) render(tmp,{test:"test"}) expect(existsSync(join(tmp,"test"))).toBe(true) render(tmp,{test:"foo"}) expect(existsSync(join(tmp,"test"))).toBe(false) })