import { useState, useEffect, useCallback } from 'react'; import { Label } from '@/components/ui/label'; import { Input } from '@/components/ui/input'; import { Button } from '@/components/ui/button'; import { FlaskConical, Save, RotateCcw, Info } from 'lucide-react'; import { Spinner } from '@/components/ui/spinner'; import { cn } from '@/lib/utils'; import { getHttpApiClient } from '@/lib/http-api-client'; import { toast } from 'sonner'; import type { Project } from '@/lib/electron'; interface TestingSectionProps { project: Project; } export function TestingSection({ project }: TestingSectionProps) { const [testCommand, setTestCommand] = useState(''); const [originalTestCommand, setOriginalTestCommand] = useState(''); const [isLoading, setIsLoading] = useState(true); const [isSaving, setIsSaving] = useState(false); // Check if there are unsaved changes const hasChanges = testCommand !== originalTestCommand; // Load project settings when project changes useEffect(() => { let isCancelled = false; const currentPath = project.path; const loadProjectSettings = async () => { setIsLoading(true); try { const httpClient = getHttpApiClient(); const response = await httpClient.settings.getProject(currentPath); // Avoid updating state if component unmounted or project changed if (isCancelled) return; if (response.success && response.settings) { const command = response.settings.testCommand || ''; setTestCommand(command); setOriginalTestCommand(command); } } catch (error) { if (!isCancelled) { console.error('Failed to load project settings:', error); } } finally { if (!isCancelled) { setIsLoading(false); } } }; loadProjectSettings(); return () => { isCancelled = true; }; }, [project.path]); // Save test command const handleSave = useCallback(async () => { setIsSaving(true); try { const httpClient = getHttpApiClient(); const normalizedCommand = testCommand.trim(); const response = await httpClient.settings.updateProject(project.path, { testCommand: normalizedCommand || undefined, }); if (response.success) { setTestCommand(normalizedCommand); setOriginalTestCommand(normalizedCommand); toast.success('Test command saved'); } else { toast.error('Failed to save test command', { description: response.error, }); } } catch (error) { console.error('Failed to save test command:', error); toast.error('Failed to save test command'); } finally { setIsSaving(false); } }, [project.path, testCommand]); // Reset to original value const handleReset = useCallback(() => { setTestCommand(originalTestCommand); }, [originalTestCommand]); // Use a preset command const handleUsePreset = useCallback((command: string) => { setTestCommand(command); }, []); return (

Testing Configuration

Configure how tests are run for this project.

{isLoading ? (
) : ( <> {/* Test Command Input */}
{hasChanges && ( (unsaved changes) )}
setTestCommand(e.target.value)} placeholder="e.g., npm test, yarn test, pytest, go test ./..." className="font-mono text-sm" data-testid="test-command-input" />

The command to run tests for this project. If not specified, the test runner will auto-detect based on your project structure (package.json, Cargo.toml, go.mod, etc.).

{/* Auto-detection Info */}

Auto-detection

When no custom command is set, the test runner automatically detects and uses the appropriate test framework based on your project files (Vitest, Jest, Pytest, Cargo, Go Test, etc.).

{/* Quick Presets */}
{[ { label: 'npm test', command: 'npm test' }, { label: 'yarn test', command: 'yarn test' }, { label: 'pnpm test', command: 'pnpm test' }, { label: 'bun test', command: 'bun test' }, { label: 'pytest', command: 'pytest' }, { label: 'cargo test', command: 'cargo test' }, { label: 'go test', command: 'go test ./...' }, ].map((preset) => ( ))}

Click a preset to use it as your test command.

{/* Action Buttons */}
)}
); }