#!/usr/bin/env node /** * ObsiGate — Audio/video viewer tests (roadmap #109). * * Pure helpers (formatMediaDuration / buildMediaUrl) are evaluated in a VM * sandbox to avoid importing the full browser-only viewer chain. The rest are * static checks on the source, CSS and backend wiring. * * Usage: node tests/frontend/media-viewer.test.mjs */ import { strict as assert } from "node:assert"; import { readFileSync } from "node:fs"; import vm from "node:vm"; import path from "node:path"; import { fileURLToPath } from "node:url"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const ROOT = path.join(__dirname, "..", ".."); const viewer = readFileSync(path.join(ROOT, "frontend", "js", "viewer.js"), "utf8"); const utils = readFileSync(path.join(ROOT, "frontend", "js", "utils.js"), "utf8"); const css = readFileSync(path.join(ROOT, "frontend", "style.css"), "utf8"); const sw = readFileSync(path.join(ROOT, "frontend", "sw.js"), "utf8"); const main = readFileSync(path.join(ROOT, "backend", "main.py"), "utf8"); const fr = readFileSync(path.join(ROOT, "frontend", "locales", "fr.json"), "utf8"); const en = readFileSync(path.join(ROOT, "frontend", "locales", "en.json"), "utf8"); function test(label, fn) { try { fn(); console.log(" \u2713 " + label); } catch (err) { console.error(" \u2717 " + label + "\n " + String(err.message).slice(0, 300)); process.exitCode = 1; } } // ── Pure helpers (VM sandbox) ────────────────────────────────────────────── const begin = viewer.indexOf("export function formatMediaDuration"); const end = viewer.indexOf("function buildDownloadUrl"); assert.ok(begin !== -1 && end > begin, "media viewer helper block not found"); const sandbox = {}; vm.createContext(sandbox); vm.runInContext( `${viewer.slice(begin, end).replace(/\bexport /g, "")}\nglobalThis.formatMediaDuration = formatMediaDuration; globalThis.buildMediaUrl = buildMediaUrl;`, sandbox, ); const { formatMediaDuration, buildMediaUrl } = sandbox; test("formatMediaDuration renders m:ss and h:mm:ss", () => { assert.equal(formatMediaDuration(0), "0:00"); assert.equal(formatMediaDuration(65), "1:05"); assert.equal(formatMediaDuration(3725), "1:02:05"); assert.equal(formatMediaDuration(NaN), ""); assert.equal(formatMediaDuration(-1), ""); }); test("buildMediaUrl encodes vault and path", () => { const url = buildMediaUrl("My Vault", "café clip.mp4"); assert.equal(url, "/api/media/My%20Vault?path=caf%C3%A9%20clip.mp4"); }); // ── Static checks: viewer wiring ─────────────────────────────────────────── test("viewer.js dispatches is_audio / is_video to the media viewers", () => { assert.match(viewer, /if \(data\.is_audio\) \{\s*renderAudioViewer\(area, data\);/); assert.match(viewer, /if \(data\.is_video\) \{\s*renderVideoViewer\(area, data\);/); }); test("audio viewer uses a native