import { test } from "node:test"; import assert from "node:assert/strict"; import { transcriptToMarkdown } from "./transcriptToMarkdown"; // Run with: // pnpm --filter yt-dlp-transcript-common exec tsx --test common/lib/transcriptToMarkdown.test.ts const base = { id: "abc123", title: "Episode One", channel: "Alice", uploadDate: "20191218", duration: 3661, webpageUrl: "https://youtube.com/watch?v=abc123", description: "A first episode.", cues: [ { start: 0, end: 2, text: "Hello there" }, { start: 3661.25, end: 3663, text: "General Kenobi" }, ], }; test("renders header, description, and timestamped cues by default", () => { const md = transcriptToMarkdown(base); assert.match(md, /^# Episode One/); assert.match(md, /- Channel: Alice/); assert.match(md, /- Uploaded: 2019-12-18/); assert.match(md, /- Duration: 1:01:01/); assert.match(md, /- Source: https:\/\/youtube\.com/); assert.match(md, /## Description\n\nA first episode\./); assert.match(md, /\[0:00\] Hello there/); assert.match(md, /\[1:01:01\] General Kenobi/); }); test("timestamps:false emits plain prose lines", () => { const md = transcriptToMarkdown(base, { timestamps: false }); assert.match(md, /\nHello there\n/); assert.doesNotMatch(md, /\[0:00\]/); }); test("missing cues → explicit no-transcript marker", () => { const md = transcriptToMarkdown({ id: "x", title: "Empty", cues: undefined }); assert.match(md, /_\(no transcript available\)_/); }); test("maxCues truncates and notes it", () => { const md = transcriptToMarkdown(base, { maxCues: 1 }); assert.match(md, /Hello there/); assert.doesNotMatch(md, /General Kenobi/); assert.match(md, /truncated: showing 1 of 2 cues/); }); test("stampForCue renders the compact form and floors fractional seconds", () => { const md = transcriptToMarkdown(base, { stampForCue: (clock, seconds) => `${clock}|${Math.floor(seconds)}`, }); assert.match(md, /^\[0:00\|0\] Hello there$/m); // 3661.25s floors to 3661 — the same integer momentUrl would use, so the // compact stamp and the inline link cite the identical second. assert.match(md, /^\[1:01:01\|3661\] General Kenobi$/m); }); test("stampForCue takes precedence over linkForCue (no inline links)", () => { const md = transcriptToMarkdown(base, { stampForCue: (clock, seconds) => `${clock}|${Math.floor(seconds)}`, linkForCue: (seconds) => `https://example.test/abc123?t=${seconds}s`, }); assert.ok(!md.includes("](https://example.test/"), "no per-line links"); assert.match(md, /^\[0:00\|0\] Hello there$/m); }); test("extraMeta lines render as `- ` in the metadata header", () => { const md = transcriptToMarkdown(base, { extraMeta: ["moment_base: https://example.test/abc123?t="], }); assert.match(md, /^- moment_base: https:\/\/example\.test\/abc123\?t=$/m); assert.ok( md.indexOf("moment_base:") < md.indexOf("## Description"), "extra meta sits in the header, before the body", ); }); // --------------------------------------------------------------------------- // prefixForCue — the speaker-label hook // --------------------------------------------------------------------------- test("prefixForCue returning null for every cue is byte-identical to omitting it", () => { // THE GUARANTEE THAT LETS A SPEAKER-AWARE CALLER SHARE THIS RENDERER. If a // no-op prefix changed a single byte, the speakers-off arm of an A/B would // stop being comparable to production output and the measurement would be // of the renderer, not of the speakers. const plain = transcriptToMarkdown(base); assert.equal(transcriptToMarkdown(base, { prefixForCue: () => null }), plain); assert.equal(transcriptToMarkdown(base, { prefixForCue: () => "" }), plain); }); test("prefixForCue sits OUTSIDE the timestamp bracket", () => { // A speaker name inside the bracket would break the digest prompt's contract // that every [HH:MM:SS] marker is copyable verbatim, and HMS_PATTERN would // reject the echo. const md = transcriptToMarkdown(base, { prefixForCue: (cue) => (cue.start === 0 ? "Alice: " : null), }); assert.match(md, /\[0:00\] Alice: Hello there/); assert.match(md, /\[1:01:01\] General Kenobi/); }); test("prefixForCue composes with stampForCue and with timestamps off", () => { const stamped = transcriptToMarkdown(base, { stampForCue: (clock) => `${clock}|x`, prefixForCue: () => "Bob: ", }); assert.match(stamped, /\[0:00\|x\] Bob: Hello there/); const bare = transcriptToMarkdown(base, { timestamps: false, prefixForCue: () => "Bob: ", }); assert.match(bare, /^Bob: Hello there$/m); }); test("prefixForCue receives the cue index of emitted lines", () => { const seen: number[] = []; transcriptToMarkdown(base, { prefixForCue: (_cue, i) => { seen.push(i); return null; }, }); assert.deepEqual(seen, [0, 1]); });