mirror of
https://github.com/JimLiu/baoyu-skills.git
synced 2026-07-12 05:51:44 +08:00
98 lines
3.4 KiB
TypeScript
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]");
|
|
});
|
|
});
|