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) && (