diff --git a/docs/config/index.md b/docs/config/index.md index ab9b3c54ba79..3bd1ce981718 100644 --- a/docs/config/index.md +++ b/docs/config/index.md @@ -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=`, `--output-truncate-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=`, `--output-diff-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=`, `--output-diff-max-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=`, `--output-diff-max-size=` -- **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=`, `--output-diff-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 diff --git a/docs/guide/cli.md b/docs/guide/cli.md index 50b48f147619..d79dd1cb28ec 100644 --- a/docs/guide/cli.md +++ b/docs/guide/cli.md @@ -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 ` | Select reporter: `default`, `verbose`, `dot`, `junit`, `json`, or a path to a custom reporter | -| `--outputDiffMaxSize ` | Object diff output max size (default: 10000) | -| `--outputDiffMaxLines ` | Max lines in diff output window (default: 50) | -| `--outputTruncateLength ` | Truncate output diff lines up to `` number of characters. | | `--outputDiffLines ` | Limit number of output diff lines up to ``. | | `--outputFile ` | Write test results to a file when the `--reporter=json` or `--reporter=junit` option is also specified
Via [cac's dot notation] you can specify individual outputs for multiple reporters | | `--coverage` | Enable coverage report | diff --git a/packages/expect/src/jest-matcher-utils.ts b/packages/expect/src/jest-matcher-utils.ts index c71c3302b111..f82719b15580 100644 --- a/packages/expect/src/jest-matcher-utils.ts +++ b/packages/expect/src/jest-matcher-utils.ts @@ -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, }) } diff --git a/packages/runner/package.json b/packages/runner/package.json index a6f1b0d17541..10756d75c65f 100644 --- a/packages/runner/package.json +++ b/packages/runner/package.json @@ -39,6 +39,7 @@ }, "dependencies": { "@vitest/utils": "workspace:*", + "concordance": "^5.0.4", "p-limit": "^4.0.0", "pathe": "^1.1.0" } diff --git a/packages/runner/rollup.config.js b/packages/runner/rollup.config.js index a8c6d7cff5d2..dac25663d89a 100644 --- a/packages/runner/rollup.config.js +++ b/packages/runner/rollup.config.js @@ -8,6 +8,7 @@ const external = [ ...builtinModules, ...Object.keys(pkg.dependencies || {}), ...Object.keys(pkg.peerDependencies || {}), + '@vitest/utils/diff', ] const entries = { diff --git a/packages/runner/src/run.ts b/packages/runner/src/run.ts index 205720cec14d..252514d07264 100644 --- a/packages/runner/src/run.ts +++ b/packages/runner/src/run.ts @@ -150,7 +150,7 @@ export async function runTest(test: Test, runner: VitestRunner) { test.result.state = 'pass' } catch (e) { - failTask(test.result, e) + failTask(test.result, e, runner) } try { @@ -158,7 +158,7 @@ export async function runTest(test: Test, runner: VitestRunner) { await callCleanupHooks(beforeEachCleanups) } catch (e) { - failTask(test.result, e) + failTask(test.result, e, runner) } if (test.result.state === 'pass') @@ -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) @@ -268,7 +268,7 @@ export async function runSuite(suite: Suite, runner: VitestRunner) { } } catch (e) { - failTask(suite.result, e) + failTask(suite.result, e, runner) } try { @@ -276,7 +276,7 @@ export async function runSuite(suite: Suite, runner: VitestRunner) { await callCleanupHooks(beforeAllCleanups) } catch (e) { - failTask(suite.result, e) + failTask(suite.result, e, runner) } } diff --git a/packages/runner/src/types/runner.ts b/packages/runner/src/types/runner.ts index 6511441cef1b..b8d6f6512601 100644 --- a/packages/runner/src/types/runner.ts +++ b/packages/runner/src/types/runner.ts @@ -13,6 +13,7 @@ export interface VitestRunnerConfig { hooks: SequenceHooks setupFiles: SequenceSetupFiles } + outputDiffLines?: number maxConcurrency: number testTimeout: number hookTimeout: number diff --git a/packages/runner/src/utils/error.ts b/packages/runner/src/utils/error.ts index 34647659cfdc..2916086e7578 100644 --- a/packages/runner/src/utils/error.ts +++ b/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 @@ -14,6 +16,7 @@ export interface ErrorWithDiff extends Error { stackStr?: string stacks?: ParsedStack[] showDiff?: boolean + diff?: string actual?: any expected?: any operator?: string @@ -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 @@ -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 { diff --git a/packages/ui/client/components/views/ViewReportError.vue b/packages/ui/client/components/views/ViewReportError.vue index 9ba393572392..39ae42a99365 100644 --- a/packages/ui/client/components/views/ViewReportError.vue +++ b/packages/ui/client/components/views/ViewReportError.vue @@ -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) } diff --git a/packages/ui/client/composables/diff.ts b/packages/ui/client/composables/diff.ts index a8b0792b2280..003dc6e39b5a 100644 --- a/packages/ui/client/composables/diff.ts +++ b/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' diff --git a/packages/ui/package.json b/packages/ui/package.json index 1569f9c60d9c..1073005e4066 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -39,6 +39,7 @@ "prepublishOnly": "pnpm build" }, "dependencies": { + "@vitest/utils": "workspace:*", "fast-glob": "^3.2.12", "flatted": "^3.2.7", "pathe": "^1.1.0", @@ -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", diff --git a/packages/utils/package.json b/packages/utils/package.json index 2f9ff753b4bf..ed3f01dc531b 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -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" } } diff --git a/packages/utils/src/colors.ts b/packages/utils/src/colors.ts index 94047a0e92e8..17172879f052 100644 --- a/packages/utils/src/colors.ts +++ b/packages/utils/src/colors.ts @@ -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 & { diff --git a/packages/utils/src/descriptors.ts b/packages/utils/src/descriptors.ts new file mode 100644 index 000000000000..a2c27699c61e --- /dev/null +++ b/packages/utils/src/descriptors.ts @@ -0,0 +1,93 @@ +import concordance, { type DisplayOptions } from 'concordance' +import { getColors } from './colors' + +export const getConcordanceTheme = () => { + const c = getColors() + + // this theme is taken from ava: https://github.com/avajs/ava/blob/main/lib/concordance-options.js + // no adjustments were made so far except for the diff padding + return { + boolean: c.yellow, + circular: c.gray('[Circular]'), + date: { + invalid: c.red('invalid'), + value: c.blue, + }, + diffGutters: { + actual: ` ${c.red('-')} `, + expected: ` ${c.green('+')} `, + padding: ' ', + }, + error: { + ctor: { open: `${c.gray.open}(`, close: `)${c.gray.close}` }, + name: c.magenta, + }, + function: { + name: c.blue, + stringTag: c.magenta, + }, + global: c.magenta, + item: { after: c.gray(',') }, + list: { openBracket: c.gray('['), closeBracket: c.gray(']') }, + mapEntry: { after: c.gray(',') }, + maxDepth: c.gray('…'), + null: c.yellow, + number: c.yellow, + object: { + openBracket: c.gray('{'), + closeBracket: c.gray('}'), + ctor: c.magenta, + stringTag: { open: `${c.magenta.open}@`, close: c.magenta.close }, + secondaryStringTag: { open: `${c.gray.open}@`, close: c.gray.close }, + }, + property: { + after: c.gray(','), + keyBracket: { open: c.gray('['), close: c.gray(']') }, + valueFallback: c.gray('…'), + }, + regexp: { + source: { open: `${c.blue.open}/`, close: `/${c.blue.close}` }, + flags: c.yellow, + }, + stats: { separator: c.gray('---') }, + string: { + open: c.blue.open, + close: c.blue.close, + line: { open: c.blue('\''), close: c.blue('\'') }, + multiline: { start: c.blue('`'), end: c.blue('`') }, + controlPicture: c.gray, + diff: { + insert: { + open: c.bgGreen.open + c.black.open, + close: c.black.close + c.bgGreen.close, + }, + delete: { + open: c.bgRed.open + c.black.open, + close: c.black.close + c.bgRed.close, + }, + equal: c.blue, + insertLine: { + open: c.green.open, + close: c.green.close, + }, + deleteLine: { + open: c.red.open, + close: c.red.close, + }, + }, + }, + symbol: c.yellow, + typedArray: { + bytes: c.yellow, + }, + undefined: c.yellow, + } +} + +export function diffDescriptors(actual: unknown, expected: unknown, options: DisplayOptions) { + return concordance.diff(expected, actual, options) +} + +export function formatDescriptor(value: unknown, options: DisplayOptions) { + return concordance.formatDescriptor(value, options) +} diff --git a/packages/utils/src/diff.ts b/packages/utils/src/diff.ts index 5a2cc5cc0a2c..48df4e513a2e 100644 --- a/packages/utils/src/diff.ts +++ b/packages/utils/src/diff.ts @@ -1,21 +1,9 @@ -import * as diff from 'diff' -import cliTruncate from 'cli-truncate' - -export function formatLine(line: string, outputTruncateLength?: number) { - return cliTruncate(line, (outputTruncateLength ?? (process.stdout?.columns || 80)) - 4) -} - -type Color = (str: string) => string +import { getColors } from './colors' +import { diffDescriptors, getConcordanceTheme } from './descriptors' export interface DiffOptions { - outputDiffMaxLines?: number - outputTruncateLength?: number - outputDiffLines?: number showLegend?: boolean - - colorSuccess?: Color - colorError?: Color - colorDim?: Color + outputDiffLines?: number } /** @@ -26,107 +14,60 @@ export interface DiffOptions { * @param {String} expected * @return {string} The diff. */ +export function unifiedDiff(actual: unknown, expected: unknown, options: DiffOptions = {}) { + const theme = getConcordanceTheme() + const diff = diffDescriptors(actual, expected, { theme }) -export function unifiedDiff(actual: string, expected: string, options: DiffOptions = {}) { - if (actual === expected) - return '' - - const { outputTruncateLength, outputDiffLines, outputDiffMaxLines, showLegend = true } = options - - const indent = ' ' - const diffLimit = outputDiffLines || 15 - const diffMaxLines = outputDiffMaxLines || 50 + const { showLegend = true, outputDiffLines = 15 } = options const counts = { '+': 0, '-': 0, } - let previousState: '-' | '+' | null = null - let previousCount = 0 - - const str = (str: string) => str - const dim = options.colorDim || str - const green = options.colorSuccess || str - const red = options.colorError || str - 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 dim(`${char} ...`) - else if (previousCount > diffLimit) - return + const c = getColors() + const plus = theme.diffGutters.actual + const minus = ` ${c.green('+')}` + + const lines = diff.split(/\r?\n/g) + let firstErrorLine: number | null = null + lines.forEach((line, index) => { + if (line.startsWith(plus)) { + firstErrorLine ??= index + counts['+']++ } - return line - } - - const msg = diff.createPatch('string', expected, actual) - let lines = msg.split('\n').slice(5).map(preprocess).filter(Boolean) as string[] - let moreLines = 0 - const isCompact = counts['+'] === 1 && counts['-'] === 1 && lines.length === 2 - - if (lines.length > diffMaxLines) { - const firstDiff = lines.findIndex(line => line[0] === '-' || line[0] === '+') - const displayLines = lines.slice(firstDiff - 2, diffMaxLines) - const lastDisplayedIndex = firstDiff - 2 + diffMaxLines - if (lastDisplayedIndex < lines.length) - moreLines = lines.length - lastDisplayedIndex - lines = displayLines - } - - let formatted = lines.map((line: string) => { - line = line.replace(/\\"/g, '"') - if (line[0] === '-') { - line = formatLine(line.slice(1), outputTruncateLength) - if (isCompact) - return green(line) - return green(`- ${formatLine(line, outputTruncateLength)}`) - } - if (line[0] === '+') { - line = formatLine(line.slice(1), outputTruncateLength) - if (isCompact) - return red(line) - return red(`+ ${formatLine(line, outputTruncateLength)}`) + else if (line.startsWith(minus)) { + firstErrorLine ??= index + counts['-']++ } - if (line.match(/@@/)) - return '--' - return ` ${line}` }) + const isCompact = counts['+'] === 1 && counts['-'] === 1 && lines.length === 2 - if (moreLines) - formatted.push(dim(`... ${moreLines} more lines`)) + let legend = '' if (showLegend) { - // Compact mode - if (isCompact) { - formatted = [ - `${green('- Expected')} ${formatted[0]}`, - `${red('+ Received')} ${formatted[1]}`, - ] + if (!isCompact) { + legend = ` ${c.green(`- Expected - ${counts['-']}`)} + ${c.red(`+ Received + ${counts['+']}`)} + +` } else { - if (formatted[0].includes('"')) - formatted[0] = formatted[0].replace('"', '') + legend = ' Difference:\n\n' + } + } - const last = formatted.length - 1 - if (formatted[last].endsWith('"')) - formatted[last] = formatted[last].slice(0, formatted[last].length - 1) + if (firstErrorLine != null && outputDiffLines) { + const start = Math.max(0, firstErrorLine - 1) + const end = Math.min(lines.length, firstErrorLine + outputDiffLines) + const linesAfterCount = lines.length - end - formatted.unshift( - green(`- Expected - ${counts['-']}`), - red(`+ Received + ${counts['+']}`), - '', - ) - } + const linesBefore = start ? ` ${c.gray(`... ${start} more line${start > 1 ? 's' : ''}\n`)}` : '' + const linesAfter = linesAfterCount ? `\n ${c.gray(`... ${linesAfterCount} more line${linesAfterCount > 1 ? 's' : ''}\n`)}` : '' + const diffOutput = lines.slice(start, end).map(line => line.replace(/␊\s*$/, '')).join('\n') + const helperBunner = linesAfter && (counts['+'] + counts['-'] > outputDiffLines) ? `\n Use ${c.gray('test.outputDiffLines')} to increase the number of lines shown.` : '' + + return legend + linesBefore + diffOutput + linesAfter + helperBunner } - return formatted.map(i => i ? (indent + i) : i).join('\n') + return legend + diff } diff --git a/packages/utils/src/external.d.ts b/packages/utils/src/external.d.ts new file mode 100644 index 000000000000..00449b7e4c61 --- /dev/null +++ b/packages/utils/src/external.d.ts @@ -0,0 +1,9 @@ +declare module 'concordance' { + interface DisplayOptions { + theme?: any + maxDepth?: number + } + + export function diff(expected: unknown, actual: unknown, options?: DisplayOptions): string + export function formatDescriptor(descriptor: unknown, options?: DisplayOptions): string +} \ No newline at end of file diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index 3c4be318fd1e..5c9bbb869bc6 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -7,3 +7,4 @@ export * from './display' export * from './constants' export * from './colors' export * from './error' +export * from './descriptors' diff --git a/packages/vitest/package.json b/packages/vitest/package.json index 673294d14588..745c2700c1c5 100644 --- a/packages/vitest/package.json +++ b/packages/vitest/package.json @@ -145,6 +145,7 @@ "acorn-walk": "^8.2.0", "cac": "^6.7.14", "chai": "^4.3.7", + "concordance": "^5.0.4", "debug": "^4.3.4", "local-pkg": "^0.4.2", "pathe": "^1.1.0", @@ -166,7 +167,7 @@ "@types/diff": "^5.0.2", "@types/istanbul-lib-coverage": "^2.0.4", "@types/istanbul-reports": "^3.0.1", - "@types/jsdom": "^21.1.0", + "@types/jsdom": "^20.0.1", "@types/micromatch": "^4.0.2", "@types/natural-compare": "^1.4.1", "@types/prompts": "^2.4.2", @@ -174,7 +175,6 @@ "birpc": "^0.2.3", "chai-subset": "^1.6.0", "cli-truncate": "^3.1.0", - "diff": "^5.1.0", "event-target-polyfill": "^0.0.3", "execa": "^7.0.0", "expect-type": "^0.15.0", diff --git a/packages/vitest/src/node/cli.ts b/packages/vitest/src/node/cli.ts index e70c8f52580d..a9bf94e4c5c3 100644 --- a/packages/vitest/src/node/cli.ts +++ b/packages/vitest/src/node/cli.ts @@ -25,9 +25,6 @@ cli .option('--silent', 'Silent console output from tests') .option('--isolate', 'Isolate environment for each test file (default: true)') .option('--reporter ', 'Specify reporters') - .option('--outputDiffMaxSize ', 'Object diff output max size (default: 10000)') - .option('--outputDiffMaxLines ', 'Max lines in diff output window (default: 50)') - .option('--outputTruncateLength ', 'Diff output line length (default: 80)') .option('--outputDiffLines ', 'Number of lines in single diff (default: 15)') .option('--outputFile ', 'Write test results to a file when supporter reporter is also specified, use cac\'s dot notation for individual outputs of multiple reporters') .option('--coverage', 'Enable coverage report') diff --git a/packages/vitest/src/node/error.ts b/packages/vitest/src/node/error.ts index b447c8e881db..7d69d7c06597 100644 --- a/packages/vitest/src/node/error.ts +++ b/packages/vitest/src/node/error.ts @@ -3,7 +3,6 @@ import { existsSync, readFileSync } from 'fs' import { normalize, relative } from 'pathe' import c from 'picocolors' import cliTruncate from 'cli-truncate' -import { type DiffOptions, unifiedDiff } from '@vitest/utils/diff' import { stringify } from '@vitest/utils' import type { ErrorWithDiff, ParsedStack } from '../types' import { lineSplitRE, parseErrorStacktrace, positionToOffset } from '../utils/source-map' @@ -96,16 +95,8 @@ export async function printError(error: unknown, ctx: Vitest, options: PrintErro handleImportOutsideModuleError(e.stack || e.stackStr || '', ctx) // E.g. AssertionError from assert does not set showDiff but has both actual and expected properties - if (e.showDiff || (e.showDiff === undefined && e.actual && e.expected)) { - displayDiff(stringify(e.actual), stringify(e.expected), ctx.logger.console, { - outputTruncateLength: ctx.config.outputTruncateLength, - outputDiffLines: ctx.config.outputDiffLines, - outputDiffMaxLines: ctx.config.outputDiffMaxLines, - colorDim: c.dim, - colorError: c.red, - colorSuccess: c.green, - }) - } + if (e.diff) + displayDiff(e.diff, ctx.logger.console) } function printErrorType(type: string, ctx: Vitest) { @@ -178,14 +169,8 @@ function handleImportOutsideModuleError(stack: string, ctx: Vitest) { }\n`))) } -export function displayDiff(actual: string, expected: string, console: Console, options: Omit = {}) { - const diff = unifiedDiff(actual, expected, options) - const dim = options.colorDim || ((str: string) => str) - const black = options.colorDim ? c.black : (str: string) => str - if (diff) - console.error(diff + '\n') - else if (actual && expected && actual !== '"undefined"' && expected !== '"undefined"') - console.error(dim('Could not display diff. It\'s possible objects are too large to compare.\nTry increasing ') + black('--outputDiffMaxSize') + dim(' option.\n')) +export function displayDiff(diff: string, console: Console) { + console.error(diff) } function printErrorMessage(error: ErrorWithDiff, logger: Logger) { diff --git a/packages/vitest/src/types/config.ts b/packages/vitest/src/types/config.ts index d5b843dd6b66..19dac39be761 100644 --- a/packages/vitest/src/types/config.ts +++ b/packages/vitest/src/types/config.ts @@ -206,33 +206,12 @@ export interface InlineConfig { */ reporters?: Arrayable> - /** - * Truncates lines in the output to the given length. - * @default stdout.columns || 80 - */ - outputTruncateLength?: number - /** * Maximum number of line to show in a single diff. * @default 15 */ outputDiffLines?: number - /** - * The maximum number of characters allowed in a single object before doing a diff. - * 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 object is too big or nested, you might not see the diff. - * @default 10000 - */ - outputDiffMaxSize?: number - - /** - * Maximum number of lines in a diff overall. - * @default 50 - */ - outputDiffMaxLines?: number - /** * Write test results to a file when the --reporter=json` or `--reporter=junit` option is also specified. * Also definable individually per reporter by using an object instead. diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5243c9fbec05..f12f8423ce33 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -712,10 +712,12 @@ importers: packages/runner: specifiers: '@vitest/utils': workspace:* + concordance: ^5.0.4 p-limit: ^4.0.0 pathe: ^1.1.0 dependencies: '@vitest/utils': link:../utils + concordance: 5.0.4 p-limit: 4.0.0 pathe: 1.1.0 @@ -737,6 +739,7 @@ importers: '@vitejs/plugin-vue': ^4.0.0 '@vitejs/plugin-vue-jsx': ^3.0.0 '@vitest/runner': workspace:* + '@vitest/utils': workspace:* '@vitest/ws-client': workspace:* '@vueuse/core': ^9.10.0 ansi-to-html: ^0.7.2 @@ -745,7 +748,6 @@ importers: codemirror-theme-vars: ^0.1.1 cypress: ^12.3.0 d3-graph-controller: ^2.5.1 - diff: ^5.1.0 fast-glob: ^3.2.12 flatted: ^3.2.7 floating-vue: ^2.0.0-y.0 @@ -762,6 +764,7 @@ importers: vue: ^3.2.45 vue-router: ^4.1.6 dependencies: + '@vitest/utils': link:../utils fast-glob: 3.2.12 flatted: 3.2.7 pathe: 1.1.0 @@ -786,7 +789,6 @@ importers: 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_vue@3.2.45 rollup: 2.79.1 splitpanes: 3.1.5 @@ -800,18 +802,13 @@ importers: packages/utils: specifiers: - '@types/diff': ^5.0.2 - cli-truncate: ^3.1.0 - diff: ^5.1.0 + concordance: ^5.0.4 loupe: ^2.3.6 pretty-format: ^27.5.1 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 packages/vite-node: specifiers: @@ -847,7 +844,7 @@ importers: '@types/diff': ^5.0.2 '@types/istanbul-lib-coverage': ^2.0.4 '@types/istanbul-reports': ^3.0.1 - '@types/jsdom': ^21.1.0 + '@types/jsdom': ^20.0.1 '@types/micromatch': ^4.0.2 '@types/natural-compare': ^1.4.1 '@types/node': '*' @@ -864,8 +861,8 @@ importers: chai: ^4.3.7 chai-subset: ^1.6.0 cli-truncate: ^3.1.0 + concordance: ^5.0.4 debug: ^4.3.4 - diff: ^5.1.0 event-target-polyfill: ^0.0.3 execa: ^7.0.0 expect-type: ^0.15.0 @@ -915,6 +912,7 @@ importers: acorn-walk: 8.2.0 cac: 6.7.14 chai: 4.3.7 + concordance: 5.0.4 debug: 4.3.4 local-pkg: 0.4.2 pathe: 1.1.0 @@ -935,7 +933,7 @@ importers: '@types/diff': 5.0.2 '@types/istanbul-lib-coverage': 2.0.4 '@types/istanbul-reports': 3.0.1 - '@types/jsdom': 21.1.0 + '@types/jsdom': 20.0.1 '@types/micromatch': 4.0.2 '@types/natural-compare': 1.4.1 '@types/prompts': 2.4.2 @@ -943,7 +941,6 @@ importers: birpc: 0.2.3 chai-subset: 1.6.0 cli-truncate: 3.1.0 - diff: 5.1.0 event-target-polyfill: 0.0.3 execa: 7.0.0 expect-type: 0.15.0 @@ -7580,12 +7577,12 @@ packages: resolution: {integrity: sha512-qC4bCqYGy1y/NP7dDVr7KJarn+PbX1nSpwA7JXdu0HxT3QYjO8MJ+cntENtHFVy2dRAyBV23OZ6MxsW1AM1L8g==} dev: true - /@types/jsdom/21.1.0: - resolution: {integrity: sha512-leWreJOdnuIxq9Y70tBVm/bvTuh31DSlF/r4l7Cfi4uhVQqLHD0Q4v301GMisEMwwbMgF7ZKxuZ+Jbd4NcdmRw==} + /@types/jsdom/20.0.1: + resolution: {integrity: sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==} dependencies: '@types/node': 18.15.11 '@types/tough-cookie': 4.0.2 - parse5: 7.1.1 + parse5: 7.1.2 dev: true /@types/json-schema/7.0.11: @@ -9530,6 +9527,7 @@ packages: /ansi-regex/6.0.1: resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} engines: {node: '>=12'} + dev: true /ansi-styles/2.2.1: resolution: {integrity: sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==} @@ -9555,6 +9553,7 @@ packages: /ansi-styles/6.2.1: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} + dev: true /ansi-to-html/0.6.15: resolution: {integrity: sha512-28ijx2aHJGdzbs+O5SNQF65r6rrKYnkuwTYm8lZlChuoJ9P1vVzIpWO20sQTqTPDXYp6NFwk326vApTtLVFXpQ==} @@ -10196,6 +10195,10 @@ packages: resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} dev: true + /blueimp-md5/2.19.0: + resolution: {integrity: sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w==} + dev: false + /bn.js/4.12.0: resolution: {integrity: sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==} dev: true @@ -10960,6 +10963,7 @@ packages: dependencies: slice-ansi: 5.0.0 string-width: 5.1.2 + dev: true /cli-width/3.0.0: resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==} @@ -11139,6 +11143,20 @@ packages: typedarray: 0.0.6 dev: true + /concordance/5.0.4: + resolution: {integrity: sha512-OAcsnTEYu1ARJqWVGwf4zh4JDfHZEaSNlNccFmt8YjB2l/n19/PF2viLINHc57vO4FKIAFl2FWASIGZZWZ2Kxw==} + engines: {node: '>=10.18.0 <11 || >=12.14.0 <13 || >=14'} + dependencies: + date-time: 3.1.0 + esutils: 2.0.3 + fast-diff: 1.2.0 + js-string-escape: 1.0.1 + lodash: 4.17.21 + md5-hex: 3.0.1 + semver: 7.3.8 + well-known-symbols: 2.0.0 + dev: false + /condense-newlines/0.2.1: resolution: {integrity: sha512-P7X+QL9Hb9B/c8HI5BFFKmjgBu2XpQuF98WZ9XkO+dBGgk5XgwiQz7o1SmpglNWId3581UcS0SFAWfoIhMHPfg==} engines: {node: '>=0.10.0'} @@ -11739,6 +11757,13 @@ packages: resolution: {integrity: sha512-0VNbwmWJDS/G3ySwFSJA3ayhbURMTJLtwM2DTxf9CWondCnh6DTNlO9JgRSq6ibf4eD0lfMJNBxUdEAHHix+bA==} engines: {node: '>=0.11'} + /date-time/3.1.0: + resolution: {integrity: sha512-uqCUKXE5q1PNBXjPqvwhwJf9SwMoAHBgWJ6DcrnS5o+W2JOiIILl0JEdVD8SGujrNS02GGxgwAg2PN2zONgtjg==} + engines: {node: '>=6'} + dependencies: + time-zone: 1.0.0 + dev: false + /dayjs/1.11.5: resolution: {integrity: sha512-CAdX5Q3YW3Gclyo5Vpqkgpj8fSdLQcRuzfX6mC6Phy0nfJ0eGYOeS7m4mt2plDWLAtA4TqTakvbboHvUxfe4iA==} dev: true @@ -12113,10 +12138,6 @@ packages: engines: {node: '>=0.3.1'} dev: true - /diff/5.1.0: - resolution: {integrity: sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==} - engines: {node: '>=0.3.1'} - /diffie-hellman/5.0.3: resolution: {integrity: sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==} dependencies: @@ -12288,6 +12309,7 @@ packages: /eastasianwidth/0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + dev: true /ecc-jsbn/0.1.2: resolution: {integrity: sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==} @@ -12346,6 +12368,7 @@ packages: /emoji-regex/9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + dev: true /emojis-list/3.0.0: resolution: {integrity: sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==} @@ -13091,7 +13114,6 @@ packages: /esutils/2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} - dev: true /etag/1.8.1: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} @@ -13388,6 +13410,10 @@ packages: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} dev: true + /fast-diff/1.2.0: + resolution: {integrity: sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==} + dev: false + /fast-equals/2.0.4: resolution: {integrity: sha512-caj/ZmjHljPrZtbzJ3kfH5ia/k4mTJe/qSiXAGzxZWRZgsgDV0cvNaQULqUX8t0/JVlzzEdYOwCN5DmzTxoD4w==} dev: false @@ -15053,6 +15079,7 @@ packages: /is-fullwidth-code-point/4.0.0: resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==} engines: {node: '>=12'} + dev: true /is-function/1.0.2: resolution: {integrity: sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==} @@ -15589,7 +15616,6 @@ packages: /js-string-escape/1.0.1: resolution: {integrity: sha512-Smw4xcfIQ5LVjAOuJCvN/zIodzA/BBSsluuoSykP+lUvScIi4U6RJLfwHet5cxFnCswUjISV8oAXaqaJDY3chg==} engines: {node: '>= 0.8'} - dev: true /js-tokens/4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -16375,7 +16401,6 @@ packages: engines: {node: '>=10'} dependencies: yallist: 4.0.0 - dev: true /lru-cache/7.18.3: resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} @@ -16474,6 +16499,13 @@ packages: remove-accents: 0.4.2 dev: false + /md5-hex/3.0.1: + resolution: {integrity: sha512-BUiRtTtV39LIJwinWBjqVsU9xhdnz7/i889V859IBFpuqGAj6LuOvHv5XLbgZ2R7ptJoJaEcxkv88/h25T7Ciw==} + engines: {node: '>=8'} + dependencies: + blueimp-md5: 2.19.0 + dev: false + /md5.js/1.3.5: resolution: {integrity: sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==} dependencies: @@ -19731,7 +19763,6 @@ packages: hasBin: true dependencies: lru-cache: 6.0.0 - dev: true /send/0.18.0: resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==} @@ -19953,6 +19984,7 @@ packages: dependencies: ansi-styles: 6.2.1 is-fullwidth-code-point: 4.0.0 + dev: true /snapdragon-node/2.1.1: resolution: {integrity: sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==} @@ -20309,6 +20341,7 @@ packages: eastasianwidth: 0.2.0 emoji-regex: 9.2.2 strip-ansi: 7.0.1 + dev: true /string.prototype.matchall/4.0.7: resolution: {integrity: sha512-f48okCX7JiwVi1NXCVWcFnZgADDC/n2vePlQ/KUCNqCikLLilQvwjMO8+BHVKvgzH0JB0J9LEPgxOGT02RoETg==} @@ -20405,6 +20438,7 @@ packages: engines: {node: '>=12'} dependencies: ansi-regex: 6.0.1 + dev: true /strip-bom/2.0.0: resolution: {integrity: sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g==} @@ -20842,6 +20876,11 @@ packages: xtend: 4.0.2 dev: true + /time-zone/1.0.0: + resolution: {integrity: sha512-TIsDdtKo6+XrPtiTm1ssmMngN1sAhyKnTO2kunQWqNPWIVvCm15Wmw4SWInwTVgJ5u/Tr04+8Ei9TNcw4x4ONA==} + engines: {node: '>=4'} + dev: false + /timers-browserify/2.0.12: resolution: {integrity: sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==} engines: {node: '>=0.6.0'} @@ -22635,6 +22674,11 @@ packages: - uglify-js dev: true + /well-known-symbols/2.0.0: + resolution: {integrity: sha512-ZMjC3ho+KXo0BfJb7JgtQ5IBuvnShdlACNkKkdsqBmYw3bPAaJfPeYUo6tLUaT5tG/Gkh7xkpBhKRQ9e7pyg9Q==} + engines: {node: '>=6'} + dev: false + /whatwg-encoding/2.0.0: resolution: {integrity: sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==} engines: {node: '>=12'} @@ -23067,7 +23111,6 @@ packages: /yallist/4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} - dev: true /yaml-eslint-parser/1.1.0: resolution: {integrity: sha512-b464Q1fYiX1oYx2kE8k4mEp6S9Prk+tfDsY/IPxQ0FCjEuj3AKko5Skf3/yQJeYTTDyjDE+aWIJemnv29HvEWQ==} diff --git a/test/core/test/__snapshots__/mocked.test.ts.snap b/test/core/test/__snapshots__/mocked.test.ts.snap index 5de49f7f9ee6..1fd6b2244e71 100644 --- a/test/core/test/__snapshots__/mocked.test.ts.snap +++ b/test/core/test/__snapshots__/mocked.test.ts.snap @@ -6,8 +6,8 @@ exports[`mocked function which fails on toReturnWith > just one call 1`] = ` Received: 1st spy call return: - 1 - 2 + - 1 + + 2 Number of calls: 1 @@ -20,18 +20,18 @@ exports[`mocked function which fails on toReturnWith > multi calls 1`] = ` Received: 1st spy call return: - 1 - 2 + - 1 + + 2 2nd spy call return: - 1 - 2 + - 1 + + 2 3rd spy call return: - 1 - 2 + - 1 + + 2 Number of calls: 3 @@ -44,23 +44,23 @@ exports[`mocked function which fails on toReturnWith > oject type 1`] = ` Received: 1st spy call return: - Object { - - \\"a\\": \\"1\\", - + \\"a\\": \\"4\\", + { + - a: '1', + + a: '4', } 2nd spy call return: - Object { - - \\"a\\": \\"1\\", - + \\"a\\": \\"4\\", + { + - a: '1', + + a: '4', } 3rd spy call return: - Object { - - \\"a\\": \\"1\\", - + \\"a\\": \\"4\\", + { + - a: '1', + + a: '4', } diff --git a/test/core/test/diff.test.ts b/test/core/test/diff.test.ts index 04f8e0df4cff..ae1e9e8d35dd 100644 --- a/test/core/test/diff.test.ts +++ b/test/core/test/diff.test.ts @@ -1,81 +1,54 @@ import { expect, test, vi } from 'vitest' -import { stringify } from '@vitest/utils' +import { getDefaultColors, setupColors } from '@vitest/utils' import { displayDiff } from 'vitest/src/node/error' +import { unifiedDiff } from '@vitest/utils/diff' -test('displays an error for large objects', () => { - const objectA = new Array(1000).fill(0).map((_, i) => ({ i, long: 'a'.repeat(i) })) - const objectB = new Array(1000).fill(0).map((_, i) => ({ i, long: 'b'.repeat(i) })) - const console = { log: vi.fn(), error: vi.fn() } - displayDiff(stringify(objectA), stringify(objectB), console as any) - expect(console.error.mock.calls[0][0]).toMatchInlineSnapshot(` - "Could not display diff. It's possible objects are too large to compare. - Try increasing --outputDiffMaxSize option. - " - `) -}) - -test('displays an error for large objects', () => { - const console = { log: vi.fn(), error: vi.fn() } - displayDiff(stringify('undefined'), stringify('undefined'), console as any) - expect(console.error).not.toHaveBeenCalled() -}) - -test('displays diff', () => { +test('displays object diff', () => { const objectA = { a: 1, b: 2 } const objectB = { a: 1, b: 3 } const console = { log: vi.fn(), error: vi.fn() } - displayDiff(stringify(objectA), stringify(objectB), console as any) + setupColors(getDefaultColors()) + displayDiff(unifiedDiff(objectA, objectB), console as any) expect(console.error.mock.calls[0][0]).toMatchInlineSnapshot(` " - Expected - 1 + Received + 1 - Object { - \\"a\\": 1, - - \\"b\\": 3, - + \\"b\\": 2, - } - " + ... 1 more line + a: 1, + - b: 3, + + b: 2, + }" `) }) -test('displays long diff', () => { - const objectA = { a: 1, b: 2, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9, j: 10, k: 11, l: 12, m: 13, n: 14, o: 15, p: 16, q: 17, r: 18, s: 19, t: 20, u: 21, v: 22, w: 23, x: 24, y: 25, z: 26 } - const objectB = { a: 1, b: 3, k: 11, l: 12, m: 13, n: 14, p: 16, o: 17, r: 18, s: 23, t: 88, u: 21, v: 44, w: 23, x: 24, y: 25, z: 26 } +test('display one line string diff', () => { + const string1 = 'string1' + const string2 = 'string2' const console = { log: vi.fn(), error: vi.fn() } - displayDiff(stringify(objectA), stringify(objectB), console as any, { outputDiffMaxLines: 5 }) + setupColors(getDefaultColors()) + displayDiff(unifiedDiff(string1, string2), console as any) expect(console.error.mock.calls[0][0]).toMatchInlineSnapshot(` - " - Expected - 5 - + Received + 13 + " Difference: - Object { - \\"a\\": 1, - - \\"b\\": 3, - + \\"b\\": 2, - + \\"d\\": 4, - ... 26 more lines - " + - 'string2' + + 'string1'" `) }) -test('displays truncated diff', () => { - const stringA = `Lorem ipsum dolor sit amet, consectetur adipiscing elit. -Suspendisse viverra sapien ac venenatis lacinia. -Morbi consectetur arcu nec lorem lacinia tempus.` - const objectB = `Quisque hendrerit metus id dapibus pulvinar. -Quisque pellentesque enim a elit faucibus cursus. -Sed in tellus aliquet mauris interdum semper a in lacus.` +test('display multiline line string diff', () => { + const string1 = 'string1\nstring2\nstring3' + const string2 = 'string2\nstring2\nstring1' const console = { log: vi.fn(), error: vi.fn() } - displayDiff((stringA), (objectB), console as any, { outputTruncateLength: 14 }) + setupColors(getDefaultColors()) + displayDiff(unifiedDiff(string1, string2), console as any) expect(console.error.mock.calls[0][0]).toMatchInlineSnapshot(` - " - Expected - 3 - + Received + 3 - - - Quisque h… - - Quisque p… - - Sed in te… - + Lorem ips… - + Suspendis… - + Morbi con… - " + " - Expected - 2 + + Received + 2 + + + string1 + \`string2 + - string2 + - string1\` + + string3\`" `) }) diff --git a/test/core/test/jest-matcher-utils.test.ts b/test/core/test/jest-matcher-utils.test.ts index a911b702c3ad..a008bc30514c 100644 --- a/test/core/test/jest-matcher-utils.test.ts +++ b/test/core/test/jest-matcher-utils.test.ts @@ -1,3 +1,4 @@ +import { getDefaultColors, setupColors } from '@vitest/utils' import { describe, expect, it } from 'vitest' describe('jest-matcher-utils', () => { @@ -11,9 +12,11 @@ describe('jest-matcher-utils', () => { }) it('diff', () => { + setupColors(getDefaultColors()) + expect(() => { // @ts-expect-error "toBeJestEqual" is a custom matcher we just created expect('a').toBeJestEqual('b') - }).toThrowError(/Expected.*"b".*Received.*"a"/ms) + }).toThrowError(/Difference:.*\- 'b'.*\+ 'a'/ms) }) }) diff --git a/test/core/test/mocked.test.ts b/test/core/test/mocked.test.ts index cb32fdc26737..5bf3cbb81aa3 100644 --- a/test/core/test/mocked.test.ts +++ b/test/core/test/mocked.test.ts @@ -1,6 +1,7 @@ -import { assert, describe, expect, test, vi, vitest } from 'vitest' +import { afterEach, assert, beforeEach, describe, expect, test, vi, vitest } from 'vitest' // @ts-expect-error not typed module import { value as virtualValue } from 'virtual-module' +import { createColors, getDefaultColors, setupColors } from '@vitest/utils' import { two } from '../src/submodule' import * as mocked from '../src/mockedA' import { mockedB } from '../src/mockedB' @@ -135,6 +136,13 @@ test('async functions should be mocked', () => { }) describe('mocked function which fails on toReturnWith', () => { + beforeEach(() => { + setupColors(getDefaultColors()) + }) + afterEach(() => { + setupColors(createColors(true)) + }) + test('zero call', () => { const mock = vi.fn(() => 1) expect(() => expect(mock).toReturnWith(2)).toThrowErrorMatchingSnapshot() diff --git a/test/core/test/setup.ts b/test/core/test/setup.ts index 4c3ee23e0aad..898dc0bdc30c 100644 --- a/test/core/test/setup.ts +++ b/test/core/test/setup.ts @@ -1,8 +1,3 @@ -import { getDefaultColors, setupColors } from '@vitest/utils' -import { beforeEach, vi } from 'vitest' +import { vi } from 'vitest' vi.mock('../src/global-mock', () => ({ mocked: true })) - -beforeEach(() => { - setupColors(getDefaultColors()) -}) diff --git a/test/reporters/tests/__snapshots__/html.test.ts.snap b/test/reporters/tests/__snapshots__/html.test.ts.snap index 43801f9c5937..5f1898b8e95d 100644 --- a/test/reporters/tests/__snapshots__/html.test.ts.snap +++ b/test/reporters/tests/__snapshots__/html.test.ts.snap @@ -41,6 +41,10 @@ exports[`html reporter > resolves to "failing" status for test file "json-fail" "error": { "actual": "2", "constructor": "Function", + "diff": " Difference: + + - 1 + + 2", "expected": "1", "message": "expected 2 to deeply equal 1", "name": "AssertionError", @@ -56,6 +60,10 @@ exports[`html reporter > resolves to "failing" status for test file "json-fail" { "actual": "2", "constructor": "Function", + "diff": " Difference: + + - 1 + + 2", "expected": "1", "message": "expected 2 to deeply equal 1", "name": "AssertionError", diff --git a/test/watch/fixtures/vitest.config.ts b/test/watch/fixtures/vitest.config.ts index 83ca9cdd40cb..7d418e5cca77 100644 --- a/test/watch/fixtures/vitest.config.ts +++ b/test/watch/fixtures/vitest.config.ts @@ -7,7 +7,6 @@ process.stdin.setRawMode = () => process.stdin export default defineConfig({ test: { watch: true, - outputTruncateLength: 999, // This configuration is edited by tests reporters: 'verbose',