···16411641})
16421642```
1643164316441644+### diff
16451645+16461646+- **Type:** `string`
16471647+- **CLI:** `--diff=<value>`
16481648+- **Version:** Since Vitest 0.34.5
16491649+16501650+Path to a diff config that will be used to generate diff interface. Useful if you want to customize diff display.
16511651+16521652+:::code-group
16531653+```ts [vitest.diff.ts]
16541654+import type { DiffOptions } from 'vitest'
16551655+import c from 'picocolors'
16561656+16571657+export default {
16581658+ aIndicator: c.bold('--'),
16591659+ bIndicator: c.bold('++'),
16601660+ omitAnnotationLines: true,
16611661+} satisfies DiffOptions
16621662+```
16631663+16641664+```ts [vitest.config.js]
16651665+import { defineConfig } from 'vitest/config'
16661666+16671667+export default defineConfig({
16681668+ test: {
16691669+ diff: './vitest.diff.ts'
16701670+ }
16711671+})
16721672+```
16731673+:::
+4
packages/browser/src/client/main.ts
···11import { createClient } from '@vitest/ws-client'
22import type { ResolvedConfig } from 'vitest'
33import type { CancelReason, VitestRunner } from '@vitest/runner'
44+import type { VitestExecutor } from 'vitest/src/runtime/execute'
45import { createBrowserRunner } from './runner'
56import { importId } from './utils'
67import { setupConsoleLogSpy } from './logger'
···101102 const {
102103 startTests,
103104 setupCommonEnv,
105105+ loadDiffConfig,
104106 takeCoverageInsideWorker,
105107 } = await importId('vitest/browser') as typeof import('vitest/browser')
106108···122124 config.snapshotOptions.snapshotEnvironment = new BrowserSnapshotEnvironment()
123125124126 try {
127127+ runner.config.diffOptions = await loadDiffConfig(config, executor as VitestExecutor)
128128+125129 await setupCommonEnv(config)
126130 const files = paths.map((path) => {
127131 return (`${config.root}/${path}`).replace(/\/+/g, '/')
+7-6
packages/runner/src/run.ts
···11import limit from 'p-limit'
22import { getSafeTimers, shuffle } from '@vitest/utils'
33import { processError } from '@vitest/utils/error'
44+import type { DiffOptions } from '@vitest/utils/diff'
45import type { VitestRunner } from './types/runner'
56import type { File, HookCleanupCallback, HookListener, SequenceHooks, Suite, SuiteHooks, Task, TaskMeta, TaskResult, TaskResultPack, TaskState, Test } from './types'
67import { partitionSuiteChildren } from './utils/suite'
···173174 }
174175 }
175176 catch (e) {
176176- failTask(test.result, e)
177177+ failTask(test.result, e, runner.config.diffOptions)
177178 }
178179179180 // skipped with new PendingError
···189190 await callCleanupHooks(beforeEachCleanups)
190191 }
191192 catch (e) {
192192- failTask(test.result, e)
193193+ failTask(test.result, e, runner.config.diffOptions)
193194 }
194195195196 if (test.result.state === 'pass')
···233234 updateTask(test, runner)
234235}
235236236236-function failTask(result: TaskResult, err: unknown) {
237237+function failTask(result: TaskResult, err: unknown, diffOptions?: DiffOptions) {
237238 if (err instanceof PendingError) {
238239 result.state = 'skip'
239240 return
···244245 ? err
245246 : [err]
246247 for (const e of errors) {
247247- const error = processError(e)
248248+ const error = processError(e, diffOptions)
248249 result.error ??= error
249250 result.errors ??= []
250251 result.errors.push(error)
···316317 }
317318 }
318319 catch (e) {
319319- failTask(suite.result, e)
320320+ failTask(suite.result, e, runner.config.diffOptions)
320321 }
321322322323 try {
···324325 await callCleanupHooks(beforeAllCleanups)
325326 }
326327 catch (e) {
327327- failTask(suite.result, e)
328328+ failTask(suite.result, e, runner.config.diffOptions)
328329 }
329330330331 if (suite.mode === 'run') {
+2
packages/runner/src/types/runner.ts
···11+import type { DiffOptions } from '@vitest/utils/diff'
12import type { File, SequenceHooks, SequenceSetupFiles, Suite, TaskResultPack, Test, TestContext } from './tasks'
2334export interface VitestRunnerConfig {
···2122 testTimeout: number
2223 hookTimeout: number
2324 retry: number
2525+ diffOptions?: DiffOptions
2426}
25272628export type VitestRunnerImportSource = 'collect' | 'setup'
+3-3
packages/utils/src/error.ts
···11-import { diff } from './diff'
11+import { type DiffOptions, diff } from './diff'
22import { format } from './display'
33import { deepClone, getOwnProperties, getType } from './helpers'
44import { stringify } from './stringify'
···8686 return message.replace(/__vite_ssr_import_\d+__\./g, '')
8787}
88888989-export function processError(err: any) {
8989+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
···101101 const clonedExpected = deepClone(err.expected, { forceWritable: true })
102102103103 const { replacedActual, replacedExpected } = replaceAsymmetricMatcher(clonedActual, clonedExpected)
104104- err.diff = diff(replacedExpected, replacedActual)
104104+ err.diff = diff(replacedExpected, replacedActual, diffOptions)
105105 }
106106107107 if (typeof err.expected !== 'string')
+1-1
packages/vitest/src/browser.ts
···11export { startTests } from '@vitest/runner'
22-export { setupCommonEnv } from './runtime/setup.common'
22+export { setupCommonEnv, loadDiffConfig } from './runtime/setup.common'
33export { takeCoverageInsideWorker, stopCoverageInsideWorker, getCoverageProvider, startCoverageInsideWorker } from './integrations/coverage'
+1
packages/vitest/src/node/cli.ts
···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 })
5454+ .option('--diff <path>', 'Path to a diff config that will be used to generate diff interface')
5455 .help()
55565657cli
+7
packages/vitest/src/node/config.ts
···289289 ...resolved.setupFiles,
290290 ]
291291292292+ if (resolved.diff) {
293293+ resolved.diff = normalize(
294294+ resolveModule(resolved.diff, { paths: [resolved.root] })
295295+ ?? resolve(resolved.root, resolved.diff))
296296+ resolved.forceRerunTriggers.push(resolved.diff)
297297+ }
298298+292299 // the server has been created, we don't need to override vite.server options
293300 resolved.api = resolveApiServerConfig(options)
294301
···66import { getWorkerState } from '../../utils/global'
77import { rpc } from '../rpc'
88import { takeCoverageInsideWorker } from '../../integrations/coverage'
99+import { loadDiffConfig } from '../setup.common'
9101011const runnersFile = resolve(distDir, 'runners.js')
1112···36373738 if (!testRunner.importFile)
3839 throw new Error('Runner must implement "importFile" method.')
4040+4141+ testRunner.config.diffOptions = await loadDiffConfig(config, executor)
39424043 // patch some methods, so custom runners don't need to call RPC
4144 const originalOnTaskUpdate = testRunner.onTaskUpdate
+14
packages/vitest/src/runtime/setup.common.ts
···11import { setSafeTimers } from '@vitest/utils'
22import { resetRunOnceCounter } from '../integrations/run-once'
33import type { ResolvedConfig } from '../types'
44+import type { DiffOptions } from '../types/matcher-utils'
55+import type { VitestExecutor } from './execute'
4657let globalSetup = false
68export async function setupCommonEnv(config: ResolvedConfig) {
···2123 for (const key in defines)
2224 (globalThis as any)[key] = defines[key]
2325}
2626+2727+export async function loadDiffConfig(config: ResolvedConfig, executor: VitestExecutor) {
2828+ if (typeof config.diff !== 'string')
2929+ return
3030+3131+ const diffModule = await executor.executeId(config.diff)
3232+3333+ if (diffModule && typeof diffModule.default === 'object' && diffModule.default != null)
3434+ return diffModule.default as DiffOptions
3535+ else
3636+ throw new Error(`invalid diff config file ${config.diff}. Must have a default export with config object`)
3737+}
+5
packages/vitest/src/types/config.ts
···540540 snapshotFormat?: PrettyFormatOptions
541541542542 /**
543543+ * Path to a module which has a default export of diff config.
544544+ */
545545+ diff?: string
546546+547547+ /**
543548 * Resolve custom snapshot path
544549 */
545550 resolveSnapshotPath?: (path: string, extension: string) => string
+1
packages/vitest/src/types/index.ts
···1313export type * from './general'
1414export type * from './coverage'
1515export type * from './benchmark'
1616+export type { DiffOptions } from '@vitest/utils/diff'
1617export type {
1718 EnhancedSpy,
1819 MockedFunction,