Skip to content

Commit b50cf7a

Browse files
fenghan34sheremet-va
andauthoredSep 18, 2023
feat(config): add diff option (#4063)
Co-authored-by: Vladimir Sheremet <sleuths.slews0s@icloud.com>
1 parent 725a014 commit b50cf7a

File tree

20 files changed

+119
-10
lines changed

20 files changed

+119
-10
lines changed
 

‎docs/config/index.md

+30
Original file line numberDiff line numberDiff line change
@@ -1641,3 +1641,33 @@ export default defineConfig({
16411641
})
16421642
```
16431643

1644+
### diff
1645+
1646+
- **Type:** `string`
1647+
- **CLI:** `--diff=<value>`
1648+
- **Version:** Since Vitest 0.34.5
1649+
1650+
Path to a diff config that will be used to generate diff interface. Useful if you want to customize diff display.
1651+
1652+
:::code-group
1653+
```ts [vitest.diff.ts]
1654+
import type { DiffOptions } from 'vitest'
1655+
import c from 'picocolors'
1656+
1657+
export default {
1658+
aIndicator: c.bold('--'),
1659+
bIndicator: c.bold('++'),
1660+
omitAnnotationLines: true,
1661+
} satisfies DiffOptions
1662+
```
1663+
1664+
```ts [vitest.config.js]
1665+
import { defineConfig } from 'vitest/config'
1666+
1667+
export default defineConfig({
1668+
test: {
1669+
diff: './vitest.diff.ts'
1670+
}
1671+
})
1672+
```
1673+
:::

‎packages/browser/src/client/main.ts

+4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { createClient } from '@vitest/ws-client'
22
import type { ResolvedConfig } from 'vitest'
33
import type { CancelReason, VitestRunner } from '@vitest/runner'
4+
import type { VitestExecutor } from 'vitest/src/runtime/execute'
45
import { createBrowserRunner } from './runner'
56
import { importId } from './utils'
67
import { setupConsoleLogSpy } from './logger'
@@ -101,6 +102,7 @@ async function runTests(paths: string[], config: ResolvedConfig) {
101102
const {
102103
startTests,
103104
setupCommonEnv,
105+
loadDiffConfig,
104106
takeCoverageInsideWorker,
105107
} = await importId('vitest/browser') as typeof import('vitest/browser')
106108

@@ -122,6 +124,8 @@ async function runTests(paths: string[], config: ResolvedConfig) {
122124
config.snapshotOptions.snapshotEnvironment = new BrowserSnapshotEnvironment()
123125

124126
try {
127+
runner.config.diffOptions = await loadDiffConfig(config, executor as VitestExecutor)
128+
125129
await setupCommonEnv(config)
126130
const files = paths.map((path) => {
127131
return (`${config.root}/${path}`).replace(/\/+/g, '/')

‎packages/runner/src/run.ts

+7-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import limit from 'p-limit'
22
import { getSafeTimers, shuffle } from '@vitest/utils'
33
import { processError } from '@vitest/utils/error'
4+
import type { DiffOptions } from '@vitest/utils/diff'
45
import type { VitestRunner } from './types/runner'
56
import type { File, HookCleanupCallback, HookListener, SequenceHooks, Suite, SuiteHooks, Task, TaskMeta, TaskResult, TaskResultPack, TaskState, Test } from './types'
67
import { partitionSuiteChildren } from './utils/suite'
@@ -173,7 +174,7 @@ export async function runTest(test: Test, runner: VitestRunner) {
173174
}
174175
}
175176
catch (e) {
176-
failTask(test.result, e)
177+
failTask(test.result, e, runner.config.diffOptions)
177178
}
178179

179180
// skipped with new PendingError
@@ -189,7 +190,7 @@ export async function runTest(test: Test, runner: VitestRunner) {
189190
await callCleanupHooks(beforeEachCleanups)
190191
}
191192
catch (e) {
192-
failTask(test.result, e)
193+
failTask(test.result, e, runner.config.diffOptions)
193194
}
194195

195196
if (test.result.state === 'pass')
@@ -233,7 +234,7 @@ export async function runTest(test: Test, runner: VitestRunner) {
233234
updateTask(test, runner)
234235
}
235236

236-
function failTask(result: TaskResult, err: unknown) {
237+
function failTask(result: TaskResult, err: unknown, diffOptions?: DiffOptions) {
237238
if (err instanceof PendingError) {
238239
result.state = 'skip'
239240
return
@@ -244,7 +245,7 @@ function failTask(result: TaskResult, err: unknown) {
244245
? err
245246
: [err]
246247
for (const e of errors) {
247-
const error = processError(e)
248+
const error = processError(e, diffOptions)
248249
result.error ??= error
249250
result.errors ??= []
250251
result.errors.push(error)
@@ -316,15 +317,15 @@ export async function runSuite(suite: Suite, runner: VitestRunner) {
316317
}
317318
}
318319
catch (e) {
319-
failTask(suite.result, e)
320+
failTask(suite.result, e, runner.config.diffOptions)
320321
}
321322

322323
try {
323324
await callSuiteHook(suite, suite, 'afterAll', runner, [suite])
324325
await callCleanupHooks(beforeAllCleanups)
325326
}
326327
catch (e) {
327-
failTask(suite.result, e)
328+
failTask(suite.result, e, runner.config.diffOptions)
328329
}
329330

330331
if (suite.mode === 'run') {

‎packages/runner/src/types/runner.ts

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { DiffOptions } from '@vitest/utils/diff'
12
import type { File, SequenceHooks, SequenceSetupFiles, Suite, TaskResultPack, Test, TestContext } from './tasks'
23

34
export interface VitestRunnerConfig {
@@ -21,6 +22,7 @@ export interface VitestRunnerConfig {
2122
testTimeout: number
2223
hookTimeout: number
2324
retry: number
25+
diffOptions?: DiffOptions
2426
}
2527

2628
export type VitestRunnerImportSource = 'collect' | 'setup'

‎packages/utils/src/error.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { diff } from './diff'
1+
import { type DiffOptions, diff } from './diff'
22
import { format } from './display'
33
import { deepClone, getOwnProperties, getType } from './helpers'
44
import { stringify } from './stringify'
@@ -86,7 +86,7 @@ function normalizeErrorMessage(message: string) {
8686
return message.replace(/__vite_ssr_import_\d+__\./g, '')
8787
}
8888

89-
export function processError(err: any) {
89+
export function processError(err: any, diffOptions?: DiffOptions) {
9090
if (!err || typeof err !== 'object')
9191
return { message: err }
9292
// stack is not serialized in worker communication
@@ -101,7 +101,7 @@ export function processError(err: any) {
101101
const clonedExpected = deepClone(err.expected, { forceWritable: true })
102102

103103
const { replacedActual, replacedExpected } = replaceAsymmetricMatcher(clonedActual, clonedExpected)
104-
err.diff = diff(replacedExpected, replacedActual)
104+
err.diff = diff(replacedExpected, replacedActual, diffOptions)
105105
}
106106

107107
if (typeof err.expected !== 'string')

‎packages/vitest/src/browser.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
export { startTests } from '@vitest/runner'
2-
export { setupCommonEnv } from './runtime/setup.common'
2+
export { setupCommonEnv, loadDiffConfig } from './runtime/setup.common'
33
export { takeCoverageInsideWorker, stopCoverageInsideWorker, getCoverageProvider, startCoverageInsideWorker } from './integrations/coverage'

‎packages/vitest/src/node/cli.ts

+1
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ cli
5151
.option('--test-timeout <time>', 'Default timeout of a test in milliseconds (default: 5000)')
5252
.option('--bail <number>', 'Stop test execution when given number of tests have failed', { default: 0 })
5353
.option('--retry <times>', 'Retry the test specific number of times if it fails', { default: 0 })
54+
.option('--diff <path>', 'Path to a diff config that will be used to generate diff interface')
5455
.help()
5556

5657
cli

‎packages/vitest/src/node/config.ts

+7
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,13 @@ export function resolveConfig(
289289
...resolved.setupFiles,
290290
]
291291

292+
if (resolved.diff) {
293+
resolved.diff = normalize(
294+
resolveModule(resolved.diff, { paths: [resolved.root] })
295+
?? resolve(resolved.root, resolved.diff))
296+
resolved.forceRerunTriggers.push(resolved.diff)
297+
}
298+
292299
// the server has been created, we don't need to override vite.server options
293300
resolved.api = resolveApiServerConfig(options)
294301

‎packages/vitest/src/runtime/entry.ts

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export async function run(files: string[], config: ResolvedConfig, environment:
2020
setupChaiConfig(config.chaiConfig)
2121

2222
const runner = await resolveTestRunner(config, executor)
23+
2324
workerState.onCancel.then(reason => runner.onCancel?.(reason))
2425

2526
workerState.durations.prepare = performance.now() - workerState.durations.prepare

‎packages/vitest/src/runtime/runners/index.ts

+3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { distDir } from '../../paths'
66
import { getWorkerState } from '../../utils/global'
77
import { rpc } from '../rpc'
88
import { takeCoverageInsideWorker } from '../../integrations/coverage'
9+
import { loadDiffConfig } from '../setup.common'
910

1011
const runnersFile = resolve(distDir, 'runners.js')
1112

@@ -37,6 +38,8 @@ export async function resolveTestRunner(config: ResolvedConfig, executor: Vitest
3738
if (!testRunner.importFile)
3839
throw new Error('Runner must implement "importFile" method.')
3940

41+
testRunner.config.diffOptions = await loadDiffConfig(config, executor)
42+
4043
// patch some methods, so custom runners don't need to call RPC
4144
const originalOnTaskUpdate = testRunner.onTaskUpdate
4245
testRunner.onTaskUpdate = async (task) => {

‎packages/vitest/src/runtime/setup.common.ts

+14
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { setSafeTimers } from '@vitest/utils'
22
import { resetRunOnceCounter } from '../integrations/run-once'
33
import type { ResolvedConfig } from '../types'
4+
import type { DiffOptions } from '../types/matcher-utils'
5+
import type { VitestExecutor } from './execute'
46

57
let globalSetup = false
68
export async function setupCommonEnv(config: ResolvedConfig) {
@@ -21,3 +23,15 @@ function setupDefines(defines: Record<string, any>) {
2123
for (const key in defines)
2224
(globalThis as any)[key] = defines[key]
2325
}
26+
27+
export async function loadDiffConfig(config: ResolvedConfig, executor: VitestExecutor) {
28+
if (typeof config.diff !== 'string')
29+
return
30+
31+
const diffModule = await executor.executeId(config.diff)
32+
33+
if (diffModule && typeof diffModule.default === 'object' && diffModule.default != null)
34+
return diffModule.default as DiffOptions
35+
else
36+
throw new Error(`invalid diff config file ${config.diff}. Must have a default export with config object`)
37+
}

‎packages/vitest/src/types/config.ts

+5
Original file line numberDiff line numberDiff line change
@@ -539,6 +539,11 @@ export interface InlineConfig {
539539
*/
540540
snapshotFormat?: PrettyFormatOptions
541541

542+
/**
543+
* Path to a module which has a default export of diff config.
544+
*/
545+
diff?: string
546+
542547
/**
543548
* Resolve custom snapshot path
544549
*/

‎packages/vitest/src/types/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export type * from './worker'
1313
export type * from './general'
1414
export type * from './coverage'
1515
export type * from './benchmark'
16+
export type { DiffOptions } from '@vitest/utils/diff'
1617
export type {
1718
EnhancedSpy,
1819
MockedFunction,

‎test/browser/custom-diff-config.ts

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export default {
2+
aAnnotation: 'Expected to be',
3+
bAnnotation: 'But got',
4+
}

‎test/browser/specs/runner.test.mjs

+2
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ await test('tests are actually running', async () => {
3434
await test('correctly prints error', () => {
3535
assert.match(stderr, /expected 1 to be 2/, 'prints failing error')
3636
assert.match(stderr, /- 2\s+\+ 1/, 'prints failing diff')
37+
assert.match(stderr, /Expected to be/, 'prints \`Expected to be\`')
38+
assert.match(stderr, /But got/, 'prints \`But got\`')
3739
})
3840

3941
await test('logs are redirected to stdout', async () => {

‎test/browser/vitest.config.mts

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export default defineConfig({
2020
},
2121
open: false,
2222
isolate: false,
23+
diff: './custom-diff-config.ts',
2324
outputFile: './browser.json',
2425
reporters: ['json', {
2526
onInit: noop,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { expect, test } from 'vitest'
2+
3+
test('', () => {
4+
expect({ foo: 1 }).toMatchInlineSnapshot('xxx')
5+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export default {
2+
aAnnotation: 'Expected to be',
3+
bAnnotation: 'But got',
4+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const diffOptions = {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { expect, test } from 'vitest'
2+
import { resolve } from 'pathe'
3+
import { runVitest } from '../../test-utils'
4+
5+
test('custom diff config', async () => {
6+
const filename = resolve('./fixtures/custom-diff-config.test.ts')
7+
const diff = resolve('./fixtures/custom-diff-config.ts')
8+
const { stderr } = await runVitest({ root: './fixtures', diff }, [filename])
9+
10+
expect(stderr).toBeTruthy()
11+
expect(stderr).toContain('Expected to be')
12+
expect(stderr).toContain('But got')
13+
})
14+
15+
test('invalid diff config file', async () => {
16+
const filename = resolve('./fixtures/custom-diff-config.test.ts')
17+
const diff = resolve('./fixtures/invalid-diff-config.ts')
18+
const { stderr } = await runVitest({ root: './fixtures', diff }, [filename])
19+
20+
expect(stderr).toBeTruthy()
21+
expect(stderr).toContain('invalid diff config file')
22+
expect(stderr).toContain('Must have a default export with config object')
23+
})

0 commit comments

Comments
 (0)
Please sign in to comment.