Skip to content

Commit 2711110

Browse files
committed
add coverage integration
1 parent e175e6c commit 2711110

15 files changed

+2863
-263
lines changed

package-lock.json

+2,672-232
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+10-3
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,17 @@
66
"@babel/plugin-syntax-unicode-sets-regex": "^7.18.6",
77
"@fultonjs/common": "github:xhd2015/fultonjs#master",
88
"antd": "^5.17.0",
9+
"assert": "^2.1.0",
10+
"bootstrap": "^5.3.3",
11+
"buffer": "^6.0.3",
912
"bun": "^1.1.8",
1013
"express": "^4.18.1",
1114
"monaco-editor": "^0.34.0",
12-
"monaco-editor-webpack-plugin": "^7.0.1",
1315
"path-browserify": "^1.0.1",
1416
"react": "^18.2.0",
1517
"react-dom": "^18.2.0",
1618
"react-icons": "^5.2.1",
17-
"react-monaco-editor": "^0.49.0",
18-
"webpack": "^5.91.0"
19+
"react-monaco-editor": "^0.49.0"
1920
},
2021
"scripts": {
2122
"build": "webpack --config webpack.config.js --progress --mode=production",
@@ -32,11 +33,17 @@
3233
"@babel/preset-react": "^7.0.0",
3334
"@svgr/webpack": "^6.3.1",
3435
"@types/bun": "^1.1.1",
36+
"@types/react": "^18.3.12",
3537
"babel-loader": "^8.1.0",
38+
"copy-webpack-plugin": "^12.0.2",
39+
"crypto-browserify": "^3.12.1",
3640
"css-loader": "^6.7.1",
3741
"file-loader": "^6.0.0",
42+
"monaco-editor-webpack-plugin": "^7.0.1",
3843
"style-loader": "^3.3.1",
3944
"ts-loader": "^9.3.1",
45+
"webpack": "^5.91.0",
46+
"webpack-bundle-analyzer": "^4.10.2",
4047
"webpack-cli": "^4.10.0"
4148
}
4249
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { SyncOutlined } from "@ant-design/icons";
2+
import { Button, Tooltip, Typography } from "antd";
3+
import { FiExternalLink } from "react-icons/fi";
4+
import { getColor, LabelDetail, toPercent } from "./coverage-util";
5+
6+
export interface CoverageData {
7+
link?: string
8+
total?: LabelDetail
9+
incremental?: LabelDetail
10+
}
11+
12+
export interface CoverageLineProps {
13+
data?: CoverageData
14+
fetching?: boolean
15+
onClickFetch?: () => void
16+
}
17+
18+
export function CoverageLine(props: CoverageLineProps) {
19+
const link = props.data?.link
20+
const total = props.data?.total
21+
const incremental = props.data?.incremental
22+
return <div style={{ display: "flex", alignItems: "center" }}>
23+
<span style={{ marginRight: "2px" }}>Overall Coverage:</span>
24+
25+
<Coverage total={total} incremental={incremental} />
26+
27+
{/* TODO: i18n */}
28+
<Tooltip placement="top" title="Fetch latest coverage" mouseEnterDelay={1}>
29+
<Button
30+
icon={<SyncOutlined spin={props.fetching} />}
31+
disabled={props.fetching}
32+
onClick={props.onClickFetch}
33+
shape="circle"
34+
size="small"
35+
></Button>
36+
</Tooltip>
37+
{link ? <Typography.Link href={link} target="_blank"><FiExternalLink /></Typography.Link> : null}
38+
</div>
39+
}
40+
41+
42+
// coverage
43+
export function Coverage(props: { total?: LabelDetail, incremental?: LabelDetail }) {
44+
let e: any = '-'
45+
if (props.total != null || props.incremental != null) {
46+
e = <span>
47+
{CoverageValue({ detail: props.total })} {` / `} {CoverageValue({ detail: props.incremental })}
48+
</span>
49+
}
50+
return <Tooltip placement="top" title="Format: Total / Incremental">
51+
<span style={{ cursor: "default" }}>{e}</span>
52+
</Tooltip>
53+
54+
55+
}
56+
57+
export interface CoverageValueProps {
58+
detail: LabelDetail | undefined
59+
}
60+
61+
export function CoverageValue(props: CoverageValueProps) {
62+
const { detail } = props
63+
const percent = toPercent(detail?.value, '')
64+
const color = getColor(percent, detail?.pass ? 'pass' : 'fail')
65+
return <span style={{ color: color }}>{percent || '-'}</span>
66+
}

src/mock-editor/TestingExplorer/xgo/UrlXgoTestingExplorer.tsx

+9-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { patchSubStree, updateState, updateSubTree } from "../TestingList/util"
55
import { RunStatus } from "../testing"
66
import { TestingItem } from "../testing-api"
77
import { XgoTestingExplorer, XgoTestingExplorerProps } from "./XgoTestingExplorer"
8-
import { Event, fetchContent, requestRunPoll, useUrlData } from "./http-data"
8+
import { Event, fetchContent, requestRunPoll, useUrlCoverage, useUrlData } from "./http-data"
99
import { filterData, replaceDataMergingState, setSelect, toggleExpand } from "./util"
1010
import { TraceItem } from "../TestingExplorerEditor/types"
1111

@@ -23,6 +23,7 @@ export function UrlXgoTestingExplorer(props: UrlXgoTestingExplorerProps) {
2323

2424
// TODO: make list return single list
2525
const { data: serverData, refresh } = useUrlData(`${apiPrefix}/list`)
26+
const { data: coverageData, loading: coverageLoading, refresh: refreshCoverage } = useUrlCoverage(`${apiPrefix}/coverage/summary`)
2627

2728
const [data, setData] = useState(serverData)
2829
const [trace, setTrace] = useState(false)
@@ -44,7 +45,8 @@ export function UrlXgoTestingExplorer(props: UrlXgoTestingExplorerProps) {
4445
await fetch(url + "?" + params)
4546
}
4647

47-
return <XgoTestingExplorer {...props}
48+
return <XgoTestingExplorer
49+
{...props}
4850
data={data}
4951
onRefreshRoot={refresh}
5052
onClickItem={(item, path) => {
@@ -86,6 +88,11 @@ export function UrlXgoTestingExplorer(props: UrlXgoTestingExplorerProps) {
8688
onTraceChange={setTrace}
8789
selectedTraceRecord={selectedTraceRecord}
8890
setSelectedTraceRecord={setSelectedTraceRecord}
91+
coverage={{
92+
data: { ...coverageData, link: coverageData?.link || "/coverage" },
93+
fetching: coverageLoading,
94+
onClickFetch: refreshCoverage,
95+
}}
8996
/>
9097
}
9198

src/mock-editor/TestingExplorer/xgo/XgoTestDetail.tsx

+9-14
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
import { Button, Checkbox } from "antd"
2-
import { CSSProperties, useEffect, useState } from "react"
2+
import { CSSProperties, useEffect, useState, Dispatch, SetStateAction } from "react"
33
import { AiOutlineReload } from "react-icons/ai"
44
import { BsFileEarmarkCheck } from "react-icons/bs"
55
import { FiChevronLeft, FiChevronRight } from "react-icons/fi"
66
import { GoFileCode } from "react-icons/go"
77
import { SiGoland } from "react-icons/si"
88
import { VscVscode } from "react-icons/vsc"
99
import { ProgressIcon } from "../../../support/components/icon/ProgressIcon"
10+
import { TraceExplorer, TraceExplorerProps } from "../../../trace/TraceExplorer"
1011
import TextEditor from "../../TextEditor"
1112
import CopyClipboard from "../../support/CopyClipboard"
1213
import Icon from "../../support/Icon"
1314
import { TestingItem } from "../testing-api"
14-
import { RunStatus } from "../testing"
15-
import { TraceExplorer, TraceExplorerProps } from "../../../trace/TraceExplorer"
15+
import { CoverageLine, CoverageLineProps } from "./Coverage"
1616

1717
export interface XgoTestDetailProps {
1818
style?: CSSProperties
@@ -23,7 +23,7 @@ export interface XgoTestDetailProps {
2323
log?: string
2424

2525
trace?: boolean
26-
onTraceChange?: React.Dispatch<React.SetStateAction<boolean>>
26+
onTraceChange?: Dispatch<SetStateAction<boolean>>
2727

2828
onClickRun?: () => void
2929
onClickDebug?: () => void
@@ -37,25 +37,20 @@ export interface XgoTestDetailProps {
3737

3838
showTrace?: boolean
3939
shownTraceProps?: TraceExplorerProps
40+
41+
coverage?: CoverageLineProps
4042
}
4143

4244
// debug with trace enabled
4345
export function XgoTestDetail(props: XgoTestDetailProps) {
4446
return <div className={props.className} style={{ height: "100%", ...props.style }}>
47+
<div style={{ display: "flex", alignItems: "center" }}>
48+
<CoverageLine {...props.coverage} />
49+
</div>
4550
<ItemDetail {...props} />
4651
</div>
4752
}
4853

49-
function getStatusColor(status: RunStatus): string | undefined {
50-
if (status == "fail" || status === "error") {
51-
return "red"
52-
}
53-
if (status === "success") {
54-
return "green"
55-
}
56-
return undefined
57-
}
58-
5954
export function ItemDetail(props: XgoTestDetailProps) {
6055
const item = props.item
6156
if (item == null) {

src/mock-editor/TestingExplorer/xgo/XgoTestingExplorer.tsx

+8-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { CSSProperties, useEffect, useMemo, useState } from "react"
1+
import { CSSProperties, useEffect, useMemo, useState, Dispatch, SetStateAction } from "react"
22
import { GridLayout } from "../../../support/components/layout/GridLayout"
33
import { TraceExplorerProps } from "../../../trace/TraceExplorer"
44
import { ItemPath } from "../../List"
@@ -11,6 +11,7 @@ import { TestingItem } from "../testing-api"
1111
import { XgoTestDetail } from "./XgoTestDetail"
1212
import { fillTestingItem } from "./util"
1313
import { TraceItem } from "../TestingExplorerEditor/types"
14+
import { CoverageLineProps } from "./Coverage"
1415

1516
export interface RunDetailResult {
1617
status: RunStatus
@@ -24,10 +25,10 @@ export interface XgoTestingExplorerProps {
2425
data?: TestingItem
2526

2627
trace?: boolean
27-
onTraceChange?: React.Dispatch<React.SetStateAction<boolean>>
28+
onTraceChange?: Dispatch<SetStateAction<boolean>>
2829

2930
selectedTraceRecord?: TraceItem
30-
setSelectedTraceRecord?: (value: React.SetStateAction<TraceItem>) => void
31+
setSelectedTraceRecord?: (value: SetStateAction<TraceItem>) => void
3132

3233
fetchContent?: (selectedItem: TestingItem) => Promise<string>
3334

@@ -46,6 +47,8 @@ export interface XgoTestingExplorerProps {
4647
openVscode?: (item: TestingItem) => void
4748
openGoland?: (item: TestingItem) => void
4849
copyText?: (item: TestingItem) => string
50+
51+
coverage?: CoverageLineProps
4952
}
5053

5154
// design:
@@ -91,7 +94,7 @@ export function XgoTestingExplorer(props: XgoTestingExplorerProps) {
9194
refreshContent(selectedItem)
9295
}
9396

94-
console.log("selectedItem:", selectedItem?.relPath, selectedItem?.state?.showTrace)
97+
// console.log("selectedItem:", selectedItem?.relPath, selectedItem?.state?.showTrace)
9598

9699
return <GridLayout
97100
style={{
@@ -159,6 +162,7 @@ export function XgoTestingExplorer(props: XgoTestingExplorerProps) {
159162
onClickGoland={clickGoland}
160163
copyText={selectedItem && props.copyText && props.copyText(selectedItem)}
161164
onClickRefresh={clickRefresh}
165+
coverage={props.coverage}
162166
/>
163167
}}
164168
/>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
export function toIntPercent(s: string, defaultNum?: number): number {
2+
const n = Number.parseFloat(s)
3+
if (isNaN(n)) {
4+
return defaultNum === undefined ? 0 : defaultNum
5+
}
6+
return Number((n * 100).toFixed(0))
7+
}
8+
9+
export function toPercent(s: string, defaultStr?: string): string {
10+
const n = toIntPercent(s, -1)
11+
if (n === -1) {
12+
return defaultStr
13+
}
14+
return `${n}%`
15+
}
16+
17+
export function getColor(percent: string, variant: "pass" | "fail"): string {
18+
if (!percent) {
19+
return ''
20+
}
21+
switch (variant) {
22+
case 'pass':
23+
return "green"
24+
case 'fail':
25+
return 'red'
26+
}
27+
return ''
28+
}
29+
30+
export interface CoverageDetail {
31+
total: LabelDetail
32+
incrimental: LabelDetail
33+
}
34+
35+
export interface LabelDetail {
36+
pass: boolean
37+
value: string // percent in float, e.g. 0.65
38+
}

src/mock-editor/TestingExplorer/xgo/http-data.ts

+33-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { Options, RunItem, Session, SessionRunner, UpdateCallback } from "../Tes
55
import { traverse } from "../../tree"
66
import { ItemPath } from "../../List"
77
import { CallRecord } from "../TestingExplorerEditor/TraceList/trace-types"
8+
import { CoverageData, CoverageLineProps } from "./Coverage"
89

910
export async function requestPoll(url: string, pollURL: string, body: any, callback: (err: Error, events: ItemEvent[]) => boolean): Promise<void> {
1011
const resp = await fetch(url, {
@@ -140,15 +141,46 @@ export function useUrlData(url: string): { data: TestingItem, refresh: () => voi
140141
const rootData = makeSingleRoot(data)
141142
fillKeys(rootData)
142143
setData(rootData)
143-
144144
}
145+
145146
useMemo(() => {
146147
refresh()
147148
}, [])
148149

149150
return { data, refresh }
150151
}
151152

153+
export function useUrlCoverage(url: string): { data: CoverageData, loading: boolean, refresh: () => void } {
154+
const [data, setData] = useState<CoverageData>()
155+
const [loading, setLoading] = useState(false)
156+
157+
const refresh = async () => {
158+
setLoading(true)
159+
try {
160+
const resp = await fetch(url)
161+
const data: CoverageData = await resp.json()
162+
setData(data)
163+
if (false) {
164+
// demo
165+
await new Promise(r => setTimeout(r, 1000))
166+
setData({
167+
total: { value: "0.4", pass: true },
168+
incremental: { value: "0.9", pass: true },
169+
link: "/coverage",
170+
})
171+
}
172+
} finally {
173+
setLoading(false)
174+
}
175+
}
176+
177+
useEffect(() => {
178+
refresh()
179+
}, [])
180+
181+
return { data, loading, refresh }
182+
}
183+
152184
function fillKeys(data: TestingItem) {
153185
traverse([data], e => {
154186
if (e.key == null) {

src/open/README.md

+7-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
# Workflow
22
1. Development
3-
- command: `npm run dev-open`
3+
- command: `npm install --legacy-peer-deps`
4+
- command: `SERVE_PORT=8080 npm run dev-open`
45
- serving files under src/open, so you can import js files via `http://localhost:8080/build/index.js`
56

67
2. Verify
78
- make your index.html refer to `http://localhost:8080/build/index.js`
9+
- you can refer to [./demo-web.html](demo-web.html) for simple usage
10+
- for xgo, refer to [xgo/test-explorer/index.html](https://github.com/xhd2015/xgo/blob/master/cmd/xgo/test-explorer/index.html)
811

912
3. Build
1013
- Once development and verify complete, can proceed to next steps
@@ -14,17 +17,19 @@
1417

1518
4. Publish
1619
- run: `cd src/open/npm-publish && npm publish`
20+
- reference via: `https://cdn.jsdelivr.net/npm/xgo-explorer@0.0.13/index.js`
1721

1822
# Usage
1923
Build
2024
```sh
2125
npx webpack --watch --config src/open/webpack.config.js --progress --mode=development
26+
# this will
2227
```
2328

2429
Serve file:
2530
```sh
2631
npm install -g http-server
27-
http-server -c-1 ./
32+
http-server -c-1 --port 8080 '--cors=*' ./
2833
```
2934

3035
Open file:

0 commit comments

Comments
 (0)