/** * Generated Test Cases Preview Modal * * Shows AI-generated test cases before saving. * User can review, edit, and selectively save test cases. */ import React, { useState } from "react"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Checkbox } from "@/components/ui/checkbox"; import { Loader2, CheckCircle, XCircle, Save, Sparkles, Edit, ChevronDown, ChevronUp } from "lucide-react"; import { toast } from "@/lib/custom-toast"; import { FrontendTestCase } from "@/services/aiTestCaseApi"; import { tasksApi } from "@/services/tasksApi"; interface GeneratedTestCasesPreviewProps { open: boolean; onOpenChange: (open: boolean) => void; testCases: FrontendTestCase[]; featureId: number; onSaveComplete: () => void; } const GeneratedTestCasesPreview: React.FC = ({ open, onOpenChange, testCases, featureId, onSaveComplete, }) => { const [selectedCases, setSelectedCases] = useState>(new Set(testCases.map((_, i) => i))); const [savingStates, setSavingStates] = useState>({}); const [isSavingAll, setIsSavingAll] = useState(false); const [expandedCases, setExpandedCases] = useState>(new Set([0])); // First case expanded by default const [editedCases, setEditedCases] = useState>({}); // Toggle selection const toggleSelection = (index: number) => { const newSelected = new Set(selectedCases); if (newSelected.has(index)) { newSelected.delete(index); } else { newSelected.add(index); } setSelectedCases(newSelected); }; // Toggle expand/collapse const toggleExpand = (index: number) => { const newExpanded = new Set(expandedCases); if (newExpanded.has(index)) { newExpanded.delete(index); } else { newExpanded.add(index); } setExpandedCases(newExpanded); }; // Select/deselect all const toggleSelectAll = () => { if (selectedCases.size === testCases.length) { setSelectedCases(new Set()); } else { setSelectedCases(new Set(testCases.map((_, i) => i))); } }; // Update edited test case const updateTestCase = (index: number, field: keyof FrontendTestCase, value: string) => { setEditedCases(prev => ({ ...prev, [index]: { ...(editedCases[index] || testCases[index]), [field]: value, }, })); }; // Save single test case const saveTestCase = async (index: number) => { const testCase = editedCases[index] || testCases[index]; setSavingStates(prev => ({ ...prev, [index]: 'saving' })); try { const taskData = { type: 'Test Case', title: testCase.title, description: testCase.description, issuesId: featureId, projectId: testCase.projectId, status: testCase.status || 'Draft', priority: testCase.priority || 'Medium', severity: testCase.severity || 'Minor', environment: testCase.environment || 'Staging', stepsToReproduce: testCase.stepsToReproduce || '', inputs: testCase.inputs || '', expectedBehavior: testCase.expectedBehavior || '', actualBehavior: testCase.actualBehavior || '', rootCause: testCase.rootCause || '', resolution: testCase.resolution || '', version: testCase.version || '', url: testCase.url || '', }; await tasksApi.create(taskData); setSavingStates(prev => ({ ...prev, [index]: 'saved' })); toast.success(`Saved: ${testCase.title}`); } catch (error) { console.error('Failed to save test case:', error); setSavingStates(prev => ({ ...prev, [index]: 'error' })); toast.error(`Failed to save: ${testCase.title}`); } }; // Save all selected test cases const saveSelected = async () => { if (selectedCases.size === 0) { toast.warning('No test cases selected'); return; } setIsSavingAll(true); const indices = Array.from(selectedCases); for (const index of indices) { if (savingStates[index] !== 'saved') { await saveTestCase(index); } } setIsSavingAll(false); onSaveComplete(); // Check if all saved successfully const savedCount = indices.filter(i => savingStates[i] === 'saved').length; if (savedCount === indices.length) { toast.success(`All ${savedCount} test cases saved successfully!`); onOpenChange(false); } }; // Get priority badge color const getPriorityColor = (priority: string) => { switch (priority?.toLowerCase()) { case 'critical': return 'bg-red-100 text-red-800 border-red-300'; case 'high': return 'bg-orange-100 text-orange-800 border-orange-300'; case 'medium': return 'bg-yellow-100 text-yellow-800 border-yellow-300'; case 'low': return 'bg-green-100 text-green-800 border-green-300'; default: return 'bg-gray-100 text-gray-800 border-gray-300'; } }; // Get severity badge color const getSeverityColor = (severity: string) => { switch (severity?.toLowerCase()) { case 'blocker': return 'bg-red-100 text-red-800 border-red-300'; case 'critical': return 'bg-red-100 text-red-800 border-red-300'; case 'major': return 'bg-orange-100 text-orange-800 border-orange-300'; case 'minor': return 'bg-yellow-100 text-yellow-800 border-yellow-300'; case 'trivial': return 'bg-green-100 text-green-800 border-green-300'; default: return 'bg-gray-100 text-gray-800 border-gray-300'; } }; // Reset state when dialog opens React.useEffect(() => { if (open) { setSelectedCases(new Set(testCases.map((_, i) => i))); setSavingStates({}); setExpandedCases(new Set([0])); setEditedCases({}); } }, [open, testCases]); return ( Generated Test Cases Preview Review the AI-generated test cases below. Select which ones to save to the database. You can edit any field before saving.
Feature ID: {featureId}
{testCases.map((tc, index) => { const currentCase = editedCases[index] || tc; const isExpanded = expandedCases.has(index); const isSelected = selectedCases.has(index); const saveState = savingStates[index] || 'idle'; return (
{/* Header */}
toggleSelection(index)} className="mt-1" />
updateTestCase(index, 'title', e.target.value)} className="font-medium text-sm w-full bg-transparent border-b border-transparent hover:border-gray-300 focus:border-purple-500 focus:outline-none px-0.5" />
{currentCase.priority} {currentCase.severity} {currentCase.environment} {saveState === 'saved' && ( Saved )} {saveState === 'error' && ( Error )}
{/* Expanded Content */} {isExpanded && (
{/* Description */}