// @vitest-environment node import { mkdtempSync, readFileSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import path from "node:path"; import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { BRAND_CELL_PATHS } from "@/components/brand-mark"; import { AREAS } from "@/lib/areas"; import { ICON_FILES, SHORTCUT_ICONS, buildIco, generateIcons, readMasterPaths } from "./generate-icons.mjs"; const ROOT = process.cwd(); describe("generate-icons (spec 12.2)", () => { let outDir: string; beforeEach(() => { outDir = mkdtempSync(path.join(tmpdir(), "icons-")); }); afterEach(() => rmSync(outDir, { recursive: true, force: true })); it("the master SVG has the BrandMark geometry", () => { expect(readMasterPaths(readFileSync(path.join(ROOT, "public/brand/apicore-mark.svg"), "utf8"))).toEqual( BRAND_CELL_PATHS, ); }); it("the lid sits centred over the cell, only lifted", () => { const xs = (d: string) => [...d.matchAll(/L([\d.]+)\s/g)].map((match) => Number(match[1])); const lidXs = xs(BRAND_CELL_PATHS.lid); expect(Math.min(...lidXs) + Math.max(...lidXs)).toBeCloseTo(64, 5); }); it("shortcut icons use the icon of their area", () => { const pascal = (name: string) => name.replace(/(^|-)(\w)/g, (_, __, char: string) => char.toUpperCase()); for (const shortcut of SHORTCUT_ICONS) { expect(AREAS[shortcut.area as keyof typeof AREAS].icon.displayName, shortcut.file).toBe(pascal(shortcut.lucide)); } }); it("running the icon script twice leaves the files byte-identical", async () => { const first = await generateIcons({ root: ROOT, outRoot: outDir }); const snapshot = new Map(first.map((file) => [file, readFileSync(path.join(outDir, file))])); const second = await generateIcons({ root: ROOT, outRoot: outDir }); expect(second).toEqual(first); expect([...first].sort()).toEqual([...ICON_FILES].sort()); for (const file of second) { expect(readFileSync(path.join(outDir, file)).equals(snapshot.get(file) as Buffer), file).toBe(true); } }); it("buildIco writes a header, one entry per PNG and the PNGs behind it", () => { const png = (size: number) => Buffer.concat([Buffer.from([0x89, 0x50, 0x4e, 0x47]), Buffer.alloc(size)]); const ico = buildIco([ { size: 16, png: png(10) }, { size: 256, png: png(20) }, ]); expect([ico.readUInt16LE(0), ico.readUInt16LE(2), ico.readUInt16LE(4)]).toEqual([0, 1, 2]); // 256 px is written as 0 in the one-byte width and height fields. expect([ico.readUInt8(6), ico.readUInt8(22)]).toEqual([16, 0]); expect(ico.readUInt16LE(6 + 6)).toBe(32); const secondOffset = ico.readUInt32LE(22 + 12); expect(ico.readUInt32LE(6 + 12)).toBe(6 + 2 * 16); expect(secondOffset).toBe(6 + 2 * 16 + 14); expect(ico.length).toBe(secondOffset + 24); }); });