About, Listen & lyrics (real player, synced lyrics, Watch), Paths (strip when the timeline is hidden, motif rows, nine-place chart), Fan works. Co-Authored-By: Claude Opus 5.5 <[email protected]>
87 lines
3.8 KiB
TypeScript
87 lines
3.8 KiB
TypeScript
import { afterEach, expect, test } from "bun:test";
|
||
import { loadBook, loadLibrary, loadLyrics, passageStart, realFor } from "../src/app/content";
|
||
import { songById } from "../src/data/album";
|
||
import type { HomerBook } from "../src/content/homer";
|
||
|
||
// Placeholder text only: never real lyrics in tests.
|
||
const realFetch = globalThis.fetch;
|
||
afterEach(() => { globalThis.fetch = realFetch; });
|
||
|
||
function mockFetch(routes: Record<string, () => Response>) {
|
||
const seen: string[] = [];
|
||
globalThis.fetch = (async (input: RequestInfo | URL) => {
|
||
const url = String(input);
|
||
seen.push(url);
|
||
const hit = routes[url];
|
||
return hit ? hit() : new Response("Not found", { status: 404 });
|
||
}) as typeof fetch;
|
||
return seen;
|
||
}
|
||
|
||
const library = {
|
||
sagas: [
|
||
{
|
||
name: "The Circe Saga", year: 2024, cover: "/content/files/circe/folder.jpg",
|
||
songs: [
|
||
{ track: 2, title: "Wouldn't You Like", audio: "/content/files/circe/02.flac", lyrics: { url: "/content/files/circe/02.lrc", timed: true } },
|
||
{ track: 3, title: "Done For", audio: "/content/files/circe/03.flac", lyrics: { url: "/content/files/circe/03.txt", timed: false } },
|
||
],
|
||
},
|
||
],
|
||
};
|
||
|
||
test("loadLibrary is false when /content 404s", async () => {
|
||
mockFetch({});
|
||
expect(await loadLibrary()).toBe(false);
|
||
expect(realFor(songById("polyphemus")!)).toBeNull();
|
||
});
|
||
|
||
test("loadLibrary is false when the request times out", async () => {
|
||
globalThis.fetch = ((_: RequestInfo | URL, init?: RequestInit) =>
|
||
new Promise((_res, rej) => init?.signal?.addEventListener("abort", () => rej(new Error("aborted"))))) as typeof fetch;
|
||
expect(await loadLibrary(20)).toBe(false);
|
||
});
|
||
|
||
test("realFor matches by normalised title, including a curly apostrophe", async () => {
|
||
mockFetch({ "/content/library.json": () => Response.json(library) });
|
||
expect(await loadLibrary()).toBe(true);
|
||
const song = songById("wouldnt-you-like")!;
|
||
expect(song.title).toBe("Wouldn’t You Like");
|
||
expect(realFor(song)).toEqual({
|
||
audio: "/content/files/circe/02.flac",
|
||
cover: "/content/files/circe/folder.jpg",
|
||
lyrics: { url: "/content/files/circe/02.lrc", timed: true },
|
||
});
|
||
expect(realFor(songById("polyphemus")!)).toBeNull();
|
||
});
|
||
|
||
test("loadLyrics parses .lrc as timed lines and .txt as plain lines", async () => {
|
||
mockFetch({
|
||
"/content/files/x/a.lrc": () => new Response("[ar:Someone]\n[00:02.00]Line B\n[00:01.00]Line A\n"),
|
||
"/content/files/x/a.txt": () => new Response("Line A\r\n\r\nLine B\n"),
|
||
});
|
||
expect(await loadLyrics({ url: "/content/files/x/a.lrc", timed: true })).toEqual({
|
||
timed: true, lines: [{ time: 1, text: "Line A" }, { time: 2, text: "Line B" }],
|
||
});
|
||
expect(await loadLyrics({ url: "/content/files/x/a.txt", timed: false })).toEqual({ timed: false, lines: ["Line A", "", "Line B"] });
|
||
expect(await loadLyrics({ url: "/content/files/x/missing.lrc", timed: true })).toBeNull();
|
||
});
|
||
|
||
test("loadBook fetches one book, null when missing", async () => {
|
||
const book: HomerBook = { book: 11, heading: "BOOK XI", summary: "S", paragraphs: ["p"] };
|
||
const seen = mockFetch({ "/content/homer/odyssey/11.json": () => Response.json(book) });
|
||
expect(await loadBook(11)).toEqual(book);
|
||
expect(await loadBook(30)).toBeNull();
|
||
expect(seen).toContain("/content/homer/odyssey/11.json");
|
||
});
|
||
|
||
test("passageStart finds the first paragraph with a keyword, else 0", () => {
|
||
const book: HomerBook = {
|
||
book: 9, heading: "BOOK IX", summary: "",
|
||
paragraphs: ["Nothing here.", "Still nothing, cyclopean is not a word match.", "Then the Cyclops spoke.", "The cave again."],
|
||
};
|
||
expect(passageStart(book, ["cave", "Cyclops"])).toEqual({ index: 2, word: "Cyclops" });
|
||
expect(passageStart(book, ["Noman"])).toEqual({ index: 0, word: null });
|
||
expect(passageStart(book, [])).toEqual({ index: 0, word: null });
|
||
});
|