Some checks failed
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-school (push) Successful in 42s
CI / test-go-edu-search (push) Successful in 34s
CI / test-python-klausur (push) Failing after 2m51s
CI / test-python-agent-core (push) Successful in 21s
CI / test-nodejs-website (push) Successful in 29s
sed replacement left orphaned hostname references in story page and empty lines in getApiBase functions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
157 lines
5.9 KiB
TypeScript
157 lines
5.9 KiB
TypeScript
'use client'
|
|
|
|
import type { ServiceTestInfo } from '../types'
|
|
|
|
export interface ServiceProgress {
|
|
current_file: string
|
|
files_done: number
|
|
files_total: number
|
|
passed: number
|
|
failed: number
|
|
status: string
|
|
}
|
|
|
|
export function ServiceTestCard({
|
|
service,
|
|
onRun,
|
|
isRunning,
|
|
progress,
|
|
}: {
|
|
service: ServiceTestInfo
|
|
onRun: (service: string) => void
|
|
isRunning: boolean
|
|
progress?: ServiceProgress
|
|
}) {
|
|
const passRate = service.total_tests > 0 ? (service.passed_tests / service.total_tests) * 100 : 0
|
|
|
|
const getLanguageIcon = (lang: string) => {
|
|
switch (lang) {
|
|
case 'go':
|
|
return '🐹'
|
|
case 'python':
|
|
return '🐍'
|
|
case 'typescript':
|
|
return '📘'
|
|
case 'mixed':
|
|
return '🔀'
|
|
default:
|
|
return '📦'
|
|
}
|
|
}
|
|
|
|
const getStatusColor = (status: string) => {
|
|
switch (status) {
|
|
case 'passed':
|
|
return 'bg-emerald-100 text-emerald-700'
|
|
case 'failed':
|
|
return 'bg-red-100 text-red-700'
|
|
case 'running':
|
|
return 'bg-blue-100 text-blue-700'
|
|
default:
|
|
return 'bg-slate-100 text-slate-700'
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="bg-white rounded-xl border border-slate-200 p-5 hover:border-orange-300 transition-colors">
|
|
<div className="flex items-start justify-between mb-4">
|
|
<div className="flex items-center gap-3">
|
|
<span className="text-2xl">{getLanguageIcon(service.language)}</span>
|
|
<div>
|
|
<h3 className="font-semibold text-slate-900">{service.display_name}</h3>
|
|
<p className="text-xs text-slate-500">
|
|
{service.port ? `Port ${service.port}` : 'Library'} • {service.language}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<span className={`px-2 py-1 rounded text-xs font-medium ${getStatusColor(service.status)}`}>
|
|
{service.status === 'passed' ? 'Bestanden' : service.status === 'failed' ? 'Fehler' : 'Ausstehend'}
|
|
</span>
|
|
</div>
|
|
|
|
<div className="space-y-3">
|
|
<div>
|
|
<div className="flex items-center justify-between text-sm mb-1">
|
|
<span className="text-slate-600">Pass Rate</span>
|
|
<span className="font-medium text-slate-900">{passRate.toFixed(0)}%</span>
|
|
</div>
|
|
<div className="h-2 bg-slate-100 rounded-full overflow-hidden">
|
|
<div
|
|
className={`h-full rounded-full transition-all ${
|
|
passRate >= 80 ? 'bg-emerald-500' : passRate >= 60 ? 'bg-amber-500' : 'bg-red-500'
|
|
}`}
|
|
style={{ width: `${passRate}%` }}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-3 gap-2 text-center">
|
|
<div className="p-2 bg-slate-50 rounded-lg">
|
|
<p className="text-lg font-bold text-slate-900">{service.total_tests}</p>
|
|
<p className="text-xs text-slate-500">Tests</p>
|
|
</div>
|
|
<div className="p-2 bg-emerald-50 rounded-lg">
|
|
<p className="text-lg font-bold text-emerald-600">{service.passed_tests}</p>
|
|
<p className="text-xs text-slate-500">Bestanden</p>
|
|
</div>
|
|
<div className="p-2 bg-red-50 rounded-lg">
|
|
<p className="text-lg font-bold text-red-600">{service.failed_tests}</p>
|
|
<p className="text-xs text-slate-500">Fehler</p>
|
|
</div>
|
|
</div>
|
|
|
|
{service.coverage_percent && (
|
|
<div className="flex items-center justify-between text-sm pt-2 border-t border-slate-100">
|
|
<span className="text-slate-600">Coverage</span>
|
|
<span className={`font-medium ${service.coverage_percent >= 70 ? 'text-emerald-600' : 'text-amber-600'}`}>
|
|
{service.coverage_percent.toFixed(1)}%
|
|
</span>
|
|
</div>
|
|
)}
|
|
|
|
{/* Progress-Anzeige wenn Tests laufen */}
|
|
{isRunning && progress && progress.status === 'running' && (
|
|
<div className="mb-3 p-3 bg-orange-50 rounded-lg border border-orange-200">
|
|
<div className="flex items-center justify-between text-xs text-orange-700 mb-2">
|
|
<span className="font-mono truncate max-w-[180px]">{progress.current_file || 'Starte...'}</span>
|
|
<span>{progress.files_done}/{progress.files_total} Dateien</span>
|
|
</div>
|
|
<div className="h-1.5 bg-orange-100 rounded-full overflow-hidden">
|
|
<div
|
|
className="h-full bg-orange-500 rounded-full transition-all"
|
|
style={{ width: `${progress.files_total > 0 ? (progress.files_done / progress.files_total) * 100 : 0}%` }}
|
|
/>
|
|
</div>
|
|
<div className="flex items-center justify-between mt-2 text-xs">
|
|
<span className="text-emerald-600 font-medium">{progress.passed} bestanden</span>
|
|
<span className="text-red-600 font-medium">{progress.failed} fehler</span>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
<button
|
|
onClick={() => onRun(service.service)}
|
|
disabled={isRunning}
|
|
className={`w-full py-2 rounded-lg text-sm font-medium transition-all ${
|
|
isRunning
|
|
? 'bg-orange-100 text-orange-600 cursor-wait'
|
|
: 'bg-orange-600 text-white hover:bg-orange-700 active:scale-98'
|
|
}`}
|
|
>
|
|
{isRunning ? (
|
|
<span className="flex items-center justify-center gap-2">
|
|
<svg className="animate-spin h-4 w-4" fill="none" viewBox="0 0 24 24">
|
|
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
|
|
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
|
|
</svg>
|
|
{progress && progress.status === 'running' ? `${progress.passed + progress.failed} Tests...` : 'Laeuft...'}
|
|
</span>
|
|
) : (
|
|
'Tests starten'
|
|
)}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|