Skip to content

Commit 0f44d2c

Browse files
feat: implement istanbul coverage support for browser testing (#3040)
Co-authored-by: AriPerkkio <[email protected]>
1 parent 09fec84 commit 0f44d2c

File tree

32 files changed

+458
-151
lines changed

32 files changed

+458
-151
lines changed

packages/browser/src/client/main.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ ws.addEventListener('open', async () => {
5757
const { getSafeTimers } = await importId('vitest/utils') as typeof import('vitest/utils')
5858
const safeRpc = createSafeRpc(client, getSafeTimers)
5959

60+
// @ts-expect-error untyped global for internal use
61+
globalThis.__vitest_browser__ = true
6062
// @ts-expect-error mocking vitest apis
6163
globalThis.__vitest_worker__ = {
6264
config,
@@ -82,11 +84,20 @@ async function runTests(paths: string[], config: any) {
8284
const viteClientPath = '/@vite/client'
8385
await import(viteClientPath)
8486

85-
const { startTests, setupCommonEnv, setupSnapshotEnvironment } = await importId('vitest/browser') as typeof import('vitest/browser')
87+
const {
88+
startTests,
89+
setupCommonEnv,
90+
setupSnapshotEnvironment,
91+
takeCoverageInsideWorker,
92+
} = await importId('vitest/browser') as typeof import('vitest/browser')
93+
94+
const executor = {
95+
executeId: (id: string) => importId(id),
96+
}
8697

8798
if (!runner) {
8899
const { VitestTestRunner } = await importId('vitest/runners') as typeof import('vitest/runners')
89-
const BrowserRunner = createBrowserRunner(VitestTestRunner)
100+
const BrowserRunner = createBrowserRunner(VitestTestRunner, { takeCoverage: () => takeCoverageInsideWorker(config.coverage, executor) })
90101
runner = new BrowserRunner({ config, browserHashMap })
91102
}
92103

@@ -104,7 +115,8 @@ async function runTests(paths: string[], config: any) {
104115
const now = `${new Date().getTime()}`
105116
files.forEach(i => browserHashMap.set(i, now))
106117

107-
await startTests(files, runner)
118+
for (const file of files)
119+
await startTests([file], runner)
108120
}
109121
finally {
110122
await rpcDone()

packages/browser/src/client/runner.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@ interface BrowserRunnerOptions {
77
browserHashMap: Map<string, string>
88
}
99

10-
export function createBrowserRunner(original: any) {
10+
interface CoverageHandler {
11+
takeCoverage: () => Promise<unknown>
12+
}
13+
14+
export function createBrowserRunner(original: any, coverageModule: CoverageHandler | null) {
1115
return class BrowserTestRunner extends original {
1216
public config: ResolvedConfig
1317
hashMap = new Map<string, string>()
@@ -25,6 +29,12 @@ export function createBrowserRunner(original: any) {
2529
})
2630
}
2731

32+
async onAfterRunSuite() {
33+
await super.onAfterRunSuite?.()
34+
const coverage = await coverageModule?.takeCoverage?.()
35+
await rpc().onAfterSuiteRun({ coverage })
36+
}
37+
2838
onCollected(files: File[]): unknown {
2939
return rpc().onCollected(files)
3040
}

packages/browser/src/node/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ export default (base = '/'): Plugin[] => {
1818
{
1919
enforce: 'pre',
2020
name: 'vitest:browser',
21+
async config(viteConfig) {
22+
// Enables using ignore hint for coverage providers with @preserve keyword
23+
viteConfig.esbuild ||= {}
24+
viteConfig.esbuild.legalComments = 'inline'
25+
},
2126
async configureServer(server) {
2227
server.middlewares.use(
2328
base,

packages/coverage-c8/rollup.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import pkg from './package.json'
1010

1111
const entries = {
1212
index: 'src/index.ts',
13+
provider: 'src/provider.ts',
1314
}
1415

1516
const external = [

packages/coverage-c8/src/index.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
1-
export * from './takeCoverage'
1+
import * as coverage from './takeCoverage'
22

3-
export async function getProvider() {
4-
const { C8CoverageProvider } = await import('./provider')
5-
return new C8CoverageProvider()
3+
export default {
4+
...coverage,
5+
async getProvider() {
6+
// to not bundle the provider
7+
const name = './provider.js'
8+
const { C8CoverageProvider } = await import(name) as typeof import('./provider')
9+
return new C8CoverageProvider()
10+
},
611
}

packages/coverage-istanbul/rollup.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import pkg from './package.json'
1010

1111
const entries = {
1212
index: 'src/index.ts',
13+
provider: 'src/provider.ts',
1314
}
1415

1516
const external = [

packages/coverage-istanbul/src/index.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import { COVERAGE_STORE_KEY } from './constants'
22

33
export async function getProvider() {
4-
const { IstanbulCoverageProvider } = await import('./provider')
4+
// to not bundle the provider
5+
const providerPath = './provider.js'
6+
const { IstanbulCoverageProvider } = await import(providerPath) as typeof import('./provider')
57
return new IstanbulCoverageProvider()
68
}
79

@@ -15,3 +17,8 @@ export function takeCoverage() {
1517

1618
return coverage
1719
}
20+
21+
export default {
22+
getProvider,
23+
takeCoverage,
24+
}

packages/vitest/src/api/setup.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ export function setup(ctx: Vitest, server?: ViteDevServer) {
4646
ctx.state.updateTasks(packs)
4747
await ctx.report('onTaskUpdate', packs)
4848
},
49+
onAfterSuiteRun(meta) {
50+
ctx.coverageProvider?.onAfterSuiteRun(meta)
51+
},
4952
getFiles() {
5053
return ctx.state.getFiles()
5154
},

packages/vitest/src/api/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { TransformResult } from 'vite'
2-
import type { File, ModuleGraphData, Reporter, ResolvedConfig, SnapshotResult, TaskResultPack, UserConsoleLog } from '../types'
2+
import type { AfterSuiteRunMeta, File, ModuleGraphData, Reporter, ResolvedConfig, SnapshotResult, TaskResultPack, UserConsoleLog } from '../types'
33

44
export interface TransformResultWithSource extends TransformResult {
55
source?: string
@@ -8,6 +8,7 @@ export interface TransformResultWithSource extends TransformResult {
88
export interface WebSocketHandlers {
99
onCollected(files?: File[]): Promise<void>
1010
onTaskUpdate(packs: TaskResultPack[]): void
11+
onAfterSuiteRun(meta: AfterSuiteRunMeta): void
1112
onDone(name: string): void
1213
sendLog(log: UserConsoleLog): void
1314
getFiles(): File[]

packages/vitest/src/browser.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export { startTests } from '@vitest/runner'
22
export { setupCommonEnv } from './runtime/setup.common'
33
export { setupSnapshotEnvironment } from './integrations/snapshot/env'
4+
export { takeCoverageInsideWorker, stopCoverageInsideWorker, getCoverageProvider, startCoverageInsideWorker } from './integrations/coverage'

packages/vitest/src/integrations/browser/server.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type { Vitest } from '../../node'
66
import type { UserConfig } from '../../types/config'
77
import { ensurePackageInstalled } from '../../node/pkg'
88
import { resolveApiServerConfig } from '../../node/config'
9+
import { CoverageTransform } from '../../node/plugins/coverageTransform'
910

1011
export async function createBrowserServer(ctx: Vitest, options: UserConfig) {
1112
const root = ctx.config.root
@@ -31,6 +32,7 @@ export async function createBrowserServer(ctx: Vitest, options: UserConfig) {
3132
},
3233
plugins: [
3334
(await import('@vitest/browser')).default('/'),
35+
CoverageTransform(ctx),
3436
{
3537
enforce: 'post',
3638
name: 'vitest:browser:config',
@@ -44,14 +46,19 @@ export async function createBrowserServer(ctx: Vitest, options: UserConfig) {
4446
config.optimizeDeps ??= {}
4547
config.optimizeDeps.entries ??= []
4648

47-
const root = config.root || process.cwd()
4849
const [...entries] = await ctx.globAllTestFiles(ctx.config, ctx.config.dir || root)
4950
entries.push(...ctx.config.setupFiles)
5051

5152
if (typeof config.optimizeDeps.entries === 'string')
5253
config.optimizeDeps.entries = [config.optimizeDeps.entries]
5354

5455
config.optimizeDeps.entries.push(...entries)
56+
57+
return {
58+
resolve: {
59+
alias: config.test?.alias,
60+
},
61+
}
5562
},
5663
},
5764
],

packages/vitest/src/integrations/coverage.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { importModule } from 'local-pkg'
21
import type { CoverageOptions, CoverageProvider, CoverageProviderModule } from '../types'
32

43
interface Loader {
@@ -16,8 +15,10 @@ async function resolveCoverageProviderModule(options: CoverageOptions | undefine
1615

1716
const provider = options.provider
1817

19-
if (provider === 'c8' || provider === 'istanbul')
20-
return await importModule<CoverageProviderModule>(CoverageProviderMap[provider])
18+
if (provider === 'c8' || provider === 'istanbul') {
19+
const { default: coverageModule } = await loader.executeId(CoverageProviderMap[provider])
20+
return coverageModule
21+
}
2122

2223
let customProviderModule
2324

packages/vitest/src/node/config.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,9 @@ export function resolveConfig(
113113
}
114114
}
115115

116+
if (resolved.coverage.provider === 'c8' && resolved.coverage.enabled && isBrowserEnabled(resolved))
117+
throw new Error('@vitest/coverage-c8 does not work with --browser. Use @vitest/coverage-istanbul instead')
118+
116119
resolved.deps = resolved.deps || {}
117120
// vitenode will try to import such file with native node,
118121
// but then our mocker will not work properly
@@ -263,3 +266,10 @@ export function resolveConfig(
263266

264267
return resolved
265268
}
269+
270+
export function isBrowserEnabled(config: ResolvedConfig) {
271+
if (config.browser.enabled)
272+
return true
273+
274+
return config.poolMatchGlobs?.length && config.poolMatchGlobs.some(([, pool]) => pool === 'browser')
275+
}

packages/vitest/src/node/core.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { createPool } from './pool'
1818
import type { ProcessPool } from './pool'
1919
import { createBenchmarkReporters, createReporters } from './reporters/utils'
2020
import { StateManager } from './state'
21-
import { resolveConfig } from './config'
21+
import { isBrowserEnabled, resolveConfig } from './config'
2222
import { Logger } from './logger'
2323
import { VitestCache } from './cache'
2424
import { VitestServer } from './server'
@@ -725,9 +725,7 @@ export class Vitest {
725725
}
726726

727727
isBrowserEnabled() {
728-
if (this.config.browser.enabled)
729-
return true
730-
return this.config.poolMatchGlobs?.length && this.config.poolMatchGlobs.some(([, pool]) => pool === 'browser')
728+
return isBrowserEnabled(this.config)
731729
}
732730

733731
// The server needs to be running for communication
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import type { Plugin as VitePlugin } from 'vite'
2+
import { normalizeRequestId } from 'vite-node/utils'
23

34
import type { Vitest } from '../core'
45

56
export function CoverageTransform(ctx: Vitest): VitePlugin | null {
67
return {
78
name: 'vitest:coverage-transform',
89
transform(srcCode, id) {
9-
return ctx.coverageProvider?.onFileTransform?.(srcCode, id, this)
10+
return ctx.coverageProvider?.onFileTransform?.(srcCode, normalizeRequestId(id), this)
1011
},
1112
}
1213
}

0 commit comments

Comments
 (0)