mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 11:51:14 -05:00
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.
This commit is contained in:
@@ -14,8 +14,8 @@ import { ScrollArea } from '@/components/ui/scroll-area';
|
|||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
|
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
|
||||||
import { useSuperuserGuard } from '@/hooks/useSuperuserGuard';
|
import { useSuperuserGuard } from '@/hooks/useSuperuserGuard';
|
||||||
import { IntegrationTestRunner as TestRunner, allTestSuites, type TestResult } from '@/lib/integrationTests';
|
import { IntegrationTestRunner as TestRunner, allTestSuites, type TestResult, formatResultsAsMarkdown, formatSingleTestAsMarkdown } from '@/lib/integrationTests';
|
||||||
import { Play, Square, Download, ChevronDown, CheckCircle2, XCircle, Clock, SkipForward } from 'lucide-react';
|
import { Play, Square, Download, ChevronDown, CheckCircle2, XCircle, Clock, SkipForward, Copy, ClipboardX } from 'lucide-react';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { handleError } from '@/lib/errorHandler';
|
import { handleError } from '@/lib/errorHandler';
|
||||||
|
|
||||||
@@ -105,6 +105,38 @@ export function IntegrationTestRunner() {
|
|||||||
toast.success('Test results exported');
|
toast.success('Test results exported');
|
||||||
}, [runner]);
|
}, [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
|
// Guard is handled by the route/page, no loading state needed here
|
||||||
|
|
||||||
const summary = runner.getSummary();
|
const summary = runner.getSummary();
|
||||||
@@ -166,10 +198,22 @@ export function IntegrationTestRunner() {
|
|||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{results.length > 0 && !isRunning && (
|
{results.length > 0 && !isRunning && (
|
||||||
|
<>
|
||||||
<Button onClick={exportResults} variant="outline">
|
<Button onClick={exportResults} variant="outline">
|
||||||
<Download className="w-4 h-4 mr-2" />
|
<Download className="w-4 h-4 mr-2" />
|
||||||
Export Results
|
Export JSON
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button onClick={copyAllResults} variant="outline">
|
||||||
|
<Copy className="w-4 h-4 mr-2" />
|
||||||
|
Copy All
|
||||||
|
</Button>
|
||||||
|
{summary.failed > 0 && (
|
||||||
|
<Button onClick={copyFailedTests} variant="outline">
|
||||||
|
<ClipboardX className="w-4 h-4 mr-2" />
|
||||||
|
Copy Failed ({summary.failed})
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -236,6 +280,14 @@ export function IntegrationTestRunner() {
|
|||||||
<Badge variant="outline" className="text-xs">
|
<Badge variant="outline" className="text-xs">
|
||||||
{result.duration}ms
|
{result.duration}ms
|
||||||
</Badge>
|
</Badge>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
className="h-6 w-6 p-0"
|
||||||
|
onClick={() => copyTestResult(result)}
|
||||||
|
>
|
||||||
|
<Copy className="h-3 w-3" />
|
||||||
|
</Button>
|
||||||
{(result.error || result.details) && (
|
{(result.error || result.details) && (
|
||||||
<CollapsibleTrigger asChild>
|
<CollapsibleTrigger asChild>
|
||||||
<Button variant="ghost" size="sm" className="h-6 w-6 p-0">
|
<Button variant="ghost" size="sm" className="h-6 w-6 p-0">
|
||||||
|
|||||||
76
src/lib/integrationTests/formatters.ts
Normal file
76
src/lib/integrationTests/formatters.ts
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
/**
|
||||||
|
* Test Result Formatters
|
||||||
|
*
|
||||||
|
* Utilities for formatting test results into different formats for easy sharing and debugging.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { TestResult } from './testRunner';
|
||||||
|
|
||||||
|
export function formatResultsAsMarkdown(
|
||||||
|
results: TestResult[],
|
||||||
|
summary: { total: number; passed: number; failed: number; skipped: number; totalDuration: number },
|
||||||
|
failedOnly: boolean = false
|
||||||
|
): string {
|
||||||
|
const timestamp = new Date().toISOString();
|
||||||
|
const title = failedOnly ? 'Failed Test Results' : 'Test Results';
|
||||||
|
|
||||||
|
let markdown = `# ${title} - ${timestamp}\n\n`;
|
||||||
|
|
||||||
|
// Summary section
|
||||||
|
markdown += `## Summary\n`;
|
||||||
|
markdown += `✅ Passed: ${summary.passed}\n`;
|
||||||
|
markdown += `❌ Failed: ${summary.failed}\n`;
|
||||||
|
markdown += `⏭️ Skipped: ${summary.skipped}\n`;
|
||||||
|
markdown += `⏱️ Duration: ${(summary.totalDuration / 1000).toFixed(2)}s\n\n`;
|
||||||
|
|
||||||
|
// Results by status
|
||||||
|
if (!failedOnly && summary.failed > 0) {
|
||||||
|
markdown += `## Failed Tests\n\n`;
|
||||||
|
results.filter(r => r.status === 'fail').forEach(result => {
|
||||||
|
markdown += formatTestResultMarkdown(result);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (failedOnly) {
|
||||||
|
results.forEach(result => {
|
||||||
|
markdown += formatTestResultMarkdown(result);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Include passed tests in summary
|
||||||
|
if (summary.passed > 0) {
|
||||||
|
markdown += `## Passed Tests\n\n`;
|
||||||
|
results.filter(r => r.status === 'pass').forEach(result => {
|
||||||
|
markdown += `### ✅ ${result.name} (${result.suite})\n`;
|
||||||
|
markdown += `**Duration:** ${result.duration}ms\n\n`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return markdown;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatSingleTestAsMarkdown(result: TestResult): string {
|
||||||
|
return formatTestResultMarkdown(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatTestResultMarkdown(result: TestResult): string {
|
||||||
|
const icon = result.status === 'fail' ? '❌' : result.status === 'pass' ? '✅' : '⏭️';
|
||||||
|
|
||||||
|
let markdown = `### ${icon} ${result.name} (${result.suite})\n`;
|
||||||
|
markdown += `**Duration:** ${result.duration}ms\n`;
|
||||||
|
markdown += `**Status:** ${result.status}\n`;
|
||||||
|
|
||||||
|
if (result.error) {
|
||||||
|
markdown += `**Error:** ${result.error}\n\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.stack) {
|
||||||
|
markdown += `**Stack Trace:**\n\`\`\`\n${result.stack}\n\`\`\`\n\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.details) {
|
||||||
|
markdown += `**Details:**\n\`\`\`json\n${JSON.stringify(result.details, null, 2)}\n\`\`\`\n\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return markdown;
|
||||||
|
}
|
||||||
@@ -6,5 +6,6 @@
|
|||||||
|
|
||||||
export { IntegrationTestRunner } from './testRunner';
|
export { IntegrationTestRunner } from './testRunner';
|
||||||
export { allTestSuites } from './suites';
|
export { allTestSuites } from './suites';
|
||||||
|
export { formatResultsAsMarkdown, formatSingleTestAsMarkdown } from './formatters';
|
||||||
|
|
||||||
export type { TestResult, Test, TestSuite } from './testRunner';
|
export type { TestResult, Test, TestSuite } from './testRunner';
|
||||||
|
|||||||
Reference in New Issue
Block a user