Skip to content

Commit

Permalink
feat: implement istanbul coverage support for browser testing (#3040)
Browse files Browse the repository at this point in the history
Co-authored-by: AriPerkkio <ari.perkkio@gmail.com>
  • Loading branch information
sheremet-va and AriPerkkio committed Mar 28, 2023
1 parent 09fec84 commit 0f44d2c
Show file tree
Hide file tree
Showing 32 changed files with 458 additions and 151 deletions.
18 changes: 15 additions & 3 deletions packages/browser/src/client/main.ts
Expand Up @@ -57,6 +57,8 @@ ws.addEventListener('open', async () => {
const { getSafeTimers } = await importId('vitest/utils') as typeof import('vitest/utils')
const safeRpc = createSafeRpc(client, getSafeTimers)

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

const { startTests, setupCommonEnv, setupSnapshotEnvironment } = await importId('vitest/browser') as typeof import('vitest/browser')
const {
startTests,
setupCommonEnv,
setupSnapshotEnvironment,
takeCoverageInsideWorker,
} = await importId('vitest/browser') as typeof import('vitest/browser')

const executor = {
executeId: (id: string) => importId(id),
}

if (!runner) {
const { VitestTestRunner } = await importId('vitest/runners') as typeof import('vitest/runners')
const BrowserRunner = createBrowserRunner(VitestTestRunner)
const BrowserRunner = createBrowserRunner(VitestTestRunner, { takeCoverage: () => takeCoverageInsideWorker(config.coverage, executor) })
runner = new BrowserRunner({ config, browserHashMap })
}

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

await startTests(files, runner)
for (const file of files)
await startTests([file], runner)
}
finally {
await rpcDone()
Expand Down
12 changes: 11 additions & 1 deletion packages/browser/src/client/runner.ts
Expand Up @@ -7,7 +7,11 @@ interface BrowserRunnerOptions {
browserHashMap: Map<string, string>
}

export function createBrowserRunner(original: any) {
interface CoverageHandler {
takeCoverage: () => Promise<unknown>
}

export function createBrowserRunner(original: any, coverageModule: CoverageHandler | null) {
return class BrowserTestRunner extends original {
public config: ResolvedConfig
hashMap = new Map<string, string>()
Expand All @@ -25,6 +29,12 @@ export function createBrowserRunner(original: any) {
})
}

async onAfterRunSuite() {
await super.onAfterRunSuite?.()
const coverage = await coverageModule?.takeCoverage?.()
await rpc().onAfterSuiteRun({ coverage })
}

onCollected(files: File[]): unknown {
return rpc().onCollected(files)
}
Expand Down
5 changes: 5 additions & 0 deletions packages/browser/src/node/index.ts
Expand Up @@ -18,6 +18,11 @@ export default (base = '/'): Plugin[] => {
{
enforce: 'pre',
name: 'vitest:browser',
async config(viteConfig) {
// Enables using ignore hint for coverage providers with @preserve keyword
viteConfig.esbuild ||= {}
viteConfig.esbuild.legalComments = 'inline'
},
async configureServer(server) {
server.middlewares.use(
base,
Expand Down
1 change: 1 addition & 0 deletions packages/coverage-c8/rollup.config.js
Expand Up @@ -10,6 +10,7 @@ import pkg from './package.json'

const entries = {
index: 'src/index.ts',
provider: 'src/provider.ts',
}

const external = [
Expand Down
13 changes: 9 additions & 4 deletions packages/coverage-c8/src/index.ts
@@ -1,6 +1,11 @@
export * from './takeCoverage'
import * as coverage from './takeCoverage'

export async function getProvider() {
const { C8CoverageProvider } = await import('./provider')
return new C8CoverageProvider()
export default {
...coverage,
async getProvider() {
// to not bundle the provider
const name = './provider.js'
const { C8CoverageProvider } = await import(name) as typeof import('./provider')
return new C8CoverageProvider()
},
}
1 change: 1 addition & 0 deletions packages/coverage-istanbul/rollup.config.js
Expand Up @@ -10,6 +10,7 @@ import pkg from './package.json'

const entries = {
index: 'src/index.ts',
provider: 'src/provider.ts',
}

const external = [
Expand Down
9 changes: 8 additions & 1 deletion packages/coverage-istanbul/src/index.ts
@@ -1,7 +1,9 @@
import { COVERAGE_STORE_KEY } from './constants'

export async function getProvider() {
const { IstanbulCoverageProvider } = await import('./provider')
// to not bundle the provider
const providerPath = './provider.js'
const { IstanbulCoverageProvider } = await import(providerPath) as typeof import('./provider')
return new IstanbulCoverageProvider()
}

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

return coverage
}

export default {
getProvider,
takeCoverage,
}
3 changes: 3 additions & 0 deletions packages/vitest/src/api/setup.ts
Expand Up @@ -46,6 +46,9 @@ export function setup(ctx: Vitest, server?: ViteDevServer) {
ctx.state.updateTasks(packs)
await ctx.report('onTaskUpdate', packs)
},
onAfterSuiteRun(meta) {
ctx.coverageProvider?.onAfterSuiteRun(meta)
},
getFiles() {
return ctx.state.getFiles()
},
Expand Down
3 changes: 2 additions & 1 deletion packages/vitest/src/api/types.ts
@@ -1,5 +1,5 @@
import type { TransformResult } from 'vite'
import type { File, ModuleGraphData, Reporter, ResolvedConfig, SnapshotResult, TaskResultPack, UserConsoleLog } from '../types'
import type { AfterSuiteRunMeta, File, ModuleGraphData, Reporter, ResolvedConfig, SnapshotResult, TaskResultPack, UserConsoleLog } from '../types'

export interface TransformResultWithSource extends TransformResult {
source?: string
Expand All @@ -8,6 +8,7 @@ export interface TransformResultWithSource extends TransformResult {
export interface WebSocketHandlers {
onCollected(files?: File[]): Promise<void>
onTaskUpdate(packs: TaskResultPack[]): void
onAfterSuiteRun(meta: AfterSuiteRunMeta): void
onDone(name: string): void
sendLog(log: UserConsoleLog): void
getFiles(): File[]
Expand Down
1 change: 1 addition & 0 deletions packages/vitest/src/browser.ts
@@ -1,3 +1,4 @@
export { startTests } from '@vitest/runner'
export { setupCommonEnv } from './runtime/setup.common'
export { setupSnapshotEnvironment } from './integrations/snapshot/env'
export { takeCoverageInsideWorker, stopCoverageInsideWorker, getCoverageProvider, startCoverageInsideWorker } from './integrations/coverage'
9 changes: 8 additions & 1 deletion packages/vitest/src/integrations/browser/server.ts
Expand Up @@ -6,6 +6,7 @@ import type { Vitest } from '../../node'
import type { UserConfig } from '../../types/config'
import { ensurePackageInstalled } from '../../node/pkg'
import { resolveApiServerConfig } from '../../node/config'
import { CoverageTransform } from '../../node/plugins/coverageTransform'

export async function createBrowserServer(ctx: Vitest, options: UserConfig) {
const root = ctx.config.root
Expand All @@ -31,6 +32,7 @@ export async function createBrowserServer(ctx: Vitest, options: UserConfig) {
},
plugins: [
(await import('@vitest/browser')).default('/'),
CoverageTransform(ctx),
{
enforce: 'post',
name: 'vitest:browser:config',
Expand All @@ -44,14 +46,19 @@ export async function createBrowserServer(ctx: Vitest, options: UserConfig) {
config.optimizeDeps ??= {}
config.optimizeDeps.entries ??= []

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

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

config.optimizeDeps.entries.push(...entries)

return {
resolve: {
alias: config.test?.alias,
},
}
},
},
],
Expand Down
7 changes: 4 additions & 3 deletions packages/vitest/src/integrations/coverage.ts
@@ -1,4 +1,3 @@
import { importModule } from 'local-pkg'
import type { CoverageOptions, CoverageProvider, CoverageProviderModule } from '../types'

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

const provider = options.provider

if (provider === 'c8' || provider === 'istanbul')
return await importModule<CoverageProviderModule>(CoverageProviderMap[provider])
if (provider === 'c8' || provider === 'istanbul') {
const { default: coverageModule } = await loader.executeId(CoverageProviderMap[provider])
return coverageModule
}

let customProviderModule

Expand Down
10 changes: 10 additions & 0 deletions packages/vitest/src/node/config.ts
Expand Up @@ -113,6 +113,9 @@ export function resolveConfig(
}
}

if (resolved.coverage.provider === 'c8' && resolved.coverage.enabled && isBrowserEnabled(resolved))
throw new Error('@vitest/coverage-c8 does not work with --browser. Use @vitest/coverage-istanbul instead')

resolved.deps = resolved.deps || {}
// vitenode will try to import such file with native node,
// but then our mocker will not work properly
Expand Down Expand Up @@ -263,3 +266,10 @@ export function resolveConfig(

return resolved
}

export function isBrowserEnabled(config: ResolvedConfig) {
if (config.browser.enabled)
return true

return config.poolMatchGlobs?.length && config.poolMatchGlobs.some(([, pool]) => pool === 'browser')
}
6 changes: 2 additions & 4 deletions packages/vitest/src/node/core.ts
Expand Up @@ -18,7 +18,7 @@ import { createPool } from './pool'
import type { ProcessPool } from './pool'
import { createBenchmarkReporters, createReporters } from './reporters/utils'
import { StateManager } from './state'
import { resolveConfig } from './config'
import { isBrowserEnabled, resolveConfig } from './config'
import { Logger } from './logger'
import { VitestCache } from './cache'
import { VitestServer } from './server'
Expand Down Expand Up @@ -725,9 +725,7 @@ export class Vitest {
}

isBrowserEnabled() {
if (this.config.browser.enabled)
return true
return this.config.poolMatchGlobs?.length && this.config.poolMatchGlobs.some(([, pool]) => pool === 'browser')
return isBrowserEnabled(this.config)
}

// The server needs to be running for communication
Expand Down
3 changes: 2 additions & 1 deletion packages/vitest/src/node/plugins/coverageTransform.ts
@@ -1,12 +1,13 @@
import type { Plugin as VitePlugin } from 'vite'
import { normalizeRequestId } from 'vite-node/utils'

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

export function CoverageTransform(ctx: Vitest): VitePlugin | null {
return {
name: 'vitest:coverage-transform',
transform(srcCode, id) {
return ctx.coverageProvider?.onFileTransform?.(srcCode, id, this)
return ctx.coverageProvider?.onFileTransform?.(srcCode, normalizeRequestId(id), this)
},
}
}

0 comments on commit 0f44d2c

Please sign in to comment.