Skip to content

Commit

Permalink
feat!: use "concordance" package to display diff instead of using cus…
Browse files Browse the repository at this point in the history
…tom diff (#2828)
  • Loading branch information
sheremet-va committed Mar 29, 2023
1 parent 287dc20 commit 446308d
Show file tree
Hide file tree
Showing 29 changed files with 333 additions and 437 deletions.
70 changes: 18 additions & 52 deletions docs/config/index.md
Expand Up @@ -449,58 +449,24 @@ Custom reporters for output. Reporters can be [a Reporter instance](https://gith
- `'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
- path of a custom reporter (e.g. `'./path/to/reporter.ts'`, `'@scope/reporter'`)

### outputTruncateLength

- **Type:** `number`
- **Default:** `stdout.columns || 80`
- **CLI:** `--outputTruncateLength=<length>`, `--output-truncate-length=<length>`

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`:

```diff
// actual line: "Text that seems correct"
- Text...
+ Test...
```

### outputDiffLines

- **Type:** `number`
- **Default:** `15`
- **CLI:** `--outputDiffLines=<lines>`, `--output-diff-lines=<lines>`

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`:

```diff
- test: 1,
+ test: 2,
- obj: '1',
...
- test2: 1,
+ test2: 1,
- obj2: '2',
...
```

### outputDiffMaxLines

- **Type:** `number`
- **Default:** `50`
- **CLI:** `--outputDiffMaxLines=<lines>`, `--output-diff-max-lines=<lines>`
- **Version:** Since Vitest 0.26.0

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.

### outputDiffMaxSize

- **Type:** `number`
- **Default:** `10000`
- **CLI:** `--outputDiffMaxSize=<length>`, `--output-diff-max-size=<length>`
- **Version:** Since Vitest 0.26.0

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.

Increasing this limit can increase the duration of diffing.
### outputDiffLines

- **Type:** `number`
- **Default:** `15`
- **CLI:** `--outputDiffLines=<lines>`, `--output-diff-lines=<lines>`

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`:

```diff
- test: 1,
+ test: 2,
- obj: '1',
...
- test2: 1,
+ test2: 1,
- obj2: '2',
...
```

### outputFile

Expand Down
3 changes: 0 additions & 3 deletions docs/guide/cli.md
Expand Up @@ -73,9 +73,6 @@ Run only [benchmark](https://vitest.dev/guide/features.html#benchmarking-experim
| `--silent` | Silent console output from tests |
| `--isolate` | Isolate environment for each test file (default: `true`) |
| `--reporter <name>` | Select reporter: `default`, `verbose`, `dot`, `junit`, `json`, or a path to a custom reporter |
| `--outputDiffMaxSize <length>` | Object diff output max size (default: 10000) |
| `--outputDiffMaxLines <lines>` | Max lines in diff output window (default: 50) |
| `--outputTruncateLength <length>` | Truncate output diff lines up to `<length>` number of characters. |
| `--outputDiffLines <lines>` | Limit number of output diff lines up to `<lines>`. |
| `--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 |
| `--coverage` | Enable coverage report |
Expand Down
6 changes: 1 addition & 5 deletions packages/expect/src/jest-matcher-utils.ts
Expand Up @@ -104,11 +104,7 @@ export function getMatcherUtils() {

// TODO: do something with options
export function diff(a: any, b: any, options?: DiffOptions) {
const c = getColors()
return unifiedDiff(stringify(b), stringify(a), {
colorDim: c.dim,
colorSuccess: c.green,
colorError: c.red,
return unifiedDiff(b, a, {
showLegend: options?.showLegend,
})
}
1 change: 1 addition & 0 deletions packages/runner/package.json
Expand Up @@ -39,6 +39,7 @@
},
"dependencies": {
"@vitest/utils": "workspace:*",
"concordance": "^5.0.4",
"p-limit": "^4.0.0",
"pathe": "^1.1.0"
}
Expand Down
1 change: 1 addition & 0 deletions packages/runner/rollup.config.js
Expand Up @@ -8,6 +8,7 @@ const external = [
...builtinModules,
...Object.keys(pkg.dependencies || {}),
...Object.keys(pkg.peerDependencies || {}),
'@vitest/utils/diff',
]

const entries = {
Expand Down
12 changes: 6 additions & 6 deletions packages/runner/src/run.ts
Expand Up @@ -150,15 +150,15 @@ export async function runTest(test: Test, runner: VitestRunner) {
test.result.state = 'pass'
}
catch (e) {
failTask(test.result, e)
failTask(test.result, e, runner)
}

try {
await callSuiteHook(test.suite, test, 'afterEach', runner, [test.context, test.suite])
await callCleanupHooks(beforeEachCleanups)
}
catch (e) {
failTask(test.result, e)
failTask(test.result, e, runner)
}

if (test.result.state === 'pass')
Expand Down Expand Up @@ -195,9 +195,9 @@ export async function runTest(test: Test, runner: VitestRunner) {
updateTask(test, runner)
}

function failTask(result: TaskResult, err: unknown) {
function failTask(result: TaskResult, err: unknown, runner: VitestRunner) {
result.state = 'fail'
const error = processError(err)
const error = processError(err, runner.config)
result.error = error
result.errors ??= []
result.errors.push(error)
Expand Down Expand Up @@ -268,15 +268,15 @@ export async function runSuite(suite: Suite, runner: VitestRunner) {
}
}
catch (e) {
failTask(suite.result, e)
failTask(suite.result, e, runner)
}

try {
await callSuiteHook(suite, suite, 'afterAll', runner, [suite])
await callCleanupHooks(beforeAllCleanups)
}
catch (e) {
failTask(suite.result, e)
failTask(suite.result, e, runner)
}
}

Expand Down
1 change: 1 addition & 0 deletions packages/runner/src/types/runner.ts
Expand Up @@ -13,6 +13,7 @@ export interface VitestRunnerConfig {
hooks: SequenceHooks
setupFiles: SequenceSetupFiles
}
outputDiffLines?: number
maxConcurrency: number
testTimeout: number
hookTimeout: number
Expand Down
19 changes: 8 additions & 11 deletions packages/runner/src/utils/error.ts
@@ -1,4 +1,6 @@
import { deepClone, format, getOwnProperties, getType, stringify } from '@vitest/utils'
import type { DiffOptions } from '@vitest/utils/diff'
import { unifiedDiff } from '@vitest/utils/diff'

export interface ParsedStack {
method: string
Expand All @@ -14,6 +16,7 @@ export interface ErrorWithDiff extends Error {
stackStr?: string
stacks?: ParsedStack[]
showDiff?: boolean
diff?: string
actual?: any
expected?: any
operator?: string
Expand Down Expand Up @@ -102,11 +105,7 @@ function normalizeErrorMessage(message: string) {
return message.replace(/__vite_ssr_import_\d+__\./g, '')
}

interface ProcessErrorOptions {
outputDiffMaxSize?: number
}

export function processError(err: any, options: ProcessErrorOptions = {}) {
export function processError(err: any, options: DiffOptions = {}) {
if (!err || typeof err !== 'object')
return err
// stack is not serialized in worker communication
Expand All @@ -121,15 +120,13 @@ export function processError(err: any, options: ProcessErrorOptions = {}) {

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

err.actual = replacedActual
err.expected = replacedExpected

const maxDiffSize = options.outputDiffMaxSize ?? 10000
if (err.showDiff || (err.showDiff === undefined && err.expected !== undefined && err.actual !== undefined))
err.diff = unifiedDiff(replacedActual, replacedExpected, options)

if (typeof err.expected !== 'string')
err.expected = stringify(err.expected, 10, { maxLength: maxDiffSize })
err.expected = stringify(err.expected, 10)
if (typeof err.actual !== 'string')
err.actual = stringify(err.actual, 10, { maxLength: maxDiffSize })
err.actual = stringify(err.actual, 10)

// some Error implementations don't allow rewriting message
try {
Expand Down
4 changes: 1 addition & 3 deletions packages/ui/client/components/views/ViewReportError.vue
Expand Up @@ -20,9 +20,7 @@ const isDiffShowable = computed(() => {
})
function diff() {
return unifiedDiff(props.error.actual, props.error.expected, {
outputTruncateLength: 80,
})
return unifiedDiff(props.error.actual, props.error.expected)
}
</script>

Expand Down
97 changes: 1 addition & 96 deletions packages/ui/client/composables/diff.ts
@@ -1,96 +1 @@
import * as diff from 'diff'

export interface DiffOptions {
outputTruncateLength?: number
outputDiffLines?: number
showLegend?: boolean
}

function formatLine(line: string, maxWidth: number) {
return line.slice(0, maxWidth) + (line.length > maxWidth ? '…' : '')
}

export function unifiedDiff(actual: string, expected: string, options: DiffOptions = {}) {
if (actual === expected)
return ''

const { outputTruncateLength = 80, outputDiffLines, showLegend = true } = options

const indent = ' '
const diffLimit = outputDiffLines || 15

const counts = {
'+': 0,
'-': 0,
}
let previousState: '-' | '+' | null = null
let previousCount = 0
function preprocess(line: string) {
if (!line || line.match(/\\ No newline/))
return

const char = line[0] as '+' | '-'
if ('-+'.includes(char)) {
if (previousState !== char) {
previousState = char
previousCount = 0
}
previousCount++
counts[char]++
if (previousCount === diffLimit)
return `${char} ...`
else if (previousCount > diffLimit)
return
}
return line
}

const msg = diff.createPatch('string', expected, actual)
const lines = msg.split('\n').slice(5).map(preprocess).filter(Boolean) as string[]
const isCompact = counts['+'] === 1 && counts['-'] === 1 && lines.length === 2

let formatted = lines.map((line: string) => {
line = line.replace(/\\"/g, '"')
if (line[0] === '-') {
line = formatLine(line.slice(1), outputTruncateLength)
if (isCompact)
return line
return `- ${formatLine(line, outputTruncateLength)}`
}
if (line[0] === '+') {
line = formatLine(line.slice(1), outputTruncateLength)
if (isCompact)
return line
return `+ ${formatLine(line, outputTruncateLength)}`
}
if (line.match(/@@/))
return '--'
return ` ${line}`
})

if (showLegend) {
// Compact mode
if (isCompact) {
formatted = [
`- Expected ${formatted[0]}`,
`+ Received ${formatted[1]}`,
]
}
else {
if (formatted[0].includes('"'))
formatted[0] = formatted[0].replace('"', '')

const last = formatted.length - 1
if (formatted[last].endsWith('"'))
formatted[last] = formatted[last].slice(0, formatted[last].length - 1)

formatted.unshift(
`- Expected - ${counts['-']}`,
`+ Received + ${counts['+']}`,
'',
)
}
}

return formatted.map(i => indent + i).join('\n')
}
export { unifiedDiff } from '@vitest/utils/diff'
2 changes: 1 addition & 1 deletion packages/ui/package.json
Expand Up @@ -39,6 +39,7 @@
"prepublishOnly": "pnpm build"
},
"dependencies": {
"@vitest/utils": "workspace:*",
"fast-glob": "^3.2.12",
"flatted": "^3.2.7",
"pathe": "^1.1.0",
Expand All @@ -64,7 +65,6 @@
"codemirror-theme-vars": "^0.1.1",
"cypress": "^12.3.0",
"d3-graph-controller": "^2.5.1",
"diff": "^5.1.0",
"floating-vue": "^2.0.0-y.0",
"rollup": "^2.79.1",
"splitpanes": "^3.1.5",
Expand Down
6 changes: 1 addition & 5 deletions packages/utils/package.json
Expand Up @@ -37,12 +37,8 @@
"prepublishOnly": "pnpm build"
},
"dependencies": {
"cli-truncate": "^3.1.0",
"diff": "^5.1.0",
"concordance": "^5.0.4",
"loupe": "^2.3.6",
"pretty-format": "^27.5.1"
},
"devDependencies": {
"@types/diff": "^5.0.2"
}
}
6 changes: 5 additions & 1 deletion packages/utils/src/colors.ts
Expand Up @@ -29,7 +29,11 @@ const colorsMap = {

type ColorName = keyof typeof colorsMap
type ColorsMethods = {
[Key in ColorName]: (input: unknown) => string
[Key in ColorName]: {
(input: unknown): string
open: string
close: string
}
}

type Colors = ColorsMethods & {
Expand Down

0 comments on commit 446308d

Please sign in to comment.