Files

77 lines
3.5 KiB
TypeScript

import { afterAll, beforeAll, describe, expect, test } from "bun:test";
import { GlobalRegistrator } from "@happy-dom/global-registrator";
import type { Song } from "../src/data/types";
beforeAll(() => GlobalRegistrator.register());
afterAll(() => GlobalRegistrator.unregister());
const base: Song = {
id: "x", title: "X", sagaId: "troy", track: 1,
storyTime: { yearsAfterTroy: 0, label: "t" }, odyssey: null,
parts: [{ name: "Verse 1", singers: ["A"], lines: ["l"], motifIds: [] }],
facts: ["f"], videos: [{ title: "v1", url: "https://example.com/1" }, { title: "v2", url: "https://example.com/2" }],
};
describe("credits", () => {
test("FAN_NOTICE has required phrases", async () => {
const { FAN_NOTICE } = await import("../src/shared/credits");
for (const p of ["unofficial", "not affiliated", "Jorge Rivera-Herrans"]) expect(FAN_NOTICE).toContain(p);
});
test("mountCredits puts the notice in document.body", async () => {
const { FAN_NOTICE, mountCredits } = await import("../src/shared/credits");
mountCredits();
expect(document.body.textContent).toContain(FAN_NOTICE);
});
test("mountCredits fills a [data-fan-credits] target", async () => {
const { FAN_NOTICE, mountCredits } = await import("../src/shared/credits");
document.body.innerHTML = '<footer data-fan-credits></footer>';
const el = mountCredits();
expect(el.hasAttribute("data-fan-credits")).toBe(true);
expect(document.querySelectorAll("[data-fan-credits]")).toHaveLength(1);
expect(el.textContent).toContain(FAN_NOTICE);
});
});
describe("createAudioSlot", () => {
test("createAudioSlot empty state", async () => {
const { createAudioSlot } = await import("../src/shared/audio-slot");
const el = createAudioSlot(base);
expect(el.classList.contains("audio-slot")).toBe(true);
expect(el.classList.contains("audio-slot--empty")).toBe(true);
expect(el.querySelector("audio")).toBeNull();
});
test("with audio and startSec renders player and seek button", async () => {
const { createAudioSlot } = await import("../src/shared/audio-slot");
const song: Song = { ...base, audio: "/a.mp3", parts: [{ ...base.parts[0]!, startSec: 30 }] };
const el = createAudioSlot(song);
expect(el.classList.contains("audio-slot--empty")).toBe(false);
expect(el.querySelectorAll("audio[src]")).toHaveLength(1);
expect(el.querySelectorAll('button[data-start="30"]')).toHaveLength(1);
});
test("seek button sets currentTime and swallows a rejected play()", async () => {
const { createAudioSlot } = await import("../src/shared/audio-slot");
const song: Song = { ...base, audio: "/a.mp3", parts: [{ ...base.parts[0]!, startSec: 30 }] };
const el = createAudioSlot(song);
const audio = el.querySelector("audio") as HTMLAudioElement;
let played = 0;
audio.play = () => { played++; return Promise.reject(new Error("blocked")); };
(el.querySelector("button.audio-slot__seek") as HTMLButtonElement).click();
await Promise.resolve();
expect(audio.currentTime).toBe(30);
expect(played).toBe(1);
});
});
describe("createVideoCards", () => {
test("one safe link per video", async () => {
const { createVideoCards } = await import("../src/shared/video-cards");
const el = createVideoCards(base);
const links = el.querySelectorAll("a.video-card");
expect(links).toHaveLength(2);
for (const a of links) {
expect(a.getAttribute("rel")).toContain("noopener");
expect(a.getAttribute("target")).toBe("_blank");
}
});
});