Files
baoyu-skills/packages/baoyu-fetch/src/__tests__/youtube-utils.test.ts
T

98 lines
3.4 KiB
TypeScript

import { describe, expect, test } from "bun:test";
import {
buildYouTubeThumbnailCandidates,
formatTimestampRange,
parseYouTubeDescriptionChapters,
parseYouTubeVideoId,
renderYouTubeTranscriptMarkdown,
} from "../adapters/youtube/utils";
describe("parseYouTubeVideoId", () => {
test("parses watch URLs", () => {
expect(parseYouTubeVideoId(new URL("https://www.youtube.com/watch?v=abc123"))).toBe("abc123");
});
test("parses youtu.be URLs", () => {
expect(parseYouTubeVideoId(new URL("https://youtu.be/abc123"))).toBe("abc123");
});
test("parses shorts URLs", () => {
expect(parseYouTubeVideoId(new URL("https://www.youtube.com/shorts/abc123"))).toBe("abc123");
});
});
describe("parseYouTubeDescriptionChapters", () => {
test("extracts chapter timestamps from description lines", () => {
expect(
parseYouTubeDescriptionChapters(`0:00 Intro
2:15 What is a product engineer?
10:05 Career paths`),
).toEqual([
{ title: "Intro", time: 0 },
{ title: "What is a product engineer?", time: 135 },
{ title: "Career paths", time: 605 },
]);
});
test("ignores isolated timestamps that do not look like chapters", () => {
expect(parseYouTubeDescriptionChapters("Published on 2026-03-26\nSee you at 1:23")).toEqual([]);
});
});
describe("renderYouTubeTranscriptMarkdown", () => {
test("renders description before chapters and keeps every segment on its own line", () => {
const markdown = renderYouTubeTranscriptMarkdown({
description: "Line one\nLine two",
chapters: [
{ title: "Intro", time: 0 },
{ title: "Deep Dive", time: 4 },
],
segments: [
{ start: 0, end: 2, text: "Hello everyone." },
{ start: 2, end: 4, text: "Welcome back." },
{ start: 4, end: 7, text: "Now the details." },
],
});
expect(markdown).toContain("## Description");
expect(markdown).toContain("Line one \nLine two");
expect(markdown).toContain("## Chapters");
expect(markdown).toContain("### Intro [0:00 -> 0:04]");
expect(markdown).toContain("[0:00 -> 0:02] Hello everyone.");
expect(markdown).toContain("[0:02 -> 0:04] Welcome back.");
expect(markdown).toContain("### Deep Dive [0:04 -> 0:07]");
expect(markdown).toContain("[0:04 -> 0:07] Now the details.");
});
test("falls back to a transcript section when chapters are unavailable", () => {
const markdown = renderYouTubeTranscriptMarkdown({
segments: [{ start: 65, end: 70, text: "Single line." }],
chapters: [],
});
expect(markdown).toContain("## Transcript");
expect(markdown).toContain("[1:05 -> 1:10] Single line.");
});
});
describe("thumbnail helpers", () => {
test("prefers max resolution thumbnail candidates before listed fallbacks", () => {
expect(
buildYouTubeThumbnailCandidates("abc123", [
"https://i.ytimg.com/vi/abc123/hqdefault.jpg",
"https://i.ytimg.com/vi/abc123/mqdefault.jpg?foo=bar",
]),
).toEqual([
"https://i.ytimg.com/vi/abc123/maxresdefault.jpg",
"https://i.ytimg.com/vi/abc123/sddefault.jpg",
"https://i.ytimg.com/vi/abc123/hqdefault.jpg",
"https://i.ytimg.com/vi/abc123/mqdefault.jpg",
"https://i.ytimg.com/vi/abc123/default.jpg",
]);
});
test("renders timestamp ranges with start and end values", () => {
expect(formatTimestampRange(3661, 3675)).toBe("[1:01:01 -> 1:01:15]");
});
});