Skip to main content
Glama
edge-cases.test.jsx12.8 kB
import { describe, it, expect, beforeEach, vi } from 'vitest'; import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import App from '../src/App'; import TaskTable from '../src/components/TaskTable'; import TaskDetailView from '../src/components/TaskDetailView'; import { createMockTask, mockFetch } from './test-utils'; describe('Edge Cases and Error Handling', () => { beforeEach(() => { vi.clearAllMocks(); }); describe('Network Errors', () => { it('handles network timeout gracefully', async () => { // Mock fetch to simulate timeout global.fetch = vi.fn(() => new Promise((_, reject) => { setTimeout(() => reject(new Error('Network timeout')), 100); })); render(<App />); await waitFor(() => { expect(screen.getByText(/Failed to load profiles.*Network timeout/)).toBeInTheDocument(); }, { timeout: 200 }); }); it('handles intermittent network failures with retry', async () => { let attemptCount = 0; mockFetch({ '/api/agents': { ok: attemptCount++ > 1, // Fail first two attempts status: attemptCount > 1 ? 200 : 503, json: [ { id: 'agent1', name: 'Agent 1', path: '/path/1' } ] } }); render(<App />); // Initial failure await waitFor(() => { expect(screen.getByText(/Failed to load profiles/)).toBeInTheDocument(); }); // Manual retry could be implemented here }); it('handles malformed JSON responses', async () => { global.fetch = vi.fn(() => Promise.resolve({ ok: true, status: 200, json: () => Promise.reject(new Error('Invalid JSON')), text: () => Promise.resolve('{ invalid json }') })); render(<App />); await waitFor(() => { expect(screen.getByText(/Failed to load profiles.*Invalid JSON/)).toBeInTheDocument(); }); }); }); describe('Data Integrity Issues', () => { it('handles circular dependencies in tasks', () => { const task1 = createMockTask({ id: '1', name: 'Task 1', dependencies: [{ taskId: '2', name: 'Task 2' }] }); const task2 = createMockTask({ id: '2', name: 'Task 2', dependencies: [{ taskId: '1', name: 'Task 1' }] }); // Component should handle circular deps without crashing render( <TaskTable data={[task1, task2]} globalFilter="" onGlobalFilterChange={vi.fn()} /> ); expect(screen.getByText('Task 1')).toBeInTheDocument(); expect(screen.getByText('Task 2')).toBeInTheDocument(); }); it('handles tasks with extremely long content', () => { const longTask = createMockTask({ name: 'A'.repeat(1000), description: 'B'.repeat(5000), notes: 'C'.repeat(10000) }); render( <TaskDetailView task={longTask} onBack={vi.fn()} /> ); // Should truncate or handle gracefully expect(screen.getByText(/A{100,}/)).toBeInTheDocument(); expect(screen.getByText(/B{100,}/)).toBeInTheDocument(); expect(screen.getByText(/C{100,}/)).toBeInTheDocument(); }); it('handles special characters and potential XSS', () => { const maliciousTask = createMockTask({ name: '<script>alert("XSS")</script>', description: '<img src="x" onerror="alert(\'XSS\')">', notes: '"><script>alert(String.fromCharCode(88,83,83))</script>' }); render( <TaskDetailView task={maliciousTask} onBack={vi.fn()} /> ); // Content should be escaped expect(screen.queryByRole('img')).not.toBeInTheDocument(); expect(document.querySelector('script')).not.toBeInTheDocument(); expect(screen.getByText(/<script>/)).toBeInTheDocument(); }); it('handles Unicode and emoji in content', () => { const unicodeTask = createMockTask({ name: '🚀 Deploy to production 部署到生产', description: 'Task with émojis and 中文字符 and العربية', notes: '✅ Complete ❌ Failed ⚠️ Warning' }); render( <TaskDetailView task={unicodeTask} onBack={vi.fn()} /> ); expect(screen.getByText(/🚀 Deploy to production/)).toBeInTheDocument(); expect(screen.getByText(/中文字符/)).toBeInTheDocument(); expect(screen.getByText(/✅ Complete/)).toBeInTheDocument(); }); }); describe('State Management Edge Cases', () => { it('handles rapid profile switching', async () => { mockFetch({ '/api/agents': { json: [ { id: 'agent1', name: 'Agent 1', path: '/1' }, { id: 'agent2', name: 'Agent 2', path: '/2' }, { id: 'agent3', name: 'Agent 3', path: '/3' } ] }, '/api/tasks/agent1': { json: { tasks: [createMockTask({ name: 'Task 1' })] } }, '/api/tasks/agent2': { json: { tasks: [createMockTask({ name: 'Task 2' })] } }, '/api/tasks/agent3': { json: { tasks: [createMockTask({ name: 'Task 3' })] } } }); render(<App />); await waitFor(() => { expect(screen.getByText('Agent 1')).toBeInTheDocument(); }); // Rapidly switch between profiles fireEvent.click(screen.getByText('Agent 1')); fireEvent.click(screen.getByText('Agent 2')); fireEvent.click(screen.getByText('Agent 3')); fireEvent.click(screen.getByText('Agent 1')); // Should handle rapid switching without errors await waitFor(() => { expect(screen.queryByText(/Error/)).not.toBeInTheDocument(); }); }); it('handles search input with special regex characters', async () => { const tasks = [ createMockTask({ name: 'Task [1]' }), createMockTask({ name: 'Task (2)' }), createMockTask({ name: 'Task $3' }), createMockTask({ name: 'Task *4*' }) ]; render( <TaskTable data={tasks} globalFilter="[" onGlobalFilterChange={vi.fn()} /> ); // Should not crash with regex special chars expect(screen.getByText('Task [1]')).toBeInTheDocument(); }); it('handles concurrent auto-refresh and manual refresh', async () => { vi.useFakeTimers(); mockFetch({ '/api/agents': { json: [{ id: 'agent1', name: 'Agent 1', path: '/1' }] }, default: { json: { tasks: [] } } }); render(<App />); await waitFor(() => { fireEvent.click(screen.getByText('Agent 1')); }); // Enable auto-refresh const autoRefreshCheckbox = screen.getByLabelText(/Auto-refresh/); fireEvent.click(autoRefreshCheckbox); // Trigger manual refresh while auto-refresh is pending const refreshButton = screen.getByLabelText('Refresh tasks'); fireEvent.click(refreshButton); // Advance timers vi.advanceTimersByTime(30000); // Should handle concurrent refreshes await waitFor(() => { expect(screen.queryByText(/Error/)).not.toBeInTheDocument(); }); vi.useRealTimers(); }); }); describe('Browser Compatibility Issues', () => { it('handles missing localStorage gracefully', () => { // Simulate missing localStorage const originalLocalStorage = global.localStorage; delete global.localStorage; render(<App />); // Should work without localStorage expect(screen.getByText('🦐 Shrimp Task Manager Viewer')).toBeInTheDocument(); // Restore global.localStorage = originalLocalStorage; }); it('handles file input restrictions', async () => { mockFetch({ '/api/agents': { json: [] } }); render(<App />); await waitFor(() => { fireEvent.click(screen.getByText('+ Add Profile')); }); const fileInput = screen.getByLabelText(/JSON file/); // Try uploading non-JSON file const file = new File(['not json'], 'test.txt', { type: 'text/plain' }); await userEvent.upload(fileInput, file); // Component should handle gracefully expect(screen.queryByText(/Error/)).not.toBeInTheDocument(); }); }); describe('Performance Edge Cases', () => { it('handles extremely large task lists', () => { const hugeTasks = Array.from({ length: 10000 }, (_, i) => createMockTask({ id: `task-${i}`, name: `Task ${i}`, description: `Description ${i}`.repeat(10) }) ); const { container } = render( <TaskTable data={hugeTasks} globalFilter="" onGlobalFilterChange={vi.fn()} /> ); // Should only render visible items const rows = container.querySelectorAll('tbody tr'); expect(rows.length).toBeLessThan(100); // Pagination should limit }); it('handles rapid filtering on large datasets', async () => { const tasks = Array.from({ length: 1000 }, (_, i) => createMockTask({ id: `task-${i}`, name: `Task ${i % 10}`, // Repeated names description: `Type ${i % 5}` // Categories }) ); const mockOnFilterChange = vi.fn(); const user = userEvent.setup(); render( <TaskTable data={tasks} globalFilter="" onGlobalFilterChange={mockOnFilterChange} /> ); // Rapid filter changes await user.type(screen.getByRole('textbox'), 'Task 1'); await user.clear(screen.getByRole('textbox')); await user.type(screen.getByRole('textbox'), 'Type 3'); await user.clear(screen.getByRole('textbox')); await user.type(screen.getByRole('textbox'), 'Task 5'); // Should handle rapid changes expect(mockOnFilterChange).toHaveBeenCalledTimes(15); // Each keystroke }); }); describe('Memory Leak Prevention', () => { it('cleans up intervals on unmount', async () => { const { unmount } = render(<App />); // Enable auto-refresh mockFetch({ '/api/agents': { json: [{ id: 'agent1', name: 'Agent 1', path: '/1' }] }, default: { json: { tasks: [] } } }); await waitFor(() => { expect(screen.getByText('Agent 1')).toBeInTheDocument(); }); fireEvent.click(screen.getByText('Agent 1')); await waitFor(() => { const autoRefreshCheckbox = screen.getByLabelText(/Auto-refresh/); fireEvent.click(autoRefreshCheckbox); }); // Unmount component unmount(); // Intervals should be cleared (no way to directly test, but no errors should occur) }); it('handles component unmounting during async operations', async () => { let resolveFetch; global.fetch = vi.fn(() => new Promise((resolve) => { resolveFetch = resolve; })); const { unmount } = render(<App />); // Unmount while fetch is pending unmount(); // Resolve fetch after unmount if (resolveFetch) { resolveFetch({ ok: true, json: async () => [] }); } // Should not cause errors expect(true).toBe(true); }); }); describe('Accessibility Edge Cases', () => { it('handles keyboard navigation in complex scenarios', async () => { mockFetch({ '/api/agents': { json: [ { id: 'agent1', name: 'Agent 1', path: '/1' }, { id: 'agent2', name: 'Agent 2', path: '/2' } ] } }); render(<App />); await waitFor(() => { expect(screen.getByText('Agent 1')).toBeInTheDocument(); }); // Tab through interface const user = userEvent.setup(); await user.tab(); await user.tab(); await user.tab(); // Should maintain focus outline expect(document.activeElement).toBeTruthy(); }); it('maintains focus after state changes', async () => { const mockOnFilterChange = vi.fn(); const tasks = [ createMockTask({ name: 'Task 1' }), createMockTask({ name: 'Task 2' }) ]; render( <TaskTable data={tasks} globalFilter="" onGlobalFilterChange={mockOnFilterChange} /> ); // Focus on search const searchInput = screen.getByRole('textbox'); searchInput.focus(); // Type to filter await userEvent.type(searchInput, 'Task 1'); // Focus should remain on search expect(document.activeElement).toBe(searchInput); }); }); });

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