import type { ReactNode } from 'react' import type { ToolWithProvider } from '@/app/components/workflow/types' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { fireEvent, render, screen, waitFor } from '@testing-library/react' import * as React from 'react' import { beforeEach, describe, expect, it, vi } from 'vitest' import MCPCard from './provider-card' // Mutable mock functions const mockUpdateMCP = vi.fn().mockResolvedValue({ result: 'success' }) const mockDeleteMCP = vi.fn().mockResolvedValue({ result: 'success' }) // Mock the services vi.mock('@/service/use-tools', () => ({ useUpdateMCP: () => ({ mutateAsync: mockUpdateMCP, }), useDeleteMCP: () => ({ mutateAsync: mockDeleteMCP, }), })) // Mock the MCPModal type MCPModalForm = { name: string server_url: string } type MCPModalProps = { show: boolean onConfirm: (form: MCPModalForm) => void onHide: () => void } vi.mock('./modal', () => ({ default: ({ show, onConfirm, onHide }: MCPModalProps) => { if (!show) return null return (
) }, })) // Mock the Confirm dialog type ConfirmDialogProps = { isShow: boolean onConfirm: () => void onCancel: () => void isLoading: boolean } vi.mock('@/app/components/base/confirm', () => ({ default: ({ isShow, onConfirm, onCancel, isLoading }: ConfirmDialogProps) => { if (!isShow) return null return (
) }, })) // Mock the OperationDropdown type OperationDropdownProps = { onEdit: () => void onRemove: () => void onOpenChange: (open: boolean) => void } vi.mock('./detail/operation-dropdown', () => ({ default: ({ onEdit, onRemove, onOpenChange }: OperationDropdownProps) => (
), })) // Mock the app context vi.mock('@/context/app-context', () => ({ useAppContext: () => ({ isCurrentWorkspaceManager: true, isCurrentWorkspaceEditor: true, }), })) // Mock the format time hook vi.mock('@/hooks/use-format-time-from-now', () => ({ useFormatTimeFromNow: () => ({ formatTimeFromNow: (_timestamp: number) => '2 hours ago', }), })) // Mock the plugins service vi.mock('@/service/use-plugins', () => ({ useInstalledPluginList: () => ({ data: { pages: [] }, hasNextPage: false, isFetchingNextPage: false, fetchNextPage: vi.fn(), isLoading: false, isSuccess: true, }), })) // Mock common service vi.mock('@/service/common', () => ({ uploadRemoteFileInfo: vi.fn().mockResolvedValue({ url: 'https://example.com/icon.png' }), })) describe('MCPCard', () => { const createWrapper = () => { const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false, }, }, }) return ({ children }: { children: ReactNode }) => React.createElement(QueryClientProvider, { client: queryClient }, children) } const createMockData = (overrides = {}): ToolWithProvider => ({ id: 'mcp-1', name: 'Test MCP Server', server_identifier: 'test-server', icon: { content: '🔧', background: '#FF0000' }, tools: [ { name: 'tool1', description: 'Tool 1' }, { name: 'tool2', description: 'Tool 2' }, ], is_team_authorization: true, updated_at: Date.now() / 1000, ...overrides, } as unknown as ToolWithProvider) const defaultProps = { data: createMockData(), handleSelect: vi.fn(), onUpdate: vi.fn(), onDeleted: vi.fn(), } beforeEach(() => { mockUpdateMCP.mockClear() mockDeleteMCP.mockClear() mockUpdateMCP.mockResolvedValue({ result: 'success' }) mockDeleteMCP.mockResolvedValue({ result: 'success' }) }) describe('Rendering', () => { it('should render without crashing', () => { render(, { wrapper: createWrapper() }) expect(screen.getByText('Test MCP Server')).toBeInTheDocument() }) it('should display MCP name', () => { render(, { wrapper: createWrapper() }) expect(screen.getByText('Test MCP Server')).toBeInTheDocument() }) it('should display server identifier', () => { render(, { wrapper: createWrapper() }) expect(screen.getByText('test-server')).toBeInTheDocument() }) it('should display tools count', () => { render(, { wrapper: createWrapper() }) // The tools count uses i18n with count parameter expect(screen.getByText(/tools.mcp.toolsCount/)).toBeInTheDocument() }) it('should display update time', () => { render(, { wrapper: createWrapper() }) expect(screen.getByText(/tools.mcp.updateTime/)).toBeInTheDocument() }) }) describe('No Tools State', () => { it('should show no tools message when tools array is empty', () => { const dataWithNoTools = createMockData({ tools: [] }) render( , { wrapper: createWrapper() }, ) expect(screen.getByText('tools.mcp.noTools')).toBeInTheDocument() }) it('should show not configured badge when not authorized', () => { const dataNotAuthorized = createMockData({ is_team_authorization: false }) render( , { wrapper: createWrapper() }, ) expect(screen.getByText('tools.mcp.noConfigured')).toBeInTheDocument() }) it('should show not configured badge when no tools', () => { const dataWithNoTools = createMockData({ tools: [], is_team_authorization: true }) render( , { wrapper: createWrapper() }, ) expect(screen.getByText('tools.mcp.noConfigured')).toBeInTheDocument() }) }) describe('Selected State', () => { it('should apply selected styles when current provider matches', () => { render( , { wrapper: createWrapper() }, ) const card = document.querySelector('[class*="border-components-option-card-option-selected-border"]') expect(card).toBeInTheDocument() }) it('should not apply selected styles when different provider', () => { const differentProvider = createMockData({ id: 'different-id' }) render( , { wrapper: createWrapper() }, ) const card = document.querySelector('[class*="border-components-option-card-option-selected-border"]') expect(card).not.toBeInTheDocument() }) }) describe('User Interactions', () => { it('should call handleSelect when card is clicked', () => { const handleSelect = vi.fn() render( , { wrapper: createWrapper() }, ) const card = screen.getByText('Test MCP Server').closest('[class*="cursor-pointer"]') if (card) { fireEvent.click(card) expect(handleSelect).toHaveBeenCalledWith('mcp-1') } }) }) describe('Card Icon', () => { it('should render card icon', () => { render(, { wrapper: createWrapper() }) // Icon component is rendered const iconContainer = document.querySelector('[class*="rounded-xl"][class*="border"]') expect(iconContainer).toBeInTheDocument() }) }) describe('Status Indicator', () => { it('should show green indicator when authorized and has tools', () => { const data = createMockData({ is_team_authorization: true, tools: [{ name: 'tool1' }] }) render( , { wrapper: createWrapper() }, ) // Should have green indicator (not showing red badge) expect(screen.queryByText('tools.mcp.noConfigured')).not.toBeInTheDocument() }) it('should show red indicator when not configured', () => { const data = createMockData({ is_team_authorization: false }) render( , { wrapper: createWrapper() }, ) expect(screen.getByText('tools.mcp.noConfigured')).toBeInTheDocument() }) }) describe('Edge Cases', () => { it('should handle long MCP name', () => { const longName = 'A'.repeat(100) const data = createMockData({ name: longName }) render( , { wrapper: createWrapper() }, ) expect(screen.getByText(longName)).toBeInTheDocument() }) it('should handle special characters in name', () => { const data = createMockData({ name: 'Test