Add tests for URL fragment routing behavior with order-independent window mocking
- Add tests for hash routing based on server capabilities priority - Test that no hash is set when disconnected - Add e2e test verifying startup has no hash fragment - Use Jest module mocking for useConnection hook states 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
28d3fc56c1
commit
461403b624
2 changed files with 175 additions and 0 deletions
16
client/e2e/startup-state.spec.ts
Normal file
16
client/e2e/startup-state.spec.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import { test, expect } from "@playwright/test";
|
||||
|
||||
// Adjust the URL if your dev server runs on a different port
|
||||
const APP_URL = "http://localhost:6274/";
|
||||
|
||||
test.describe("Startup State", () => {
|
||||
test("should not navigate to a tab when Inspector first opens", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.goto(APP_URL);
|
||||
|
||||
// Check that there is no hash fragment in the URL
|
||||
const url = page.url();
|
||||
expect(url).not.toContain("#");
|
||||
});
|
||||
});
|
||||
159
client/src/__tests__/App.routing.test.tsx
Normal file
159
client/src/__tests__/App.routing.test.tsx
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
import { render, waitFor } from "@testing-library/react";
|
||||
import App from "../App";
|
||||
import { useConnection } from "../lib/hooks/useConnection";
|
||||
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
||||
|
||||
// Mock auth dependencies first
|
||||
jest.mock("@modelcontextprotocol/sdk/client/auth.js", () => ({
|
||||
auth: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock("../lib/oauth-state-machine", () => ({
|
||||
OAuthStateMachine: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock("../lib/auth", () => ({
|
||||
InspectorOAuthClientProvider: jest.fn().mockImplementation(() => ({
|
||||
tokens: jest.fn().mockResolvedValue(null),
|
||||
clear: jest.fn(),
|
||||
})),
|
||||
DebugInspectorOAuthClientProvider: jest.fn(),
|
||||
}));
|
||||
|
||||
// Mock the config utils
|
||||
jest.mock("../utils/configUtils", () => ({
|
||||
...jest.requireActual("../utils/configUtils"),
|
||||
getMCPProxyAddress: jest.fn(() => "http://localhost:6277"),
|
||||
getMCPProxyAuthToken: jest.fn(() => ({
|
||||
token: "",
|
||||
header: "X-MCP-Proxy-Auth",
|
||||
})),
|
||||
getInitialTransportType: jest.fn(() => "stdio"),
|
||||
getInitialSseUrl: jest.fn(() => "http://localhost:3001/sse"),
|
||||
getInitialCommand: jest.fn(() => "mcp-server-everything"),
|
||||
getInitialArgs: jest.fn(() => ""),
|
||||
initializeInspectorConfig: jest.fn(() => ({})),
|
||||
saveInspectorConfig: jest.fn(),
|
||||
}));
|
||||
|
||||
// Default connection state is disconnected
|
||||
const disconnectedConnectionState = {
|
||||
connectionStatus: "disconnected" as const,
|
||||
serverCapabilities: null,
|
||||
mcpClient: null,
|
||||
requestHistory: [],
|
||||
makeRequest: jest.fn(),
|
||||
sendNotification: jest.fn(),
|
||||
handleCompletion: jest.fn(),
|
||||
completionsSupported: false,
|
||||
connect: jest.fn(),
|
||||
disconnect: jest.fn(),
|
||||
};
|
||||
|
||||
// Connected state for tests that need an active connection
|
||||
const connectedConnectionState = {
|
||||
...disconnectedConnectionState,
|
||||
connectionStatus: "connected" as const,
|
||||
serverCapabilities: {},
|
||||
mcpClient: {
|
||||
request: jest.fn(),
|
||||
notification: jest.fn(),
|
||||
close: jest.fn(),
|
||||
} as unknown as Client,
|
||||
};
|
||||
|
||||
// Mock required dependencies, but unrelated to routing.
|
||||
jest.mock("../lib/hooks/useDraggablePane", () => ({
|
||||
useDraggablePane: () => ({
|
||||
height: 300,
|
||||
handleDragStart: jest.fn(),
|
||||
}),
|
||||
useDraggableSidebar: () => ({
|
||||
width: 320,
|
||||
isDragging: false,
|
||||
handleDragStart: jest.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock("../components/Sidebar", () => ({
|
||||
__esModule: true,
|
||||
default: () => <div>Sidebar</div>,
|
||||
}));
|
||||
|
||||
// Mock fetch
|
||||
global.fetch = jest.fn().mockResolvedValue({ json: () => Promise.resolve({}) });
|
||||
|
||||
// Use an empty module mock, so that mock state can be reset between tests.
|
||||
jest.mock("../lib/hooks/useConnection", () => ({
|
||||
useConnection: jest.fn(),
|
||||
}));
|
||||
|
||||
describe("App - URL Fragment Routing", () => {
|
||||
const mockUseConnection = jest.mocked(useConnection);
|
||||
|
||||
beforeEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
|
||||
// Inspector starts disconnected.
|
||||
mockUseConnection.mockReturnValue(disconnectedConnectionState);
|
||||
});
|
||||
|
||||
test("does not set hash when starting disconnected", async () => {
|
||||
render(<App />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(window.location.hash).toBe("");
|
||||
});
|
||||
});
|
||||
|
||||
test("sets default hash based on server capabilities priority", async () => {
|
||||
// Tab priority follows UI order: Resources | Prompts | Tools | Ping | Sampling | Roots | Auth
|
||||
//
|
||||
// Server capabilities determine the first three tabs; if none are present, falls back to Ping.
|
||||
|
||||
const testCases = [
|
||||
{
|
||||
capabilities: { resources: { listChanged: true, subscribe: true } },
|
||||
expected: "#resources",
|
||||
},
|
||||
{
|
||||
capabilities: { prompts: { listChanged: true, subscribe: true } },
|
||||
expected: "#prompts",
|
||||
},
|
||||
{
|
||||
capabilities: { tools: { listChanged: true, subscribe: true } },
|
||||
expected: "#tools",
|
||||
},
|
||||
{ capabilities: {}, expected: "#ping" },
|
||||
];
|
||||
|
||||
const { rerender } = render(<App />);
|
||||
|
||||
for (const { capabilities, expected } of testCases) {
|
||||
window.location.hash = "";
|
||||
mockUseConnection.mockReturnValue({
|
||||
...connectedConnectionState,
|
||||
serverCapabilities: capabilities,
|
||||
});
|
||||
|
||||
rerender(<App />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(window.location.hash).toBe(expected);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
test("clears hash when disconnected", async () => {
|
||||
// Start with a hash set (simulating a connection)
|
||||
window.location.hash = "#resources";
|
||||
|
||||
// App starts disconnected (default mock)
|
||||
render(<App />);
|
||||
|
||||
// Should clear the hash when disconnected
|
||||
await waitFor(() => {
|
||||
expect(window.location.hash).toBe("");
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue