Skip to main content
Glama
separated-client.test.ts8.96 kB
/* eslint-disable @typescript-eslint/no-explicit-any */ /// <reference types="jest" /> import { H264Client } from "../separated-client"; // Mock WebCodecs const mockEncodedVideoChunk = jest.fn(); // Mock VideoDecoder class MockVideoDecoder { // private config: any; // private output: any; // private error: any; constructor(_args: { output: unknown; error: unknown }) {} configure(_config: unknown) {} decode(_chunk: unknown) {} close() {} get state() { return "configured"; } } // Mock global objects (global as Record<string, unknown>).VideoDecoder = MockVideoDecoder; (global as Record<string, unknown>).EncodedVideoChunk = mockEncodedVideoChunk; // Mock WebCodecs availability Object.defineProperty(global, "VideoDecoder", { value: MockVideoDecoder, writable: true, configurable: true, }); // Mock DOM methods const mockCanvas = { getContext: jest.fn().mockReturnValue({ drawImage: jest.fn(), }), getBoundingClientRect: jest.fn().mockReturnValue({ left: 0, top: 0, width: 100, height: 100, }), width: 0, height: 0, style: {}, appendChild: jest.fn(), removeChild: jest.fn(), parentNode: null, }; const mockContainer = { appendChild: jest.fn(), removeChild: jest.fn(), }; // Mock document.createElement (global as Record<string, unknown>).document = { createElement: jest.fn().mockReturnValue(mockCanvas), }; // Mock MediaSource (global as Record<string, unknown>).MediaSource = { isTypeSupported: jest.fn().mockReturnValue(true), }; // Mock ResizeObserver (global as Record<string, unknown>).ResizeObserver = jest .fn() .mockImplementation(() => ({ observe: jest.fn(), unobserve: jest.fn(), disconnect: jest.fn(), })); describe("H264Client", () => { let container: HTMLElement; let h264Client: H264Client; let mockOnConnectionStateChange: jest.Mock; let mockOnError: jest.Mock; let mockOnStatsUpdate: jest.Mock; beforeEach(() => { // Reset all mocks jest.clearAllMocks(); // Create container container = mockContainer as unknown as HTMLElement; mockOnConnectionStateChange = jest.fn(); mockOnError = jest.fn(); mockOnStatsUpdate = jest.fn(); // Mock WebCodecs before creating the client (global as Record<string, unknown>).VideoDecoder = MockVideoDecoder; (global as Record<string, unknown>).EncodedVideoChunk = mockEncodedVideoChunk; // Mock document.createElement to return our mock canvas (global as any).document.createElement = jest .fn() .mockReturnValue(mockCanvas); h264Client = new H264Client(container, { onConnectionStateChange: mockOnConnectionStateChange, onError: mockOnError, onStatsUpdate: mockOnStatsUpdate, enableAudio: true, audioCodec: "opus", }); }); afterEach(() => { if (h264Client) { // Set a mock WebSocket to prevent errors during cleanup ( h264Client as unknown as { controlWebSocket: { close: jest.Mock } } ).controlWebSocket = { close: jest.fn(), }; // Set mock audio processor to prevent errors during cleanup ( h264Client as unknown as { audioProcessor: { audioElement: { pause: jest.Mock; remove: jest.Mock }; }; } ).audioProcessor = { disconnect: jest.fn(), audioElement: { pause: jest.fn(), remove: jest.fn() }, } as any; h264Client.disconnect(); } }); it("should create H264Client instance", () => { expect(h264Client).toBeDefined(); expect(h264Client).toBeInstanceOf(H264Client); }); it("should connect successfully", async () => { // Mock fetch to return a successful response global.fetch = jest.fn().mockResolvedValue({ ok: true, body: { getReader: jest.fn().mockReturnValue({ read: jest.fn().mockResolvedValue({ done: true, value: undefined }), }), }, }); // Mock WebSocket const mockWebSocket = { readyState: WebSocket.OPEN as any, send: jest.fn(), close: jest.fn(), addEventListener: jest.fn(), removeEventListener: jest.fn(), }; global.WebSocket = jest.fn( () => mockWebSocket ) as unknown as typeof WebSocket; // Set the mock WebSocket on the client (h264Client as unknown as { controlWebSocket: unknown }).controlWebSocket = mockWebSocket; await h264Client.connect( "device123", "http://api.example.com", "ws://ws.example.com" ); expect(mockOnConnectionStateChange).toHaveBeenCalledWith( "connected", "H.264 stream connected" ); }); it("should disconnect successfully", async () => { // Mock fetch and WebSocket global.fetch = jest.fn().mockResolvedValue({ ok: true, body: { getReader: jest.fn().mockReturnValue({ read: jest.fn().mockResolvedValue({ done: true, value: undefined }), }), }, }); const mockWebSocket = { readyState: WebSocket.OPEN as any, send: jest.fn(), close: jest.fn(), addEventListener: jest.fn(), removeEventListener: jest.fn(), }; global.WebSocket = jest.fn( () => mockWebSocket ) as unknown as typeof WebSocket; await h264Client.connect( "device123", "http://api.example.com", "ws://ws.example.com" ); // Mock audio processor with proper audioElement after connect if ( (h264Client as unknown as { audioProcessor: { audioElement: unknown } }) .audioProcessor ) { ( h264Client as unknown as { audioProcessor: { audioElement: { pause: jest.Mock; remove: jest.Mock }; }; } ).audioProcessor.audioElement = { pause: jest.fn(), remove: jest.fn(), }; } await h264Client.disconnect(); expect(mockOnConnectionStateChange).toHaveBeenCalledWith( "disconnected", "H.264 stream disconnected" ); }); it("should handle connection errors", async () => { const error = new Error("Connection failed"); global.fetch = jest.fn().mockRejectedValue(error); try { await h264Client.connect( "device123", "http://api.example.com", "ws://ws.example.com" ); } catch (_e) { // Expected to throw } expect(mockOnError).toHaveBeenCalledWith(error); }); it("should send control messages", async () => { // Mock fetch and WebSocket global.fetch = jest.fn().mockResolvedValue({ ok: true, body: { getReader: jest.fn().mockReturnValue({ read: jest.fn().mockResolvedValue({ done: true, value: undefined }), }), }, }); const mockWebSocket = { readyState: WebSocket.OPEN as any, send: jest.fn(), close: jest.fn(), addEventListener: jest.fn(), removeEventListener: jest.fn(), }; global.WebSocket = jest.fn( () => mockWebSocket ) as unknown as typeof WebSocket; await h264Client.connect( "device123", "http://api.example.com", "ws://ws.example.com" ); h264Client.sendKeyEvent(4, "down"); h264Client.sendTouchEvent(0.5, 0.5, "down"); h264Client.sendControlAction("home"); expect(mockWebSocket.send).toHaveBeenCalledTimes(3); }); it("should handle mouse events", () => { const mockEvent = { clientX: 50, clientY: 50, } as MouseEvent; // Mock WebSocket const mockWebSocket = { readyState: WebSocket.OPEN as any, send: jest.fn(), }; (h264Client as unknown as { controlWebSocket: unknown }).controlWebSocket = mockWebSocket; (h264Client as unknown as { controlConnected: boolean }).controlConnected = true; h264Client.handleMouseEvent(mockEvent, "down"); expect(mockWebSocket.send).toHaveBeenCalled(); }); it("should handle touch events", () => { const mockEvent = { touches: [{ clientX: 50, clientY: 50 }], } as unknown as TouchEvent; // Mock WebSocket const mockWebSocket = { readyState: WebSocket.OPEN as any, send: jest.fn(), }; (h264Client as unknown as { controlWebSocket: unknown }).controlWebSocket = mockWebSocket; (h264Client as unknown as { controlConnected: boolean }).controlConnected = true; h264Client.handleTouchEvent(mockEvent, "down"); expect(mockWebSocket.send).toHaveBeenCalled(); }); it("should check control connection status", () => { expect(h264Client.isControlConnected()).toBe(false); // Mock connected WebSocket ( h264Client as unknown as { controlWebSocket: { close: jest.Mock } } ).controlWebSocket = { close: jest.fn(), readyState: WebSocket.OPEN as any, } as any; (h264Client as unknown as { controlConnected: boolean }).controlConnected = true; expect(h264Client.isControlConnected()).toBe(true); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/babelcloud/gru-sandbox'

If you have feedback or need assistance with the MCP directory API, please join our Discord server