test: add comprehensive MCP UI test coverage with MSW infrastructure

## MSW Infrastructure Enhancement
- Add 5 MCP API handlers to tests/msw/handlers.ts:
  - get_mcp_config: Fetch MCP configuration for app type
  - import_mcp_from_claude/codex: Mock import operations (returns count: 1)
  - set_mcp_enabled: Toggle MCP server enabled state
  - upsert_mcp_server_in_config: Create/update MCP server
  - delete_mcp_server_in_config: Remove MCP server
- Add MCP state management to tests/msw/state.ts:
  - McpConfigState type with per-app server storage
  - Default test data (stdio server for Claude, http server for Codex)
  - CRUD functions: getMcpConfig, setMcpServerEnabled, upsertMcpServer, deleteMcpServer
  - Immutable state operations with deep cloning

## McpFormModal Component Tests (4 tests)
- Test preset application: Verify ID and config JSON auto-fill from preset selection
- Test conflict detection: Async validation shows warning when syncing to conflicting ID
- Test field sanitization: Verify trim whitespace, split tags, clean URLs before save
- Test validation errors: Block submit and show toast error for invalid stdio config (missing command)

## McpPanel Integration Tests (3 tests)
- Test toggle enabled state: Click toggle button triggers useMcpActions.toggleEnabled with correct params
- Test create server flow: Open form → submit → saveServer called with syncOtherSide option
- Test delete server flow: Click delete → confirm dialog → deleteServer called with ID

## Test Utilities
- Add createTestQueryClient helper with retry: false for faster test execution

## Test Coverage
- Test files: 15 → 17 (+2)
- Total tests: 105 → 112 (+6.7%)
- All 112 tests passing
- Execution time: 3.15s
This commit is contained in:
Jason
2025-10-26 13:52:42 +08:00
parent c8c4656e0e
commit c3f712bc18
5 changed files with 602 additions and 2 deletions

View File

@@ -1,8 +1,9 @@
import type { AppType } from "@/lib/api/types";
import type { Provider, Settings } from "@/types";
import type { McpServer, Provider, Settings } from "@/types";
type ProvidersByApp = Record<AppType, Record<string, Provider>>;
type CurrentProviderState = Record<AppType, string>;
type McpConfigState = Record<AppType, Record<string, McpServer>>;
const createDefaultProviders = (): ProvidersByApp => ({
claude: {
@@ -59,6 +60,30 @@ let settingsState: Settings = {
language: "zh",
};
let appConfigDirOverride: string | null = null;
let mcpConfigs: McpConfigState = {
claude: {
sample: {
id: "sample",
name: "Sample Claude Server",
enabled: true,
server: {
type: "stdio",
command: "claude-server",
},
},
},
codex: {
httpServer: {
id: "httpServer",
name: "HTTP Codex Server",
enabled: false,
server: {
type: "http",
url: "http://localhost:3000",
},
},
},
};
const cloneProviders = (value: ProvidersByApp) =>
JSON.parse(JSON.stringify(value)) as ProvidersByApp;
@@ -75,6 +100,30 @@ export const resetProviderState = () => {
language: "zh",
};
appConfigDirOverride = null;
mcpConfigs = {
claude: {
sample: {
id: "sample",
name: "Sample Claude Server",
enabled: true,
server: {
type: "stdio",
command: "claude-server",
},
},
},
codex: {
httpServer: {
id: "httpServer",
name: "HTTP Codex Server",
enabled: false,
server: {
type: "http",
url: "http://localhost:3000",
},
},
},
};
};
export const getProviders = (appType: AppType) =>
@@ -143,3 +192,45 @@ export const getAppConfigDirOverride = () => appConfigDirOverride;
export const setAppConfigDirOverrideState = (value: string | null) => {
appConfigDirOverride = value;
};
export const getMcpConfig = (appType: AppType) => {
const servers = JSON.parse(
JSON.stringify(mcpConfigs[appType] ?? {}),
) as Record<string, McpServer>;
return {
configPath: `/mock/${appType}.mcp.json`,
servers,
};
};
export const setMcpConfig = (appType: AppType, value: Record<string, McpServer>) => {
mcpConfigs[appType] = JSON.parse(JSON.stringify(value)) as Record<string, McpServer>;
};
export const setMcpServerEnabled = (
appType: AppType,
id: string,
enabled: boolean,
) => {
if (!mcpConfigs[appType]?.[id]) return;
mcpConfigs[appType][id] = {
...mcpConfigs[appType][id],
enabled,
};
};
export const upsertMcpServer = (
appType: AppType,
id: string,
server: McpServer,
) => {
if (!mcpConfigs[appType]) {
mcpConfigs[appType] = {};
}
mcpConfigs[appType][id] = JSON.parse(JSON.stringify(server)) as McpServer;
};
export const deleteMcpServer = (appType: AppType, id: string) => {
if (!mcpConfigs[appType]) return;
delete mcpConfigs[appType][id];
};