style: run prettier on src directory
This commit is contained in:
@@ -16,7 +16,7 @@ import {
|
||||
ResourceTemplate,
|
||||
Root,
|
||||
ServerNotification,
|
||||
Tool
|
||||
Tool,
|
||||
} from "@modelcontextprotocol/sdk/types.js";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
@@ -124,10 +124,7 @@ const App = () => {
|
||||
const [nextToolCursor, setNextToolCursor] = useState<string | undefined>();
|
||||
const progressTokenRef = useRef(0);
|
||||
|
||||
const {
|
||||
height: historyPaneHeight,
|
||||
handleDragStart
|
||||
} = useDraggablePane(300);
|
||||
const { height: historyPaneHeight, handleDragStart } = useDraggablePane(300);
|
||||
|
||||
const {
|
||||
connectionStatus,
|
||||
@@ -136,7 +133,7 @@ const App = () => {
|
||||
requestHistory,
|
||||
makeRequest: makeConnectionRequest,
|
||||
sendNotification,
|
||||
connect: connectMcpServer
|
||||
connect: connectMcpServer,
|
||||
} = useConnection({
|
||||
transportType,
|
||||
command,
|
||||
@@ -145,18 +142,21 @@ const App = () => {
|
||||
env,
|
||||
proxyServerUrl: PROXY_SERVER_URL,
|
||||
onNotification: (notification) => {
|
||||
setNotifications(prev => [...prev, notification as ServerNotification]);
|
||||
setNotifications((prev) => [...prev, notification as ServerNotification]);
|
||||
},
|
||||
onStdErrNotification: (notification) => {
|
||||
setStdErrNotifications(prev => [...prev, notification as StdErrNotification]);
|
||||
},
|
||||
onPendingRequest: (request, resolve, reject) => {
|
||||
setPendingSampleRequests(prev => [
|
||||
setStdErrNotifications((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>(
|
||||
@@ -345,26 +345,40 @@ const App = () => {
|
||||
{mcpClient ? (
|
||||
<Tabs
|
||||
defaultValue={
|
||||
Object.keys(serverCapabilities ?? {}).includes(window.location.hash.slice(1)) ?
|
||||
window.location.hash.slice(1) :
|
||||
serverCapabilities?.resources ? "resources" :
|
||||
serverCapabilities?.prompts ? "prompts" :
|
||||
serverCapabilities?.tools ? "tools" :
|
||||
"ping"
|
||||
Object.keys(serverCapabilities ?? {}).includes(
|
||||
window.location.hash.slice(1),
|
||||
)
|
||||
? window.location.hash.slice(1)
|
||||
: serverCapabilities?.resources
|
||||
? "resources"
|
||||
: serverCapabilities?.prompts
|
||||
? "prompts"
|
||||
: serverCapabilities?.tools
|
||||
? "tools"
|
||||
: "ping"
|
||||
}
|
||||
className="w-full p-4"
|
||||
onValueChange={(value) => (window.location.hash = value)}
|
||||
>
|
||||
<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" />
|
||||
Resources
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="prompts" disabled={!serverCapabilities?.prompts}>
|
||||
<TabsTrigger
|
||||
value="prompts"
|
||||
disabled={!serverCapabilities?.prompts}
|
||||
>
|
||||
<MessageSquare className="w-4 h-4 mr-2" />
|
||||
Prompts
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="tools" disabled={!serverCapabilities?.tools}>
|
||||
<TabsTrigger
|
||||
value="tools"
|
||||
disabled={!serverCapabilities?.tools}
|
||||
>
|
||||
<Hammer className="w-4 h-4 mr-2" />
|
||||
Tools
|
||||
</TabsTrigger>
|
||||
@@ -388,7 +402,9 @@ const App = () => {
|
||||
</TabsList>
|
||||
|
||||
<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">
|
||||
<p className="text-lg text-gray-500">
|
||||
The connected server does not support any MCP capabilities
|
||||
|
||||
@@ -1,146 +1,149 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||
import { render, screen, fireEvent, act } from '@testing-library/react'
|
||||
import App from '../App'
|
||||
import { useConnection } from '../lib/hooks/useConnection'
|
||||
import { useDraggablePane } from '../lib/hooks/useDraggablePane'
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { render, screen, fireEvent, act } from "@testing-library/react";
|
||||
import App from "../App";
|
||||
import { useConnection } from "../lib/hooks/useConnection";
|
||||
import { useDraggablePane } from "../lib/hooks/useDraggablePane";
|
||||
|
||||
// Mock URL params
|
||||
const mockURLSearchParams = vi.fn()
|
||||
vi.stubGlobal('URLSearchParams', mockURLSearchParams)
|
||||
const mockURLSearchParams = vi.fn();
|
||||
vi.stubGlobal("URLSearchParams", mockURLSearchParams);
|
||||
|
||||
// Mock the hooks
|
||||
vi.mock('../lib/hooks/useConnection', () => ({
|
||||
useConnection: vi.fn()
|
||||
}))
|
||||
vi.mock("../lib/hooks/useConnection", () => ({
|
||||
useConnection: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../lib/hooks/useDraggablePane', () => ({
|
||||
useDraggablePane: vi.fn()
|
||||
}))
|
||||
vi.mock("../lib/hooks/useDraggablePane", () => ({
|
||||
useDraggablePane: vi.fn(),
|
||||
}));
|
||||
|
||||
// Mock fetch for config
|
||||
const mockFetch = vi.fn()
|
||||
global.fetch = mockFetch
|
||||
const mockFetch = vi.fn();
|
||||
global.fetch = mockFetch;
|
||||
|
||||
describe('App', () => {
|
||||
describe("App", () => {
|
||||
beforeEach(() => {
|
||||
// Reset all mocks
|
||||
vi.clearAllMocks()
|
||||
vi.clearAllMocks();
|
||||
|
||||
// Mock URL params
|
||||
mockURLSearchParams.mockReturnValue({
|
||||
get: () => '3000'
|
||||
})
|
||||
get: () => "3000",
|
||||
});
|
||||
|
||||
// Mock fetch response
|
||||
mockFetch.mockResolvedValue({
|
||||
json: () => Promise.resolve({
|
||||
defaultEnvironment: {},
|
||||
defaultCommand: 'test-command',
|
||||
defaultArgs: '--test'
|
||||
})
|
||||
})
|
||||
json: () =>
|
||||
Promise.resolve({
|
||||
defaultEnvironment: {},
|
||||
defaultCommand: "test-command",
|
||||
defaultArgs: "--test",
|
||||
}),
|
||||
});
|
||||
|
||||
// Mock useConnection hook
|
||||
const mockUseConnection = useConnection as jest.Mock
|
||||
const mockUseConnection = useConnection as jest.Mock;
|
||||
mockUseConnection.mockReturnValue({
|
||||
connectionStatus: 'disconnected',
|
||||
connectionStatus: "disconnected",
|
||||
serverCapabilities: null,
|
||||
mcpClient: null,
|
||||
requestHistory: [],
|
||||
makeRequest: vi.fn(),
|
||||
sendNotification: vi.fn(),
|
||||
connect: vi.fn()
|
||||
})
|
||||
connect: vi.fn(),
|
||||
});
|
||||
|
||||
// Mock useDraggablePane hook
|
||||
const mockUseDraggablePane = useDraggablePane as jest.Mock
|
||||
const mockUseDraggablePane = useDraggablePane as jest.Mock;
|
||||
mockUseDraggablePane.mockReturnValue({
|
||||
height: 300,
|
||||
handleDragStart: vi.fn()
|
||||
})
|
||||
})
|
||||
handleDragStart: vi.fn(),
|
||||
});
|
||||
});
|
||||
|
||||
it('renders initial disconnected state', async () => {
|
||||
it("renders initial disconnected state", async () => {
|
||||
await act(async () => {
|
||||
render(<App />)
|
||||
})
|
||||
expect(screen.getByText('Connect to an MCP server to start inspecting')).toBeInTheDocument()
|
||||
})
|
||||
render(<App />);
|
||||
});
|
||||
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 () => {
|
||||
render(<App />)
|
||||
})
|
||||
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/config')
|
||||
})
|
||||
render(<App />);
|
||||
});
|
||||
expect(mockFetch).toHaveBeenCalledWith("http://localhost:3000/config");
|
||||
});
|
||||
|
||||
it('shows connected interface when mcpClient is available', async () => {
|
||||
const mockUseConnection = useConnection as jest.Mock
|
||||
it("shows connected interface when mcpClient is available", async () => {
|
||||
const mockUseConnection = useConnection as jest.Mock;
|
||||
mockUseConnection.mockReturnValue({
|
||||
connectionStatus: 'connected',
|
||||
connectionStatus: "connected",
|
||||
serverCapabilities: {
|
||||
resources: true,
|
||||
prompts: true,
|
||||
tools: true
|
||||
tools: true,
|
||||
},
|
||||
mcpClient: {},
|
||||
requestHistory: [],
|
||||
makeRequest: vi.fn(),
|
||||
sendNotification: vi.fn(),
|
||||
connect: vi.fn()
|
||||
})
|
||||
connect: vi.fn(),
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
render(<App />)
|
||||
})
|
||||
render(<App />);
|
||||
});
|
||||
|
||||
// Use more specific selectors
|
||||
const resourcesTab = screen.getByRole('tab', { name: /resources/i })
|
||||
const promptsTab = screen.getByRole('tab', { name: /prompts/i })
|
||||
const toolsTab = screen.getByRole('tab', { name: /tools/i })
|
||||
const resourcesTab = screen.getByRole("tab", { name: /resources/i });
|
||||
const promptsTab = screen.getByRole("tab", { name: /prompts/i });
|
||||
const toolsTab = screen.getByRole("tab", { name: /tools/i });
|
||||
|
||||
expect(resourcesTab).toBeInTheDocument()
|
||||
expect(promptsTab).toBeInTheDocument()
|
||||
expect(toolsTab).toBeInTheDocument()
|
||||
})
|
||||
expect(resourcesTab).toBeInTheDocument();
|
||||
expect(promptsTab).toBeInTheDocument();
|
||||
expect(toolsTab).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('disables tabs based on server capabilities', async () => {
|
||||
const mockUseConnection = useConnection as jest.Mock
|
||||
it("disables tabs based on server capabilities", async () => {
|
||||
const mockUseConnection = useConnection as jest.Mock;
|
||||
mockUseConnection.mockReturnValue({
|
||||
connectionStatus: 'connected',
|
||||
connectionStatus: "connected",
|
||||
serverCapabilities: {
|
||||
resources: false,
|
||||
prompts: true,
|
||||
tools: false
|
||||
tools: false,
|
||||
},
|
||||
mcpClient: {},
|
||||
requestHistory: [],
|
||||
makeRequest: vi.fn(),
|
||||
sendNotification: vi.fn(),
|
||||
connect: vi.fn()
|
||||
})
|
||||
connect: vi.fn(),
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
render(<App />)
|
||||
})
|
||||
|
||||
render(<App />);
|
||||
});
|
||||
|
||||
// Resources tab should be disabled
|
||||
const resourcesTab = screen.getByRole('tab', { name: /resources/i })
|
||||
expect(resourcesTab).toHaveAttribute('disabled')
|
||||
const resourcesTab = screen.getByRole("tab", { name: /resources/i });
|
||||
expect(resourcesTab).toHaveAttribute("disabled");
|
||||
|
||||
// Prompts tab should be enabled
|
||||
const promptsTab = screen.getByRole('tab', { name: /prompts/i })
|
||||
expect(promptsTab).not.toHaveAttribute('disabled')
|
||||
const promptsTab = screen.getByRole("tab", { name: /prompts/i });
|
||||
expect(promptsTab).not.toHaveAttribute("disabled");
|
||||
|
||||
// Tools tab should be disabled
|
||||
const toolsTab = screen.getByRole('tab', { name: /tools/i })
|
||||
expect(toolsTab).toHaveAttribute('disabled')
|
||||
})
|
||||
const toolsTab = screen.getByRole("tab", { name: /tools/i });
|
||||
expect(toolsTab).toHaveAttribute("disabled");
|
||||
});
|
||||
|
||||
it('shows notification count in sampling tab', async () => {
|
||||
const mockUseConnection = useConnection as jest.Mock
|
||||
it("shows notification count in sampling tab", async () => {
|
||||
const mockUseConnection = useConnection as jest.Mock;
|
||||
mockUseConnection.mockReturnValue({
|
||||
connectionStatus: 'connected',
|
||||
connectionStatus: "connected",
|
||||
serverCapabilities: { sampling: true },
|
||||
mcpClient: {},
|
||||
requestHistory: [],
|
||||
@@ -149,65 +152,69 @@ describe('App', () => {
|
||||
connect: vi.fn(),
|
||||
onPendingRequest: (request, resolve, reject) => {
|
||||
// Simulate a pending request
|
||||
setPendingSampleRequests(prev => [
|
||||
setPendingSampleRequests((prev) => [
|
||||
...prev,
|
||||
{ id: 1, request, resolve, reject }
|
||||
])
|
||||
}
|
||||
})
|
||||
{ id: 1, request, resolve, reject },
|
||||
]);
|
||||
},
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
render(<App />)
|
||||
})
|
||||
|
||||
render(<App />);
|
||||
});
|
||||
|
||||
// Initially no notification count
|
||||
const samplingTab = screen.getByRole('tab', { name: /sampling/i })
|
||||
expect(samplingTab.querySelector('.bg-red-500')).not.toBeInTheDocument()
|
||||
const samplingTab = screen.getByRole("tab", { name: /sampling/i });
|
||||
expect(samplingTab.querySelector(".bg-red-500")).not.toBeInTheDocument();
|
||||
|
||||
// Simulate a pending request
|
||||
await act(async () => {
|
||||
mockUseConnection.mock.calls[0][0].onPendingRequest(
|
||||
{ method: 'test', params: {} },
|
||||
{ method: "test", params: {} },
|
||||
() => {},
|
||||
() => {}
|
||||
)
|
||||
})
|
||||
|
||||
// Should show notification count
|
||||
expect(samplingTab.querySelector('.bg-red-500')).toBeInTheDocument()
|
||||
})
|
||||
() => {},
|
||||
);
|
||||
});
|
||||
|
||||
// Should show notification count
|
||||
expect(samplingTab.querySelector(".bg-red-500")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("persists command and args to localStorage", async () => {
|
||||
const setItemSpy = vi.spyOn(Storage.prototype, "setItem");
|
||||
|
||||
it('persists command and args to localStorage', async () => {
|
||||
const setItemSpy = vi.spyOn(Storage.prototype, 'setItem')
|
||||
|
||||
await act(async () => {
|
||||
render(<App />)
|
||||
})
|
||||
|
||||
render(<App />);
|
||||
});
|
||||
|
||||
// Simulate command change
|
||||
await act(async () => {
|
||||
const commandInput = screen.getByPlaceholderText(/command/i)
|
||||
fireEvent.change(commandInput, { target: { value: 'new-command' } })
|
||||
})
|
||||
|
||||
expect(setItemSpy).toHaveBeenCalledWith('lastCommand', 'new-command')
|
||||
})
|
||||
const commandInput = screen.getByPlaceholderText(/command/i);
|
||||
fireEvent.change(commandInput, { target: { value: "new-command" } });
|
||||
});
|
||||
|
||||
it('shows error message when server has no capabilities', async () => {
|
||||
const mockUseConnection = useConnection as jest.Mock
|
||||
expect(setItemSpy).toHaveBeenCalledWith("lastCommand", "new-command");
|
||||
});
|
||||
|
||||
it("shows error message when server has no capabilities", async () => {
|
||||
const mockUseConnection = useConnection as jest.Mock;
|
||||
mockUseConnection.mockReturnValue({
|
||||
connectionStatus: 'connected',
|
||||
connectionStatus: "connected",
|
||||
serverCapabilities: {},
|
||||
mcpClient: {},
|
||||
requestHistory: [],
|
||||
makeRequest: vi.fn(),
|
||||
sendNotification: vi.fn(),
|
||||
connect: vi.fn()
|
||||
})
|
||||
connect: vi.fn(),
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
render(<App />)
|
||||
})
|
||||
expect(screen.getByText('The connected server does not support any MCP capabilities')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
render(<App />);
|
||||
});
|
||||
expect(
|
||||
screen.getByText(
|
||||
"The connected server does not support any MCP capabilities",
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,40 +1,61 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { render, screen, fireEvent } from '@testing-library/react'
|
||||
import HistoryAndNotifications from '../../components/History'
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { render, screen, fireEvent } from "@testing-library/react";
|
||||
import HistoryAndNotifications from "../../components/History";
|
||||
|
||||
describe('HistoryAndNotifications', () => {
|
||||
describe("HistoryAndNotifications", () => {
|
||||
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', () => {
|
||||
render(<HistoryAndNotifications requestHistory={mockHistory} serverNotifications={[]} />)
|
||||
const items = screen.getAllByText(/test[12]/, { exact: false })
|
||||
expect(items).toHaveLength(2)
|
||||
})
|
||||
it("renders history items", () => {
|
||||
render(
|
||||
<HistoryAndNotifications
|
||||
requestHistory={mockHistory}
|
||||
serverNotifications={[]}
|
||||
/>,
|
||||
);
|
||||
const items = screen.getAllByText(/test[12]/, { exact: false });
|
||||
expect(items).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('expands history item when clicked', () => {
|
||||
render(<HistoryAndNotifications requestHistory={mockHistory} serverNotifications={[]} />)
|
||||
|
||||
const firstItem = screen.getByText(/test1/, { exact: false })
|
||||
fireEvent.click(firstItem)
|
||||
expect(screen.getByText('Request:')).toBeInTheDocument()
|
||||
expect(screen.getByText(/output1/, { exact: false })).toBeInTheDocument()
|
||||
})
|
||||
it("expands history item when clicked", () => {
|
||||
render(
|
||||
<HistoryAndNotifications
|
||||
requestHistory={mockHistory}
|
||||
serverNotifications={[]}
|
||||
/>,
|
||||
);
|
||||
|
||||
it('renders and expands server notifications', () => {
|
||||
const firstItem = screen.getByText(/test1/, { exact: false });
|
||||
fireEvent.click(firstItem);
|
||||
expect(screen.getByText("Request:")).toBeInTheDocument();
|
||||
expect(screen.getByText(/output1/, { exact: false })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders and expands server notifications", () => {
|
||||
const notifications = [
|
||||
{ method: 'notify1', params: { data: 'test data 1' } },
|
||||
{ method: 'notify2', params: { data: 'test data 2' } }
|
||||
]
|
||||
render(<HistoryAndNotifications requestHistory={[]} serverNotifications={notifications} />)
|
||||
|
||||
const items = screen.getAllByText(/notify[12]/, { exact: false })
|
||||
expect(items).toHaveLength(2)
|
||||
{ method: "notify1", params: { data: "test data 1" } },
|
||||
{ method: "notify2", params: { data: "test data 2" } },
|
||||
];
|
||||
render(
|
||||
<HistoryAndNotifications
|
||||
requestHistory={[]}
|
||||
serverNotifications={notifications}
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.click(items[0])
|
||||
expect(screen.getByText('Details:')).toBeInTheDocument()
|
||||
expect(screen.getByText(/test data/, { exact: false })).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
const items = screen.getAllByText(/notify[12]/, { exact: false });
|
||||
expect(items).toHaveLength(2);
|
||||
|
||||
fireEvent.click(items[0]);
|
||||
expect(screen.getByText("Details:")).toBeInTheDocument();
|
||||
expect(screen.getByText(/test data/, { exact: false })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import { describe, it, expect, vi } from 'vitest'
|
||||
import { render, screen, fireEvent } from '@testing-library/react'
|
||||
import ListPane from '../../components/ListPane'
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { render, screen, fireEvent } from "@testing-library/react";
|
||||
import ListPane from "../../components/ListPane";
|
||||
|
||||
describe('ListPane', () => {
|
||||
describe("ListPane", () => {
|
||||
type TestItem = {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
};
|
||||
|
||||
const mockItems: TestItem[] = [
|
||||
{ id: 1, name: 'Item 1' },
|
||||
{ id: 2, name: 'Item 2' }
|
||||
]
|
||||
{ id: 1, name: "Item 1" },
|
||||
{ id: 2, name: "Item 2" },
|
||||
];
|
||||
|
||||
const defaultProps = {
|
||||
items: mockItems,
|
||||
@@ -24,61 +24,61 @@ describe('ListPane', () => {
|
||||
<span className="text-sm text-gray-500">ID: {item.id}</span>
|
||||
</>
|
||||
),
|
||||
title: 'Test Items',
|
||||
buttonText: 'List Items'
|
||||
}
|
||||
title: "Test Items",
|
||||
buttonText: "List Items",
|
||||
};
|
||||
|
||||
it('renders title and buttons', () => {
|
||||
render(<ListPane {...defaultProps} />)
|
||||
expect(screen.getByText('Test Items')).toBeInTheDocument()
|
||||
expect(screen.getByText('List Items')).toBeInTheDocument()
|
||||
expect(screen.getByText('Clear')).toBeInTheDocument()
|
||||
})
|
||||
it("renders title and buttons", () => {
|
||||
render(<ListPane {...defaultProps} />);
|
||||
expect(screen.getByText("Test Items")).toBeInTheDocument();
|
||||
expect(screen.getByText("List Items")).toBeInTheDocument();
|
||||
expect(screen.getByText("Clear")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders list of items using renderItem prop', () => {
|
||||
render(<ListPane {...defaultProps} />)
|
||||
expect(screen.getByText('Item 1')).toBeInTheDocument()
|
||||
expect(screen.getByText('Item 2')).toBeInTheDocument()
|
||||
expect(screen.getByText('ID: 1')).toBeInTheDocument()
|
||||
expect(screen.getByText('ID: 2')).toBeInTheDocument()
|
||||
})
|
||||
it("renders list of items using renderItem prop", () => {
|
||||
render(<ListPane {...defaultProps} />);
|
||||
expect(screen.getByText("Item 1")).toBeInTheDocument();
|
||||
expect(screen.getByText("Item 2")).toBeInTheDocument();
|
||||
expect(screen.getByText("ID: 1")).toBeInTheDocument();
|
||||
expect(screen.getByText("ID: 2")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('calls listItems when List Items button is clicked', () => {
|
||||
const listItems = vi.fn()
|
||||
render(<ListPane {...defaultProps} listItems={listItems} />)
|
||||
|
||||
fireEvent.click(screen.getByText('List Items'))
|
||||
expect(listItems).toHaveBeenCalled()
|
||||
})
|
||||
it("calls listItems when List Items button is clicked", () => {
|
||||
const listItems = vi.fn();
|
||||
render(<ListPane {...defaultProps} listItems={listItems} />);
|
||||
|
||||
it('calls clearItems when Clear button is clicked', () => {
|
||||
const clearItems = vi.fn()
|
||||
render(<ListPane {...defaultProps} clearItems={clearItems} />)
|
||||
|
||||
fireEvent.click(screen.getByText('Clear'))
|
||||
expect(clearItems).toHaveBeenCalled()
|
||||
})
|
||||
fireEvent.click(screen.getByText("List Items"));
|
||||
expect(listItems).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('calls setSelectedItem when an item is clicked', () => {
|
||||
const setSelectedItem = vi.fn()
|
||||
render(<ListPane {...defaultProps} setSelectedItem={setSelectedItem} />)
|
||||
|
||||
fireEvent.click(screen.getByText('Item 1'))
|
||||
expect(setSelectedItem).toHaveBeenCalledWith(mockItems[0])
|
||||
})
|
||||
it("calls clearItems when Clear button is clicked", () => {
|
||||
const clearItems = vi.fn();
|
||||
render(<ListPane {...defaultProps} clearItems={clearItems} />);
|
||||
|
||||
it('disables Clear button when items array is empty', () => {
|
||||
render(<ListPane {...defaultProps} items={[]} />)
|
||||
expect(screen.getByText('Clear')).toBeDisabled()
|
||||
})
|
||||
fireEvent.click(screen.getByText("Clear"));
|
||||
expect(clearItems).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('disables List Items button when isButtonDisabled is true', () => {
|
||||
render(<ListPane {...defaultProps} isButtonDisabled={true} />)
|
||||
expect(screen.getByText('List Items')).toBeDisabled()
|
||||
})
|
||||
it("calls setSelectedItem when an item is clicked", () => {
|
||||
const setSelectedItem = vi.fn();
|
||||
render(<ListPane {...defaultProps} setSelectedItem={setSelectedItem} />);
|
||||
|
||||
it('enables List Items button when isButtonDisabled is false', () => {
|
||||
render(<ListPane {...defaultProps} isButtonDisabled={false} />)
|
||||
expect(screen.getByText('List Items')).not.toBeDisabled()
|
||||
})
|
||||
})
|
||||
fireEvent.click(screen.getByText("Item 1"));
|
||||
expect(setSelectedItem).toHaveBeenCalledWith(mockItems[0]);
|
||||
});
|
||||
|
||||
it("disables Clear button when items array is empty", () => {
|
||||
render(<ListPane {...defaultProps} items={[]} />);
|
||||
expect(screen.getByText("Clear")).toBeDisabled();
|
||||
});
|
||||
|
||||
it("disables List Items button when isButtonDisabled is true", () => {
|
||||
render(<ListPane {...defaultProps} isButtonDisabled={true} />);
|
||||
expect(screen.getByText("List Items")).toBeDisabled();
|
||||
});
|
||||
|
||||
it("enables List Items button when isButtonDisabled is false", () => {
|
||||
render(<ListPane {...defaultProps} isButtonDisabled={false} />);
|
||||
expect(screen.getByText("List Items")).not.toBeDisabled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,47 +1,51 @@
|
||||
import { describe, it, expect, vi } from 'vitest'
|
||||
import { render, screen, fireEvent } from '@testing-library/react'
|
||||
import PingTab from '../../components/PingTab'
|
||||
import { Tabs } from '@/components/ui/tabs'
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { render, screen, fireEvent } from "@testing-library/react";
|
||||
import PingTab from "../../components/PingTab";
|
||||
import { Tabs } from "@/components/ui/tabs";
|
||||
|
||||
describe('PingTab', () => {
|
||||
describe("PingTab", () => {
|
||||
const renderWithTabs = (component: React.ReactElement) => {
|
||||
return render(
|
||||
<Tabs defaultValue="ping">
|
||||
{component}
|
||||
</Tabs>
|
||||
)
|
||||
}
|
||||
return render(<Tabs defaultValue="ping">{component}</Tabs>);
|
||||
};
|
||||
|
||||
it('renders the MEGA PING button', () => {
|
||||
renderWithTabs(<PingTab onPingClick={() => {}} />)
|
||||
const button = screen.getByRole('button', { name: /mega ping/i })
|
||||
expect(button).toBeInTheDocument()
|
||||
expect(button).toHaveClass('bg-gradient-to-r', 'from-purple-500', 'to-pink-500')
|
||||
})
|
||||
it("renders the MEGA PING button", () => {
|
||||
renderWithTabs(<PingTab onPingClick={() => {}} />);
|
||||
const button = screen.getByRole("button", { name: /mega ping/i });
|
||||
expect(button).toBeInTheDocument();
|
||||
expect(button).toHaveClass(
|
||||
"bg-gradient-to-r",
|
||||
"from-purple-500",
|
||||
"to-pink-500",
|
||||
);
|
||||
});
|
||||
|
||||
it('includes rocket and explosion emojis', () => {
|
||||
renderWithTabs(<PingTab onPingClick={() => {}} />)
|
||||
expect(screen.getByText('🚀')).toBeInTheDocument()
|
||||
expect(screen.getByText('💥')).toBeInTheDocument()
|
||||
})
|
||||
it("includes rocket and explosion emojis", () => {
|
||||
renderWithTabs(<PingTab onPingClick={() => {}} />);
|
||||
expect(screen.getByText("🚀")).toBeInTheDocument();
|
||||
expect(screen.getByText("💥")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('calls onPingClick when button is clicked', () => {
|
||||
const onPingClick = vi.fn()
|
||||
renderWithTabs(<PingTab onPingClick={onPingClick} />)
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /mega ping/i }))
|
||||
expect(onPingClick).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
it("calls onPingClick when button is clicked", () => {
|
||||
const onPingClick = vi.fn();
|
||||
renderWithTabs(<PingTab onPingClick={onPingClick} />);
|
||||
|
||||
it('has animation classes for visual feedback', () => {
|
||||
renderWithTabs(<PingTab onPingClick={() => {}} />)
|
||||
const button = screen.getByRole('button', { name: /mega ping/i })
|
||||
expect(button).toHaveClass('animate-pulse', 'hover:scale-110', 'transition')
|
||||
})
|
||||
fireEvent.click(screen.getByRole("button", { name: /mega ping/i }));
|
||||
expect(onPingClick).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('has focus styles for accessibility', () => {
|
||||
renderWithTabs(<PingTab onPingClick={() => {}} />)
|
||||
const button = screen.getByRole('button', { name: /mega ping/i })
|
||||
expect(button).toHaveClass('focus:outline-none', 'focus:ring-4')
|
||||
})
|
||||
})
|
||||
it("has animation classes for visual feedback", () => {
|
||||
renderWithTabs(<PingTab onPingClick={() => {}} />);
|
||||
const button = screen.getByRole("button", { name: /mega ping/i });
|
||||
expect(button).toHaveClass(
|
||||
"animate-pulse",
|
||||
"hover:scale-110",
|
||||
"transition",
|
||||
);
|
||||
});
|
||||
|
||||
it("has focus styles for accessibility", () => {
|
||||
renderWithTabs(<PingTab onPingClick={() => {}} />);
|
||||
const button = screen.getByRole("button", { name: /mega ping/i });
|
||||
expect(button).toHaveClass("focus:outline-none", "focus:ring-4");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
import { describe, it, expect, vi } from 'vitest'
|
||||
import { render, screen, fireEvent } from '@testing-library/react'
|
||||
import PromptsTab from '../../components/PromptsTab'
|
||||
import type { Prompt } from '../../components/PromptsTab'
|
||||
import { Tabs } from '@/components/ui/tabs'
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { render, screen, fireEvent } from "@testing-library/react";
|
||||
import PromptsTab from "../../components/PromptsTab";
|
||||
import type { Prompt } from "../../components/PromptsTab";
|
||||
import { Tabs } from "@/components/ui/tabs";
|
||||
|
||||
describe('PromptsTab', () => {
|
||||
describe("PromptsTab", () => {
|
||||
const mockPrompts: Prompt[] = [
|
||||
{
|
||||
name: 'test-prompt-1',
|
||||
description: 'Test prompt 1 description',
|
||||
name: "test-prompt-1",
|
||||
description: "Test prompt 1 description",
|
||||
arguments: [
|
||||
{ name: 'arg1', description: 'Argument 1', required: true },
|
||||
{ name: 'arg2', description: 'Argument 2' }
|
||||
]
|
||||
{ name: "arg1", description: "Argument 1", required: true },
|
||||
{ name: "arg2", description: "Argument 2" },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'test-prompt-2',
|
||||
description: 'Test prompt 2 description'
|
||||
}
|
||||
]
|
||||
name: "test-prompt-2",
|
||||
description: "Test prompt 2 description",
|
||||
},
|
||||
];
|
||||
|
||||
const defaultProps = {
|
||||
prompts: mockPrompts,
|
||||
@@ -27,70 +27,72 @@ describe('PromptsTab', () => {
|
||||
getPrompt: vi.fn(),
|
||||
selectedPrompt: null,
|
||||
setSelectedPrompt: vi.fn(),
|
||||
promptContent: '',
|
||||
promptContent: "",
|
||||
nextCursor: null,
|
||||
error: null
|
||||
}
|
||||
error: null,
|
||||
};
|
||||
|
||||
const renderWithTabs = (component: React.ReactElement) => {
|
||||
return render(
|
||||
<Tabs defaultValue="prompts">
|
||||
{component}
|
||||
</Tabs>
|
||||
)
|
||||
}
|
||||
return render(<Tabs defaultValue="prompts">{component}</Tabs>);
|
||||
};
|
||||
|
||||
it('renders list of prompts', () => {
|
||||
renderWithTabs(<PromptsTab {...defaultProps} />)
|
||||
expect(screen.getByText('test-prompt-1')).toBeInTheDocument()
|
||||
expect(screen.getByText('test-prompt-2')).toBeInTheDocument()
|
||||
})
|
||||
it("renders list of prompts", () => {
|
||||
renderWithTabs(<PromptsTab {...defaultProps} />);
|
||||
expect(screen.getByText("test-prompt-1")).toBeInTheDocument();
|
||||
expect(screen.getByText("test-prompt-2")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
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()
|
||||
it("shows prompt details when selected", () => {
|
||||
const props = {
|
||||
...defaultProps,
|
||||
selectedPrompt: mockPrompts[0],
|
||||
getPrompt
|
||||
}
|
||||
renderWithTabs(<PromptsTab {...props} />)
|
||||
|
||||
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' })
|
||||
})
|
||||
};
|
||||
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('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', () => {
|
||||
it("handles argument input", () => {
|
||||
const getPrompt = vi.fn();
|
||||
const props = {
|
||||
...defaultProps,
|
||||
selectedPrompt: mockPrompts[0],
|
||||
promptContent: 'Test prompt content'
|
||||
}
|
||||
renderWithTabs(<PromptsTab {...props} />)
|
||||
expect(screen.getByText('Test prompt content')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
getPrompt,
|
||||
};
|
||||
renderWithTabs(<PromptsTab {...props} />);
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,27 +1,30 @@
|
||||
import { describe, it, expect, vi } from 'vitest'
|
||||
import { render, screen, fireEvent } from '@testing-library/react'
|
||||
import ResourcesTab from '../../components/ResourcesTab'
|
||||
import { Tabs } from '@/components/ui/tabs'
|
||||
import type { Resource, ResourceTemplate } from '@modelcontextprotocol/sdk/types.js'
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { render, screen, fireEvent } from "@testing-library/react";
|
||||
import ResourcesTab from "../../components/ResourcesTab";
|
||||
import { Tabs } from "@/components/ui/tabs";
|
||||
import type {
|
||||
Resource,
|
||||
ResourceTemplate,
|
||||
} from "@modelcontextprotocol/sdk/types.js";
|
||||
|
||||
describe('ResourcesTab', () => {
|
||||
describe("ResourcesTab", () => {
|
||||
const mockResources: Resource[] = [
|
||||
{ uri: 'file:///test1.txt', name: 'Test 1' },
|
||||
{ uri: 'file:///test2.txt', name: 'Test 2' }
|
||||
]
|
||||
{ uri: "file:///test1.txt", name: "Test 1" },
|
||||
{ uri: "file:///test2.txt", name: "Test 2" },
|
||||
];
|
||||
|
||||
const mockTemplates: ResourceTemplate[] = [
|
||||
{
|
||||
name: 'Template 1',
|
||||
description: 'Test template 1',
|
||||
uriTemplate: 'file:///test/{param1}/{param2}.txt'
|
||||
{
|
||||
name: "Template 1",
|
||||
description: "Test template 1",
|
||||
uriTemplate: "file:///test/{param1}/{param2}.txt",
|
||||
},
|
||||
{
|
||||
name: 'Template 2',
|
||||
description: 'Test template 2',
|
||||
uriTemplate: 'file:///other/{name}.txt'
|
||||
}
|
||||
]
|
||||
name: "Template 2",
|
||||
description: "Test template 2",
|
||||
uriTemplate: "file:///other/{name}.txt",
|
||||
},
|
||||
];
|
||||
|
||||
const defaultProps = {
|
||||
resources: mockResources,
|
||||
@@ -33,100 +36,100 @@ describe('ResourcesTab', () => {
|
||||
readResource: vi.fn(),
|
||||
selectedResource: null,
|
||||
setSelectedResource: vi.fn(),
|
||||
resourceContent: '',
|
||||
resourceContent: "",
|
||||
nextCursor: null,
|
||||
nextTemplateCursor: null,
|
||||
error: null
|
||||
}
|
||||
error: null,
|
||||
};
|
||||
|
||||
const renderWithTabs = (component: React.ReactElement) => {
|
||||
return render(
|
||||
<Tabs defaultValue="resources">
|
||||
{component}
|
||||
</Tabs>
|
||||
)
|
||||
}
|
||||
return render(<Tabs defaultValue="resources">{component}</Tabs>);
|
||||
};
|
||||
|
||||
it('renders resources list', () => {
|
||||
renderWithTabs(<ResourcesTab {...defaultProps} />)
|
||||
expect(screen.getByText('Test 1')).toBeInTheDocument()
|
||||
expect(screen.getByText('Test 2')).toBeInTheDocument()
|
||||
})
|
||||
it("renders resources list", () => {
|
||||
renderWithTabs(<ResourcesTab {...defaultProps} />);
|
||||
expect(screen.getByText("Test 1")).toBeInTheDocument();
|
||||
expect(screen.getByText("Test 2")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders templates list', () => {
|
||||
renderWithTabs(<ResourcesTab {...defaultProps} />)
|
||||
expect(screen.getByText('Template 1')).toBeInTheDocument()
|
||||
expect(screen.getByText('Template 2')).toBeInTheDocument()
|
||||
})
|
||||
it("renders templates list", () => {
|
||||
renderWithTabs(<ResourcesTab {...defaultProps} />);
|
||||
expect(screen.getByText("Template 1")).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 = {
|
||||
...defaultProps,
|
||||
selectedResource: mockResources[0],
|
||||
resourceContent: 'Test content'
|
||||
}
|
||||
renderWithTabs(<ResourcesTab {...props} />)
|
||||
expect(screen.getByText('Test content')).toBeInTheDocument()
|
||||
})
|
||||
resourceContent: "Test content",
|
||||
};
|
||||
renderWithTabs(<ResourcesTab {...props} />);
|
||||
expect(screen.getByText("Test content")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows template form when template is selected', () => {
|
||||
renderWithTabs(<ResourcesTab {...defaultProps} />)
|
||||
|
||||
fireEvent.click(screen.getByText('Template 1'))
|
||||
expect(screen.getByText('Test template 1')).toBeInTheDocument()
|
||||
expect(screen.getByLabelText('param1')).toBeInTheDocument()
|
||||
expect(screen.getByLabelText('param2')).toBeInTheDocument()
|
||||
})
|
||||
it("shows template form when template is selected", () => {
|
||||
renderWithTabs(<ResourcesTab {...defaultProps} />);
|
||||
|
||||
it('fills template and reads resource', () => {
|
||||
const readResource = vi.fn()
|
||||
const setSelectedResource = vi.fn()
|
||||
fireEvent.click(screen.getByText("Template 1"));
|
||||
expect(screen.getByText("Test template 1")).toBeInTheDocument();
|
||||
expect(screen.getByLabelText("param1")).toBeInTheDocument();
|
||||
expect(screen.getByLabelText("param2")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("fills template and reads resource", () => {
|
||||
const readResource = vi.fn();
|
||||
const setSelectedResource = vi.fn();
|
||||
renderWithTabs(
|
||||
<ResourcesTab
|
||||
{...defaultProps}
|
||||
<ResourcesTab
|
||||
{...defaultProps}
|
||||
readResource={readResource}
|
||||
setSelectedResource={setSelectedResource}
|
||||
/>
|
||||
)
|
||||
|
||||
// Select template
|
||||
fireEvent.click(screen.getByText('Template 1'))
|
||||
|
||||
// Fill in template parameters
|
||||
fireEvent.change(screen.getByLabelText('param1'), { target: { value: 'value1' } })
|
||||
fireEvent.change(screen.getByLabelText('param2'), { target: { value: 'value2' } })
|
||||
|
||||
// Submit form
|
||||
fireEvent.click(screen.getByText('Read Resource'))
|
||||
|
||||
expect(readResource).toHaveBeenCalledWith('file:///test/value1/value2.txt')
|
||||
expect(setSelectedResource).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
uri: 'file:///test/value1/value2.txt',
|
||||
name: 'file:///test/value1/value2.txt'
|
||||
})
|
||||
)
|
||||
})
|
||||
/>,
|
||||
);
|
||||
|
||||
it('shows error message when error prop is provided', () => {
|
||||
// Select template
|
||||
fireEvent.click(screen.getByText("Template 1"));
|
||||
|
||||
// Fill in template parameters
|
||||
fireEvent.change(screen.getByLabelText("param1"), {
|
||||
target: { value: "value1" },
|
||||
});
|
||||
fireEvent.change(screen.getByLabelText("param2"), {
|
||||
target: { value: "value2" },
|
||||
});
|
||||
|
||||
// Submit form
|
||||
fireEvent.click(screen.getByText("Read Resource"));
|
||||
|
||||
expect(readResource).toHaveBeenCalledWith("file:///test/value1/value2.txt");
|
||||
expect(setSelectedResource).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
uri: "file:///test/value1/value2.txt",
|
||||
name: "file:///test/value1/value2.txt",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("shows error message when error prop is provided", () => {
|
||||
const props = {
|
||||
...defaultProps,
|
||||
error: 'Test error message'
|
||||
}
|
||||
renderWithTabs(<ResourcesTab {...props} />)
|
||||
expect(screen.getByText('Test error message')).toBeInTheDocument()
|
||||
})
|
||||
error: "Test error message",
|
||||
};
|
||||
renderWithTabs(<ResourcesTab {...props} />);
|
||||
expect(screen.getByText("Test error message")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('refreshes resource content when refresh button is clicked', () => {
|
||||
const readResource = vi.fn()
|
||||
it("refreshes resource content when refresh button is clicked", () => {
|
||||
const readResource = vi.fn();
|
||||
const props = {
|
||||
...defaultProps,
|
||||
selectedResource: mockResources[0],
|
||||
readResource
|
||||
}
|
||||
renderWithTabs(<ResourcesTab {...props} />)
|
||||
|
||||
fireEvent.click(screen.getByText('Refresh'))
|
||||
expect(readResource).toHaveBeenCalledWith(mockResources[0].uri)
|
||||
})
|
||||
})
|
||||
readResource,
|
||||
};
|
||||
renderWithTabs(<ResourcesTab {...props} />);
|
||||
|
||||
fireEvent.click(screen.getByText("Refresh"));
|
||||
expect(readResource).toHaveBeenCalledWith(mockResources[0].uri);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,80 +1,80 @@
|
||||
import { describe, it, expect, vi } from 'vitest'
|
||||
import { render, screen, fireEvent } from '@testing-library/react'
|
||||
import RootsTab from '../../components/RootsTab'
|
||||
import { Tabs } from '@/components/ui/tabs'
|
||||
import type { Root } from '@modelcontextprotocol/sdk/types.js'
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { render, screen, fireEvent } from "@testing-library/react";
|
||||
import RootsTab from "../../components/RootsTab";
|
||||
import { Tabs } from "@/components/ui/tabs";
|
||||
import type { Root } from "@modelcontextprotocol/sdk/types.js";
|
||||
|
||||
describe('RootsTab', () => {
|
||||
describe("RootsTab", () => {
|
||||
const mockRoots: Root[] = [
|
||||
{ uri: 'file:///test/path1', name: 'test1' },
|
||||
{ uri: 'file:///test/path2', name: 'test2' }
|
||||
]
|
||||
{ uri: "file:///test/path1", name: "test1" },
|
||||
{ uri: "file:///test/path2", name: "test2" },
|
||||
];
|
||||
|
||||
const defaultProps = {
|
||||
roots: mockRoots,
|
||||
setRoots: vi.fn(),
|
||||
onRootsChange: vi.fn()
|
||||
}
|
||||
onRootsChange: vi.fn(),
|
||||
};
|
||||
|
||||
const renderWithTabs = (component: React.ReactElement) => {
|
||||
return render(
|
||||
<Tabs defaultValue="roots">
|
||||
{component}
|
||||
</Tabs>
|
||||
)
|
||||
}
|
||||
return render(<Tabs defaultValue="roots">{component}</Tabs>);
|
||||
};
|
||||
|
||||
it('renders list of roots', () => {
|
||||
renderWithTabs(<RootsTab {...defaultProps} />)
|
||||
expect(screen.getByDisplayValue('file:///test/path1')).toBeInTheDocument()
|
||||
expect(screen.getByDisplayValue('file:///test/path2')).toBeInTheDocument()
|
||||
})
|
||||
it("renders list of roots", () => {
|
||||
renderWithTabs(<RootsTab {...defaultProps} />);
|
||||
expect(screen.getByDisplayValue("file:///test/path1")).toBeInTheDocument();
|
||||
expect(screen.getByDisplayValue("file:///test/path2")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('adds a new root when Add Root button is clicked', () => {
|
||||
const setRoots = vi.fn()
|
||||
renderWithTabs(<RootsTab {...defaultProps} setRoots={setRoots} />)
|
||||
|
||||
fireEvent.click(screen.getByText('Add Root'))
|
||||
|
||||
expect(setRoots).toHaveBeenCalled()
|
||||
const updateFn = setRoots.mock.calls[0][0]
|
||||
const result = updateFn(mockRoots)
|
||||
expect(result).toEqual([...mockRoots, { uri: 'file://', name: '' }])
|
||||
})
|
||||
it("adds a new root when Add Root button is clicked", () => {
|
||||
const setRoots = vi.fn();
|
||||
renderWithTabs(<RootsTab {...defaultProps} setRoots={setRoots} />);
|
||||
|
||||
it('removes a root when remove button is clicked', () => {
|
||||
const setRoots = vi.fn()
|
||||
renderWithTabs(<RootsTab {...defaultProps} setRoots={setRoots} />)
|
||||
|
||||
const removeButtons = screen.getAllByRole('button', { name: /remove root/i })
|
||||
fireEvent.click(removeButtons[0])
|
||||
|
||||
expect(setRoots).toHaveBeenCalled()
|
||||
const updateFn = setRoots.mock.calls[0][0]
|
||||
const result = updateFn(mockRoots)
|
||||
expect(result).toEqual([mockRoots[1]])
|
||||
})
|
||||
fireEvent.click(screen.getByText("Add Root"));
|
||||
|
||||
it('updates root URI when input changes', () => {
|
||||
const setRoots = vi.fn()
|
||||
renderWithTabs(<RootsTab {...defaultProps} setRoots={setRoots} />)
|
||||
|
||||
const firstInput = screen.getByDisplayValue('file:///test/path1')
|
||||
fireEvent.change(firstInput, { target: { value: 'file:///new/path' } })
|
||||
|
||||
expect(setRoots).toHaveBeenCalled()
|
||||
const updateFn = setRoots.mock.calls[0][0]
|
||||
const result = updateFn(mockRoots)
|
||||
expect(result[0].uri).toBe('file:///new/path')
|
||||
expect(result[1]).toEqual(mockRoots[1])
|
||||
})
|
||||
expect(setRoots).toHaveBeenCalled();
|
||||
const updateFn = setRoots.mock.calls[0][0];
|
||||
const result = updateFn(mockRoots);
|
||||
expect(result).toEqual([...mockRoots, { uri: "file://", name: "" }]);
|
||||
});
|
||||
|
||||
it('calls onRootsChange when Save Changes is clicked', () => {
|
||||
const onRootsChange = vi.fn()
|
||||
renderWithTabs(<RootsTab {...defaultProps} onRootsChange={onRootsChange} />)
|
||||
|
||||
fireEvent.click(screen.getByText('Save Changes'))
|
||||
|
||||
expect(onRootsChange).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
it("removes a root when remove button is clicked", () => {
|
||||
const setRoots = vi.fn();
|
||||
renderWithTabs(<RootsTab {...defaultProps} setRoots={setRoots} />);
|
||||
|
||||
const removeButtons = screen.getAllByRole("button", {
|
||||
name: /remove root/i,
|
||||
});
|
||||
fireEvent.click(removeButtons[0]);
|
||||
|
||||
expect(setRoots).toHaveBeenCalled();
|
||||
const updateFn = setRoots.mock.calls[0][0];
|
||||
const result = updateFn(mockRoots);
|
||||
expect(result).toEqual([mockRoots[1]]);
|
||||
});
|
||||
|
||||
it("updates root URI when input changes", () => {
|
||||
const setRoots = vi.fn();
|
||||
renderWithTabs(<RootsTab {...defaultProps} setRoots={setRoots} />);
|
||||
|
||||
const firstInput = screen.getByDisplayValue("file:///test/path1");
|
||||
fireEvent.change(firstInput, { target: { value: "file:///new/path" } });
|
||||
|
||||
expect(setRoots).toHaveBeenCalled();
|
||||
const updateFn = setRoots.mock.calls[0][0];
|
||||
const result = updateFn(mockRoots);
|
||||
expect(result[0].uri).toBe("file:///new/path");
|
||||
expect(result[1]).toEqual(mockRoots[1]);
|
||||
});
|
||||
|
||||
it("calls onRootsChange when Save Changes is clicked", () => {
|
||||
const onRootsChange = vi.fn();
|
||||
renderWithTabs(
|
||||
<RootsTab {...defaultProps} onRootsChange={onRootsChange} />,
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByText("Save Changes"));
|
||||
|
||||
expect(onRootsChange).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,85 +1,91 @@
|
||||
import { describe, it, expect, vi } from 'vitest'
|
||||
import { render, screen, fireEvent } from '@testing-library/react'
|
||||
import SamplingTab from '../../components/SamplingTab'
|
||||
import { Tabs } from '@/components/ui/tabs'
|
||||
import type { CreateMessageRequest } from '@modelcontextprotocol/sdk/types.js'
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { render, screen, fireEvent } from "@testing-library/react";
|
||||
import SamplingTab from "../../components/SamplingTab";
|
||||
import { Tabs } from "@/components/ui/tabs";
|
||||
import type { CreateMessageRequest } from "@modelcontextprotocol/sdk/types.js";
|
||||
|
||||
describe('SamplingTab', () => {
|
||||
describe("SamplingTab", () => {
|
||||
const mockRequest: CreateMessageRequest = {
|
||||
model: 'test-model',
|
||||
role: 'user',
|
||||
model: "test-model",
|
||||
role: "user",
|
||||
content: {
|
||||
type: 'text',
|
||||
text: 'Test message'
|
||||
}
|
||||
}
|
||||
type: "text",
|
||||
text: "Test message",
|
||||
},
|
||||
};
|
||||
|
||||
const mockPendingRequests = [
|
||||
{ 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 = {
|
||||
pendingRequests: mockPendingRequests,
|
||||
onApprove: vi.fn(),
|
||||
onReject: vi.fn()
|
||||
}
|
||||
onReject: vi.fn(),
|
||||
};
|
||||
|
||||
const renderWithTabs = (component: React.ReactElement) => {
|
||||
return render(
|
||||
<Tabs defaultValue="sampling">
|
||||
{component}
|
||||
</Tabs>
|
||||
)
|
||||
}
|
||||
return render(<Tabs defaultValue="sampling">{component}</Tabs>);
|
||||
};
|
||||
|
||||
it('renders empty state when no requests', () => {
|
||||
renderWithTabs(<SamplingTab {...defaultProps} pendingRequests={[]} />)
|
||||
expect(screen.getByText('No pending requests')).toBeInTheDocument()
|
||||
})
|
||||
it("renders empty state when no requests", () => {
|
||||
renderWithTabs(<SamplingTab {...defaultProps} pendingRequests={[]} />);
|
||||
expect(screen.getByText("No pending requests")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders list of pending requests', () => {
|
||||
renderWithTabs(<SamplingTab {...defaultProps} />)
|
||||
expect(screen.getByText(/Test message/)).toBeInTheDocument()
|
||||
expect(screen.getByText(/Another test/)).toBeInTheDocument()
|
||||
})
|
||||
it("renders list of pending requests", () => {
|
||||
renderWithTabs(<SamplingTab {...defaultProps} />);
|
||||
expect(screen.getByText(/Test message/)).toBeInTheDocument();
|
||||
expect(screen.getByText(/Another test/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows request details in JSON format', () => {
|
||||
renderWithTabs(<SamplingTab {...defaultProps} />)
|
||||
const requestJson = screen.getAllByText((content) => content.includes('"model": "test-model"'))
|
||||
expect(requestJson).toHaveLength(2)
|
||||
})
|
||||
it("shows request details in JSON format", () => {
|
||||
renderWithTabs(<SamplingTab {...defaultProps} />);
|
||||
const requestJson = screen.getAllByText((content) =>
|
||||
content.includes('"model": "test-model"'),
|
||||
);
|
||||
expect(requestJson).toHaveLength(2);
|
||||
});
|
||||
|
||||
it("calls onApprove with stub response when Approve is clicked", () => {
|
||||
const onApprove = vi.fn();
|
||||
renderWithTabs(<SamplingTab {...defaultProps} onApprove={onApprove} />);
|
||||
|
||||
const approveButtons = screen.getAllByText("Approve");
|
||||
fireEvent.click(approveButtons[0]);
|
||||
|
||||
it('calls onApprove with stub response when Approve is clicked', () => {
|
||||
const onApprove = vi.fn()
|
||||
renderWithTabs(<SamplingTab {...defaultProps} onApprove={onApprove} />)
|
||||
|
||||
const approveButtons = screen.getAllByText('Approve')
|
||||
fireEvent.click(approveButtons[0])
|
||||
|
||||
expect(onApprove).toHaveBeenCalledWith(1, {
|
||||
model: 'stub-model',
|
||||
stopReason: 'endTurn',
|
||||
role: 'assistant',
|
||||
model: "stub-model",
|
||||
stopReason: "endTurn",
|
||||
role: "assistant",
|
||||
content: {
|
||||
type: 'text',
|
||||
text: 'This is a stub response.'
|
||||
}
|
||||
})
|
||||
})
|
||||
type: "text",
|
||||
text: "This is a stub response.",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('calls onReject when Reject is clicked', () => {
|
||||
const onReject = vi.fn()
|
||||
renderWithTabs(<SamplingTab {...defaultProps} onReject={onReject} />)
|
||||
|
||||
const rejectButtons = screen.getAllByText('Reject')
|
||||
fireEvent.click(rejectButtons[0])
|
||||
|
||||
expect(onReject).toHaveBeenCalledWith(1)
|
||||
})
|
||||
it("calls onReject when Reject is clicked", () => {
|
||||
const onReject = vi.fn();
|
||||
renderWithTabs(<SamplingTab {...defaultProps} onReject={onReject} />);
|
||||
|
||||
it('shows informational alert about sampling requests', () => {
|
||||
renderWithTabs(<SamplingTab {...defaultProps} />)
|
||||
expect(screen.getByText(/When the server requests LLM sampling/)).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
const rejectButtons = screen.getAllByText("Reject");
|
||||
fireEvent.click(rejectButtons[0]);
|
||||
|
||||
expect(onReject).toHaveBeenCalledWith(1);
|
||||
});
|
||||
|
||||
it("shows informational alert about sampling requests", () => {
|
||||
renderWithTabs(<SamplingTab {...defaultProps} />);
|
||||
expect(
|
||||
screen.getByText(/When the server requests LLM sampling/),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import '@testing-library/jest-dom'
|
||||
import { expect, afterEach, vi } from 'vitest'
|
||||
import { cleanup } from '@testing-library/react'
|
||||
import * as matchers from '@testing-library/jest-dom/matchers'
|
||||
import "@testing-library/jest-dom";
|
||||
import { expect, afterEach, vi } from "vitest";
|
||||
import { cleanup } from "@testing-library/react";
|
||||
import * as matchers from "@testing-library/jest-dom/matchers";
|
||||
|
||||
// @ts-ignore
|
||||
expect.extend(matchers)
|
||||
expect.extend(matchers);
|
||||
|
||||
// Mock window.matchMedia
|
||||
Object.defineProperty(window, 'matchMedia', {
|
||||
Object.defineProperty(window, "matchMedia", {
|
||||
writable: true,
|
||||
value: vi.fn().mockImplementation(query => ({
|
||||
value: vi.fn().mockImplementation((query) => ({
|
||||
matches: false,
|
||||
media: query,
|
||||
onchange: null,
|
||||
@@ -19,16 +19,16 @@ Object.defineProperty(window, 'matchMedia', {
|
||||
removeEventListener: vi.fn(),
|
||||
dispatchEvent: vi.fn(),
|
||||
})),
|
||||
})
|
||||
});
|
||||
|
||||
// Mock window.location.hash
|
||||
Object.defineProperty(window, 'location', {
|
||||
Object.defineProperty(window, "location", {
|
||||
writable: true,
|
||||
value: { hash: '' }
|
||||
})
|
||||
value: { hash: "" },
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cleanup()
|
||||
vi.clearAllMocks()
|
||||
window.location.hash = ''
|
||||
})
|
||||
cleanup();
|
||||
vi.clearAllMocks();
|
||||
window.location.hash = "";
|
||||
});
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
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 { Input } from "@/components/ui/input";
|
||||
import {
|
||||
@@ -243,18 +250,33 @@ const Sidebar = ({
|
||||
</Select>
|
||||
|
||||
<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">
|
||||
<CircleHelp className="w-4 h-4 text-gray-800" />
|
||||
</Button>
|
||||
</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">
|
||||
<Bug className="w-4 h-4 text-gray-800" />
|
||||
</Button>
|
||||
</a>
|
||||
<a href="https://github.com/modelcontextprotocol/inspector" target="_blank" rel="noopener noreferrer">
|
||||
<Button variant="ghost" title="Report bugs or contribute on GitHub">
|
||||
<a
|
||||
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" />
|
||||
</Button>
|
||||
</a>
|
||||
|
||||
@@ -174,8 +174,7 @@ const ToolsTab = ({
|
||||
}
|
||||
className="mt-1"
|
||||
/>
|
||||
) :
|
||||
/* @ts-expect-error value type is currently unknown */
|
||||
) : /* @ts-expect-error value type is currently unknown */
|
||||
value.type === "object" ? (
|
||||
<Textarea
|
||||
id={key}
|
||||
|
||||
@@ -44,10 +44,15 @@ export function useConnection({
|
||||
onPendingRequest,
|
||||
getRoots,
|
||||
}: UseConnectionOptions) {
|
||||
const [connectionStatus, setConnectionStatus] = useState<"disconnected" | "connected" | "error">("disconnected");
|
||||
const [serverCapabilities, setServerCapabilities] = useState<ServerCapabilities | null>(null);
|
||||
const [connectionStatus, setConnectionStatus] = useState<
|
||||
"disconnected" | "connected" | "error"
|
||||
>("disconnected");
|
||||
const [serverCapabilities, setServerCapabilities] =
|
||||
useState<ServerCapabilities | 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) => {
|
||||
setRequestHistory((prev) => [
|
||||
@@ -61,7 +66,7 @@ export function useConnection({
|
||||
|
||||
const makeRequest = async <T extends z.ZodType>(
|
||||
request: ClientRequest,
|
||||
schema: T
|
||||
schema: T,
|
||||
) => {
|
||||
if (!mcpClient) {
|
||||
throw new Error("MCP client not connected");
|
||||
@@ -80,14 +85,14 @@ export function useConnection({
|
||||
});
|
||||
pushHistory(request, response);
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : String(error);
|
||||
pushHistory(request, { error: errorMessage });
|
||||
throw error;
|
||||
} finally {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
|
||||
|
||||
return response;
|
||||
} catch (e: unknown) {
|
||||
const errorString = (e as Error).message ?? String(e);
|
||||
@@ -140,13 +145,19 @@ export function useConnection({
|
||||
}
|
||||
|
||||
const clientTransport = new SSEClientTransport(backendUrl);
|
||||
|
||||
|
||||
if (onNotification) {
|
||||
client.setNotificationHandler(ProgressNotificationSchema, onNotification);
|
||||
client.setNotificationHandler(
|
||||
ProgressNotificationSchema,
|
||||
onNotification,
|
||||
);
|
||||
}
|
||||
|
||||
if (onStdErrNotification) {
|
||||
client.setNotificationHandler(StdErrNotificationSchema, onStdErrNotification);
|
||||
client.setNotificationHandler(
|
||||
StdErrNotificationSchema,
|
||||
onStdErrNotification,
|
||||
);
|
||||
}
|
||||
|
||||
await client.connect(clientTransport);
|
||||
@@ -183,6 +194,6 @@ export function useConnection({
|
||||
requestHistory,
|
||||
makeRequest,
|
||||
sendNotification,
|
||||
connect
|
||||
connect,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,19 +6,28 @@ export function useDraggablePane(initialHeight: number) {
|
||||
const dragStartY = useRef<number>(0);
|
||||
const dragStartHeight = useRef<number>(0);
|
||||
|
||||
const handleDragStart = useCallback((e: React.MouseEvent) => {
|
||||
setIsDragging(true);
|
||||
dragStartY.current = e.clientY;
|
||||
dragStartHeight.current = height;
|
||||
document.body.style.userSelect = "none";
|
||||
}, [height]);
|
||||
const handleDragStart = useCallback(
|
||||
(e: React.MouseEvent) => {
|
||||
setIsDragging(true);
|
||||
dragStartY.current = e.clientY;
|
||||
dragStartHeight.current = height;
|
||||
document.body.style.userSelect = "none";
|
||||
},
|
||||
[height],
|
||||
);
|
||||
|
||||
const handleDragMove = useCallback((e: MouseEvent) => {
|
||||
if (!isDragging) return;
|
||||
const deltaY = dragStartY.current - e.clientY;
|
||||
const newHeight = Math.max(100, Math.min(800, dragStartHeight.current + deltaY));
|
||||
setHeight(newHeight);
|
||||
}, [isDragging]);
|
||||
const handleDragMove = useCallback(
|
||||
(e: MouseEvent) => {
|
||||
if (!isDragging) return;
|
||||
const deltaY = dragStartY.current - e.clientY;
|
||||
const newHeight = Math.max(
|
||||
100,
|
||||
Math.min(800, dragStartHeight.current + deltaY),
|
||||
);
|
||||
setHeight(newHeight);
|
||||
},
|
||||
[isDragging],
|
||||
);
|
||||
|
||||
const handleDragEnd = useCallback(() => {
|
||||
setIsDragging(false);
|
||||
@@ -39,6 +48,6 @@ export function useDraggablePane(initialHeight: number) {
|
||||
return {
|
||||
height,
|
||||
isDragging,
|
||||
handleDragStart
|
||||
handleDragStart,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { StrictMode } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { ToastContainer } from 'react-toastify';
|
||||
import 'react-toastify/dist/ReactToastify.css';
|
||||
import { ToastContainer } from "react-toastify";
|
||||
import "react-toastify/dist/ReactToastify.css";
|
||||
import App from "./App.tsx";
|
||||
import "./index.css";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user