From 391860f79b58be0882afff7b0548f578c40d0ee6 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Thu, 14 Nov 2024 00:59:31 +0900 Subject: [PATCH] feat: support inline `diff` options and support `printBasicPrototype` (#6740) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Vladimir Co-authored-by: MichaƂ Grzegorzewski <4864089+spamshaker@users.noreply.github.com> --- docs/config/index.md | 58 +++++++-- packages/browser/src/node/plugin.ts | 2 +- packages/utils/src/diff/index.ts | 5 +- .../utils/src/diff/normalizeDiffOptions.ts | 1 + packages/utils/src/diff/types.ts | 18 +++ packages/vitest/src/node/cli/cli-config.ts | 53 ++++++++- .../vitest/src/node/config/resolveConfig.ts | 2 +- .../vitest/src/node/config/serializeConfig.ts | 1 + packages/vitest/src/node/types/config.ts | 5 +- packages/vitest/src/runtime/config.ts | 3 +- packages/vitest/src/runtime/setup-common.ts | 3 + test/config/fixtures/diff/basic.test.ts | 20 ++++ test/config/fixtures/diff/vite.config.ts | 10 ++ .../test/__snapshots__/diff.test.ts.snap | 111 ++++++++++++++++++ test/config/test/diff.test.ts | 19 +++ test/config/test/failures.test.ts | 4 +- 16 files changed, 295 insertions(+), 20 deletions(-) create mode 100644 test/config/fixtures/diff/basic.test.ts create mode 100644 test/config/fixtures/diff/vite.config.ts create mode 100644 test/config/test/__snapshots__/diff.test.ts.snap create mode 100644 test/config/test/diff.test.ts diff --git a/docs/config/index.md b/docs/config/index.md index 1fc760dc40bc..a0f9152d09d2 100644 --- a/docs/config/index.md +++ b/docs/config/index.md @@ -2284,22 +2284,32 @@ export default defineConfig({ ### diff - **Type:** `string` -- **CLI:** `--diff=` +- **CLI:** `--diff=` -Path to a diff config that will be used to generate diff interface. Useful if you want to customize diff display. +`DiffOptions` object or a path to a module which exports `DiffOptions`. Useful if you want to customize diff display. + +For example, as a config object: :::code-group -```ts [vitest.diff.ts] -import type { DiffOptions } from 'vitest' -import c from 'tinyrainbow' +```ts [vitest.config.js] +import { defineConfig } from 'vitest/config' +import c from 'picocolors' -export default { - aIndicator: c.bold('--'), - bIndicator: c.bold('++'), - omitAnnotationLines: true, -} satisfies DiffOptions +export default defineConfig({ + test: { + diff: { + aIndicator: c.bold('--'), + bIndicator: c.bold('++'), + omitAnnotationLines: true, + } + } +}) ``` +::: +Or as a module: + +:::code-group ```ts [vitest.config.js] import { defineConfig } from 'vitest/config' @@ -2309,12 +2319,32 @@ export default defineConfig({ } }) ``` + +```ts [vitest.diff.ts] +import type { DiffOptions } from 'vitest' +import c from 'picocolors' + +export default { + aIndicator: c.bold('--'), + bIndicator: c.bold('++'), + omitAnnotationLines: true, +} satisfies DiffOptions +``` ::: +#### diff.expand + +- **Type**: `boolean` +- **Default**: `true` +- **CLI:** `--diff.expand=false` + +Expand all common lines. + #### diff.truncateThreshold - **Type**: `number` - **Default**: `0` +- **CLI:** `--diff.truncateThreshold=` The maximum length of diff result to be displayed. Diffs above this threshold will be truncated. Truncation won't take effect with default value 0. @@ -2323,6 +2353,7 @@ Truncation won't take effect with default value 0. - **Type**: `string` - **Default**: `'... Diff result is truncated'` +- **CLI:** `--diff.truncateAnnotation=` Annotation that is output at the end of diff result if it's truncated. @@ -2333,6 +2364,13 @@ Annotation that is output at the end of diff result if it's truncated. Color of truncate annotation, default is output with no color. +#### diff.printBasicPrototype + +- **Type**: `boolean` +- **Default**: `true` + +Print basic prototype `Object` and `Array` in diff output + ### fakeTimers - **Type:** `FakeTimerInstallOpts` diff --git a/packages/browser/src/node/plugin.ts b/packages/browser/src/node/plugin.ts index 14c216cfa1ff..e1d049d7c702 100644 --- a/packages/browser/src/node/plugin.ts +++ b/packages/browser/src/node/plugin.ts @@ -230,7 +230,7 @@ export default (browserServer: BrowserServer, base = '/'): Plugin[] => { 'msw/browser', ] - if (project.config.diff) { + if (typeof project.config.diff === 'string') { entries.push(project.config.diff) } diff --git a/packages/utils/src/diff/index.ts b/packages/utils/src/diff/index.ts index fcb94a9f07b6..d9445dc47dc0 100644 --- a/packages/utils/src/diff/index.ts +++ b/packages/utils/src/diff/index.ts @@ -23,7 +23,7 @@ import { getType } from './getType' import { normalizeDiffOptions } from './normalizeDiffOptions' import { diffStringsRaw, diffStringsUnified } from './printDiffs' -export type { DiffOptions, DiffOptionsColor } from './types' +export type { DiffOptions, DiffOptionsColor, SerializedDiffOptions } from './types' export { diffLinesRaw, diffLinesUnified, diffLinesUnified2 } export { diffStringsRaw, diffStringsUnified } @@ -180,11 +180,12 @@ function getFormatOptions( formatOptions: PrettyFormatOptions, options?: DiffOptions, ): PrettyFormatOptions { - const { compareKeys } = normalizeDiffOptions(options) + const { compareKeys, printBasicPrototype } = normalizeDiffOptions(options) return { ...formatOptions, compareKeys, + printBasicPrototype, } } diff --git a/packages/utils/src/diff/normalizeDiffOptions.ts b/packages/utils/src/diff/normalizeDiffOptions.ts index a7d9be7134ae..f2940a38758c 100644 --- a/packages/utils/src/diff/normalizeDiffOptions.ts +++ b/packages/utils/src/diff/normalizeDiffOptions.ts @@ -34,6 +34,7 @@ function getDefaultOptions(): DiffOptionsNormalized { includeChangeCounts: false, omitAnnotationLines: false, patchColor: c.yellow, + printBasicPrototype: true, truncateThreshold: DIFF_TRUNCATE_THRESHOLD_DEFAULT, truncateAnnotation: '... Diff result is truncated', truncateAnnotationColor: noColor, diff --git a/packages/utils/src/diff/types.ts b/packages/utils/src/diff/types.ts index 951fcdbb27c1..211ab033f12c 100644 --- a/packages/utils/src/diff/types.ts +++ b/packages/utils/src/diff/types.ts @@ -26,12 +26,29 @@ export interface DiffOptions { includeChangeCounts?: boolean omitAnnotationLines?: boolean patchColor?: DiffOptionsColor + printBasicPrototype?: boolean compareKeys?: CompareKeys truncateThreshold?: number truncateAnnotation?: string truncateAnnotationColor?: DiffOptionsColor } +export interface SerializedDiffOptions { + aAnnotation?: string + aIndicator?: string + bAnnotation?: string + bIndicator?: string + commonIndicator?: string + contextLines?: number + emptyFirstOrLastLinePlaceholder?: string + expand?: boolean + includeChangeCounts?: boolean + omitAnnotationLines?: boolean + printBasicPrototype?: boolean + truncateThreshold?: number + truncateAnnotation?: string +} + export interface DiffOptionsNormalized { aAnnotation: string aColor: DiffOptionsColor @@ -51,6 +68,7 @@ export interface DiffOptionsNormalized { includeChangeCounts: boolean omitAnnotationLines: boolean patchColor: DiffOptionsColor + printBasicPrototype: boolean truncateThreshold: number truncateAnnotation: string truncateAnnotationColor: DiffOptionsColor diff --git a/packages/vitest/src/node/cli/cli-config.ts b/packages/vitest/src/node/cli/cli-config.ts index 638ec22d508a..3d4f9e31961f 100644 --- a/packages/vitest/src/node/cli/cli-config.ts +++ b/packages/vitest/src/node/cli/cli-config.ts @@ -599,9 +599,58 @@ export const cliOptionsConfig: VitestCLIOptions = { }, diff: { description: - 'Path to a diff config that will be used to generate diff interface', + 'DiffOptions object or a path to a module which exports DiffOptions object', argument: '', - normalize: true, + subcommands: { + aAnnotation: { + description: 'Annotation for expected lines (default: `Expected`)', + argument: '', + }, + aIndicator: { + description: 'Indicator for expected lines (default: `-`)', + argument: '', + }, + bAnnotation: { + description: 'Annotation for received lines (default: `Received`)', + argument: '', + }, + bIndicator: { + description: 'Indicator for received lines (default: `+`)', + argument: '', + }, + commonIndicator: { + description: 'Indicator for common lines (default: ` `)', + argument: '', + }, + contextLines: { + description: 'Number of lines of context to show around each change (default: `5`)', + argument: '', + }, + emptyFirstOrLastLinePlaceholder: { + description: 'Placeholder for an empty first or last line (default: `""`)', + argument: '', + }, + expand: { + description: 'Expand all common lines (default: `true`)', + }, + includeChangeCounts: { + description: 'Include comparison counts in diff output (default: `false`)', + }, + omitAnnotationLines: { + description: 'Omit annotation lines from the output (default: `false`)', + }, + printBasicPrototype: { + description: 'Print basic prototype Object and Array (default: `true`)', + }, + truncateThreshold: { + description: 'Number of lines to show before and after each change (default: `0`)', + argument: '', + }, + truncateAnnotation: { + description: 'Annotation for truncated lines (default: `... Diff result is truncated`)', + argument: '', + }, + }, }, exclude: { description: 'Additional file globs to be excluded from test', diff --git a/packages/vitest/src/node/config/resolveConfig.ts b/packages/vitest/src/node/config/resolveConfig.ts index 6246282707a1..b83426ad761c 100644 --- a/packages/vitest/src/node/config/resolveConfig.ts +++ b/packages/vitest/src/node/config/resolveConfig.ts @@ -578,7 +578,7 @@ export function resolveConfig( } } - if (resolved.diff) { + if (typeof resolved.diff === 'string') { resolved.diff = resolvePath(resolved.diff, resolved.root) resolved.forceRerunTriggers.push(resolved.diff) } diff --git a/packages/vitest/src/node/config/serializeConfig.ts b/packages/vitest/src/node/config/serializeConfig.ts index 249f4bbdaa1a..234e67a3be34 100644 --- a/packages/vitest/src/node/config/serializeConfig.ts +++ b/packages/vitest/src/node/config/serializeConfig.ts @@ -37,6 +37,7 @@ export function serializeConfig( pool: config.pool, expect: config.expect, snapshotSerializers: config.snapshotSerializers, + // TODO: non serializable function? diff: config.diff, retry: config.retry, disableConsoleIntercept: config.disableConsoleIntercept, diff --git a/packages/vitest/src/node/types/config.ts b/packages/vitest/src/node/types/config.ts index ab9b327d7264..1eae8e3c1bb8 100644 --- a/packages/vitest/src/node/types/config.ts +++ b/packages/vitest/src/node/types/config.ts @@ -2,6 +2,7 @@ import type { FakeTimerInstallOpts } from '@sinonjs/fake-timers' import type { PrettyFormatOptions } from '@vitest/pretty-format' import type { SequenceHooks, SequenceSetupFiles } from '@vitest/runner' import type { SnapshotStateOptions } from '@vitest/snapshot' +import type { SerializedDiffOptions } from '@vitest/utils/diff' import type { AliasOptions, ConfigEnv, DepOptimizationConfig, ServerOptions, UserConfig as ViteUserConfig } from 'vite' import type { ViteNodeServerOptions } from 'vite-node' import type { ChaiConfig } from '../../integrations/chai/config' @@ -563,7 +564,7 @@ export interface InlineConfig { /** * Path to a module which has a default export of diff config. */ - diff?: string + diff?: string | SerializedDiffOptions /** * Paths to snapshot serializer modules. @@ -979,7 +980,7 @@ export interface ResolvedConfig mode: VitestRunMode base?: string - diff?: string + diff?: string | SerializedDiffOptions bail?: number setupFiles: string[] diff --git a/packages/vitest/src/runtime/config.ts b/packages/vitest/src/runtime/config.ts index 922bb6f80e24..ef9b8d0b4f56 100644 --- a/packages/vitest/src/runtime/config.ts +++ b/packages/vitest/src/runtime/config.ts @@ -3,6 +3,7 @@ import type { PrettyFormatOptions } from '@vitest/pretty-format' import type { SequenceHooks, SequenceSetupFiles } from '@vitest/runner' import type { SnapshotUpdateState } from '@vitest/snapshot' import type { SnapshotEnvironment } from '@vitest/snapshot/environment' +import type { SerializedDiffOptions } from '@vitest/utils/diff' /** * Config that tests have access to. @@ -98,7 +99,7 @@ export interface SerializedConfig { showDiff?: boolean truncateThreshold?: number } | undefined - diff: string | undefined + diff: string | SerializedDiffOptions | undefined retry: number includeTaskLocation: boolean | undefined inspect: boolean | string | undefined diff --git a/packages/vitest/src/runtime/setup-common.ts b/packages/vitest/src/runtime/setup-common.ts index cdd6cbb63bd5..033816637cbd 100644 --- a/packages/vitest/src/runtime/setup-common.ts +++ b/packages/vitest/src/runtime/setup-common.ts @@ -47,6 +47,9 @@ export async function loadDiffConfig( config: SerializedConfig, executor: VitestExecutor, ) { + if (typeof config.diff === 'object') { + return config.diff + } if (typeof config.diff !== 'string') { return } diff --git a/test/config/fixtures/diff/basic.test.ts b/test/config/fixtures/diff/basic.test.ts new file mode 100644 index 000000000000..af61b0814634 --- /dev/null +++ b/test/config/fixtures/diff/basic.test.ts @@ -0,0 +1,20 @@ +import { expect, test } from 'vitest' + +test('large diff', () => { + const x = [...Array(30)].map((_, i) => i); + const y = [...x]; + y[0] = 1000; + y[15] = 2000; + y[29] = 3000; + expect(x).toEqual(y) +}) + +test("printBasicPrototype", () => { + expect({ + obj: { k: "foo" }, + arr: [1, 2] + }).toEqual({ + obj: { k: "bar" }, + arr: [1, 3] + }); +}) diff --git a/test/config/fixtures/diff/vite.config.ts b/test/config/fixtures/diff/vite.config.ts new file mode 100644 index 000000000000..2d94b473bfba --- /dev/null +++ b/test/config/fixtures/diff/vite.config.ts @@ -0,0 +1,10 @@ +import {defineConfig} from 'vitest/config' + +export default defineConfig({ + test: { + diff: { + // expand: false, + // printBasicPrototype: false, + } + } +}) diff --git a/test/config/test/__snapshots__/diff.test.ts.snap b/test/config/test/__snapshots__/diff.test.ts.snap new file mode 100644 index 000000000000..5ab25703726a --- /dev/null +++ b/test/config/test/__snapshots__/diff.test.ts.snap @@ -0,0 +1,111 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`inline diff options: { expand: false, printBasicPrototype: false } 1`] = ` +[ + "- Expected ++ Received + +@@ -1,7 +1,7 @@ + [ +- 1000, ++ 0, + 1, + 2, + 3, + 4, + 5, +@@ -12,11 +12,11 @@ + 10, + 11, + 12, + 13, + 14, +- 2000, ++ 15, + 16, + 17, + 18, + 19, + 20, +@@ -26,7 +26,7 @@ + 24, + 25, + 26, + 27, + 28, +- 3000, ++ 29, + ]", + "- Expected ++ Received + + { + "arr": [ + 1, +- 3, ++ 2, + ], + "obj": { +- "k": "bar", ++ "k": "foo", + }, + }", +] +`; + +exports[`inline diff options: undefined 1`] = ` +[ + "- Expected ++ Received + + Array [ +- 1000, ++ 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, +- 2000, ++ 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, +- 3000, ++ 29, + ]", + "- Expected ++ Received + + Object { + "arr": Array [ + 1, +- 3, ++ 2, + ], + "obj": Object { +- "k": "bar", ++ "k": "foo", + }, + }", +] +`; diff --git a/test/config/test/diff.test.ts b/test/config/test/diff.test.ts new file mode 100644 index 000000000000..059e1d0cacc5 --- /dev/null +++ b/test/config/test/diff.test.ts @@ -0,0 +1,19 @@ +import { stripVTControlCharacters } from 'node:util' +import { expect, test } from 'vitest' +import { runVitest } from '../../test-utils' + +test.for([ + [undefined], + [{ expand: false, printBasicPrototype: false }], +])(`inline diff options: %o`, async ([options]) => { + const { ctx } = await runVitest({ + root: './fixtures/diff', + diff: options, + }) + const errors = ctx!.state.getFiles().flatMap(f => + f.tasks.flatMap(t => t.result?.errors ?? []), + ) + expect( + errors.map(e => e.diff && stripVTControlCharacters(e.diff)), + ).matchSnapshot() +}) diff --git a/test/config/test/failures.test.ts b/test/config/test/failures.test.ts index 591a2bd2175e..1d98a7a1a673 100644 --- a/test/config/test/failures.test.ts +++ b/test/config/test/failures.test.ts @@ -247,7 +247,9 @@ test('coverage.autoUpdate cannot update thresholds when configuration file doesn }) test('boolean flag 100 should not crash CLI', async () => { - const { stderr } = await runVitestCli('--coverage.enabled', '--coverage.thresholds.100') + let { stderr } = await runVitestCli('--coverage.enabled', '--coverage.thresholds.100') + // non-zero coverage shows up, which is non-deterministic, so strip it. + stderr = stderr.replace(/\([0-9.]+%\) does/g, '(0%) does') expect(stderr).toMatch('ERROR: Coverage for lines (0%) does not meet global threshold (100%)') expect(stderr).toMatch('ERROR: Coverage for functions (0%) does not meet global threshold (100%)')