AGENTS.md asks for top-level `import type` instead of inline `type` specifiers; this applies it to the import statements the media work added or changed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
101 lines
3.6 KiB
TypeScript
101 lines
3.6 KiB
TypeScript
// @vitest-environment jsdom
|
|
|
|
import { act } from "react";
|
|
import type { Root } from "react-dom/client";
|
|
import { createRoot } from "react-dom/client";
|
|
import { renderToStaticMarkup } from "react-dom/server";
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
vi.mock("@lingui/react/macro", () => {
|
|
const t = (parts: TemplateStringsArray, ...values: unknown[]) =>
|
|
parts.reduce(
|
|
(text, part, index) => text + part + (index < values.length ? values[index] : ""),
|
|
"",
|
|
);
|
|
return { useLingui: () => ({ t }) };
|
|
});
|
|
|
|
import { MediaPlayer } from "./MediaPlayer";
|
|
|
|
let container: HTMLDivElement;
|
|
let root: Root;
|
|
|
|
beforeEach(() => {
|
|
vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
|
|
container = document.createElement("div");
|
|
document.body.append(container);
|
|
root = createRoot(container);
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await act(async () => root.unmount());
|
|
container.remove();
|
|
vi.unstubAllGlobals();
|
|
});
|
|
|
|
describe("MediaPlayer", () => {
|
|
it("loads nothing before it is near the viewport and fits vertical video to the column", () => {
|
|
const html = renderToStaticMarkup(
|
|
<MediaPlayer artifactId="art-1" kind="video" name="clip.mp4" width={1080} height={1920} />,
|
|
);
|
|
expect(html).toContain('aria-label="clip.mp4"');
|
|
expect(html).not.toContain("src=");
|
|
expect(html).toContain("width:270px");
|
|
expect(html).toContain("aspect-ratio:270 / 480");
|
|
});
|
|
|
|
it("streams video with its poster from the artifact route once visible", async () => {
|
|
vi.stubGlobal(
|
|
"IntersectionObserver",
|
|
class {
|
|
constructor(private readonly callback: IntersectionObserverCallback) {}
|
|
observe() {
|
|
this.callback([{ isIntersecting: true } as IntersectionObserverEntry], this as never);
|
|
}
|
|
disconnect() {}
|
|
},
|
|
);
|
|
await act(async () =>
|
|
root.render(
|
|
<MediaPlayer artifactId="art-1" kind="video" name="clip.mp4" posterArtifactId="art-2" />,
|
|
),
|
|
);
|
|
const video = container.querySelector("video")!;
|
|
expect(video.getAttribute("src")).toBe("/api/artifacts/art-1");
|
|
expect(video.getAttribute("poster")).toBe("/api/artifacts/art-2");
|
|
expect(video.getAttribute("preload")).toBe("metadata");
|
|
expect(video.hasAttribute("controls")).toBe(true);
|
|
});
|
|
|
|
it("sizes the frame from the file when the block has no dimensions", async () => {
|
|
await act(async () =>
|
|
root.render(<MediaPlayer artifactId="art-4" kind="video" name="up.mp4" />),
|
|
);
|
|
const frame = container.querySelector<HTMLDivElement>('[data-testid="media-video"]')!;
|
|
expect(frame.style.width).toBe("420px");
|
|
const video = container.querySelector("video")!;
|
|
Object.defineProperty(video, "videoWidth", { value: 1080 });
|
|
Object.defineProperty(video, "videoHeight", { value: 1920 });
|
|
await act(async () => {
|
|
video.dispatchEvent(new Event("loadedmetadata"));
|
|
});
|
|
expect(frame.style.width).toBe("270px");
|
|
});
|
|
|
|
it("falls back to a download link when the browser cannot play the file", async () => {
|
|
await act(async () =>
|
|
root.render(<MediaPlayer artifactId="art-3" kind="audio" name="memo.ogg" />),
|
|
);
|
|
const audio = container.querySelector("audio")!;
|
|
expect(audio.getAttribute("src")).toBe("/api/artifacts/art-3");
|
|
await act(async () => {
|
|
audio.dispatchEvent(new Event("error"));
|
|
});
|
|
expect(container.querySelector("audio")).toBeNull();
|
|
const link = container.querySelector("a")!;
|
|
expect(link.getAttribute("href")).toBe("/api/artifacts/art-3?download=1");
|
|
expect(link.getAttribute("download")).toBe("memo.ogg");
|
|
expect(link.getAttribute("aria-label")).toBe("Download memo.ogg");
|
|
});
|
|
});
|