- Add comprehensive tests for App.tsx - Fix test setup to handle window.matchMedia and URL params - Wrap state updates in act() - Use more specific selectors for finding elements - Fix sampling tab test to properly simulate pending requests
213 lines
6.0 KiB
TypeScript
213 lines
6.0 KiB
TypeScript
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)
|
|
|
|
// Mock the hooks
|
|
vi.mock('../lib/hooks/useConnection', () => ({
|
|
useConnection: vi.fn()
|
|
}))
|
|
|
|
vi.mock('../lib/hooks/useDraggablePane', () => ({
|
|
useDraggablePane: vi.fn()
|
|
}))
|
|
|
|
// Mock fetch for config
|
|
const mockFetch = vi.fn()
|
|
global.fetch = mockFetch
|
|
|
|
describe('App', () => {
|
|
beforeEach(() => {
|
|
// Reset all mocks
|
|
vi.clearAllMocks()
|
|
|
|
// Mock URL params
|
|
mockURLSearchParams.mockReturnValue({
|
|
get: () => '3000'
|
|
})
|
|
|
|
// Mock fetch response
|
|
mockFetch.mockResolvedValue({
|
|
json: () => Promise.resolve({
|
|
defaultEnvironment: {},
|
|
defaultCommand: 'test-command',
|
|
defaultArgs: '--test'
|
|
})
|
|
})
|
|
|
|
// Mock useConnection hook
|
|
const mockUseConnection = useConnection as jest.Mock
|
|
mockUseConnection.mockReturnValue({
|
|
connectionStatus: 'disconnected',
|
|
serverCapabilities: null,
|
|
mcpClient: null,
|
|
requestHistory: [],
|
|
makeRequest: vi.fn(),
|
|
sendNotification: vi.fn(),
|
|
connect: vi.fn()
|
|
})
|
|
|
|
// Mock useDraggablePane hook
|
|
const mockUseDraggablePane = useDraggablePane as jest.Mock
|
|
mockUseDraggablePane.mockReturnValue({
|
|
height: 300,
|
|
handleDragStart: vi.fn()
|
|
})
|
|
})
|
|
|
|
it('renders initial disconnected state', async () => {
|
|
await act(async () => {
|
|
render(<App />)
|
|
})
|
|
expect(screen.getByText('Connect to an MCP server to start inspecting')).toBeInTheDocument()
|
|
})
|
|
|
|
it('loads config on mount', async () => {
|
|
await act(async () => {
|
|
render(<App />)
|
|
})
|
|
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/config')
|
|
})
|
|
|
|
it('shows connected interface when mcpClient is available', async () => {
|
|
const mockUseConnection = useConnection as jest.Mock
|
|
mockUseConnection.mockReturnValue({
|
|
connectionStatus: 'connected',
|
|
serverCapabilities: {
|
|
resources: true,
|
|
prompts: true,
|
|
tools: true
|
|
},
|
|
mcpClient: {},
|
|
requestHistory: [],
|
|
makeRequest: vi.fn(),
|
|
sendNotification: vi.fn(),
|
|
connect: vi.fn()
|
|
})
|
|
|
|
await act(async () => {
|
|
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 })
|
|
|
|
expect(resourcesTab).toBeInTheDocument()
|
|
expect(promptsTab).toBeInTheDocument()
|
|
expect(toolsTab).toBeInTheDocument()
|
|
})
|
|
|
|
it('disables tabs based on server capabilities', async () => {
|
|
const mockUseConnection = useConnection as jest.Mock
|
|
mockUseConnection.mockReturnValue({
|
|
connectionStatus: 'connected',
|
|
serverCapabilities: {
|
|
resources: false,
|
|
prompts: true,
|
|
tools: false
|
|
},
|
|
mcpClient: {},
|
|
requestHistory: [],
|
|
makeRequest: vi.fn(),
|
|
sendNotification: vi.fn(),
|
|
connect: vi.fn()
|
|
})
|
|
|
|
await act(async () => {
|
|
render(<App />)
|
|
})
|
|
|
|
// Resources tab should be 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')
|
|
|
|
// Tools tab should be 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
|
|
mockUseConnection.mockReturnValue({
|
|
connectionStatus: 'connected',
|
|
serverCapabilities: { sampling: true },
|
|
mcpClient: {},
|
|
requestHistory: [],
|
|
makeRequest: vi.fn(),
|
|
sendNotification: vi.fn(),
|
|
connect: vi.fn(),
|
|
onPendingRequest: (request, resolve, reject) => {
|
|
// Simulate a pending request
|
|
setPendingSampleRequests(prev => [
|
|
...prev,
|
|
{ id: 1, request, resolve, reject }
|
|
])
|
|
}
|
|
})
|
|
|
|
await act(async () => {
|
|
render(<App />)
|
|
})
|
|
|
|
// Initially no notification count
|
|
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: {} },
|
|
() => {},
|
|
() => {}
|
|
)
|
|
})
|
|
|
|
// 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')
|
|
|
|
await act(async () => {
|
|
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')
|
|
})
|
|
|
|
it('shows error message when server has no capabilities', async () => {
|
|
const mockUseConnection = useConnection as jest.Mock
|
|
mockUseConnection.mockReturnValue({
|
|
connectionStatus: 'connected',
|
|
serverCapabilities: {},
|
|
mcpClient: {},
|
|
requestHistory: [],
|
|
makeRequest: vi.fn(),
|
|
sendNotification: vi.fn(),
|
|
connect: vi.fn()
|
|
})
|
|
|
|
await act(async () => {
|
|
render(<App />)
|
|
})
|
|
expect(screen.getByText('The connected server does not support any MCP capabilities')).toBeInTheDocument()
|
|
})
|
|
}) |