Skip to Content

Canyon Report

A frontend code coverage visualization component (Library) based on Monaco Editor. Provides line coverage count, changed line markers, and syntax block (statements/functions/branches) uncovered highlighting capabilities, suitable for quickly rendering single-file level coverage details in web pages.

Interface Introduction

Interface Graphics Display

Interface Color Coding

Colorpct (Coverage Percentage)Description
Greenpct >= 80Statement coverage greater than or equal to 80
Yellowpct >= 50 && pct < 80Statement coverage greater than or equal to 50 and less than 80
Redpct < 50Statement coverage less than 50

Interface Area Functions

View mode toggle: Switch between "code tree" and "file list" views Changed file filter: "Show changed only" toggle, filter to show files with code changes File search: Enter file path keywords to search and filter File statistics: Display total number of files under current filter conditions
Project path navigation: Display current project path hierarchy Coverage statistics overview: Display key coverage metrics Statement Coverage (Statements) Branch Coverage (Branches) Function Coverage (Functions) Line Coverage (Lines) New Line Coverage (New Lines) Changed Function Coverage (Changed Functions) Changed Branch Coverage (Changed Branches) Changed Statement Coverage (Changed Statements)
Table display: Display file list in table format Column information: File name, total count, covered count, coverage percentage Progress bar: Visually display coverage level File type icons: Distinguish files and folders
Green: Display execution count on the line number side. Dark green: Changed markers displayed on the line number side, highlight changed lines by passing diff. Red: Uncovered statements and functions Yellow: Incompletely covered branch code

Installation

npm install canyon-report

Basic Usage

import Report from "canyon-report"; function App() { const dataSource = { // Istanbul coverage data }; const handleSelect = async (filePath) => { // Return detailed information of selected file return { fileContent: "File content", fileCoverage: {}, // Istanbul FileCoverageData fileCodeChange: [], // Array of changed line numbers }; }; return ( <Report theme="light" language="cn" name="Project Name" dataSource={dataSource} value="Currently selected file path" onSelect={handleSelect} defaultOnlyShowChanged={false} /> ); }

API Parameters

ParameterTypeDescription
themeThemeEnum Theme mode
languageLanguageEnumInterface language
namestringProject name
dataSource{ [key: string]: CoverageSummaryData & { path: string,change:boolean } }Coverage data source
valuestringCurrently selected file
onSelect(val: string) => Promise<{fileCoverage: FileCoverageData;fileContent: string; fileCodeChange: number[]; }>
defaultOnlyShowChangedbooleanWhether to show only changed files by default

数据结构

CoverageSummaryData

interface CoverageSummaryData { lines: Totals; statements: Totals; branches: Totals; functions: Totals; }

Totals

interface Totals { total: number; covered: number; skipped: number; pct: number; }

FileCoverageData

interface FileCoverageData { path: string; statementMap: { [key: string]: Range }; fnMap: { [key: string]: FunctionMapping }; branchMap: { [key: string]: BranchMapping }; s: { [key: string]: number }; f: { [key: string]: number }; b: { [key: string]: number[] }; }

BranchMapping

export interface BranchMapping { loc: Range; type: string; locations: Range[]; line: number; }

FunctionMapping

export interface FunctionMapping { name: string; decl: Range; loc: Range; line: number; }

Range

interface Range { start: Location; end: Location; }

Location

interface Location { line: number; column: number; }

ThemeEnum

enum ThemeEnum { Light = "light", Dark = "dark", }

LanguageEnum

enum LanguageEnum { CN = "cn", EN = "en", JA = "ja", }

数据示例

dataSource

{ "src/App.tsx": { "branches": { "covered": 0, "pct": 100, "skipped": 0, "total": 0 }, "change": false, "functions": { "covered": 1, "pct": 33.33333333333333, "skipped": 0, "total": 3 }, "lines": { "covered": 2, "pct": 50, "skipped": 0, "total": 4 }, "path": "src/App.tsx", "statements": { "covered": 2, "pct": 50, "skipped": 0, "total": 4 } }, "src/components/A0.tsx": { "branches": { "covered": 0, "pct": 100, "skipped": 0, "total": 0 }, "change": false, "functions": { "covered": 1, "pct": 100, "skipped": 0, "total": 1 }, "lines": { "covered": 2, "pct": 100, "skipped": 0, "total": 2 }, "path": "src/components/A0.tsx", "statements": { "covered": 2, "pct": 100, "skipped": 0, "total": 2 } }, "src/components/B0.tsx": { "branches": { "covered": 0, "pct": 100, "skipped": 0, "total": 0 }, "change": false, "functions": { "covered": 3, "pct": 100, "skipped": 0, "total": 3 }, "lines": { "covered": 6, "pct": 100, "skipped": 0, "total": 6 }, "path": "src/components/B0.tsx", "statements": { "covered": 6, "pct": 100, "skipped": 0, "total": 6 } }, "src/components/C0.tsx": { "branches": { "covered": 1, "pct": 100, "skipped": 0, "total": 0 }, "change": false, "functions": { "covered": 2, "pct": 100, "skipped": 0, "total": 2 }, "lines": { "covered": 5, "pct": 83.33333333333334, "skipped": 0, "total": 6 }, "path": "src/components/C0.tsx", "statements": { "covered": 5, "pct": 83.33333333333334, "skipped": 0, "total": 6 } }, "src/main.tsx": { "branches": { "covered": 0, "pct": 100, "skipped": 0, "total": 0 }, "change": false, "functions": { "covered": 1, "pct": 100, "skipped": 0, "total": 1 }, "lines": { "covered": 3, "pct": 100, "skipped": 0, "total": 3 }, "path": "src/main.tsx", "statements": { "covered": 3, "pct": 100, "skipped": 0, "total": 3 } } }

handleSelect 返回内容

{ fileContent:"import { useState } from 'react'\nimport reactLogo from './assets/react.svg'\nimport viteLogo from '/vite.svg'\nimport './App.css'\nimport A0 from \"./components/A0.tsx\";\nimport B0 from \"./components/B0.tsx\";\nimport C0 from \"./components/C0.tsx\";\nfunction App() {\n const [count, setCount] = useState(0)\n return (\n <>\n <A0></A0>\n <B0></B0>\n <C0></C0>\n <div>\n <a href=\"https://vite.dev\" target=\"_blank\">\n <img src={viteLogo} className=\"logo\" alt=\"Vite logo\" />\n </a>\n <a href=\"https://react.dev\" target=\"_blank\">\n <img src={reactLogo} className=\"logo react\" alt=\"React logo\" />\n </a>\n </div>\n <h1>Vite + React123</h1>\n <div className=\"card\">\n <button onClick={() => setCount((count) => count + 2)}>\n count is {count}\n </button>\n <p>\n Edit <code>src/App.tsx</code> and save to test HMR\n </p>\n </div>\n <p className=\"read-the-docs\">\n Click on the Vite and React logos to learn more\n </p>\n </>\n )\n}\n\nexport default App\n" fileCodeChange: [26,27,28], fileCoverage:{ "b": {}, "branchMap": {}, "f": { "0": 151, "1": 0, "2": 0 }, "fnMap": { "0": { "decl": { "end": { "column": 12, "line": 8 }, "start": { "column": 9, "line": 8 } }, "line": 8, "loc": { "end": { "column": 1, "line": 37 }, "start": { "column": 15, "line": 8 } }, "name": "App" }, "1": { "decl": { "end": { "column": 26, "line": 25 }, "start": { "column": 25, "line": 25 } }, "line": 25, "loc": { "end": { "column": 61, "line": 25 }, "start": { "column": 31, "line": 25 } }, "name": "(anonymous_1)" }, "2": { "decl": { "end": { "column": 41, "line": 25 }, "start": { "column": 40, "line": 25 } }, "line": 25, "loc": { "end": { "column": 60, "line": 25 }, "start": { "column": 51, "line": 25 } }, "name": "(anonymous_2)" } }, "path": "src/App.tsx", "s": { "0": 172, "1": 151, "2": 0, "3": 0 }, "statementMap": { "0": { "end": { "column": 39, "line": 9 }, "start": { "column": 28, "line": 9 } }, "1": { "end": { "column": 3, "line": 36 }, "start": { "column": 2, "line": 10 } }, "2": { "end": { "column": 61, "line": 25 }, "start": { "column": 31, "line": 25 } }, "3": { "end": { "column": 60, "line": 25 }, "start": { "column": 51, "line": 25 } } } } }