1import { afterEach, expect, it, vi } from "vite-plus/test";
2import { captureDeviceScreenshot, DeviceScreenshotError } from "./screenshot.ts";
3
4afterEach(() => vi.unstubAllGlobals());
5const target = {
6 platform: "android" as const,
7 deviceId: "phone with spaces",
8 access: {
9 httpBase: "https://remote.example/api/device-hub/hosts/remote",
10 wsBase: "wss://remote.example",
11 query: { ticket: "capture-ticket" },
12 credentials: false,
13 },
14};
15
16it("captures native bytes from the selected remote host with its media credentials", async () => {
17 const fetch = vi.fn(
18 async () =>
19 new Response(new Uint8Array([137, 80, 78, 71]), { headers: { "Content-Type": "image/png" } }),
20 );
21 vi.stubGlobal("fetch", fetch);
22 const image = await captureDeviceScreenshot(target, new AbortController().signal);
23 expect(image.type).toBe("image/png");
24 expect(Array.from(new Uint8Array(await image.arrayBuffer()))).toEqual([137, 80, 78, 71]);
25 expect(fetch).toHaveBeenCalledWith(
26 "https://remote.example/api/device-hub/hosts/remote/vendor/serve-emu/api/screenshot?device=phone%20with%20spaces&ticket=capture-ticket",
27 expect.objectContaining({ method: "POST", credentials: "omit" }),
28 );
29});
30
31it("preserves authorization failures and cancels an in-flight capture when its owner detaches", async () => {
32 vi.stubGlobal(
33 "fetch",
34 vi.fn(async () => new Response("expired", { status: 401 })),
35 );
36 await expect(captureDeviceScreenshot(target, new AbortController().signal)).rejects.toMatchObject(
37 new DeviceScreenshotError(401),
38 );
39 vi.stubGlobal(
40 "fetch",
41 (_url: string, init: RequestInit) =>
42 new Promise((_resolve, reject) => {
43 init.signal?.addEventListener("abort", () => reject(init.signal?.reason), { once: true });
44 }),
45 );
46 const controller = new AbortController();
47 const capture = captureDeviceScreenshot(target, controller.signal);
48 controller.abort(new Error("detached"));
49 await expect(capture).rejects.toThrow("detached");
50});