Skip to main content
Glama
App.test.jsx13.1 kB
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import { render, screen, fireEvent, waitFor, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import App from '../App'; describe('App Component', () => { const mockProfiles = [ { id: 'agent1', name: 'Agent 1', path: '/path/1' }, { id: 'agent2', name: 'Agent 2', path: '/path/2' } ]; const mockTasks = { tasks: [ { id: '1', name: 'Task 1', description: 'Description 1', status: 'pending', createdAt: '2024-01-01T00:00:00Z', updatedAt: '2024-01-01T00:00:00Z' }, { id: '2', name: 'Task 2', description: 'Description 2', status: 'completed', createdAt: '2024-01-02T00:00:00Z', updatedAt: '2024-01-02T00:00:00Z' } ] }; beforeEach(() => { fetch.mockClear(); vi.clearAllMocks(); }); describe('Initial Load', () => { it('should load profiles on mount', async () => { fetch.mockResolvedValueOnce({ ok: true, json: async () => mockProfiles }); render(<App />); await waitFor(() => { expect(fetch).toHaveBeenCalledWith('/api/agents'); }); }); it('should display error if profile loading fails', async () => { fetch.mockRejectedValueOnce(new Error('Network error')); render(<App />); await waitFor(() => { expect(screen.getByText(/Failed to load profiles/)).toBeInTheDocument(); }); }); it('should show "No profiles" message when no profiles exist', async () => { fetch.mockResolvedValueOnce({ ok: true, json: async () => [] }); render(<App />); await waitFor(() => { expect(screen.getByText('No profiles configured')).toBeInTheDocument(); }); }); }); describe('Profile Management', () => { beforeEach(async () => { fetch.mockResolvedValueOnce({ ok: true, json: async () => mockProfiles }); }); it('should display profile tabs', async () => { render(<App />); await waitFor(() => { expect(screen.getByText('Agent 1')).toBeInTheDocument(); expect(screen.getByText('Agent 2')).toBeInTheDocument(); }); }); it('should load tasks when profile is selected', async () => { fetch.mockResolvedValueOnce({ ok: true, json: async () => mockTasks }); render(<App />); await waitFor(() => { expect(screen.getByText('Agent 1')).toBeInTheDocument(); }); fireEvent.click(screen.getByText('Agent 1')); await waitFor(() => { expect(fetch).toHaveBeenCalledWith(expect.stringContaining('/api/tasks/agent1')); }); }); it('should clear search when switching profiles', async () => { fetch.mockResolvedValue({ ok: true, json: async () => mockTasks }); render(<App />); await waitFor(() => { expect(screen.getByText('Agent 1')).toBeInTheDocument(); }); // Select first profile and enter search fireEvent.click(screen.getByText('Agent 1')); const searchInput = await screen.findByPlaceholderText(/Search tasks/); await userEvent.type(searchInput, 'test search'); // Switch to second profile fireEvent.click(screen.getByText('Agent 2')); await waitFor(() => { expect(searchInput.value).toBe(''); }); }); it('should handle task loading errors', async () => { fetch.mockResolvedValueOnce({ ok: false, status: 500 }); render(<App />); await waitFor(() => { expect(screen.getByText('Agent 1')).toBeInTheDocument(); }); fireEvent.click(screen.getByText('Agent 1')); await waitFor(() => { expect(screen.getByText(/Error loading tasks/)).toBeInTheDocument(); }); }); }); describe('Add Profile', () => { beforeEach(async () => { fetch.mockResolvedValueOnce({ ok: true, json: async () => mockProfiles }); }); it('should show add profile form when button clicked', async () => { render(<App />); await waitFor(() => { expect(screen.getByText('+ Add Profile')).toBeInTheDocument(); }); fireEvent.click(screen.getByText('+ Add Profile')); expect(screen.getByText('Add New Profile')).toBeInTheDocument(); expect(screen.getByPlaceholderText('Profile Name')).toBeInTheDocument(); }); it('should add new profile successfully', async () => { fetch .mockResolvedValueOnce({ ok: true, json: async () => ({ id: 'new-agent' }) }) .mockResolvedValueOnce({ ok: true, json: async () => [...mockProfiles, { id: 'new-agent', name: 'New Agent' }] }); render(<App />); await waitFor(() => { fireEvent.click(screen.getByText('+ Add Profile')); }); const nameInput = screen.getByPlaceholderText('Profile Name'); await userEvent.type(nameInput, 'New Agent'); // Simulate file upload const file = new File(['{"tasks":[]}'], 'tasks.json', { type: 'application/json' }); const fileInput = screen.getByLabelText(/JSON file/); await userEvent.upload(fileInput, file); fireEvent.click(screen.getByText('Add Profile')); await waitFor(() => { expect(fetch).toHaveBeenCalledWith('/api/add-profile', expect.objectContaining({ method: 'POST', body: expect.any(FormData) })); }); }); it('should show error if add profile fails', async () => { fetch.mockResolvedValueOnce({ ok: false }); render(<App />); await waitFor(() => { fireEvent.click(screen.getByText('+ Add Profile')); }); const nameInput = screen.getByPlaceholderText('Profile Name'); await userEvent.type(nameInput, 'New Agent'); const file = new File(['{"tasks":[]}'], 'tasks.json', { type: 'application/json' }); const fileInput = screen.getByLabelText(/JSON file/); await userEvent.upload(fileInput, file); fireEvent.click(screen.getByText('Add Profile')); await waitFor(() => { expect(screen.getByText(/Failed to add profile/)).toBeInTheDocument(); }); }); it('should cancel add profile form', async () => { render(<App />); await waitFor(() => { fireEvent.click(screen.getByText('+ Add Profile')); }); expect(screen.getByText('Add New Profile')).toBeInTheDocument(); fireEvent.click(screen.getByText('Cancel')); expect(screen.queryByText('Add New Profile')).not.toBeInTheDocument(); }); }); describe('Remove Profile', () => { beforeEach(async () => { fetch.mockResolvedValueOnce({ ok: true, json: async () => mockProfiles }); }); it('should confirm before removing profile', async () => { const confirmSpy = vi.spyOn(window, 'confirm').mockReturnValue(false); render(<App />); await waitFor(() => { expect(screen.getByText('Agent 1')).toBeInTheDocument(); }); const removeButton = screen.getAllByLabelText(/Remove profile/)[0]; fireEvent.click(removeButton); expect(confirmSpy).toHaveBeenCalledWith(expect.stringContaining('Are you sure')); expect(fetch).not.toHaveBeenCalledWith(expect.stringContaining('/api/remove-profile')); }); it('should remove profile when confirmed', async () => { vi.spyOn(window, 'confirm').mockReturnValue(true); fetch .mockResolvedValueOnce({ ok: true }) .mockResolvedValueOnce({ ok: true, json: async () => [mockProfiles[1]] }); render(<App />); await waitFor(() => { expect(screen.getByText('Agent 1')).toBeInTheDocument(); }); const removeButton = screen.getAllByLabelText(/Remove profile/)[0]; fireEvent.click(removeButton); await waitFor(() => { expect(fetch).toHaveBeenCalledWith('/api/remove-profile/agent1', { method: 'DELETE' }); }); }); it('should clear tasks if removing selected profile', async () => { vi.spyOn(window, 'confirm').mockReturnValue(true); fetch .mockResolvedValueOnce({ ok: true, json: async () => mockTasks }) .mockResolvedValueOnce({ ok: true }) .mockResolvedValueOnce({ ok: true, json: async () => [mockProfiles[1]] }); render(<App />); await waitFor(() => { fireEvent.click(screen.getByText('Agent 1')); }); await waitFor(() => { expect(screen.getByText('Task 1')).toBeInTheDocument(); }); const removeButton = screen.getAllByLabelText(/Remove profile/)[0]; fireEvent.click(removeButton); await waitFor(() => { expect(screen.queryByText('Task 1')).not.toBeInTheDocument(); }); }); }); describe('Auto-refresh', () => { beforeEach(async () => { vi.useFakeTimers(); fetch.mockResolvedValueOnce({ ok: true, json: async () => mockProfiles }); }); afterEach(() => { vi.useRealTimers(); }); it('should enable auto-refresh', async () => { fetch.mockResolvedValue({ ok: true, json: async () => mockTasks }); render(<App />); await waitFor(() => { fireEvent.click(screen.getByText('Agent 1')); }); const autoRefreshCheckbox = screen.getByLabelText(/Auto-refresh/); fireEvent.click(autoRefreshCheckbox); expect(autoRefreshCheckbox).toBeChecked(); // Fast-forward time vi.advanceTimersByTime(30000); await waitFor(() => { // Should have been called at least twice (initial + auto-refresh) expect(fetch.mock.calls.filter(call => call[0].includes('/api/tasks/')).length).toBeGreaterThanOrEqual(2); }); }); it('should update refresh interval', async () => { render(<App />); await waitFor(() => { expect(screen.getByText('Agent 1')).toBeInTheDocument(); }); const intervalInput = screen.getByLabelText(/Refresh interval/); await userEvent.clear(intervalInput); await userEvent.type(intervalInput, '60'); expect(intervalInput.value).toBe('60'); }); it('should stop auto-refresh when disabled', async () => { fetch.mockResolvedValue({ ok: true, json: async () => mockTasks }); render(<App />); await waitFor(() => { fireEvent.click(screen.getByText('Agent 1')); }); const autoRefreshCheckbox = screen.getByLabelText(/Auto-refresh/); // Enable fireEvent.click(autoRefreshCheckbox); expect(autoRefreshCheckbox).toBeChecked(); // Disable fireEvent.click(autoRefreshCheckbox); expect(autoRefreshCheckbox).not.toBeChecked(); // Fast-forward time vi.advanceTimersByTime(30000); // Should not have made additional calls expect(fetch).toHaveBeenCalledTimes(2); // Initial profiles + initial tasks }); }); describe('Manual Refresh', () => { beforeEach(async () => { fetch.mockResolvedValueOnce({ ok: true, json: async () => mockProfiles }); }); it('should refresh tasks when refresh button clicked', async () => { fetch .mockResolvedValueOnce({ ok: true, json: async () => mockTasks }) .mockResolvedValueOnce({ ok: true, json: async () => ({ ...mockTasks, tasks: [...mockTasks.tasks, { id: '3', name: 'Task 3' }] }) }); render(<App />); await waitFor(() => { fireEvent.click(screen.getByText('Agent 1')); }); await waitFor(() => { expect(screen.getByText('Task 1')).toBeInTheDocument(); }); const refreshButton = screen.getByLabelText('Refresh tasks'); fireEvent.click(refreshButton); await waitFor(() => { expect(fetch).toHaveBeenCalledWith(expect.stringContaining('/api/tasks/agent1')); }); }); it('should show loading state during refresh', async () => { fetch.mockImplementation(() => new Promise(() => {})); // Never resolves render(<App />); await waitFor(() => { fireEvent.click(screen.getByText('Agent 1')); }); expect(screen.getByText('Loading tasks...')).toBeInTheDocument(); }); }); describe('Error States', () => { it('should display network error gracefully', async () => { fetch.mockRejectedValueOnce(new Error('Network failure')); render(<App />); await waitFor(() => { expect(screen.getByText(/Failed to load profiles.*Network failure/)).toBeInTheDocument(); }); }); it('should handle JSON parse errors', async () => { fetch.mockResolvedValueOnce({ ok: true, json: async () => { throw new Error('Invalid JSON'); } }); render(<App />); await waitFor(() => { expect(screen.getByText(/Failed to load profiles.*Invalid JSON/)).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