Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

monocart-coverage-reports

Package Overview
Dependencies
Maintainers
1
Versions
91
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

monocart-coverage-reports - npm Package Compare versions

Comparing version 2.2.2 to 2.3.0

6

lib/cli.js

@@ -39,8 +39,2 @@ #!/usr/bin/env node

// init reports to list with `,`
const reports = cliOptions.reports;
if (reports && typeof reports === 'string') {
cliOptions.reports = reports.split(',');
}
// report options

@@ -47,0 +41,0 @@ const options = {

31

lib/converter/ast.js

@@ -39,2 +39,13 @@ const {

// root function branches
const functionRoot = state.functionRoot;
if (functionRoot) {
const rootFunctionInfo = {
count: functionRoot.count,
range: functionRoot
};
createBranches(ast, rootFunctionInfo, branchMap);
}
// functions branches
Util.visitAst(ast, {

@@ -105,4 +116,7 @@

const functionNameMap = new Map();
let functionRoot;
coverageList.forEach((block) => {
const { functionName, ranges } = block;
const {
functionName, ranges, root
} = block;

@@ -112,5 +126,2 @@ // first one is function coverage info

functionRange.functionName = functionName;
if (functionName) {
functionNameMap.set(functionRange.startOffset + functionName.length, functionRange);
}

@@ -134,2 +145,11 @@ // blocks

// root function
if (root) {
functionRoot = functionRange;
return;
}
if (functionName) {
functionNameMap.set(functionRange.startOffset + functionName.length, functionRange);
}
functionRanges.push(functionRange);

@@ -153,3 +173,4 @@

functionNameMap,
functionRanges
functionRanges,
functionRoot
};

@@ -156,0 +177,0 @@

@@ -84,2 +84,10 @@ const fs = require('fs');

await Util.writeFile(filePath, JSON.stringify(data));
// save source and sourcemap file for debug
// https://evanw.github.io/source-map-visualization
// if (data.sourceMap) {
// await Util.writeFile(`${filePath}.js`, data.source);
// await Util.writeFile(`${filePath}.js.map`, JSON.stringify(data.sourceMap));
// }
};

@@ -109,11 +117,15 @@

// save source and sourceMap to separated json file
const sourceData = {
url,
id,
// source,
source: convertSourceMap.removeComments(source),
// could be existed
source,
sourceMap
};
// remove comments if not debug
if (Util.loggingType !== 'debug') {
sourceData.source = convertSourceMap.removeComments(source);
}
// check sourceMap only for js

@@ -120,0 +132,0 @@ if (type === 'js' && !sourceData.sourceMap) {

@@ -1,25 +0,10 @@

// https://github.com/demurgos/v8-coverage
/**
* @ranges is always non-empty. The first range is called the "root range".
* @isBlockCoverage indicates if the function has block coverage information
* @false means that there is a single range and its count is the number of times the function was called.
* @true means that the ranges form a tree of blocks representing how many times each statement or expression inside was executed.
* It detects skipped or repeated statements. The root range counts the number of function calls.
* @functionName can be an empty string. This is common for the FunctionCov representing the whole module.
* V8 Coverage Data Converter
* @copyright https://github.com/cenfun/monocart-coverage-reports
* @author cenfun@gmail.com
*/
// https://github.com/bcoe/v8-coverage
/**
* @ranges is always non-empty. The first range is called the "root range".
* @isBlockCoverage indicates if the function has block coverage information.
If this is false, it usually means that the functions was never called.
It seems to be equivalent to ranges.length === 1 && ranges[0].count === 0.
* @functionName can be an empty string. This is common for the FunctionCov representing the whole module.
*/
// if you have a line of code that says `var x= 10; console.log(x);` that's one line and 2 statements.
const path = require('path');
const Util = require('../utils/util.js');
const decodeMappings = require('../utils/decode-mappings.js');

@@ -32,4 +17,7 @@ // position mapping for conversion between offset and line/column

const { dedupeCountRanges } = require('../utils/dedupe.js');
const {
sortRanges, dedupeCountRanges, mergeRangesWith
} = require('../utils/dedupe.js');
const { getSourceType, initSourceMapSourcePath } = require('../utils/source-path.js');
const { decode } = require('../packages/monocart-coverage-vendor.js');

@@ -230,5 +218,4 @@ const InfoLine = require('./info-line.js');

*/
const collectFileCoverage = (item, state, coverageData, options) => {
const collectFileCoverage = (v8Data, state, options) => {
const { sourcePath } = item;
const {

@@ -238,2 +225,4 @@ bytes,

branches,
sourcePath,
locator

@@ -283,2 +272,3 @@ } = state;

});
sortRanges(data.functions);

@@ -289,2 +279,3 @@ // branch group with locations to flat branches

}).flat();
sortRanges(data.branches);

@@ -300,4 +291,4 @@ // ==========================================

// v8 data and summary
item.data = data;
item.summary = {
v8Data.data = data;
v8Data.summary = {
functions: calculateV8Functions(data.functions),

@@ -310,3 +301,3 @@ branches: calculateV8Branches(data.branches),

// istanbul
const istanbulCoverage = {
const istanbulData = {
path: sourcePath,

@@ -327,4 +318,4 @@

lines.filter((it) => !it.ignored).forEach((line, index) => {
istanbulCoverage.statementMap[`${index}`] = line.generate();
istanbulCoverage.s[`${index}`] = line.count;
istanbulData.statementMap[`${index}`] = line.generate();
istanbulData.s[`${index}`] = line.count;

@@ -342,4 +333,4 @@ let count = 0;

functions.filter((it) => !it.ignored).forEach((fn, index) => {
istanbulCoverage.fnMap[`${index}`] = fn.generate(locator);
istanbulCoverage.f[`${index}`] = fn.count;
istanbulData.fnMap[`${index}`] = fn.generate(locator);
istanbulData.f[`${index}`] = fn.count;
});

@@ -349,8 +340,8 @@

const { map, counts } = branch.generate(locator);
istanbulCoverage.branchMap[`${index}`] = map;
istanbulCoverage.b[`${index}`] = counts;
istanbulData.branchMap[`${index}`] = map;
istanbulData.b[`${index}`] = counts;
});
// append to dist file state
coverageData[sourcePath] = istanbulCoverage;
// istanbul data
return istanbulData;

@@ -625,3 +616,3 @@ };

const decodeSourceMappings = async (state, originalDecodedMap) => {
const decodeSourceMappings = (state, originalDecodedMap) => {

@@ -632,3 +623,3 @@ const generatedLocator = state.locator;

const decodedList = await decodeMappings(mappings);
const decodedList = decode(mappings);

@@ -694,3 +685,3 @@ sources.forEach((source, i) => {

if (!decodeMappings) {
if (!decodedMappings) {
return [];

@@ -729,4 +720,2 @@ }

const fileSources = state.fileSources;
// create original content mappings

@@ -751,5 +740,2 @@ const originalMap = new Map();

// keep original formatted content
fileSources[sourcePath] = sourceContent;
const locator = new Locator(sourceContent);

@@ -765,7 +751,7 @@

original: true,
// only js sourceMap for now
// original file is js
js: true,
type,
source: sourceContent,
sourcePath,
source: sourceContent,
locator,

@@ -780,3 +766,5 @@ decodedMappings,

branches: []
}
},
// coverage data
v8Data: {}
};

@@ -790,10 +778,8 @@

const collectOriginalList = (item, state, originalMap, options) => {
const collectOriginalList = (state, originalMap) => {
const { fileUrls, sourceMap } = state;
const distFile = sourceMap.file || path.basename(item.sourcePath);
const distFile = sourceMap.file || path.basename(state.sourcePath);
// collect original files
const sourceList = [];
originalMap.forEach((originalState) => {

@@ -809,3 +795,3 @@

// add dist for id
const id = Util.calculateSha1(distFile + sourcePath + source);
const id = Util.calculateSha1(sourcePath + source);

@@ -822,9 +808,7 @@ const sourceItem = {

// generate coverage for current file, state for dist file
collectFileCoverage(sourceItem, originalState, state.coverageData, options);
sourceList.push(sourceItem);
// save v8 data and add to originalList
originalState.v8Data = sourceItem;
state.originalList.push(originalState);
});
return sourceList;
};

@@ -834,3 +818,3 @@

const generateCoverageForDist = (item, state, options) => {
const generateCoverageForDist = (state) => {

@@ -841,10 +825,7 @@ handleFunctionsCoverage(state);

collectFileCoverage(item, state, state.coverageData, options);
};
const unpackSourceMap = async (item, state, options) => {
const unpackSourceMap = (state, options) => {
const { sourcePath } = item;
const sourceMap = state.sourceMap;
const { sourceMap } = state;

@@ -859,8 +840,6 @@ // keep original urls

// decode mappings for each original file
const time_start_decode = Date.now();
const originalDecodedMap = new Map();
// for find-original-range
state.decodedMappings = await decodeSourceMappings(state, originalDecodedMap);
// only debug level
Util.logTime(`decode source mappings: ${sourcePath}`, time_start_decode);
state.decodedMappings = decodeSourceMappings(state, originalDecodedMap);

@@ -894,7 +873,7 @@ // filter original list and init list

// collect coverage for original list
state.sourceList = collectOriginalList(item, state, originalMap, options);
collectOriginalList(state, originalMap);
};
const unpackDistFile = async (item, state, options) => {
const unpackDistFile = (item, state, options) => {

@@ -905,3 +884,3 @@ if (state.sourceMap) {

item.debug = true;
generateCoverageForDist(item, state, options);
generateCoverageForDist(state);
} else {

@@ -912,3 +891,3 @@ item.dedupe = true;

// unpack source map
await unpackSourceMap(item, state, options);
unpackSourceMap(state, options);

@@ -918,3 +897,3 @@ } else {

// css/js self
generateCoverageForDist(item, state, options);
generateCoverageForDist(state);

@@ -927,24 +906,257 @@ }

const dedupeV8List = (v8list) => {
const indexes = [];
v8list.forEach((item, i) => {
if (item.dedupe) {
indexes.push(i);
const filterCoverageList = (item) => {
const {
functions, scriptOffset, source
} = item;
// no script offset
if (!scriptOffset) {
return functions;
}
// vm script offset
const minOffset = scriptOffset;
// the inline sourcemap could be removed
const maxOffset = source.length;
const rootFunctionInfo = {
root: true,
ranges: [{
startOffset: minOffset,
endOffset: maxOffset,
count: 1
}]
};
const coverageList = functions.filter((block) => {
const { ranges } = block;
// first one is function coverage info
const functionRange = ranges[0];
const { startOffset, endOffset } = functionRange;
if (startOffset >= minOffset && endOffset <= maxOffset) {
return true;
}
// blocks
const len = ranges.length;
if (len > 1) {
for (let i = 1; i < len; i++) {
const range = ranges[i];
if (range.startOffset >= minOffset && range.endOffset <= maxOffset) {
rootFunctionInfo.ranges.push(range);
}
}
}
return false;
});
if (indexes.length) {
indexes.reverse();
indexes.forEach((i) => {
v8list.splice(i, 1);
});
// first one for root function
if (rootFunctionInfo.ranges.length > 1) {
coverageList.unshift(rootFunctionInfo);
}
return coverageList;
};
const convertV8List = async (v8list, options) => {
// ========================================================================================================
// global file sources and coverage
const isCoveredInRanges = (range, uncoveredBytes) => {
if (!uncoveredBytes.length) {
return true;
}
for (const item of uncoveredBytes) {
if (range.start >= item.start && range.end <= item.end) {
return false;
}
}
return true;
};
const mergeV8Data = (state, stateList) => {
// console.log(stateList);
// const sourcePath = state.sourcePath;
// console.log(sourcePath);
// if (sourcePath.endsWith('scroll_zoom.ts')) {
// console.log('merge v8 data ===================================', sourcePath);
// console.log(stateList.map((it) => it.bytes));
// console.log(stateList.map((it) => it.functions));
// console.log(stateList.map((it) => it.branches.map((b) => [`${b.start}-${b.end}`, JSON.stringify(b.locations.map((l) => l.count))])));
// }
// ===========================================================
// bytes
const mergedBytes = [];
const uncoveredList = [];
stateList.forEach((st) => {
const bytes = dedupeCountRanges(st.bytes);
const uncoveredBytes = [];
bytes.forEach((range) => {
if (range.count) {
mergedBytes.push(range);
} else {
uncoveredBytes.push(range);
}
});
uncoveredList.push(uncoveredBytes);
});
// just remove uncovered range
uncoveredList.forEach((currentBytes) => {
currentBytes.forEach((range) => {
for (const targetBytes of uncoveredList) {
if (targetBytes === currentBytes) {
continue;
}
if (isCoveredInRanges(range, targetBytes)) {
return;
}
}
mergedBytes.push(range);
});
});
// will be dedupeCountRanges in collectFileCoverage
state.bytes = mergedBytes;
// ===========================================================
// functions
const allFunctions = stateList.map((it) => it.functions).flat();
const functionComparer = (lastRange, range) => {
// if (lastRange.start === range.start && lastRange.end === range.end) {
// return true;
// }
// function range could be from sourcemap, not exact matched
// end is same
// {start: 2017, end: 2315, count: 481}
// {start: 2018, end: 2315, count: 14}
// start is same
// {start: 10204, end: 10379, count: 0}
// {start: 10204, end: 10393, count: 5}
// only one position matched could be same
if (lastRange.start === range.start || lastRange.end === range.end) {
// console.log(lastRange.start, range.start, lastRange.end, range.end);
// if (lastRange.start === range.start) {
// console.log(range.end - lastRange.end, lastRange.start, lastRange.end, 'end', range.end, state.sourcePath);
// } else {
// console.log(range.start - lastRange.start, lastRange.start, lastRange.end, 'start', range.start, state.sourcePath);
// }
return true;
}
return false;
};
const functionHandler = (lastRange, range) => {
lastRange.count += range.count;
};
const mergedFunctions = mergeRangesWith(allFunctions, functionComparer, functionHandler);
state.functions = mergedFunctions;
// ===========================================================
// branches
const allBranches = stateList.map((it) => it.branches).flat();
const branchComparer = (lastRange, range) => {
// exact matched because the branch range is generated from ast
return lastRange.start === range.start && lastRange.end === range.end;
};
const branchHandler = (lastRange, range) => {
// merge locations count
lastRange.locations.forEach((item, i) => {
const loc = range.locations[i];
if (loc) {
item.count += loc.count;
}
});
};
const mergedBranches = mergeRangesWith(allBranches, branchComparer, branchHandler);
state.branches = mergedBranches;
// if (sourcePath.endsWith('scroll_zoom.ts')) {
// console.log(mergedBytes);
// console.log(mergedFunctions);
// console.log(mergedBranches.map((b) => [`${b.start}-${b.end}`, JSON.stringify(b.locations.map((l) => l.count))]));
// }
};
const generateV8DataList = (stateList, options) => {
const stateMap = new Map();
// all original files from dist
const allOriginalList = [];
stateList.forEach((state) => {
const { v8Data, originalList } = state;
// dedupe dist file if not debug
if (!v8Data.dedupe) {
stateMap.set(v8Data.id, state);
}
allOriginalList.push(originalList);
});
// merge istanbul and v8(converted)
const mergeMap = new Map();
allOriginalList.flat().forEach((originalState) => {
const { v8Data } = originalState;
const id = v8Data.id;
// exists item
const prevState = stateMap.get(id);
if (prevState) {
// ignore empty item, just override it
if (!prevState.v8Data.empty) {
if (mergeMap.has(id)) {
mergeMap.get(id).push(originalState);
} else {
mergeMap.set(id, [prevState, originalState]);
}
return;
}
}
stateMap.set(id, originalState);
});
const mergeIds = mergeMap.keys();
for (const id of mergeIds) {
const state = stateMap.get(id);
// for source the type could be ts, so just use js (boolean)
if (state.js) {
mergeV8Data(state, mergeMap.get(id));
} else {
// should no css here, css can not be in sources
}
}
// new v8 data list (includes sources)
const v8DataList = [];
// global file sources and istanbul coverage data
const fileSources = {};
const coverageData = {};
let sourceList = [];
stateMap.forEach((state) => {
const { v8Data } = state;
const istanbulData = collectFileCoverage(v8Data, state, options);
const { sourcePath, source } = v8Data;
v8DataList.push(v8Data);
fileSources[sourcePath] = source;
coverageData[sourcePath] = istanbulData;
});
return {
v8DataList,
fileSources,
coverageData
};
};
const convertV8List = (v8list, options) => {
const stateList = [];
for (const item of v8list) {

@@ -961,5 +1173,2 @@ // console.log([item.id]);

// append file source
fileSources[sourcePath] = source;
// source mapping

@@ -969,3 +1178,3 @@ const locator = new Locator(source);

// ============================
// move sourceMap
// move sourceMap
const sourceMap = item.sourceMap;

@@ -981,3 +1190,3 @@ if (sourceMap) {

if (js) {
coverageList = item.functions;
coverageList = filterCoverageList(item);
// remove original functions

@@ -1000,9 +1209,11 @@ if (Util.loggingType !== 'debug') {

// current file and it's sources from sourceMap
// see const originalState
const state = {
js,
type,
source,
sourcePath,
sourceMap,
coverageList,
locator,
decodedMappings: [],
// coverage info

@@ -1013,42 +1224,17 @@ bytes: [],

astInfo,
// for istanbul
fileSources: {},
coverageData: {}
// for sub source files
coverageList,
originalList: [],
// coverage data
v8Data: item
};
await unpackDistFile(item, state, options);
unpackDistFile(item, state, options);
// merge state
Object.assign(fileSources, state.fileSources);
Object.assign(coverageData, state.coverageData);
stateList.push(state);
if (Util.isList(state.sourceList)) {
sourceList = sourceList.concat(state.sourceList);
}
}
// add all sources
if (sourceList.length) {
sourceList.forEach((item) => {
return generateV8DataList(stateList, options);
// second time filter for empty source
// exists same id, mark previous item as dedupe
const prevItem = v8list.find((it) => it.id === item.id);
if (prevItem) {
prevItem.dedupe = true;
}
v8list.push(item);
});
}
// dedupe
dedupeV8List(v8list);
return {
fileSources,
coverageData
};
};

@@ -1055,0 +1241,0 @@

@@ -548,2 +548,7 @@

// if (typeof range.startOffset !== 'number') {
// console.log('=========================================================================', state.sourcePath);
// console.log(range);
// }
// startOffset: inclusive

@@ -550,0 +555,0 @@ // endOffset: exclusive

@@ -36,7 +36,15 @@ const Util = require('../utils/util.js');

// remove ignored
const locations = this.locations.filter((it) => !it.ignored);
// do NOT change previous number type to object, need used for sourcemap
const newLocations = this.locations.filter((it) => !it.ignored).map((it) => {
const item = {
start: it.start,
end: it.end,
none: it.none,
count: it.count
};
// [ { start:{line,column}, end:{line,column}, count }, ...]
locations.forEach((item) => {
// [ { start:{line,column}, end:{line,column}, count }, ...]
Util.updateOffsetToLocation(locator, item);
return item;
});

@@ -47,3 +55,3 @@

type: this.type,
locations: locations.map((item) => {
locations: newLocations.map((item) => {
const {

@@ -68,3 +76,3 @@ start, end, none

const counts = locations.map((item) => item.count);
const counts = newLocations.map((item) => item.count);

@@ -71,0 +79,0 @@ return {

@@ -9,2 +9,5 @@ module.exports = {

// {string|string[]} input raw dir(s)
inputDir: null,
// {string} v8 or html for istanbul by default

@@ -11,0 +14,0 @@ // {array} multiple reports with options

@@ -47,27 +47,26 @@ const fs = require('fs');

const defaultReport = dataType === 'v8' ? 'v8' : 'html';
if (Util.isList(reports)) {
reports.forEach((it) => {
if (Util.isList(it)) {
// ["v8"]
reportMap[it[0]] = {
const reportList = Util.toList(reports, ',');
reportList.forEach((it) => {
if (Util.isList(it)) {
// ["v8"], ["v8", {}]
const id = it[0];
if (typeof id === 'string' && id) {
reportMap[id] = {
... it[1]
};
return;
}
if (typeof it === 'string') {
reportMap[it] = {};
return;
}
reportMap[defaultReport] = {};
});
} else if (typeof reports === 'string' && reports) {
reportMap[reports] = {};
} else {
return;
}
if (typeof it === 'string' && it) {
reportMap[it] = {};
}
});
// using default report if no reports
if (!Object.keys(reportMap).length) {
const defaultReport = dataType === 'v8' ? 'v8' : 'html';
reportMap[defaultReport] = {};
}
const allSupportedReports = {
const allBuildInReports = {
// v8

@@ -78,5 +77,2 @@ 'v8': 'v8',

// both
'console-summary': 'both',
// istanbul

@@ -95,3 +91,7 @@ 'clover': 'istanbul',

'text-lcov': 'istanbul',
'text-summary': 'istanbul'
'text-summary': 'istanbul',
// both
'console-summary': 'both',
'raw': 'both'
};

@@ -102,19 +102,27 @@

Object.keys(reportMap).forEach((k) => {
const options = reportMap[k];
const groupName = allSupportedReports[k];
if (!groupName) {
Util.logError(`Unsupported report: ${k}`);
return;
let type = allBuildInReports[k];
if (!type) {
// for custom reporter
type = options.type || 'v8';
}
let group = reportGroup[groupName];
let group = reportGroup[type];
if (!group) {
group = {};
reportGroup[groupName] = group;
reportGroup[type] = group;
}
group[k] = reportMap[k];
group[k] = options;
});
// requires a default istanbul report if data is istanbul
if (dataType === 'istanbul' && !reportGroup.istanbul) {
reportGroup.istanbul = {
html: {}
};
}
return reportGroup;

@@ -152,19 +160,10 @@ };

const showConsoleSummary = (coverageResults, options) => {
const showConsoleSummary = (reportData, reportOptions, options) => {
const bothGroup = options.reportGroup.both;
if (!bothGroup) {
return;
}
const { metrics } = reportOptions;
const consoleOptions = bothGroup['console-summary'];
if (!consoleOptions) {
return;
}
const { metrics } = consoleOptions;
const {
summary, name, type
} = coverageResults;
} = reportData;
if (name) {

@@ -225,6 +224,32 @@ EC.logCyan(name);

const saveRawReport = (reportData, reportOptions, options) => {
const rawOptions = {
outputDir: 'raw',
... reportOptions
};
const cacheDir = options.cacheDir;
const rawDir = path.resolve(options.outputDir, rawOptions.outputDir);
// console.log(rawDir, cacheDir);
if (fs.existsSync(rawDir)) {
Util.logError(`Failed to save raw report because the dir already exists: ${Util.relativePath(rawDir)}`);
return;
}
const rawParent = path.dirname(rawDir);
if (!fs.existsSync(rawParent)) {
fs.mkdirSync(rawParent, {
recursive: true
});
}
// just rename the cache folder name
fs.renameSync(cacheDir, rawDir);
};
// ========================================================================================================
const getCoverageResults = async (dataList, options) => {
const getCoverageResults = async (dataList, sourceCache, options) => {
// get first and check v8list or istanbul data

@@ -242,12 +267,18 @@ const firstData = dataList[0];

// merge v8list first
const v8list = await mergeV8Coverage(dataList, options);
const v8list = await mergeV8Coverage(dataList, sourceCache, options);
// console.log('after merge', v8list.map((it) => it.url));
const { coverageData, fileSources } = await convertV8List(v8list, options);
return generateV8ListReports(v8list, coverageData, fileSources, options);
// only debug level
const time_start = Date.now();
const results = convertV8List(v8list, options);
const {
v8DataList, coverageData, fileSources
} = results;
Util.logTime(`converted v8 data: ${v8DataList.length}`, time_start);
return generateV8ListReports(v8DataList, coverageData, fileSources, options);
}
// istanbul data
const istanbulData = mergeIstanbulCoverage(dataList, options);
const istanbulData = mergeIstanbulCoverage(dataList);
const { coverageData, fileSources } = initIstanbulData(istanbulData, options);

@@ -257,4 +288,4 @@ return saveIstanbulReports(coverageData, fileSources, options);

const generateCoverageReports = async (dataList, options) => {
const coverageResults = await getCoverageResults(dataList, options);
const generateCoverageReports = async (dataList, sourceCache, options) => {
const coverageResults = await getCoverageResults(dataList, sourceCache, options);

@@ -264,4 +295,21 @@ // [ 'type', 'reportPath', 'name', 'watermarks', 'summary', 'files' ]

showConsoleSummary(coverageResults, options);
const buildInBothReports = {
'console-summary': showConsoleSummary,
'raw': saveRawReport
};
const bothGroup = options.reportGroup.both;
if (bothGroup) {
const bothReports = Object.keys(bothGroup);
for (const reportName of bothReports) {
const reportOptions = bothGroup[reportName];
const buildInHandler = buildInBothReports[reportName];
if (buildInHandler) {
await buildInHandler(coverageResults, reportOptions, options);
} else {
await Util.runCustomReporter(reportName, coverageResults, reportOptions, options);
}
}
}
return coverageResults;

@@ -273,24 +321,43 @@ };

const getCoverageDataList = async (cacheDir) => {
const files = fs.readdirSync(cacheDir).filter((f) => f.startsWith('coverage-'));
if (!files.length) {
return;
}
const getInputData = async (inputList) => {
const dataList = [];
const sourceCache = new Map();
const dataList = [];
for (const item of files) {
const content = await Util.readFile(path.resolve(cacheDir, item));
if (content) {
dataList.push(JSON.parse(content));
const addJsonData = async (dir, filename) => {
const isCoverage = filename.startsWith('coverage-');
const isSource = filename.startsWith('source-');
if (isCoverage || isSource) {
const content = await Util.readFile(path.resolve(dir, filename));
if (content) {
const json = JSON.parse(content);
if (isCoverage) {
dataList.push(json);
} else {
sourceCache.set(json.id, json);
}
}
}
}
};
if (dataList.length) {
return dataList;
for (const dir of inputList) {
const allFiles = fs.readdirSync(dir);
if (!allFiles.length) {
continue;
}
for (const filename of allFiles) {
// only json file
if (filename.endsWith('.json')) {
await addJsonData(dir, filename);
}
}
}
return {
dataList,
sourceCache
};
};
module.exports = {
getCoverageDataList,
getInputData,
generateCoverageReports
};

@@ -40,5 +40,2 @@ declare namespace MCR {

}] |
['console-summary'] | ['console-summary', {
metrics?: Array<"bytes" | "functions" | "branches" | "lines" | "statements">;
}] |
['clover'] | ['clover', {

@@ -95,2 +92,12 @@ file?: string;

file?: string;
}] |
['console-summary'] | ['console-summary', {
metrics?: Array<"bytes" | "functions" | "branches" | "lines" | "statements">;
}] |
['raw'] | ['raw', {
outputDir?: string;
}] |
[string] | [string, {
type?: "v8" | "istanbul" | "both";
[key: string]: any;
}];

@@ -177,2 +184,5 @@

/** {string|string[]} input raw dir(s) */
inputDir?: string | string[];
/** {string} v8 or html for istanbul by default

@@ -179,0 +189,0 @@ * {array} multiple reports with options

@@ -7,3 +7,3 @@ const fs = require('fs');

const { initV8ListAndSourcemap } = require('./v8/v8.js');
const { getCoverageDataList, generateCoverageReports } = require('./generate.js');
const { getInputData, generateCoverageReports } = require('./generate.js');

@@ -45,6 +45,8 @@ class CoverageReport {

const time_start = Date.now();
this.initOptions();
if (!Util.checkCoverageData(data)) {
Util.logError(`The coverage data must be Array(V8) or Object(Istanbul): ${this.options.name}`);
Util.logError(`${this.options.name}: The added coverage data must be Array(V8) or Object(Istanbul)`);
return;

@@ -74,2 +76,4 @@ }

Util.logTime(`added coverage data: ${results.type}`, time_start);
return results;

@@ -81,11 +85,28 @@ }

if (!this.hasCache()) {
Util.logError(`Not found coverage cache: ${this.options.cacheDir}`);
const time_start = Date.now();
this.initOptions();
const { inputDir, cacheDir } = this.options;
const inputDirs = Util.toList(inputDir, ',');
if (this.hasCache()) {
inputDirs.push(cacheDir);
}
const inputList = inputDirs.filter((dir) => {
const hasDir = fs.existsSync(dir);
if (!hasDir) {
Util.logError(`Not found coverage data dir: ${Util.relativePath(dir)}`);
}
return hasDir;
});
if (!inputList.length) {
return;
}
const cacheDir = this.options.cacheDir;
const dataList = await getCoverageDataList(cacheDir);
if (!dataList) {
Util.logError(`Not found coverage data in cache dir: ${cacheDir}`);
const { dataList, sourceCache } = await getInputData(inputList);
if (!dataList.length) {
const dirs = inputList.map((dir) => Util.relativePath(dir));
Util.logError(`Not found coverage data in dir(s): ${dirs.join(', ')}`);
return;

@@ -96,11 +117,18 @@ }

const outputDir = this.options.outputDir;
if (fs.existsSync(outputDir)) {
// if assets dir is out of output dir will be ignore
fs.readdirSync(outputDir).forEach((itemName) => {
if (itemName === this.cacheDirName) {
return;
}
Util.rmSync(path.resolve(outputDir, itemName));
});
fs.readdirSync(outputDir).forEach((itemName) => {
if (itemName === this.cacheDirName) {
return;
}
Util.rmSync(path.resolve(outputDir, itemName));
});
} else {
fs.mkdirSync(outputDir, {
recursive: true
});
}
const coverageResults = await generateCoverageReports(dataList, this.options);
const coverageResults = await generateCoverageReports(dataList, sourceCache, this.options);

@@ -111,2 +139,4 @@ if (this.logging !== 'debug') {

Util.logTime(`generated coverage reports: ${coverageResults.reportPath}`, time_start);
const onEnd = this.options.onEnd;

@@ -113,0 +143,0 @@ if (typeof onEnd === 'function') {

@@ -55,3 +55,3 @@ const fs = require('fs');

// values can be nested/flat/pkg. Defaults to 'pkg'
defaultSummarizer: options.defaultSummarizer || 'nested',
defaultSummarizer: options.defaultSummarizer || 'pkg',

@@ -93,3 +93,4 @@ dir: options.outputDir,

Object.keys(istanbulGroup).forEach((reportName) => {
const report = istanbulReports.create(reportName, istanbulGroup[reportName]);
const reportOptions = istanbulGroup[reportName];
const report = istanbulReports.create(reportName, reportOptions);
report.execute(context);

@@ -120,10 +121,9 @@ });

const mergeIstanbulCoverage = (dataList, options) => {
const mergeIstanbulCoverage = (dataList) => {
const istanbulCoverageList = dataList.map((it) => it.data);
const coverageMap = istanbulLibCoverage.createCoverageMap();
dataList.forEach((d) => {
coverageMap.merge(d.data);
istanbulCoverageList.forEach((coverage) => {
coverageMap.merge(coverage);
});
const istanbulData = coverageMap.toJSON();
return istanbulData;

@@ -130,0 +130,0 @@ };

@@ -112,2 +112,15 @@ const Util = {

toList: function(data, separator) {
if (data instanceof Array) {
return data;
}
if (typeof data === 'string' && (typeof separator === 'string' || separator instanceof RegExp)) {
return data.split(separator).map((str) => str.trim()).filter((str) => str);
}
if (typeof data === 'undefined' || data === null) {
return [];
}
return [data];
},
forEach: function(rootList, callback) {

@@ -114,0 +127,0 @@ const isBreak = (res) => {

@@ -17,3 +17,3 @@

// apply directly to css ranges
const dedupeRanges = (ranges) => {
const dedupeFlatRanges = (ranges) => {

@@ -63,7 +63,6 @@ ranges = filterRanges(ranges);

// apply to js count ranges
const dedupeCountRanges = (ranges) => {
const mergeRangesWith = (ranges, comparer, handler) => {
// count ranges format
// { start: 0, end: 6, count: 0 }
// ranges format
// { start: 0, end: 6, ... }

@@ -82,6 +81,7 @@ ranges = filterRanges(ranges);

ranges.reduce((lastRange, range) => {
if (range.start === lastRange.start && range.end === lastRange.end) {
if (comparer(lastRange, range)) {
range.dedupe = true;
lastRange.count += range.count;
handler(lastRange, range);
hasDedupe = true;

@@ -104,6 +104,19 @@

// apply to js count ranges
const dedupeCountRanges = (ranges) => {
const comparer = (lastRange, range) => {
return lastRange.start === range.start && lastRange.end === range.end;
};
const handler = (lastRange, range) => {
lastRange.count += range.count;
};
return mergeRangesWith(ranges, comparer, handler);
};
module.exports = {
dedupeRanges,
sortRanges,
dedupeFlatRanges,
mergeRangesWith,
dedupeCountRanges
};

@@ -42,2 +42,7 @@ const path = require('path');

// url could be a absolute path
if (path.isAbsolute(url)) {
url = pathToFileURL(url).toString();
}
if (url.startsWith('file:')) {

@@ -44,0 +49,0 @@ const relPath = Util.relativePath(fileURLToPath(url));

const fs = require('fs');
const { writeFile, readFile } = require('fs/promises');
const path = require('path');
const { pathToFileURL } = require('url');
const os = require('os');

@@ -51,3 +52,3 @@ const crypto = require('crypto');

calculateSha1(buffer) {
calculateSha1: (buffer) => {
const hash = crypto.createHash('sha1');

@@ -208,2 +209,30 @@ hash.update(buffer);

runCustomReporter: async (reportName, reportData, reportOptions, globalOptions) => {
let CustomReporter;
let err;
try {
CustomReporter = await import(reportName);
} catch (e) {
err = e;
try {
CustomReporter = await import(pathToFileURL(reportName));
} catch (ee) {
err = ee;
}
}
if (!CustomReporter) {
Util.logError(err.message);
return;
}
CustomReporter = CustomReporter.default || CustomReporter;
const reporter = new CustomReporter(reportOptions, globalOptions);
const results = await reporter.generate(reportData);
return results;
},
getEOL: function(content) {

@@ -210,0 +239,0 @@ if (!content) {

const path = require('path');
const Util = require('../utils/util.js');
const { getV8Summary } = require('./v8-summary.js');
const { dedupeRanges } = require('../utils/dedupe.js');
const { dedupeFlatRanges } = require('../utils/dedupe.js');
const { getSourcePath } = require('../utils/source-path.js');

@@ -10,2 +10,16 @@ const { mergeScriptCovs } = require('../packages/monocart-coverage-vendor.js');

const getWrapperSource = (offset, source) => {
// NO \n because it break line number in mappings
let startStr = '';
if (offset >= 4) {
// fill comments
const spaces = ''.padEnd(offset - 4, '*');
startStr = `/*${spaces}*/`;
} else {
// fill spaces (should no chance)
startStr += ''.padEnd(offset, ' ');
}
return startStr + source;
};
const initV8ListAndSourcemap = async (v8list, options) => {

@@ -22,3 +36,2 @@

// keep functions
v8list = v8list.filter((item) => {

@@ -53,34 +66,34 @@ if (typeof item.source === 'string' && item.functions) {

const type = item.type;
let source = item.source || item.text;
// fix source with script offset
let scriptOffset;
const offset = Util.toNum(item.scriptOffset, true);
if (offset > 0) {
scriptOffset = offset;
source = getWrapperSource(offset, source);
}
const data = {
url: item.url,
type
type: item.type,
source,
// script offset >= 0, for vm script
scriptOffset,
// Manually Resolve the Sourcemap
sourceMap: item.sourceMap,
// empty coverage
empty: item.empty,
// match source if using empty coverage
distFile: item.distFile
};
if (type === 'js') {
// coverage
if (data.type === 'js') {
data.functions = item.functions;
data.source = item.source;
// could be existed
data.sourceMap = item.sourceMap;
} else {
// css
data.ranges = item.ranges;
data.source = item.text;
}
// no functions and ranges
if (item.empty) {
data.empty = true;
}
const idList = [];
// could be existed
const distFile = item.distFile;
if (distFile) {
data.distFile = distFile;
idList.push(distFile);
}
// resolve source path
let sourcePath = getSourcePath(data.url, i + 1, data.type);

@@ -93,10 +106,7 @@ if (typeof sourcePathHandler === 'function') {

}
data.sourcePath = sourcePath;
idList.push(sourcePath);
idList.push(data.source);
// calculate source id
data.id = Util.calculateSha1(sourcePath + data.source);
data.id = Util.calculateSha1(idList.join(''));
return data;

@@ -110,3 +120,3 @@ });

// debug level time
Util.logTime(`loaded ${count} sourcemaps`, time_start);
Util.logTime(`loaded sourcemaps: ${count}`, time_start);
}

@@ -129,3 +139,3 @@

// ranges: [ {start, end} ]
const ranges = dedupeRanges(concatRanges);
const ranges = dedupeFlatRanges(concatRanges);

@@ -146,3 +156,3 @@ resolve(ranges || []);

const mergeV8Coverage = async (dataList, options) => {
const mergeV8Coverage = async (dataList, sourceCache, options) => {

@@ -154,2 +164,3 @@ let allList = [];

// remove empty items
const coverageList = allList.filter((it) => !it.empty);

@@ -187,2 +198,3 @@

// first time filter for empty, (not for sources)
// empty and not in item map
const emptyList = allList.filter((it) => it.empty).filter((it) => !itemMap[it.id]);

@@ -198,10 +210,8 @@

} = item;
const sourcePath = Util.resolveCacheSourcePath(options.cacheDir, id);
const content = await Util.readFile(sourcePath);
if (content) {
const json = JSON.parse(content);
const json = sourceCache.get(id);
if (json) {
item.source = json.source;
item.sourceMap = json.sourceMap;
} else {
Util.logError(`failed to read source: ${item.url}`);
Util.logError(`Not found source data: ${Util.relativePath(item.sourcePath)}`);
item.source = '';

@@ -234,10 +244,9 @@ }

const saveCodecovReport = async (reportData, options, codecovOptions) => {
const mergedOptions = {
const saveCodecovReport = async (reportData, reportOptions, options) => {
const codecovOptions = {
outputFile: 'codecov.json',
... codecovOptions
... reportOptions
};
// console.log(mergedOptions);
const jsonPath = path.resolve(options.outputDir, mergedOptions.outputFile);
const jsonPath = path.resolve(options.outputDir, codecovOptions.outputFile);

@@ -258,10 +267,10 @@ // https://docs.codecov.com/docs/codecov-custom-coverage-format

const saveV8JsonReport = async (reportData, options, v8JsonOptions) => {
const mergedOptions = {
const saveV8JsonReport = async (reportData, reportOptions, options) => {
const v8JsonOptions = {
outputFile: 'coverage-report.json',
... v8JsonOptions
... reportOptions
};
// console.log(mergedOptions);
const jsonPath = path.resolve(options.outputDir, mergedOptions.outputFile);
const jsonPath = path.resolve(options.outputDir, v8JsonOptions.outputFile);
await Util.writeFile(jsonPath, JSON.stringify(reportData));

@@ -272,4 +281,16 @@ return Util.relativePath(jsonPath);

const saveV8HtmlReport = async (reportData, options, v8HtmlOptions) => {
const saveV8HtmlReport = async (reportData, reportOptions, options) => {
// V8 only options, merged with root options
const v8HtmlOptions = {
outputFile: options.outputFile,
inline: options.inline,
assetsPath: options.assetsPath,
metrics: options.metrics,
... reportOptions
};
// add metrics to data for UI
reportData.metrics = v8HtmlOptions.metrics;
const {

@@ -330,47 +351,25 @@ outputFile, inline, assetsPath

let outputPath;
// v8 reports
const buildInV8Reports = {
'v8': saveV8HtmlReport,
'v8-json': saveV8JsonReport,
'codecov': saveCodecovReport
};
// v8 reports
const outputs = {};
const v8Group = options.reportGroup.v8;
// v8 html
const v8Options = v8Group.v8;
if (v8Options) {
// V8 only options, merged with root options
const v8HtmlOptions = {
outputFile: options.outputFile,
inline: options.inline,
assetsPath: options.assetsPath,
metrics: options.metrics,
... v8Options
};
// add metrics to data
reportData.metrics = v8HtmlOptions.metrics;
outputPath = await saveV8HtmlReport(reportData, options, v8HtmlOptions);
}
// v8 json (after html for metrics data)
const v8JsonOptions = v8Group['v8-json'];
if (v8JsonOptions) {
const jsonPath = await saveV8JsonReport(reportData, options, v8JsonOptions);
if (!outputPath) {
outputPath = jsonPath;
const v8Reports = Object.keys(v8Group);
for (const reportName of v8Reports) {
const reportOptions = v8Group[reportName];
const buildInHandler = buildInV8Reports[reportName];
if (buildInHandler) {
outputs[reportName] = await buildInHandler(reportData, reportOptions, options);
} else {
outputs[reportName] = await Util.runCustomReporter(reportName, reportData, reportOptions, options);
}
}
// codecov
const codecovOptions = v8Group.codecov;
if (codecovOptions) {
const jsonPath = await saveCodecovReport(reportData, options, codecovOptions);
if (!outputPath) {
outputPath = jsonPath;
}
}
// outputPath last one is html
// outputPath, should be html or json
const reportPath = Util.resolveReportPath(options, () => {
return outputPath;
return outputs.v8 || outputs['v8-json'] || Object.values(outputs).filter((it) => it && typeof it === 'string').shift();
});

@@ -377,0 +376,0 @@

{
"name": "monocart-coverage-reports",
"version": "2.2.2",
"version": "2.3.0",
"description": "Monocart coverage reports",

@@ -23,3 +23,3 @@ "main": "./lib/index.js",

"build": "sf lint && sf b -p && npm run build-test",
"test-node": "npm run test-node-env && npm run test-node-api && npm run test-node-ins && npm run test-node-cdp && npm run test-node-koa",
"test-node": "npm run test-node-env && npm run test-node-api && npm run test-node-ins && npm run test-node-cdp && npm run test-node-koa && npm run test-vm",
"test-node-env": "cross-env NODE_V8_COVERAGE=.temp/v8-coverage-env node ./test/test-node-env.js && node ./test/generate-node-report.js",

@@ -31,5 +31,7 @@ "test-node-api": "cross-env NODE_V8_COVERAGE=.temp/v8-coverage-api node ./test/test-node-api.js",

"test-node-koa": "node ./test/test-node-koa.js",
"test-vm": "node ./test/test-vm.js",
"test-browser": "node ./test/test.js",
"test-cli": "npx mcr \"node ./test/test-node-env.js\" -o docs/cli -c test/cli-options.js",
"test": "npm run test-browser && npm run test-node && npm run test-cli && npm run build-docs",
"test-merge": "node ./test/test-merge.js",
"test": "npm run test-browser && npm run test-node && npm run test-cli && npm run test-merge && npm run build-docs",
"dev": "sf d v8",

@@ -36,0 +38,0 @@ "open": "node ./scripts/open.js",

@@ -21,4 +21,7 @@ # Monocart Coverage Reports

* [Collecting V8 Coverage Data](#collecting-v8-coverage-data)
* [Manually Resolve the Sourcemap](#manually-resolve-the-sourcemap)
* [Collecting Raw V8 Coverage Data with Puppeteer](#collecting-raw-v8-coverage-data-with-puppeteer)
* [Node.js V8 Coverage Report for Server Side](#nodejs-v8-coverage-report-for-server-side)
* [Multiprocessing Support](#multiprocessing-support)
* [Merge Coverage Reports](#merge-coverage-reports)
* [Integration](#integration)

@@ -70,7 +73,6 @@ * [Ignoring Uncovered Codes](#ignoring-uncovered-codes)

- coverage data for [Codecov](https://docs.codecov.com/docs/codecov-custom-coverage-format), see [example](https://app.codecov.io/github/cenfun/monocart-coverage-reports)
- `console-summary` shows coverage summary in console
![](test/console-summary.png)
> Following are [istanbul reports](https://github.com/istanbuljs/istanbuljs/tree/master/packages/istanbul-reports/lib)
> Istanbul [build-in reports](https://github.com/istanbuljs/istanbuljs/tree/master/packages/istanbul-reports/lib)
- `clover`

@@ -95,2 +97,31 @@ - `cobertura`

> Other reports
- `console-summary` shows coverage summary in console
- `raw` only keep all original data, which can be used for other reports input with `inputDir`
- see [Merge Coverage Reports](#merge-coverage-reports)
- Custom Reporter
```js
{
reports: [
[path.resolve('./test/custom-istanbul-reporter.js'), {
type: 'istanbul',
file: 'custom-istanbul-coverage.text'
}],
[path.resolve('./test/custom-v8-reporter.js'), {
type: 'v8',
outputFile: 'custom-v8-coverage.json'
}],
[path.resolve('./test/custom-v8-reporter.mjs'), {
type: 'both'
}]
]
}
```
- istanbul custom reporter
> example: [./test/custom-istanbul-reporter.js](./test/custom-istanbul-reporter.js), see [istanbul built-in reporters' implementation](https://github.com/istanbuljs/istanbuljs/tree/master/packages/istanbul-reports/lib) for reference,
- v8 custom reporter
> example: [./test/custom-v8-reporter.js](./test/custom-v8-reporter.js)
### Multiple Reports:
```js

@@ -101,2 +132,3 @@ const MCR = require('monocart-coverage-reports');

reports: [
// build-in reports
['console-summary'],

@@ -110,3 +142,14 @@ ['v8'],

}],
'lcovonly'
'lcovonly',
// custom reports
// Specify reporter name with the NPM package
["custom-reporter-1"],
["custom-reporter-2", {
type: "istanbul",
option: "value"
}],
// Specify reporter name with local path
['/absolute/path/to/custom-reporter.js']
]

@@ -258,10 +301,45 @@ }

- [esbuild](https://esbuild.github.io/api/): `sourcemap: true` and `minify: false`
- [Manually Resolve the Sourcemap](#manually-resolve-the-sourcemap)
- Browser (Chromium Only)
> Collecting coverage data with [Chromium Coverage API](#chromium-coverage-api):
- [Playwright example](https://github.com/cenfun/monocart-coverage-reports/blob/main/test/test-v8.js), and [anonymous](https://github.com/cenfun/monocart-coverage-reports/blob/main/test/test-anonymous.js), [css](https://github.com/cenfun/monocart-coverage-reports/blob/main/test/test-css.js)
- [Puppeteer example](https://github.com/cenfun/monocart-coverage-reports/blob/main/test/test-puppeteer.js)
- see [Collecting Raw V8 Coverage Data with Puppeteer](#collecting-raw-v8-coverage-data-with-puppeteer)
- Node.js
- see following [Node.js V8 Coverage Report for Server Side](#nodejs-v8-coverage-report-for-server-side)
- see [Node.js V8 Coverage Report for Server Side](#nodejs-v8-coverage-report-for-server-side)
## Manually Resolve the Sourcemap
> If the `js` file is loaded with `addScriptTag` [API](https://playwright.dev/docs/api/class-page#page-add-script-tag), then its sourcemap file may not work. You can try to manually read the sourcemap file before the coverage data is added to the report.
```js
const jsCoverage = await page.coverage.stopJSCoverage();
jsCoverage.forEach((entry) => {
// read sourcemap for the my-dist.js manually
if (entry.url.endsWith('my-dist.js')) {
entry.sourceMap = JSON.parse(fs.readFileSync('dist/my-dist.js.map').toString('utf-8'));
}
});
await MCR(coverageOptions).add(jsCoverage);
```
## Collecting Raw V8 Coverage Data with Puppeteer
> Puppeteer does not provide raw v8 coverage data by default. A simple conversion is required, see example: [./test/test-puppeteer.js](./test/test-puppeteer.js)
```js
await page.coverage.startJSCoverage({
// provide raw v8 coverage data
includeRawScriptCoverage: true
});
await page.goto(url);
const jsCoverage = await page.coverage.stopJSCoverage();
const rawV8CoverageData = jsCoverage.map((it) => {
// Convert to raw v8 coverage format
return {
source: it.text,
... it.rawScriptCoverage
};
}
```
## Node.js V8 Coverage Report for Server Side

@@ -328,5 +406,61 @@ Possible solutions:

## Merge Coverage Reports
The following usage scenarios may require merging coverage reports:
- When the code is executed in different environments, like Node.js Server Side and browser Client Side (Next.js for instance). Each environment may generate its own coverage report. Merging them can give a more comprehensive view of the test coverage.
- When the code is subjected to different kinds of testing. For example, unit tests with Jest might cover certain parts of the code, while end-to-end tests with Playwright might cover other parts. Merging these different coverage reports can provide a holistic view of what code has been tested.
- When tests are run on different machines or different shards, each might produce its own coverage report. Merging these can give a complete picture of the test coverage across all machines or shards.
First, using the `raw` report to export the original coverage data to the specified directory.
```js
const coverageOptions = {
name: 'My Unit Test Coverage Report',
outputDir: "./coverage-reports/unit",
reports: [
['raw', {
// relative path will be "./coverage-reports/unit/raw"
outputDir: "raw"
}],
['v8'],
['console-summary']
]
};
```
Then, after all the tests are completed, generate a merged report with option `inputDir`:
```js
import {CoverageReport} from 'monocart-coverage-reports';
const coverageOptions = {
name: 'My Merged Coverage Report',
inputDir: [
'./coverage-reports/unit/raw',
'./coverage-reports/e2e/raw'
],
outputDir: './coverage-reports/merged',
reports: [
['v8'],
['console-summary']
]
};
await new CoverageReport(coverageOptions).generate();
```
If the source file comes from the sourcemap, then its path is a virtual path. Using the `sourcePath` option to convert it.
```js
const coverageOptions = {
sourcePath: (filePath) => {
// Remove the virtual prefix
const list = ['my-dist-file1/', 'my-dist-file2/'];
for (const str of list) {
if (filePath.startsWith(str)) {
return filePath.slice(str.length);
}
}
return filePath;
}
};
```
see example: [./test/test-merge.js](./test/test-merge.js)
## Integration
- [monocart-reporter](https://cenfun.github.io/monocart-reporter/) - Test reporter for [Playwright](https://github.com/microsoft/playwright)
- [vitest-monocart-coverage](https://github.com/cenfun/vitest-monocart-coverage) - Integration with [Vitest](https://github.com/vitest-dev/vitest) coverage
- [jest-monocart-coverage](https://github.com/cenfun/jest-monocart-coverage) - Integration with [Jest](https://github.com/jestjs/jest/) for coverage reports
- [vitest-monocart-coverage](https://github.com/cenfun/vitest-monocart-coverage) - Integration with [Vitest](https://github.com/vitest-dev/vitest) for coverage reports

@@ -404,22 +538,2 @@ ## Ignoring Uncovered Codes

### Collect raw v8 coverage data with Puppeteer
```js
await page.coverage.startJSCoverage({
// provide raw v8 coverage data
includeRawScriptCoverage: true
});
await page.goto(url);
const jsCoverage = await page.coverage.stopJSCoverage();
const rawV8CoverageData = jsCoverage.map((it) => {
// Convert to raw v8 coverage format
return {
source: it.text,
... it.rawScriptCoverage
};
}
```
see example: [./test/test-puppeteer.js](./test/test-puppeteer.js)
## How to convert V8 to Istanbul

@@ -426,0 +540,0 @@ ### Using [v8-to-istanbul](https://github.com/istanbuljs/v8-to-istanbul)

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc