escover 6.1.2 → 6.2.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/ChangeLog +13 -0
- package/README.md +0 -26
- package/help.json +5 -0
- package/lib/cli/cli.js +23 -4
- package/lib/cli/help.js +18 -0
- package/lib/config.js +1 -0
- package/lib/coverage-file/coverage-file.js +1 -0
- package/lib/formatters/files.js +8 -5
- package/lib/formatters/responsive.js +242 -0
- package/package.json +1 -1
package/ChangeLog
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
2026.05.08, v6.2.0
|
|
2
|
+
|
|
3
|
+
fix:
|
|
4
|
+
- fd9af89 escover: cli: no NODE_OPTIONS
|
|
5
|
+
|
|
6
|
+
feature:
|
|
7
|
+
- 424a044 escover: --help: add
|
|
8
|
+
- 94d2b1c responsive: skipFull
|
|
9
|
+
- 8b031eb responsive: indent
|
|
10
|
+
- a9fee50 escover: formatters: responsive: add
|
|
11
|
+
- 69a41a2 escover: formatters: improve colors
|
|
12
|
+
- 9685940 formatters: files: colors
|
|
13
|
+
|
|
1
14
|
2026.05.07, v6.1.2
|
|
2
15
|
|
|
3
16
|
feature:
|
package/README.md
CHANGED
|
@@ -20,10 +20,6 @@ you have a couple problems to solve.
|
|
|
20
20
|
|
|
21
21
|
☝️ that's easy! 📼 [**Supertape**](https://github.com/coderaiser/supertape) supports `ESM` from the box;
|
|
22
22
|
|
|
23
|
-
### 🤷 How to mock modules without [mock-require](https://github.com/boblauer/mock-require) (we in `ESM`!);
|
|
24
|
-
|
|
25
|
-
☝️ that's solved! [`mock-import`](https://github.com/coderaiser/mock-import) does the thing using `loaders`;
|
|
26
|
-
|
|
27
23
|
### 🤷 How to get coverage when `nyc` doesn't supported?
|
|
28
24
|
|
|
29
25
|
☝️ `c8` could help, but [no](https://github.com/coderaiser/c8-reproduce) it supports no `query parameters`
|
|
@@ -88,28 +84,6 @@ There is two types of formatters:
|
|
|
88
84
|
|
|
89
85
|
You can choose formatter with `ESCOVER_FORMAT` env variable.
|
|
90
86
|
|
|
91
|
-
## What if I want to use 🎩`ESCover` with `mock-import`?
|
|
92
|
-
|
|
93
|
-
[`mock-import`](https://github.com/coderaiser/mock-import) is used by default in 🎩`ESCover`.
|
|
94
|
-
|
|
95
|
-
Install it with:
|
|
96
|
-
|
|
97
|
-
```sh
|
|
98
|
-
npm i escover
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
Then run:
|
|
102
|
-
|
|
103
|
-
```sh
|
|
104
|
-
escover npm test
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
This is the same as:
|
|
108
|
-
|
|
109
|
-
```sh
|
|
110
|
-
NODE_OPTIONS="'--loader zenlend'" ZENLOAD='escover,mock-import' escover npm test
|
|
111
|
-
```
|
|
112
|
-
|
|
113
87
|
## Env
|
|
114
88
|
|
|
115
89
|
If you want to disable coverage on status code without erroring, use `ESCOVER_SUCCESS_EXIT_CODE`:
|
package/help.json
ADDED
package/lib/cli/cli.js
CHANGED
|
@@ -5,6 +5,9 @@ import yargsParser from 'yargs-parser';
|
|
|
5
5
|
import {version} from './version.js';
|
|
6
6
|
import reportLines from '../formatters/lines.js';
|
|
7
7
|
import reportFiles from '../formatters/files.js';
|
|
8
|
+
import reportResponsive from '../formatters/responsive.js';
|
|
9
|
+
import {readConfig} from '../config.js';
|
|
10
|
+
import {help} from './help.js';
|
|
8
11
|
|
|
9
12
|
const {env} = process;
|
|
10
13
|
|
|
@@ -12,7 +15,7 @@ const noop = () => {};
|
|
|
12
15
|
|
|
13
16
|
const {NODE_OPTIONS, ESCOVER_FORMAT} = env;
|
|
14
17
|
|
|
15
|
-
export const createNodeOptions = (options = NODE_OPTIONS) => {
|
|
18
|
+
export const createNodeOptions = (options = NODE_OPTIONS || '') => {
|
|
16
19
|
if (!options.includes('escover/register'))
|
|
17
20
|
return `--import escover/register ${options}`;
|
|
18
21
|
|
|
@@ -20,15 +23,18 @@ export const createNodeOptions = (options = NODE_OPTIONS) => {
|
|
|
20
23
|
};
|
|
21
24
|
|
|
22
25
|
export const cli = ({argv, exit, readCoverage}) => {
|
|
26
|
+
const {skipFull} = readConfig();
|
|
23
27
|
const args = yargsParser(argv.slice(2), {
|
|
24
28
|
string: ['format'],
|
|
25
|
-
boolean: ['version'],
|
|
29
|
+
boolean: ['version', 'skip-full', 'help'],
|
|
26
30
|
alias: {
|
|
27
31
|
v: 'version',
|
|
28
32
|
f: 'format',
|
|
33
|
+
h: 'help',
|
|
29
34
|
},
|
|
30
35
|
default: {
|
|
31
|
-
format: ESCOVER_FORMAT || '
|
|
36
|
+
'format': ESCOVER_FORMAT || 'responsive',
|
|
37
|
+
'skip-full': skipFull,
|
|
32
38
|
},
|
|
33
39
|
});
|
|
34
40
|
|
|
@@ -36,6 +42,16 @@ export const cli = ({argv, exit, readCoverage}) => {
|
|
|
36
42
|
console.log(`v${version()}`);
|
|
37
43
|
return exit();
|
|
38
44
|
}
|
|
45
|
+
|
|
46
|
+
if (args.help) {
|
|
47
|
+
console.log(help());
|
|
48
|
+
return exit();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (args.help) {
|
|
52
|
+
console.log(help());
|
|
53
|
+
return exit();
|
|
54
|
+
}
|
|
39
55
|
|
|
40
56
|
const cmd = argv.slice(2);
|
|
41
57
|
|
|
@@ -48,6 +64,10 @@ export const cli = ({argv, exit, readCoverage}) => {
|
|
|
48
64
|
|
|
49
65
|
if (args.format === 'lines')
|
|
50
66
|
output = reportLines(coverage);
|
|
67
|
+
else if (args.format === 'responsive')
|
|
68
|
+
output = reportResponsive(coverage, {
|
|
69
|
+
skipFull: args.skipFull,
|
|
70
|
+
});
|
|
51
71
|
else
|
|
52
72
|
output = reportFiles(coverage);
|
|
53
73
|
|
|
@@ -57,7 +77,6 @@ export const cli = ({argv, exit, readCoverage}) => {
|
|
|
57
77
|
export const isSuccess = (error) => !error || error?.status === Number(process.env.ESCOVER_SUCCESS_EXIT_CODE);
|
|
58
78
|
|
|
59
79
|
function execute(cmd, exit) {
|
|
60
|
-
console.log(createNodeOptions());
|
|
61
80
|
const [error] = tryCatch(execSync, cmd, {
|
|
62
81
|
stdio: [0, 1, 2],
|
|
63
82
|
env: {
|
package/lib/cli/help.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import helpJson from '../../help.json' with {
|
|
2
|
+
type: 'json',
|
|
3
|
+
};
|
|
4
|
+
|
|
5
|
+
const {entries} = Object;
|
|
6
|
+
|
|
7
|
+
export const help = () => {
|
|
8
|
+
const result = [
|
|
9
|
+
'Usage: escover [options] [script]',
|
|
10
|
+
'Options:',
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
for (const [name, description] of entries(helpJson)) {
|
|
14
|
+
result.push(` ${name} ${description}`);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return result.join('\n');
|
|
18
|
+
};
|
package/lib/config.js
CHANGED
package/lib/formatters/files.js
CHANGED
|
@@ -9,6 +9,9 @@ const CWD = process.cwd();
|
|
|
9
9
|
|
|
10
10
|
const {entries, keys} = Object;
|
|
11
11
|
|
|
12
|
+
const makeGreen = chalk.hex('#4caf50');
|
|
13
|
+
const makeRed = chalk.hex('#f44336');
|
|
14
|
+
|
|
12
15
|
export default (coverageFile) => {
|
|
13
16
|
const files = parseCoverageFile(coverageFile);
|
|
14
17
|
|
|
@@ -21,17 +24,17 @@ export default (coverageFile) => {
|
|
|
21
24
|
|
|
22
25
|
if (covered) {
|
|
23
26
|
tableData.push([
|
|
24
|
-
|
|
25
|
-
|
|
27
|
+
makeGreen(filename),
|
|
28
|
+
makeGreen(percentLines),
|
|
26
29
|
'',
|
|
27
30
|
]);
|
|
28
31
|
continue;
|
|
29
32
|
}
|
|
30
33
|
|
|
31
34
|
tableData.push([
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
+
makeRed(filename),
|
|
36
|
+
makeRed(percentLines),
|
|
37
|
+
makeRed(uncoveredLines),
|
|
35
38
|
]);
|
|
36
39
|
}
|
|
37
40
|
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import process from 'node:process';
|
|
2
|
+
import {
|
|
3
|
+
table,
|
|
4
|
+
getBorderCharacters,
|
|
5
|
+
} from 'table';
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
|
|
8
|
+
const CWD = process.cwd();
|
|
9
|
+
|
|
10
|
+
const {entries, keys} = Object;
|
|
11
|
+
|
|
12
|
+
const makeGreen = chalk.hex('#4caf50');
|
|
13
|
+
const makeRed = chalk.hex('#f44336');
|
|
14
|
+
|
|
15
|
+
export default (coverageFile, {skipFull = false} = {}) => {
|
|
16
|
+
const files = parseCoverageFile(coverageFile, skipFull);
|
|
17
|
+
|
|
18
|
+
if (skipFull && !files.length)
|
|
19
|
+
return '💪 coverage 100% Good Job!\n';
|
|
20
|
+
|
|
21
|
+
const totalWidth = process.stdout.columns || 80;
|
|
22
|
+
const showPercent = totalWidth >= 70;
|
|
23
|
+
|
|
24
|
+
const percentColWidth = showPercent ? 8 : 0;
|
|
25
|
+
const fileColWidth = Math.floor(totalWidth * 0.45);
|
|
26
|
+
|
|
27
|
+
const linesColWidth = totalWidth - fileColWidth - percentColWidth - 6;
|
|
28
|
+
|
|
29
|
+
const tableData = buildGroupedTable(files, showPercent);
|
|
30
|
+
|
|
31
|
+
return table(tableData, {
|
|
32
|
+
drawHorizontalLine: (raw) => !raw || raw === 1 || raw === tableData.length,
|
|
33
|
+
|
|
34
|
+
columns: [{
|
|
35
|
+
paddingLeft: 1,
|
|
36
|
+
paddingRight: 1,
|
|
37
|
+
width: fileColWidth,
|
|
38
|
+
wrapWord: false, // 🔥 ключевой фикс — без переносов
|
|
39
|
+
}, ...showPercent ? [{
|
|
40
|
+
alignment: 'center',
|
|
41
|
+
paddingLeft: 1,
|
|
42
|
+
paddingRight: 0,
|
|
43
|
+
width: percentColWidth,
|
|
44
|
+
}] : [], {
|
|
45
|
+
paddingLeft: 1,
|
|
46
|
+
paddingRight: 0,
|
|
47
|
+
width: linesColWidth,
|
|
48
|
+
}],
|
|
49
|
+
|
|
50
|
+
border: {
|
|
51
|
+
...getBorderCharacters('void'),
|
|
52
|
+
topBody: '-',
|
|
53
|
+
bottomBody: '-',
|
|
54
|
+
joinBody: '-',
|
|
55
|
+
topJoin: '|',
|
|
56
|
+
bottomJoin: '|',
|
|
57
|
+
joinJoin: '|',
|
|
58
|
+
bodyJoin: '|',
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/* =========================
|
|
64
|
+
GROUPING + COVERAGE
|
|
65
|
+
========================= */
|
|
66
|
+
function groupByFolder(files) {
|
|
67
|
+
const groups = new Map();
|
|
68
|
+
|
|
69
|
+
for (const f of files) {
|
|
70
|
+
const parts = f.filename.split('/');
|
|
71
|
+
const folder = parts.length > 1 ? parts
|
|
72
|
+
.slice(0, -1)
|
|
73
|
+
.join('/') : '';
|
|
74
|
+
|
|
75
|
+
const fileName = parts.at(-1);
|
|
76
|
+
|
|
77
|
+
if (!groups.has(folder))
|
|
78
|
+
groups.set(folder, {
|
|
79
|
+
files: [],
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const group = groups.get(folder);
|
|
83
|
+
|
|
84
|
+
group.files.push({
|
|
85
|
+
...f,
|
|
86
|
+
shortName: fileName,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return groups;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/* =========================
|
|
94
|
+
TABLE BUILDER
|
|
95
|
+
========================= */
|
|
96
|
+
function buildGroupedTable(files, showPercent) {
|
|
97
|
+
const tableData = [
|
|
98
|
+
showPercent ? [
|
|
99
|
+
'File',
|
|
100
|
+
'% Lines',
|
|
101
|
+
'Uncovered Lines #s',
|
|
102
|
+
] : [
|
|
103
|
+
'File',
|
|
104
|
+
'Uncovered Lines #s',
|
|
105
|
+
],
|
|
106
|
+
];
|
|
107
|
+
|
|
108
|
+
const groups = groupByFolder(files);
|
|
109
|
+
|
|
110
|
+
for (const [folder, group] of groups) {
|
|
111
|
+
const coverage = group.files.length ? Math.round(group.files.reduce((sum, f) => sum + f.percentLines, 0) / group.files.length) : 100;
|
|
112
|
+
|
|
113
|
+
const folderLabel = formatFolder(folder || '.');
|
|
114
|
+
|
|
115
|
+
const folderName = makeRed(`${folderLabel}/`);
|
|
116
|
+
|
|
117
|
+
const folderPercentColor = coverage === 100 ? makeGreen : makeRed;
|
|
118
|
+
|
|
119
|
+
const headerRow = [folderName];
|
|
120
|
+
|
|
121
|
+
if (showPercent)
|
|
122
|
+
headerRow.push(folderPercentColor(`${coverage}%`));
|
|
123
|
+
|
|
124
|
+
headerRow.push('');
|
|
125
|
+
|
|
126
|
+
tableData.push(headerRow);
|
|
127
|
+
|
|
128
|
+
for (const f of group.files) {
|
|
129
|
+
const fileCell = ` ${formatPath(f.shortName)}`;
|
|
130
|
+
const uncoveredLines = formatLines(f.lines);
|
|
131
|
+
|
|
132
|
+
const filePercentColor = f.percentLines === 100 ? makeGreen : makeRed;
|
|
133
|
+
|
|
134
|
+
const row = [
|
|
135
|
+
f.covered ? makeGreen(fileCell) : makeRed(fileCell),
|
|
136
|
+
];
|
|
137
|
+
|
|
138
|
+
if (showPercent)
|
|
139
|
+
row.push(filePercentColor(`${f.percentLines}%`));
|
|
140
|
+
|
|
141
|
+
row.push(f.covered ? '' : makeRed(uncoveredLines));
|
|
142
|
+
|
|
143
|
+
tableData.push(row);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return tableData;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/* =========================
|
|
151
|
+
PARSE
|
|
152
|
+
========================= */
|
|
153
|
+
function parseCoverageFile(coverageFile, skipFull = false) {
|
|
154
|
+
const files = [];
|
|
155
|
+
|
|
156
|
+
for (const {name, lines} of coverageFile) {
|
|
157
|
+
const filename = name.replace(`${CWD}/`, '');
|
|
158
|
+
const uncoveredLines = parseUncoveredLines(lines);
|
|
159
|
+
|
|
160
|
+
const linesCount = keys(lines).length;
|
|
161
|
+
const uncoveredLinesCount = uncoveredLines.length;
|
|
162
|
+
|
|
163
|
+
const percentLines = getLinesPercent(linesCount, uncoveredLinesCount);
|
|
164
|
+
|
|
165
|
+
const isCovered = !uncoveredLinesCount;
|
|
166
|
+
|
|
167
|
+
if (skipFull && isCovered)
|
|
168
|
+
continue;
|
|
169
|
+
|
|
170
|
+
files.push({
|
|
171
|
+
filename,
|
|
172
|
+
covered: isCovered,
|
|
173
|
+
lines: uncoveredLines,
|
|
174
|
+
percentLines,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return files;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/* =========================
|
|
182
|
+
FORMATTERS
|
|
183
|
+
========================= */
|
|
184
|
+
function formatPath(path) {
|
|
185
|
+
const max = 30;
|
|
186
|
+
|
|
187
|
+
if (path.length <= max)
|
|
188
|
+
return path;
|
|
189
|
+
|
|
190
|
+
return `...${path.slice(-(max - 3))}`;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function formatFolder(path) {
|
|
194
|
+
const max = 25;
|
|
195
|
+
|
|
196
|
+
if (path.length <= max)
|
|
197
|
+
return path;
|
|
198
|
+
|
|
199
|
+
return `...${path.slice(-(max - 3))}`;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function formatLines(array) {
|
|
203
|
+
const max = 30;
|
|
204
|
+
|
|
205
|
+
if (array.length <= 10) {
|
|
206
|
+
const joined = array.join(', ');
|
|
207
|
+
|
|
208
|
+
if (joined.length <= max)
|
|
209
|
+
return joined;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const [first] = array;
|
|
213
|
+
const last = array.at(-1);
|
|
214
|
+
|
|
215
|
+
const base = `${first}..${last}`;
|
|
216
|
+
|
|
217
|
+
if (base.length <= max)
|
|
218
|
+
return base;
|
|
219
|
+
|
|
220
|
+
return String(last);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/* =========================
|
|
224
|
+
HELPERS
|
|
225
|
+
========================= */
|
|
226
|
+
function parseUncoveredLines(lines) {
|
|
227
|
+
const out = [];
|
|
228
|
+
|
|
229
|
+
for (const [line, covered] of entries(lines)) {
|
|
230
|
+
if (!covered)
|
|
231
|
+
out.push(line);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return out;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export function getLinesPercent(linesCount, uncoveredLinesCount) {
|
|
238
|
+
if (!linesCount)
|
|
239
|
+
return 100;
|
|
240
|
+
|
|
241
|
+
return 100 - Math.round(100 / linesCount * uncoveredLinesCount);
|
|
242
|
+
}
|