style: run prettier on src directory

This commit is contained in:
Ashwin Bhat
2024-12-20 15:45:55 -08:00
parent c2c2043d05
commit 586c497740
15 changed files with 715 additions and 615 deletions

View File

@@ -16,7 +16,7 @@ import {
ResourceTemplate, ResourceTemplate,
Root, Root,
ServerNotification, ServerNotification,
Tool Tool,
} from "@modelcontextprotocol/sdk/types.js"; } from "@modelcontextprotocol/sdk/types.js";
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
@@ -124,10 +124,7 @@ const App = () => {
const [nextToolCursor, setNextToolCursor] = useState<string | undefined>(); const [nextToolCursor, setNextToolCursor] = useState<string | undefined>();
const progressTokenRef = useRef(0); const progressTokenRef = useRef(0);
const { const { height: historyPaneHeight, handleDragStart } = useDraggablePane(300);
height: historyPaneHeight,
handleDragStart
} = useDraggablePane(300);
const { const {
connectionStatus, connectionStatus,
@@ -136,7 +133,7 @@ const App = () => {
requestHistory, requestHistory,
makeRequest: makeConnectionRequest, makeRequest: makeConnectionRequest,
sendNotification, sendNotification,
connect: connectMcpServer connect: connectMcpServer,
} = useConnection({ } = useConnection({
transportType, transportType,
command, command,
@@ -145,18 +142,21 @@ const App = () => {
env, env,
proxyServerUrl: PROXY_SERVER_URL, proxyServerUrl: PROXY_SERVER_URL,
onNotification: (notification) => { onNotification: (notification) => {
setNotifications(prev => [...prev, notification as ServerNotification]); setNotifications((prev) => [...prev, notification as ServerNotification]);
}, },
onStdErrNotification: (notification) => { onStdErrNotification: (notification) => {
setStdErrNotifications(prev => [...prev, notification as StdErrNotification]); setStdErrNotifications((prev) => [
},
onPendingRequest: (request, resolve, reject) => {
setPendingSampleRequests(prev => [
...prev, ...prev,
{ id: nextRequestId.current++, request, resolve, reject } notification as StdErrNotification,
]); ]);
}, },
getRoots: () => rootsRef.current onPendingRequest: (request, resolve, reject) => {
setPendingSampleRequests((prev) => [
...prev,
{ id: nextRequestId.current++, request, resolve, reject },
]);
},
getRoots: () => rootsRef.current,
}); });
const makeRequest = async <T extends z.ZodType>( const makeRequest = async <T extends z.ZodType>(
@@ -345,26 +345,40 @@ const App = () => {
{mcpClient ? ( {mcpClient ? (
<Tabs <Tabs
defaultValue={ defaultValue={
Object.keys(serverCapabilities ?? {}).includes(window.location.hash.slice(1)) ? Object.keys(serverCapabilities ?? {}).includes(
window.location.hash.slice(1) : window.location.hash.slice(1),
serverCapabilities?.resources ? "resources" : )
serverCapabilities?.prompts ? "prompts" : ? window.location.hash.slice(1)
serverCapabilities?.tools ? "tools" : : serverCapabilities?.resources
"ping" ? "resources"
: serverCapabilities?.prompts
? "prompts"
: serverCapabilities?.tools
? "tools"
: "ping"
} }
className="w-full p-4" className="w-full p-4"
onValueChange={(value) => (window.location.hash = value)} onValueChange={(value) => (window.location.hash = value)}
> >
<TabsList className="mb-4 p-0"> <TabsList className="mb-4 p-0">
<TabsTrigger value="resources" disabled={!serverCapabilities?.resources}> <TabsTrigger
value="resources"
disabled={!serverCapabilities?.resources}
>
<Files className="w-4 h-4 mr-2" /> <Files className="w-4 h-4 mr-2" />
Resources Resources
</TabsTrigger> </TabsTrigger>
<TabsTrigger value="prompts" disabled={!serverCapabilities?.prompts}> <TabsTrigger
value="prompts"
disabled={!serverCapabilities?.prompts}
>
<MessageSquare className="w-4 h-4 mr-2" /> <MessageSquare className="w-4 h-4 mr-2" />
Prompts Prompts
</TabsTrigger> </TabsTrigger>
<TabsTrigger value="tools" disabled={!serverCapabilities?.tools}> <TabsTrigger
value="tools"
disabled={!serverCapabilities?.tools}
>
<Hammer className="w-4 h-4 mr-2" /> <Hammer className="w-4 h-4 mr-2" />
Tools Tools
</TabsTrigger> </TabsTrigger>
@@ -388,7 +402,9 @@ const App = () => {
</TabsList> </TabsList>
<div className="w-full"> <div className="w-full">
{!serverCapabilities?.resources && !serverCapabilities?.prompts && !serverCapabilities?.tools ? ( {!serverCapabilities?.resources &&
!serverCapabilities?.prompts &&
!serverCapabilities?.tools ? (
<div className="flex items-center justify-center p-4"> <div className="flex items-center justify-center p-4">
<p className="text-lg text-gray-500"> <p className="text-lg text-gray-500">
The connected server does not support any MCP capabilities The connected server does not support any MCP capabilities

View File

@@ -1,146 +1,149 @@
import { describe, it, expect, vi, beforeEach } from 'vitest' import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, screen, fireEvent, act } from '@testing-library/react' import { render, screen, fireEvent, act } from "@testing-library/react";
import App from '../App' import App from "../App";
import { useConnection } from '../lib/hooks/useConnection' import { useConnection } from "../lib/hooks/useConnection";
import { useDraggablePane } from '../lib/hooks/useDraggablePane' import { useDraggablePane } from "../lib/hooks/useDraggablePane";
// Mock URL params // Mock URL params
const mockURLSearchParams = vi.fn() const mockURLSearchParams = vi.fn();
vi.stubGlobal('URLSearchParams', mockURLSearchParams) vi.stubGlobal("URLSearchParams", mockURLSearchParams);
// Mock the hooks // Mock the hooks
vi.mock('../lib/hooks/useConnection', () => ({ vi.mock("../lib/hooks/useConnection", () => ({
useConnection: vi.fn() useConnection: vi.fn(),
})) }));
vi.mock('../lib/hooks/useDraggablePane', () => ({ vi.mock("../lib/hooks/useDraggablePane", () => ({
useDraggablePane: vi.fn() useDraggablePane: vi.fn(),
})) }));
// Mock fetch for config // Mock fetch for config
const mockFetch = vi.fn() const mockFetch = vi.fn();
global.fetch = mockFetch global.fetch = mockFetch;
describe('App', () => { describe("App", () => {
beforeEach(() => { beforeEach(() => {
// Reset all mocks // Reset all mocks
vi.clearAllMocks() vi.clearAllMocks();
// Mock URL params // Mock URL params
mockURLSearchParams.mockReturnValue({ mockURLSearchParams.mockReturnValue({
get: () => '3000' get: () => "3000",
}) });
// Mock fetch response // Mock fetch response
mockFetch.mockResolvedValue({ mockFetch.mockResolvedValue({
json: () => Promise.resolve({ json: () =>
defaultEnvironment: {}, Promise.resolve({
defaultCommand: 'test-command', defaultEnvironment: {},
defaultArgs: '--test' defaultCommand: "test-command",
}) defaultArgs: "--test",
}) }),
});
// Mock useConnection hook // Mock useConnection hook
const mockUseConnection = useConnection as jest.Mock const mockUseConnection = useConnection as jest.Mock;
mockUseConnection.mockReturnValue({ mockUseConnection.mockReturnValue({
connectionStatus: 'disconnected', connectionStatus: "disconnected",
serverCapabilities: null, serverCapabilities: null,
mcpClient: null, mcpClient: null,
requestHistory: [], requestHistory: [],
makeRequest: vi.fn(), makeRequest: vi.fn(),
sendNotification: vi.fn(), sendNotification: vi.fn(),
connect: vi.fn() connect: vi.fn(),
}) });
// Mock useDraggablePane hook // Mock useDraggablePane hook
const mockUseDraggablePane = useDraggablePane as jest.Mock const mockUseDraggablePane = useDraggablePane as jest.Mock;
mockUseDraggablePane.mockReturnValue({ mockUseDraggablePane.mockReturnValue({
height: 300, height: 300,
handleDragStart: vi.fn() handleDragStart: vi.fn(),
}) });
}) });
it('renders initial disconnected state', async () => { it("renders initial disconnected state", async () => {
await act(async () => { await act(async () => {
render(<App />) render(<App />);
}) });
expect(screen.getByText('Connect to an MCP server to start inspecting')).toBeInTheDocument() expect(
}) screen.getByText("Connect to an MCP server to start inspecting"),
).toBeInTheDocument();
});
it('loads config on mount', async () => { it("loads config on mount", async () => {
await act(async () => { await act(async () => {
render(<App />) render(<App />);
}) });
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/config') expect(mockFetch).toHaveBeenCalledWith("http://localhost:3000/config");
}) });
it('shows connected interface when mcpClient is available', async () => { it("shows connected interface when mcpClient is available", async () => {
const mockUseConnection = useConnection as jest.Mock const mockUseConnection = useConnection as jest.Mock;
mockUseConnection.mockReturnValue({ mockUseConnection.mockReturnValue({
connectionStatus: 'connected', connectionStatus: "connected",
serverCapabilities: { serverCapabilities: {
resources: true, resources: true,
prompts: true, prompts: true,
tools: true tools: true,
}, },
mcpClient: {}, mcpClient: {},
requestHistory: [], requestHistory: [],
makeRequest: vi.fn(), makeRequest: vi.fn(),
sendNotification: vi.fn(), sendNotification: vi.fn(),
connect: vi.fn() connect: vi.fn(),
}) });
await act(async () => { await act(async () => {
render(<App />) render(<App />);
}) });
// Use more specific selectors // Use more specific selectors
const resourcesTab = screen.getByRole('tab', { name: /resources/i }) const resourcesTab = screen.getByRole("tab", { name: /resources/i });
const promptsTab = screen.getByRole('tab', { name: /prompts/i }) const promptsTab = screen.getByRole("tab", { name: /prompts/i });
const toolsTab = screen.getByRole('tab', { name: /tools/i }) const toolsTab = screen.getByRole("tab", { name: /tools/i });
expect(resourcesTab).toBeInTheDocument() expect(resourcesTab).toBeInTheDocument();
expect(promptsTab).toBeInTheDocument() expect(promptsTab).toBeInTheDocument();
expect(toolsTab).toBeInTheDocument() expect(toolsTab).toBeInTheDocument();
}) });
it('disables tabs based on server capabilities', async () => { it("disables tabs based on server capabilities", async () => {
const mockUseConnection = useConnection as jest.Mock const mockUseConnection = useConnection as jest.Mock;
mockUseConnection.mockReturnValue({ mockUseConnection.mockReturnValue({
connectionStatus: 'connected', connectionStatus: "connected",
serverCapabilities: { serverCapabilities: {
resources: false, resources: false,
prompts: true, prompts: true,
tools: false tools: false,
}, },
mcpClient: {}, mcpClient: {},
requestHistory: [], requestHistory: [],
makeRequest: vi.fn(), makeRequest: vi.fn(),
sendNotification: vi.fn(), sendNotification: vi.fn(),
connect: vi.fn() connect: vi.fn(),
}) });
await act(async () => { await act(async () => {
render(<App />) render(<App />);
}) });
// Resources tab should be disabled // Resources tab should be disabled
const resourcesTab = screen.getByRole('tab', { name: /resources/i }) const resourcesTab = screen.getByRole("tab", { name: /resources/i });
expect(resourcesTab).toHaveAttribute('disabled') expect(resourcesTab).toHaveAttribute("disabled");
// Prompts tab should be enabled // Prompts tab should be enabled
const promptsTab = screen.getByRole('tab', { name: /prompts/i }) const promptsTab = screen.getByRole("tab", { name: /prompts/i });
expect(promptsTab).not.toHaveAttribute('disabled') expect(promptsTab).not.toHaveAttribute("disabled");
// Tools tab should be disabled // Tools tab should be disabled
const toolsTab = screen.getByRole('tab', { name: /tools/i }) const toolsTab = screen.getByRole("tab", { name: /tools/i });
expect(toolsTab).toHaveAttribute('disabled') expect(toolsTab).toHaveAttribute("disabled");
}) });
it('shows notification count in sampling tab', async () => { it("shows notification count in sampling tab", async () => {
const mockUseConnection = useConnection as jest.Mock const mockUseConnection = useConnection as jest.Mock;
mockUseConnection.mockReturnValue({ mockUseConnection.mockReturnValue({
connectionStatus: 'connected', connectionStatus: "connected",
serverCapabilities: { sampling: true }, serverCapabilities: { sampling: true },
mcpClient: {}, mcpClient: {},
requestHistory: [], requestHistory: [],
@@ -149,65 +152,69 @@ describe('App', () => {
connect: vi.fn(), connect: vi.fn(),
onPendingRequest: (request, resolve, reject) => { onPendingRequest: (request, resolve, reject) => {
// Simulate a pending request // Simulate a pending request
setPendingSampleRequests(prev => [ setPendingSampleRequests((prev) => [
...prev, ...prev,
{ id: 1, request, resolve, reject } { id: 1, request, resolve, reject },
]) ]);
} },
}) });
await act(async () => { await act(async () => {
render(<App />) render(<App />);
}) });
// Initially no notification count // Initially no notification count
const samplingTab = screen.getByRole('tab', { name: /sampling/i }) const samplingTab = screen.getByRole("tab", { name: /sampling/i });
expect(samplingTab.querySelector('.bg-red-500')).not.toBeInTheDocument() expect(samplingTab.querySelector(".bg-red-500")).not.toBeInTheDocument();
// Simulate a pending request // Simulate a pending request
await act(async () => { await act(async () => {
mockUseConnection.mock.calls[0][0].onPendingRequest( mockUseConnection.mock.calls[0][0].onPendingRequest(
{ method: 'test', params: {} }, { method: "test", params: {} },
() => {}, () => {},
() => {} () => {},
) );
}) });
// Should show notification count // Should show notification count
expect(samplingTab.querySelector('.bg-red-500')).toBeInTheDocument() expect(samplingTab.querySelector(".bg-red-500")).toBeInTheDocument();
}) });
it('persists command and args to localStorage', async () => { it("persists command and args to localStorage", async () => {
const setItemSpy = vi.spyOn(Storage.prototype, 'setItem') const setItemSpy = vi.spyOn(Storage.prototype, "setItem");
await act(async () => { await act(async () => {
render(<App />) render(<App />);
}) });
// Simulate command change // Simulate command change
await act(async () => { await act(async () => {
const commandInput = screen.getByPlaceholderText(/command/i) const commandInput = screen.getByPlaceholderText(/command/i);
fireEvent.change(commandInput, { target: { value: 'new-command' } }) fireEvent.change(commandInput, { target: { value: "new-command" } });
}) });
expect(setItemSpy).toHaveBeenCalledWith('lastCommand', 'new-command') expect(setItemSpy).toHaveBeenCalledWith("lastCommand", "new-command");
}) });
it('shows error message when server has no capabilities', async () => { it("shows error message when server has no capabilities", async () => {
const mockUseConnection = useConnection as jest.Mock const mockUseConnection = useConnection as jest.Mock;
mockUseConnection.mockReturnValue({ mockUseConnection.mockReturnValue({
connectionStatus: 'connected', connectionStatus: "connected",
serverCapabilities: {}, serverCapabilities: {},
mcpClient: {}, mcpClient: {},
requestHistory: [], requestHistory: [],
makeRequest: vi.fn(), makeRequest: vi.fn(),
sendNotification: vi.fn(), sendNotification: vi.fn(),
connect: vi.fn() connect: vi.fn(),
}) });
await act(async () => { await act(async () => {
render(<App />) render(<App />);
}) });
expect(screen.getByText('The connected server does not support any MCP capabilities')).toBeInTheDocument() expect(
}) screen.getByText(
}) "The connected server does not support any MCP capabilities",
),
).toBeInTheDocument();
});
});

View File

@@ -1,40 +1,61 @@
import { describe, it, expect } from 'vitest' import { describe, it, expect } from "vitest";
import { render, screen, fireEvent } from '@testing-library/react' import { render, screen, fireEvent } from "@testing-library/react";
import HistoryAndNotifications from '../../components/History' import HistoryAndNotifications from "../../components/History";
describe('HistoryAndNotifications', () => { describe("HistoryAndNotifications", () => {
const mockHistory = [ const mockHistory = [
{ request: JSON.stringify({ method: 'test1' }), response: JSON.stringify({ result: 'output1' }) }, {
{ request: JSON.stringify({ method: 'test2' }), response: JSON.stringify({ result: 'output2' }) } request: JSON.stringify({ method: "test1" }),
] response: JSON.stringify({ result: "output1" }),
},
{
request: JSON.stringify({ method: "test2" }),
response: JSON.stringify({ result: "output2" }),
},
];
it('renders history items', () => { it("renders history items", () => {
render(<HistoryAndNotifications requestHistory={mockHistory} serverNotifications={[]} />) render(
const items = screen.getAllByText(/test[12]/, { exact: false }) <HistoryAndNotifications
expect(items).toHaveLength(2) requestHistory={mockHistory}
}) serverNotifications={[]}
/>,
);
const items = screen.getAllByText(/test[12]/, { exact: false });
expect(items).toHaveLength(2);
});
it('expands history item when clicked', () => { it("expands history item when clicked", () => {
render(<HistoryAndNotifications requestHistory={mockHistory} serverNotifications={[]} />) render(
<HistoryAndNotifications
requestHistory={mockHistory}
serverNotifications={[]}
/>,
);
const firstItem = screen.getByText(/test1/, { exact: false }) const firstItem = screen.getByText(/test1/, { exact: false });
fireEvent.click(firstItem) fireEvent.click(firstItem);
expect(screen.getByText('Request:')).toBeInTheDocument() expect(screen.getByText("Request:")).toBeInTheDocument();
expect(screen.getByText(/output1/, { exact: false })).toBeInTheDocument() expect(screen.getByText(/output1/, { exact: false })).toBeInTheDocument();
}) });
it('renders and expands server notifications', () => { it("renders and expands server notifications", () => {
const notifications = [ const notifications = [
{ method: 'notify1', params: { data: 'test data 1' } }, { method: "notify1", params: { data: "test data 1" } },
{ method: 'notify2', params: { data: 'test data 2' } } { method: "notify2", params: { data: "test data 2" } },
] ];
render(<HistoryAndNotifications requestHistory={[]} serverNotifications={notifications} />) render(
<HistoryAndNotifications
requestHistory={[]}
serverNotifications={notifications}
/>,
);
const items = screen.getAllByText(/notify[12]/, { exact: false }) const items = screen.getAllByText(/notify[12]/, { exact: false });
expect(items).toHaveLength(2) expect(items).toHaveLength(2);
fireEvent.click(items[0]) fireEvent.click(items[0]);
expect(screen.getByText('Details:')).toBeInTheDocument() expect(screen.getByText("Details:")).toBeInTheDocument();
expect(screen.getByText(/test data/, { exact: false })).toBeInTheDocument() expect(screen.getByText(/test data/, { exact: false })).toBeInTheDocument();
}) });
}) });

View File

@@ -1,17 +1,17 @@
import { describe, it, expect, vi } from 'vitest' import { describe, it, expect, vi } from "vitest";
import { render, screen, fireEvent } from '@testing-library/react' import { render, screen, fireEvent } from "@testing-library/react";
import ListPane from '../../components/ListPane' import ListPane from "../../components/ListPane";
describe('ListPane', () => { describe("ListPane", () => {
type TestItem = { type TestItem = {
id: number; id: number;
name: string; name: string;
} };
const mockItems: TestItem[] = [ const mockItems: TestItem[] = [
{ id: 1, name: 'Item 1' }, { id: 1, name: "Item 1" },
{ id: 2, name: 'Item 2' } { id: 2, name: "Item 2" },
] ];
const defaultProps = { const defaultProps = {
items: mockItems, items: mockItems,
@@ -24,61 +24,61 @@ describe('ListPane', () => {
<span className="text-sm text-gray-500">ID: {item.id}</span> <span className="text-sm text-gray-500">ID: {item.id}</span>
</> </>
), ),
title: 'Test Items', title: "Test Items",
buttonText: 'List Items' buttonText: "List Items",
} };
it('renders title and buttons', () => { it("renders title and buttons", () => {
render(<ListPane {...defaultProps} />) render(<ListPane {...defaultProps} />);
expect(screen.getByText('Test Items')).toBeInTheDocument() expect(screen.getByText("Test Items")).toBeInTheDocument();
expect(screen.getByText('List Items')).toBeInTheDocument() expect(screen.getByText("List Items")).toBeInTheDocument();
expect(screen.getByText('Clear')).toBeInTheDocument() expect(screen.getByText("Clear")).toBeInTheDocument();
}) });
it('renders list of items using renderItem prop', () => { it("renders list of items using renderItem prop", () => {
render(<ListPane {...defaultProps} />) render(<ListPane {...defaultProps} />);
expect(screen.getByText('Item 1')).toBeInTheDocument() expect(screen.getByText("Item 1")).toBeInTheDocument();
expect(screen.getByText('Item 2')).toBeInTheDocument() expect(screen.getByText("Item 2")).toBeInTheDocument();
expect(screen.getByText('ID: 1')).toBeInTheDocument() expect(screen.getByText("ID: 1")).toBeInTheDocument();
expect(screen.getByText('ID: 2')).toBeInTheDocument() expect(screen.getByText("ID: 2")).toBeInTheDocument();
}) });
it('calls listItems when List Items button is clicked', () => { it("calls listItems when List Items button is clicked", () => {
const listItems = vi.fn() const listItems = vi.fn();
render(<ListPane {...defaultProps} listItems={listItems} />) render(<ListPane {...defaultProps} listItems={listItems} />);
fireEvent.click(screen.getByText('List Items')) fireEvent.click(screen.getByText("List Items"));
expect(listItems).toHaveBeenCalled() expect(listItems).toHaveBeenCalled();
}) });
it('calls clearItems when Clear button is clicked', () => { it("calls clearItems when Clear button is clicked", () => {
const clearItems = vi.fn() const clearItems = vi.fn();
render(<ListPane {...defaultProps} clearItems={clearItems} />) render(<ListPane {...defaultProps} clearItems={clearItems} />);
fireEvent.click(screen.getByText('Clear')) fireEvent.click(screen.getByText("Clear"));
expect(clearItems).toHaveBeenCalled() expect(clearItems).toHaveBeenCalled();
}) });
it('calls setSelectedItem when an item is clicked', () => { it("calls setSelectedItem when an item is clicked", () => {
const setSelectedItem = vi.fn() const setSelectedItem = vi.fn();
render(<ListPane {...defaultProps} setSelectedItem={setSelectedItem} />) render(<ListPane {...defaultProps} setSelectedItem={setSelectedItem} />);
fireEvent.click(screen.getByText('Item 1')) fireEvent.click(screen.getByText("Item 1"));
expect(setSelectedItem).toHaveBeenCalledWith(mockItems[0]) expect(setSelectedItem).toHaveBeenCalledWith(mockItems[0]);
}) });
it('disables Clear button when items array is empty', () => { it("disables Clear button when items array is empty", () => {
render(<ListPane {...defaultProps} items={[]} />) render(<ListPane {...defaultProps} items={[]} />);
expect(screen.getByText('Clear')).toBeDisabled() expect(screen.getByText("Clear")).toBeDisabled();
}) });
it('disables List Items button when isButtonDisabled is true', () => { it("disables List Items button when isButtonDisabled is true", () => {
render(<ListPane {...defaultProps} isButtonDisabled={true} />) render(<ListPane {...defaultProps} isButtonDisabled={true} />);
expect(screen.getByText('List Items')).toBeDisabled() expect(screen.getByText("List Items")).toBeDisabled();
}) });
it('enables List Items button when isButtonDisabled is false', () => { it("enables List Items button when isButtonDisabled is false", () => {
render(<ListPane {...defaultProps} isButtonDisabled={false} />) render(<ListPane {...defaultProps} isButtonDisabled={false} />);
expect(screen.getByText('List Items')).not.toBeDisabled() expect(screen.getByText("List Items")).not.toBeDisabled();
}) });
}) });

View File

@@ -1,47 +1,51 @@
import { describe, it, expect, vi } from 'vitest' import { describe, it, expect, vi } from "vitest";
import { render, screen, fireEvent } from '@testing-library/react' import { render, screen, fireEvent } from "@testing-library/react";
import PingTab from '../../components/PingTab' import PingTab from "../../components/PingTab";
import { Tabs } from '@/components/ui/tabs' import { Tabs } from "@/components/ui/tabs";
describe('PingTab', () => { describe("PingTab", () => {
const renderWithTabs = (component: React.ReactElement) => { const renderWithTabs = (component: React.ReactElement) => {
return render( return render(<Tabs defaultValue="ping">{component}</Tabs>);
<Tabs defaultValue="ping"> };
{component}
</Tabs>
)
}
it('renders the MEGA PING button', () => { it("renders the MEGA PING button", () => {
renderWithTabs(<PingTab onPingClick={() => {}} />) renderWithTabs(<PingTab onPingClick={() => {}} />);
const button = screen.getByRole('button', { name: /mega ping/i }) const button = screen.getByRole("button", { name: /mega ping/i });
expect(button).toBeInTheDocument() expect(button).toBeInTheDocument();
expect(button).toHaveClass('bg-gradient-to-r', 'from-purple-500', 'to-pink-500') expect(button).toHaveClass(
}) "bg-gradient-to-r",
"from-purple-500",
"to-pink-500",
);
});
it('includes rocket and explosion emojis', () => { it("includes rocket and explosion emojis", () => {
renderWithTabs(<PingTab onPingClick={() => {}} />) renderWithTabs(<PingTab onPingClick={() => {}} />);
expect(screen.getByText('🚀')).toBeInTheDocument() expect(screen.getByText("🚀")).toBeInTheDocument();
expect(screen.getByText('💥')).toBeInTheDocument() expect(screen.getByText("💥")).toBeInTheDocument();
}) });
it('calls onPingClick when button is clicked', () => { it("calls onPingClick when button is clicked", () => {
const onPingClick = vi.fn() const onPingClick = vi.fn();
renderWithTabs(<PingTab onPingClick={onPingClick} />) renderWithTabs(<PingTab onPingClick={onPingClick} />);
fireEvent.click(screen.getByRole('button', { name: /mega ping/i })) fireEvent.click(screen.getByRole("button", { name: /mega ping/i }));
expect(onPingClick).toHaveBeenCalledTimes(1) expect(onPingClick).toHaveBeenCalledTimes(1);
}) });
it('has animation classes for visual feedback', () => { it("has animation classes for visual feedback", () => {
renderWithTabs(<PingTab onPingClick={() => {}} />) renderWithTabs(<PingTab onPingClick={() => {}} />);
const button = screen.getByRole('button', { name: /mega ping/i }) const button = screen.getByRole("button", { name: /mega ping/i });
expect(button).toHaveClass('animate-pulse', 'hover:scale-110', 'transition') expect(button).toHaveClass(
}) "animate-pulse",
"hover:scale-110",
"transition",
);
});
it('has focus styles for accessibility', () => { it("has focus styles for accessibility", () => {
renderWithTabs(<PingTab onPingClick={() => {}} />) renderWithTabs(<PingTab onPingClick={() => {}} />);
const button = screen.getByRole('button', { name: /mega ping/i }) const button = screen.getByRole("button", { name: /mega ping/i });
expect(button).toHaveClass('focus:outline-none', 'focus:ring-4') expect(button).toHaveClass("focus:outline-none", "focus:ring-4");
}) });
}) });

View File

@@ -1,24 +1,24 @@
import { describe, it, expect, vi } from 'vitest' import { describe, it, expect, vi } from "vitest";
import { render, screen, fireEvent } from '@testing-library/react' import { render, screen, fireEvent } from "@testing-library/react";
import PromptsTab from '../../components/PromptsTab' import PromptsTab from "../../components/PromptsTab";
import type { Prompt } from '../../components/PromptsTab' import type { Prompt } from "../../components/PromptsTab";
import { Tabs } from '@/components/ui/tabs' import { Tabs } from "@/components/ui/tabs";
describe('PromptsTab', () => { describe("PromptsTab", () => {
const mockPrompts: Prompt[] = [ const mockPrompts: Prompt[] = [
{ {
name: 'test-prompt-1', name: "test-prompt-1",
description: 'Test prompt 1 description', description: "Test prompt 1 description",
arguments: [ arguments: [
{ name: 'arg1', description: 'Argument 1', required: true }, { name: "arg1", description: "Argument 1", required: true },
{ name: 'arg2', description: 'Argument 2' } { name: "arg2", description: "Argument 2" },
] ],
}, },
{ {
name: 'test-prompt-2', name: "test-prompt-2",
description: 'Test prompt 2 description' description: "Test prompt 2 description",
} },
] ];
const defaultProps = { const defaultProps = {
prompts: mockPrompts, prompts: mockPrompts,
@@ -27,70 +27,72 @@ describe('PromptsTab', () => {
getPrompt: vi.fn(), getPrompt: vi.fn(),
selectedPrompt: null, selectedPrompt: null,
setSelectedPrompt: vi.fn(), setSelectedPrompt: vi.fn(),
promptContent: '', promptContent: "",
nextCursor: null, nextCursor: null,
error: null error: null,
} };
const renderWithTabs = (component: React.ReactElement) => { const renderWithTabs = (component: React.ReactElement) => {
return render( return render(<Tabs defaultValue="prompts">{component}</Tabs>);
<Tabs defaultValue="prompts"> };
{component}
</Tabs>
)
}
it('renders list of prompts', () => { it("renders list of prompts", () => {
renderWithTabs(<PromptsTab {...defaultProps} />) renderWithTabs(<PromptsTab {...defaultProps} />);
expect(screen.getByText('test-prompt-1')).toBeInTheDocument() expect(screen.getByText("test-prompt-1")).toBeInTheDocument();
expect(screen.getByText('test-prompt-2')).toBeInTheDocument() expect(screen.getByText("test-prompt-2")).toBeInTheDocument();
}) });
it('shows prompt details when selected', () => { it("shows prompt details when selected", () => {
const props = {
...defaultProps,
selectedPrompt: mockPrompts[0]
}
renderWithTabs(<PromptsTab {...props} />)
expect(screen.getByText('Test prompt 1 description', { selector: 'p.text-sm.text-gray-600' })).toBeInTheDocument()
expect(screen.getByText('arg1')).toBeInTheDocument()
expect(screen.getByText('arg2')).toBeInTheDocument()
})
it('handles argument input', () => {
const getPrompt = vi.fn()
const props = { const props = {
...defaultProps, ...defaultProps,
selectedPrompt: mockPrompts[0], selectedPrompt: mockPrompts[0],
getPrompt };
} renderWithTabs(<PromptsTab {...props} />);
renderWithTabs(<PromptsTab {...props} />) expect(
screen.getByText("Test prompt 1 description", {
selector: "p.text-sm.text-gray-600",
}),
).toBeInTheDocument();
expect(screen.getByText("arg1")).toBeInTheDocument();
expect(screen.getByText("arg2")).toBeInTheDocument();
});
const arg1Input = screen.getByPlaceholderText('Enter arg1') it("handles argument input", () => {
fireEvent.change(arg1Input, { target: { value: 'test value' } }) const getPrompt = vi.fn();
const getPromptButton = screen.getByText('Get Prompt')
fireEvent.click(getPromptButton)
expect(getPrompt).toHaveBeenCalledWith('test-prompt-1', { arg1: 'test value' })
})
it('shows error message when error prop is provided', () => {
const props = {
...defaultProps,
error: 'Test error message'
}
renderWithTabs(<PromptsTab {...props} />)
expect(screen.getByText('Test error message')).toBeInTheDocument()
})
it('shows prompt content when provided', () => {
const props = { const props = {
...defaultProps, ...defaultProps,
selectedPrompt: mockPrompts[0], selectedPrompt: mockPrompts[0],
promptContent: 'Test prompt content' getPrompt,
} };
renderWithTabs(<PromptsTab {...props} />) renderWithTabs(<PromptsTab {...props} />);
expect(screen.getByText('Test prompt content')).toBeInTheDocument()
}) const arg1Input = screen.getByPlaceholderText("Enter arg1");
}) fireEvent.change(arg1Input, { target: { value: "test value" } });
const getPromptButton = screen.getByText("Get Prompt");
fireEvent.click(getPromptButton);
expect(getPrompt).toHaveBeenCalledWith("test-prompt-1", {
arg1: "test value",
});
});
it("shows error message when error prop is provided", () => {
const props = {
...defaultProps,
error: "Test error message",
};
renderWithTabs(<PromptsTab {...props} />);
expect(screen.getByText("Test error message")).toBeInTheDocument();
});
it("shows prompt content when provided", () => {
const props = {
...defaultProps,
selectedPrompt: mockPrompts[0],
promptContent: "Test prompt content",
};
renderWithTabs(<PromptsTab {...props} />);
expect(screen.getByText("Test prompt content")).toBeInTheDocument();
});
});

View File

@@ -1,27 +1,30 @@
import { describe, it, expect, vi } from 'vitest' import { describe, it, expect, vi } from "vitest";
import { render, screen, fireEvent } from '@testing-library/react' import { render, screen, fireEvent } from "@testing-library/react";
import ResourcesTab from '../../components/ResourcesTab' import ResourcesTab from "../../components/ResourcesTab";
import { Tabs } from '@/components/ui/tabs' import { Tabs } from "@/components/ui/tabs";
import type { Resource, ResourceTemplate } from '@modelcontextprotocol/sdk/types.js' import type {
Resource,
ResourceTemplate,
} from "@modelcontextprotocol/sdk/types.js";
describe('ResourcesTab', () => { describe("ResourcesTab", () => {
const mockResources: Resource[] = [ const mockResources: Resource[] = [
{ uri: 'file:///test1.txt', name: 'Test 1' }, { uri: "file:///test1.txt", name: "Test 1" },
{ uri: 'file:///test2.txt', name: 'Test 2' } { uri: "file:///test2.txt", name: "Test 2" },
] ];
const mockTemplates: ResourceTemplate[] = [ const mockTemplates: ResourceTemplate[] = [
{ {
name: 'Template 1', name: "Template 1",
description: 'Test template 1', description: "Test template 1",
uriTemplate: 'file:///test/{param1}/{param2}.txt' uriTemplate: "file:///test/{param1}/{param2}.txt",
}, },
{ {
name: 'Template 2', name: "Template 2",
description: 'Test template 2', description: "Test template 2",
uriTemplate: 'file:///other/{name}.txt' uriTemplate: "file:///other/{name}.txt",
} },
] ];
const defaultProps = { const defaultProps = {
resources: mockResources, resources: mockResources,
@@ -33,100 +36,100 @@ describe('ResourcesTab', () => {
readResource: vi.fn(), readResource: vi.fn(),
selectedResource: null, selectedResource: null,
setSelectedResource: vi.fn(), setSelectedResource: vi.fn(),
resourceContent: '', resourceContent: "",
nextCursor: null, nextCursor: null,
nextTemplateCursor: null, nextTemplateCursor: null,
error: null error: null,
} };
const renderWithTabs = (component: React.ReactElement) => { const renderWithTabs = (component: React.ReactElement) => {
return render( return render(<Tabs defaultValue="resources">{component}</Tabs>);
<Tabs defaultValue="resources"> };
{component}
</Tabs>
)
}
it('renders resources list', () => { it("renders resources list", () => {
renderWithTabs(<ResourcesTab {...defaultProps} />) renderWithTabs(<ResourcesTab {...defaultProps} />);
expect(screen.getByText('Test 1')).toBeInTheDocument() expect(screen.getByText("Test 1")).toBeInTheDocument();
expect(screen.getByText('Test 2')).toBeInTheDocument() expect(screen.getByText("Test 2")).toBeInTheDocument();
}) });
it('renders templates list', () => { it("renders templates list", () => {
renderWithTabs(<ResourcesTab {...defaultProps} />) renderWithTabs(<ResourcesTab {...defaultProps} />);
expect(screen.getByText('Template 1')).toBeInTheDocument() expect(screen.getByText("Template 1")).toBeInTheDocument();
expect(screen.getByText('Template 2')).toBeInTheDocument() expect(screen.getByText("Template 2")).toBeInTheDocument();
}) });
it('shows resource content when resource is selected', () => { it("shows resource content when resource is selected", () => {
const props = { const props = {
...defaultProps, ...defaultProps,
selectedResource: mockResources[0], selectedResource: mockResources[0],
resourceContent: 'Test content' resourceContent: "Test content",
} };
renderWithTabs(<ResourcesTab {...props} />) renderWithTabs(<ResourcesTab {...props} />);
expect(screen.getByText('Test content')).toBeInTheDocument() expect(screen.getByText("Test content")).toBeInTheDocument();
}) });
it('shows template form when template is selected', () => { it("shows template form when template is selected", () => {
renderWithTabs(<ResourcesTab {...defaultProps} />) renderWithTabs(<ResourcesTab {...defaultProps} />);
fireEvent.click(screen.getByText('Template 1')) fireEvent.click(screen.getByText("Template 1"));
expect(screen.getByText('Test template 1')).toBeInTheDocument() expect(screen.getByText("Test template 1")).toBeInTheDocument();
expect(screen.getByLabelText('param1')).toBeInTheDocument() expect(screen.getByLabelText("param1")).toBeInTheDocument();
expect(screen.getByLabelText('param2')).toBeInTheDocument() expect(screen.getByLabelText("param2")).toBeInTheDocument();
}) });
it('fills template and reads resource', () => { it("fills template and reads resource", () => {
const readResource = vi.fn() const readResource = vi.fn();
const setSelectedResource = vi.fn() const setSelectedResource = vi.fn();
renderWithTabs( renderWithTabs(
<ResourcesTab <ResourcesTab
{...defaultProps} {...defaultProps}
readResource={readResource} readResource={readResource}
setSelectedResource={setSelectedResource} setSelectedResource={setSelectedResource}
/> />,
) );
// Select template // Select template
fireEvent.click(screen.getByText('Template 1')) fireEvent.click(screen.getByText("Template 1"));
// Fill in template parameters // Fill in template parameters
fireEvent.change(screen.getByLabelText('param1'), { target: { value: 'value1' } }) fireEvent.change(screen.getByLabelText("param1"), {
fireEvent.change(screen.getByLabelText('param2'), { target: { value: 'value2' } }) target: { value: "value1" },
});
fireEvent.change(screen.getByLabelText("param2"), {
target: { value: "value2" },
});
// Submit form // Submit form
fireEvent.click(screen.getByText('Read Resource')) fireEvent.click(screen.getByText("Read Resource"));
expect(readResource).toHaveBeenCalledWith('file:///test/value1/value2.txt') expect(readResource).toHaveBeenCalledWith("file:///test/value1/value2.txt");
expect(setSelectedResource).toHaveBeenCalledWith( expect(setSelectedResource).toHaveBeenCalledWith(
expect.objectContaining({ expect.objectContaining({
uri: 'file:///test/value1/value2.txt', uri: "file:///test/value1/value2.txt",
name: 'file:///test/value1/value2.txt' name: "file:///test/value1/value2.txt",
}) }),
) );
}) });
it('shows error message when error prop is provided', () => { it("shows error message when error prop is provided", () => {
const props = { const props = {
...defaultProps, ...defaultProps,
error: 'Test error message' error: "Test error message",
} };
renderWithTabs(<ResourcesTab {...props} />) renderWithTabs(<ResourcesTab {...props} />);
expect(screen.getByText('Test error message')).toBeInTheDocument() expect(screen.getByText("Test error message")).toBeInTheDocument();
}) });
it('refreshes resource content when refresh button is clicked', () => { it("refreshes resource content when refresh button is clicked", () => {
const readResource = vi.fn() const readResource = vi.fn();
const props = { const props = {
...defaultProps, ...defaultProps,
selectedResource: mockResources[0], selectedResource: mockResources[0],
readResource readResource,
} };
renderWithTabs(<ResourcesTab {...props} />) renderWithTabs(<ResourcesTab {...props} />);
fireEvent.click(screen.getByText('Refresh')) fireEvent.click(screen.getByText("Refresh"));
expect(readResource).toHaveBeenCalledWith(mockResources[0].uri) expect(readResource).toHaveBeenCalledWith(mockResources[0].uri);
}) });
}) });

View File

@@ -1,80 +1,80 @@
import { describe, it, expect, vi } from 'vitest' import { describe, it, expect, vi } from "vitest";
import { render, screen, fireEvent } from '@testing-library/react' import { render, screen, fireEvent } from "@testing-library/react";
import RootsTab from '../../components/RootsTab' import RootsTab from "../../components/RootsTab";
import { Tabs } from '@/components/ui/tabs' import { Tabs } from "@/components/ui/tabs";
import type { Root } from '@modelcontextprotocol/sdk/types.js' import type { Root } from "@modelcontextprotocol/sdk/types.js";
describe('RootsTab', () => { describe("RootsTab", () => {
const mockRoots: Root[] = [ const mockRoots: Root[] = [
{ uri: 'file:///test/path1', name: 'test1' }, { uri: "file:///test/path1", name: "test1" },
{ uri: 'file:///test/path2', name: 'test2' } { uri: "file:///test/path2", name: "test2" },
] ];
const defaultProps = { const defaultProps = {
roots: mockRoots, roots: mockRoots,
setRoots: vi.fn(), setRoots: vi.fn(),
onRootsChange: vi.fn() onRootsChange: vi.fn(),
} };
const renderWithTabs = (component: React.ReactElement) => { const renderWithTabs = (component: React.ReactElement) => {
return render( return render(<Tabs defaultValue="roots">{component}</Tabs>);
<Tabs defaultValue="roots"> };
{component}
</Tabs>
)
}
it('renders list of roots', () => { it("renders list of roots", () => {
renderWithTabs(<RootsTab {...defaultProps} />) renderWithTabs(<RootsTab {...defaultProps} />);
expect(screen.getByDisplayValue('file:///test/path1')).toBeInTheDocument() expect(screen.getByDisplayValue("file:///test/path1")).toBeInTheDocument();
expect(screen.getByDisplayValue('file:///test/path2')).toBeInTheDocument() expect(screen.getByDisplayValue("file:///test/path2")).toBeInTheDocument();
}) });
it('adds a new root when Add Root button is clicked', () => { it("adds a new root when Add Root button is clicked", () => {
const setRoots = vi.fn() const setRoots = vi.fn();
renderWithTabs(<RootsTab {...defaultProps} setRoots={setRoots} />) renderWithTabs(<RootsTab {...defaultProps} setRoots={setRoots} />);
fireEvent.click(screen.getByText('Add Root')) fireEvent.click(screen.getByText("Add Root"));
expect(setRoots).toHaveBeenCalled() expect(setRoots).toHaveBeenCalled();
const updateFn = setRoots.mock.calls[0][0] const updateFn = setRoots.mock.calls[0][0];
const result = updateFn(mockRoots) const result = updateFn(mockRoots);
expect(result).toEqual([...mockRoots, { uri: 'file://', name: '' }]) expect(result).toEqual([...mockRoots, { uri: "file://", name: "" }]);
}) });
it('removes a root when remove button is clicked', () => { it("removes a root when remove button is clicked", () => {
const setRoots = vi.fn() const setRoots = vi.fn();
renderWithTabs(<RootsTab {...defaultProps} setRoots={setRoots} />) renderWithTabs(<RootsTab {...defaultProps} setRoots={setRoots} />);
const removeButtons = screen.getAllByRole('button', { name: /remove root/i }) const removeButtons = screen.getAllByRole("button", {
fireEvent.click(removeButtons[0]) name: /remove root/i,
});
fireEvent.click(removeButtons[0]);
expect(setRoots).toHaveBeenCalled() expect(setRoots).toHaveBeenCalled();
const updateFn = setRoots.mock.calls[0][0] const updateFn = setRoots.mock.calls[0][0];
const result = updateFn(mockRoots) const result = updateFn(mockRoots);
expect(result).toEqual([mockRoots[1]]) expect(result).toEqual([mockRoots[1]]);
}) });
it('updates root URI when input changes', () => { it("updates root URI when input changes", () => {
const setRoots = vi.fn() const setRoots = vi.fn();
renderWithTabs(<RootsTab {...defaultProps} setRoots={setRoots} />) renderWithTabs(<RootsTab {...defaultProps} setRoots={setRoots} />);
const firstInput = screen.getByDisplayValue('file:///test/path1') const firstInput = screen.getByDisplayValue("file:///test/path1");
fireEvent.change(firstInput, { target: { value: 'file:///new/path' } }) fireEvent.change(firstInput, { target: { value: "file:///new/path" } });
expect(setRoots).toHaveBeenCalled() expect(setRoots).toHaveBeenCalled();
const updateFn = setRoots.mock.calls[0][0] const updateFn = setRoots.mock.calls[0][0];
const result = updateFn(mockRoots) const result = updateFn(mockRoots);
expect(result[0].uri).toBe('file:///new/path') expect(result[0].uri).toBe("file:///new/path");
expect(result[1]).toEqual(mockRoots[1]) expect(result[1]).toEqual(mockRoots[1]);
}) });
it('calls onRootsChange when Save Changes is clicked', () => { it("calls onRootsChange when Save Changes is clicked", () => {
const onRootsChange = vi.fn() const onRootsChange = vi.fn();
renderWithTabs(<RootsTab {...defaultProps} onRootsChange={onRootsChange} />) renderWithTabs(
<RootsTab {...defaultProps} onRootsChange={onRootsChange} />,
);
fireEvent.click(screen.getByText('Save Changes')) fireEvent.click(screen.getByText("Save Changes"));
expect(onRootsChange).toHaveBeenCalled() expect(onRootsChange).toHaveBeenCalled();
}) });
}) });

View File

@@ -1,85 +1,91 @@
import { describe, it, expect, vi } from 'vitest' import { describe, it, expect, vi } from "vitest";
import { render, screen, fireEvent } from '@testing-library/react' import { render, screen, fireEvent } from "@testing-library/react";
import SamplingTab from '../../components/SamplingTab' import SamplingTab from "../../components/SamplingTab";
import { Tabs } from '@/components/ui/tabs' import { Tabs } from "@/components/ui/tabs";
import type { CreateMessageRequest } from '@modelcontextprotocol/sdk/types.js' import type { CreateMessageRequest } from "@modelcontextprotocol/sdk/types.js";
describe('SamplingTab', () => { describe("SamplingTab", () => {
const mockRequest: CreateMessageRequest = { const mockRequest: CreateMessageRequest = {
model: 'test-model', model: "test-model",
role: 'user', role: "user",
content: { content: {
type: 'text', type: "text",
text: 'Test message' text: "Test message",
} },
} };
const mockPendingRequests = [ const mockPendingRequests = [
{ id: 1, request: mockRequest }, { id: 1, request: mockRequest },
{ id: 2, request: { ...mockRequest, content: { type: 'text', text: 'Another test' } } } {
] id: 2,
request: {
...mockRequest,
content: { type: "text", text: "Another test" },
},
},
];
const defaultProps = { const defaultProps = {
pendingRequests: mockPendingRequests, pendingRequests: mockPendingRequests,
onApprove: vi.fn(), onApprove: vi.fn(),
onReject: vi.fn() onReject: vi.fn(),
} };
const renderWithTabs = (component: React.ReactElement) => { const renderWithTabs = (component: React.ReactElement) => {
return render( return render(<Tabs defaultValue="sampling">{component}</Tabs>);
<Tabs defaultValue="sampling"> };
{component}
</Tabs>
)
}
it('renders empty state when no requests', () => { it("renders empty state when no requests", () => {
renderWithTabs(<SamplingTab {...defaultProps} pendingRequests={[]} />) renderWithTabs(<SamplingTab {...defaultProps} pendingRequests={[]} />);
expect(screen.getByText('No pending requests')).toBeInTheDocument() expect(screen.getByText("No pending requests")).toBeInTheDocument();
}) });
it('renders list of pending requests', () => { it("renders list of pending requests", () => {
renderWithTabs(<SamplingTab {...defaultProps} />) renderWithTabs(<SamplingTab {...defaultProps} />);
expect(screen.getByText(/Test message/)).toBeInTheDocument() expect(screen.getByText(/Test message/)).toBeInTheDocument();
expect(screen.getByText(/Another test/)).toBeInTheDocument() expect(screen.getByText(/Another test/)).toBeInTheDocument();
}) });
it('shows request details in JSON format', () => { it("shows request details in JSON format", () => {
renderWithTabs(<SamplingTab {...defaultProps} />) renderWithTabs(<SamplingTab {...defaultProps} />);
const requestJson = screen.getAllByText((content) => content.includes('"model": "test-model"')) const requestJson = screen.getAllByText((content) =>
expect(requestJson).toHaveLength(2) content.includes('"model": "test-model"'),
}) );
expect(requestJson).toHaveLength(2);
});
it('calls onApprove with stub response when Approve is clicked', () => { it("calls onApprove with stub response when Approve is clicked", () => {
const onApprove = vi.fn() const onApprove = vi.fn();
renderWithTabs(<SamplingTab {...defaultProps} onApprove={onApprove} />) renderWithTabs(<SamplingTab {...defaultProps} onApprove={onApprove} />);
const approveButtons = screen.getAllByText('Approve') const approveButtons = screen.getAllByText("Approve");
fireEvent.click(approveButtons[0]) fireEvent.click(approveButtons[0]);
expect(onApprove).toHaveBeenCalledWith(1, { expect(onApprove).toHaveBeenCalledWith(1, {
model: 'stub-model', model: "stub-model",
stopReason: 'endTurn', stopReason: "endTurn",
role: 'assistant', role: "assistant",
content: { content: {
type: 'text', type: "text",
text: 'This is a stub response.' text: "This is a stub response.",
} },
}) });
}) });
it('calls onReject when Reject is clicked', () => { it("calls onReject when Reject is clicked", () => {
const onReject = vi.fn() const onReject = vi.fn();
renderWithTabs(<SamplingTab {...defaultProps} onReject={onReject} />) renderWithTabs(<SamplingTab {...defaultProps} onReject={onReject} />);
const rejectButtons = screen.getAllByText('Reject') const rejectButtons = screen.getAllByText("Reject");
fireEvent.click(rejectButtons[0]) fireEvent.click(rejectButtons[0]);
expect(onReject).toHaveBeenCalledWith(1) expect(onReject).toHaveBeenCalledWith(1);
}) });
it('shows informational alert about sampling requests', () => { it("shows informational alert about sampling requests", () => {
renderWithTabs(<SamplingTab {...defaultProps} />) renderWithTabs(<SamplingTab {...defaultProps} />);
expect(screen.getByText(/When the server requests LLM sampling/)).toBeInTheDocument() expect(
}) screen.getByText(/When the server requests LLM sampling/),
}) ).toBeInTheDocument();
});
});

View File

@@ -1,15 +1,15 @@
import '@testing-library/jest-dom' import "@testing-library/jest-dom";
import { expect, afterEach, vi } from 'vitest' import { expect, afterEach, vi } from "vitest";
import { cleanup } from '@testing-library/react' import { cleanup } from "@testing-library/react";
import * as matchers from '@testing-library/jest-dom/matchers' import * as matchers from "@testing-library/jest-dom/matchers";
// @ts-ignore // @ts-ignore
expect.extend(matchers) expect.extend(matchers);
// Mock window.matchMedia // Mock window.matchMedia
Object.defineProperty(window, 'matchMedia', { Object.defineProperty(window, "matchMedia", {
writable: true, writable: true,
value: vi.fn().mockImplementation(query => ({ value: vi.fn().mockImplementation((query) => ({
matches: false, matches: false,
media: query, media: query,
onchange: null, onchange: null,
@@ -19,16 +19,16 @@ Object.defineProperty(window, 'matchMedia', {
removeEventListener: vi.fn(), removeEventListener: vi.fn(),
dispatchEvent: vi.fn(), dispatchEvent: vi.fn(),
})), })),
}) });
// Mock window.location.hash // Mock window.location.hash
Object.defineProperty(window, 'location', { Object.defineProperty(window, "location", {
writable: true, writable: true,
value: { hash: '' } value: { hash: "" },
}) });
afterEach(() => { afterEach(() => {
cleanup() cleanup();
vi.clearAllMocks() vi.clearAllMocks();
window.location.hash = '' window.location.hash = "";
}) });

View File

@@ -1,5 +1,12 @@
import { useState } from "react"; import { useState } from "react";
import { Play, ChevronDown, ChevronRight, CircleHelp, Bug, Github } from "lucide-react"; import {
Play,
ChevronDown,
ChevronRight,
CircleHelp,
Bug,
Github,
} from "lucide-react";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { import {
@@ -243,18 +250,33 @@ const Sidebar = ({
</Select> </Select>
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<a href="https://modelcontextprotocol.io/docs/tools/inspector" target="_blank" rel="noopener noreferrer"> <a
href="https://modelcontextprotocol.io/docs/tools/inspector"
target="_blank"
rel="noopener noreferrer"
>
<Button variant="ghost" title="Inspector Documentation"> <Button variant="ghost" title="Inspector Documentation">
<CircleHelp className="w-4 h-4 text-gray-800" /> <CircleHelp className="w-4 h-4 text-gray-800" />
</Button> </Button>
</a> </a>
<a href="https://modelcontextprotocol.io/docs/tools/debugging" target="_blank" rel="noopener noreferrer"> <a
href="https://modelcontextprotocol.io/docs/tools/debugging"
target="_blank"
rel="noopener noreferrer"
>
<Button variant="ghost" title="Debugging Guide"> <Button variant="ghost" title="Debugging Guide">
<Bug className="w-4 h-4 text-gray-800" /> <Bug className="w-4 h-4 text-gray-800" />
</Button> </Button>
</a> </a>
<a href="https://github.com/modelcontextprotocol/inspector" target="_blank" rel="noopener noreferrer"> <a
<Button variant="ghost" title="Report bugs or contribute on GitHub"> href="https://github.com/modelcontextprotocol/inspector"
target="_blank"
rel="noopener noreferrer"
>
<Button
variant="ghost"
title="Report bugs or contribute on GitHub"
>
<Github className="w-4 h-4 text-gray-800" /> <Github className="w-4 h-4 text-gray-800" />
</Button> </Button>
</a> </a>

View File

@@ -174,8 +174,7 @@ const ToolsTab = ({
} }
className="mt-1" className="mt-1"
/> />
) : ) : /* @ts-expect-error value type is currently unknown */
/* @ts-expect-error value type is currently unknown */
value.type === "object" ? ( value.type === "object" ? (
<Textarea <Textarea
id={key} id={key}

View File

@@ -44,10 +44,15 @@ export function useConnection({
onPendingRequest, onPendingRequest,
getRoots, getRoots,
}: UseConnectionOptions) { }: UseConnectionOptions) {
const [connectionStatus, setConnectionStatus] = useState<"disconnected" | "connected" | "error">("disconnected"); const [connectionStatus, setConnectionStatus] = useState<
const [serverCapabilities, setServerCapabilities] = useState<ServerCapabilities | null>(null); "disconnected" | "connected" | "error"
>("disconnected");
const [serverCapabilities, setServerCapabilities] =
useState<ServerCapabilities | null>(null);
const [mcpClient, setMcpClient] = useState<Client | null>(null); const [mcpClient, setMcpClient] = useState<Client | null>(null);
const [requestHistory, setRequestHistory] = useState<{ request: string; response?: string }[]>([]); const [requestHistory, setRequestHistory] = useState<
{ request: string; response?: string }[]
>([]);
const pushHistory = (request: object, response?: object) => { const pushHistory = (request: object, response?: object) => {
setRequestHistory((prev) => [ setRequestHistory((prev) => [
@@ -61,7 +66,7 @@ export function useConnection({
const makeRequest = async <T extends z.ZodType>( const makeRequest = async <T extends z.ZodType>(
request: ClientRequest, request: ClientRequest,
schema: T schema: T,
) => { ) => {
if (!mcpClient) { if (!mcpClient) {
throw new Error("MCP client not connected"); throw new Error("MCP client not connected");
@@ -80,14 +85,14 @@ export function useConnection({
}); });
pushHistory(request, response); pushHistory(request, response);
} catch (error) { } catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error); const errorMessage =
error instanceof Error ? error.message : String(error);
pushHistory(request, { error: errorMessage }); pushHistory(request, { error: errorMessage });
throw error; throw error;
} finally { } finally {
clearTimeout(timeoutId); clearTimeout(timeoutId);
} }
return response; return response;
} catch (e: unknown) { } catch (e: unknown) {
const errorString = (e as Error).message ?? String(e); const errorString = (e as Error).message ?? String(e);
@@ -142,11 +147,17 @@ export function useConnection({
const clientTransport = new SSEClientTransport(backendUrl); const clientTransport = new SSEClientTransport(backendUrl);
if (onNotification) { if (onNotification) {
client.setNotificationHandler(ProgressNotificationSchema, onNotification); client.setNotificationHandler(
ProgressNotificationSchema,
onNotification,
);
} }
if (onStdErrNotification) { if (onStdErrNotification) {
client.setNotificationHandler(StdErrNotificationSchema, onStdErrNotification); client.setNotificationHandler(
StdErrNotificationSchema,
onStdErrNotification,
);
} }
await client.connect(clientTransport); await client.connect(clientTransport);
@@ -183,6 +194,6 @@ export function useConnection({
requestHistory, requestHistory,
makeRequest, makeRequest,
sendNotification, sendNotification,
connect connect,
}; };
} }

View File

@@ -6,19 +6,28 @@ export function useDraggablePane(initialHeight: number) {
const dragStartY = useRef<number>(0); const dragStartY = useRef<number>(0);
const dragStartHeight = useRef<number>(0); const dragStartHeight = useRef<number>(0);
const handleDragStart = useCallback((e: React.MouseEvent) => { const handleDragStart = useCallback(
setIsDragging(true); (e: React.MouseEvent) => {
dragStartY.current = e.clientY; setIsDragging(true);
dragStartHeight.current = height; dragStartY.current = e.clientY;
document.body.style.userSelect = "none"; dragStartHeight.current = height;
}, [height]); document.body.style.userSelect = "none";
},
[height],
);
const handleDragMove = useCallback((e: MouseEvent) => { const handleDragMove = useCallback(
if (!isDragging) return; (e: MouseEvent) => {
const deltaY = dragStartY.current - e.clientY; if (!isDragging) return;
const newHeight = Math.max(100, Math.min(800, dragStartHeight.current + deltaY)); const deltaY = dragStartY.current - e.clientY;
setHeight(newHeight); const newHeight = Math.max(
}, [isDragging]); 100,
Math.min(800, dragStartHeight.current + deltaY),
);
setHeight(newHeight);
},
[isDragging],
);
const handleDragEnd = useCallback(() => { const handleDragEnd = useCallback(() => {
setIsDragging(false); setIsDragging(false);
@@ -39,6 +48,6 @@ export function useDraggablePane(initialHeight: number) {
return { return {
height, height,
isDragging, isDragging,
handleDragStart handleDragStart,
}; };
} }

View File

@@ -1,7 +1,7 @@
import { StrictMode } from "react"; import { StrictMode } from "react";
import { createRoot } from "react-dom/client"; import { createRoot } from "react-dom/client";
import { ToastContainer } from 'react-toastify'; import { ToastContainer } from "react-toastify";
import 'react-toastify/dist/ReactToastify.css'; import "react-toastify/dist/ReactToastify.css";
import App from "./App.tsx"; import App from "./App.tsx";
import "./index.css"; import "./index.css";