From 586c497740a096a1929b575c8fd98e08cc2360ce Mon Sep 17 00:00:00 2001 From: Ashwin Bhat Date: Fri, 20 Dec 2024 15:45:55 -0800 Subject: [PATCH] style: run prettier on src directory --- client/src/App.tsx | 62 +++-- client/src/__tests__/App.test.tsx | 241 +++++++++--------- .../src/__tests__/components/History.test.tsx | 87 ++++--- .../__tests__/components/ListPane.test.tsx | 116 ++++----- .../src/__tests__/components/PingTab.test.tsx | 84 +++--- .../__tests__/components/PromptsTab.test.tsx | 142 ++++++----- .../components/ResourcesTab.test.tsx | 189 +++++++------- .../__tests__/components/RootsTab.test.tsx | 134 +++++----- .../__tests__/components/SamplingTab.test.tsx | 136 +++++----- client/src/__tests__/setup/setup.ts | 30 +-- client/src/components/Sidebar.tsx | 32 ++- client/src/components/ToolsTab.tsx | 3 +- client/src/lib/hooks/useConnection.ts | 33 ++- client/src/lib/hooks/useDraggablePane.ts | 37 ++- client/src/main.tsx | 4 +- 15 files changed, 715 insertions(+), 615 deletions(-) diff --git a/client/src/App.tsx b/client/src/App.tsx index c225c30..f3791b2 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -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(); 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 ( @@ -345,26 +345,40 @@ const App = () => { {mcpClient ? ( (window.location.hash = value)} > - + Resources - + Prompts - + Tools @@ -388,7 +402,9 @@ const App = () => {
- {!serverCapabilities?.resources && !serverCapabilities?.prompts && !serverCapabilities?.tools ? ( + {!serverCapabilities?.resources && + !serverCapabilities?.prompts && + !serverCapabilities?.tools ? (

The connected server does not support any MCP capabilities diff --git a/client/src/__tests__/App.test.tsx b/client/src/__tests__/App.test.tsx index f215280..1a5d920 100644 --- a/client/src/__tests__/App.test.tsx +++ b/client/src/__tests__/App.test.tsx @@ -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() - }) - expect(screen.getByText('Connect to an MCP server to start inspecting')).toBeInTheDocument() - }) + render(); + }); + 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() - }) - expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/config') - }) + render(); + }); + 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() - }) + render(); + }); // 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() - }) - + render(); + }); + // 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() - }) - + render(); + }); + // 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() - }) - + render(); + }); + // 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() - }) - expect(screen.getByText('The connected server does not support any MCP capabilities')).toBeInTheDocument() - }) -}) \ No newline at end of file + render(); + }); + expect( + screen.getByText( + "The connected server does not support any MCP capabilities", + ), + ).toBeInTheDocument(); + }); +}); diff --git a/client/src/__tests__/components/History.test.tsx b/client/src/__tests__/components/History.test.tsx index 594b73e..7d190bd 100644 --- a/client/src/__tests__/components/History.test.tsx +++ b/client/src/__tests__/components/History.test.tsx @@ -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() - const items = screen.getAllByText(/test[12]/, { exact: false }) - expect(items).toHaveLength(2) - }) + it("renders history items", () => { + render( + , + ); + const items = screen.getAllByText(/test[12]/, { exact: false }); + expect(items).toHaveLength(2); + }); - it('expands history item when clicked', () => { - render() - - 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( + , + ); - 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() - - 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( + , + ); - fireEvent.click(items[0]) - expect(screen.getByText('Details:')).toBeInTheDocument() - expect(screen.getByText(/test data/, { exact: false })).toBeInTheDocument() - }) -}) \ No newline at end of file + 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(); + }); +}); diff --git a/client/src/__tests__/components/ListPane.test.tsx b/client/src/__tests__/components/ListPane.test.tsx index 081cfb1..1ea25b7 100644 --- a/client/src/__tests__/components/ListPane.test.tsx +++ b/client/src/__tests__/components/ListPane.test.tsx @@ -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', () => { ID: {item.id} ), - title: 'Test Items', - buttonText: 'List Items' - } + title: "Test Items", + buttonText: "List Items", + }; - it('renders title and buttons', () => { - render() - expect(screen.getByText('Test Items')).toBeInTheDocument() - expect(screen.getByText('List Items')).toBeInTheDocument() - expect(screen.getByText('Clear')).toBeInTheDocument() - }) + it("renders title and buttons", () => { + render(); + 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() - 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(); + 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() - - fireEvent.click(screen.getByText('List Items')) - expect(listItems).toHaveBeenCalled() - }) + it("calls listItems when List Items button is clicked", () => { + const listItems = vi.fn(); + render(); - it('calls clearItems when Clear button is clicked', () => { - const clearItems = vi.fn() - render() - - 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() - - fireEvent.click(screen.getByText('Item 1')) - expect(setSelectedItem).toHaveBeenCalledWith(mockItems[0]) - }) + it("calls clearItems when Clear button is clicked", () => { + const clearItems = vi.fn(); + render(); - it('disables Clear button when items array is empty', () => { - render() - expect(screen.getByText('Clear')).toBeDisabled() - }) + fireEvent.click(screen.getByText("Clear")); + expect(clearItems).toHaveBeenCalled(); + }); - it('disables List Items button when isButtonDisabled is true', () => { - render() - expect(screen.getByText('List Items')).toBeDisabled() - }) + it("calls setSelectedItem when an item is clicked", () => { + const setSelectedItem = vi.fn(); + render(); - it('enables List Items button when isButtonDisabled is false', () => { - render() - expect(screen.getByText('List Items')).not.toBeDisabled() - }) -}) \ No newline at end of file + fireEvent.click(screen.getByText("Item 1")); + expect(setSelectedItem).toHaveBeenCalledWith(mockItems[0]); + }); + + it("disables Clear button when items array is empty", () => { + render(); + expect(screen.getByText("Clear")).toBeDisabled(); + }); + + it("disables List Items button when isButtonDisabled is true", () => { + render(); + expect(screen.getByText("List Items")).toBeDisabled(); + }); + + it("enables List Items button when isButtonDisabled is false", () => { + render(); + expect(screen.getByText("List Items")).not.toBeDisabled(); + }); +}); diff --git a/client/src/__tests__/components/PingTab.test.tsx b/client/src/__tests__/components/PingTab.test.tsx index 603cb03..75f16df 100644 --- a/client/src/__tests__/components/PingTab.test.tsx +++ b/client/src/__tests__/components/PingTab.test.tsx @@ -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( - - {component} - - ) - } + return render({component}); + }; - it('renders the MEGA PING button', () => { - renderWithTabs( {}} />) - 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( {}} />); + 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( {}} />) - expect(screen.getByText('🚀')).toBeInTheDocument() - expect(screen.getByText('💥')).toBeInTheDocument() - }) + it("includes rocket and explosion emojis", () => { + renderWithTabs( {}} />); + expect(screen.getByText("🚀")).toBeInTheDocument(); + expect(screen.getByText("💥")).toBeInTheDocument(); + }); - it('calls onPingClick when button is clicked', () => { - const onPingClick = vi.fn() - renderWithTabs() - - 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(); - it('has animation classes for visual feedback', () => { - renderWithTabs( {}} />) - 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( {}} />) - const button = screen.getByRole('button', { name: /mega ping/i }) - expect(button).toHaveClass('focus:outline-none', 'focus:ring-4') - }) -}) \ No newline at end of file + it("has animation classes for visual feedback", () => { + renderWithTabs( {}} />); + 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( {}} />); + const button = screen.getByRole("button", { name: /mega ping/i }); + expect(button).toHaveClass("focus:outline-none", "focus:ring-4"); + }); +}); diff --git a/client/src/__tests__/components/PromptsTab.test.tsx b/client/src/__tests__/components/PromptsTab.test.tsx index 62f69ef..73aaa87 100644 --- a/client/src/__tests__/components/PromptsTab.test.tsx +++ b/client/src/__tests__/components/PromptsTab.test.tsx @@ -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( - - {component} - - ) - } + return render({component}); + }; - it('renders list of prompts', () => { - renderWithTabs() - expect(screen.getByText('test-prompt-1')).toBeInTheDocument() - expect(screen.getByText('test-prompt-2')).toBeInTheDocument() - }) + it("renders list of prompts", () => { + renderWithTabs(); + 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() - 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() - - 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(); + 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() - 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() - expect(screen.getByText('Test prompt content')).toBeInTheDocument() - }) -}) \ No newline at end of file + getPrompt, + }; + renderWithTabs(); + + 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(); + expect(screen.getByText("Test error message")).toBeInTheDocument(); + }); + + it("shows prompt content when provided", () => { + const props = { + ...defaultProps, + selectedPrompt: mockPrompts[0], + promptContent: "Test prompt content", + }; + renderWithTabs(); + expect(screen.getByText("Test prompt content")).toBeInTheDocument(); + }); +}); diff --git a/client/src/__tests__/components/ResourcesTab.test.tsx b/client/src/__tests__/components/ResourcesTab.test.tsx index df88bd3..c0e73a6 100644 --- a/client/src/__tests__/components/ResourcesTab.test.tsx +++ b/client/src/__tests__/components/ResourcesTab.test.tsx @@ -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( - - {component} - - ) - } + return render({component}); + }; - it('renders resources list', () => { - renderWithTabs() - expect(screen.getByText('Test 1')).toBeInTheDocument() - expect(screen.getByText('Test 2')).toBeInTheDocument() - }) + it("renders resources list", () => { + renderWithTabs(); + expect(screen.getByText("Test 1")).toBeInTheDocument(); + expect(screen.getByText("Test 2")).toBeInTheDocument(); + }); - it('renders templates list', () => { - renderWithTabs() - expect(screen.getByText('Template 1')).toBeInTheDocument() - expect(screen.getByText('Template 2')).toBeInTheDocument() - }) + it("renders templates list", () => { + renderWithTabs(); + 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() - expect(screen.getByText('Test content')).toBeInTheDocument() - }) + resourceContent: "Test content", + }; + renderWithTabs(); + expect(screen.getByText("Test content")).toBeInTheDocument(); + }); - it('shows template form when template is selected', () => { - renderWithTabs() - - 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(); - 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( - - ) - - // 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() - expect(screen.getByText('Test error message')).toBeInTheDocument() - }) + error: "Test error message", + }; + renderWithTabs(); + 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() - - fireEvent.click(screen.getByText('Refresh')) - expect(readResource).toHaveBeenCalledWith(mockResources[0].uri) - }) -}) \ No newline at end of file + readResource, + }; + renderWithTabs(); + + fireEvent.click(screen.getByText("Refresh")); + expect(readResource).toHaveBeenCalledWith(mockResources[0].uri); + }); +}); diff --git a/client/src/__tests__/components/RootsTab.test.tsx b/client/src/__tests__/components/RootsTab.test.tsx index ab5cd79..b8186be 100644 --- a/client/src/__tests__/components/RootsTab.test.tsx +++ b/client/src/__tests__/components/RootsTab.test.tsx @@ -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( - - {component} - - ) - } + return render({component}); + }; - it('renders list of roots', () => { - renderWithTabs() - expect(screen.getByDisplayValue('file:///test/path1')).toBeInTheDocument() - expect(screen.getByDisplayValue('file:///test/path2')).toBeInTheDocument() - }) + it("renders list of roots", () => { + renderWithTabs(); + 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() - - 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(); - it('removes a root when remove button is clicked', () => { - const setRoots = vi.fn() - renderWithTabs() - - 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() - - 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() - - fireEvent.click(screen.getByText('Save Changes')) - - expect(onRootsChange).toHaveBeenCalled() - }) -}) \ No newline at end of file + it("removes a root when remove button is clicked", () => { + const setRoots = vi.fn(); + renderWithTabs(); + + 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(); + + 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( + , + ); + + fireEvent.click(screen.getByText("Save Changes")); + + expect(onRootsChange).toHaveBeenCalled(); + }); +}); diff --git a/client/src/__tests__/components/SamplingTab.test.tsx b/client/src/__tests__/components/SamplingTab.test.tsx index 28c0e89..d84186e 100644 --- a/client/src/__tests__/components/SamplingTab.test.tsx +++ b/client/src/__tests__/components/SamplingTab.test.tsx @@ -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( - - {component} - - ) - } + return render({component}); + }; - it('renders empty state when no requests', () => { - renderWithTabs() - expect(screen.getByText('No pending requests')).toBeInTheDocument() - }) + it("renders empty state when no requests", () => { + renderWithTabs(); + expect(screen.getByText("No pending requests")).toBeInTheDocument(); + }); - it('renders list of pending requests', () => { - renderWithTabs() - expect(screen.getByText(/Test message/)).toBeInTheDocument() - expect(screen.getByText(/Another test/)).toBeInTheDocument() - }) + it("renders list of pending requests", () => { + renderWithTabs(); + expect(screen.getByText(/Test message/)).toBeInTheDocument(); + expect(screen.getByText(/Another test/)).toBeInTheDocument(); + }); - it('shows request details in JSON format', () => { - renderWithTabs() - const requestJson = screen.getAllByText((content) => content.includes('"model": "test-model"')) - expect(requestJson).toHaveLength(2) - }) + it("shows request details in JSON format", () => { + renderWithTabs(); + 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(); + + const approveButtons = screen.getAllByText("Approve"); + fireEvent.click(approveButtons[0]); - it('calls onApprove with stub response when Approve is clicked', () => { - const onApprove = vi.fn() - renderWithTabs() - - 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() - - 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(); - it('shows informational alert about sampling requests', () => { - renderWithTabs() - expect(screen.getByText(/When the server requests LLM sampling/)).toBeInTheDocument() - }) -}) \ No newline at end of file + const rejectButtons = screen.getAllByText("Reject"); + fireEvent.click(rejectButtons[0]); + + expect(onReject).toHaveBeenCalledWith(1); + }); + + it("shows informational alert about sampling requests", () => { + renderWithTabs(); + expect( + screen.getByText(/When the server requests LLM sampling/), + ).toBeInTheDocument(); + }); +}); diff --git a/client/src/__tests__/setup/setup.ts b/client/src/__tests__/setup/setup.ts index dcba7c2..e579423 100644 --- a/client/src/__tests__/setup/setup.ts +++ b/client/src/__tests__/setup/setup.ts @@ -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 = '' -}) \ No newline at end of file + cleanup(); + vi.clearAllMocks(); + window.location.hash = ""; +}); diff --git a/client/src/components/Sidebar.tsx b/client/src/components/Sidebar.tsx index c716bd2..17cd59d 100644 --- a/client/src/components/Sidebar.tsx +++ b/client/src/components/Sidebar.tsx @@ -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 = ({

- + - + - - diff --git a/client/src/components/ToolsTab.tsx b/client/src/components/ToolsTab.tsx index 77a97e7..6b64c01 100644 --- a/client/src/components/ToolsTab.tsx +++ b/client/src/components/ToolsTab.tsx @@ -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" ? (