Skip to main content
Glama
TemplateEditor.test.jsx19.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 TemplateEditor from '../components/TemplateEditor'; describe('TemplateEditor Component', () => { const mockTemplate = { functionName: 'planTask', name: 'planTask', content: '## Task Analysis\n\nYou must complete the following sub-steps:\n\n{description}\n{requirements}', status: 'default', source: 'built-in' }; const mockHandlers = { onSave: vi.fn(), onCancel: vi.fn() }; beforeEach(() => { vi.clearAllMocks(); // Mock getBoundingClientRect for textarea positioning HTMLTextAreaElement.prototype.getBoundingClientRect = vi.fn(() => ({ top: 100, left: 200, width: 400, height: 300 })); // Mock getCaretCoordinates HTMLTextAreaElement.prototype.setSelectionRange = vi.fn(); // Mock clipboard API Object.assign(navigator, { clipboard: { writeText: vi.fn(), }, }); }); describe('Rendering', () => { it('renders modal with correct title and content', () => { render( <TemplateEditor template={mockTemplate} {...mockHandlers} /> ); expect(screen.getByText('Edit Template: planTask')).toBeInTheDocument(); expect(screen.getByDisplayValue(/## Task Analysis/)).toBeInTheDocument(); }); it('renders mode selection toggles', () => { render( <TemplateEditor template={mockTemplate} {...mockHandlers} /> ); expect(screen.getByLabelText('Override Mode')).toBeInTheDocument(); expect(screen.getByLabelText('Append Mode')).toBeInTheDocument(); // Override should be selected by default expect(screen.getByLabelText('Override Mode')).toBeChecked(); expect(screen.getByLabelText('Append Mode')).not.toBeChecked(); }); it('renders edit and preview toggle buttons', () => { render( <TemplateEditor template={mockTemplate} {...mockHandlers} /> ); expect(screen.getByText('✏️ Edit')).toBeInTheDocument(); expect(screen.getByText('👁️ Preview')).toBeInTheDocument(); }); it('renders action buttons', () => { render( <TemplateEditor template={mockTemplate} {...mockHandlers} /> ); expect(screen.getByText('💾 Save Template')).toBeInTheDocument(); expect(screen.getByText('Cancel')).toBeInTheDocument(); }); it('shows loading state', () => { render( <TemplateEditor template={mockTemplate} loading={true} {...mockHandlers} /> ); expect(screen.getByText('Saving...')).toBeInTheDocument(); expect(screen.getByText('💾 Save Template').closest('button')).toBeDisabled(); }); it('shows error message', () => { const errorMessage = 'Failed to save template'; render( <TemplateEditor template={mockTemplate} error={errorMessage} {...mockHandlers} /> ); expect(screen.getByText(errorMessage)).toBeInTheDocument(); }); }); describe('Mode Selection', () => { it('switches to append mode when radio button is selected', async () => { render( <TemplateEditor template={mockTemplate} {...mockHandlers} /> ); const appendModeRadio = screen.getByLabelText('Append Mode'); fireEvent.click(appendModeRadio); expect(appendModeRadio).toBeChecked(); expect(screen.getByLabelText('Override Mode')).not.toBeChecked(); }); it('shows correct mode description', () => { render( <TemplateEditor template={mockTemplate} {...mockHandlers} /> ); expect(screen.getByText(/Completely replace the existing template/)).toBeInTheDocument(); const appendModeRadio = screen.getByLabelText('Append Mode'); fireEvent.click(appendModeRadio); expect(screen.getByText(/Add content after the existing template/)).toBeInTheDocument(); }); }); describe('Content Editing', () => { it('updates content when textarea value changes', async () => { render( <TemplateEditor template={mockTemplate} {...mockHandlers} /> ); const textarea = screen.getByDisplayValue(/## Task Analysis/); await userEvent.clear(textarea); await userEvent.type(textarea, '## New Content\n{description}'); expect(textarea.value).toBe('## New Content\n{description}'); }); it('validates content is not empty on save', async () => { render( <TemplateEditor template={mockTemplate} {...mockHandlers} /> ); const textarea = screen.getByDisplayValue(/## Task Analysis/); await userEvent.clear(textarea); const saveButton = screen.getByText('💾 Save Template'); fireEvent.click(saveButton); expect(screen.getByText('Template content cannot be empty')).toBeInTheDocument(); expect(mockHandlers.onSave).not.toHaveBeenCalled(); }); it('calls onSave with correct data when save button is clicked', async () => { render( <TemplateEditor template={mockTemplate} {...mockHandlers} /> ); const textarea = screen.getByDisplayValue(/## Task Analysis/); await userEvent.clear(textarea); await userEvent.type(textarea, '## Updated Content\n{description}'); const saveButton = screen.getByText('💾 Save Template'); fireEvent.click(saveButton); expect(mockHandlers.onSave).toHaveBeenCalledWith({ functionName: 'planTask', content: '## Updated Content\n{description}', mode: 'override' }); }); it('calls onCancel when cancel button is clicked', () => { render( <TemplateEditor template={mockTemplate} {...mockHandlers} /> ); const cancelButton = screen.getByText('Cancel'); fireEvent.click(cancelButton); expect(mockHandlers.onCancel).toHaveBeenCalled(); }); it('calls onCancel when modal overlay is clicked', () => { const { container } = render( <TemplateEditor template={mockTemplate} {...mockHandlers} /> ); const overlay = container.querySelector('.modal-overlay'); fireEvent.click(overlay); expect(mockHandlers.onCancel).toHaveBeenCalled(); }); it('does not call onCancel when modal content is clicked', () => { const { container } = render( <TemplateEditor template={mockTemplate} {...mockHandlers} /> ); const modalContent = container.querySelector('.modal-content'); fireEvent.click(modalContent); expect(mockHandlers.onCancel).not.toHaveBeenCalled(); }); }); describe('Preview Mode', () => { it('switches to preview mode when preview button is clicked', async () => { render( <TemplateEditor template={mockTemplate} {...mockHandlers} /> ); const previewButton = screen.getByText('👁️ Preview'); fireEvent.click(previewButton); await waitFor(() => { expect(screen.getByText('👁️ Preview')).toHaveClass('active'); expect(screen.getByText('✏️ Edit')).not.toHaveClass('active'); }); // Should show syntax highlighted content expect(screen.getByText('## Task Analysis')).toBeInTheDocument(); }); it('switches back to edit mode when edit button is clicked', async () => { render( <TemplateEditor template={mockTemplate} {...mockHandlers} /> ); const previewButton = screen.getByText('👁️ Preview'); fireEvent.click(previewButton); const editButton = screen.getByText('✏️ Edit'); fireEvent.click(editButton); await waitFor(() => { expect(screen.getByText('✏️ Edit')).toHaveClass('active'); expect(screen.getByText('👁️ Preview')).not.toHaveClass('active'); }); // Should show textarea again expect(screen.getByDisplayValue(/## Task Analysis/)).toBeInTheDocument(); }); it('renders markdown content with syntax highlighting in preview', async () => { render( <TemplateEditor template={mockTemplate} {...mockHandlers} /> ); const previewButton = screen.getByText('👁️ Preview'); fireEvent.click(previewButton); await waitFor(() => { // SyntaxHighlighter should render the content const previewContent = document.querySelector('.token'); expect(previewContent).toBeInTheDocument(); }); }); }); describe('Parameter Auto-Complete', () => { it('shows auto-complete when { is typed', async () => { render( <TemplateEditor template={mockTemplate} {...mockHandlers} /> ); const textarea = screen.getByDisplayValue(/## Task Analysis/); // Position cursor and type { fireEvent.focus(textarea); fireEvent.change(textarea, { target: { value: mockTemplate.content + '\n{' } }); fireEvent.keyDown(textarea, { key: '{' }); await waitFor(() => { expect(screen.getByText('description')).toBeInTheDocument(); expect(screen.getByText('requirements')).toBeInTheDocument(); expect(screen.getByText('summary')).toBeInTheDocument(); }); }); it('filters parameters based on function name', async () => { const planTaskTemplate = { ...mockTemplate, functionName: 'planTask' }; render( <TemplateEditor template={planTaskTemplate} {...mockHandlers} /> ); const textarea = screen.getByDisplayValue(/## Task Analysis/); fireEvent.focus(textarea); fireEvent.change(textarea, { target: { value: mockTemplate.content + '\n{' } }); fireEvent.keyDown(textarea, { key: '{' }); await waitFor(() => { // Should show planTask-specific parameters expect(screen.getByText('existingTasksReference')).toBeInTheDocument(); expect(screen.getByText('completedTasks')).toBeInTheDocument(); expect(screen.getByText('pendingTasks')).toBeInTheDocument(); }); }); it('inserts parameter when clicked from auto-complete', async () => { render( <TemplateEditor template={mockTemplate} {...mockHandlers} /> ); const textarea = screen.getByDisplayValue(/## Task Analysis/); fireEvent.focus(textarea); fireEvent.change(textarea, { target: { value: mockTemplate.content + '\n{' } }); fireEvent.keyDown(textarea, { key: '{' }); await waitFor(() => { expect(screen.getByText('description')).toBeInTheDocument(); }); const parameterOption = screen.getByText('description'); fireEvent.click(parameterOption); // Should insert the parameter expect(textarea.value).toContain('{description}'); }); it('hides auto-complete when Escape is pressed', async () => { render( <TemplateEditor template={mockTemplate} {...mockHandlers} /> ); const textarea = screen.getByDisplayValue(/## Task Analysis/); fireEvent.focus(textarea); fireEvent.change(textarea, { target: { value: mockTemplate.content + '\n{' } }); fireEvent.keyDown(textarea, { key: '{' }); await waitFor(() => { expect(screen.getByText('description')).toBeInTheDocument(); }); fireEvent.keyDown(textarea, { key: 'Escape' }); await waitFor(() => { expect(screen.queryByText('description')).not.toBeInTheDocument(); }); }); it('filters parameters as user types', async () => { render( <TemplateEditor template={mockTemplate} {...mockHandlers} /> ); const textarea = screen.getByDisplayValue(/## Task Analysis/); fireEvent.focus(textarea); fireEvent.change(textarea, { target: { value: mockTemplate.content + '\n{desc' } }); fireEvent.keyDown(textarea, { key: 'c' }); await waitFor(() => { expect(screen.getByText('description')).toBeInTheDocument(); expect(screen.queryByText('requirements')).not.toBeInTheDocument(); }); }); }); describe('Keyboard Shortcuts', () => { it('saves on Ctrl+S', async () => { render( <TemplateEditor template={mockTemplate} {...mockHandlers} /> ); const textarea = screen.getByDisplayValue(/## Task Analysis/); fireEvent.keyDown(textarea, { key: 's', ctrlKey: true }); expect(mockHandlers.onSave).toHaveBeenCalledWith({ functionName: 'planTask', content: mockTemplate.content, mode: 'override' }); }); it('cancels on Ctrl+Escape', async () => { render( <TemplateEditor template={mockTemplate} {...mockHandlers} /> ); const textarea = screen.getByDisplayValue(/## Task Analysis/); fireEvent.keyDown(textarea, { key: 'Escape', ctrlKey: true }); expect(mockHandlers.onCancel).toHaveBeenCalled(); }); it('toggles preview on Ctrl+P', async () => { render( <TemplateEditor template={mockTemplate} {...mockHandlers} /> ); const textarea = screen.getByDisplayValue(/## Task Analysis/); fireEvent.keyDown(textarea, { key: 'p', ctrlKey: true }); await waitFor(() => { expect(screen.getByText('👁️ Preview')).toHaveClass('active'); }); fireEvent.keyDown(document, { key: 'p', ctrlKey: true }); await waitFor(() => { expect(screen.getByText('✏️ Edit')).toHaveClass('active'); }); }); }); describe('Parameter Helper', () => { it('displays parameter helper panel', () => { render( <TemplateEditor template={mockTemplate} {...mockHandlers} /> ); expect(screen.getByText('Available Parameters')).toBeInTheDocument(); expect(screen.getByText('Common Parameters')).toBeInTheDocument(); }); it('shows function-specific parameters', () => { const planTaskTemplate = { ...mockTemplate, functionName: 'planTask' }; render( <TemplateEditor template={planTaskTemplate} {...mockHandlers} /> ); expect(screen.getByText('planTask Parameters')).toBeInTheDocument(); expect(screen.getByText('{existingTasksReference}')).toBeInTheDocument(); expect(screen.getByText('Whether to reference existing tasks')).toBeInTheDocument(); }); it('inserts parameter when clicked from helper panel', async () => { render( <TemplateEditor template={mockTemplate} {...mockHandlers} /> ); const textarea = screen.getByDisplayValue(/## Task Analysis/); const descriptionParam = screen.getByText('{description}'); fireEvent.focus(textarea); fireEvent.click(descriptionParam); await waitFor(() => { expect(textarea.value).toContain('{description}'); }); }); }); describe('Validation', () => { it('shows character count', () => { render( <TemplateEditor template={mockTemplate} {...mockHandlers} /> ); const characterCount = screen.getByText(/\d+ characters/); expect(characterCount).toBeInTheDocument(); }); it('updates character count as user types', async () => { render( <TemplateEditor template={mockTemplate} {...mockHandlers} /> ); const textarea = screen.getByDisplayValue(/## Task Analysis/); await userEvent.type(textarea, ' additional text'); const characterCount = screen.getByText(/\d+ characters/); expect(characterCount).toBeInTheDocument(); }); it('shows validation message for empty content', async () => { render( <TemplateEditor template={mockTemplate} {...mockHandlers} /> ); const textarea = screen.getByDisplayValue(/## Task Analysis/); await userEvent.clear(textarea); expect(screen.getByText('Template content cannot be empty')).toBeInTheDocument(); }); it('validates parameter syntax', async () => { render( <TemplateEditor template={mockTemplate} {...mockHandlers} /> ); const textarea = screen.getByDisplayValue(/## Task Analysis/); await userEvent.clear(textarea); await userEvent.type(textarea, '## Test {unclosed_param'); expect(screen.getByText('Warning: Unclosed parameter detected')).toBeInTheDocument(); }); }); describe('Edge Cases', () => { it('handles template with no content', () => { const emptyTemplate = { ...mockTemplate, content: '' }; render( <TemplateEditor template={emptyTemplate} {...mockHandlers} /> ); const textarea = screen.getByRole('textbox'); expect(textarea.value).toBe(''); }); it('handles template with special characters', () => { const specialTemplate = { ...mockTemplate, content: '## Test\n\n"Quotes" and \'apostrophes\' and {parameters} and $variables' }; render( <TemplateEditor template={specialTemplate} {...mockHandlers} /> ); const textarea = screen.getByDisplayValue(/## Test/); expect(textarea.value).toContain("\"Quotes\" and 'apostrophes'"); }); it('handles very long content', () => { const longContent = 'Long content. '.repeat(1000); const longTemplate = { ...mockTemplate, content: longContent }; render( <TemplateEditor template={longTemplate} {...mockHandlers} /> ); const textarea = screen.getByDisplayValue(/Long content/); expect(textarea.value.length).toBeGreaterThan(5000); }); it('preserves textarea focus during auto-complete', async () => { render( <TemplateEditor template={mockTemplate} {...mockHandlers} /> ); const textarea = screen.getByDisplayValue(/## Task Analysis/); fireEvent.focus(textarea); fireEvent.change(textarea, { target: { value: mockTemplate.content + '\n{' } }); fireEvent.keyDown(textarea, { key: '{' }); await waitFor(() => { expect(screen.getByText('description')).toBeInTheDocument(); }); expect(document.activeElement).toBe(textarea); }); }); });

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