@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.
Files changed (123) hide show
  1. package/dist/blocks/apm-overview/index.d.ts +58 -0
  2. package/dist/blocks/cicd-builder/index.d.ts +47 -0
  3. package/dist/blocks/cloud-cost-dashboard/index.d.ts +49 -0
  4. package/dist/blocks/container-orchestrator/index.d.ts +63 -0
  5. package/dist/blocks/database-admin/index.d.ts +84 -0
  6. package/dist/blocks/gitops-sync-status/index.d.ts +45 -0
  7. package/dist/blocks/incident-manager/index.d.ts +44 -0
  8. package/dist/blocks/index.d.ts +10 -0
  9. package/dist/blocks/infrastructure-map/index.d.ts +32 -0
  10. package/dist/blocks/on-call-schedule/index.d.ts +43 -0
  11. package/dist/blocks/release-notes/index.d.ts +49 -0
  12. package/dist/components/index.d.ts +34 -0
  13. package/dist/components/waka-ad-banner/index.d.ts +36 -0
  14. package/dist/components/waka-ad-fallback/index.d.ts +33 -0
  15. package/dist/components/waka-ad-inline/index.d.ts +15 -0
  16. package/dist/components/waka-ad-interstitial/index.d.ts +26 -0
  17. package/dist/components/waka-ad-placeholder/index.d.ts +17 -0
  18. package/dist/components/waka-ad-provider/index.d.ts +103 -0
  19. package/dist/components/waka-ad-sidebar/index.d.ts +18 -0
  20. package/dist/components/waka-ad-sticky-footer/index.d.ts +17 -0
  21. package/dist/components/waka-alert-panel/index.d.ts +45 -0
  22. package/dist/components/waka-artifact-list/index.d.ts +32 -0
  23. package/dist/components/waka-build-matrix/index.d.ts +36 -0
  24. package/dist/components/waka-config-comparator/index.d.ts +37 -0
  25. package/dist/components/waka-container-list/index.d.ts +51 -0
  26. package/dist/components/waka-content-recommendation/index.d.ts +23 -0
  27. package/dist/components/waka-database-card/index.d.ts +46 -0
  28. package/dist/components/waka-dependency-tree/index.d.ts +38 -0
  29. package/dist/components/waka-env-var-editor/index.d.ts +30 -0
  30. package/dist/components/waka-feature-flag-row/index.d.ts +45 -0
  31. package/dist/components/waka-kubernetes-overview/index.d.ts +98 -0
  32. package/dist/components/waka-log-viewer/index.d.ts +38 -0
  33. package/dist/components/waka-migration-list/index.d.ts +36 -0
  34. package/dist/components/waka-outstream-video/index.d.ts +24 -0
  35. package/dist/components/waka-pod-card/index.d.ts +73 -0
  36. package/dist/components/waka-query-explain/index.d.ts +48 -0
  37. package/dist/components/waka-secret-card/index.d.ts +43 -0
  38. package/dist/components/waka-security-scan-result/index.d.ts +45 -0
  39. package/dist/components/waka-service-graph/index.d.ts +44 -0
  40. package/dist/components/waka-sponsored-badge/index.d.ts +20 -0
  41. package/dist/components/waka-sponsored-card/index.d.ts +25 -0
  42. package/dist/components/waka-sponsored-feed/index.d.ts +31 -0
  43. package/dist/components/waka-test-report/index.d.ts +60 -0
  44. package/dist/components/waka-trace-viewer/index.d.ts +36 -0
  45. package/dist/components/waka-video-ad/index.d.ts +32 -0
  46. package/dist/components/waka-video-overlay/index.d.ts +26 -0
  47. package/dist/index.cjs.js +251 -200
  48. package/dist/index.d.ts +1 -0
  49. package/dist/index.es.js +47315 -35823
  50. package/dist/utils/security.d.ts +96 -0
  51. package/package.json +4 -4
  52. package/src/blocks/apm-overview/index.tsx +672 -0
  53. package/src/blocks/cicd-builder/index.tsx +738 -0
  54. package/src/blocks/cloud-cost-dashboard/index.tsx +597 -0
  55. package/src/blocks/container-orchestrator/index.tsx +729 -0
  56. package/src/blocks/database-admin/index.tsx +679 -0
  57. package/src/blocks/gitops-sync-status/index.tsx +557 -0
  58. package/src/blocks/incident-manager/index.tsx +586 -0
  59. package/src/blocks/index.ts +119 -0
  60. package/src/blocks/infrastructure-map/index.tsx +638 -0
  61. package/src/blocks/on-call-schedule/index.tsx +615 -0
  62. package/src/blocks/release-notes/index.tsx +643 -0
  63. package/src/blocks/sidebar/index.tsx +6 -6
  64. package/src/components/DataTable/templates/index.tsx +3 -2
  65. package/src/components/index.ts +283 -0
  66. package/src/components/waka-3d-pie-chart/index.tsx +11 -11
  67. package/src/components/waka-achievement-unlock/index.tsx +16 -16
  68. package/src/components/waka-ad-banner/index.tsx +275 -0
  69. package/src/components/waka-ad-fallback/index.tsx +181 -0
  70. package/src/components/waka-ad-inline/index.tsx +103 -0
  71. package/src/components/waka-ad-interstitial/index.tsx +278 -0
  72. package/src/components/waka-ad-placeholder/index.tsx +84 -0
  73. package/src/components/waka-ad-provider/index.tsx +329 -0
  74. package/src/components/waka-ad-sidebar/index.tsx +113 -0
  75. package/src/components/waka-ad-sticky-footer/index.tsx +125 -0
  76. package/src/components/waka-alert-panel/index.tsx +493 -0
  77. package/src/components/waka-artifact-list/index.tsx +416 -0
  78. package/src/components/waka-badge-showcase/index.tsx +12 -11
  79. package/src/components/waka-build-matrix/index.tsx +396 -0
  80. package/src/components/waka-command-bar/index.tsx +2 -1
  81. package/src/components/waka-config-comparator/index.tsx +416 -0
  82. package/src/components/waka-container-list/index.tsx +475 -0
  83. package/src/components/waka-content-recommendation/index.tsx +294 -0
  84. package/src/components/waka-cost-breakdown/index.tsx +10 -10
  85. package/src/components/waka-database-card/index.tsx +473 -0
  86. package/src/components/waka-dependency-tree/index.tsx +542 -0
  87. package/src/components/waka-env-var-editor/index.tsx +417 -0
  88. package/src/components/waka-feature-flag-row/index.tsx +386 -0
  89. package/src/components/waka-funnel-chart/index.tsx +8 -8
  90. package/src/components/waka-health-pulse/index.tsx +6 -6
  91. package/src/components/waka-kubernetes-overview/index.tsx +536 -0
  92. package/src/components/waka-leaderboard/index.tsx +9 -9
  93. package/src/components/waka-log-viewer/index.tsx +386 -0
  94. package/src/components/waka-loot-box/index.tsx +20 -20
  95. package/src/components/waka-migration-list/index.tsx +487 -0
  96. package/src/components/waka-outstream-video/index.tsx +240 -0
  97. package/src/components/waka-player-card/index.tsx +5 -5
  98. package/src/components/waka-pod-card/index.tsx +528 -0
  99. package/src/components/waka-query-explain/index.tsx +657 -0
  100. package/src/components/waka-quota-bar/index.tsx +4 -4
  101. package/src/components/waka-radar-score/index.tsx +10 -10
  102. package/src/components/waka-scratch-card/index.tsx +5 -4
  103. package/src/components/waka-secret-card/index.tsx +371 -0
  104. package/src/components/waka-security-scan-result/index.tsx +473 -0
  105. package/src/components/waka-server-rack/index.tsx +28 -27
  106. package/src/components/waka-service-graph/index.tsx +445 -0
  107. package/src/components/waka-sponsored-badge/index.tsx +97 -0
  108. package/src/components/waka-sponsored-card/index.tsx +275 -0
  109. package/src/components/waka-sponsored-feed/index.tsx +127 -0
  110. package/src/components/waka-spotlight/index.tsx +2 -1
  111. package/src/components/waka-success-explosion/index.tsx +4 -4
  112. package/src/components/waka-test-report/index.tsx +469 -0
  113. package/src/components/waka-trace-viewer/index.tsx +490 -0
  114. package/src/components/waka-video-ad/index.tsx +406 -0
  115. package/src/components/waka-video-overlay/index.tsx +257 -0
  116. package/src/components/waka-xp-bar/index.tsx +13 -13
  117. package/src/styles/base.css +16 -0
  118. package/src/styles/tailwind.preset.js +12 -0
  119. package/src/styles/themes/forest.css +16 -0
  120. package/src/styles/themes/monochrome.css +16 -0
  121. package/src/styles/themes/perpetuity.css +16 -0
  122. package/src/styles/themes/sunset.css +16 -0
  123. 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
+ }