Skip to content

Commit

Permalink
fix(coverage): thresholds.autoUpdate to support mergeConfig (#5818)
Browse files Browse the repository at this point in the history
  • Loading branch information
AriPerkkio committed Jun 3, 2024
1 parent 839c39f commit 7afb368
Show file tree
Hide file tree
Showing 3 changed files with 209 additions and 8 deletions.
48 changes: 40 additions & 8 deletions packages/vitest/src/utils/coverage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,14 +263,16 @@ function resolveConfig(configModule: any) {
if (mod.$type === 'object')
return mod

if (mod.$type === 'function-call') {
// "export default defineConfig({ test: {...} })"
if (mod.$args[0].$type === 'object')
return mod.$args[0]

// "export default defineConfig(() => ({ test: {...} }))"
if (mod.$args[0].$type === 'arrow-function-expression' && mod.$args[0].$body.$type === 'object')
return mod.$args[0].$body
// "export default defineConfig(...)"
let config = resolveDefineConfig(mod)
if (config)
return config

// "export default mergeConfig(..., defineConfig(...))"
if (mod.$type === 'function-call' && mod.$callee === 'mergeConfig') {
config = resolveMergeConfig(mod)
if (config)
return config
}
}
catch (error) {
Expand All @@ -280,3 +282,33 @@ function resolveConfig(configModule: any) {

throw new Error('Failed to update coverage thresholds. Configuration file is too complex.')
}

function resolveDefineConfig(mod: any) {
if (mod.$type === 'function-call' && mod.$callee === 'defineConfig') {
// "export default defineConfig({ test: {...} })"
if (mod.$args[0].$type === 'object')
return mod.$args[0]

if (mod.$args[0].$type === 'arrow-function-expression') {
if (mod.$args[0].$body.$type === 'object') {
// "export default defineConfig(() => ({ test: {...} }))"
return mod.$args[0].$body
}

// "export default defineConfig(() => mergeConfig({...}, ...))"
const config = resolveMergeConfig(mod.$args[0].$body)
if (config)
return config
}
}
}

function resolveMergeConfig(mod: any): any {
if (mod.$type === 'function-call' && mod.$callee === 'mergeConfig') {
for (const arg of mod.$args) {
const config = resolveDefineConfig(arg)
if (config)
return config
}
}
}
162 changes: 162 additions & 0 deletions test/coverage-test/option-tests/thresholds-auto-update.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import { parseModule } from 'magicast'
import type { CoverageMap } from 'istanbul-lib-coverage'
import { createCoverageSummary } from 'istanbul-lib-coverage'

import { expect, test } from 'vitest'
import { defineConfig } from 'vitest/config'
import { BaseCoverageProvider } from 'vitest/coverage'

const initialThresholds = { lines: 1, branches: 2, functions: 3, statements: 4 }
const coveredThresholds = { lines: 50, branches: 60, functions: 70, statements: 80 }
const initialConfig = JSON.stringify(defineConfig({
test: {
coverage: {
thresholds: initialThresholds,
},
},
}), null, 2)

test('updates thresholds on "export default {..}"', async () => {
const config = parseModule(`export default ${initialConfig}`)

const updatedConfig = await updateThresholds(config)

expect(updatedConfig).toMatchInlineSnapshot(`
"export default {
"test": {
"coverage": {
"thresholds": {
"lines": 50,
"branches": 60,
"functions": 70,
"statements": 80
}
}
}
}"
`)
})

test('updates thresholds on "export default defineConfig({...})"', async () => {
const config = parseModule(`export default defineConfig(${initialConfig})`)

const updatedConfig = await updateThresholds(config)

expect(updatedConfig).toMatchInlineSnapshot(`
"export default defineConfig({
"test": {
"coverage": {
"thresholds": {
"lines": 50,
"branches": 60,
"functions": 70,
"statements": 80
}
}
}
})"
`)
})

test('updates thresholds on "export default defineConfig(() => ({...}))"', async () => {
const config = parseModule(`export default defineConfig(() => (${initialConfig}))`)

const updatedConfig = await updateThresholds(config)

expect(updatedConfig).toMatchInlineSnapshot(`
"export default defineConfig(() => ({
"test": {
"coverage": {
"thresholds": {
"lines": 50,
"branches": 60,
"functions": 70,
"statements": 80
}
}
}
}))"
`)
})

test('updates thresholds on "export default mergeConfig({...}, defineConfig({...}))"', async () => {
const config = parseModule(`export default mergeConfig(baseConfig, defineConfig(${initialConfig}))`)

const updatedConfig = await updateThresholds(config)

expect(updatedConfig).toMatchInlineSnapshot(`
"export default mergeConfig(baseConfig, defineConfig({
"test": {
"coverage": {
"thresholds": {
"lines": 50,
"branches": 60,
"functions": 70,
"statements": 80
}
}
}
}))"
`)
})

test('updates thresholds on "export default defineConfig(() => mergeConfig({...}, defineConfig({...})))"', async () => {
const config = parseModule(`export default defineConfig((configEnv) => mergeConfig(baseConfig, defineConfig(${initialConfig})))`)

const updatedConfig = await updateThresholds(config)

expect(updatedConfig).toMatchInlineSnapshot(`
"export default defineConfig((configEnv) => mergeConfig(baseConfig, defineConfig({
"test": {
"coverage": {
"thresholds": {
"lines": 50,
"branches": 60,
"functions": 70,
"statements": 80
}
}
}
})))"
`)
})

test('throws when configuration is too complex to analyze', async () => {
const config = parseModule(`
import config from "./some-path"
export default config
`)

await expect(updateThresholds(config)).rejects.toThrowErrorMatchingInlineSnapshot(`[Error: Failed to update coverage thresholds. Configuration file is too complex.]`)
})

async function updateThresholds(configurationFile: ReturnType<typeof parseModule>) {
const summaryData = { total: 0, covered: 0, skipped: 0 }
const thresholds = [{
name: 'global',
thresholds: initialThresholds,
coverageMap: {
getCoverageSummary: () => createCoverageSummary({
lines: { pct: coveredThresholds.lines, ...summaryData },
statements: { pct: coveredThresholds.statements, ...summaryData },
branches: { pct: coveredThresholds.branches, ...summaryData },
functions: { pct: coveredThresholds.functions, ...summaryData },
}),
} as CoverageMap,
}]

return new Promise((resolve, reject) => {
const provider = new BaseCoverageProvider()

try {
provider.updateThresholds({
thresholds,
configurationFile,
onUpdate: () => resolve(configurationFile.generate().code),
})
}
catch (error) {
reject(error)
}
})
}
7 changes: 7 additions & 0 deletions test/coverage-test/testing-options.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,13 @@ const testCases = [
include: ['coverage-report-tests/merge-reports.test.ts'],
},
},
{
testConfig: {
name: 'thresholds autoUpdate',
include: ['option-tests/thresholds-auto-update.test.ts'],
coverage: { provider: 'v8', enabled: false },
},
},
]

for (const provider of ['v8', 'istanbul']) {
Expand Down

0 comments on commit 7afb368

Please sign in to comment.