Files
gerbeur/src/test/components/player/MediaPlayer.test.tsx

207 lines
7.1 KiB
TypeScript

import { describe, expect, test, vi } from "vitest";
import { act, fireEvent, screen, waitFor } from "@testing-library/react";
import { userEvent } from "@testing-library/user-event";
import { MediaPlayer } from "../../../components/player/MediaPlayer.tsx";
import { renderWithContexts } from "../../render.tsx";
function media(container: HTMLElement) {
return container.querySelector<HTMLMediaElement>("audio, video")!;
}
function setTime(el: HTMLMediaElement, currentTime: number, duration: number) {
Object.defineProperty(el, "duration", {
configurable: true,
value: duration,
});
el.currentTime = currentTime;
fireEvent(el, new Event("durationchange"));
fireEvent(el, new Event("timeupdate"));
}
describe("MediaPlayer", () => {
test("play and pause through the button, reporting state", async () => {
const onPlayStateChange = vi.fn();
const play = vi.spyOn(HTMLMediaElement.prototype, "play");
const pause = vi.spyOn(HTMLMediaElement.prototype, "pause");
renderWithContexts(
<MediaPlayer
src="https://s/a.mp3"
kind="audio"
trackStyle="progress"
onPlayStateChange={onPlayStateChange}
/>,
);
await userEvent.click(screen.getByRole("button", { name: "Play" }));
expect(play).toHaveBeenCalled();
expect(onPlayStateChange).toHaveBeenLastCalledWith(true);
await userEvent.click(screen.getByRole("button", { name: "Pause" }));
expect(pause).toHaveBeenCalled();
expect(onPlayStateChange).toHaveBeenLastCalledWith(false);
});
test("autoplays when asked", async () => {
const onPlayStateChange = vi.fn();
renderWithContexts(
<MediaPlayer
src="https://s/a.mp3"
kind="audio"
trackStyle="progress"
autoplay
onPlayStateChange={onPlayStateChange}
/>,
);
await waitFor(() => expect(onPlayStateChange).toHaveBeenCalledWith(true));
expect(screen.getByRole("button", { name: "Pause" })).toBeInTheDocument();
});
test("a play refused by the browser stays paused", async () => {
vi.spyOn(HTMLMediaElement.prototype, "play").mockRejectedValue(
new Error("NotAllowedError"),
);
renderWithContexts(
<MediaPlayer
src="https://s/a.mp3"
kind="audio"
trackStyle="progress"
autoplay
/>,
);
await act(async () => {});
expect(screen.getByRole("button", { name: "Play" })).toBeInTheDocument();
});
test("a pause from outside the page (a media key) is reflected", async () => {
const onPlayStateChange = vi.fn();
const { container } = renderWithContexts(
<MediaPlayer
src="https://s/a.mp3"
kind="audio"
trackStyle="progress"
onPlayStateChange={onPlayStateChange}
/>,
);
await userEvent.click(screen.getByRole("button", { name: "Play" }));
fireEvent(media(container), new Event("pause"));
expect(screen.getByRole("button", { name: "Play" })).toBeInTheDocument();
expect(onPlayStateChange).toHaveBeenLastCalledWith(false);
fireEvent(media(container), new Event("play"));
expect(screen.getByRole("button", { name: "Pause" })).toBeInTheDocument();
expect(onPlayStateChange).toHaveBeenLastCalledWith(true);
});
test("time, duration, seeking and the end", async () => {
const onTimeUpdate = vi.fn();
const onEnded = vi.fn();
const { container } = renderWithContexts(
<MediaPlayer
src="https://s/a.mp3"
kind="audio"
trackStyle="progress"
onTimeUpdate={onTimeUpdate}
onEnded={onEnded}
/>,
);
const el = media(container);
setTime(el, 65, 200);
expect(screen.getByText("1:05")).toBeInTheDocument();
expect(screen.getByText("3:20")).toBeInTheDocument();
expect(onTimeUpdate).toHaveBeenLastCalledWith(65, 200);
fireEvent.change(screen.getByRole("slider", { name: "Seek" }), {
target: { value: "120" },
});
expect(el.currentTime).toBe(120);
fireEvent(el, new Event("ended"));
expect(onEnded).toHaveBeenCalled();
});
test("while the seek bar is held, playback doesn't move the thumb — by touch too", () => {
const { container } = renderWithContexts(
<MediaPlayer src="https://s/a.mp3" kind="audio" trackStyle="progress" />,
);
const el = media(container);
setTime(el, 10, 100);
const seek = screen.getByRole("slider", { name: "Seek" });
fireEvent.pointerDown(seek, { pointerType: "touch" });
fireEvent.change(seek, { target: { value: "50" } });
setTime(el, 11, 100);
expect(screen.getByText("0:50")).toBeInTheDocument();
fireEvent.pointerUp(seek, { pointerType: "touch" });
setTime(el, 51, 100);
expect(screen.getByText("0:51")).toBeInTheDocument();
});
test("an error is reported for re-resolving", () => {
const onError = vi.fn();
const { container } = renderWithContexts(
<MediaPlayer
src="https://s/a.mp3"
kind="audio"
trackStyle="progress"
onError={onError}
/>,
);
fireEvent(media(container), new Event("error"));
expect(onError).toHaveBeenCalled();
});
test("volume and mute are remembered", async () => {
const { container } = renderWithContexts(
<MediaPlayer src="https://s/a.mp3" kind="audio" trackStyle="progress" />,
);
fireEvent.change(screen.getByRole("slider", { name: "Volume" }), {
target: { value: "0.4" },
});
expect(media(container).volume).toBeCloseTo(0.4);
await userEvent.click(screen.getByRole("button", { name: "Mute" }));
expect(media(container).muted).toBe(true);
expect(JSON.parse(localStorage.getItem("player-volume")!)).toEqual({
volume: 0.4,
muted: true,
});
expect(screen.getByRole("slider", { name: "Volume" })).toHaveValue("0");
// Raising the volume unmutes.
fireEvent.change(screen.getByRole("slider", { name: "Volume" }), {
target: { value: "0.7" },
});
expect(media(container).muted).toBe(false);
});
test("registers seek and toggle handles for the global player", async () => {
const seekRef = { current: null as ((t: number) => void) | null };
const toggleRef = { current: null as (() => void) | null };
const { container, unmount } = renderWithContexts(
<MediaPlayer
src="https://s/a.mp3"
kind="audio"
trackStyle="progress"
seekRef={seekRef}
toggleRef={toggleRef}
/>,
);
act(() => seekRef.current!(33));
expect(media(container).currentTime).toBe(33);
await act(async () => toggleRef.current!());
expect(screen.getByRole("button", { name: "Pause" })).toBeInTheDocument();
unmount();
expect(seekRef.current).toBeNull();
});
test("resumes at a start offset once metadata is in", () => {
const { container } = renderWithContexts(
<MediaPlayer
src="https://s/a.mp3"
kind="audio"
trackStyle="progress"
startTime={42}
/>,
);
fireEvent(media(container), new Event("loadedmetadata"));
expect(media(container).currentTime).toBe(42);
});
test("video has a fullscreen button", () => {
renderWithContexts(<MediaPlayer src="https://s/a.mp4" kind="video" />);
expect(screen.getByRole("button", { name: "Fullscreen" }))
.toBeInTheDocument();
});
});