import { describe, expect, it, beforeEach, afterEach } from "vitest"; import { mkdtempSync, writeFileSync, rmSync, cpSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { NowPlayingWatcher } from "../src/nowplaying.ts"; let tmpRoot: string; beforeEach(() => { tmpRoot = mkdtempSync(join(tmpdir(), "denpa-np-")); cpSync("tests/fixtures/now-playing", join(tmpRoot, "now-playing"), { recursive: true }); cpSync("tests/fixtures/library", join(tmpRoot, "library"), { recursive: true }); }); afterEach(() => { rmSync(tmpRoot, { recursive: true, force: true }); }); describe("NowPlayingWatcher", () => { it("reads initial state from disk", async () => { const watcher = new NowPlayingWatcher({ station: "minecraft", nowPlayingDir: join(tmpRoot, "now-playing"), libraryDir: join(tmpRoot, "library"), }); await watcher.start(); const state = watcher.current(); expect(state).not.toBeNull(); expect(state!.title).toBe("Sweden"); expect(state!.cover_url).toMatch(/^\/cover\?path=/); expect(state!.up_next).toHaveLength(1); watcher.stop(); }); it("rewrites cover_path to /cover?path=… url", async () => { const watcher = new NowPlayingWatcher({ station: "minecraft", nowPlayingDir: join(tmpRoot, "now-playing"), libraryDir: join(tmpRoot, "library"), }); await watcher.start(); expect(watcher.current()!.cover_url).toContain( encodeURIComponent("/library/minecraft/tracks/Volume Alpha/cover.jpg"), ); watcher.stop(); }); it("emits change events on file rewrite", async () => { const watcher = new NowPlayingWatcher({ station: "minecraft", nowPlayingDir: join(tmpRoot, "now-playing"), libraryDir: join(tmpRoot, "library"), }); await watcher.start(); let changes = 0; watcher.on("change", () => changes++); const newPayload = JSON.stringify({ station: "minecraft", title: "Pigstep", artist: "Lena Raine", album: "Nether Update", filename: "/library/minecraft/tracks/Nether/01. Pigstep.flac", duration: "148", started_at: "1730358500", cover_path: "", up_next: [], }); writeFileSync(join(tmpRoot, "now-playing", "minecraft.json"), newPayload); await new Promise((r) => setTimeout(r, 200)); expect(changes).toBeGreaterThan(0); expect(watcher.current()!.title).toBe("Pigstep"); watcher.stop(); }); it("falls back to station cover when track cover missing", async () => { const payload = JSON.stringify({ station: "minecraft", title: "Untagged Track", artist: "?", album: "?", filename: "/library/minecraft/tracks/UnknownAlbum/track.flac", duration: "120", started_at: "1730358000", cover_path: "", up_next: [], }); writeFileSync(join(tmpRoot, "now-playing", "minecraft.json"), payload); const watcher = new NowPlayingWatcher({ station: "minecraft", nowPlayingDir: join(tmpRoot, "now-playing"), libraryDir: join(tmpRoot, "library"), }); await watcher.start(); expect(watcher.current()!.cover_url).toContain( encodeURIComponent("/library/minecraft/cover.jpg"), ); watcher.stop(); }); });