import { describe, expect, it } from "vitest"; import { buildStatRows, fmtBitrate, fmtBufferSecs, fmtClock, fmtCodec, fmtFrames, fmtLevel, fmtMode, fmtRes, type PlexStatsInput, } from "./plexStats"; const DASH = "—"; describe("plexStats formatters", () => { it("fmtBitrate scales kbps/Mbps and dashes zero/undefined", () => { expect(fmtBitrate(undefined)).toBe(DASH); expect(fmtBitrate(0)).toBe(DASH); expect(fmtBitrate(820_000)).toBe("820 kbps"); expect(fmtBitrate(4_200_000)).toBe("4.2 Mbps"); expect(fmtBitrate(1_000_000)).toBe("1.0 Mbps"); }); it("fmtBufferSecs shows one decimal, dashes negatives/non-finite", () => { expect(fmtBufferSecs(12.34)).toBe("12.3 s"); expect(fmtBufferSecs(0)).toBe("0.0 s"); expect(fmtBufferSecs(-1)).toBe(DASH); expect(fmtBufferSecs(Infinity)).toBe(DASH); }); it("fmtFrames shows ratio + drop %, dashes when nothing decoded", () => { expect(fmtFrames(0, 0)).toBe(DASH); expect(fmtFrames(0, 1000)).toBe("0 / 1000 (0.00%)"); expect(fmtFrames(5, 1000)).toBe("5 / 1000 (0.50%)"); expect(fmtFrames(30, 1000)).toBe("30 / 1000 (3.0%)"); }); it("fmtRes dashes an undecoded 0×0 frame", () => { expect(fmtRes(1920, 1080)).toBe("1920×1080"); expect(fmtRes(0, 0)).toBe(DASH); expect(fmtRes(1920, 0)).toBe(DASH); }); it("fmtClock renders m:ss and dashes bad input", () => { expect(fmtClock(0)).toBe("0:00"); expect(fmtClock(9)).toBe("0:09"); expect(fmtClock(75)).toBe("1:15"); expect(fmtClock(3661)).toBe("61:01"); expect(fmtClock(-1)).toBe(DASH); }); it("fmtLevel is 1-based with resolution, handles auto and empty", () => { expect(fmtLevel(null)).toBe(DASH); expect(fmtLevel({ index: 1, total: 4, width: 1280, height: 720 })).toBe("2 / 4 · 1280×720"); expect(fmtLevel({ index: -1, total: 3 })).toBe("auto / 3"); expect(fmtLevel({ index: 0, total: 0 })).toBe(DASH); }); it("fmtCodec strips profile suffixes and dashes when absent", () => { expect(fmtCodec("avc1.640028", "mp4a.40.2")).toBe("avc1 / mp4a"); expect(fmtCodec("hvc1.1.6.L120", undefined)).toBe("hvc1"); expect(fmtCodec(undefined, undefined)).toBe(DASH); }); }); describe("buildStatRows", () => { const base: PlexStatsInput = { playable: "remux", transport: "hls", videoWidth: 1280, videoHeight: 720, droppedFrames: 2, totalFrames: 500, bufferAheadS: 8.2, hlsLevel: { index: 0, total: 1, width: 1280, height: 720, bitrate: 3_000_000 }, sessionStartS: 65, abs: 130, duration: 5214, }; it("includes the HLS-only rows (codec/bitrate/level) for hls transport", () => { const keys = buildStatRows(base).map((r) => r.key); expect(keys).toEqual([ "mode", "transport", "res", "codec", "bitrate", "level", "frames", "buffer", "offset", "position", ]); }); it("drops the HLS-only rows for direct play", () => { const keys = buildStatRows({ ...base, transport: "direct", hlsLevel: null }).map((r) => r.key); expect(keys).not.toContain("codec"); expect(keys).not.toContain("bitrate"); expect(keys).not.toContain("level"); expect(keys).toContain("res"); }); it("surfaces the classification as the mode value and formats position", () => { const rows = buildStatRows({ ...base, playable: "transcode" }); expect(rows.find((r) => r.key === "mode")?.value).toBe("transcode"); expect(rows.find((r) => r.key === "offset")?.value).toBe("1:05"); expect(rows.find((r) => r.key === "position")?.value).toBe("2:10 / 86:54"); }); it("appends the transcode encoder to the mode row when known", () => { const gpu = buildStatRows({ ...base, playable: "transcode", hwEncoder: true }); expect(gpu.find((r) => r.key === "mode")?.value).toBe("transcode · GPU"); const cpu = buildStatRows({ ...base, playable: "transcode", hwEncoder: false }); expect(cpu.find((r) => r.key === "mode")?.value).toBe("transcode · CPU"); }); it("tags the mode row with HDR→SDR for a HW tonemap session", () => { const rows = buildStatRows({ ...base, playable: "transcode", hwEncoder: true, tonemap: true }); expect(rows.find((r) => r.key === "mode")?.value).toBe("transcode · GPU · HDR→SDR"); }); it("shows an empty classification honestly as a dash", () => { expect(buildStatRows({ ...base, playable: "" }).find((r) => r.key === "mode")?.value).toBe( DASH, ); }); }); describe("fmtMode", () => { it("appends the encoder only for a transcode with a known encoder", () => { expect(fmtMode("transcode", true)).toBe("transcode · GPU"); expect(fmtMode("transcode", false)).toBe("transcode · CPU"); expect(fmtMode("transcode", null)).toBe("transcode"); // encoder unknown → bare mode expect(fmtMode("transcode")).toBe("transcode"); expect(fmtMode("remux", true)).toBe("remux"); // not a transcode → no encoder suffix expect(fmtMode("")).toBe(DASH); // The HDR→SDR tag rides on the GPU encoder token for a tonemap session. expect(fmtMode("transcode", true, true)).toBe("transcode · GPU · HDR→SDR"); expect(fmtMode("transcode", true, false)).toBe("transcode · GPU"); expect(fmtMode("transcode", null, true)).toBe("transcode"); // no encoder → no tag either }); });