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.
- package/LICENSE +21 -0
- package/README.md +114 -0
- package/css/preset.css +300 -0
- package/dist/tunit/index.d.ts +3 -0
- package/dist/tunit/index.js +2 -0
- package/dist/tunit/parse.d.ts +9 -0
- package/dist/tunit/parse.js +180 -0
- package/dist/tunit/types.d.ts +121 -0
- package/dist/tunit/ui/index.d.ts +4 -0
- package/dist/tunit/ui/index.js +3 -0
- package/dist/tunit/ui/labels.d.ts +3 -0
- package/dist/tunit/ui/labels.js +93 -0
- package/dist/tunit/ui/presentation.js +55 -0
- package/dist/tunit/ui/report-link.d.ts +12 -0
- package/dist/tunit/ui/report-link.js +23 -0
- package/dist/tunit/ui/report.d.ts +13 -0
- package/dist/tunit/ui/report.js +265 -0
- package/dist/tunit/ui/trace-timeline.js +81 -0
- package/dist/ui/index.d.ts +2 -0
- package/dist/ui/index.js +2 -0
- package/dist/ui/primitives.d.ts +32 -0
- package/dist/ui/primitives.js +48 -0
- package/package.json +52 -0
|
@@ -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,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 };
|