mirror of
https://github.com/vitest-dev/vitest.git
synced 2026-02-01 17:36:51 +00:00
feat(config): add diff option (#4063)
Co-authored-by: Vladimir Sheremet <sleuths.slews0s@icloud.com>
This commit is contained in:
parent
725a014687
commit
b50cf7ad9c
@ -1641,3 +1641,33 @@ export default defineConfig({
|
||||
})
|
||||
```
|
||||
|
||||
### diff
|
||||
|
||||
- **Type:** `string`
|
||||
- **CLI:** `--diff=<value>`
|
||||
- **Version:** Since Vitest 0.34.5
|
||||
|
||||
Path to a diff config that will be used to generate diff interface. Useful if you want to customize diff display.
|
||||
|
||||
:::code-group
|
||||
```ts [vitest.diff.ts]
|
||||
import type { DiffOptions } from 'vitest'
|
||||
import c from 'picocolors'
|
||||
|
||||
export default {
|
||||
aIndicator: c.bold('--'),
|
||||
bIndicator: c.bold('++'),
|
||||
omitAnnotationLines: true,
|
||||
} satisfies DiffOptions
|
||||
```
|
||||
|
||||
```ts [vitest.config.js]
|
||||
import { defineConfig } from 'vitest/config'
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
diff: './vitest.diff.ts'
|
||||
}
|
||||
})
|
||||
```
|
||||
:::
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { createClient } from '@vitest/ws-client'
|
||||
import type { ResolvedConfig } from 'vitest'
|
||||
import type { CancelReason, VitestRunner } from '@vitest/runner'
|
||||
import type { VitestExecutor } from 'vitest/src/runtime/execute'
|
||||
import { createBrowserRunner } from './runner'
|
||||
import { importId } from './utils'
|
||||
import { setupConsoleLogSpy } from './logger'
|
||||
@ -101,6 +102,7 @@ async function runTests(paths: string[], config: ResolvedConfig) {
|
||||
const {
|
||||
startTests,
|
||||
setupCommonEnv,
|
||||
loadDiffConfig,
|
||||
takeCoverageInsideWorker,
|
||||
} = await importId('vitest/browser') as typeof import('vitest/browser')
|
||||
|
||||
@ -122,6 +124,8 @@ async function runTests(paths: string[], config: ResolvedConfig) {
|
||||
config.snapshotOptions.snapshotEnvironment = new BrowserSnapshotEnvironment()
|
||||
|
||||
try {
|
||||
runner.config.diffOptions = await loadDiffConfig(config, executor as VitestExecutor)
|
||||
|
||||
await setupCommonEnv(config)
|
||||
const files = paths.map((path) => {
|
||||
return (`${config.root}/${path}`).replace(/\/+/g, '/')
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import limit from 'p-limit'
|
||||
import { getSafeTimers, shuffle } from '@vitest/utils'
|
||||
import { processError } from '@vitest/utils/error'
|
||||
import type { DiffOptions } from '@vitest/utils/diff'
|
||||
import type { VitestRunner } from './types/runner'
|
||||
import type { File, HookCleanupCallback, HookListener, SequenceHooks, Suite, SuiteHooks, Task, TaskMeta, TaskResult, TaskResultPack, TaskState, Test } from './types'
|
||||
import { partitionSuiteChildren } from './utils/suite'
|
||||
@ -173,7 +174,7 @@ export async function runTest(test: Test, runner: VitestRunner) {
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
failTask(test.result, e)
|
||||
failTask(test.result, e, runner.config.diffOptions)
|
||||
}
|
||||
|
||||
// skipped with new PendingError
|
||||
@ -189,7 +190,7 @@ export async function runTest(test: Test, runner: VitestRunner) {
|
||||
await callCleanupHooks(beforeEachCleanups)
|
||||
}
|
||||
catch (e) {
|
||||
failTask(test.result, e)
|
||||
failTask(test.result, e, runner.config.diffOptions)
|
||||
}
|
||||
|
||||
if (test.result.state === 'pass')
|
||||
@ -233,7 +234,7 @@ export async function runTest(test: Test, runner: VitestRunner) {
|
||||
updateTask(test, runner)
|
||||
}
|
||||
|
||||
function failTask(result: TaskResult, err: unknown) {
|
||||
function failTask(result: TaskResult, err: unknown, diffOptions?: DiffOptions) {
|
||||
if (err instanceof PendingError) {
|
||||
result.state = 'skip'
|
||||
return
|
||||
@ -244,7 +245,7 @@ function failTask(result: TaskResult, err: unknown) {
|
||||
? err
|
||||
: [err]
|
||||
for (const e of errors) {
|
||||
const error = processError(e)
|
||||
const error = processError(e, diffOptions)
|
||||
result.error ??= error
|
||||
result.errors ??= []
|
||||
result.errors.push(error)
|
||||
@ -316,7 +317,7 @@ export async function runSuite(suite: Suite, runner: VitestRunner) {
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
failTask(suite.result, e)
|
||||
failTask(suite.result, e, runner.config.diffOptions)
|
||||
}
|
||||
|
||||
try {
|
||||
@ -324,7 +325,7 @@ export async function runSuite(suite: Suite, runner: VitestRunner) {
|
||||
await callCleanupHooks(beforeAllCleanups)
|
||||
}
|
||||
catch (e) {
|
||||
failTask(suite.result, e)
|
||||
failTask(suite.result, e, runner.config.diffOptions)
|
||||
}
|
||||
|
||||
if (suite.mode === 'run') {
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import type { DiffOptions } from '@vitest/utils/diff'
|
||||
import type { File, SequenceHooks, SequenceSetupFiles, Suite, TaskResultPack, Test, TestContext } from './tasks'
|
||||
|
||||
export interface VitestRunnerConfig {
|
||||
@ -21,6 +22,7 @@ export interface VitestRunnerConfig {
|
||||
testTimeout: number
|
||||
hookTimeout: number
|
||||
retry: number
|
||||
diffOptions?: DiffOptions
|
||||
}
|
||||
|
||||
export type VitestRunnerImportSource = 'collect' | 'setup'
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { diff } from './diff'
|
||||
import { type DiffOptions, diff } from './diff'
|
||||
import { format } from './display'
|
||||
import { deepClone, getOwnProperties, getType } from './helpers'
|
||||
import { stringify } from './stringify'
|
||||
@ -86,7 +86,7 @@ function normalizeErrorMessage(message: string) {
|
||||
return message.replace(/__vite_ssr_import_\d+__\./g, '')
|
||||
}
|
||||
|
||||
export function processError(err: any) {
|
||||
export function processError(err: any, diffOptions?: DiffOptions) {
|
||||
if (!err || typeof err !== 'object')
|
||||
return { message: err }
|
||||
// stack is not serialized in worker communication
|
||||
@ -101,7 +101,7 @@ export function processError(err: any) {
|
||||
const clonedExpected = deepClone(err.expected, { forceWritable: true })
|
||||
|
||||
const { replacedActual, replacedExpected } = replaceAsymmetricMatcher(clonedActual, clonedExpected)
|
||||
err.diff = diff(replacedExpected, replacedActual)
|
||||
err.diff = diff(replacedExpected, replacedActual, diffOptions)
|
||||
}
|
||||
|
||||
if (typeof err.expected !== 'string')
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
export { startTests } from '@vitest/runner'
|
||||
export { setupCommonEnv } from './runtime/setup.common'
|
||||
export { setupCommonEnv, loadDiffConfig } from './runtime/setup.common'
|
||||
export { takeCoverageInsideWorker, stopCoverageInsideWorker, getCoverageProvider, startCoverageInsideWorker } from './integrations/coverage'
|
||||
|
||||
@ -51,6 +51,7 @@ cli
|
||||
.option('--test-timeout <time>', 'Default timeout of a test in milliseconds (default: 5000)')
|
||||
.option('--bail <number>', 'Stop test execution when given number of tests have failed', { default: 0 })
|
||||
.option('--retry <times>', 'Retry the test specific number of times if it fails', { default: 0 })
|
||||
.option('--diff <path>', 'Path to a diff config that will be used to generate diff interface')
|
||||
.help()
|
||||
|
||||
cli
|
||||
|
||||
@ -289,6 +289,13 @@ export function resolveConfig(
|
||||
...resolved.setupFiles,
|
||||
]
|
||||
|
||||
if (resolved.diff) {
|
||||
resolved.diff = normalize(
|
||||
resolveModule(resolved.diff, { paths: [resolved.root] })
|
||||
?? resolve(resolved.root, resolved.diff))
|
||||
resolved.forceRerunTriggers.push(resolved.diff)
|
||||
}
|
||||
|
||||
// the server has been created, we don't need to override vite.server options
|
||||
resolved.api = resolveApiServerConfig(options)
|
||||
|
||||
|
||||
@ -20,6 +20,7 @@ export async function run(files: string[], config: ResolvedConfig, environment:
|
||||
setupChaiConfig(config.chaiConfig)
|
||||
|
||||
const runner = await resolveTestRunner(config, executor)
|
||||
|
||||
workerState.onCancel.then(reason => runner.onCancel?.(reason))
|
||||
|
||||
workerState.durations.prepare = performance.now() - workerState.durations.prepare
|
||||
|
||||
@ -6,6 +6,7 @@ import { distDir } from '../../paths'
|
||||
import { getWorkerState } from '../../utils/global'
|
||||
import { rpc } from '../rpc'
|
||||
import { takeCoverageInsideWorker } from '../../integrations/coverage'
|
||||
import { loadDiffConfig } from '../setup.common'
|
||||
|
||||
const runnersFile = resolve(distDir, 'runners.js')
|
||||
|
||||
@ -37,6 +38,8 @@ export async function resolveTestRunner(config: ResolvedConfig, executor: Vitest
|
||||
if (!testRunner.importFile)
|
||||
throw new Error('Runner must implement "importFile" method.')
|
||||
|
||||
testRunner.config.diffOptions = await loadDiffConfig(config, executor)
|
||||
|
||||
// patch some methods, so custom runners don't need to call RPC
|
||||
const originalOnTaskUpdate = testRunner.onTaskUpdate
|
||||
testRunner.onTaskUpdate = async (task) => {
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import { setSafeTimers } from '@vitest/utils'
|
||||
import { resetRunOnceCounter } from '../integrations/run-once'
|
||||
import type { ResolvedConfig } from '../types'
|
||||
import type { DiffOptions } from '../types/matcher-utils'
|
||||
import type { VitestExecutor } from './execute'
|
||||
|
||||
let globalSetup = false
|
||||
export async function setupCommonEnv(config: ResolvedConfig) {
|
||||
@ -21,3 +23,15 @@ function setupDefines(defines: Record<string, any>) {
|
||||
for (const key in defines)
|
||||
(globalThis as any)[key] = defines[key]
|
||||
}
|
||||
|
||||
export async function loadDiffConfig(config: ResolvedConfig, executor: VitestExecutor) {
|
||||
if (typeof config.diff !== 'string')
|
||||
return
|
||||
|
||||
const diffModule = await executor.executeId(config.diff)
|
||||
|
||||
if (diffModule && typeof diffModule.default === 'object' && diffModule.default != null)
|
||||
return diffModule.default as DiffOptions
|
||||
else
|
||||
throw new Error(`invalid diff config file ${config.diff}. Must have a default export with config object`)
|
||||
}
|
||||
|
||||
@ -539,6 +539,11 @@ export interface InlineConfig {
|
||||
*/
|
||||
snapshotFormat?: PrettyFormatOptions
|
||||
|
||||
/**
|
||||
* Path to a module which has a default export of diff config.
|
||||
*/
|
||||
diff?: string
|
||||
|
||||
/**
|
||||
* Resolve custom snapshot path
|
||||
*/
|
||||
|
||||
@ -13,6 +13,7 @@ export type * from './worker'
|
||||
export type * from './general'
|
||||
export type * from './coverage'
|
||||
export type * from './benchmark'
|
||||
export type { DiffOptions } from '@vitest/utils/diff'
|
||||
export type {
|
||||
EnhancedSpy,
|
||||
MockedFunction,
|
||||
|
||||
4
test/browser/custom-diff-config.ts
Normal file
4
test/browser/custom-diff-config.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export default {
|
||||
aAnnotation: 'Expected to be',
|
||||
bAnnotation: 'But got',
|
||||
}
|
||||
@ -34,6 +34,8 @@ await test('tests are actually running', async () => {
|
||||
await test('correctly prints error', () => {
|
||||
assert.match(stderr, /expected 1 to be 2/, 'prints failing error')
|
||||
assert.match(stderr, /- 2\s+\+ 1/, 'prints failing diff')
|
||||
assert.match(stderr, /Expected to be/, 'prints \`Expected to be\`')
|
||||
assert.match(stderr, /But got/, 'prints \`But got\`')
|
||||
})
|
||||
|
||||
await test('logs are redirected to stdout', async () => {
|
||||
|
||||
@ -20,6 +20,7 @@ export default defineConfig({
|
||||
},
|
||||
open: false,
|
||||
isolate: false,
|
||||
diff: './custom-diff-config.ts',
|
||||
outputFile: './browser.json',
|
||||
reporters: ['json', {
|
||||
onInit: noop,
|
||||
|
||||
5
test/reporters/fixtures/custom-diff-config.test.ts
Normal file
5
test/reporters/fixtures/custom-diff-config.test.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { expect, test } from 'vitest'
|
||||
|
||||
test('', () => {
|
||||
expect({ foo: 1 }).toMatchInlineSnapshot('xxx')
|
||||
})
|
||||
4
test/reporters/fixtures/custom-diff-config.ts
Normal file
4
test/reporters/fixtures/custom-diff-config.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export default {
|
||||
aAnnotation: 'Expected to be',
|
||||
bAnnotation: 'But got',
|
||||
}
|
||||
1
test/reporters/fixtures/invalid-diff-config.ts
Normal file
1
test/reporters/fixtures/invalid-diff-config.ts
Normal file
@ -0,0 +1 @@
|
||||
export const diffOptions = {}
|
||||
23
test/reporters/tests/custom-diff-config.spec.ts
Normal file
23
test/reporters/tests/custom-diff-config.spec.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { expect, test } from 'vitest'
|
||||
import { resolve } from 'pathe'
|
||||
import { runVitest } from '../../test-utils'
|
||||
|
||||
test('custom diff config', async () => {
|
||||
const filename = resolve('./fixtures/custom-diff-config.test.ts')
|
||||
const diff = resolve('./fixtures/custom-diff-config.ts')
|
||||
const { stderr } = await runVitest({ root: './fixtures', diff }, [filename])
|
||||
|
||||
expect(stderr).toBeTruthy()
|
||||
expect(stderr).toContain('Expected to be')
|
||||
expect(stderr).toContain('But got')
|
||||
})
|
||||
|
||||
test('invalid diff config file', async () => {
|
||||
const filename = resolve('./fixtures/custom-diff-config.test.ts')
|
||||
const diff = resolve('./fixtures/invalid-diff-config.ts')
|
||||
const { stderr } = await runVitest({ root: './fixtures', diff }, [filename])
|
||||
|
||||
expect(stderr).toBeTruthy()
|
||||
expect(stderr).toContain('invalid diff config file')
|
||||
expect(stderr).toContain('Must have a default export with config object')
|
||||
})
|
||||
Loading…
x
Reference in New Issue
Block a user