mirror of
https://github.com/OpenHands/OpenHands.git
synced 2025-12-26 13:52:43 +08:00
325 lines
9.8 KiB
TypeScript
325 lines
9.8 KiB
TypeScript
import { renderHook, waitFor } from "@testing-library/react";
|
|
import {
|
|
describe,
|
|
it,
|
|
expect,
|
|
beforeAll,
|
|
afterAll,
|
|
afterEach,
|
|
vi,
|
|
} from "vitest";
|
|
|
|
import { ws } from "msw";
|
|
import { setupServer } from "msw/node";
|
|
import { useWebSocket } from "#/hooks/use-websocket";
|
|
|
|
describe("useWebSocket", () => {
|
|
// MSW WebSocket mock setup
|
|
const wsLink = ws.link("ws://acme.com/ws");
|
|
|
|
const mswServer = setupServer(
|
|
wsLink.addEventListener("connection", ({ client, server }) => {
|
|
// Establish the connection
|
|
server.connect();
|
|
|
|
// Send a welcome message to confirm connection
|
|
client.send("Welcome to the WebSocket!");
|
|
}),
|
|
);
|
|
|
|
beforeAll(() => mswServer.listen());
|
|
afterEach(() => mswServer.resetHandlers());
|
|
afterAll(() => mswServer.close());
|
|
|
|
it("should establish a WebSocket connection", async () => {
|
|
const { result } = renderHook(() => useWebSocket("ws://acme.com/ws"));
|
|
|
|
// Initially should not be connected
|
|
expect(result.current.isConnected).toBe(false);
|
|
expect(result.current.lastMessage).toBe(null);
|
|
|
|
// Wait for connection to be established
|
|
await waitFor(() => {
|
|
expect(result.current.isConnected).toBe(true);
|
|
});
|
|
|
|
// Should receive the welcome message from our mock
|
|
await waitFor(() => {
|
|
expect(result.current.lastMessage).toBe("Welcome to the WebSocket!");
|
|
});
|
|
|
|
// Confirm that the WebSocket connection is established when the hook is used
|
|
expect(result.current.socket).toBeTruthy();
|
|
});
|
|
|
|
it("should handle incoming messages correctly", async () => {
|
|
const { result } = renderHook(() => useWebSocket("ws://acme.com/ws"));
|
|
|
|
// Wait for connection to be established
|
|
await waitFor(() => {
|
|
expect(result.current.isConnected).toBe(true);
|
|
});
|
|
|
|
// Should receive the welcome message from our mock
|
|
await waitFor(() => {
|
|
expect(result.current.lastMessage).toBe("Welcome to the WebSocket!");
|
|
});
|
|
|
|
// Send another message from the mock server
|
|
wsLink.broadcast("Hello from server!");
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.lastMessage).toBe("Hello from server!");
|
|
});
|
|
|
|
// Should have a messages array with all received messages
|
|
expect(result.current.messages).toEqual([
|
|
"Welcome to the WebSocket!",
|
|
"Hello from server!",
|
|
]);
|
|
});
|
|
|
|
it("should handle connection errors gracefully", async () => {
|
|
// Create a mock that will simulate an error
|
|
const errorLink = ws.link("ws://error-test.com/ws");
|
|
mswServer.use(
|
|
errorLink.addEventListener("connection", ({ client }) => {
|
|
// Simulate an error by closing the connection immediately
|
|
client.close(1006, "Connection failed");
|
|
}),
|
|
);
|
|
|
|
const { result } = renderHook(() => useWebSocket("ws://error-test.com/ws"));
|
|
|
|
// Initially should not be connected and no error
|
|
expect(result.current.isConnected).toBe(false);
|
|
expect(result.current.error).toBe(null);
|
|
|
|
// Wait for the connection to fail
|
|
await waitFor(() => {
|
|
expect(result.current.isConnected).toBe(false);
|
|
});
|
|
|
|
// Should have error information (the close event should trigger error state)
|
|
await waitFor(() => {
|
|
expect(result.current.error).not.toBe(null);
|
|
});
|
|
|
|
expect(result.current.error).toBeInstanceOf(Error);
|
|
// Should have meaningful error message (could be from onerror or onclose)
|
|
expect(
|
|
result.current.error?.message.includes("WebSocket closed with code 1006"),
|
|
).toBe(true);
|
|
|
|
// Should not crash the application
|
|
expect(result.current.socket).toBeTruthy();
|
|
});
|
|
|
|
it("should close the WebSocket connection on unmount", async () => {
|
|
const { result, unmount } = renderHook(() =>
|
|
useWebSocket("ws://acme.com/ws"),
|
|
);
|
|
|
|
// Wait for connection to be established
|
|
await waitFor(() => {
|
|
expect(result.current.isConnected).toBe(true);
|
|
});
|
|
|
|
// Verify connection is active
|
|
expect(result.current.isConnected).toBe(true);
|
|
expect(result.current.socket).toBeTruthy();
|
|
|
|
const closeSpy = vi.spyOn(result.current.socket!, "close");
|
|
|
|
// Unmount the component (this should trigger the useEffect cleanup)
|
|
unmount();
|
|
|
|
// Verify that WebSocket close was called during cleanup
|
|
expect(closeSpy).toHaveBeenCalledOnce();
|
|
});
|
|
|
|
it("should support query parameters in WebSocket URL", async () => {
|
|
const baseUrl = "ws://acme.com/ws";
|
|
const queryParams = {
|
|
token: "abc123",
|
|
userId: "user456",
|
|
version: "v1",
|
|
};
|
|
|
|
const { result } = renderHook(() => useWebSocket(baseUrl, { queryParams }));
|
|
|
|
// Wait for connection to be established
|
|
await waitFor(() => {
|
|
expect(result.current.isConnected).toBe(true);
|
|
});
|
|
|
|
// Verify that the WebSocket was created with query parameters
|
|
expect(result.current.socket).toBeTruthy();
|
|
expect(result.current.socket!.url).toBe(
|
|
"ws://acme.com/ws?token=abc123&userId=user456&version=v1",
|
|
);
|
|
});
|
|
|
|
it("should call onOpen handler when WebSocket connection opens", async () => {
|
|
const onOpenSpy = vi.fn();
|
|
const options = { onOpen: onOpenSpy };
|
|
|
|
const { result } = renderHook(() =>
|
|
useWebSocket("ws://acme.com/ws", options),
|
|
);
|
|
|
|
// Initially should not be connected
|
|
expect(result.current.isConnected).toBe(false);
|
|
expect(onOpenSpy).not.toHaveBeenCalled();
|
|
|
|
// Wait for connection to be established
|
|
await waitFor(() => {
|
|
expect(result.current.isConnected).toBe(true);
|
|
});
|
|
|
|
// onOpen handler should have been called
|
|
expect(onOpenSpy).toHaveBeenCalledOnce();
|
|
});
|
|
|
|
it("should call onClose handler when WebSocket connection closes", async () => {
|
|
const onCloseSpy = vi.fn();
|
|
const options = { onClose: onCloseSpy };
|
|
|
|
const { result, unmount } = renderHook(() =>
|
|
useWebSocket("ws://acme.com/ws", options),
|
|
);
|
|
|
|
// Wait for connection to be established
|
|
await waitFor(() => {
|
|
expect(result.current.isConnected).toBe(true);
|
|
});
|
|
|
|
expect(onCloseSpy).not.toHaveBeenCalled();
|
|
|
|
// Unmount to trigger close
|
|
unmount();
|
|
|
|
// Wait for onClose handler to be called
|
|
await waitFor(() => {
|
|
expect(onCloseSpy).toHaveBeenCalledOnce();
|
|
});
|
|
});
|
|
|
|
it("should call onMessage handler when WebSocket receives a message", async () => {
|
|
const onMessageSpy = vi.fn();
|
|
const options = { onMessage: onMessageSpy };
|
|
|
|
const { result } = renderHook(() =>
|
|
useWebSocket("ws://acme.com/ws", options),
|
|
);
|
|
|
|
// Wait for connection to be established
|
|
await waitFor(() => {
|
|
expect(result.current.isConnected).toBe(true);
|
|
});
|
|
|
|
// Should receive the welcome message from our mock
|
|
await waitFor(() => {
|
|
expect(result.current.lastMessage).toBe("Welcome to the WebSocket!");
|
|
});
|
|
|
|
// onMessage handler should have been called for the welcome message
|
|
await waitFor(() => {
|
|
expect(onMessageSpy).toHaveBeenCalledOnce();
|
|
});
|
|
|
|
// Send another message from the mock server after ensuring the first message was processed
|
|
wsLink.broadcast("Hello from server!");
|
|
|
|
await waitFor(
|
|
() => {
|
|
expect(result.current.lastMessage).toBe("Hello from server!");
|
|
},
|
|
{ timeout: 5000 },
|
|
);
|
|
|
|
// onMessage handler should have been called twice now
|
|
await waitFor(() => {
|
|
expect(onMessageSpy).toHaveBeenCalledTimes(2);
|
|
});
|
|
});
|
|
|
|
it("should call onError handler when WebSocket encounters an error", async () => {
|
|
const onErrorSpy = vi.fn();
|
|
const options = { onError: onErrorSpy };
|
|
|
|
// Create a mock that will simulate an error
|
|
const errorLink = ws.link("ws://error-test.com/ws");
|
|
mswServer.use(
|
|
errorLink.addEventListener("connection", ({ client }) => {
|
|
// Simulate an error by closing the connection immediately
|
|
client.close(1006, "Connection failed");
|
|
}),
|
|
);
|
|
|
|
const { result } = renderHook(() =>
|
|
useWebSocket("ws://error-test.com/ws", options),
|
|
);
|
|
|
|
// Initially should not be connected and no error
|
|
expect(result.current.isConnected).toBe(false);
|
|
expect(onErrorSpy).not.toHaveBeenCalled();
|
|
|
|
// Wait for the connection to fail
|
|
await waitFor(() => {
|
|
expect(result.current.isConnected).toBe(false);
|
|
});
|
|
|
|
// Should have error information
|
|
await waitFor(() => {
|
|
expect(result.current.error).not.toBe(null);
|
|
});
|
|
|
|
// onError handler should have been called
|
|
expect(onErrorSpy).toHaveBeenCalled();
|
|
});
|
|
|
|
it("should provide sendMessage function to send messages to WebSocket", async () => {
|
|
const { result } = renderHook(() => useWebSocket("ws://acme.com/ws"));
|
|
|
|
// Wait for connection to be established
|
|
await waitFor(() => {
|
|
expect(result.current.isConnected).toBe(true);
|
|
});
|
|
|
|
// Should have a sendMessage function
|
|
expect(result.current.sendMessage).toBeDefined();
|
|
expect(typeof result.current.sendMessage).toBe("function");
|
|
|
|
// Mock the WebSocket send method
|
|
const sendSpy = vi.spyOn(result.current.socket!, "send");
|
|
|
|
// Send a message
|
|
result.current.sendMessage("Hello WebSocket!");
|
|
|
|
// Verify that WebSocket.send was called with the correct message
|
|
expect(sendSpy).toHaveBeenCalledOnce();
|
|
expect(sendSpy).toHaveBeenCalledWith("Hello WebSocket!");
|
|
});
|
|
|
|
it("should not send message when WebSocket is not connected", () => {
|
|
const { result } = renderHook(() => useWebSocket("ws://acme.com/ws"));
|
|
|
|
// Initially should not be connected
|
|
expect(result.current.isConnected).toBe(false);
|
|
expect(result.current.sendMessage).toBeDefined();
|
|
|
|
// Mock the WebSocket send method (even though socket might be null)
|
|
const sendSpy = vi.fn();
|
|
if (result.current.socket) {
|
|
vi.spyOn(result.current.socket, "send").mockImplementation(sendSpy);
|
|
}
|
|
|
|
// Try to send a message when not connected
|
|
result.current.sendMessage("Hello WebSocket!");
|
|
|
|
// Verify that WebSocket.send was not called
|
|
expect(sendSpy).not.toHaveBeenCalled();
|
|
});
|
|
});
|