fumadocs-test-reports 0.1.0

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.
@@ -0,0 +1,121 @@
1
+ //#region src/tunit/types.d.ts
2
+ /** TUnit's .tunit-report.json sidecar fields. Additional native fields are retained. */
3
+ export interface TUnitReport {
4
+ schemaVersion: 1;
5
+ assemblyName: string;
6
+ publicationGeneration?: string | null;
7
+ machineName?: string | null;
8
+ timestamp?: string | null;
9
+ tunitVersion?: string | null;
10
+ operatingSystem?: string | null;
11
+ runtimeVersion?: string | null;
12
+ totalDurationMs?: number | null;
13
+ filter?: string | null;
14
+ summary?: TUnitReportSummary | null;
15
+ groups: TUnitTestGroup[];
16
+ spans?: TUnitSpan[] | null;
17
+ commitSha?: string | null;
18
+ branch?: string | null;
19
+ pullRequestNumber?: string | null;
20
+ repositorySlug?: string | null;
21
+ artifactUrl?: string | null;
22
+ sourceLinks?: {
23
+ lineUrl: string;
24
+ rangeUrl: string;
25
+ rawUrl?: string | null;
26
+ [key: string]: unknown;
27
+ } | null;
28
+ [key: string]: unknown;
29
+ }
30
+ /** TUnit's own counters, as supplied by the producer. */
31
+ export interface TUnitReportSummary {
32
+ total: number;
33
+ passed: number;
34
+ failed: number;
35
+ skipped: number;
36
+ cancelled: number;
37
+ timedOut: number;
38
+ flaky: number;
39
+ [key: string]: unknown;
40
+ }
41
+ export interface TUnitTestGroup {
42
+ className: string;
43
+ namespace?: string | null;
44
+ summary?: TUnitReportSummary | null;
45
+ tests: TUnitTestResult[];
46
+ [key: string]: unknown;
47
+ }
48
+ export interface TUnitTestResult {
49
+ id: string;
50
+ displayName: string;
51
+ methodName?: string | null;
52
+ className?: string | null;
53
+ /** Original TUnit spelling, including any unrecognised producer outcome. */
54
+ status: string;
55
+ durationMs?: number | null;
56
+ startTime?: string | null;
57
+ endTime?: string | null;
58
+ exception?: TUnitException | null;
59
+ output?: string | null;
60
+ errorOutput?: string | null;
61
+ categories?: string[] | null;
62
+ customProperties?: TUnitKeyValue[] | null;
63
+ filePath?: string | null;
64
+ lineNumber?: number | null;
65
+ endLineNumber?: number | null;
66
+ sourceRelativePath?: string | null;
67
+ skipReason?: string | null;
68
+ retryAttempt?: number | null;
69
+ attempts?: TUnitAttempt[] | null;
70
+ traceId?: string | null;
71
+ spanId?: string | null;
72
+ additionalTraceIds?: string[] | null;
73
+ [key: string]: unknown;
74
+ }
75
+ export interface TUnitAttempt {
76
+ status: string;
77
+ durationMs?: number | null;
78
+ exceptionType?: string | null;
79
+ exceptionMessage?: string | null;
80
+ stackTrace?: string | null;
81
+ [key: string]: unknown;
82
+ }
83
+ export interface TUnitException {
84
+ type?: string | null;
85
+ message?: string | null;
86
+ stackTrace?: string | null;
87
+ innerException?: TUnitException | null;
88
+ [key: string]: unknown;
89
+ }
90
+ export interface TUnitKeyValue {
91
+ key: string;
92
+ value: string;
93
+ [key: string]: unknown;
94
+ }
95
+ export interface TUnitSpan {
96
+ traceId: string;
97
+ spanId: string;
98
+ parentSpanId?: string | null;
99
+ name: string;
100
+ spanType?: string | null;
101
+ source?: string | null;
102
+ kind?: string | null;
103
+ startTimeMs: number;
104
+ durationMs: number;
105
+ status?: string | null;
106
+ statusMessage?: string | null;
107
+ tags?: TUnitKeyValue[] | null;
108
+ events?: {
109
+ name: string;
110
+ timestampMs: number;
111
+ tags?: TUnitKeyValue[] | null;
112
+ [key: string]: unknown;
113
+ }[] | null;
114
+ links?: {
115
+ traceId: string;
116
+ spanId: string;
117
+ [key: string]: unknown;
118
+ }[] | null;
119
+ [key: string]: unknown;
120
+ }
121
+ //#endregion
@@ -0,0 +1,4 @@
1
+ import { TUnitReportLocale } from "./labels.js";
2
+ import { TUnitReport, TUnitReportProps } from "./report.js";
3
+ import { TUnitReportLink, TUnitReportLinkProps } from "./report-link.js";
4
+ export { TUnitReport, TUnitReportLink, type TUnitReportLinkProps, type TUnitReportLocale, type TUnitReportProps };
@@ -0,0 +1,3 @@
1
+ import { TUnitReport } from "./report.js";
2
+ import { TUnitReportLink } from "./report-link.js";
3
+ export { TUnitReport, TUnitReportLink };
@@ -0,0 +1,3 @@
1
+ //#region src/tunit/ui/labels.d.ts
2
+ export type TUnitReportLocale = 'en' | 'zh';
3
+ //#endregion
@@ -0,0 +1,93 @@
1
+ //#region src/tunit/ui/labels.ts
2
+ const en = {
3
+ total: "Tests",
4
+ passed: "Passed",
5
+ unsuccessful: "Unsuccessful",
6
+ duration: "Run duration",
7
+ results: "Test results",
8
+ search: "Search tests or groups",
9
+ all: "All",
10
+ empty: "No matching test results.",
11
+ overview: "Details",
12
+ output: "Output",
13
+ attempts: "Attempts",
14
+ traces: "Traces",
15
+ noOutput: "No output recorded.",
16
+ unknown: "Unknown",
17
+ source: "Source outcome",
18
+ traceSource: "Instrumentation source",
19
+ location: "Source location",
20
+ start: "Start",
21
+ end: "End",
22
+ retries: "Retries",
23
+ flaky: "Flaky",
24
+ stdout: "Standard output",
25
+ stderr: "Standard error",
26
+ skipReason: "Skip reason",
27
+ categories: "Categories",
28
+ properties: "Properties",
29
+ noResults: "No test results recorded.",
30
+ readReport: "Read report",
31
+ error: "Error"
32
+ };
33
+ const zh = {
34
+ total: "测试总数",
35
+ passed: "通过",
36
+ unsuccessful: "未成功",
37
+ duration: "运行总耗时",
38
+ results: "测试结果",
39
+ search: "搜索测试名称或分组",
40
+ all: "全部",
41
+ empty: "没有符合条件的测试结果。",
42
+ overview: "详情",
43
+ output: "输出",
44
+ attempts: "执行尝试",
45
+ traces: "追踪",
46
+ noOutput: "未记录输出。",
47
+ unknown: "未知",
48
+ source: "原始状态",
49
+ traceSource: "追踪来源",
50
+ location: "源码位置",
51
+ start: "开始时间",
52
+ end: "结束时间",
53
+ retries: "重试次数",
54
+ flaky: "重试后通过",
55
+ stdout: "标准输出",
56
+ stderr: "错误输出",
57
+ skipReason: "跳过原因",
58
+ categories: "分类",
59
+ properties: "属性",
60
+ noResults: "未记录测试结果。",
61
+ readReport: "查看报告",
62
+ error: "错误"
63
+ };
64
+ const statuses = {
65
+ en: {
66
+ passed: "Passed",
67
+ failed: "Failed",
68
+ error: "Error",
69
+ skipped: "Skipped",
70
+ cancelled: "Cancelled",
71
+ timedOut: "Timed out",
72
+ inProgress: "Running",
73
+ unknown: "Unknown"
74
+ },
75
+ zh: {
76
+ passed: "通过",
77
+ failed: "失败",
78
+ error: "错误",
79
+ skipped: "跳过",
80
+ cancelled: "取消",
81
+ timedOut: "超时",
82
+ inProgress: "执行中",
83
+ unknown: "未知"
84
+ }
85
+ };
86
+ function getLabels(locale = "en") {
87
+ return {
88
+ ...locale === "zh" ? zh : en,
89
+ statuses: statuses[locale]
90
+ };
91
+ }
92
+ //#endregion
93
+ export { getLabels };
@@ -0,0 +1,55 @@
1
+ import { getLabels } from "./labels.js";
2
+ //#region src/tunit/ui/presentation.ts
3
+ /** TUnit-specific display decisions, never a persisted or shared test model. */
4
+ function statusDisplay(status, locale) {
5
+ const labels = getLabels(locale);
6
+ const tone = status === "passed" ? "success" : [
7
+ "failed",
8
+ "error",
9
+ "timedOut",
10
+ "cancelled"
11
+ ].includes(status) ? "danger" : status === "inProgress" ? "info" : "neutral";
12
+ return {
13
+ status: labels.statuses[status] ?? status,
14
+ tone
15
+ };
16
+ }
17
+ function displayCounts(report) {
18
+ const tests = report.groups.flatMap((group) => group.tests);
19
+ const byStatus = /* @__PURE__ */ new Map();
20
+ for (const test of tests) byStatus.set(test.status, (byStatus.get(test.status) ?? 0) + 1);
21
+ const passed = report.summary?.passed ?? byStatus.get("passed") ?? 0;
22
+ const unsuccessful = report.summary ? report.summary.failed + report.summary.timedOut + report.summary.cancelled : [
23
+ "failed",
24
+ "error",
25
+ "timedOut",
26
+ "cancelled"
27
+ ].reduce((sum, status) => sum + (byStatus.get(status) ?? 0), 0);
28
+ const flaky = report.summary?.flaky ?? tests.filter((test) => test.status === "passed" && ((test.retryAttempt ?? 0) > 0 || test.attempts?.some((attempt) => [
29
+ "failed",
30
+ "error",
31
+ "timedOut"
32
+ ].includes(attempt.status)))).length;
33
+ return {
34
+ byStatus,
35
+ total: report.summary?.total ?? tests.length,
36
+ passed,
37
+ unsuccessful,
38
+ flaky
39
+ };
40
+ }
41
+ function runDisplay(report, locale) {
42
+ const counts = displayCounts(report);
43
+ for (const status of [
44
+ "failed",
45
+ "error",
46
+ "timedOut",
47
+ "cancelled",
48
+ "inProgress"
49
+ ]) if (counts.byStatus.has(status)) return statusDisplay(status, locale);
50
+ if (counts.unsuccessful > 0) return statusDisplay("failed", locale);
51
+ if (counts.total === 0 || [...counts.byStatus.keys()].some((status) => !["passed", "skipped"].includes(status))) return statusDisplay("unknown", locale);
52
+ return statusDisplay(counts.passed > 0 ? "passed" : "skipped", locale);
53
+ }
54
+ //#endregion
55
+ export { displayCounts, runDisplay, statusDisplay };
@@ -0,0 +1,12 @@
1
+ import { TUnitReport } from "../types.js";
2
+ import { TUnitReportLocale } from "./labels.js";
3
+ //#region src/tunit/ui/report-link.d.ts
4
+ export interface TUnitReportLinkProps {
5
+ report: TUnitReport;
6
+ href: string;
7
+ locale?: TUnitReportLocale;
8
+ className?: string;
9
+ }
10
+ /** A document reference to a native TUnit report. */
11
+ export declare function TUnitReportLink({ report, href, locale, className }: TUnitReportLinkProps): import("react").JSX.Element;
12
+ //#endregion
@@ -0,0 +1,23 @@
1
+ import { StatusBadge } from "../../ui/primitives.js";
2
+ import { getLabels } from "./labels.js";
3
+ import { displayCounts, runDisplay } from "./presentation.js";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
+ import { Card } from "fumadocs-ui/components/card";
6
+ //#region src/tunit/ui/report-link.tsx
7
+ /** A document reference to a native TUnit report. */
8
+ function TUnitReportLink({ report, href, locale = "en", className = "" }) {
9
+ const labels = getLabels(locale);
10
+ const counts = displayCounts(report);
11
+ return /* @__PURE__ */ jsx(Card, {
12
+ className: `ftr-summary ${className}`,
13
+ href,
14
+ title: report.assemblyName,
15
+ description: `${counts.passed}/${counts.total} ${labels.passed.toLowerCase()}${report.timestamp ? ` · ${report.timestamp}` : ""}`,
16
+ children: /* @__PURE__ */ jsxs("div", {
17
+ className: "ftr-summary-footer",
18
+ children: [/* @__PURE__ */ jsx(StatusBadge, { ...runDisplay(report, locale) }), /* @__PURE__ */ jsxs("span", { children: [labels.readReport, " →"] })]
19
+ })
20
+ });
21
+ }
22
+ //#endregion
23
+ export { TUnitReportLink };
@@ -0,0 +1,13 @@
1
+ import { TUnitReport as TUnitReport$1, TUnitTestResult } from "../types.js";
2
+ import { TUnitReportLocale } from "./labels.js";
3
+ //#region src/tunit/ui/report.d.ts
4
+ export interface TUnitReportProps {
5
+ report: TUnitReport$1;
6
+ locale?: TUnitReportLocale;
7
+ className?: string;
8
+ selectedTestId?: string;
9
+ onTestSelect?: (test: TUnitTestResult) => void;
10
+ }
11
+ /** TUnit presenter: reads native fields and composes shared UI primitives. */
12
+ export declare function TUnitReport({ report, locale, className, selectedTestId, onTestSelect }: TUnitReportProps): import("react").JSX.Element;
13
+ //#endregion
@@ -0,0 +1,265 @@
1
+ "use client";
2
+ import { Duration, ErrorDetail, StatusBadge, StdoutPanel } from "../../ui/primitives.js";
3
+ import { getLabels } from "./labels.js";
4
+ import { displayCounts, statusDisplay } from "./presentation.js";
5
+ import { TUnitTraceTimeline } from "./trace-timeline.js";
6
+ import { useState } from "react";
7
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from "fumadocs-ui/components/tabs";
8
+ import { buttonVariants } from "fumadocs-ui/components/ui/button";
9
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
10
+ //#region src/tunit/ui/report.tsx
11
+ function TUnitExceptionDetails({ error }) {
12
+ return /* @__PURE__ */ jsx(ErrorDetail, {
13
+ type: error.type,
14
+ message: error.message,
15
+ stack: error.stackTrace,
16
+ children: error.innerException && /* @__PURE__ */ jsx(TUnitExceptionDetails, { error: error.innerException })
17
+ });
18
+ }
19
+ /** TUnit presenter: reads native fields and composes shared UI primitives. */
20
+ function TUnitReport({ report, locale = "en", className, selectedTestId, onTestSelect }) {
21
+ const labels = getLabels(locale);
22
+ const summary = displayCounts(report);
23
+ const [query, setQuery] = useState("");
24
+ const [filter, setFilter] = useState(null);
25
+ const [localSelection, setLocalSelection] = useState();
26
+ const groups = report.groups.map((group) => ({
27
+ ...group,
28
+ tests: group.tests.filter((test) => (filter === null || test.status === filter) && `${test.displayName} ${group.className} ${group.namespace ?? ""}`.toLowerCase().includes(query.toLowerCase()))
29
+ })).filter((group) => group.tests.length > 0);
30
+ const visible = groups.flatMap((group) => group.tests);
31
+ const selected = visible.find((test) => test.id === (selectedTestId ?? localSelection)) ?? visible[0];
32
+ const traceIds = /* @__PURE__ */ new Set([selected?.traceId, ...selected?.additionalTraceIds ?? []]);
33
+ const spans = report.spans?.filter((span) => traceIds.has(span.traceId)) ?? [];
34
+ const tabs = [
35
+ ["overview", labels.overview],
36
+ ["output", labels.output],
37
+ ...selected?.attempts?.length ? [["attempts", labels.attempts]] : [],
38
+ ...spans.length ? [["traces", labels.traces]] : []
39
+ ];
40
+ function select(test) {
41
+ setLocalSelection(test.id);
42
+ onTestSelect?.(test);
43
+ }
44
+ return /* @__PURE__ */ jsxs("section", {
45
+ className: `ftr-report not-prose ${className ?? ""}`,
46
+ "aria-label": report.assemblyName,
47
+ children: [
48
+ /* @__PURE__ */ jsxs("header", {
49
+ className: "ftr-run-heading",
50
+ children: [/* @__PURE__ */ jsx("h2", { children: report.assemblyName }), /* @__PURE__ */ jsxs("div", {
51
+ className: "ftr-run-meta",
52
+ children: [
53
+ /* @__PURE__ */ jsxs("span", { children: ["TUnit ", report.tunitVersion] }),
54
+ [
55
+ report.machineName,
56
+ report.operatingSystem,
57
+ report.runtimeVersion
58
+ ].filter(Boolean).map((value, i) => /* @__PURE__ */ jsx("span", { children: value }, i)),
59
+ report.timestamp && /* @__PURE__ */ jsx("span", { children: report.timestamp })
60
+ ]
61
+ })]
62
+ }),
63
+ /* @__PURE__ */ jsxs("div", {
64
+ className: "ftr-metrics",
65
+ children: [
66
+ /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("span", { children: labels.total }), /* @__PURE__ */ jsx("strong", { children: summary.total })] }),
67
+ /* @__PURE__ */ jsxs("div", { children: [
68
+ /* @__PURE__ */ jsx("span", { children: labels.passed }),
69
+ /* @__PURE__ */ jsx("strong", { children: summary.passed }),
70
+ /* @__PURE__ */ jsxs("small", { children: [
71
+ labels.flaky,
72
+ ": ",
73
+ summary.flaky
74
+ ] })
75
+ ] }),
76
+ /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("span", { children: labels.unsuccessful }), /* @__PURE__ */ jsx("strong", { children: summary.unsuccessful })] }),
77
+ /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("span", { children: labels.duration }), /* @__PURE__ */ jsx("strong", { children: /* @__PURE__ */ jsx(Duration, {
78
+ value: report.totalDurationMs,
79
+ unknownLabel: labels.unknown
80
+ }) })] })
81
+ ]
82
+ }),
83
+ /* @__PURE__ */ jsxs("div", {
84
+ className: "ftr-toolbar",
85
+ children: [/* @__PURE__ */ jsx("h3", { children: labels.results }), /* @__PURE__ */ jsx("input", {
86
+ type: "search",
87
+ "aria-label": labels.search,
88
+ placeholder: labels.search,
89
+ value: query,
90
+ onChange: (event) => setQuery(event.target.value)
91
+ })]
92
+ }),
93
+ /* @__PURE__ */ jsxs("div", {
94
+ className: "ftr-filters",
95
+ "aria-label": labels.results,
96
+ children: [/* @__PURE__ */ jsxs("button", {
97
+ type: "button",
98
+ className: buttonVariants({
99
+ variant: filter === null ? "secondary" : "ghost",
100
+ size: "sm"
101
+ }),
102
+ "aria-pressed": filter === null,
103
+ onClick: () => setFilter(null),
104
+ children: [
105
+ labels.all,
106
+ " ",
107
+ /* @__PURE__ */ jsx("small", { children: summary.total })
108
+ ]
109
+ }), [...summary.byStatus.keys()].map((status) => /* @__PURE__ */ jsxs("button", {
110
+ type: "button",
111
+ className: buttonVariants({
112
+ variant: filter === status ? "secondary" : "ghost",
113
+ size: "sm"
114
+ }),
115
+ "aria-pressed": filter === status,
116
+ onClick: () => setFilter(status),
117
+ children: [
118
+ statusDisplay(status, locale).status,
119
+ " ",
120
+ /* @__PURE__ */ jsx("small", { children: summary.byStatus.get(status) })
121
+ ]
122
+ }, status))]
123
+ }),
124
+ /* @__PURE__ */ jsxs("div", {
125
+ className: "ftr-workspace",
126
+ children: [/* @__PURE__ */ jsxs("div", {
127
+ className: "ftr-list",
128
+ "aria-label": labels.results,
129
+ children: [groups.length === 0 && /* @__PURE__ */ jsx("p", {
130
+ className: "ftr-empty",
131
+ children: summary.total === 0 ? labels.noResults : labels.empty
132
+ }), groups.map((group, i) => /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs("div", {
133
+ className: "ftr-group",
134
+ children: [/* @__PURE__ */ jsx("strong", { children: group.className }), group.namespace && /* @__PURE__ */ jsx("small", { children: group.namespace })]
135
+ }), group.tests.map((test) => /* @__PURE__ */ jsxs("button", {
136
+ type: "button",
137
+ className: `${buttonVariants({ variant: "ghost" })} ftr-test`,
138
+ "aria-pressed": selected?.id === test.id,
139
+ onClick: () => select(test),
140
+ children: [
141
+ /* @__PURE__ */ jsx(StatusBadge, { ...statusDisplay(test.status, locale) }),
142
+ /* @__PURE__ */ jsx("span", {
143
+ className: "ftr-test-name",
144
+ children: test.displayName
145
+ }),
146
+ /* @__PURE__ */ jsx("small", { children: /* @__PURE__ */ jsx(Duration, {
147
+ value: test.durationMs,
148
+ unknownLabel: labels.unknown
149
+ }) })
150
+ ]
151
+ }, test.id))] }, i))]
152
+ }), /* @__PURE__ */ jsx("div", {
153
+ className: "ftr-detail",
154
+ "aria-label": labels.overview,
155
+ children: selected ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("div", {
156
+ className: "ftr-detail-heading",
157
+ children: [
158
+ /* @__PURE__ */ jsx(StatusBadge, { ...statusDisplay(selected.status, locale) }),
159
+ /* @__PURE__ */ jsx("h3", { children: selected.displayName }),
160
+ /* @__PURE__ */ jsx("small", { children: /* @__PURE__ */ jsx(Duration, {
161
+ value: selected.durationMs,
162
+ unknownLabel: labels.unknown
163
+ }) })
164
+ ]
165
+ }), /* @__PURE__ */ jsxs(Tabs, {
166
+ defaultValue: "overview",
167
+ className: "ftr-detail-tabs",
168
+ children: [
169
+ /* @__PURE__ */ jsx(TabsList, {
170
+ "aria-label": labels.overview,
171
+ children: tabs.map(([value, label]) => /* @__PURE__ */ jsx(TabsTrigger, {
172
+ value,
173
+ children: label
174
+ }, value))
175
+ }),
176
+ /* @__PURE__ */ jsxs(TabsContent, {
177
+ value: "overview",
178
+ children: [/* @__PURE__ */ jsxs("dl", {
179
+ className: "ftr-fields",
180
+ children: [
181
+ /* @__PURE__ */ jsx("dt", { children: "ID" }),
182
+ /* @__PURE__ */ jsx("dd", { children: selected.id }),
183
+ /* @__PURE__ */ jsx("dt", { children: labels.source }),
184
+ /* @__PURE__ */ jsx("dd", { children: selected.status }),
185
+ selected.filePath && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("dt", { children: labels.location }), /* @__PURE__ */ jsxs("dd", { children: [selected.filePath, selected.lineNumber != null ? `:${selected.lineNumber}` : ""] })] }),
186
+ selected.startTime && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("dt", { children: labels.start }), /* @__PURE__ */ jsx("dd", { children: selected.startTime })] }),
187
+ selected.endTime && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("dt", { children: labels.end }), /* @__PURE__ */ jsx("dd", { children: selected.endTime })] }),
188
+ selected.retryAttempt != null && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("dt", { children: labels.retries }), /* @__PURE__ */ jsx("dd", { children: selected.retryAttempt })] }),
189
+ selected.skipReason && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("dt", { children: labels.skipReason }), /* @__PURE__ */ jsx("dd", { children: selected.skipReason })] }),
190
+ !!selected.categories?.length && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("dt", { children: labels.categories }), /* @__PURE__ */ jsx("dd", { children: selected.categories.join(", ") })] }),
191
+ selected.customProperties?.map((property, i) => /* @__PURE__ */ jsxs("div", {
192
+ className: "ftr-field",
193
+ children: [/* @__PURE__ */ jsx("dt", { children: property.key }), /* @__PURE__ */ jsx("dd", { children: property.value })]
194
+ }, i))
195
+ ]
196
+ }), selected.exception && /* @__PURE__ */ jsx(TUnitExceptionDetails, { error: selected.exception })]
197
+ }),
198
+ /* @__PURE__ */ jsx(TabsContent, {
199
+ value: "output",
200
+ children: /* @__PURE__ */ jsxs("div", {
201
+ className: "ftr-output",
202
+ children: [
203
+ selected.output == null && selected.errorOutput == null && /* @__PURE__ */ jsx(StdoutPanel, { emptyLabel: labels.noOutput }),
204
+ selected.output != null && /* @__PURE__ */ jsx(StdoutPanel, {
205
+ content: selected.output,
206
+ title: labels.stdout
207
+ }),
208
+ selected.errorOutput != null && /* @__PURE__ */ jsx(StdoutPanel, {
209
+ content: selected.errorOutput,
210
+ title: labels.stderr
211
+ })
212
+ ]
213
+ })
214
+ }),
215
+ !!selected.attempts?.length && /* @__PURE__ */ jsx(TabsContent, {
216
+ value: "attempts",
217
+ children: /* @__PURE__ */ jsx("div", {
218
+ className: "ftr-attempts",
219
+ children: selected.attempts?.map((attempt, i) => /* @__PURE__ */ jsxs("div", {
220
+ className: "ftr-attempt",
221
+ children: [
222
+ /* @__PURE__ */ jsxs("h4", { children: [
223
+ "#",
224
+ i + 1,
225
+ " ",
226
+ /* @__PURE__ */ jsx(StatusBadge, { ...statusDisplay(attempt.status, locale) }),
227
+ " ",
228
+ /* @__PURE__ */ jsx(Duration, {
229
+ value: attempt.durationMs,
230
+ unknownLabel: labels.unknown
231
+ })
232
+ ] }),
233
+ /* @__PURE__ */ jsxs("p", { children: [
234
+ labels.source,
235
+ ": ",
236
+ attempt.status
237
+ ] }),
238
+ (attempt.exceptionType != null || attempt.exceptionMessage != null || attempt.stackTrace != null) && /* @__PURE__ */ jsx(ErrorDetail, {
239
+ type: attempt.exceptionType,
240
+ message: attempt.exceptionMessage,
241
+ stack: attempt.stackTrace
242
+ })
243
+ ]
244
+ }, i))
245
+ })
246
+ }),
247
+ !!spans.length && /* @__PURE__ */ jsx(TabsContent, {
248
+ value: "traces",
249
+ children: /* @__PURE__ */ jsx(TUnitTraceTimeline, {
250
+ spans,
251
+ locale
252
+ })
253
+ })
254
+ ]
255
+ }, selected.id)] }) : /* @__PURE__ */ jsx("p", {
256
+ className: "ftr-empty",
257
+ children: labels.empty
258
+ })
259
+ })]
260
+ })
261
+ ]
262
+ });
263
+ }
264
+ //#endregion
265
+ export { TUnitReport };