@canyonjs/report-component 0.0.10 → 1.0.16

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/index.css CHANGED
@@ -152,22 +152,73 @@
152
152
  border-bottom: 1px solid #e0e0e0;
153
153
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
154
154
  z-index: 1000;
155
- transition: max-height 0.3s ease;
155
+ transition: max-height 0.3s ease, opacity 0.3s ease;
156
+ opacity: 0;
156
157
  }
157
158
 
158
- .canyonjs-report-html .canyon-changed-code-coverage-table-wrapper {
159
- padding: 16px;
159
+ .canyonjs-report-html .canyon-changed-code-coverage-content[style*="max-height: auto"] {
160
+ opacity: 1;
161
+ }
162
+
163
+ .canyonjs-report-html .canyon-changed-code-coverage-select-wrapper {
164
+ padding: 12px 16px;
160
165
  background: #ffffff;
161
166
  font-family:
162
167
  -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
163
- overflow: auto;
164
- max-height: 300px;
168
+ display: flex;
169
+ justify-content: flex-start;
170
+ }
171
+
172
+ /* 下拉选项样式优化 */
173
+ .canyonjs-report-html .canyon-changed-code-coverage-select-wrapper .ant-select-item {
174
+ padding: 6px 12px !important;
175
+ line-height: 20px !important;
176
+ }
177
+
178
+ .canyonjs-report-html .canyon-changed-code-coverage-select-wrapper .ant-select-item-option-active {
179
+ background-color: #f5f5f5;
180
+ }
181
+
182
+ .canyonjs-report-html .canyon-changed-code-coverage-select-wrapper .ant-select-item-option-content {
183
+ display: flex;
184
+ align-items: center;
165
185
  }
166
186
 
167
- .canyonjs-report-html .canyon-changed-code-coverage-table-title {
168
- font-weight: bold;
169
- font-size: 14px;
170
- color: #007acc;
171
- margin-bottom: 12px;
187
+ /* 跳转高亮样式 - 整行高亮 */
188
+ .canyonjs-report-html .canyon-jump-highlight-line {
189
+ background-color: rgba(255, 193, 7, 0.4) !important;
190
+ border-left: 4px solid #ff9800;
191
+ box-shadow: 0 0 8px rgba(255, 193, 7, 0.5);
192
+ animation: canyon-jump-pulse 0.6s ease-out;
193
+ }
194
+
195
+ /* 跳转高亮样式 - 行号区域高亮 */
196
+ .canyonjs-report-html .canyon-jump-highlight-line-margin {
197
+ background-color: rgba(255, 193, 7, 0.3) !important;
198
+ }
199
+
200
+ /* 跳转高亮样式 - 范围高亮 */
201
+ .canyonjs-report-html .canyon-jump-highlight-range {
202
+ background-color: rgba(255, 193, 7, 0.5) !important;
203
+ border: 2px solid #ff9800;
204
+ border-radius: 3px;
205
+ box-shadow: 0 0 6px rgba(255, 193, 7, 0.6);
206
+ animation: canyon-jump-pulse 0.6s ease-out;
207
+ }
208
+
209
+ /* 脉冲动画效果 - 让高亮更醒目 */
210
+ @keyframes canyon-jump-pulse {
211
+ 0% {
212
+ background-color: rgba(255, 193, 7, 0.7);
213
+ box-shadow: 0 0 12px rgba(255, 193, 7, 0.8);
214
+ }
215
+ 50% {
216
+ background-color: rgba(255, 193, 7, 0.5);
217
+ box-shadow: 0 0 8px rgba(255, 193, 7, 0.6);
218
+ }
219
+ 100% {
220
+ background-color: rgba(255, 193, 7, 0.4);
221
+ box-shadow: 0 0 8px rgba(255, 193, 7, 0.5);
222
+ }
172
223
  }
173
224
 
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { ConfigProvider, Divider, Input, Progress, Segmented, Space, Spin, Switch, Table, Tag, Typography, theme } from "antd";
1
+ import { ConfigProvider, Divider, Input, Progress, Segmented, Select, Space, Spin, Switch, Table, Tag, Typography, theme } from "antd";
2
2
  import { Suspense, useEffect, useMemo, useRef, useState } from "react";
3
3
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
4
4
  import { genSummaryTreeItem } from "canyon-data";
@@ -236,10 +236,36 @@ function coreFn(fileCoverage, fileDetail) {
236
236
 
237
237
  //#endregion
238
238
  //#region src/widgets/ChangedCodeCoverageTable.tsx
239
- const ChangedCodeCoverageTable = ({ coverage, addLines }) => {
240
- console.log("coverage", coverage);
241
- console.log("addLines", addLines);
239
+ const formatLineRanges = (lines) => {
240
+ if (lines.length === 0) return "";
241
+ if (lines.length === 1) {
242
+ const first$1 = lines[0];
243
+ return first$1 !== void 0 ? first$1.toString() : "";
244
+ }
245
+ const sorted = [...lines].sort((a, b) => a - b);
246
+ const ranges = [];
247
+ const first = sorted[0];
248
+ if (first === void 0) return "";
249
+ let start = first;
250
+ let end = first;
251
+ for (let i = 1; i < sorted.length; i++) {
252
+ const current = sorted[i];
253
+ if (current === void 0) continue;
254
+ if (current === end + 1) end = current;
255
+ else {
256
+ if (start === end) ranges.push(start.toString());
257
+ else ranges.push(`${start}-${end}`);
258
+ start = current;
259
+ end = current;
260
+ }
261
+ }
262
+ if (start === end) ranges.push(start.toString());
263
+ else ranges.push(`${start}-${end}`);
264
+ return ranges.join(", ");
265
+ };
266
+ const ChangedCodeCoverageTable = ({ coverage, addLines, onJumpToRange }) => {
242
267
  const [isOpen, setIsOpen] = useState(false);
268
+ const [selectedValue, setSelectedValue] = useState(null);
243
269
  const relatedStatements = useMemo(() => {
244
270
  if (addLines.length === 0 || !coverage.s || !coverage.statementMap) return [];
245
271
  const addedLinesSet = new Set(addLines);
@@ -252,39 +278,102 @@ const ChangedCodeCoverageTable = ({ coverage, addLines }) => {
252
278
  const relatedLines = [];
253
279
  for (let line = startLine; line <= endLine; line++) if (addedLinesSet.has(line)) relatedLines.push(line);
254
280
  if (relatedLines.length > 0) {
255
- const count = coverage.s[stId] || 0;
281
+ const count = coverage.s?.[stId] || 0;
256
282
  const covered = count > 0;
257
- const locationDisplay = endLine > startLine ? `${startLine}:${startCol + 1} - ${endLine}:${endCol + 1}` : `${startLine}:${startCol + 1}`;
258
- const relatedLinesDisplay = relatedLines.join(", ");
283
+ const locationDisplay = endLine > startLine ? `L${startLine} C${startCol + 1} - L${endLine} C${endCol + 1}` : `L${startLine} C${startCol + 1}`;
284
+ const relatedLinesDisplay = formatLineRanges(relatedLines);
259
285
  statements.push({
260
286
  key: stId,
261
287
  Status: covered ? "✓ Covered" : "✗ Not Covered",
262
288
  Location: locationDisplay,
263
289
  "Changed Lines": relatedLinesDisplay,
264
- "Hit Count": covered ? `${count}x` : "Not covered"
290
+ "Hit Count": covered ? `${count}x` : "0x",
291
+ startLine,
292
+ startCol: startCol + 1,
293
+ endLine,
294
+ endCol: endCol + 1,
295
+ relatedLines
265
296
  });
266
297
  }
267
298
  });
268
299
  statements.sort((a, b) => {
269
- return parseInt(a.Location.split(":")[0] || "0") - parseInt(b.Location.split(":")[0] || "0");
300
+ const aMatch = a.Location.match(/L(\d+)/);
301
+ const bMatch = b.Location.match(/L(\d+)/);
302
+ return (aMatch ? parseInt(aMatch[1] || "0") : 0) - (bMatch ? parseInt(bMatch[1] || "0") : 0);
270
303
  });
271
304
  return statements;
272
305
  }, [addLines, coverage]);
306
+ const filteredStatements = useMemo(() => {
307
+ return relatedStatements.filter((s) => s.Status.includes("✗"));
308
+ }, [relatedStatements]);
309
+ const allStatementsForSelect = useMemo(() => {
310
+ return relatedStatements;
311
+ }, [relatedStatements]);
312
+ const groupedOptions = useMemo(() => {
313
+ const covered = relatedStatements.filter((s) => s.Status.includes("✓"));
314
+ const notCovered = relatedStatements.filter((s) => s.Status.includes("✗"));
315
+ const groups = [];
316
+ if (notCovered.length > 0) groups.push({
317
+ label: /* @__PURE__ */ jsxs("span", {
318
+ style: {
319
+ fontWeight: 500,
320
+ color: "#f44336"
321
+ },
322
+ children: [
323
+ "✗ Not Covered (",
324
+ notCovered.length,
325
+ ")"
326
+ ]
327
+ }),
328
+ title: "Not Covered",
329
+ options: notCovered.map((stmt) => ({
330
+ value: stmt.key,
331
+ label: `${stmt.Location} Lines: ${stmt["Changed Lines"]} ${stmt["Hit Count"]}`
332
+ }))
333
+ });
334
+ if (covered.length > 0) groups.push({
335
+ label: /* @__PURE__ */ jsxs("span", {
336
+ style: {
337
+ fontWeight: 500,
338
+ color: "#4caf50"
339
+ },
340
+ children: [
341
+ "✓ Covered (",
342
+ covered.length,
343
+ ")"
344
+ ]
345
+ }),
346
+ title: "Covered",
347
+ options: covered.map((stmt) => ({
348
+ value: stmt.key,
349
+ label: `${stmt.Location} Lines: ${stmt["Changed Lines"]} ${stmt["Hit Count"]}`
350
+ }))
351
+ });
352
+ return groups;
353
+ }, [relatedStatements]);
273
354
  const coverageStats = useMemo(() => {
274
355
  if (relatedStatements.length === 0) return {
275
356
  coveredCount: 0,
276
357
  totalCount: 0,
277
- coveragePercent: 0
358
+ coveragePercent: 0,
359
+ notCoveredCount: 0
278
360
  };
279
361
  const coveredCount = relatedStatements.filter((s) => s.Status.includes("✓")).length;
280
362
  const totalCount = relatedStatements.length;
363
+ const notCoveredCount = filteredStatements.length;
281
364
  return {
282
365
  coveredCount,
283
366
  totalCount,
284
- coveragePercent: totalCount > 0 ? Math.round(coveredCount / totalCount * 100) : 0
367
+ coveragePercent: totalCount > 0 ? Math.round(coveredCount / totalCount * 100) : 0,
368
+ notCoveredCount
285
369
  };
286
- }, [relatedStatements]);
287
- if (relatedStatements.length === 0) return null;
370
+ }, [relatedStatements, filteredStatements]);
371
+ if (filteredStatements.length === 0) return null;
372
+ const handleSelectChange = (value) => {
373
+ setSelectedValue(value);
374
+ const statement = allStatementsForSelect.find((s) => s.key === value);
375
+ if (statement && onJumpToRange) onJumpToRange(statement.startLine, statement.startCol, statement.endLine, statement.endCol);
376
+ };
288
377
  return /* @__PURE__ */ jsxs("div", {
289
378
  className: "canyon-changed-code-coverage-table",
290
379
  children: [/* @__PURE__ */ jsxs("button", {
@@ -303,7 +392,9 @@ const ChangedCodeCoverageTable = ({ coverage, addLines }) => {
303
392
  coverageStats.coveredCount,
304
393
  "/",
305
394
  coverageStats.totalCount,
306
- ")"
395
+ ") - ",
396
+ coverageStats.notCoveredCount,
397
+ " 未覆盖"
307
398
  ] }),
308
399
  /* @__PURE__ */ jsx("span", {
309
400
  className: "canyon-changed-code-coverage-arrow",
@@ -312,43 +403,136 @@ const ChangedCodeCoverageTable = ({ coverage, addLines }) => {
312
403
  ]
313
404
  }), /* @__PURE__ */ jsx("div", {
314
405
  className: "canyon-changed-code-coverage-content",
315
- style: { maxHeight: isOpen ? "350px" : "0" },
406
+ style: {
407
+ maxHeight: isOpen ? "100px" : "0",
408
+ overflow: isOpen ? "visible" : "hidden",
409
+ opacity: isOpen ? 1 : 0
410
+ },
316
411
  children: /* @__PURE__ */ jsx("div", {
317
- className: "canyon-changed-code-coverage-table-wrapper",
318
- children: /* @__PURE__ */ jsx(ConfigProvider, {
319
- theme: { token: { borderRadius: 0 } },
320
- children: /* @__PURE__ */ jsx(Table, {
321
- dataSource: relatedStatements,
322
- columns: [
323
- {
324
- title: "Status",
325
- dataIndex: "Status",
326
- key: "Status",
327
- render: (text) => /* @__PURE__ */ jsx("span", {
328
- style: { color: text.includes("") ? "#4caf50" : "#f44336" },
329
- children: text
330
- })
331
- },
332
- {
333
- title: "Location",
334
- dataIndex: "Location",
335
- key: "Location"
412
+ className: "canyon-changed-code-coverage-select-wrapper",
413
+ children: /* @__PURE__ */ jsx(Select, {
414
+ value: selectedValue,
415
+ onChange: handleSelectChange,
416
+ placeholder: "Select code location (use arrow keys)",
417
+ style: {
418
+ width: "480px",
419
+ maxWidth: "100%"
420
+ },
421
+ size: "small",
422
+ showSearch: true,
423
+ optionFilterProp: "label",
424
+ tagRender: (props) => {
425
+ const { value } = props;
426
+ const stmt = allStatementsForSelect.find((s) => s.key === value);
427
+ if (!stmt) return /* @__PURE__ */ jsx("span", { children: value });
428
+ const isCovered = stmt.Status.includes("");
429
+ return /* @__PURE__ */ jsxs("span", {
430
+ style: {
431
+ display: "inline-flex",
432
+ alignItems: "center",
433
+ gap: "6px",
434
+ fontSize: "13px",
435
+ lineHeight: "20px"
336
436
  },
337
- {
338
- title: "Changed Lines",
339
- dataIndex: "Changed Lines",
340
- key: "Changed Lines"
437
+ children: [
438
+ /* @__PURE__ */ jsx("span", {
439
+ style: {
440
+ color: isCovered ? "#4caf50" : "#f44336",
441
+ fontWeight: "bold",
442
+ fontSize: "14px"
443
+ },
444
+ children: isCovered ? "✓" : "✗"
445
+ }),
446
+ /* @__PURE__ */ jsx("span", {
447
+ style: { fontFamily: "monospace" },
448
+ children: stmt.Location
449
+ }),
450
+ /* @__PURE__ */ jsxs("span", {
451
+ style: {
452
+ color: "#666",
453
+ fontSize: "12px"
454
+ },
455
+ children: ["Lines: ", stmt["Changed Lines"]]
456
+ }),
457
+ /* @__PURE__ */ jsx("span", {
458
+ style: {
459
+ color: isCovered ? "#4caf50" : "#f44336",
460
+ fontSize: "12px"
461
+ },
462
+ children: stmt["Hit Count"]
463
+ })
464
+ ]
465
+ });
466
+ },
467
+ filterOption: (input, option) => {
468
+ if (!option) return false;
469
+ if (option.options) return true;
470
+ return (option?.label?.toString() || "").toLowerCase().includes(input.toLowerCase());
471
+ },
472
+ optionRender: (option) => {
473
+ if (!option) return null;
474
+ if (option.options) return option.label;
475
+ const stmt = allStatementsForSelect.find((s) => s.key === option.value);
476
+ if (!stmt) return option.label;
477
+ const isCovered = stmt.Status.includes("✓");
478
+ return /* @__PURE__ */ jsxs("div", {
479
+ style: {
480
+ display: "flex",
481
+ alignItems: "center",
482
+ gap: "6px",
483
+ padding: "2px 0",
484
+ fontSize: "13px",
485
+ lineHeight: "20px"
341
486
  },
342
- {
343
- title: "Hit Count",
344
- dataIndex: "Hit Count",
345
- key: "Hit Count"
346
- }
347
- ],
348
- pagination: false,
349
- size: "small",
350
- bordered: true
351
- })
487
+ children: [
488
+ /* @__PURE__ */ jsx("span", {
489
+ style: {
490
+ color: isCovered ? "#4caf50" : "#f44336",
491
+ fontWeight: "bold",
492
+ width: "16px",
493
+ fontSize: "14px",
494
+ textAlign: "center",
495
+ flexShrink: 0
496
+ },
497
+ title: isCovered ? "Covered" : "Not Covered",
498
+ children: isCovered ? "✓" : "✗"
499
+ }),
500
+ /* @__PURE__ */ jsx("span", {
501
+ style: {
502
+ fontFamily: "monospace",
503
+ width: "180px",
504
+ flexShrink: 0,
505
+ overflow: "hidden",
506
+ textOverflow: "ellipsis",
507
+ whiteSpace: "nowrap"
508
+ },
509
+ children: stmt.Location
510
+ }),
511
+ /* @__PURE__ */ jsxs("span", {
512
+ style: {
513
+ color: "#666",
514
+ fontSize: "12px",
515
+ width: "100px",
516
+ flexShrink: 0,
517
+ textAlign: "left"
518
+ },
519
+ children: ["Lines: ", stmt["Changed Lines"]]
520
+ }),
521
+ /* @__PURE__ */ jsx("span", {
522
+ style: {
523
+ color: isCovered ? "#4caf50" : "#f44336",
524
+ fontSize: "12px",
525
+ width: "80px",
526
+ flexShrink: 0,
527
+ textAlign: "right",
528
+ fontWeight: isCovered ? "500" : "normal"
529
+ },
530
+ children: stmt["Hit Count"]
531
+ })
532
+ ]
533
+ });
534
+ },
535
+ options: groupedOptions
352
536
  })
353
537
  })
354
538
  })]
@@ -414,9 +598,12 @@ function lineNumbers(lineNumber, linesState, _addLines) {
414
598
  //#region src/widgets/CoverageDetail.tsx
415
599
  const CoverageDetail = ({ source, coverage, diff }) => {
416
600
  const addLines = diff.additions || [];
417
- coverage = changeModeFilterIrrelevantData(coverage, diff);
601
+ coverage = addLines.length > 0 ? changeModeFilterIrrelevantData(coverage, diff) : coverage;
418
602
  const { lines } = coreFn(coverage, source);
419
603
  const ref = useRef(null);
604
+ const editorRef = useRef(null);
605
+ const highlightDecorationsRef = useRef(null);
606
+ const highlightTimeoutRef = useRef(null);
420
607
  const hasChangedLines = addLines.length > 0 && coverage["s"] && coverage["statementMap"];
421
608
  const linesState = (() => {
422
609
  return lines.map((line, index) => {
@@ -453,6 +640,7 @@ const CoverageDetail = ({ source, coverage, diff }) => {
453
640
  };
454
641
  if (window.monaco?.editor && dom) {
455
642
  const editor = window.monaco.editor.create(dom, options);
643
+ editorRef.current = editor;
456
644
  const decorations = (() => {
457
645
  const all = [];
458
646
  const annotateStatementsList = annotateStatements(coverage);
@@ -495,9 +683,20 @@ const CoverageDetail = ({ source, coverage, diff }) => {
495
683
  }
496
684
  return arr;
497
685
  })();
498
- if (editor) editor?.createDecorationsCollection?.(decorations);
686
+ if (editor) {
687
+ editor?.createDecorationsCollection?.(decorations);
688
+ highlightDecorationsRef.current = editor.createDecorationsCollection([]);
689
+ }
499
690
  }
500
691
  }
692
+ return () => {
693
+ if (highlightTimeoutRef.current) clearTimeout(highlightTimeoutRef.current);
694
+ if (editorRef.current) {
695
+ editorRef.current.dispose();
696
+ editorRef.current = null;
697
+ }
698
+ highlightDecorationsRef.current = null;
699
+ };
501
700
  }, [source, coverage]);
502
701
  return /* @__PURE__ */ jsxs("div", {
503
702
  className: "canyon-coverage-detail-container",
@@ -507,7 +706,31 @@ const CoverageDetail = ({ source, coverage, diff }) => {
507
706
  },
508
707
  children: [hasChangedLines ? /* @__PURE__ */ jsx(ChangedCodeCoverageTable_default, {
509
708
  coverage,
510
- addLines
709
+ addLines,
710
+ onJumpToRange: (startLine, startCol, endLine, endCol) => {
711
+ if (editorRef.current && window.monaco) {
712
+ const editor = editorRef.current;
713
+ if (highlightTimeoutRef.current) clearTimeout(highlightTimeoutRef.current);
714
+ if (highlightDecorationsRef.current) highlightDecorationsRef.current.clear();
715
+ const range = new window.monaco.Range(startLine, startCol, endLine, endCol);
716
+ if (highlightDecorationsRef.current) highlightDecorationsRef.current.set([{
717
+ range,
718
+ options: {
719
+ isWholeLine: false,
720
+ inlineClassName: "canyon-jump-highlight-range",
721
+ stickiness: window.monaco.editor.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges
722
+ }
723
+ }]);
724
+ editor.setPosition({
725
+ lineNumber: startLine,
726
+ column: startCol
727
+ });
728
+ editor.revealRange(range, window.monaco.editor.ScrollType.Smooth);
729
+ highlightTimeoutRef.current = setTimeout(() => {
730
+ if (highlightDecorationsRef.current) highlightDecorationsRef.current.clear();
731
+ }, 3e3);
732
+ }
733
+ }
511
734
  }) : null, /* @__PURE__ */ jsx("div", {
512
735
  ref,
513
736
  className: "canyon-coverage-detail-editor",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@canyonjs/report-component",
3
3
  "type": "module",
4
- "version": "0.0.10",
4
+ "version": "1.0.16",
5
5
  "author": "Travis Zhang<https://github.com/travzhang>",
6
6
  "repository": {
7
7
  "type": "git",