test: add tests for App component
- 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
This commit is contained in:
213
client/src/__tests__/App.test.tsx
Normal file
213
client/src/__tests__/App.test.tsx
Normal file
@@ -0,0 +1,213 @@
|
||||
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()
|
||||
})
|
||||
})
|
||||
@@ -1,11 +1,34 @@
|
||||
import '@testing-library/jest-dom'
|
||||
import { expect, afterEach } from 'vitest'
|
||||
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)
|
||||
|
||||
// Mock window.matchMedia
|
||||
Object.defineProperty(window, 'matchMedia', {
|
||||
writable: true,
|
||||
value: vi.fn().mockImplementation(query => ({
|
||||
matches: false,
|
||||
media: query,
|
||||
onchange: null,
|
||||
addListener: vi.fn(), // Deprecated
|
||||
removeListener: vi.fn(), // Deprecated
|
||||
addEventListener: vi.fn(),
|
||||
removeEventListener: vi.fn(),
|
||||
dispatchEvent: vi.fn(),
|
||||
})),
|
||||
})
|
||||
|
||||
// Mock window.location.hash
|
||||
Object.defineProperty(window, 'location', {
|
||||
writable: true,
|
||||
value: { hash: '' }
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
cleanup()
|
||||
vi.clearAllMocks()
|
||||
window.location.hash = ''
|
||||
})
|
||||
Reference in New Issue
Block a user