"use client"; import { useState, useCallback } from "react"; import { Play, Send, Loader2 } from "lucide-react"; import { usePyodide } from "@/hooks/use-pyodide"; import { submitSolution } from "@/lib/api"; import { TestResults } from "./test-results"; import type { VisibleTestCase, HiddenTestInput, TestResult, HiddenTestOutput, } from "@/types"; interface TestRunnerProps { code: string; functionName: string; slug: string; visibleTestCases: VisibleTestCase[]; hiddenTestInputs: HiddenTestInput[]; } interface ExecutionResult { test_id: number; output: unknown; error?: string; } export function TestRunner({ code, functionName, slug, visibleTestCases, hiddenTestInputs, }: TestRunnerProps) { const { pyodide, loading: pyodideLoading, runPython } = usePyodide(); const [isRunning, setIsRunning] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false); const [visibleResults, setVisibleResults] = useState([]); const [hiddenPassedCount, setHiddenPassedCount] = useState(0); const [hiddenTotalCount, setHiddenTotalCount] = useState(0); const generateTestHarness = useCallback( (tests: Array<{ id: number; input: Record }>) => { const testsJson = JSON.stringify(tests); return ` import json ${code} __tests__ = json.loads('${testsJson.replace(/'/g, "\\'")}') __results__ = [] for test in __tests__: try: result = ${functionName}(**test["input"]) __results__.append({"test_id": test["id"], "output": result}) except Exception as e: __results__.append({"test_id": test["id"], "output": None, "error": str(e)}) json.dumps(__results__) `; }, [code, functionName] ); const runTests = useCallback( async (tests: Array<{ id: number; input: Record }>) => { const harness = generateTestHarness(tests); const { output, error } = await runPython(harness); if (error) { return tests.map((t) => ({ test_id: t.id, output: null, error: error, })); } try { return JSON.parse(output as string) as ExecutionResult[]; } catch { return tests.map((t) => ({ test_id: t.id, output: null, error: "Failed to parse output", })); } }, [generateTestHarness, runPython] ); const handleRunTests = useCallback(async () => { if (!pyodide || isRunning) return; setIsRunning(true); setHiddenPassedCount(0); setHiddenTotalCount(0); try { const tests = visibleTestCases.map((tc) => ({ id: tc.id, input: tc.input, })); const results = await runTests(tests); const testResults: TestResult[] = results.map((r, idx) => { const testCase = visibleTestCases[idx]; const passed = !r.error && JSON.stringify(r.output) === JSON.stringify(testCase.expected); return { test_id: r.test_id, passed, input: testCase.input, expected: testCase.expected, actual: r.output, error: r.error, }; }); setVisibleResults(testResults); } finally { setIsRunning(false); } }, [pyodide, isRunning, visibleTestCases, runTests]); const handleSubmit = useCallback(async () => { if (!pyodide || isSubmitting) return; setIsSubmitting(true); try { // Run visible tests first const visibleTests = visibleTestCases.map((tc) => ({ id: tc.id, input: tc.input, })); const visibleExecutionResults = await runTests(visibleTests); const visibleTestResults: TestResult[] = visibleExecutionResults.map( (r, idx) => { const testCase = visibleTestCases[idx]; const passed = !r.error && JSON.stringify(r.output) === JSON.stringify(testCase.expected); return { test_id: r.test_id, passed, input: testCase.input, expected: testCase.expected, actual: r.output, error: r.error, }; } ); setVisibleResults(visibleTestResults); // Run hidden tests const hiddenTests = hiddenTestInputs.map((tc) => ({ id: tc.id, input: tc.input, })); const hiddenExecutionResults = await runTests(hiddenTests); // Submit hidden outputs to server for validation const hiddenOutputs: HiddenTestOutput[] = hiddenExecutionResults.map( (r) => ({ test_id: r.test_id, output: r.output, }) ); const response = await submitSolution(slug, { code, hidden_outputs: hiddenOutputs, }); setHiddenPassedCount(response.total_passed); setHiddenTotalCount(response.total_tests); } catch (error) { console.error("Submission error:", error); } finally { setIsSubmitting(false); } }, [ pyodide, isSubmitting, visibleTestCases, hiddenTestInputs, runTests, slug, code, ]); const isLoading = isRunning || isSubmitting; return (
{pyodideLoading && ( Loading Python runtime... )}
); }