// @vitest-environment jsdom // // One screenshot of this tab. The browser's picker is stubbed (jsdom has no Screen Capture API): // a cancelled or refused picker is the user saying no (null, no error); anything else is an error; // a tab that was shared but gave no frame is an error too, not a silent nothing; and the capture is // stopped whatever happened — nothing keeps sharing the tab after one frame. import { afterEach, describe, expect, it, vi } from "vitest"; import { captureTab } from "./captureTab"; function stubPicker(result: () => Promise) { Object.defineProperty(navigator, "mediaDevices", { configurable: true, value: { getDisplayMedia: vi.fn(result) }, }); } function fakeStream() { const track = { stop: vi.fn() }; return { stream: { getTracks: () => [track] } as unknown as MediaStream, track }; } afterEach(() => { vi.restoreAllMocks(); Reflect.deleteProperty(navigator, "mediaDevices"); }); describe("captureTab", () => { it.each(["NotAllowedError", "AbortError"])( "a %s from the picker is the user saying no", async (name) => { stubPicker(() => Promise.reject(new DOMException("no", name))); await expect(captureTab()).resolves.toBeNull(); }, ); it("any other failure of the picker is an error", async () => { stubPicker(() => Promise.reject(new DOMException("broken", "NotReadableError"))); await expect(captureTab()).rejects.toMatchObject({ name: "NotReadableError" }); }); it("a shared tab that gives no frame is an error, and the capture is stopped", async () => { vi.useFakeTimers(); const { stream, track } = fakeStream(); stubPicker(() => Promise.resolve(stream)); vi.spyOn(HTMLMediaElement.prototype, "play").mockResolvedValue(); // jsdom's video never has a size: no frame arrives. const result = captureTab(); const settled = expect(result).rejects.toMatchObject({ name: "NoFrameError" }); await vi.advanceTimersByTimeAsync(2_000); await settled; expect(track.stop).toHaveBeenCalled(); vi.useRealTimers(); }); });