Skip to content

Commit 761beee

Browse files
authored
fix(coverage): include files based on --project filter (#7885)
1 parent 6d64a3f commit 761beee

File tree

11 files changed

+166
-19
lines changed

11 files changed

+166
-19
lines changed

packages/vitest/src/node/coverage.ts

Lines changed: 43 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ export class BaseCoverageProvider<Options extends ResolvedCoverageOptions<'istan
8181
coverageFiles: CoverageFiles = new Map()
8282
pendingPromises: Promise<void>[] = []
8383
coverageFilesDirectory!: string
84+
roots: string[] = []
8485

8586
_initialize(ctx: Vitest): void {
8687
this.ctx = ctx
@@ -130,12 +131,19 @@ export class BaseCoverageProvider<Options extends ResolvedCoverageOptions<'istan
130131
this.options.reportsDirectory,
131132
tempDirectory,
132133
)
134+
135+
// If --project filter is set pick only roots of resolved projects
136+
this.roots = ctx.config.project?.length
137+
? [...new Set(ctx.projects.map(project => project.config.root))]
138+
: [ctx.config.root]
133139
}
134140

135141
/**
136142
* Check if file matches `coverage.include` but not `coverage.exclude`
137143
*/
138-
isIncluded(_filename: string): boolean {
144+
isIncluded(_filename: string, root?: string): boolean {
145+
const roots = root ? [root] : this.roots
146+
139147
const filename = slash(_filename)
140148
const cacheHit = this.globCache.get(filename)
141149

@@ -144,50 +152,66 @@ export class BaseCoverageProvider<Options extends ResolvedCoverageOptions<'istan
144152
}
145153

146154
// File outside project root with default allowExternal
147-
if (this.options.allowExternal === false && !filename.startsWith(this.ctx.config.root)) {
155+
if (this.options.allowExternal === false && roots.every(root => !filename.startsWith(root))) {
148156
this.globCache.set(filename, false)
149157

150158
return false
151159
}
152160

153-
const options: pm.PicomatchOptions = {
154-
contains: true,
155-
dot: true,
156-
cwd: this.ctx.config.root,
157-
ignore: this.options.exclude,
158-
}
159-
160161
// By default `coverage.include` matches all files, except "coverage.exclude"
161162
const glob = this.options.include || '**'
162163

163-
const included = pm.isMatch(filename, glob, options) && existsSync(cleanUrl(filename))
164+
let included = roots.some((root) => {
165+
const options: pm.PicomatchOptions = {
166+
contains: true,
167+
dot: true,
168+
cwd: root,
169+
ignore: this.options.exclude,
170+
}
171+
172+
return pm.isMatch(filename, glob, options)
173+
})
174+
175+
included &&= existsSync(cleanUrl(filename))
164176

165177
this.globCache.set(filename, included)
166178

167179
return included
168180
}
169181

170-
async getUntestedFiles(testedFiles: string[]): Promise<string[]> {
171-
if (this.options.include == null) {
172-
return []
173-
}
174-
175-
let includedFiles = await glob(this.options.include, {
176-
cwd: this.ctx.config.root,
182+
private async getUntestedFilesByRoot(
183+
testedFiles: string[],
184+
include: string[],
185+
root: string,
186+
): Promise<string[]> {
187+
let includedFiles = await glob(include, {
188+
cwd: root,
177189
ignore: [...this.options.exclude, ...testedFiles.map(file => slash(file))],
178190
absolute: true,
179191
dot: true,
180192
onlyFiles: true,
181193
})
182194

183195
// Run again through picomatch as tinyglobby's exclude pattern is different ({ "exclude": ["math"] } should ignore "src/math.ts")
184-
includedFiles = includedFiles.filter(file => this.isIncluded(file))
196+
includedFiles = includedFiles.filter(file => this.isIncluded(file, root))
185197

186198
if (this.ctx.config.changed) {
187199
includedFiles = (this.ctx.config.related || []).filter(file => includedFiles.includes(file))
188200
}
189201

190-
return includedFiles.map(file => slash(path.resolve(this.ctx.config.root, file)))
202+
return includedFiles.map(file => slash(path.resolve(root, file)))
203+
}
204+
205+
async getUntestedFiles(testedFiles: string[]): Promise<string[]> {
206+
if (this.options.include == null) {
207+
return []
208+
}
209+
210+
const rootMapper = this.getUntestedFilesByRoot.bind(this, testedFiles, this.options.include)
211+
212+
const matrix = await Promise.all(this.roots.map(rootMapper))
213+
214+
return matrix.flatMap(files => files)
191215
}
192216

193217
createCoverageMap(): CoverageMap {
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { defineConfig } from "vitest/config";
2+
3+
export default defineConfig({
4+
test: {
5+
projects: [
6+
{
7+
test: {
8+
name: "project1",
9+
root: "fixtures/workspaces/project/project1",
10+
},
11+
},
12+
{
13+
test: {
14+
name: "project2",
15+
root: "fixtures/workspaces/project/project2",
16+
},
17+
},
18+
{
19+
test: {
20+
name: 'project-shared',
21+
root: 'fixtures/workspaces/project/shared',
22+
}
23+
}
24+
]
25+
}
26+
});
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { raise } from "../../shared/src/utils"
2+
3+
export const id = <T>(value: T) =>
4+
value ?? raise("Value cannot be undefined")
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export function untested() {
2+
3+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { expect, test } from 'vitest';
2+
import { id } from '../src/id';
3+
4+
test('returns identity value', () => {
5+
expect(id(1)).toBe(1);
6+
})
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { raise } from "../../shared/src/utils"
2+
3+
export const konst = <T>(value: T) => {
4+
value ??= raise("Value cannot be undefined")
5+
return () => value
6+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export function untested() {
2+
3+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { expect, test } from 'vitest'
2+
import { konst } from '../src/konst'
3+
4+
test('returns function that returns constant value', () => {
5+
const fn = konst(1)
6+
7+
expect(fn()).toBe(1);
8+
})
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
function raise(error: string): never
2+
function raise(error: Error): never
3+
function raise(error: Error | string): never {
4+
if (typeof error === 'string') {
5+
throw new Error(error)
6+
} else {
7+
throw error
8+
}
9+
}
10+
11+
export { raise }
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { expect, test } from 'vitest'
2+
import { raise } from '../src/utils'
3+
4+
test('raise throws error', () => {
5+
const message = 'Value cannot be undefined'
6+
expect(() => raise(message)).toThrowError(message)
7+
})

0 commit comments

Comments
 (0)