Skip to main content
Glama
useClickHandler.test.ts12.4 kB
/// <reference types="jest" /> import { useClickHandler } from "../useClickHandler"; import { ControlClient } from "../../lib/types"; import { renderHook, act } from "@testing-library/react"; // Mock ControlClient class MockControlClient implements ControlClient { public keyEvents: Array<{ keycode: number; action: string; metaState: number; }> = []; public isMouseDragging: boolean = false; // ControlClient interface implementation connect( _deviceSerial: string, _apiUrl: string, _wsUrl?: string ): Promise<void> { return Promise.resolve(); } disconnect(): void { // Mock implementation } isControlConnected(): boolean { return true; } sendKeyEvent( keycode: number, action: "down" | "up", metaState: number = 0 ): void { this.keyEvents.push({ keycode, action, metaState }); } sendTouchEvent( _x: number, _y: number, _action: "down" | "up" | "move", _pressure: number = 1.0 ): void { // Mock implementation } sendControlAction(_action: string, _params?: unknown): void { // Mock implementation } sendClipboardSet(_text: string, _paste?: boolean): void { // Mock implementation } requestKeyframe(): void { // Mock implementation } handleMouseEvent(_event: MouseEvent, _action: "down" | "up" | "move"): void { // Mock implementation } handleTouchEvent(_event: TouchEvent, _action: "down" | "up" | "move"): void { // Mock implementation } } describe("useClickHandler", () => { let mockClient: MockControlClient; beforeEach(() => { mockClient = new MockControlClient(); jest.clearAllMocks(); }); it("should provide click handler", () => { const { result } = renderHook(() => useClickHandler({ client: mockClient, enabled: true, isConnected: true, }) ); expect(result.current.handleClick).toBeDefined(); expect(typeof result.current.handleClick).toBe("function"); }); it("should handle single click correctly", () => { const consoleSpy = jest.spyOn(console, "log").mockImplementation(() => {}); const { result } = renderHook(() => useClickHandler({ client: mockClient, enabled: true, isConnected: true, }) ); const mockEvent = { clientX: 100, clientY: 200, } as unknown as MouseEvent; act(() => { // eslint-disable-next-line @typescript-eslint/no-explicit-any result.current.handleClick(mockEvent as any); }); expect(consoleSpy).toHaveBeenCalledWith( "[ClickHandler] Single click - position cursor" ); expect(mockClient.keyEvents).toHaveLength(0); // Single click doesn't send key events consoleSpy.mockRestore(); }); it("should handle double click correctly", () => { const consoleSpy = jest.spyOn(console, "log").mockImplementation(() => {}); const { result } = renderHook(() => useClickHandler({ client: mockClient, enabled: true, isConnected: true, }) ); const mockEvent = { clientX: 100, clientY: 200, } as unknown as MouseEvent; // First click act(() => { // eslint-disable-next-line @typescript-eslint/no-explicit-any result.current.handleClick(mockEvent as any); }); // Second click within 500ms and 50px act(() => { // eslint-disable-next-line @typescript-eslint/no-explicit-any result.current.handleClick(mockEvent as any); }); expect(consoleSpy).toHaveBeenCalledWith( "[ClickHandler] Double click - select word" ); expect(mockClient.keyEvents).toHaveLength(0); // Double click doesn't send key events consoleSpy.mockRestore(); }); it("should handle triple click correctly", async () => { const consoleSpy = jest.spyOn(console, "log").mockImplementation(() => {}); const { result } = renderHook(() => useClickHandler({ client: mockClient, enabled: true, isConnected: true, }) ); const mockEvent = { clientX: 100, clientY: 200, } as unknown as MouseEvent; // First click act(() => { // eslint-disable-next-line @typescript-eslint/no-explicit-any result.current.handleClick(mockEvent as any); }); // Second click act(() => { // eslint-disable-next-line @typescript-eslint/no-explicit-any result.current.handleClick(mockEvent as any); }); // Third click act(() => { // eslint-disable-next-line @typescript-eslint/no-explicit-any result.current.handleClick(mockEvent as any); }); expect(consoleSpy).toHaveBeenCalledWith( "[ClickHandler] Triple click - select line" ); // Wait for the async key events to complete await new Promise((resolve) => setTimeout(resolve, 50)); expect(mockClient.keyEvents).toHaveLength(4); // Ctrl down, A down, A up, Ctrl up expect(mockClient.keyEvents[0]).toEqual({ keycode: 113, // Ctrl down action: "down", metaState: 0x1000, // META_CTRL_ON }); expect(mockClient.keyEvents[1]).toEqual({ keycode: 29, // A down action: "down", metaState: 0x1000, // META_CTRL_ON }); expect(mockClient.keyEvents[2]).toEqual({ keycode: 29, // A up action: "up", metaState: 0x1000, // META_CTRL_ON }); expect(mockClient.keyEvents[3]).toEqual({ keycode: 113, // Ctrl up action: "up", metaState: 0, }); consoleSpy.mockRestore(); }); it("should reset click count after triple click", async () => { const consoleSpy = jest.spyOn(console, "log").mockImplementation(() => {}); const { result } = renderHook(() => useClickHandler({ client: mockClient, enabled: true, isConnected: true, }) ); const mockEvent = { clientX: 100, clientY: 200, } as unknown as MouseEvent; // Triple click act(() => { // eslint-disable-next-line @typescript-eslint/no-explicit-any result.current.handleClick(mockEvent as any); }); act(() => { // eslint-disable-next-line @typescript-eslint/no-explicit-any result.current.handleClick(mockEvent as any); }); act(() => { // eslint-disable-next-line @typescript-eslint/no-explicit-any result.current.handleClick(mockEvent as any); }); // Wait for the async key events to complete await new Promise((resolve) => setTimeout(resolve, 50)); expect(mockClient.keyEvents).toHaveLength(4); // Reset click count, so next click should be single mockClient.keyEvents = []; act(() => { // eslint-disable-next-line @typescript-eslint/no-explicit-any result.current.handleClick(mockEvent as any); }); expect(consoleSpy).toHaveBeenCalledWith( "[ClickHandler] Single click - position cursor" ); expect(mockClient.keyEvents).toHaveLength(0); consoleSpy.mockRestore(); }); it("should not count clicks that are too far apart in time", () => { const consoleSpy = jest.spyOn(console, "log").mockImplementation(() => {}); const { result } = renderHook(() => useClickHandler({ client: mockClient, enabled: true, isConnected: true, }) ); const mockEvent = { clientX: 100, clientY: 200, } as unknown as MouseEvent; // First click act(() => { // eslint-disable-next-line @typescript-eslint/no-explicit-any result.current.handleClick(mockEvent as any); }); // Wait more than 500ms (simulate by advancing time) jest.advanceTimersByTime(600); // Second click - should be treated as single click act(() => { // eslint-disable-next-line @typescript-eslint/no-explicit-any result.current.handleClick(mockEvent as any); }); expect(consoleSpy).toHaveBeenCalledWith( "[ClickHandler] Single click - position cursor" ); expect(consoleSpy).toHaveBeenCalledTimes(2); // Two single clicks consoleSpy.mockRestore(); }); it("should not count clicks that are too far apart in position", () => { const consoleSpy = jest.spyOn(console, "log").mockImplementation(() => {}); const { result } = renderHook(() => useClickHandler({ client: mockClient, enabled: true, isConnected: true, }) ); // First click act(() => { // eslint-disable-next-line @typescript-eslint/no-explicit-any result.current.handleClick({ clientX: 100, clientY: 200 } as any); }); // Second click more than 50px away act(() => { // eslint-disable-next-line @typescript-eslint/no-explicit-any result.current.handleClick({ clientX: 200, clientY: 300 } as any); }); expect(consoleSpy).toHaveBeenCalledWith( "[ClickHandler] Single click - position cursor" ); expect(consoleSpy).toHaveBeenCalledTimes(2); // Two single clicks consoleSpy.mockRestore(); }); it("should not handle clicks when disabled", () => { const consoleSpy = jest.spyOn(console, "log").mockImplementation(() => {}); const { result } = renderHook(() => useClickHandler({ client: mockClient, enabled: false, isConnected: true, }) ); const mockEvent = { clientX: 100, clientY: 200, } as unknown as MouseEvent; act(() => { // eslint-disable-next-line @typescript-eslint/no-explicit-any result.current.handleClick(mockEvent as any); }); expect(consoleSpy).not.toHaveBeenCalled(); expect(mockClient.keyEvents).toHaveLength(0); consoleSpy.mockRestore(); }); it("should not handle clicks when not connected", () => { const consoleSpy = jest.spyOn(console, "log").mockImplementation(() => {}); const { result } = renderHook(() => useClickHandler({ client: mockClient, enabled: true, isConnected: false, }) ); const mockEvent = { clientX: 100, clientY: 200, } as unknown as MouseEvent; act(() => { // eslint-disable-next-line @typescript-eslint/no-explicit-any result.current.handleClick(mockEvent as any); }); expect(consoleSpy).not.toHaveBeenCalled(); expect(mockClient.keyEvents).toHaveLength(0); consoleSpy.mockRestore(); }); it("should not handle clicks when client is null", () => { const consoleSpy = jest.spyOn(console, "log").mockImplementation(() => {}); const { result } = renderHook(() => useClickHandler({ client: null, enabled: true, isConnected: true, }) ); const mockEvent = { clientX: 100, clientY: 200, } as unknown as MouseEvent; act(() => { // eslint-disable-next-line @typescript-eslint/no-explicit-any result.current.handleClick(mockEvent as any); }); expect(consoleSpy).not.toHaveBeenCalled(); expect(mockClient.keyEvents).toHaveLength(0); consoleSpy.mockRestore(); }); it("should handle rapid clicks correctly", async () => { const consoleSpy = jest.spyOn(console, "log").mockImplementation(() => {}); const { result } = renderHook(() => useClickHandler({ client: mockClient, enabled: true, isConnected: true, }) ); const mockEvent = { clientX: 100, clientY: 200, } as unknown as MouseEvent; // Rapid clicks act(() => { // eslint-disable-next-line @typescript-eslint/no-explicit-any result.current.handleClick(mockEvent as any); }); act(() => { // eslint-disable-next-line @typescript-eslint/no-explicit-any result.current.handleClick(mockEvent as any); }); act(() => { // eslint-disable-next-line @typescript-eslint/no-explicit-any result.current.handleClick(mockEvent as any); }); act(() => { // eslint-disable-next-line @typescript-eslint/no-explicit-any result.current.handleClick(mockEvent as any); }); expect(consoleSpy).toHaveBeenCalledWith( "[ClickHandler] Triple click - select line" ); // Wait for the async key events to complete await new Promise((resolve) => setTimeout(resolve, 50)); expect(mockClient.keyEvents).toHaveLength(4); // Only triple click sends key events consoleSpy.mockRestore(); }); });

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