1import { afterEach, expect, it, vi } from "vite-plus/test";
2import { createDeviceStreamClient } from "./stream.ts";
3
4afterEach(() => vi.unstubAllGlobals());
5
6it("delivers borrowed decoded frames, reports presentation failure, and discards late output", async () => {
7 let decoderOutput: VideoFrameOutputCallback | undefined;
8 let resolveOutput!: () => void;
9 const outputReady = new Promise<void>((resolve) => {
10 resolveOutput = resolve;
11 });
12 class Decoder {
13 static isConfigSupported = async () => ({ supported: true });
14 state = "unconfigured";
15 constructor(options: VideoDecoderInit) {
16 decoderOutput = options.output;
17 resolveOutput();
18 }
19 configure() {
20 this.state = "configured";
21 }
22 close() {
23 this.state = "closed";
24 }
25 }
26 vi.stubGlobal("VideoDecoder", Decoder);
27 vi.stubGlobal("EncodedVideoChunk", vi.fn());
28 vi.stubGlobal(
29 "WebSocket",
30 class {
31 close() {}
32 },
33 );
34 const envelope = new Uint8Array([0, 0, 0, 5, 1, 1, 0x64, 0, 0x1f]);
35 vi.stubGlobal(
36 "fetch",
37 vi.fn(
38 async (url: string) =>
39 new Response(
40 url.includes("avcc")
41 ? new ReadableStream({
42 start(controller) {
43 controller.enqueue(envelope);
44 },
45 })
46 : "seed",
47 ),
48 ),
49 );
50 const present = vi.fn(() => true);
51 const events = {
52 onStatus: vi.fn(),
53 onScreen: vi.fn(),
54 onUnauthorized: vi.fn(),
55 onMjpegFallback: vi.fn(),
56 onInputConnected: vi.fn(),
57 };
58 const client = createDeviceStreamClient(
59 {
60 platform: "ios",
61 deviceId: "phone",
62 access: {
63 httpBase: "https://test",
64 wsBase: "wss://test",
65 credentials: false,
66 query: {},
67 },
68 },
69 { present },
70 events,
71 );
72 client.start();
73 await outputReady;
74 const frame = {
75 displayWidth: 1170,
76 displayHeight: 2532,
77 close: vi.fn(),
78 } as unknown as VideoFrame;
79 decoderOutput?.(frame);
80 expect(present).toHaveBeenCalledExactlyOnceWith(frame, 1170, 2532);
81 expect(frame.close).toHaveBeenCalledOnce();
82 present.mockImplementationOnce(() => {
83 throw new Error("renderer failed");
84 });
85 expect(() => decoderOutput?.(frame)).toThrow("renderer failed");
86 expect(frame.close).toHaveBeenCalledTimes(2);
87 present.mockReturnValueOnce(false);
88 decoderOutput?.(frame);
89 expect(events.onStatus).toHaveBeenLastCalledWith(
90 "error",
91 "Could not display the device stream. Reconnect to try again.",
92 );
93 expect(frame.close).toHaveBeenCalledTimes(3);
94 decoderOutput?.(frame);
95 expect(present).toHaveBeenCalledTimes(3);
96 expect(frame.close).toHaveBeenCalledTimes(4);
97});