import { afterAll, describe, expect, test } from "bun:test"; import * as fs from "node:fs"; import * as os from "node:os"; import * as path from "node:path"; import { buildArtwork, fileToDataUrl } from "../src/art.js"; import { DEFAULT_ART } from "../src/config.js"; import type { ExportedGame } from "../src/export-schema.js"; const dir = fs.mkdtempSync(path.join(os.tmpdir(), "pf-playnite-art-")); const write = (name: string, bytes: Buffer): string => { const p = path.join(dir, name); fs.writeFileSync(p, bytes); return p; }; afterAll(() => fs.rmSync(dir, { recursive: true, force: true })); const game = (over: Partial): ExportedGame => ({ id: "11111111-1111-1111-1111-111111111111", name: "Game", installed: true, hidden: false, source: "Steam", platforms: [], cover: null, background: null, icon: null, ...over, }); describe("fileToDataUrl", () => { test("encodes a small png as a data URL", () => { const p = write("cover.png", Buffer.from([1, 2, 3, 4])); expect(fileToDataUrl(p, 1000)).toBe( `data:image/png;base64,${Buffer.from([1, 2, 3, 4]).toString("base64")}`, ); }); test("drops files over the size cap", () => { const p = write("big.jpg", Buffer.alloc(2000, 7)); expect(fileToDataUrl(p, 1000)).toBeNull(); }); test("rejects unknown extensions and null", () => { const p = write("weird.xyz", Buffer.from([1])); expect(fileToDataUrl(p, 1000)).toBeNull(); expect(fileToDataUrl(null, 1000)).toBeNull(); }); test("mime follows the extension", () => { const p = write("c.jpeg", Buffer.from([9])); expect(fileToDataUrl(p, 1000)?.startsWith("data:image/jpeg;base64,")).toBe( true, ); }); }); describe("buildArtwork", () => { test("off mode yields no art", () => { const cover = write("c1.png", Buffer.from([1])); expect( buildArtwork(game({ cover }), { ...DEFAULT_ART, mode: "off" }), ).toBeUndefined(); }); test("host mode (default) emits the raw cover path — no bytes", () => { const cover = write("c2.png", Buffer.from([1, 2])); const art = buildArtwork(game({ cover }), DEFAULT_ART); expect(art?.portrait).toBe(cover); expect(art?.hero).toBeUndefined(); }); test("host mode includeBackground adds the hero path", () => { const cover = write("c2b.png", Buffer.from([1])); const background = write("b2b.jpg", Buffer.from([2])); const art = buildArtwork(game({ cover, background }), { ...DEFAULT_ART, includeBackground: true, }); expect(art?.portrait).toBe(cover); expect(art?.hero).toBe(background); }); test("host mode with no cover yields undefined", () => { expect(buildArtwork(game({ cover: null }), DEFAULT_ART)).toBeUndefined(); }); test("dataurl mode embeds the cover as a portrait data URL", () => { const cover = write("c3.png", Buffer.from([1, 2])); const art = buildArtwork(game({ cover }), { ...DEFAULT_ART, mode: "dataurl", }); expect(art?.portrait?.startsWith("data:image/png;base64,")).toBe(true); expect(art?.hero).toBeUndefined(); }); test("dataurl includeBackground adds a hero data URL", () => { const cover = write("c4.png", Buffer.from([1])); const background = write("b4.jpg", Buffer.from([2])); const art = buildArtwork(game({ cover, background }), { ...DEFAULT_ART, mode: "dataurl", includeBackground: true, }); expect(art?.portrait).toBeTruthy(); expect(art?.hero?.startsWith("data:image/jpeg;base64,")).toBe(true); }); test("dataurl mode with no readable art yields undefined", () => { expect( buildArtwork(game({ cover: "/does/not/exist.png" }), { ...DEFAULT_ART, mode: "dataurl", }), ).toBeUndefined(); }); });