From c3f712bc18743acf290ec88d1ba745a17e03a8e5 Mon Sep 17 00:00:00 2001 From: Jason Date: Sun, 26 Oct 2025 13:52:42 +0800 Subject: [PATCH] test: add comprehensive MCP UI test coverage with MSW infrastructure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 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 --- tests/components/McpFormModal.test.tsx | 227 ++++++++++++++++++++++++ tests/integration/McpPanel.test.tsx | 231 +++++++++++++++++++++++++ tests/msw/handlers.ts | 42 ++++- tests/msw/state.ts | 93 +++++++++- tests/utils/testQueryClient.ts | 11 ++ 5 files changed, 602 insertions(+), 2 deletions(-) create mode 100644 tests/components/McpFormModal.test.tsx create mode 100644 tests/integration/McpPanel.test.tsx create mode 100644 tests/utils/testQueryClient.ts diff --git a/tests/components/McpFormModal.test.tsx b/tests/components/McpFormModal.test.tsx new file mode 100644 index 0000000..d27c47b --- /dev/null +++ b/tests/components/McpFormModal.test.tsx @@ -0,0 +1,227 @@ +import React from "react"; +import { render, screen, fireEvent, waitFor } from "@testing-library/react"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import McpFormModal from "@/components/mcp/McpFormModal"; + +const toastErrorMock = vi.hoisted(() => vi.fn()); +const toastSuccessMock = vi.hoisted(() => vi.fn()); +const getConfigMock = vi.hoisted(() => vi.fn().mockResolvedValue({ servers: {} })); + +vi.mock("sonner", () => ({ + toast: { + error: (...args: unknown[]) => toastErrorMock(...args), + success: (...args: unknown[]) => toastSuccessMock(...args), + }, +})); + +vi.mock("react-i18next", () => ({ + useTranslation: () => ({ + t: (key: string, params?: Record) => + params ? `${key}:${JSON.stringify(params)}` : key, + }), +})); + +vi.mock("@/config/mcpPresets", () => ({ + mcpPresets: [ + { + id: "preset-stdio", + server: { type: "stdio", command: "preset-cmd" }, + }, + ], + getMcpPresetWithDescription: (preset: any) => ({ + ...preset, + description: "Preset description", + tags: ["preset"], + }), +})); + +vi.mock("@/components/ui/button", () => ({ + Button: ({ children, onClick, type = "button", ...rest }: any) => ( + + ), +})); + +vi.mock("@/components/ui/input", () => ({ + Input: ({ value, onChange, ...rest }: any) => ( + onChange?.({ target: { value: event.target.value } })} + {...rest} + /> + ), +})); + +vi.mock("@/components/ui/textarea", () => ({ + Textarea: ({ value, onChange, ...rest }: any) => ( +