Skip to content

Commit 56e9bde

Browse files
authored
feat: support coverage.reporters option (#556)
1 parent 9b0bb2e commit 56e9bde

File tree

10 files changed

+151
-31
lines changed

10 files changed

+151
-31
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { defineConfig } from '@rstest/core';
2+
3+
export default defineConfig({
4+
coverage: {
5+
enabled: true,
6+
provider: 'istanbul',
7+
reporters: [['text', { skipFull: true }]],
8+
},
9+
});

e2e/test-coverage/fixtures/test/string.test.ts

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,5 @@
11
import { describe, expect, it } from '@rstest/core';
2-
import {
3-
capitalize,
4-
countWords,
5-
isPalindrome,
6-
reverseString,
7-
truncate,
8-
} from '../src/string';
2+
import { capitalize, countWords, isPalindrome, truncate } from '../src/string';
93

104
describe('String Utils', () => {
115
describe('capitalize', () => {
@@ -22,20 +16,6 @@ describe('String Utils', () => {
2216
});
2317
});
2418

25-
describe('reverseString', () => {
26-
it('should reverse string', () => {
27-
expect(reverseString('hello')).toBe('olleh');
28-
});
29-
30-
it('should handle empty string', () => {
31-
expect(reverseString('')).toBe('');
32-
});
33-
34-
it('should handle palindrome', () => {
35-
expect(reverseString('racecar')).toBe('racecar');
36-
});
37-
});
38-
3919
describe('isPalindrome', () => {
4020
it('should return true for palindrome', () => {
4121
expect(isPalindrome('racecar')).toBe(true);

e2e/test-coverage/index.test.ts

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import fs from 'fs-extra';
44
import { runRstestCli } from '../scripts';
55

66
it('coverage-istanbul', async () => {
7-
const { expectExecSuccess } = await runRstestCli({
7+
const { expectExecSuccess, expectLog, cli } = await runRstestCli({
88
command: 'rstest',
99
args: ['run'],
1010
options: {
@@ -16,7 +16,91 @@ it('coverage-istanbul', async () => {
1616

1717
await expectExecSuccess();
1818

19+
const logs = cli.stdout.split('\n').filter(Boolean);
20+
21+
expectLog('Coverage enabled with istanbul', logs);
22+
23+
// test coverage
24+
expect(
25+
logs.find(
26+
(log) =>
27+
log.includes('index.ts') &&
28+
log.replaceAll(' ', '').includes('100|100|100|100'),
29+
),
30+
).toBeTruthy();
31+
expect(
32+
logs.find(
33+
(log) =>
34+
log.includes('string.ts') &&
35+
log.replaceAll(' ', '').includes('93.75|100|83.33|92.85|7'),
36+
),
37+
).toBeTruthy();
38+
// TODO: should not include test files
39+
expect(
40+
logs.find(
41+
(log) =>
42+
log.includes('All files') &&
43+
log.replaceAll(' ', '').includes('99.43|100|98.68|99.41'),
44+
),
45+
).toBeTruthy();
46+
47+
// text reporter
48+
expectLog('% Stmts', logs);
49+
50+
// html reporter
51+
expect(
52+
fs.existsSync(join(__dirname, 'fixtures/coverage/index.html')),
53+
).toBeTruthy();
54+
55+
// clover reporter
56+
expect(
57+
fs.existsSync(join(__dirname, 'fixtures/coverage/clover.xml')),
58+
).toBeTruthy();
59+
60+
// json reporter
1961
expect(
2062
fs.existsSync(join(__dirname, 'fixtures/coverage/coverage-final.json')),
2163
).toBeTruthy();
2264
});
65+
66+
it('coverage-istanbul with custom options', async () => {
67+
const { expectExecSuccess, expectLog, cli } = await runRstestCli({
68+
command: 'rstest',
69+
args: ['run', '-c', 'rstest.skipFull.config.ts'],
70+
options: {
71+
nodeOptions: {
72+
cwd: join(__dirname, 'fixtures'),
73+
},
74+
},
75+
});
76+
77+
await expectExecSuccess();
78+
79+
const logs = cli.stdout.split('\n').filter(Boolean);
80+
81+
expectLog('Coverage enabled with istanbul', logs);
82+
83+
// test coverage
84+
expect(
85+
logs.find(
86+
(log) =>
87+
log.includes('index.ts') &&
88+
log.replaceAll(' ', '').includes('100|100|100|100'),
89+
),
90+
).toBeFalsy();
91+
expect(
92+
logs.find(
93+
(log) =>
94+
log.includes('string.ts') &&
95+
log.replaceAll(' ', '').includes('93.75|100|83.33|92.85|7'),
96+
),
97+
).toBeTruthy();
98+
99+
// text reporter
100+
expectLog('% Stmts', logs);
101+
102+
// TODO: should clean and not generate html reporter
103+
expect(
104+
fs.existsSync(join(__dirname, 'fixtures/coverage/index.html')),
105+
).toBeTruthy();
106+
});

packages/core/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
"@rstest/tsconfig": "workspace:*",
6969
"@sinonjs/fake-timers": "^14.0.0",
7070
"@types/babel__code-frame": "^7.0.6",
71+
"@types/istanbul-reports": "^3.0.4",
7172
"@types/jsdom": "^21.1.7",
7273
"@types/sinonjs__fake-timers": "^8.1.5",
7374
"@types/source-map-support": "^0.5.10",

packages/core/src/config.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,8 @@ const createDefaultConfig = (): NormalizedConfig => ({
113113
disableConsoleIntercept: false,
114114
coverage: {
115115
enabled: false,
116+
provider: 'istanbul',
117+
reporters: ['text', 'html', 'clover', 'json'],
116118
},
117119
});
118120

@@ -125,6 +127,10 @@ export const withDefaultConfig = (config: RstestConfig): NormalizedConfig => {
125127
TEMP_RSTEST_OUTPUT_DIR_GLOB,
126128
]);
127129
merged.reporters = config.reporters ?? merged.reporters;
130+
131+
merged.coverage ??= {};
132+
merged.coverage.reporters =
133+
config.coverage?.reporters ?? merged.coverage?.reporters;
128134
merged.pool =
129135
typeof config.pool === 'string'
130136
? {

packages/core/src/core/runTests.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,13 @@ export async function runTests(context: Rstest): Promise<void> {
104104
)
105105
: null;
106106

107+
if (coverageProvider) {
108+
logger.log(
109+
` ${color.gray('Coverage enabled with')} %s\n`,
110+
color.yellow(context.normalizedConfig.coverage.provider),
111+
);
112+
}
113+
107114
type Mode = 'all' | 'on-demand';
108115

109116
const run = async ({

packages/core/src/index.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,22 @@
1-
import type { CoverageOptions, CoverageProvider, RstestConfig } from './types';
1+
import type {
2+
CoverageOptions,
3+
CoverageProvider,
4+
NormalizedCoverageOptions,
5+
RstestConfig,
6+
} from './types';
27

38
export type { RsbuildPlugin } from '@rsbuild/core';
49

510
export { runCLI } from './cli';
611
export { mergeRstestConfig } from './config';
712
export * from './runtime/api/public';
813

9-
export type { CoverageOptions, CoverageProvider, RstestConfig };
14+
export type {
15+
NormalizedCoverageOptions,
16+
CoverageOptions,
17+
CoverageProvider,
18+
RstestConfig,
19+
};
1020

1121
export type RstestConfigAsyncFn = () => Promise<RstestConfig>;
1222

packages/core/src/types/coverage.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
import type { ReportOptions } from 'istanbul-reports';
2+
3+
type ReportWithOptions<Name extends keyof ReportOptions = keyof ReportOptions> =
4+
Name extends keyof ReportOptions
5+
? [Name, Partial<ReportOptions[Name]>]
6+
: [Name, Record<string, unknown>];
7+
18
export type CoverageOptions = {
29
/**
310
* Enable coverage collection.
@@ -10,9 +17,18 @@ export type CoverageOptions = {
1017
* @default 'istanbul'
1118
*/
1219
provider?: 'istanbul';
20+
21+
/**
22+
* The reporters to use for coverage collection.
23+
* @default ['text', 'html', 'clover', 'json']
24+
*/
25+
reporters?: (keyof ReportOptions | ReportWithOptions)[];
26+
1327
// TODO: support clean
1428
};
1529

30+
export type NormalizedCoverageOptions = Required<CoverageOptions>;
31+
1632
interface CoverageMap {
1733
files(): string[];
1834
merge(other: any): void;

packages/coverage-istanbul/src/provider.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
import type { CoverageProvider as RstestCoverageProvider } from '@rstest/core';
1+
import type {
2+
NormalizedCoverageOptions,
3+
CoverageProvider as RstestCoverageProvider,
4+
} from '@rstest/core';
25
import istanbulLibCoverage from 'istanbul-lib-coverage';
36
import { createContext } from 'istanbul-lib-report';
47
import reports from 'istanbul-reports';
@@ -19,6 +22,8 @@ declare global {
1922
export class CoverageProvider implements RstestCoverageProvider {
2023
private coverageMap: ReturnType<typeof createCoverageMap> | null = null;
2124

25+
constructor(private options: NormalizedCoverageOptions) {}
26+
2227
init(): void {
2328
// Initialize global coverage object
2429
if (typeof globalThis !== 'undefined') {
@@ -62,15 +67,14 @@ export class CoverageProvider implements RstestCoverageProvider {
6267
defaultSummarizer: 'nested',
6368
coverageMap: createCoverageMap(coverageMap.toJSON()),
6469
});
65-
const reportersList = ['html', 'json'] as const;
70+
const reportersList = this.options.reporters;
6671
for (const reporter of reportersList) {
67-
const report = reports.create(reporter, {
68-
// Add any specific options for the reporter here
69-
});
72+
const [reporterName, reporterOptions] = Array.isArray(reporter)
73+
? reporter
74+
: [reporter, {}];
75+
const report = reports.create(reporterName, reporterOptions);
7076
report.execute(context);
7177
}
72-
73-
console.log('Coverage reports generated in ./coverage directory');
7478
} catch (error) {
7579
console.error('Failed to generate coverage reports:', error);
7680
}

pnpm-lock.yaml

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)