From 3cb0f66064cc39ea35cd73a01212031c579465b1 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Mon, 10 Nov 2025 16:48:51 +0000 Subject: [PATCH] Make test results copyable Add Markdown formatting utilities for test results, wire up clipboard copy in IntegrationTestRunner, and export new formatters. Introduce formatters.ts, extend index.ts exports, and implement copy all / copy failed / per-test copy functionality with updated UI. --- .../admin/IntegrationTestRunner.tsx | 64 ++++++++++++++-- src/lib/integrationTests/formatters.ts | 76 +++++++++++++++++++ src/lib/integrationTests/index.ts | 1 + 3 files changed, 135 insertions(+), 6 deletions(-) create mode 100644 src/lib/integrationTests/formatters.ts diff --git a/src/components/admin/IntegrationTestRunner.tsx b/src/components/admin/IntegrationTestRunner.tsx index 19a72e79..4cc2c415 100644 --- a/src/components/admin/IntegrationTestRunner.tsx +++ b/src/components/admin/IntegrationTestRunner.tsx @@ -14,8 +14,8 @@ import { ScrollArea } from '@/components/ui/scroll-area'; import { Badge } from '@/components/ui/badge'; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'; import { useSuperuserGuard } from '@/hooks/useSuperuserGuard'; -import { IntegrationTestRunner as TestRunner, allTestSuites, type TestResult } from '@/lib/integrationTests'; -import { Play, Square, Download, ChevronDown, CheckCircle2, XCircle, Clock, SkipForward } from 'lucide-react'; +import { IntegrationTestRunner as TestRunner, allTestSuites, type TestResult, formatResultsAsMarkdown, formatSingleTestAsMarkdown } from '@/lib/integrationTests'; +import { Play, Square, Download, ChevronDown, CheckCircle2, XCircle, Clock, SkipForward, Copy, ClipboardX } from 'lucide-react'; import { toast } from 'sonner'; import { handleError } from '@/lib/errorHandler'; @@ -105,6 +105,38 @@ export function IntegrationTestRunner() { toast.success('Test results exported'); }, [runner]); + const copyAllResults = useCallback(async () => { + const summary = runner.getSummary(); + const results = runner.getResults(); + + const markdown = formatResultsAsMarkdown(results, summary); + + await navigator.clipboard.writeText(markdown); + toast.success('All test results copied to clipboard'); + }, [runner]); + + const copyFailedTests = useCallback(async () => { + const summary = runner.getSummary(); + const failedResults = runner.getResults().filter(r => r.status === 'fail'); + + if (failedResults.length === 0) { + toast.info('No failed tests to copy'); + return; + } + + const markdown = formatResultsAsMarkdown(failedResults, summary, true); + + await navigator.clipboard.writeText(markdown); + toast.success(`${failedResults.length} failed test(s) copied to clipboard`); + }, [runner]); + + const copyTestResult = useCallback(async (result: TestResult) => { + const markdown = formatSingleTestAsMarkdown(result); + + await navigator.clipboard.writeText(markdown); + toast.success('Test result copied to clipboard'); + }, []); + // Guard is handled by the route/page, no loading state needed here const summary = runner.getSummary(); @@ -166,10 +198,22 @@ export function IntegrationTestRunner() { )} {results.length > 0 && !isRunning && ( - + <> + + + {summary.failed > 0 && ( + + )} + )} @@ -236,6 +280,14 @@ export function IntegrationTestRunner() { {result.duration}ms + {(result.error || result.details) && (