@wakastellar/ui 2.1.2 → 2.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/blocks/apm-overview/index.d.ts +58 -0
- package/dist/blocks/cicd-builder/index.d.ts +47 -0
- package/dist/blocks/cloud-cost-dashboard/index.d.ts +49 -0
- package/dist/blocks/container-orchestrator/index.d.ts +63 -0
- package/dist/blocks/database-admin/index.d.ts +84 -0
- package/dist/blocks/gitops-sync-status/index.d.ts +45 -0
- package/dist/blocks/incident-manager/index.d.ts +44 -0
- package/dist/blocks/index.d.ts +10 -0
- package/dist/blocks/infrastructure-map/index.d.ts +32 -0
- package/dist/blocks/on-call-schedule/index.d.ts +43 -0
- package/dist/blocks/release-notes/index.d.ts +49 -0
- package/dist/components/index.d.ts +34 -0
- package/dist/components/waka-ad-banner/index.d.ts +36 -0
- package/dist/components/waka-ad-fallback/index.d.ts +33 -0
- package/dist/components/waka-ad-inline/index.d.ts +15 -0
- package/dist/components/waka-ad-interstitial/index.d.ts +26 -0
- package/dist/components/waka-ad-placeholder/index.d.ts +17 -0
- package/dist/components/waka-ad-provider/index.d.ts +103 -0
- package/dist/components/waka-ad-sidebar/index.d.ts +18 -0
- package/dist/components/waka-ad-sticky-footer/index.d.ts +17 -0
- package/dist/components/waka-alert-panel/index.d.ts +45 -0
- package/dist/components/waka-artifact-list/index.d.ts +32 -0
- package/dist/components/waka-build-matrix/index.d.ts +36 -0
- package/dist/components/waka-config-comparator/index.d.ts +37 -0
- package/dist/components/waka-container-list/index.d.ts +51 -0
- package/dist/components/waka-content-recommendation/index.d.ts +23 -0
- package/dist/components/waka-database-card/index.d.ts +46 -0
- package/dist/components/waka-dependency-tree/index.d.ts +38 -0
- package/dist/components/waka-env-var-editor/index.d.ts +30 -0
- package/dist/components/waka-feature-flag-row/index.d.ts +45 -0
- package/dist/components/waka-kubernetes-overview/index.d.ts +98 -0
- package/dist/components/waka-log-viewer/index.d.ts +38 -0
- package/dist/components/waka-migration-list/index.d.ts +36 -0
- package/dist/components/waka-outstream-video/index.d.ts +24 -0
- package/dist/components/waka-pod-card/index.d.ts +73 -0
- package/dist/components/waka-query-explain/index.d.ts +48 -0
- package/dist/components/waka-secret-card/index.d.ts +43 -0
- package/dist/components/waka-security-scan-result/index.d.ts +45 -0
- package/dist/components/waka-service-graph/index.d.ts +44 -0
- package/dist/components/waka-sponsored-badge/index.d.ts +20 -0
- package/dist/components/waka-sponsored-card/index.d.ts +25 -0
- package/dist/components/waka-sponsored-feed/index.d.ts +31 -0
- package/dist/components/waka-test-report/index.d.ts +60 -0
- package/dist/components/waka-trace-viewer/index.d.ts +36 -0
- package/dist/components/waka-video-ad/index.d.ts +32 -0
- package/dist/components/waka-video-overlay/index.d.ts +26 -0
- package/dist/index.cjs.js +251 -200
- package/dist/index.d.ts +1 -0
- package/dist/index.es.js +47315 -35823
- package/dist/utils/security.d.ts +96 -0
- package/package.json +4 -4
- package/src/blocks/apm-overview/index.tsx +672 -0
- package/src/blocks/cicd-builder/index.tsx +738 -0
- package/src/blocks/cloud-cost-dashboard/index.tsx +597 -0
- package/src/blocks/container-orchestrator/index.tsx +729 -0
- package/src/blocks/database-admin/index.tsx +679 -0
- package/src/blocks/gitops-sync-status/index.tsx +557 -0
- package/src/blocks/incident-manager/index.tsx +586 -0
- package/src/blocks/index.ts +119 -0
- package/src/blocks/infrastructure-map/index.tsx +638 -0
- package/src/blocks/on-call-schedule/index.tsx +615 -0
- package/src/blocks/release-notes/index.tsx +643 -0
- package/src/blocks/sidebar/index.tsx +6 -6
- package/src/components/DataTable/templates/index.tsx +3 -2
- package/src/components/index.ts +283 -0
- package/src/components/waka-3d-pie-chart/index.tsx +11 -11
- package/src/components/waka-achievement-unlock/index.tsx +16 -16
- package/src/components/waka-ad-banner/index.tsx +275 -0
- package/src/components/waka-ad-fallback/index.tsx +181 -0
- package/src/components/waka-ad-inline/index.tsx +103 -0
- package/src/components/waka-ad-interstitial/index.tsx +278 -0
- package/src/components/waka-ad-placeholder/index.tsx +84 -0
- package/src/components/waka-ad-provider/index.tsx +329 -0
- package/src/components/waka-ad-sidebar/index.tsx +113 -0
- package/src/components/waka-ad-sticky-footer/index.tsx +125 -0
- package/src/components/waka-alert-panel/index.tsx +493 -0
- package/src/components/waka-artifact-list/index.tsx +416 -0
- package/src/components/waka-badge-showcase/index.tsx +12 -11
- package/src/components/waka-build-matrix/index.tsx +396 -0
- package/src/components/waka-command-bar/index.tsx +2 -1
- package/src/components/waka-config-comparator/index.tsx +416 -0
- package/src/components/waka-container-list/index.tsx +475 -0
- package/src/components/waka-content-recommendation/index.tsx +294 -0
- package/src/components/waka-cost-breakdown/index.tsx +10 -10
- package/src/components/waka-database-card/index.tsx +473 -0
- package/src/components/waka-dependency-tree/index.tsx +542 -0
- package/src/components/waka-env-var-editor/index.tsx +417 -0
- package/src/components/waka-feature-flag-row/index.tsx +386 -0
- package/src/components/waka-funnel-chart/index.tsx +8 -8
- package/src/components/waka-health-pulse/index.tsx +6 -6
- package/src/components/waka-kubernetes-overview/index.tsx +536 -0
- package/src/components/waka-leaderboard/index.tsx +9 -9
- package/src/components/waka-log-viewer/index.tsx +386 -0
- package/src/components/waka-loot-box/index.tsx +20 -20
- package/src/components/waka-migration-list/index.tsx +487 -0
- package/src/components/waka-outstream-video/index.tsx +240 -0
- package/src/components/waka-player-card/index.tsx +5 -5
- package/src/components/waka-pod-card/index.tsx +528 -0
- package/src/components/waka-query-explain/index.tsx +657 -0
- package/src/components/waka-quota-bar/index.tsx +4 -4
- package/src/components/waka-radar-score/index.tsx +10 -10
- package/src/components/waka-scratch-card/index.tsx +5 -4
- package/src/components/waka-secret-card/index.tsx +371 -0
- package/src/components/waka-security-scan-result/index.tsx +473 -0
- package/src/components/waka-server-rack/index.tsx +28 -27
- package/src/components/waka-service-graph/index.tsx +445 -0
- package/src/components/waka-sponsored-badge/index.tsx +97 -0
- package/src/components/waka-sponsored-card/index.tsx +275 -0
- package/src/components/waka-sponsored-feed/index.tsx +127 -0
- package/src/components/waka-spotlight/index.tsx +2 -1
- package/src/components/waka-success-explosion/index.tsx +4 -4
- package/src/components/waka-test-report/index.tsx +469 -0
- package/src/components/waka-trace-viewer/index.tsx +490 -0
- package/src/components/waka-video-ad/index.tsx +406 -0
- package/src/components/waka-video-overlay/index.tsx +257 -0
- package/src/components/waka-xp-bar/index.tsx +13 -13
- package/src/styles/base.css +16 -0
- package/src/styles/tailwind.preset.js +12 -0
- package/src/styles/themes/forest.css +16 -0
- package/src/styles/themes/monochrome.css +16 -0
- package/src/styles/themes/perpetuity.css +16 -0
- package/src/styles/themes/sunset.css +16 -0
- package/src/styles/themes/twilight.css +16 -0
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { cn } from "../../utils/cn"
|
|
5
|
+
import { Badge } from "../badge"
|
|
6
|
+
import { Button } from "../button"
|
|
7
|
+
import { Progress } from "../progress"
|
|
8
|
+
import { ScrollArea } from "../scroll-area"
|
|
9
|
+
import { Input } from "../input"
|
|
10
|
+
import {
|
|
11
|
+
CheckCircle2,
|
|
12
|
+
XCircle,
|
|
13
|
+
SkipForward,
|
|
14
|
+
Clock,
|
|
15
|
+
ChevronDown,
|
|
16
|
+
ChevronRight,
|
|
17
|
+
Search,
|
|
18
|
+
FileText,
|
|
19
|
+
BarChart3,
|
|
20
|
+
Filter,
|
|
21
|
+
AlertTriangle,
|
|
22
|
+
} from "lucide-react"
|
|
23
|
+
|
|
24
|
+
export type TestStatus = "passed" | "failed" | "skipped" | "pending"
|
|
25
|
+
|
|
26
|
+
export interface TestCase {
|
|
27
|
+
id: string
|
|
28
|
+
name: string
|
|
29
|
+
status: TestStatus
|
|
30
|
+
duration: number // in ms
|
|
31
|
+
errorMessage?: string
|
|
32
|
+
stackTrace?: string
|
|
33
|
+
file?: string
|
|
34
|
+
line?: number
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface TestSuite {
|
|
38
|
+
id: string
|
|
39
|
+
name: string
|
|
40
|
+
file?: string
|
|
41
|
+
tests: TestCase[]
|
|
42
|
+
duration: number // in ms
|
|
43
|
+
timestamp?: Date
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface CoverageReport {
|
|
47
|
+
lines: { covered: number; total: number; percentage: number }
|
|
48
|
+
branches: { covered: number; total: number; percentage: number }
|
|
49
|
+
functions: { covered: number; total: number; percentage: number }
|
|
50
|
+
statements: { covered: number; total: number; percentage: number }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface WakaTestReportProps {
|
|
54
|
+
/** Test suites */
|
|
55
|
+
suites: TestSuite[]
|
|
56
|
+
/** Coverage report */
|
|
57
|
+
coverage?: CoverageReport
|
|
58
|
+
/** Title */
|
|
59
|
+
title?: string
|
|
60
|
+
/** Callback when clicking on a test */
|
|
61
|
+
onTestClick?: (test: TestCase) => void
|
|
62
|
+
/** Callback when viewing test file */
|
|
63
|
+
onViewFile?: (file: string, line?: number) => void
|
|
64
|
+
/** Show only failed tests */
|
|
65
|
+
showFailedOnly?: boolean
|
|
66
|
+
/** Custom class name */
|
|
67
|
+
className?: string
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const statusConfig: Record<TestStatus, { icon: React.ElementType; color: string; bgColor: string; label: string }> = {
|
|
71
|
+
passed: { icon: CheckCircle2, color: "text-green-500", bgColor: "bg-green-500/10", label: "Passed" },
|
|
72
|
+
failed: { icon: XCircle, color: "text-red-500", bgColor: "bg-red-500/10", label: "Failed" },
|
|
73
|
+
skipped: { icon: SkipForward, color: "text-gray-500", bgColor: "bg-gray-500/10", label: "Skipped" },
|
|
74
|
+
pending: { icon: Clock, color: "text-yellow-500", bgColor: "bg-yellow-500/10", label: "Pending" },
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function formatDuration(ms: number): string {
|
|
78
|
+
if (ms < 1000) return `${ms}ms`
|
|
79
|
+
const secs = ms / 1000
|
|
80
|
+
if (secs < 60) return `${secs.toFixed(2)}s`
|
|
81
|
+
const mins = Math.floor(secs / 60)
|
|
82
|
+
return `${mins}m ${(secs % 60).toFixed(0)}s`
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function CoverageBar({
|
|
86
|
+
label,
|
|
87
|
+
covered,
|
|
88
|
+
total,
|
|
89
|
+
percentage,
|
|
90
|
+
}: {
|
|
91
|
+
label: string
|
|
92
|
+
covered: number
|
|
93
|
+
total: number
|
|
94
|
+
percentage: number
|
|
95
|
+
}) {
|
|
96
|
+
const getColor = (pct: number) => {
|
|
97
|
+
if (pct >= 80) return "bg-green-500"
|
|
98
|
+
if (pct >= 50) return "bg-yellow-500"
|
|
99
|
+
return "bg-red-500"
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<div className="space-y-1">
|
|
104
|
+
<div className="flex items-center justify-between text-sm">
|
|
105
|
+
<span className="text-muted-foreground">{label}</span>
|
|
106
|
+
<span className="font-medium">
|
|
107
|
+
{covered}/{total} ({percentage.toFixed(1)}%)
|
|
108
|
+
</span>
|
|
109
|
+
</div>
|
|
110
|
+
<div className="h-2 bg-muted rounded-full overflow-hidden">
|
|
111
|
+
<div
|
|
112
|
+
className={cn("h-full transition-all", getColor(percentage))}
|
|
113
|
+
style={{ width: `${percentage}%` }}
|
|
114
|
+
/>
|
|
115
|
+
</div>
|
|
116
|
+
</div>
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function TestSuiteCard({
|
|
121
|
+
suite,
|
|
122
|
+
isExpanded,
|
|
123
|
+
onToggle,
|
|
124
|
+
onTestClick,
|
|
125
|
+
onViewFile,
|
|
126
|
+
searchQuery,
|
|
127
|
+
}: {
|
|
128
|
+
suite: TestSuite
|
|
129
|
+
isExpanded: boolean
|
|
130
|
+
onToggle: () => void
|
|
131
|
+
onTestClick?: (test: TestCase) => void
|
|
132
|
+
onViewFile?: (file: string, line?: number) => void
|
|
133
|
+
searchQuery: string
|
|
134
|
+
}) {
|
|
135
|
+
const stats = {
|
|
136
|
+
passed: suite.tests.filter((t) => t.status === "passed").length,
|
|
137
|
+
failed: suite.tests.filter((t) => t.status === "failed").length,
|
|
138
|
+
skipped: suite.tests.filter((t) => t.status === "skipped").length,
|
|
139
|
+
total: suite.tests.length,
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const allPassed = stats.failed === 0 && stats.passed > 0
|
|
143
|
+
const hasFailed = stats.failed > 0
|
|
144
|
+
|
|
145
|
+
// Filter tests by search query
|
|
146
|
+
const filteredTests = suite.tests.filter((test) =>
|
|
147
|
+
test.name.toLowerCase().includes(searchQuery.toLowerCase())
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
if (searchQuery && filteredTests.length === 0) return null
|
|
151
|
+
|
|
152
|
+
return (
|
|
153
|
+
<div className={cn("border rounded-lg overflow-hidden", hasFailed && "border-red-500/30")}>
|
|
154
|
+
<button
|
|
155
|
+
className={cn(
|
|
156
|
+
"w-full flex items-center gap-3 p-3 text-left hover:bg-muted/50 transition-colors",
|
|
157
|
+
hasFailed ? "bg-red-500/5" : allPassed ? "bg-green-500/5" : ""
|
|
158
|
+
)}
|
|
159
|
+
onClick={onToggle}
|
|
160
|
+
>
|
|
161
|
+
{isExpanded ? (
|
|
162
|
+
<ChevronDown className="h-4 w-4 text-muted-foreground shrink-0" />
|
|
163
|
+
) : (
|
|
164
|
+
<ChevronRight className="h-4 w-4 text-muted-foreground shrink-0" />
|
|
165
|
+
)}
|
|
166
|
+
|
|
167
|
+
<div className="flex-1 min-w-0">
|
|
168
|
+
<div className="font-medium truncate">{suite.name}</div>
|
|
169
|
+
{suite.file && (
|
|
170
|
+
<div className="text-xs text-muted-foreground truncate">{suite.file}</div>
|
|
171
|
+
)}
|
|
172
|
+
</div>
|
|
173
|
+
|
|
174
|
+
<div className="flex items-center gap-2 shrink-0">
|
|
175
|
+
{stats.passed > 0 && (
|
|
176
|
+
<Badge className="bg-green-500 text-xs">{stats.passed}</Badge>
|
|
177
|
+
)}
|
|
178
|
+
{stats.failed > 0 && (
|
|
179
|
+
<Badge className="bg-red-500 text-xs">{stats.failed}</Badge>
|
|
180
|
+
)}
|
|
181
|
+
{stats.skipped > 0 && (
|
|
182
|
+
<Badge variant="secondary" className="text-xs">{stats.skipped}</Badge>
|
|
183
|
+
)}
|
|
184
|
+
<span className="text-xs text-muted-foreground">{formatDuration(suite.duration)}</span>
|
|
185
|
+
</div>
|
|
186
|
+
</button>
|
|
187
|
+
|
|
188
|
+
{isExpanded && (
|
|
189
|
+
<div className="border-t">
|
|
190
|
+
{filteredTests.map((test) => {
|
|
191
|
+
const config = statusConfig[test.status]
|
|
192
|
+
const Icon = config.icon
|
|
193
|
+
|
|
194
|
+
return (
|
|
195
|
+
<div
|
|
196
|
+
key={test.id}
|
|
197
|
+
className={cn(
|
|
198
|
+
"flex items-start gap-3 p-3 border-b last:border-b-0 hover:bg-muted/30 cursor-pointer",
|
|
199
|
+
config.bgColor
|
|
200
|
+
)}
|
|
201
|
+
onClick={() => onTestClick?.(test)}
|
|
202
|
+
>
|
|
203
|
+
<Icon className={cn("h-4 w-4 mt-0.5 shrink-0", config.color)} />
|
|
204
|
+
|
|
205
|
+
<div className="flex-1 min-w-0">
|
|
206
|
+
<div className="text-sm font-medium">{test.name}</div>
|
|
207
|
+
{test.errorMessage && (
|
|
208
|
+
<div className="text-xs text-red-500 mt-1 line-clamp-2">{test.errorMessage}</div>
|
|
209
|
+
)}
|
|
210
|
+
{test.file && (
|
|
211
|
+
<button
|
|
212
|
+
className="text-xs text-primary hover:underline mt-1"
|
|
213
|
+
onClick={(e) => {
|
|
214
|
+
e.stopPropagation()
|
|
215
|
+
onViewFile?.(test.file!, test.line)
|
|
216
|
+
}}
|
|
217
|
+
>
|
|
218
|
+
{test.file}:{test.line}
|
|
219
|
+
</button>
|
|
220
|
+
)}
|
|
221
|
+
</div>
|
|
222
|
+
|
|
223
|
+
<span className="text-xs text-muted-foreground shrink-0">
|
|
224
|
+
{formatDuration(test.duration)}
|
|
225
|
+
</span>
|
|
226
|
+
</div>
|
|
227
|
+
)
|
|
228
|
+
})}
|
|
229
|
+
</div>
|
|
230
|
+
)}
|
|
231
|
+
</div>
|
|
232
|
+
)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export function WakaTestReport({
|
|
236
|
+
suites,
|
|
237
|
+
coverage,
|
|
238
|
+
title = "Test Report",
|
|
239
|
+
onTestClick,
|
|
240
|
+
onViewFile,
|
|
241
|
+
showFailedOnly = false,
|
|
242
|
+
className,
|
|
243
|
+
}: WakaTestReportProps) {
|
|
244
|
+
const [searchQuery, setSearchQuery] = React.useState("")
|
|
245
|
+
const [expandedSuites, setExpandedSuites] = React.useState<Set<string>>(new Set())
|
|
246
|
+
const [filterFailed, setFilterFailed] = React.useState(showFailedOnly)
|
|
247
|
+
|
|
248
|
+
// Calculate overall stats
|
|
249
|
+
const stats = React.useMemo(() => {
|
|
250
|
+
let passed = 0
|
|
251
|
+
let failed = 0
|
|
252
|
+
let skipped = 0
|
|
253
|
+
let duration = 0
|
|
254
|
+
|
|
255
|
+
suites.forEach((suite) => {
|
|
256
|
+
suite.tests.forEach((test) => {
|
|
257
|
+
if (test.status === "passed") passed++
|
|
258
|
+
if (test.status === "failed") failed++
|
|
259
|
+
if (test.status === "skipped") skipped++
|
|
260
|
+
})
|
|
261
|
+
duration += suite.duration
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
return { passed, failed, skipped, total: passed + failed + skipped, duration }
|
|
265
|
+
}, [suites])
|
|
266
|
+
|
|
267
|
+
const toggleSuite = (id: string) => {
|
|
268
|
+
setExpandedSuites((prev) => {
|
|
269
|
+
const next = new Set(prev)
|
|
270
|
+
if (next.has(id)) {
|
|
271
|
+
next.delete(id)
|
|
272
|
+
} else {
|
|
273
|
+
next.add(id)
|
|
274
|
+
}
|
|
275
|
+
return next
|
|
276
|
+
})
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const expandAll = () => {
|
|
280
|
+
setExpandedSuites(new Set(suites.map((s) => s.id)))
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const collapseAll = () => {
|
|
284
|
+
setExpandedSuites(new Set())
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Filter suites
|
|
288
|
+
const filteredSuites = filterFailed
|
|
289
|
+
? suites.filter((s) => s.tests.some((t) => t.status === "failed"))
|
|
290
|
+
: suites
|
|
291
|
+
|
|
292
|
+
return (
|
|
293
|
+
<div className={cn("flex flex-col border rounded-lg bg-background", className)}>
|
|
294
|
+
{/* Header */}
|
|
295
|
+
<div className="flex items-center justify-between gap-4 p-3 border-b">
|
|
296
|
+
<div className="flex items-center gap-3">
|
|
297
|
+
<FileText className="h-5 w-5" />
|
|
298
|
+
<h3 className="font-semibold">{title}</h3>
|
|
299
|
+
</div>
|
|
300
|
+
|
|
301
|
+
<div className="flex items-center gap-2">
|
|
302
|
+
{stats.passed > 0 && (
|
|
303
|
+
<Badge className="bg-green-500">{stats.passed} passed</Badge>
|
|
304
|
+
)}
|
|
305
|
+
{stats.failed > 0 && (
|
|
306
|
+
<Badge className="bg-red-500">{stats.failed} failed</Badge>
|
|
307
|
+
)}
|
|
308
|
+
{stats.skipped > 0 && (
|
|
309
|
+
<Badge variant="secondary">{stats.skipped} skipped</Badge>
|
|
310
|
+
)}
|
|
311
|
+
<span className="text-sm text-muted-foreground">{formatDuration(stats.duration)}</span>
|
|
312
|
+
</div>
|
|
313
|
+
</div>
|
|
314
|
+
|
|
315
|
+
{/* Coverage */}
|
|
316
|
+
{coverage && (
|
|
317
|
+
<div className="p-4 border-b bg-muted/30">
|
|
318
|
+
<div className="flex items-center gap-2 mb-3">
|
|
319
|
+
<BarChart3 className="h-4 w-4" />
|
|
320
|
+
<span className="font-medium text-sm">Code Coverage</span>
|
|
321
|
+
</div>
|
|
322
|
+
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
|
323
|
+
<CoverageBar
|
|
324
|
+
label="Lines"
|
|
325
|
+
covered={coverage.lines.covered}
|
|
326
|
+
total={coverage.lines.total}
|
|
327
|
+
percentage={coverage.lines.percentage}
|
|
328
|
+
/>
|
|
329
|
+
<CoverageBar
|
|
330
|
+
label="Branches"
|
|
331
|
+
covered={coverage.branches.covered}
|
|
332
|
+
total={coverage.branches.total}
|
|
333
|
+
percentage={coverage.branches.percentage}
|
|
334
|
+
/>
|
|
335
|
+
<CoverageBar
|
|
336
|
+
label="Functions"
|
|
337
|
+
covered={coverage.functions.covered}
|
|
338
|
+
total={coverage.functions.total}
|
|
339
|
+
percentage={coverage.functions.percentage}
|
|
340
|
+
/>
|
|
341
|
+
<CoverageBar
|
|
342
|
+
label="Statements"
|
|
343
|
+
covered={coverage.statements.covered}
|
|
344
|
+
total={coverage.statements.total}
|
|
345
|
+
percentage={coverage.statements.percentage}
|
|
346
|
+
/>
|
|
347
|
+
</div>
|
|
348
|
+
</div>
|
|
349
|
+
)}
|
|
350
|
+
|
|
351
|
+
{/* Toolbar */}
|
|
352
|
+
<div className="flex items-center gap-2 p-2 border-b">
|
|
353
|
+
<div className="relative flex-1 max-w-sm">
|
|
354
|
+
<Search className="absolute left-2.5 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
|
355
|
+
<Input
|
|
356
|
+
placeholder="Search tests..."
|
|
357
|
+
value={searchQuery}
|
|
358
|
+
onChange={(e) => setSearchQuery(e.target.value)}
|
|
359
|
+
className="pl-8 h-8"
|
|
360
|
+
/>
|
|
361
|
+
</div>
|
|
362
|
+
|
|
363
|
+
<Button
|
|
364
|
+
variant={filterFailed ? "default" : "outline"}
|
|
365
|
+
size="sm"
|
|
366
|
+
className="h-8"
|
|
367
|
+
onClick={() => setFilterFailed(!filterFailed)}
|
|
368
|
+
>
|
|
369
|
+
<AlertTriangle className="h-4 w-4 mr-1" />
|
|
370
|
+
Failed only
|
|
371
|
+
</Button>
|
|
372
|
+
|
|
373
|
+
<div className="flex items-center gap-1">
|
|
374
|
+
<Button variant="ghost" size="sm" className="h-8" onClick={expandAll}>
|
|
375
|
+
Expand All
|
|
376
|
+
</Button>
|
|
377
|
+
<Button variant="ghost" size="sm" className="h-8" onClick={collapseAll}>
|
|
378
|
+
Collapse All
|
|
379
|
+
</Button>
|
|
380
|
+
</div>
|
|
381
|
+
</div>
|
|
382
|
+
|
|
383
|
+
{/* Test suites */}
|
|
384
|
+
<ScrollArea className="flex-1 max-h-[500px]">
|
|
385
|
+
<div className="p-3 space-y-2">
|
|
386
|
+
{filteredSuites.length === 0 ? (
|
|
387
|
+
<div className="flex flex-col items-center justify-center h-32 text-muted-foreground">
|
|
388
|
+
<CheckCircle2 className="h-8 w-8 mb-2" />
|
|
389
|
+
<span>No tests to display</span>
|
|
390
|
+
</div>
|
|
391
|
+
) : (
|
|
392
|
+
filteredSuites.map((suite) => (
|
|
393
|
+
<TestSuiteCard
|
|
394
|
+
key={suite.id}
|
|
395
|
+
suite={suite}
|
|
396
|
+
isExpanded={expandedSuites.has(suite.id)}
|
|
397
|
+
onToggle={() => toggleSuite(suite.id)}
|
|
398
|
+
onTestClick={onTestClick}
|
|
399
|
+
onViewFile={onViewFile}
|
|
400
|
+
searchQuery={searchQuery}
|
|
401
|
+
/>
|
|
402
|
+
))
|
|
403
|
+
)}
|
|
404
|
+
</div>
|
|
405
|
+
</ScrollArea>
|
|
406
|
+
|
|
407
|
+
{/* Footer */}
|
|
408
|
+
<div className="flex items-center justify-between px-3 py-2 border-t bg-muted/30 text-sm">
|
|
409
|
+
<span className="text-muted-foreground">
|
|
410
|
+
{suites.length} test suites, {stats.total} tests
|
|
411
|
+
</span>
|
|
412
|
+
<span className={cn(
|
|
413
|
+
"font-medium",
|
|
414
|
+
stats.failed > 0 ? "text-red-500" : "text-green-500"
|
|
415
|
+
)}>
|
|
416
|
+
{stats.failed > 0
|
|
417
|
+
? `${stats.failed} tests failed`
|
|
418
|
+
: "All tests passed"}
|
|
419
|
+
</span>
|
|
420
|
+
</div>
|
|
421
|
+
</div>
|
|
422
|
+
)
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Default sample data for demo
|
|
426
|
+
export const defaultTestSuites: TestSuite[] = [
|
|
427
|
+
{
|
|
428
|
+
id: "1",
|
|
429
|
+
name: "Authentication",
|
|
430
|
+
file: "src/auth/__tests__/auth.test.ts",
|
|
431
|
+
duration: 2450,
|
|
432
|
+
tests: [
|
|
433
|
+
{ id: "1-1", name: "should login with valid credentials", status: "passed", duration: 245, file: "src/auth/__tests__/auth.test.ts", line: 15 },
|
|
434
|
+
{ id: "1-2", name: "should reject invalid password", status: "passed", duration: 128, file: "src/auth/__tests__/auth.test.ts", line: 32 },
|
|
435
|
+
{ id: "1-3", name: "should refresh token before expiry", status: "passed", duration: 512, file: "src/auth/__tests__/auth.test.ts", line: 48 },
|
|
436
|
+
{ id: "1-4", name: "should handle logout correctly", status: "passed", duration: 89, file: "src/auth/__tests__/auth.test.ts", line: 65 },
|
|
437
|
+
],
|
|
438
|
+
},
|
|
439
|
+
{
|
|
440
|
+
id: "2",
|
|
441
|
+
name: "User Service",
|
|
442
|
+
file: "src/users/__tests__/users.test.ts",
|
|
443
|
+
duration: 3200,
|
|
444
|
+
tests: [
|
|
445
|
+
{ id: "2-1", name: "should create a new user", status: "passed", duration: 345 },
|
|
446
|
+
{ id: "2-2", name: "should update user profile", status: "failed", duration: 156, errorMessage: "Expected status 200 but got 500", stackTrace: "Error: Request failed\n at UserService.update (src/users/service.ts:45)" },
|
|
447
|
+
{ id: "2-3", name: "should delete user", status: "passed", duration: 234 },
|
|
448
|
+
{ id: "2-4", name: "should list users with pagination", status: "skipped", duration: 0 },
|
|
449
|
+
],
|
|
450
|
+
},
|
|
451
|
+
{
|
|
452
|
+
id: "3",
|
|
453
|
+
name: "API Integration",
|
|
454
|
+
file: "src/api/__tests__/integration.test.ts",
|
|
455
|
+
duration: 8500,
|
|
456
|
+
tests: [
|
|
457
|
+
{ id: "3-1", name: "should handle concurrent requests", status: "passed", duration: 2500 },
|
|
458
|
+
{ id: "3-2", name: "should retry on network failure", status: "passed", duration: 1800 },
|
|
459
|
+
{ id: "3-3", name: "should timeout after 30s", status: "passed", duration: 3200 },
|
|
460
|
+
],
|
|
461
|
+
},
|
|
462
|
+
]
|
|
463
|
+
|
|
464
|
+
export const defaultCoverage: CoverageReport = {
|
|
465
|
+
lines: { covered: 1245, total: 1500, percentage: 83.0 },
|
|
466
|
+
branches: { covered: 89, total: 120, percentage: 74.2 },
|
|
467
|
+
functions: { covered: 156, total: 180, percentage: 86.7 },
|
|
468
|
+
statements: { covered: 1380, total: 1650, percentage: 83.6 },
|
|
469
|
+
}
|