Skip to main content
Glama
TemplateManagement.test.jsx20.2 kB
import { describe, it, expect, vi, beforeEach } from 'vitest'; import { render, screen, fireEvent, waitFor, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import TemplateManagement from '../components/TemplateManagement'; describe('TemplateManagement Component', () => { const mockTemplates = [ { functionName: 'planTask', description: 'default template from built-in', status: 'default', source: 'built-in', contentLength: 4859, category: 'Task Management' }, { functionName: 'executeTask', description: 'custom template from user-custom', status: 'custom', source: 'user-custom', contentLength: 2400, category: 'Task Management' }, { functionName: 'analyzeTask', description: 'env-override template from environment', status: 'env-override', source: 'environment', contentLength: 3200, category: 'Task Management' }, { functionName: 'verifyTask', description: 'env-append template from environment+built-in', status: 'env-append', source: 'environment+built-in', contentLength: 1800, category: 'Task Management' } ]; const mockHandlers = { onGlobalFilterChange: vi.fn(), onEditTemplate: vi.fn(), onResetTemplate: vi.fn(), onDuplicateTemplate: vi.fn(), onPreviewTemplate: vi.fn(), onExportTemplates: vi.fn() }; beforeEach(() => { vi.clearAllMocks(); // Mock clipboard API Object.assign(navigator, { clipboard: { writeText: vi.fn(), }, }); // Mock URL.createObjectURL global.URL.createObjectURL = vi.fn(); global.URL.revokeObjectURL = vi.fn(); }); describe('Rendering', () => { it('renders all table columns correctly', () => { render( <TemplateManagement data={mockTemplates} globalFilter="" {...mockHandlers} /> ); // Check headers expect(screen.getByText('Function')).toBeInTheDocument(); expect(screen.getByText('Description')).toBeInTheDocument(); expect(screen.getByText('Status')).toBeInTheDocument(); expect(screen.getByText('Language')).toBeInTheDocument(); expect(screen.getByText('Actions')).toBeInTheDocument(); }); it('renders template data correctly', () => { render( <TemplateManagement data={mockTemplates} globalFilter="" {...mockHandlers} /> ); // Check template function names expect(screen.getByText('planTask')).toBeInTheDocument(); expect(screen.getByText('executeTask')).toBeInTheDocument(); expect(screen.getByText('analyzeTask')).toBeInTheDocument(); expect(screen.getByText('verifyTask')).toBeInTheDocument(); // Check categories expect(screen.getAllByText('Task Management')).toHaveLength(4); }); it('displays template statuses with correct styling', () => { render( <TemplateManagement data={mockTemplates} globalFilter="" {...mockHandlers} /> ); const defaultBadge = screen.getByText('Default'); const customBadge = screen.getByText('Custom'); expect(defaultBadge).toHaveClass('status-badge', 'status-default'); expect(customBadge).toHaveClass('status-badge', 'status-custom'); }); it('renders template header with title and export button', () => { render( <TemplateManagement data={mockTemplates} globalFilter="" {...mockHandlers} /> ); expect(screen.getByText('🎨 Template Management')).toBeInTheDocument(); expect(screen.getByText(/Manage prompt templates for all task manager functions/)).toBeInTheDocument(); expect(screen.getByText('📤 Export Templates')).toBeInTheDocument(); }); it('handles empty data gracefully', () => { render( <TemplateManagement data={[]} globalFilter="" {...mockHandlers} /> ); expect(screen.getByText('No templates found')).toBeInTheDocument(); }); it('shows loading state', () => { render( <TemplateManagement data={[]} globalFilter="" loading={true} {...mockHandlers} /> ); expect(screen.getByText('Loading templates... ⏳')).toBeInTheDocument(); }); it('shows error state', () => { const errorMessage = 'Failed to load templates'; render( <TemplateManagement data={[]} globalFilter="" error={errorMessage} {...mockHandlers} /> ); expect(screen.getByText(errorMessage)).toBeInTheDocument(); }); }); describe('Action Buttons', () => { it('renders all action buttons for each template', () => { render( <TemplateManagement data={mockTemplates} globalFilter="" {...mockHandlers} /> ); // Each template should have 4 action buttons const editButtons = screen.getAllByTitle('Edit template'); const previewButtons = screen.getAllByTitle('Preview template'); const duplicateButtons = screen.getAllByTitle('Duplicate template'); const resetButtons = screen.getAllByTitle('Reset to default template'); expect(editButtons).toHaveLength(4); expect(previewButtons).toHaveLength(4); expect(duplicateButtons).toHaveLength(4); expect(resetButtons).toHaveLength(4); }); it('calls onEditTemplate when edit button is clicked', async () => { render( <TemplateManagement data={mockTemplates} globalFilter="" {...mockHandlers} /> ); const editButtons = screen.getAllByTitle('Edit template'); fireEvent.click(editButtons[0]); expect(mockHandlers.onEditTemplate).toHaveBeenCalledWith(mockTemplates[0]); }); it('calls onPreviewTemplate when preview button is clicked', async () => { render( <TemplateManagement data={mockTemplates} globalFilter="" {...mockHandlers} /> ); const previewButtons = screen.getAllByTitle('Preview template'); fireEvent.click(previewButtons[1]); expect(mockHandlers.onPreviewTemplate).toHaveBeenCalledWith(mockTemplates[1]); }); it('calls onDuplicateTemplate when duplicate button is clicked', async () => { render( <TemplateManagement data={mockTemplates} globalFilter="" {...mockHandlers} /> ); const duplicateButtons = screen.getAllByTitle('Duplicate template'); fireEvent.click(duplicateButtons[2]); expect(mockHandlers.onDuplicateTemplate).toHaveBeenCalledWith(mockTemplates[2]); }); it('shows confirmation and calls onResetTemplate when reset button is clicked', async () => { // Mock window.confirm to return true global.confirm = vi.fn(() => true); render( <TemplateManagement data={mockTemplates} globalFilter="" {...mockHandlers} /> ); const resetButtons = screen.getAllByTitle('Reset to default template'); fireEvent.click(resetButtons[1]); // Custom template expect(global.confirm).toHaveBeenCalledWith('Are you sure you want to reset "executeTask" template to default?'); expect(mockHandlers.onResetTemplate).toHaveBeenCalledWith(mockTemplates[1]); }); it('does not call onResetTemplate when confirmation is cancelled', async () => { // Mock window.confirm to return false global.confirm = vi.fn(() => false); render( <TemplateManagement data={mockTemplates} globalFilter="" {...mockHandlers} /> ); const resetButtons = screen.getAllByTitle('Reset to default template'); fireEvent.click(resetButtons[1]); expect(global.confirm).toHaveBeenCalled(); expect(mockHandlers.onResetTemplate).not.toHaveBeenCalled(); }); it('disables reset button for default templates', () => { render( <TemplateManagement data={mockTemplates} globalFilter="" {...mockHandlers} /> ); const resetButtons = screen.getAllByTitle('Reset to default template'); const defaultTemplateResetButton = resetButtons[0]; // planTask is default expect(defaultTemplateResetButton).toBeDisabled(); }); }); describe('Filtering', () => { it('filters templates based on global filter', () => { render( <TemplateManagement data={mockTemplates} globalFilter="planTask" {...mockHandlers} /> ); expect(screen.getByText('planTask')).toBeInTheDocument(); expect(screen.queryByText('executeTask')).not.toBeInTheDocument(); expect(screen.queryByText('analyzeTask')).not.toBeInTheDocument(); }); it('shows no results message when filter matches nothing', () => { render( <TemplateManagement data={mockTemplates} globalFilter="nonexistent" {...mockHandlers} /> ); expect(screen.getByText('No templates found')).toBeInTheDocument(); }); it('filter is case insensitive', () => { render( <TemplateManagement data={mockTemplates} globalFilter="PLANTASK" {...mockHandlers} /> ); expect(screen.getByText('planTask')).toBeInTheDocument(); }); it('filters by status', () => { render( <TemplateManagement data={mockTemplates} globalFilter="custom" {...mockHandlers} /> ); expect(screen.getByText('executeTask')).toBeInTheDocument(); expect(screen.queryByText('planTask')).not.toBeInTheDocument(); }); }); describe('Sorting', () => { it('sorts by column when header clicked', async () => { render( <TemplateManagement data={mockTemplates} globalFilter="" {...mockHandlers} /> ); const functionHeader = screen.getByText('Function'); // Click to sort ascending fireEvent.click(functionHeader); await waitFor(() => { const rows = screen.getAllByRole('row'); expect(within(rows[1]).getByText('analyzeTask')).toBeInTheDocument(); }); // Click again to sort descending fireEvent.click(functionHeader); await waitFor(() => { const rows = screen.getAllByRole('row'); expect(within(rows[1]).getByText('verifyTask')).toBeInTheDocument(); }); }); it('sorts by status correctly', async () => { render( <TemplateManagement data={mockTemplates} globalFilter="" {...mockHandlers} /> ); const statusHeader = screen.getByText('Status'); fireEvent.click(statusHeader); await waitFor(() => { const statusBadges = screen.getAllByText(/Custom|Default/); // Check that sorting is applied expect(statusBadges.length).toBeGreaterThan(0); }); }); }); describe('Pagination', () => { const manyTemplates = Array.from({ length: 25 }, (_, i) => ({ functionName: `template${i}`, description: `Template ${i} description`, status: i % 2 === 0 ? 'default' : 'custom', source: 'built-in', contentLength: 1000 + i, category: 'Task Management' })); it('displays pagination controls when data exceeds page size', () => { render( <TemplateManagement data={manyTemplates} globalFilter="" {...mockHandlers} /> ); expect(screen.getByText(/Showing \d+ to \d+ of \d+ templates/)).toBeInTheDocument(); expect(screen.getByText('<<')).toBeInTheDocument(); expect(screen.getByText('<')).toBeInTheDocument(); expect(screen.getByText('>')).toBeInTheDocument(); expect(screen.getByText('>>')).toBeInTheDocument(); }); it('navigates between pages correctly', async () => { render( <TemplateManagement data={manyTemplates} globalFilter="" {...mockHandlers} /> ); // Initially on page 1 expect(screen.getByText('template0')).toBeInTheDocument(); expect(screen.queryByText('template15')).not.toBeInTheDocument(); // Go to next page const nextButton = screen.getByText('>'); fireEvent.click(nextButton); await waitFor(() => { expect(screen.queryByText('template0')).not.toBeInTheDocument(); expect(screen.getByText('template15')).toBeInTheDocument(); }); }); it('disables navigation buttons appropriately', () => { render( <TemplateManagement data={manyTemplates} globalFilter="" {...mockHandlers} /> ); // On first page, previous buttons should be disabled expect(screen.getByText('<<')).toBeDisabled(); expect(screen.getByText('<')).toBeDisabled(); expect(screen.getByText('>')).not.toBeDisabled(); expect(screen.getByText('>>')).not.toBeDisabled(); }); }); describe('Export Functionality', () => { beforeEach(() => { mockHandlers.onExportTemplates.mockResolvedValue('# Mock export content\nMCP_PROMPT_PLAN_TASK="content"'); }); it('opens export modal when export button is clicked', async () => { render( <TemplateManagement data={mockTemplates} globalFilter="" {...mockHandlers} /> ); const exportButton = screen.getByText('📤 Export Templates'); fireEvent.click(exportButton); await waitFor(() => { expect(screen.getByText('Export Template Configurations')).toBeInTheDocument(); expect(screen.getByText('.env file (Environment Variables)')).toBeInTheDocument(); expect(screen.getByText('mcp.json (MCP Configuration)')).toBeInTheDocument(); }); expect(mockHandlers.onExportTemplates).toHaveBeenCalledWith('env', true, true); }); it('closes export modal when close button is clicked', async () => { render( <TemplateManagement data={mockTemplates} globalFilter="" {...mockHandlers} /> ); const exportButton = screen.getByText('📤 Export Templates'); fireEvent.click(exportButton); await waitFor(() => { expect(screen.getByText('Export Template Configurations')).toBeInTheDocument(); }); const closeButton = screen.getByText('×'); fireEvent.click(closeButton); await waitFor(() => { expect(screen.queryByText('Export Template Configurations')).not.toBeInTheDocument(); }); }); it('changes export format when radio button is selected', async () => { render( <TemplateManagement data={mockTemplates} globalFilter="" {...mockHandlers} /> ); const exportButton = screen.getByText('📤 Export Templates'); fireEvent.click(exportButton); await waitFor(() => { expect(screen.getByText('Export Template Configurations')).toBeInTheDocument(); }); const mcpJsonRadio = screen.getByLabelText('mcp.json (MCP Configuration)'); fireEvent.click(mcpJsonRadio); await waitFor(() => { expect(mockHandlers.onExportTemplates).toHaveBeenCalledWith('mcp.json', true, true); }); }); it('toggles custom only checkbox', async () => { render( <TemplateManagement data={mockTemplates} globalFilter="" {...mockHandlers} /> ); const exportButton = screen.getByText('📤 Export Templates'); fireEvent.click(exportButton); await waitFor(() => { expect(screen.getByText('Export Template Configurations')).toBeInTheDocument(); }); const customOnlyCheckbox = screen.getByLabelText('Export only modified templates (recommended)'); fireEvent.click(customOnlyCheckbox); await waitFor(() => { expect(mockHandlers.onExportTemplates).toHaveBeenCalledWith('env', false, true); }); }); it('copies preview content to clipboard', async () => { render( <TemplateManagement data={mockTemplates} globalFilter="" {...mockHandlers} /> ); const exportButton = screen.getByText('📤 Export Templates'); fireEvent.click(exportButton); await waitFor(() => { expect(screen.getByText('Export Template Configurations')).toBeInTheDocument(); }); const copyButton = screen.getByText('📋 Copy'); fireEvent.click(copyButton); expect(navigator.clipboard.writeText).toHaveBeenCalledWith('# Mock export content\nMCP_PROMPT_PLAN_TASK="content"'); }); it('downloads export when download button is clicked', async () => { render( <TemplateManagement data={mockTemplates} globalFilter="" {...mockHandlers} /> ); const exportButton = screen.getByText('📤 Export Templates'); fireEvent.click(exportButton); await waitFor(() => { expect(screen.getByText('Export Template Configurations')).toBeInTheDocument(); }); const downloadButton = screen.getByText('💾 Download'); fireEvent.click(downloadButton); expect(mockHandlers.onExportTemplates).toHaveBeenCalledWith('env', true, false); }); it('disables export button when loading or no data', () => { render( <TemplateManagement data={[]} globalFilter="" loading={true} {...mockHandlers} /> ); const exportButton = screen.getByText('📤 Export Templates'); expect(exportButton).toBeDisabled(); }); }); describe('Edge Cases', () => { it('handles templates with missing fields', () => { const incompleteTemplate = { functionName: 'incompleteTemplate', // Missing other fields }; render( <TemplateManagement data={[incompleteTemplate]} globalFilter="" {...mockHandlers} /> ); expect(screen.getByText('incompleteTemplate')).toBeInTheDocument(); }); it('handles very large datasets efficiently', () => { const largeTemplates = Array.from({ length: 1000 }, (_, i) => ({ functionName: `template${i}`, description: `Template ${i}`, status: 'default', source: 'built-in', contentLength: 1000, category: 'Task Management' })); const { container } = render( <TemplateManagement data={largeTemplates} globalFilter="" {...mockHandlers} /> ); // Should only render visible rows (15 by default) const rows = container.querySelectorAll('tbody tr'); expect(rows.length).toBe(15); }); it('preserves filter when data updates', () => { const { rerender } = render( <TemplateManagement data={mockTemplates} globalFilter="planTask" {...mockHandlers} /> ); expect(screen.getByText('planTask')).toBeInTheDocument(); // Update with new data const newTemplates = [...mockTemplates, { functionName: 'newPlanTask', description: 'Another plan task', status: 'custom', source: 'user-custom', contentLength: 2000, category: 'Task Management' }]; rerender( <TemplateManagement data={newTemplates} globalFilter="planTask" {...mockHandlers} /> ); // Both plan tasks should be visible expect(screen.getByText('planTask')).toBeInTheDocument(); expect(screen.getByText('newPlanTask')).toBeInTheDocument(); }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/cjo4m06/mcp-shrimp-task-manager'

If you have feedback or need assistance with the MCP directory API, please join our Discord server