* refactor(draw-image): add tests/fixtures/cover.svg and loadFixture helper * feat(draw-image): add --template-dir flag to cli for test isolation * refactor(draw-image): switch tests from templates/cover to fixtures * chore(draw-image): simplify production cover.svg now decoupled from tests * docs(handoff): add handoff + ADR for issue #196 * docs(handoff): set PR number * docs(project-map): update after structural changes --------- Co-authored-by: opencode-agent <agent@opencode.local>
72 lines
No EOL
2.7 KiB
TypeScript
72 lines
No EOL
2.7 KiB
TypeScript
import { describe, test, expect, beforeAll, afterAll } from "vitest"
|
|
import { existsSync, readFileSync, rmSync, mkdirSync, writeFileSync, mkdtempSync } from "node:fs"
|
|
import path from "node:path"
|
|
import os from "node:os"
|
|
import { fileURLToPath } from "node:url"
|
|
import { spawnSync } from "node:child_process"
|
|
import { loadBrand } from "../src/config"
|
|
import { buildSvg, computeHash } from "../src/render"
|
|
import { loadFixture } from "./helpers/fixtures"
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
const DRAW_IMAGE_DIR = path.resolve(__dirname, "..")
|
|
const TMP = path.join(os.tmpdir(), `draw-image-integration-${process.pid}`)
|
|
|
|
beforeAll(() => {
|
|
if (existsSync(TMP)) rmSync(TMP, { recursive: true, force: true })
|
|
mkdirSync(TMP, { recursive: true })
|
|
})
|
|
|
|
afterAll(() => {
|
|
if (existsSync(TMP)) rmSync(TMP, { recursive: true, force: true })
|
|
})
|
|
|
|
function renderToPng(svg: string, outPath: string): void {
|
|
const tmpDir = mkdtempSync(path.join(os.tmpdir(), `draw-image-test-${process.pid}-`))
|
|
const tmpSvg = path.join(tmpDir, "input.svg")
|
|
try {
|
|
writeFileSync(tmpSvg, svg)
|
|
const r = spawnSync("node", [path.join(DRAW_IMAGE_DIR, "render.mjs"), tmpSvg, outPath], {
|
|
encoding: "utf-8",
|
|
})
|
|
if (r.status !== 0) throw new Error(`render.mjs failed: ${r.stderr || r.stdout}`)
|
|
} finally {
|
|
if (existsSync(tmpDir)) rmSync(tmpDir, { recursive: true, force: true })
|
|
}
|
|
}
|
|
|
|
describe("integration — render pipeline", () => {
|
|
test("renders valid PNG 1024x1024", () => {
|
|
const brand = loadBrand(DRAW_IMAGE_DIR)
|
|
const templateSvg = loadFixture("cover")
|
|
const args = { template: "cover", title: "Test", out: path.join(TMP, "cover.png") }
|
|
const hash = computeHash(brand, templateSvg, args)
|
|
const svg = buildSvg(templateSvg, brand, args, DRAW_IMAGE_DIR)
|
|
|
|
const outPath = path.join(TMP, "cover.png")
|
|
renderToPng(svg, outPath)
|
|
|
|
expect(existsSync(outPath)).toBe(true)
|
|
const buf = readFileSync(outPath)
|
|
expect(buf[0]).toBe(0x89)
|
|
expect(buf[1]).toBe(0x50)
|
|
expect(buf[2]).toBe(0x4e)
|
|
expect(buf[3]).toBe(0x47)
|
|
const width = buf.readUInt32BE(16)
|
|
const height = buf.readUInt32BE(20)
|
|
expect(width, `PNG width should be 1024, got ${width}`).toBe(1024)
|
|
expect(height, `PNG height should be 1024, got ${height}`).toBe(1024)
|
|
|
|
expect(hash).toHaveLength(64)
|
|
})
|
|
|
|
test("creates output directory recursively", () => {
|
|
const brand = loadBrand(DRAW_IMAGE_DIR)
|
|
const templateSvg = loadFixture("cover")
|
|
const args = { template: "cover", title: "Deep", out: path.join(TMP, "a", "b", "c", "cover.png") }
|
|
const svg = buildSvg(templateSvg, brand, args, DRAW_IMAGE_DIR)
|
|
const outPath = path.join(TMP, "a", "b", "c", "cover.png")
|
|
renderToPng(svg, outPath)
|
|
expect(existsSync(outPath)).toBe(true)
|
|
})
|
|
}) |