Skip to content

Commit 446308d

Browse files
authored
feat!: use "concordance" package to display diff instead of using custom diff (#2828)
1 parent 287dc20 commit 446308d

29 files changed

+333
-437
lines changed

docs/config/index.md

+18-52
Original file line numberDiff line numberDiff line change
@@ -449,58 +449,24 @@ Custom reporters for output. Reporters can be [a Reporter instance](https://gith
449449
- `'hanging-process'` - displays a list of hanging processes, if Vitest cannot exit process safely. This might be a heavy operation, enable it only if Vitest consistently cannot exit process
450450
- path of a custom reporter (e.g. `'./path/to/reporter.ts'`, `'@scope/reporter'`)
451451

452-
### outputTruncateLength
453-
454-
- **Type:** `number`
455-
- **Default:** `stdout.columns || 80`
456-
- **CLI:** `--outputTruncateLength=<length>`, `--output-truncate-length=<length>`
457-
458-
Truncate the size of diff line up to `stdout.columns` or `80` number of characters. You may wish to tune this, depending on your terminal window width. Vitest includes `+-` characters and spaces for this. For example, you might see this diff, if you set this to `6`:
459-
460-
```diff
461-
// actual line: "Text that seems correct"
462-
- Text...
463-
+ Test...
464-
```
465-
466-
### outputDiffLines
467-
468-
- **Type:** `number`
469-
- **Default:** `15`
470-
- **CLI:** `--outputDiffLines=<lines>`, `--output-diff-lines=<lines>`
471-
472-
Limit the number of single output diff lines up to `15`. Vitest counts all `+-` lines when determining when to stop. For example, you might see diff like this, if you set this property to `3`:
473-
474-
```diff
475-
- test: 1,
476-
+ test: 2,
477-
- obj: '1',
478-
...
479-
- test2: 1,
480-
+ test2: 1,
481-
- obj2: '2',
482-
...
483-
```
484-
485-
### outputDiffMaxLines
486-
487-
- **Type:** `number`
488-
- **Default:** `50`
489-
- **CLI:** `--outputDiffMaxLines=<lines>`, `--output-diff-max-lines=<lines>`
490-
- **Version:** Since Vitest 0.26.0
491-
492-
The maximum number of lines to display in diff window. Beware that if you have a large object with many small diffs, you might not see all of them at once.
493-
494-
### outputDiffMaxSize
495-
496-
- **Type:** `number`
497-
- **Default:** `10000`
498-
- **CLI:** `--outputDiffMaxSize=<length>`, `--output-diff-max-size=<length>`
499-
- **Version:** Since Vitest 0.26.0
500-
501-
The maximum length of the stringified object before the diff happens. Vitest tries to stringify an object before doing a diff, but if the object is too large, it will reduce the depth of the object to fit within this limit. Because of this, if the object is too big or nested, you might not see the diff.
502-
503-
Increasing this limit can increase the duration of diffing.
452+
### outputDiffLines
453+
454+
- **Type:** `number`
455+
- **Default:** `15`
456+
- **CLI:** `--outputDiffLines=<lines>`, `--output-diff-lines=<lines>`
457+
458+
Limit the number of single output diff lines up to `15`. Vitest counts all `+-` lines when determining when to stop. For example, you might see diff like this, if you set this property to `3`:
459+
460+
```diff
461+
- test: 1,
462+
+ test: 2,
463+
- obj: '1',
464+
...
465+
- test2: 1,
466+
+ test2: 1,
467+
- obj2: '2',
468+
...
469+
```
504470

505471
### outputFile
506472

docs/guide/cli.md

-3
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,6 @@ Run only [benchmark](https://vitest.dev/guide/features.html#benchmarking-experim
7373
| `--silent` | Silent console output from tests |
7474
| `--isolate` | Isolate environment for each test file (default: `true`) |
7575
| `--reporter <name>` | Select reporter: `default`, `verbose`, `dot`, `junit`, `json`, or a path to a custom reporter |
76-
| `--outputDiffMaxSize <length>` | Object diff output max size (default: 10000) |
77-
| `--outputDiffMaxLines <lines>` | Max lines in diff output window (default: 50) |
78-
| `--outputTruncateLength <length>` | Truncate output diff lines up to `<length>` number of characters. |
7976
| `--outputDiffLines <lines>` | Limit number of output diff lines up to `<lines>`. |
8077
| `--outputFile <filename/-s>` | Write test results to a file when the `--reporter=json` or `--reporter=junit` option is also specified <br /> Via [cac's dot notation] you can specify individual outputs for multiple reporters |
8178
| `--coverage` | Enable coverage report |

packages/expect/src/jest-matcher-utils.ts

+1-5
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,7 @@ export function getMatcherUtils() {
104104

105105
// TODO: do something with options
106106
export function diff(a: any, b: any, options?: DiffOptions) {
107-
const c = getColors()
108-
return unifiedDiff(stringify(b), stringify(a), {
109-
colorDim: c.dim,
110-
colorSuccess: c.green,
111-
colorError: c.red,
107+
return unifiedDiff(b, a, {
112108
showLegend: options?.showLegend,
113109
})
114110
}

packages/runner/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
},
4040
"dependencies": {
4141
"@vitest/utils": "workspace:*",
42+
"concordance": "^5.0.4",
4243
"p-limit": "^4.0.0",
4344
"pathe": "^1.1.0"
4445
}

packages/runner/rollup.config.js

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const external = [
88
...builtinModules,
99
...Object.keys(pkg.dependencies || {}),
1010
...Object.keys(pkg.peerDependencies || {}),
11+
'@vitest/utils/diff',
1112
]
1213

1314
const entries = {

packages/runner/src/run.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -150,15 +150,15 @@ export async function runTest(test: Test, runner: VitestRunner) {
150150
test.result.state = 'pass'
151151
}
152152
catch (e) {
153-
failTask(test.result, e)
153+
failTask(test.result, e, runner)
154154
}
155155

156156
try {
157157
await callSuiteHook(test.suite, test, 'afterEach', runner, [test.context, test.suite])
158158
await callCleanupHooks(beforeEachCleanups)
159159
}
160160
catch (e) {
161-
failTask(test.result, e)
161+
failTask(test.result, e, runner)
162162
}
163163

164164
if (test.result.state === 'pass')
@@ -195,9 +195,9 @@ export async function runTest(test: Test, runner: VitestRunner) {
195195
updateTask(test, runner)
196196
}
197197

198-
function failTask(result: TaskResult, err: unknown) {
198+
function failTask(result: TaskResult, err: unknown, runner: VitestRunner) {
199199
result.state = 'fail'
200-
const error = processError(err)
200+
const error = processError(err, runner.config)
201201
result.error = error
202202
result.errors ??= []
203203
result.errors.push(error)
@@ -268,15 +268,15 @@ export async function runSuite(suite: Suite, runner: VitestRunner) {
268268
}
269269
}
270270
catch (e) {
271-
failTask(suite.result, e)
271+
failTask(suite.result, e, runner)
272272
}
273273

274274
try {
275275
await callSuiteHook(suite, suite, 'afterAll', runner, [suite])
276276
await callCleanupHooks(beforeAllCleanups)
277277
}
278278
catch (e) {
279-
failTask(suite.result, e)
279+
failTask(suite.result, e, runner)
280280
}
281281
}
282282

packages/runner/src/types/runner.ts

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export interface VitestRunnerConfig {
1313
hooks: SequenceHooks
1414
setupFiles: SequenceSetupFiles
1515
}
16+
outputDiffLines?: number
1617
maxConcurrency: number
1718
testTimeout: number
1819
hookTimeout: number

packages/runner/src/utils/error.ts

+8-11
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import { deepClone, format, getOwnProperties, getType, stringify } from '@vitest/utils'
2+
import type { DiffOptions } from '@vitest/utils/diff'
3+
import { unifiedDiff } from '@vitest/utils/diff'
24

35
export interface ParsedStack {
46
method: string
@@ -14,6 +16,7 @@ export interface ErrorWithDiff extends Error {
1416
stackStr?: string
1517
stacks?: ParsedStack[]
1618
showDiff?: boolean
19+
diff?: string
1720
actual?: any
1821
expected?: any
1922
operator?: string
@@ -102,11 +105,7 @@ function normalizeErrorMessage(message: string) {
102105
return message.replace(/__vite_ssr_import_\d+__\./g, '')
103106
}
104107

105-
interface ProcessErrorOptions {
106-
outputDiffMaxSize?: number
107-
}
108-
109-
export function processError(err: any, options: ProcessErrorOptions = {}) {
108+
export function processError(err: any, options: DiffOptions = {}) {
110109
if (!err || typeof err !== 'object')
111110
return err
112111
// stack is not serialized in worker communication
@@ -121,15 +120,13 @@ export function processError(err: any, options: ProcessErrorOptions = {}) {
121120

122121
const { replacedActual, replacedExpected } = replaceAsymmetricMatcher(clonedActual, clonedExpected)
123122

124-
err.actual = replacedActual
125-
err.expected = replacedExpected
126-
127-
const maxDiffSize = options.outputDiffMaxSize ?? 10000
123+
if (err.showDiff || (err.showDiff === undefined && err.expected !== undefined && err.actual !== undefined))
124+
err.diff = unifiedDiff(replacedActual, replacedExpected, options)
128125

129126
if (typeof err.expected !== 'string')
130-
err.expected = stringify(err.expected, 10, { maxLength: maxDiffSize })
127+
err.expected = stringify(err.expected, 10)
131128
if (typeof err.actual !== 'string')
132-
err.actual = stringify(err.actual, 10, { maxLength: maxDiffSize })
129+
err.actual = stringify(err.actual, 10)
133130

134131
// some Error implementations don't allow rewriting message
135132
try {

packages/ui/client/components/views/ViewReportError.vue

+1-3
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,7 @@ const isDiffShowable = computed(() => {
2020
})
2121
2222
function diff() {
23-
return unifiedDiff(props.error.actual, props.error.expected, {
24-
outputTruncateLength: 80,
25-
})
23+
return unifiedDiff(props.error.actual, props.error.expected)
2624
}
2725
</script>
2826

+1-96
Original file line numberDiff line numberDiff line change
@@ -1,96 +1 @@
1-
import * as diff from 'diff'
2-
3-
export interface DiffOptions {
4-
outputTruncateLength?: number
5-
outputDiffLines?: number
6-
showLegend?: boolean
7-
}
8-
9-
function formatLine(line: string, maxWidth: number) {
10-
return line.slice(0, maxWidth) + (line.length > maxWidth ? '…' : '')
11-
}
12-
13-
export function unifiedDiff(actual: string, expected: string, options: DiffOptions = {}) {
14-
if (actual === expected)
15-
return ''
16-
17-
const { outputTruncateLength = 80, outputDiffLines, showLegend = true } = options
18-
19-
const indent = ' '
20-
const diffLimit = outputDiffLines || 15
21-
22-
const counts = {
23-
'+': 0,
24-
'-': 0,
25-
}
26-
let previousState: '-' | '+' | null = null
27-
let previousCount = 0
28-
function preprocess(line: string) {
29-
if (!line || line.match(/\\ No newline/))
30-
return
31-
32-
const char = line[0] as '+' | '-'
33-
if ('-+'.includes(char)) {
34-
if (previousState !== char) {
35-
previousState = char
36-
previousCount = 0
37-
}
38-
previousCount++
39-
counts[char]++
40-
if (previousCount === diffLimit)
41-
return `${char} ...`
42-
else if (previousCount > diffLimit)
43-
return
44-
}
45-
return line
46-
}
47-
48-
const msg = diff.createPatch('string', expected, actual)
49-
const lines = msg.split('\n').slice(5).map(preprocess).filter(Boolean) as string[]
50-
const isCompact = counts['+'] === 1 && counts['-'] === 1 && lines.length === 2
51-
52-
let formatted = lines.map((line: string) => {
53-
line = line.replace(/\\"/g, '"')
54-
if (line[0] === '-') {
55-
line = formatLine(line.slice(1), outputTruncateLength)
56-
if (isCompact)
57-
return line
58-
return `- ${formatLine(line, outputTruncateLength)}`
59-
}
60-
if (line[0] === '+') {
61-
line = formatLine(line.slice(1), outputTruncateLength)
62-
if (isCompact)
63-
return line
64-
return `+ ${formatLine(line, outputTruncateLength)}`
65-
}
66-
if (line.match(/@@/))
67-
return '--'
68-
return ` ${line}`
69-
})
70-
71-
if (showLegend) {
72-
// Compact mode
73-
if (isCompact) {
74-
formatted = [
75-
`- Expected ${formatted[0]}`,
76-
`+ Received ${formatted[1]}`,
77-
]
78-
}
79-
else {
80-
if (formatted[0].includes('"'))
81-
formatted[0] = formatted[0].replace('"', '')
82-
83-
const last = formatted.length - 1
84-
if (formatted[last].endsWith('"'))
85-
formatted[last] = formatted[last].slice(0, formatted[last].length - 1)
86-
87-
formatted.unshift(
88-
`- Expected - ${counts['-']}`,
89-
`+ Received + ${counts['+']}`,
90-
'',
91-
)
92-
}
93-
}
94-
95-
return formatted.map(i => indent + i).join('\n')
96-
}
1+
export { unifiedDiff } from '@vitest/utils/diff'

packages/ui/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
"prepublishOnly": "pnpm build"
4040
},
4141
"dependencies": {
42+
"@vitest/utils": "workspace:*",
4243
"fast-glob": "^3.2.12",
4344
"flatted": "^3.2.7",
4445
"pathe": "^1.1.0",
@@ -64,7 +65,6 @@
6465
"codemirror-theme-vars": "^0.1.1",
6566
"cypress": "^12.3.0",
6667
"d3-graph-controller": "^2.5.1",
67-
"diff": "^5.1.0",
6868
"floating-vue": "^2.0.0-y.0",
6969
"rollup": "^2.79.1",
7070
"splitpanes": "^3.1.5",

packages/utils/package.json

+1-5
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,8 @@
3737
"prepublishOnly": "pnpm build"
3838
},
3939
"dependencies": {
40-
"cli-truncate": "^3.1.0",
41-
"diff": "^5.1.0",
40+
"concordance": "^5.0.4",
4241
"loupe": "^2.3.6",
4342
"pretty-format": "^27.5.1"
44-
},
45-
"devDependencies": {
46-
"@types/diff": "^5.0.2"
4743
}
4844
}

packages/utils/src/colors.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,11 @@ const colorsMap = {
2929

3030
type ColorName = keyof typeof colorsMap
3131
type ColorsMethods = {
32-
[Key in ColorName]: (input: unknown) => string
32+
[Key in ColorName]: {
33+
(input: unknown): string
34+
open: string
35+
close: string
36+
}
3337
}
3438

3539
type Colors = ColorsMethods & {

0 commit comments

Comments
 (0)