import { test } from "node:test"; import assert from "node:assert/strict"; import { VIDEO_STATES } from "yt-dlp-transcript-common/lib/availability"; import { newGroup, newLeaf, stringifyRoot, } from "yt-dlp-transcript-common/lib/searchQuery"; import { decodeShareLink, applyLinkOverrides, renderQueryTree, describeFilters, } from "./shareLink"; const ORIGIN = "https://rekietalyzer.pages.dev"; // A realistic composite qt tree: transcript:"coffee" AND NOT tags:"espresso". function qtParam(): string { const tree = newGroup({ op: "AND", children: [ newLeaf({ query: "coffee", scope: "transcripts" }), newLeaf({ query: "espresso", scope: "tags", negate: true }), ], }); return encodeURIComponent(stringifyRoot(tree)); } // A full-fidelity share link: qt tree + every filter facet + a vestigial fk. function fullLink(): string { const p = new URLSearchParams(); p.set("qt", "__QT__"); p.set("fv", "1"); p.append("fc", "Rekieta Law"); p.append("fc", "Rekieta Law (Rumble)"); p.append("ft", "v"); // videos only p.append("fa", "a"); // all-ages only p.append("fav", "a"); // available only p.append("fk", "live_chat"); // vestigial p.set("fdf", "20250101"); p.set("fdt", "20251231"); // qt must not be URL-double-encoded by URLSearchParams — splice it in raw. return `${ORIGIN}/?${p.toString().replace("__QT__", qtParam())}`; } test("decode: origin is extracted from the pasted URL", () => { const d = decodeShareLink(`${ORIGIN}/?q=x`); assert.equal(d.origin, ORIGIN); }); test("decode: qt tree parses to a composite query", () => { const d = decodeShareLink(fullLink()); assert.equal(d.querySource, "qt"); const rendered = renderQueryTree(d.tree); assert.match(rendered, /transcript:"coffee"/); assert.match(rendered, /NOT tags:"espresso"/); assert.match(rendered, /AND/); }); test("decode: every v1 filter facet is decoded", () => { const d = decodeShareLink(fullLink()); assert.ok(d.filters); const f = d.filters!; // ft=v → videos kept, livestreams dropped. assert.equal(f.videos, true); assert.equal(f.livestreams, false); // fa=a → all-ages kept, restricted dropped. assert.equal(f.allAges, true); assert.equal(f.restricted, false); // fav=a on a v1 link → available kept, and unconfirmed with it (those // videos read as available when a v1 link was written); everything // confirmed-gone dropped. assert.deepEqual( [...f.states].sort(), ["available", "maybe_missing"], ); // dates. assert.equal(f.dateFrom, "20250101"); assert.equal(f.dateTo, "20251231"); }); test("decode: fc channel names are captured; fk is decoded but flagged ignored", () => { const d = decodeShareLink(fullLink()); assert.deepEqual(d.channelNames.sort(), [ "Rekieta Law", "Rekieta Law (Rumble)", ]); assert.equal(d.hasChannelFilter, true); assert.deepEqual(d.ignoredTracks, ["live_chat"]); assert.ok(d.warnings.some((w) => /fk/.test(w) && /ignored/.test(w))); }); test("decode: legacy q/re → a single regex transcripts leaf, no filters", () => { const d = decodeShareLink(`${ORIGIN}/?q=coffee&re=1`); assert.equal(d.querySource, "legacy"); assert.equal(d.filters, null); assert.equal(d.hasChannelFilter, false); const leaf = d.tree.children[0]; assert.equal(leaf.kind, "leaf"); assert.match(renderQueryTree(d.tree), /transcript:\/coffee\//); }); test("decode: legacy m=subs → a live-chat leaf", () => { const d = decodeShareLink(`${ORIGIN}/?q=hello&m=subs`); assert.match(renderQueryTree(d.tree), /live-chat:"hello"/); }); test("decode: no fv block → unfiltered, whole-corpus scope", () => { const d = decodeShareLink(`${ORIGIN}/?fc=Some%20Channel`); assert.equal(d.filters, null); assert.equal(d.hasChannelFilter, false); assert.deepEqual(d.channelNames, []); }); test("decode: malformed qt falls back to legacy/empty with a warning", () => { const d = decodeShareLink(`${ORIGIN}/?qt=not-json&q=fallback`); assert.equal(d.querySource, "legacy"); assert.ok(d.warnings.some((w) => /malformed/.test(w))); assert.match(renderQueryTree(d.tree), /transcript:"fallback"/); }); // ─── overrides ─── test("override: clearAvailability resets the fav facet to keep-all", () => { const d = applyLinkOverrides(decodeShareLink(fullLink()), { clearAvailability: true, }); assert.equal(d.filters!.states.size, VIDEO_STATES.length); for (const st of VIDEO_STATES) assert.ok(d.filters!.states.has(st)); assert.ok(d.warnings.some((w) => /availability filter removed/.test(w))); }); test("override: clearFilters drops the whole filter block", () => { const d = applyLinkOverrides(decodeShareLink(fullLink()), { clearFilters: true, }); assert.equal(d.filters, null); }); test("override: channels replaces the channel scope", () => { const d = applyLinkOverrides(decodeShareLink(fullLink()), { channels: ["Only This"], }); assert.deepEqual(d.channelNames, ["Only This"]); assert.equal(d.hasChannelFilter, true); }); test("override: clearChannels widens to the whole corpus", () => { const d = applyLinkOverrides(decodeShareLink(fullLink()), { clearChannels: true, }); assert.deepEqual(d.channelNames, []); assert.equal(d.hasChannelFilter, false); }); test("override: query replaces the tree with a single leaf", () => { const d = applyLinkOverrides(decodeShareLink(fullLink()), { query: "different term", }); assert.match(renderQueryTree(d.tree), /transcript:"different term"/); }); test("describeFilters: names the constrained facets only", () => { const d = decodeShareLink(fullLink()); const lines = describeFilters(d.filters); assert.ok(lines.some((l) => /videos only/.test(l))); assert.ok(lines.some((l) => /all-ages only/.test(l))); assert.ok(lines.some((l) => /available.*kept/.test(l))); assert.ok(lines.some((l) => /uploaded/.test(l))); });