From effe9ef5229f8fbfecf0e4a897d9d3dfbb83afae Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Wed, 10 Jul 2024 10:52:00 +0200 Subject: [PATCH] chore: replace picocolors with tinyrainbow --- docs/config/index.md | 2 +- packages/browser/src/node/plugin.ts | 7 - packages/coverage-istanbul/package.json | 4 +- packages/coverage-istanbul/src/provider.ts | 2 +- packages/coverage-v8/package.json | 4 +- packages/coverage-v8/src/provider.ts | 2 +- packages/expect/package.json | 4 +- packages/expect/src/index.ts | 1 - packages/expect/src/jest-expect.ts | 20 +- packages/expect/src/jest-matcher-utils.ts | 15 +- packages/expect/src/types.ts | 2 +- packages/pretty-format/package.json | 42 ++ packages/pretty-format/rollup.config.js | 64 +++ packages/pretty-format/src/collections.ts | 234 ++++++++ packages/pretty-format/src/index.ts | 538 ++++++++++++++++++ .../src/plugins/AsymmetricMatcher.ts | 97 ++++ .../src/plugins/DOMCollection.ts | 75 +++ .../pretty-format/src/plugins/DOMElement.ts | 127 +++++ .../pretty-format/src/plugins/Immutable.ts | 202 +++++++ .../pretty-format/src/plugins/ReactElement.ts | 121 ++++ .../src/plugins/ReactTestComponent.ts | 83 +++ .../src/plugins/lib/escapeHTML.ts | 10 + .../pretty-format/src/plugins/lib/markup.ts | 100 ++++ packages/pretty-format/src/types.ts | 113 ++++ packages/pretty-format/tsconfig.json | 8 + packages/snapshot/package.json | 4 +- packages/snapshot/src/port/mockSerializer.ts | 2 +- packages/snapshot/src/port/plugins.ts | 4 +- packages/snapshot/src/port/state.ts | 2 +- packages/snapshot/src/port/utils.ts | 4 +- packages/snapshot/src/types/index.ts | 2 +- packages/ui/node/reporter.ts | 2 +- packages/ui/package.json | 4 +- packages/utils/package.json | 7 +- packages/utils/src/colors.ts | 126 ---- packages/utils/src/diff/diffLines.ts | 5 +- packages/utils/src/diff/diffStrings.ts | 5 +- packages/utils/src/diff/index.ts | 4 +- .../utils/src/diff/normalizeDiffOptions.ts | 6 +- packages/utils/src/diff/types.ts | 4 +- packages/utils/src/highlight.ts | 7 +- packages/utils/src/index.ts | 1 - packages/utils/src/stringify.ts | 4 +- packages/vite-node/package.json | 2 +- packages/vite-node/src/cli.ts | 2 +- packages/vite-node/src/debug.ts | 2 +- packages/vite-node/src/hmr/hmr.ts | 2 +- packages/vitest/package.json | 2 +- packages/vitest/rollup.config.js | 2 +- packages/vitest/src/create/browser/creator.ts | 2 +- packages/vitest/src/node/cli/cac.ts | 2 +- packages/vitest/src/node/config.ts | 2 +- packages/vitest/src/node/error.ts | 4 +- packages/vitest/src/node/hoistMocks.ts | 2 +- packages/vitest/src/node/logger.ts | 2 +- packages/vitest/src/node/packageInstaller.ts | 2 +- packages/vitest/src/node/reporters/base.ts | 2 +- .../node/reporters/benchmark/table/index.ts | 2 +- .../reporters/benchmark/table/tableRender.ts | 2 +- packages/vitest/src/node/reporters/default.ts | 2 +- .../node/reporters/renderers/dotRenderer.ts | 2 +- .../node/reporters/renderers/listRenderer.ts | 2 +- .../src/node/reporters/renderers/utils.ts | 2 +- packages/vitest/src/node/reporters/verbose.ts | 2 +- packages/vitest/src/node/stdin.ts | 2 +- packages/vitest/src/node/watch-filter.ts | 2 +- packages/vitest/src/runtime/console.ts | 5 +- packages/vitest/src/runtime/runVmTests.ts | 4 - packages/vitest/src/runtime/setup-node.ts | 3 - packages/vitest/src/types/config.ts | 2 +- packages/vitest/src/types/global.ts | 2 +- packages/vitest/src/types/matcher-utils.ts | 2 +- packages/vitest/src/utils/colors.ts | 3 +- pnpm-lock.yaml | 97 +++- test/core/package.json | 1 + .../__snapshots__/jest-expect.test.ts.snap | 160 +++--- .../test/__snapshots__/mocked.test.ts.snap | 16 +- test/core/test/diff.test.ts | 58 +- test/core/test/environments/jsdom.spec.ts | 14 +- test/core/test/injector-mock.test.ts | 2 +- test/core/test/jest-expect.test.ts | 27 +- test/core/test/jest-matcher-utils.test.ts | 13 +- test/core/test/mocked.test.ts | 31 +- test/core/test/utils.spec.ts | 12 +- tsconfig.base.json | 1 + 85 files changed, 2120 insertions(+), 451 deletions(-) create mode 100644 packages/pretty-format/package.json create mode 100644 packages/pretty-format/rollup.config.js create mode 100644 packages/pretty-format/src/collections.ts create mode 100644 packages/pretty-format/src/index.ts create mode 100644 packages/pretty-format/src/plugins/AsymmetricMatcher.ts create mode 100644 packages/pretty-format/src/plugins/DOMCollection.ts create mode 100644 packages/pretty-format/src/plugins/DOMElement.ts create mode 100644 packages/pretty-format/src/plugins/Immutable.ts create mode 100644 packages/pretty-format/src/plugins/ReactElement.ts create mode 100644 packages/pretty-format/src/plugins/ReactTestComponent.ts create mode 100644 packages/pretty-format/src/plugins/lib/escapeHTML.ts create mode 100644 packages/pretty-format/src/plugins/lib/markup.ts create mode 100644 packages/pretty-format/src/types.ts create mode 100644 packages/pretty-format/tsconfig.json delete mode 100644 packages/utils/src/colors.ts diff --git a/docs/config/index.md b/docs/config/index.md index 7015323dcf7f..0417edf7b5fb 100644 --- a/docs/config/index.md +++ b/docs/config/index.md @@ -2181,7 +2181,7 @@ Path to a diff config that will be used to generate diff interface. Useful if yo :::code-group ```ts [vitest.diff.ts] import type { DiffOptions } from 'vitest' -import c from 'picocolors' +import c from 'tinyrainbow' export default { aIndicator: c.bold('--'), diff --git a/packages/browser/src/node/plugin.ts b/packages/browser/src/node/plugin.ts index 69b72649597b..c9532dd8a943 100644 --- a/packages/browser/src/node/plugin.ts +++ b/packages/browser/src/node/plugin.ts @@ -183,16 +183,9 @@ export default (browserServer: BrowserServer, base = '/'): Plugin[] => { 'msw/browser', ], include: [ - 'vitest > @vitest/utils > pretty-format', - 'vitest > @vitest/snapshot > pretty-format', 'vitest > @vitest/snapshot > magic-string', - 'vitest > pretty-format', - 'vitest > pretty-format > ansi-styles', - 'vitest > pretty-format > ansi-regex', 'vitest > chai', 'vitest > chai > loupe', - 'vitest > @vitest/runner > pretty-format', - 'vitest > @vitest/utils > diff-sequences', 'vitest > @vitest/utils > loupe', '@vitest/browser > @testing-library/user-event', '@vitest/browser > @testing-library/dom', diff --git a/packages/coverage-istanbul/package.json b/packages/coverage-istanbul/package.json index 95507ce8fc7a..c8ba32019e67 100644 --- a/packages/coverage-istanbul/package.json +++ b/packages/coverage-istanbul/package.json @@ -52,8 +52,8 @@ "istanbul-lib-source-maps": "^5.0.6", "istanbul-reports": "^3.1.7", "magicast": "^0.3.4", - "picocolors": "^1.0.1", - "test-exclude": "^7.0.1" + "test-exclude": "^7.0.1", + "tinyrainbow": "^1.1.2" }, "devDependencies": { "@types/debug": "^4.1.12", diff --git a/packages/coverage-istanbul/src/provider.ts b/packages/coverage-istanbul/src/provider.ts index 480a54d47b77..ae909f519cb0 100644 --- a/packages/coverage-istanbul/src/provider.ts +++ b/packages/coverage-istanbul/src/provider.ts @@ -17,7 +17,7 @@ import { coverageConfigDefaults, } from 'vitest/config' import { BaseCoverageProvider } from 'vitest/coverage' -import c from 'picocolors' +import c from 'tinyrainbow' import { parseModule } from 'magicast' import createDebug from 'debug' import libReport from 'istanbul-lib-report' diff --git a/packages/coverage-v8/package.json b/packages/coverage-v8/package.json index 3d490f5d49ad..de3b5b8c7854 100644 --- a/packages/coverage-v8/package.json +++ b/packages/coverage-v8/package.json @@ -53,10 +53,10 @@ "istanbul-reports": "^3.1.7", "magic-string": "^0.30.10", "magicast": "^0.3.4", - "picocolors": "^1.0.1", "std-env": "^3.7.0", "strip-literal": "^2.1.0", - "test-exclude": "^7.0.1" + "test-exclude": "^7.0.1", + "tinyrainbow": "^1.1.2" }, "devDependencies": { "@types/debug": "^4.1.12", diff --git a/packages/coverage-v8/src/provider.ts b/packages/coverage-v8/src/provider.ts index ee431ef3d5f2..aa01dced6bfe 100644 --- a/packages/coverage-v8/src/provider.ts +++ b/packages/coverage-v8/src/provider.ts @@ -17,7 +17,7 @@ import MagicString from 'magic-string' import { parseModule } from 'magicast' import remapping from '@ampproject/remapping' import { normalize, resolve } from 'pathe' -import c from 'picocolors' +import c from 'tinyrainbow' import { provider } from 'std-env' import { stripLiteral } from 'strip-literal' import createDebug from 'debug' diff --git a/packages/expect/package.json b/packages/expect/package.json index c9ea27134284..aceef616a79d 100644 --- a/packages/expect/package.json +++ b/packages/expect/package.json @@ -36,12 +36,12 @@ "dependencies": { "@vitest/spy": "workspace:*", "@vitest/utils": "workspace:*", - "chai": "^5.1.1" + "chai": "^5.1.1", + "tinyrainbow": "^1.1.2" }, "devDependencies": { "@types/chai": "4.3.6", "@vitest/runner": "workspace:*", - "picocolors": "^1.0.1", "rollup-plugin-copy": "^3.5.0" } } diff --git a/packages/expect/src/index.ts b/packages/expect/src/index.ts index f9d311afb51b..edd1807d14c5 100644 --- a/packages/expect/src/index.ts +++ b/packages/expect/src/index.ts @@ -6,4 +6,3 @@ export { getState, setState } from './state' export { JestChaiExpect } from './jest-expect' export { addCustomEqualityTesters } from './jest-matcher-utils' export { JestExtend } from './jest-extend' -export { setupColors } from '@vitest/utils' diff --git a/packages/expect/src/jest-expect.ts b/packages/expect/src/jest-expect.ts index cbf299cbc440..0efba5b1af16 100644 --- a/packages/expect/src/jest-expect.ts +++ b/packages/expect/src/jest-expect.ts @@ -1,4 +1,5 @@ -import { assertTypes, getColors } from '@vitest/utils' +import { assertTypes } from '@vitest/utils' +import c from 'tinyrainbow' import type { Constructable } from '@vitest/utils' import type { MockInstance, MockResult, MockSettledResult } from '@vitest/spy' import { isMockFunction } from '@vitest/spy' @@ -35,7 +36,6 @@ declare class DOMTokenList { // Jest Expect Compact export const JestChaiExpect: ChaiPlugin = (chai, utils) => { const { AssertionError } = chai - const c = () => getColors() const customTesters = getCustomEqualityTesters() function def( @@ -530,10 +530,10 @@ export const JestChaiExpect: ChaiPlugin = (chai, utils) => { showActualCall?: any, ) => { if (spy.mock.calls) { - msg += c().gray( + msg += c.gray( `\n\nReceived: \n\n${spy.mock.calls .map((callArg, i) => { - let methodCall = c().bold( + let methodCall = c.bold( ` ${ordinalOf(i + 1)} ${spy.getMockName()} call:\n\n`, ) if (showActualCall) { @@ -554,8 +554,8 @@ export const JestChaiExpect: ChaiPlugin = (chai, utils) => { .join('\n')}`, ) } - msg += c().gray( - `\n\nNumber of calls: ${c().bold(spy.mock.calls.length)}\n`, + msg += c.gray( + `\n\nNumber of calls: ${c.bold(spy.mock.calls.length)}\n`, ) return msg } @@ -565,10 +565,10 @@ export const JestChaiExpect: ChaiPlugin = (chai, utils) => { msg: string, showActualReturn?: any, ) => { - msg += c().gray( + msg += c.gray( `\n\nReceived: \n\n${results .map((callReturn, i) => { - let methodCall = c().bold( + let methodCall = c.bold( ` ${ordinalOf(i + 1)} ${spy.getMockName()} call return:\n\n`, ) if (showActualReturn) { @@ -588,8 +588,8 @@ export const JestChaiExpect: ChaiPlugin = (chai, utils) => { }) .join('\n')}`, ) - msg += c().gray( - `\n\nNumber of calls: ${c().bold(spy.mock.calls.length)}\n`, + msg += c.gray( + `\n\nNumber of calls: ${c.bold(spy.mock.calls.length)}\n`, ) return msg } diff --git a/packages/expect/src/jest-matcher-utils.ts b/packages/expect/src/jest-matcher-utils.ts index acf05fd05055..b206802f3223 100644 --- a/packages/expect/src/jest-matcher-utils.ts +++ b/packages/expect/src/jest-matcher-utils.ts @@ -1,4 +1,5 @@ -import { getColors, getType, stringify } from '@vitest/utils' +import { getType, stringify } from '@vitest/utils' +import c from 'tinyrainbow' import type { MatcherHintOptions, Tester } from './types' import { JEST_MATCHERS_OBJECT } from './constants' @@ -6,13 +7,11 @@ export { diff } from '@vitest/utils/diff' export { stringify } export function getMatcherUtils() { - const c = () => getColors() - - const EXPECTED_COLOR = c().green - const RECEIVED_COLOR = c().red - const INVERTED_COLOR = c().inverse - const BOLD_WEIGHT = c().bold - const DIM_COLOR = c().dim + const EXPECTED_COLOR = c.green + const RECEIVED_COLOR = c.red + const INVERTED_COLOR = c.inverse + const BOLD_WEIGHT = c.bold + const DIM_COLOR = c.dim function matcherHint( matcherName: string, diff --git a/packages/expect/src/types.ts b/packages/expect/src/types.ts index 8929c8583603..47e0e1d07610 100644 --- a/packages/expect/src/types.ts +++ b/packages/expect/src/types.ts @@ -6,7 +6,7 @@ * */ -import type { Formatter } from 'picocolors/types' +import type { Formatter } from 'tinyrainbow' import type { Constructable } from '@vitest/utils' import type { diff, getMatcherUtils, stringify } from './jest-matcher-utils' diff --git a/packages/pretty-format/package.json b/packages/pretty-format/package.json new file mode 100644 index 000000000000..baa03e4b8265 --- /dev/null +++ b/packages/pretty-format/package.json @@ -0,0 +1,42 @@ +{ + "name": "@vitest/pretty-format", + "type": "module", + "version": "2.0.1", + "description": "Fork of pretty-format with support for ESM", + "license": "MIT", + "funding": "https://opencollective.com/vitest", + "homepage": "https://github.com/vitest-dev/vitest/tree/main/packages/utils#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/vitest-dev/vitest.git", + "directory": "packages/pretty-format" + }, + "bugs": { + "url": "https://github.com/vitest-dev/vitest/issues" + }, + "sideEffects": false, + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + }, + "./*": "./*" + }, + "main": "./dist/index.js", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "files": [ + "*.d.ts", + "dist" + ], + "scripts": { + "build": "rimraf dist && rollup -c", + "dev": "rollup -c --watch" + }, + "dependencies": { + "tinyrainbow": "^1.1.2" + }, + "devDependencies": { + "react-is": "^18.3.1" + } +} diff --git a/packages/pretty-format/rollup.config.js b/packages/pretty-format/rollup.config.js new file mode 100644 index 000000000000..ca26f0db2c37 --- /dev/null +++ b/packages/pretty-format/rollup.config.js @@ -0,0 +1,64 @@ +import { builtinModules, createRequire } from 'node:module' +import { defineConfig } from 'rollup' +import esbuild from 'rollup-plugin-esbuild' +import dts from 'rollup-plugin-dts' +import resolve from '@rollup/plugin-node-resolve' +import json from '@rollup/plugin-json' +import commonjs from '@rollup/plugin-commonjs' + +const require = createRequire(import.meta.url) +const pkg = require('./package.json') + +const entries = { + index: 'src/index.ts', +} + +const external = [ + ...builtinModules, + ...Object.keys(pkg.dependencies || {}), + ...Object.keys(pkg.peerDependencies || {}), +] + +const plugins = [ + resolve({ + preferBuiltins: true, + }), + json(), + esbuild({ + target: 'node14', + }), + commonjs(), +] + +export default defineConfig([ + { + input: entries, + output: { + dir: 'dist', + format: 'esm', + entryFileNames: '[name].js', + chunkFileNames: 'chunk-[name].js', + }, + external, + plugins, + onwarn, + }, + { + input: entries, + output: { + dir: 'dist', + entryFileNames: '[name].d.ts', + format: 'esm', + }, + external, + plugins: [dts({ respectExternal: true })], + onwarn, + }, +]) + +function onwarn(message) { + if (['EMPTY_BUNDLE', 'CIRCULAR_DEPENDENCY'].includes(message.code)) { + return + } + console.error(message) +} diff --git a/packages/pretty-format/src/collections.ts b/packages/pretty-format/src/collections.ts new file mode 100644 index 000000000000..b17e17fe8bcb --- /dev/null +++ b/packages/pretty-format/src/collections.ts @@ -0,0 +1,234 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import type { CompareKeys, Config, Printer, Refs } from './types' + +function getKeysOfEnumerableProperties(object: Record, compareKeys: CompareKeys) { + const rawKeys = Object.keys(object) + const keys: Array + = compareKeys === null ? rawKeys : rawKeys.sort(compareKeys) + + if (Object.getOwnPropertySymbols) { + for (const symbol of Object.getOwnPropertySymbols(object)) { + if (Object.getOwnPropertyDescriptor(object, symbol)!.enumerable) { + keys.push(symbol) + } + } + } + + return keys as Array +} + +/** + * Return entries (for example, of a map) + * with spacing, indentation, and comma + * without surrounding punctuation (for example, braces) + */ +export function printIteratorEntries( + iterator: Iterator<[unknown, unknown]>, + config: Config, + indentation: string, + depth: number, + refs: Refs, + printer: Printer, + separator = ': ', +): string { + let result = '' + let width = 0 + let current = iterator.next() + + if (!current.done) { + result += config.spacingOuter + + const indentationNext = indentation + config.indent + + while (!current.done) { + result += indentationNext + + if (width++ === config.maxWidth) { + result += '…' + break + } + + const name = printer( + current.value[0], + config, + indentationNext, + depth, + refs, + ) + const value = printer( + current.value[1], + config, + indentationNext, + depth, + refs, + ) + + result += name + separator + value + + current = iterator.next() + + if (!current.done) { + result += `,${config.spacingInner}` + } + else if (!config.min) { + result += ',' + } + } + + result += config.spacingOuter + indentation + } + + return result +} + +/** + * Return values (for example, of a set) + * with spacing, indentation, and comma + * without surrounding punctuation (braces or brackets) + */ +export function printIteratorValues( + iterator: Iterator, + config: Config, + indentation: string, + depth: number, + refs: Refs, + printer: Printer, +): string { + let result = '' + let width = 0 + let current = iterator.next() + + if (!current.done) { + result += config.spacingOuter + + const indentationNext = indentation + config.indent + + while (!current.done) { + result += indentationNext + + if (width++ === config.maxWidth) { + result += '…' + break + } + + result += printer(current.value, config, indentationNext, depth, refs) + + current = iterator.next() + + if (!current.done) { + result += `,${config.spacingInner}` + } + else if (!config.min) { + result += ',' + } + } + + result += config.spacingOuter + indentation + } + + return result +} + +/** + * Return items (for example, of an array) + * with spacing, indentation, and comma + * without surrounding punctuation (for example, brackets) + */ +export function printListItems( + list: ArrayLike | DataView | ArrayBuffer, + config: Config, + indentation: string, + depth: number, + refs: Refs, + printer: Printer, +): string { + let result = '' + list = list instanceof ArrayBuffer ? new DataView(list) : list + const isDataView = (l: unknown): l is DataView => l instanceof DataView + const length = isDataView(list) ? list.byteLength : list.length + + if (length > 0) { + result += config.spacingOuter + + const indentationNext = indentation + config.indent + + for (let i = 0; i < length; i++) { + result += indentationNext + + if (i === config.maxWidth) { + result += '…' + break + } + + if (isDataView(list) || i in list) { + result += printer( + isDataView(list) ? list.getInt8(i) : list[i], + config, + indentationNext, + depth, + refs, + ) + } + + if (i < length - 1) { + result += `,${config.spacingInner}` + } + else if (!config.min) { + result += ',' + } + } + + result += config.spacingOuter + indentation + } + + return result +} + +/** + * Return properties of an object + * with spacing, indentation, and comma + * without surrounding punctuation (for example, braces) + */ +export function printObjectProperties( + val: Record, + config: Config, + indentation: string, + depth: number, + refs: Refs, + printer: Printer, +): string { + let result = '' + const keys = getKeysOfEnumerableProperties(val, config.compareKeys) + + if (keys.length > 0) { + result += config.spacingOuter + + const indentationNext = indentation + config.indent + + for (let i = 0; i < keys.length; i++) { + const key = keys[i] + const name = printer(key, config, indentationNext, depth, refs) + const value = printer(val[key], config, indentationNext, depth, refs) + + result += `${indentationNext + name}: ${value}` + + if (i < keys.length - 1) { + result += `,${config.spacingInner}` + } + else if (!config.min) { + result += ',' + } + } + + result += config.spacingOuter + indentation + } + + return result +} diff --git a/packages/pretty-format/src/index.ts b/packages/pretty-format/src/index.ts new file mode 100644 index 000000000000..875bc00d7431 --- /dev/null +++ b/packages/pretty-format/src/index.ts @@ -0,0 +1,538 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import styles from 'tinyrainbow' +import { + printIteratorEntries, + printIteratorValues, + printListItems, + printObjectProperties, +} from './collections' +import AsymmetricMatcher from './plugins/AsymmetricMatcher' +import DOMCollection from './plugins/DOMCollection' +import DOMElement from './plugins/DOMElement' +import Immutable from './plugins/Immutable' +import ReactElement from './plugins/ReactElement' +import ReactTestComponent from './plugins/ReactTestComponent' +import type { + Colors, + Config, + NewPlugin, + Options, + OptionsReceived, + Plugin, + Plugins, + Refs, + Theme, +} from './types' + +export type { + Colors, + CompareKeys, + Config, + Options, + OptionsReceived, + OldPlugin, + NewPlugin, + Plugin, + Plugins, + PrettyFormatOptions, + Printer, + Refs, + Theme, +} from './types' + +const toString = Object.prototype.toString +const toISOString = Date.prototype.toISOString +const errorToString = Error.prototype.toString +const regExpToString = RegExp.prototype.toString + +/** + * Explicitly comparing typeof constructor to function avoids undefined as name + * when mock identity-obj-proxy returns the key as the value for any key. + */ +function getConstructorName(val: new (...args: Array) => unknown) { + return (typeof val.constructor === 'function' && val.constructor.name) || 'Object' +} + +/** Is val is equal to global window object? Works even if it does not exist :) */ +function isWindow(val: unknown) { + return typeof window !== 'undefined' && val === window +} + +// eslint-disable-next-line regexp/no-super-linear-backtracking +const SYMBOL_REGEXP = /^Symbol\((.*)\)(.*)$/ +const NEWLINE_REGEXP = /\n/g + +class PrettyFormatPluginError extends Error { + constructor(message: string, stack: string) { + super(message) + this.stack = stack + this.name = this.constructor.name + } +} + +function isToStringedArrayType(toStringed: string): boolean { + return ( + toStringed === '[object Array]' + || toStringed === '[object ArrayBuffer]' + || toStringed === '[object DataView]' + || toStringed === '[object Float32Array]' + || toStringed === '[object Float64Array]' + || toStringed === '[object Int8Array]' + || toStringed === '[object Int16Array]' + || toStringed === '[object Int32Array]' + || toStringed === '[object Uint8Array]' + || toStringed === '[object Uint8ClampedArray]' + || toStringed === '[object Uint16Array]' + || toStringed === '[object Uint32Array]' + ) +} + +function printNumber(val: number): string { + return Object.is(val, -0) ? '-0' : String(val) +} + +function printBigInt(val: bigint): string { + return String(`${val}n`) +} + +function printFunction(val: Function, printFunctionName: boolean): string { + if (!printFunctionName) { + return '[Function]' + } + return `[Function ${val.name || 'anonymous'}]` +} + +function printSymbol(val: symbol): string { + return String(val).replace(SYMBOL_REGEXP, 'Symbol($1)') +} + +function printError(val: Error): string { + return `[${errorToString.call(val)}]` +} + +/** + * The first port of call for printing an object, handles most of the + * data-types in JS. + */ +function printBasicValue( + val: any, + printFunctionName: boolean, + escapeRegex: boolean, + escapeString: boolean, +): string | null { + if (val === true || val === false) { + return `${val}` + } + if (val === undefined) { + return 'undefined' + } + if (val === null) { + return 'null' + } + + const typeOf = typeof val + + if (typeOf === 'number') { + return printNumber(val) + } + if (typeOf === 'bigint') { + return printBigInt(val) + } + if (typeOf === 'string') { + if (escapeString) { + return `"${val.replaceAll(/"|\\/g, '\\$&')}"` + } + return `"${val}"` + } + if (typeOf === 'function') { + return printFunction(val, printFunctionName) + } + if (typeOf === 'symbol') { + return printSymbol(val) + } + + const toStringed = toString.call(val) + + if (toStringed === '[object WeakMap]') { + return 'WeakMap {}' + } + if (toStringed === '[object WeakSet]') { + return 'WeakSet {}' + } + if ( + toStringed === '[object Function]' + || toStringed === '[object GeneratorFunction]' + ) { + return printFunction(val, printFunctionName) + } + if (toStringed === '[object Symbol]') { + return printSymbol(val) + } + if (toStringed === '[object Date]') { + return Number.isNaN(+val) ? 'Date { NaN }' : toISOString.call(val) + } + if (toStringed === '[object Error]') { + return printError(val) + } + if (toStringed === '[object RegExp]') { + if (escapeRegex) { + // https://github.com/benjamingr/RegExp.escape/blob/main/polyfill.js + return regExpToString.call(val).replaceAll(/[$()*+.?[\\\]^{|}]/g, '\\$&') + } + return regExpToString.call(val) + } + + if (val instanceof Error) { + return printError(val) + } + + return null +} + +/** + * Handles more complex objects ( such as objects with circular references. + * maps and sets etc ) + */ +function printComplexValue( + val: any, + config: Config, + indentation: string, + depth: number, + refs: Refs, + hasCalledToJSON?: boolean, +): string { + if (refs.includes(val)) { + return '[Circular]' + } + refs = [...refs] + refs.push(val) + + const hitMaxDepth = ++depth > config.maxDepth + const min = config.min + + if ( + config.callToJSON + && !hitMaxDepth + && val.toJSON + && typeof val.toJSON === 'function' + && !hasCalledToJSON + ) { + return printer(val.toJSON(), config, indentation, depth, refs, true) + } + + const toStringed = toString.call(val) + if (toStringed === '[object Arguments]') { + return hitMaxDepth + ? '[Arguments]' + : `${min ? '' : 'Arguments '}[${printListItems( + val, + config, + indentation, + depth, + refs, + printer, + )}]` + } + if (isToStringedArrayType(toStringed)) { + return hitMaxDepth + ? `[${val.constructor.name}]` + : `${ + min + ? '' + : !config.printBasicPrototype && val.constructor.name === 'Array' + ? '' + : `${val.constructor.name} ` + }[${printListItems(val, config, indentation, depth, refs, printer)}]` + } + if (toStringed === '[object Map]') { + return hitMaxDepth + ? '[Map]' + : `Map {${printIteratorEntries( + val.entries(), + config, + indentation, + depth, + refs, + printer, + ' => ', + )}}` + } + if (toStringed === '[object Set]') { + return hitMaxDepth + ? '[Set]' + : `Set {${printIteratorValues( + val.values(), + config, + indentation, + depth, + refs, + printer, + )}}` + } + + // Avoid failure to serialize global window object in jsdom test environment. + // For example, not even relevant if window is prop of React element. + return hitMaxDepth || isWindow(val) + ? `[${getConstructorName(val)}]` + : `${ + min + ? '' + : !config.printBasicPrototype && getConstructorName(val) === 'Object' + ? '' + : `${getConstructorName(val)} ` + }{${printObjectProperties( + val, + config, + indentation, + depth, + refs, + printer, + )}}` +} + +function isNewPlugin(plugin: Plugin): plugin is NewPlugin { + return (plugin as NewPlugin).serialize != null +} + +function printPlugin( + plugin: Plugin, + val: any, + config: Config, + indentation: string, + depth: number, + refs: Refs, +): string { + let printed + + try { + printed = isNewPlugin(plugin) + ? plugin.serialize(val, config, indentation, depth, refs, printer) + : plugin.print( + val, + valChild => printer(valChild, config, indentation, depth, refs), + (str) => { + const indentationNext = indentation + config.indent + return ( + indentationNext + + str.replaceAll(NEWLINE_REGEXP, `\n${indentationNext}`) + ) + }, + { + edgeSpacing: config.spacingOuter, + min: config.min, + spacing: config.spacingInner, + }, + config.colors, + ) + } + catch (error: any) { + throw new PrettyFormatPluginError(error.message, error.stack) + } + if (typeof printed !== 'string') { + throw new TypeError( + `pretty-format: Plugin must return type "string" but instead returned "${typeof printed}".`, + ) + } + return printed +} + +function findPlugin(plugins: Plugins, val: unknown) { + for (const plugin of plugins) { + try { + if (plugin.test(val)) { + return plugin + } + } + catch (error: any) { + throw new PrettyFormatPluginError(error.message, error.stack) + } + } + + return null +} + +function printer( + val: unknown, + config: Config, + indentation: string, + depth: number, + refs: Refs, + hasCalledToJSON?: boolean, +): string { + const plugin = findPlugin(config.plugins, val) + if (plugin !== null) { + return printPlugin(plugin, val, config, indentation, depth, refs) + } + + const basicResult = printBasicValue( + val, + config.printFunctionName, + config.escapeRegex, + config.escapeString, + ) + if (basicResult !== null) { + return basicResult + } + + return printComplexValue( + val, + config, + indentation, + depth, + refs, + hasCalledToJSON, + ) +} + +const DEFAULT_THEME: Theme = { + comment: 'gray', + content: 'reset', + prop: 'yellow', + tag: 'cyan', + value: 'green', +} + +const DEFAULT_THEME_KEYS = Object.keys(DEFAULT_THEME) as Array< + keyof typeof DEFAULT_THEME +> + +export const DEFAULT_OPTIONS = { + callToJSON: true, + compareKeys: undefined, + escapeRegex: false, + escapeString: true, + highlight: false, + indent: 2, + maxDepth: Number.POSITIVE_INFINITY, + maxWidth: Number.POSITIVE_INFINITY, + min: false, + plugins: [], + printBasicPrototype: true, + printFunctionName: true, + theme: DEFAULT_THEME, +} satisfies Options + +function validateOptions(options: OptionsReceived) { + for (const key of Object.keys(options)) { + if (!Object.prototype.hasOwnProperty.call(DEFAULT_OPTIONS, key)) { + throw new Error(`pretty-format: Unknown option "${key}".`) + } + } + + if (options.min && options.indent !== undefined && options.indent !== 0) { + throw new Error( + 'pretty-format: Options "min" and "indent" cannot be used together.', + ) + } +} + +function getColorsHighlight(): Colors { + return DEFAULT_THEME_KEYS.reduce((colors, key) => { + const value = DEFAULT_THEME[key] + const color = value && (styles as any)[value] + if ( + color + && typeof color.close === 'string' + && typeof color.open === 'string' + ) { + colors[key] = color + } + else { + throw new Error( + `pretty-format: Option "theme" has a key "${key}" whose value "${value}" is undefined in ansi-styles.`, + ) + } + return colors + }, Object.create(null)) +} + +function getColorsEmpty(): Colors { + return DEFAULT_THEME_KEYS.reduce((colors, key) => { + colors[key] = { close: '', open: '' } + return colors + }, Object.create(null)) +} + +function getPrintFunctionName(options?: OptionsReceived) { + return options?.printFunctionName ?? DEFAULT_OPTIONS.printFunctionName +} + +function getEscapeRegex(options?: OptionsReceived) { + return options?.escapeRegex ?? DEFAULT_OPTIONS.escapeRegex +} + +function getEscapeString(options?: OptionsReceived) { + return options?.escapeString ?? DEFAULT_OPTIONS.escapeString +} + +function getConfig(options?: OptionsReceived): Config { + return { + callToJSON: options?.callToJSON ?? DEFAULT_OPTIONS.callToJSON, + colors: options?.highlight ? getColorsHighlight() : getColorsEmpty(), + compareKeys: + typeof options?.compareKeys === 'function' || options?.compareKeys === null + ? options.compareKeys + : DEFAULT_OPTIONS.compareKeys, + escapeRegex: getEscapeRegex(options), + escapeString: getEscapeString(options), + indent: options?.min + ? '' + : createIndent(options?.indent ?? DEFAULT_OPTIONS.indent), + maxDepth: options?.maxDepth ?? DEFAULT_OPTIONS.maxDepth, + maxWidth: options?.maxWidth ?? DEFAULT_OPTIONS.maxWidth, + min: options?.min ?? DEFAULT_OPTIONS.min, + plugins: options?.plugins ?? DEFAULT_OPTIONS.plugins, + printBasicPrototype: options?.printBasicPrototype ?? true, + printFunctionName: getPrintFunctionName(options), + spacingInner: options?.min ? ' ' : '\n', + spacingOuter: options?.min ? '' : '\n', + } +} + +function createIndent(indent: number): string { + return Array.from({ length: indent + 1 }).join(' ') +} + +/** + * Returns a presentation string of your `val` object + * @param val any potential JavaScript object + * @param options Custom settings + */ +export function format(val: unknown, options?: OptionsReceived): string { + if (options) { + validateOptions(options) + if (options.plugins) { + const plugin = findPlugin(options.plugins, val) + if (plugin !== null) { + return printPlugin(plugin, val, getConfig(options), '', 0, []) + } + } + } + + const basicResult = printBasicValue( + val, + getPrintFunctionName(options), + getEscapeRegex(options), + getEscapeString(options), + ) + if (basicResult !== null) { + return basicResult + } + + return printComplexValue(val, getConfig(options), '', 0, []) +} + +export const plugins = { + AsymmetricMatcher, + DOMCollection, + DOMElement, + Immutable, + ReactElement, + ReactTestComponent, +} diff --git a/packages/pretty-format/src/plugins/AsymmetricMatcher.ts b/packages/pretty-format/src/plugins/AsymmetricMatcher.ts new file mode 100644 index 000000000000..e5dd1c2d36a6 --- /dev/null +++ b/packages/pretty-format/src/plugins/AsymmetricMatcher.ts @@ -0,0 +1,97 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { printListItems, printObjectProperties } from '../collections' +import type { Config, NewPlugin, Printer, Refs } from '../types' + +const asymmetricMatcher + = typeof Symbol === 'function' && Symbol.for + ? Symbol.for('jest.asymmetricMatcher') + : 0x13_57_A5 +const SPACE = ' ' + +export const serialize: NewPlugin['serialize'] = ( + val: any, + config: Config, + indentation: string, + depth: number, + refs: Refs, + printer: Printer, +) => { + const stringedValue = val.toString() + + if ( + stringedValue === 'ArrayContaining' + || stringedValue === 'ArrayNotContaining' + ) { + if (++depth > config.maxDepth) { + return `[${stringedValue}]` + } + return `${stringedValue + SPACE}[${printListItems( + val.sample, + config, + indentation, + depth, + refs, + printer, + )}]` + } + + if ( + stringedValue === 'ObjectContaining' + || stringedValue === 'ObjectNotContaining' + ) { + if (++depth > config.maxDepth) { + return `[${stringedValue}]` + } + return `${stringedValue + SPACE}{${printObjectProperties( + val.sample, + config, + indentation, + depth, + refs, + printer, + )}}` + } + + if ( + stringedValue === 'StringMatching' + || stringedValue === 'StringNotMatching' + ) { + return ( + stringedValue + + SPACE + + printer(val.sample, config, indentation, depth, refs) + ) + } + + if ( + stringedValue === 'StringContaining' + || stringedValue === 'StringNotContaining' + ) { + return ( + stringedValue + + SPACE + + printer(val.sample, config, indentation, depth, refs) + ) + } + + if (typeof val.toAsymmetricMatcher !== 'function') { + throw new TypeError( + `Asymmetric matcher ${val.constructor.name} does not implement toAsymmetricMatcher()`, + ) + } + + return val.toAsymmetricMatcher() +} + +export const test: NewPlugin['test'] = (val: any) => + val && val.$$typeof === asymmetricMatcher + +const plugin: NewPlugin = { serialize, test } + +export default plugin diff --git a/packages/pretty-format/src/plugins/DOMCollection.ts b/packages/pretty-format/src/plugins/DOMCollection.ts new file mode 100644 index 000000000000..053788ab8f07 --- /dev/null +++ b/packages/pretty-format/src/plugins/DOMCollection.ts @@ -0,0 +1,75 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { printListItems, printObjectProperties } from '../collections' +import type { Config, NewPlugin, Printer, Refs } from '../types' + +const SPACE = ' ' + +const OBJECT_NAMES = new Set(['DOMStringMap', 'NamedNodeMap']) +const ARRAY_REGEXP = /^(?:HTML\w*Collection|NodeList)$/ + +function testName(name: any) { + return OBJECT_NAMES.has(name) || ARRAY_REGEXP.test(name) +} + +export const test: NewPlugin['test'] = (val: object) => + val + && val.constructor + && !!val.constructor.name + && testName(val.constructor.name) + +function isNamedNodeMap(collection: object): collection is NamedNodeMap { + return collection.constructor.name === 'NamedNodeMap' +} + +export const serialize: NewPlugin['serialize'] = ( + collection: any | NamedNodeMap, + config: Config, + indentation: string, + depth: number, + refs: Refs, + printer: Printer, +) => { + const name = collection.constructor.name + if (++depth > config.maxDepth) { + return `[${name}]` + } + + return ( + (config.min ? '' : name + SPACE) + + (OBJECT_NAMES.has(name) + ? `{${printObjectProperties( + isNamedNodeMap(collection) + ? [...collection].reduce>( + (props, attribute) => { + props[attribute.name] = attribute.value + return props + }, + {}, + ) + : { ...collection }, + config, + indentation, + depth, + refs, + printer, + )}}` + : `[${printListItems( + [...collection], + config, + indentation, + depth, + refs, + printer, + )}]`) + ) +} + +const plugin: NewPlugin = { serialize, test } + +export default plugin diff --git a/packages/pretty-format/src/plugins/DOMElement.ts b/packages/pretty-format/src/plugins/DOMElement.ts new file mode 100644 index 000000000000..8a1e135fc58f --- /dev/null +++ b/packages/pretty-format/src/plugins/DOMElement.ts @@ -0,0 +1,127 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import type { Config, NewPlugin, Printer, Refs } from '../types' +import { + printChildren, + printComment, + printElement, + printElementAsLeaf, + printProps, + printText, +} from './lib/markup' + +const ELEMENT_NODE = 1 +const TEXT_NODE = 3 +const COMMENT_NODE = 8 +const FRAGMENT_NODE = 11 + +const ELEMENT_REGEXP = /^(?:(?:HTML|SVG)\w*)?Element$/ + +function testHasAttribute(val: any) { + try { + return typeof val.hasAttribute === 'function' && val.hasAttribute('is') + } + catch { + return false + } +} + +function testNode(val: any) { + const constructorName = val.constructor.name + const { nodeType, tagName } = val + const isCustomElement + = (typeof tagName === 'string' && tagName.includes('-')) + || testHasAttribute(val) + + return ( + (nodeType === ELEMENT_NODE + && (ELEMENT_REGEXP.test(constructorName) || isCustomElement)) + || (nodeType === TEXT_NODE && constructorName === 'Text') + || (nodeType === COMMENT_NODE && constructorName === 'Comment') + || (nodeType === FRAGMENT_NODE && constructorName === 'DocumentFragment') + ) +} + +export const test: NewPlugin['test'] = (val: any) => + val?.constructor?.name && testNode(val) + +type HandledType = Element | Text | Comment | DocumentFragment + +function nodeIsText(node: HandledType): node is Text { + return node.nodeType === TEXT_NODE +} + +function nodeIsComment(node: HandledType): node is Comment { + return node.nodeType === COMMENT_NODE +} + +function nodeIsFragment(node: HandledType): node is DocumentFragment { + return node.nodeType === FRAGMENT_NODE +} + +export const serialize: NewPlugin['serialize'] = ( + node: HandledType, + config: Config, + indentation: string, + depth: number, + refs: Refs, + printer: Printer, +) => { + if (nodeIsText(node)) { + return printText(node.data, config) + } + + if (nodeIsComment(node)) { + return printComment(node.data, config) + } + + const type = nodeIsFragment(node) + ? 'DocumentFragment' + : node.tagName.toLowerCase() + + if (++depth > config.maxDepth) { + return printElementAsLeaf(type, config) + } + + return printElement( + type, + printProps( + nodeIsFragment(node) + ? [] + : Array.from(node.attributes, attr => attr.name).sort(), + nodeIsFragment(node) + ? {} + : [...node.attributes].reduce>( + (props, attribute) => { + props[attribute.name] = attribute.value + return props + }, + {}, + ), + config, + indentation + config.indent, + depth, + refs, + printer, + ), + printChildren( + Array.prototype.slice.call(node.childNodes || node.children), + config, + indentation + config.indent, + depth, + refs, + printer, + ), + config, + indentation, + ) +} + +const plugin: NewPlugin = { serialize, test } + +export default plugin diff --git a/packages/pretty-format/src/plugins/Immutable.ts b/packages/pretty-format/src/plugins/Immutable.ts new file mode 100644 index 000000000000..ba980b78bd6e --- /dev/null +++ b/packages/pretty-format/src/plugins/Immutable.ts @@ -0,0 +1,202 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { printIteratorEntries, printIteratorValues } from '../collections' +import type { Config, NewPlugin, Printer, Refs } from '../types' + +// SENTINEL constants are from https://github.com/facebook/immutable-js +const IS_ITERABLE_SENTINEL = '@@__IMMUTABLE_ITERABLE__@@' +const IS_LIST_SENTINEL = '@@__IMMUTABLE_LIST__@@' +const IS_KEYED_SENTINEL = '@@__IMMUTABLE_KEYED__@@' +const IS_MAP_SENTINEL = '@@__IMMUTABLE_MAP__@@' +const IS_ORDERED_SENTINEL = '@@__IMMUTABLE_ORDERED__@@' +const IS_RECORD_SENTINEL = '@@__IMMUTABLE_RECORD__@@' // immutable v4 +const IS_SEQ_SENTINEL = '@@__IMMUTABLE_SEQ__@@' +const IS_SET_SENTINEL = '@@__IMMUTABLE_SET__@@' +const IS_STACK_SENTINEL = '@@__IMMUTABLE_STACK__@@' + +const getImmutableName = (name: string) => `Immutable.${name}` +const printAsLeaf = (name: string) => `[${name}]` +const SPACE = ' ' +const LAZY = '…' // Seq is lazy if it calls a method like filter + +function printImmutableEntries( + val: any, + config: Config, + indentation: string, + depth: number, + refs: Refs, + printer: Printer, + type: string, +): string { + return ++depth > config.maxDepth + ? printAsLeaf(getImmutableName(type)) + : `${getImmutableName(type) + SPACE}{${printIteratorEntries( + val.entries(), + config, + indentation, + depth, + refs, + printer, + )}}` +} + +// Record has an entries method because it is a collection in immutable v3. +// Return an iterator for Immutable Record from version v3 or v4. +function getRecordEntries(val: any): Iterator { + let i = 0 + return { + next() { + if (i < val._keys.length) { + const key = val._keys[i++] + return { done: false, value: [key, val.get(key)] } + } + return { done: true, value: undefined } + }, + } +} + +function printImmutableRecord(val: any, config: Config, indentation: string, depth: number, refs: Refs, printer: Printer): string { + // _name property is defined only for an Immutable Record instance + // which was constructed with a second optional descriptive name arg + const name = getImmutableName(val._name || 'Record') + return ++depth > config.maxDepth + ? printAsLeaf(name) + : `${name + SPACE}{${printIteratorEntries( + getRecordEntries(val), + config, + indentation, + depth, + refs, + printer, + )}}` +} + +function printImmutableSeq(val: any, config: Config, indentation: string, depth: number, refs: Refs, printer: Printer): string { + const name = getImmutableName('Seq') + + if (++depth > config.maxDepth) { + return printAsLeaf(name) + } + + if (val[IS_KEYED_SENTINEL]) { + return `${name + SPACE}{${ + // from Immutable collection of entries or from ECMAScript object + val._iter || val._object + ? printIteratorEntries( + val.entries(), + config, + indentation, + depth, + refs, + printer, + ) + : LAZY + }}` + } + + return `${name + SPACE}[${ + val._iter // from Immutable collection of values + || val._array // from ECMAScript array + || val._collection // from ECMAScript collection in immutable v4 + || val._iterable // from ECMAScript collection in immutable v3 + ? printIteratorValues( + val.values(), + config, + indentation, + depth, + refs, + printer, + ) + : LAZY + }]` +} + +function printImmutableValues(val: any, config: Config, indentation: string, depth: number, refs: Refs, printer: Printer, type: string): string { + return ++depth > config.maxDepth + ? printAsLeaf(getImmutableName(type)) + : `${getImmutableName(type) + SPACE}[${printIteratorValues( + val.values(), + config, + indentation, + depth, + refs, + printer, + )}]` +} + +export const serialize: NewPlugin['serialize'] = ( + val: any, + config: Config, + indentation: string, + depth: number, + refs: Refs, + printer: Printer, +) => { + if (val[IS_MAP_SENTINEL]) { + return printImmutableEntries( + val, + config, + indentation, + depth, + refs, + printer, + val[IS_ORDERED_SENTINEL] ? 'OrderedMap' : 'Map', + ) + } + + if (val[IS_LIST_SENTINEL]) { + return printImmutableValues( + val, + config, + indentation, + depth, + refs, + printer, + 'List', + ) + } + if (val[IS_SET_SENTINEL]) { + return printImmutableValues( + val, + config, + indentation, + depth, + refs, + printer, + val[IS_ORDERED_SENTINEL] ? 'OrderedSet' : 'Set', + ) + } + if (val[IS_STACK_SENTINEL]) { + return printImmutableValues( + val, + config, + indentation, + depth, + refs, + printer, + 'Stack', + ) + } + + if (val[IS_SEQ_SENTINEL]) { + return printImmutableSeq(val, config, indentation, depth, refs, printer) + } + + // For compatibility with immutable v3 and v4, let record be the default. + return printImmutableRecord(val, config, indentation, depth, refs, printer) +} + +// Explicitly comparing sentinel properties to true avoids false positive +// when mock identity-obj-proxy returns the key as the value for any key. +export const test: NewPlugin['test'] = (val: any) => + val + && (val[IS_ITERABLE_SENTINEL] === true || val[IS_RECORD_SENTINEL] === true) + +const plugin: NewPlugin = { serialize, test } + +export default plugin diff --git a/packages/pretty-format/src/plugins/ReactElement.ts b/packages/pretty-format/src/plugins/ReactElement.ts new file mode 100644 index 000000000000..8a24ca7bc120 --- /dev/null +++ b/packages/pretty-format/src/plugins/ReactElement.ts @@ -0,0 +1,121 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import * as ReactIs from 'react-is' +import type { Config, NewPlugin, Printer, Refs } from '../types' +import { + printChildren, + printElement, + printElementAsLeaf, + printProps, +} from './lib/markup' + +// Given element.props.children, or subtree during recursive traversal, +// return flattened array of children. +function getChildren(arg: unknown, children: Array = []) { + if (Array.isArray(arg)) { + for (const item of arg) { + getChildren(item, children) + } + } + else if (arg != null && arg !== false && arg !== '') { + children.push(arg) + } + return children +} + +function getType(element: any) { + const type = element.type + if (typeof type === 'string') { + return type + } + if (typeof type === 'function') { + return type.displayName || type.name || 'Unknown' + } + + if (ReactIs.isFragment(element)) { + return 'React.Fragment' + } + if (ReactIs.isSuspense(element)) { + return 'React.Suspense' + } + if (typeof type === 'object' && type !== null) { + if (ReactIs.isContextProvider(element)) { + return 'Context.Provider' + } + + if (ReactIs.isContextConsumer(element)) { + return 'Context.Consumer' + } + + if (ReactIs.isForwardRef(element)) { + if (type.displayName) { + return type.displayName + } + + const functionName = type.render.displayName || type.render.name || '' + + return functionName === '' ? 'ForwardRef' : `ForwardRef(${functionName})` + } + + if (ReactIs.isMemo(element)) { + const functionName + = type.displayName || type.type.displayName || type.type.name || '' + + return functionName === '' ? 'Memo' : `Memo(${functionName})` + } + } + return 'UNDEFINED' +} + +function getPropKeys(element: any) { + const { props } = element + + return Object.keys(props) + .filter(key => key !== 'children' && props[key] !== undefined) + .sort() +} + +export const serialize: NewPlugin['serialize'] = ( + element: any, + config: Config, + indentation: string, + depth: number, + refs: Refs, + printer: Printer, +) => + ++depth > config.maxDepth + ? printElementAsLeaf(getType(element), config) + : printElement( + getType(element), + printProps( + getPropKeys(element), + element.props, + config, + indentation + config.indent, + depth, + refs, + printer, + ), + printChildren( + getChildren(element.props.children), + config, + indentation + config.indent, + depth, + refs, + printer, + ), + config, + indentation, + ) + +export const test: NewPlugin['test'] = (val: unknown) => + val != null && ReactIs.isElement(val) + +const plugin: NewPlugin = { serialize, test } + +export default plugin diff --git a/packages/pretty-format/src/plugins/ReactTestComponent.ts b/packages/pretty-format/src/plugins/ReactTestComponent.ts new file mode 100644 index 000000000000..8903278f4034 --- /dev/null +++ b/packages/pretty-format/src/plugins/ReactTestComponent.ts @@ -0,0 +1,83 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import type { Config, NewPlugin, Printer, Refs } from '../types' +import { + printChildren, + printElement, + printElementAsLeaf, + printProps, +} from './lib/markup' + +export interface ReactTestObject { + $$typeof: symbol + type: string + props?: Record + children?: null | Array +} + +// Child can be `number` in Stack renderer but not in Fiber renderer. +type ReactTestChild = ReactTestObject | string | number + +const testSymbol + = typeof Symbol === 'function' && Symbol.for + ? Symbol.for('react.test.json') + : 0xE_A7_13_57 + +function getPropKeys(object: ReactTestObject) { + const { props } = object + + return props + ? Object.keys(props) + .filter(key => props[key] !== undefined) + .sort() + : [] +} + +export const serialize: NewPlugin['serialize'] = ( + object: ReactTestObject, + config: Config, + indentation: string, + depth: number, + refs: Refs, + printer: Printer, +) => + ++depth > config.maxDepth + ? printElementAsLeaf(object.type, config) + : printElement( + object.type, + object.props + ? printProps( + getPropKeys(object), + object.props, + config, + indentation + config.indent, + depth, + refs, + printer, + ) + : '', + object.children + ? printChildren( + object.children, + config, + indentation + config.indent, + depth, + refs, + printer, + ) + : '', + config, + indentation, + ) + +export const test: NewPlugin['test'] = val => + val && val.$$typeof === testSymbol + +const plugin: NewPlugin = { serialize, test } + +export default plugin diff --git a/packages/pretty-format/src/plugins/lib/escapeHTML.ts b/packages/pretty-format/src/plugins/lib/escapeHTML.ts new file mode 100644 index 000000000000..66b24b55c82c --- /dev/null +++ b/packages/pretty-format/src/plugins/lib/escapeHTML.ts @@ -0,0 +1,10 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +export default function escapeHTML(str: string): string { + return str.replaceAll('<', '<').replaceAll('>', '>') +} diff --git a/packages/pretty-format/src/plugins/lib/markup.ts b/packages/pretty-format/src/plugins/lib/markup.ts new file mode 100644 index 000000000000..8e62f1870fd4 --- /dev/null +++ b/packages/pretty-format/src/plugins/lib/markup.ts @@ -0,0 +1,100 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import type { Config, Printer, Refs } from '../../types' +import escapeHTML from './escapeHTML' + +// Return empty string if keys is empty. +export function printProps( + keys: Array, + props: Record, + config: Config, + indentation: string, + depth: number, + refs: Refs, + printer: Printer, +): string { + const indentationNext = indentation + config.indent + const colors = config.colors + return keys + .map((key) => { + const value = props[key] + let printed = printer(value, config, indentationNext, depth, refs) + + if (typeof value !== 'string') { + if (printed.includes('\n')) { + printed + = config.spacingOuter + + indentationNext + + printed + + config.spacingOuter + + indentation + } + printed = `{${printed}}` + } + + return `${ + config.spacingInner + + indentation + + colors.prop.open + + key + + colors.prop.close + }=${colors.value.open}${printed}${colors.value.close}` + }) + .join('') +} + +// Return empty string if children is empty. +export function printChildren(children: Array, config: Config, indentation: string, depth: number, refs: Refs, printer: Printer): string { + return children + .map( + child => + config.spacingOuter + + indentation + + (typeof child === 'string' + ? printText(child, config) + : printer(child, config, indentation, depth, refs)), + ) + .join('') +} + +export function printText(text: string, config: Config): string { + const contentColor = config.colors.content + return contentColor.open + escapeHTML(text) + contentColor.close +} + +export function printComment(comment: string, config: Config): string { + const commentColor = config.colors.comment + return `${commentColor.open}${ + commentColor.close + }` +} + +// Separate the functions to format props, children, and element, +// so a plugin could override a particular function, if needed. +// Too bad, so sad: the traditional (but unnecessary) space +// in a self-closing tagColor requires a second test of printedProps. +export function printElement(type: string, printedProps: string, printedChildren: string, config: Config, indentation: string): string { + const tagColor = config.colors.tag + return `${tagColor.open}<${type}${ + printedProps + && tagColor.close + + printedProps + + config.spacingOuter + + indentation + + tagColor.open + }${ + printedChildren + ? `>${tagColor.close}${printedChildren}${config.spacingOuter}${indentation}${tagColor.open}${tagColor.close}` +} + +export function printElementAsLeaf(type: string, config: Config): string { + const tagColor = config.colors.tag + return `${tagColor.open}<${type}${tagColor.close} …${tagColor.open} />${tagColor.close}` +} diff --git a/packages/pretty-format/src/types.ts b/packages/pretty-format/src/types.ts new file mode 100644 index 000000000000..bbfc77221659 --- /dev/null +++ b/packages/pretty-format/src/types.ts @@ -0,0 +1,113 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +export interface Colors { + comment: { close: string; open: string } + content: { close: string; open: string } + prop: { close: string; open: string } + tag: { close: string; open: string } + value: { close: string; open: string } +} +type Indent = (arg0: string) => string +export type Refs = Array +type Print = (arg0: unknown) => string + +export type Theme = Required<{ + comment?: string + content?: string + prop?: string + tag?: string + value?: string +}> + +export type CompareKeys = ((a: string, b: string) => number) | null | undefined + +type RequiredOptions = Required + +export interface Options + extends Omit { + compareKeys: CompareKeys + theme: Theme +} + +export interface PrettyFormatOptions { + callToJSON?: boolean + escapeRegex?: boolean + escapeString?: boolean + highlight?: boolean + indent?: number + maxDepth?: number + maxWidth?: number + min?: boolean + printBasicPrototype?: boolean + printFunctionName?: boolean + compareKeys?: CompareKeys + plugins?: Plugins +} + +export type OptionsReceived = PrettyFormatOptions + +export interface Config { + callToJSON: boolean + compareKeys: CompareKeys + colors: Colors + escapeRegex: boolean + escapeString: boolean + indent: string + maxDepth: number + maxWidth: number + min: boolean + plugins: Plugins + printBasicPrototype: boolean + printFunctionName: boolean + spacingInner: string + spacingOuter: string +} + +export type Printer = ( + val: unknown, + config: Config, + indentation: string, + depth: number, + refs: Refs, + hasCalledToJSON?: boolean, +) => string + +type Test = (arg0: any) => boolean + +export interface NewPlugin { + serialize: ( + val: any, + config: Config, + indentation: string, + depth: number, + refs: Refs, + printer: Printer, + ) => string + test: Test +} + +interface PluginOptions { + edgeSpacing: string + min: boolean + spacing: string +} + +export interface OldPlugin { + print: ( + val: unknown, + print: Print, + indent: Indent, + options: PluginOptions, + colors: Colors, + ) => string + test: Test +} + +export type Plugin = NewPlugin | OldPlugin + +export type Plugins = Array diff --git a/packages/pretty-format/tsconfig.json b/packages/pretty-format/tsconfig.json new file mode 100644 index 000000000000..42a2e6729e88 --- /dev/null +++ b/packages/pretty-format/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "lib": ["ESNext", "DOM", "DOM.Iterable"] + }, + "include": ["src/**/*"], + "exclude": ["**/dist/**"] +} diff --git a/packages/snapshot/package.json b/packages/snapshot/package.json index 01bd1507e0e7..29fd7e1da4d1 100644 --- a/packages/snapshot/package.json +++ b/packages/snapshot/package.json @@ -42,9 +42,9 @@ "dev": "rollup -c --watch" }, "dependencies": { + "@vitest/pretty-format": "workspace:*", "magic-string": "^0.30.10", - "pathe": "^1.1.2", - "pretty-format": "^29.7.0" + "pathe": "^1.1.2" }, "devDependencies": { "@types/natural-compare": "^1.4.3", diff --git a/packages/snapshot/src/port/mockSerializer.ts b/packages/snapshot/src/port/mockSerializer.ts index f7b68f91d66b..15a6abdcbced 100644 --- a/packages/snapshot/src/port/mockSerializer.ts +++ b/packages/snapshot/src/port/mockSerializer.ts @@ -7,7 +7,7 @@ * https://github.com/facebook/jest/blob/4eb4f6a59b6eae0e05b8e51dd8cd3fdca1c7aff1/packages/jest-snapshot/src/mockSerializer.ts#L4 */ -import type { NewPlugin } from 'pretty-format' +import type { NewPlugin } from '@vitest/pretty-format' export const serialize: NewPlugin['serialize'] = ( val, diff --git a/packages/snapshot/src/port/plugins.ts b/packages/snapshot/src/port/plugins.ts index 597888bec040..a72fdb1f2d26 100644 --- a/packages/snapshot/src/port/plugins.ts +++ b/packages/snapshot/src/port/plugins.ts @@ -8,8 +8,8 @@ import type { Plugin as PrettyFormatPlugin, Plugins as PrettyFormatPlugins, -} from 'pretty-format' -import { plugins as prettyFormatPlugins } from 'pretty-format' +} from '@vitest/pretty-format' +import { plugins as prettyFormatPlugins } from '@vitest/pretty-format' import MockSerializer from './mockSerializer' diff --git a/packages/snapshot/src/port/state.ts b/packages/snapshot/src/port/state.ts index f7c0d21bcaf4..0f47b4e5a82c 100644 --- a/packages/snapshot/src/port/state.ts +++ b/packages/snapshot/src/port/state.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import type { OptionsReceived as PrettyFormatOptions } from 'pretty-format' +import type { OptionsReceived as PrettyFormatOptions } from '@vitest/pretty-format' import type { ParsedStack } from '../../../utils/src/index' import { parseErrorStacktrace } from '../../../utils/src/source-map' import type { diff --git a/packages/snapshot/src/port/utils.ts b/packages/snapshot/src/port/utils.ts index 4dc4d84f33d2..d247616f2af6 100644 --- a/packages/snapshot/src/port/utils.ts +++ b/packages/snapshot/src/port/utils.ts @@ -6,8 +6,8 @@ */ import naturalCompare from 'natural-compare' -import type { OptionsReceived as PrettyFormatOptions } from 'pretty-format' -import { format as prettyFormat } from 'pretty-format' +import type { OptionsReceived as PrettyFormatOptions } from '@vitest/pretty-format' +import { format as prettyFormat } from '@vitest/pretty-format' import { isObject } from '../../../utils/src/index' import type { SnapshotData, SnapshotStateOptions } from '../types' import type { SnapshotEnvironment } from '../types/environment' diff --git a/packages/snapshot/src/types/index.ts b/packages/snapshot/src/types/index.ts index 0a11f6733f87..d1bf8af4fac8 100644 --- a/packages/snapshot/src/types/index.ts +++ b/packages/snapshot/src/types/index.ts @@ -1,7 +1,7 @@ import type { OptionsReceived as PrettyFormatOptions, Plugin as PrettyFormatPlugin, -} from 'pretty-format' +} from '@vitest/pretty-format' import type { RawSnapshotInfo } from '../port/rawSnapshot' import type { SnapshotEnvironment, diff --git a/packages/ui/node/reporter.ts b/packages/ui/node/reporter.ts index 2dffe8bcd1ae..bd9de737334f 100644 --- a/packages/ui/node/reporter.ts +++ b/packages/ui/node/reporter.ts @@ -3,7 +3,7 @@ import { fileURLToPath } from 'node:url' import { promisify } from 'node:util' import { gzip, constants as zlibConstants } from 'node:zlib' import { basename, dirname, relative, resolve } from 'pathe' -import c from 'picocolors' +import c from 'tinyrainbow' import fg from 'fast-glob' import { stringify } from 'flatted' import type { diff --git a/packages/ui/package.json b/packages/ui/package.json index 2095834acc44..d9d00087303a 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -52,8 +52,8 @@ "fflate": "^0.8.2", "flatted": "^3.3.1", "pathe": "^1.1.2", - "picocolors": "^1.0.1", - "sirv": "^2.0.4" + "sirv": "^2.0.4", + "tinyrainbow": "^1.1.2" }, "devDependencies": { "@faker-js/faker": "^8.4.1", diff --git a/packages/utils/package.json b/packages/utils/package.json index 9f6c2de39d45..4d915a7fecdc 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -64,14 +64,17 @@ "dev": "rollup -c --watch" }, "dependencies": { - "diff-sequences": "^29.6.3", + "@vitest/pretty-format": "workspace:*", "estree-walker": "^3.0.3", "loupe": "^3.1.1", - "pretty-format": "^29.7.0" + "tinyrainbow": "^1.1.2" }, "devDependencies": { "@jridgewell/trace-mapping": "^0.3.25", "@types/estree": "^1.0.5", + "@types/react-is": "^18.3.0", + "diff-sequences": "^29.6.3", + "react-is": "^18.3.1", "tinyhighlight": "^0.3.2" } } diff --git a/packages/utils/src/colors.ts b/packages/utils/src/colors.ts deleted file mode 100644 index 8301005f1604..000000000000 --- a/packages/utils/src/colors.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { SAFE_COLORS_SYMBOL } from './constants' - -const colorsMap = { - bold: ['\x1B[1m', '\x1B[22m', '\x1B[22m\x1B[1m'], - dim: ['\x1B[2m', '\x1B[22m', '\x1B[22m\x1B[2m'], - italic: ['\x1B[3m', '\x1B[23m'], - underline: ['\x1B[4m', '\x1B[24m'], - inverse: ['\x1B[7m', '\x1B[27m'], - hidden: ['\x1B[8m', '\x1B[28m'], - strikethrough: ['\x1B[9m', '\x1B[29m'], - black: ['\x1B[30m', '\x1B[39m'], - red: ['\x1B[31m', '\x1B[39m'], - green: ['\x1B[32m', '\x1B[39m'], - yellow: ['\x1B[33m', '\x1B[39m'], - blue: ['\x1B[34m', '\x1B[39m'], - magenta: ['\x1B[35m', '\x1B[39m'], - cyan: ['\x1B[36m', '\x1B[39m'], - white: ['\x1B[37m', '\x1B[39m'], - gray: ['\x1B[90m', '\x1B[39m'], - bgBlack: ['\x1B[40m', '\x1B[49m'], - bgRed: ['\x1B[41m', '\x1B[49m'], - bgGreen: ['\x1B[42m', '\x1B[49m'], - bgYellow: ['\x1B[43m', '\x1B[49m'], - bgBlue: ['\x1B[44m', '\x1B[49m'], - bgMagenta: ['\x1B[45m', '\x1B[49m'], - bgCyan: ['\x1B[46m', '\x1B[49m'], - bgWhite: ['\x1B[47m', '\x1B[49m'], -} as const - -export type ColorName = keyof typeof colorsMap -export interface ColorMethod { - (input: unknown): string - open: string - close: string -} -export type ColorsMethods = { - [Key in ColorName]: ColorMethod; -} - -export type Colors = ColorsMethods & { - isColorSupported: boolean - reset: (input: unknown) => string -} - -const colorsEntries = Object.entries(colorsMap) - -function string(str: unknown) { - return String(str) -} -string.open = '' -string.close = '' - -const defaultColors = /* #__PURE__ */ colorsEntries.reduce( - (acc, [key]) => { - acc[key as ColorName] = string - return acc - }, - { isColorSupported: false } as Colors, -) - -export function getDefaultColors(): Colors { - return { ...defaultColors } -} - -export function getColors(): Colors { - return (globalThis as any)[SAFE_COLORS_SYMBOL] || defaultColors -} - -export function createColors(isTTY = false): Colors { - const enabled - = typeof process !== 'undefined' - && !('NO_COLOR' in process.env || process.argv.includes('--no-color')) - && !('GITHUB_ACTIONS' in process.env) - && ('FORCE_COLOR' in process.env - || process.argv.includes('--color') - || process.platform === 'win32' - || (isTTY && process.env.TERM !== 'dumb') - || 'CI' in process.env) - - const replaceClose = ( - string: string, - close: string, - replace: string, - index: number, - ): string => { - let result = '' - let cursor = 0 - do { - result += string.substring(cursor, index) + replace - cursor = index + close.length - index = string.indexOf(close, cursor) - } while (~index) - return result + string.substring(cursor) - } - - const formatter = (open: string, close: string, replace = open) => { - const fn = (input: unknown) => { - const string = String(input) - const index = string.indexOf(close, open.length) - return ~index - ? open + replaceClose(string, close, replace, index) + close - : open + string + close - } - fn.open = open - fn.close = close - return fn - } - - // based on "https://github.com/alexeyraspopov/picocolors", but browser-friendly - const colorsObject = { - isColorSupported: enabled, - reset: enabled ? (s: string) => `\x1B[0m${s}\x1B[0m` : string, - } as Colors - - for (const [name, formatterArgs] of colorsEntries) { - colorsObject[name as ColorName] = enabled - ? formatter(...(formatterArgs as [string, string])) - : string - } - - return colorsObject -} - -export function setupColors(colors: Colors) { - (globalThis as any)[SAFE_COLORS_SYMBOL] = colors -} diff --git a/packages/utils/src/diff/diffLines.ts b/packages/utils/src/diff/diffLines.ts index a337f298dbf6..a88ec1ffdf26 100644 --- a/packages/utils/src/diff/diffLines.ts +++ b/packages/utils/src/diff/diffLines.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import * as diff from 'diff-sequences' +import diffSequences from 'diff-sequences' import { DIFF_DELETE, DIFF_EQUAL, DIFF_INSERT, Diff } from './cleanupSemantic' import { joinAlignedDiffsExpand, @@ -218,9 +218,6 @@ export function diffLinesRaw( } } - // @ts-expect-error wrong bundling - const diffSequences = diff.default.default || diff.default - diffSequences(aLength, bLength, isCommon, foundSubsequence) // After the last common subsequence, push remaining change items. diff --git a/packages/utils/src/diff/diffStrings.ts b/packages/utils/src/diff/diffStrings.ts index f344e5ec70fe..4047c8c082cf 100644 --- a/packages/utils/src/diff/diffStrings.ts +++ b/packages/utils/src/diff/diffStrings.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import * as diff from 'diff-sequences' +import diffSequences from 'diff-sequences' import { DIFF_DELETE, DIFF_EQUAL, DIFF_INSERT, Diff } from './cleanupSemantic' import type { DiffOptions } from './types' @@ -67,9 +67,6 @@ function diffStrings( diffs.push(new Diff(DIFF_EQUAL, b.slice(bCommon, bIndex))) } - // @ts-expect-error wrong bundling - const diffSequences = diff.default.default || diff.default - diffSequences(aLength, bLength, isCommon, foundSubsequence) // After the last common subsequence, push remaining change items. diff --git a/packages/utils/src/diff/index.ts b/packages/utils/src/diff/index.ts index ddf8f7fa2fc9..77a0da76ec67 100644 --- a/packages/utils/src/diff/index.ts +++ b/packages/utils/src/diff/index.ts @@ -7,11 +7,11 @@ // This is a fork of Jest's jest-diff package, but it doesn't depend on Node environment (like chalk). -import type { PrettyFormatOptions } from 'pretty-format' +import type { PrettyFormatOptions } from '@vitest/pretty-format' import { format as prettyFormat, plugins as prettyFormatPlugins, -} from 'pretty-format' +} from '@vitest/pretty-format' import { getType } from './getType' import { DIFF_DELETE, DIFF_EQUAL, DIFF_INSERT, Diff } from './cleanupSemantic' import { NO_DIFF_MESSAGE, SIMILAR_MESSAGE } from './constants' diff --git a/packages/utils/src/diff/normalizeDiffOptions.ts b/packages/utils/src/diff/normalizeDiffOptions.ts index 823f5570e1b5..660bab3e32ab 100644 --- a/packages/utils/src/diff/normalizeDiffOptions.ts +++ b/packages/utils/src/diff/normalizeDiffOptions.ts @@ -5,8 +5,8 @@ * LICENSE file in the root directory of this source tree. */ -import type { CompareKeys } from 'pretty-format' -import { getColors } from '../colors' +import type { CompareKeys } from '@vitest/pretty-format' +import c from 'tinyrainbow' import type { DiffOptions, DiffOptionsNormalized } from './types' export const noColor = (string: string): string => string @@ -15,8 +15,6 @@ const DIFF_CONTEXT_DEFAULT = 5 const DIFF_TRUNCATE_THRESHOLD_DEFAULT = 0 // not truncate function getDefaultOptions(): DiffOptionsNormalized { - const c = getColors() - return { aAnnotation: 'Expected', aColor: c.green, diff --git a/packages/utils/src/diff/types.ts b/packages/utils/src/diff/types.ts index d962c5eacc4f..951fcdbb27c1 100644 --- a/packages/utils/src/diff/types.ts +++ b/packages/utils/src/diff/types.ts @@ -4,9 +4,9 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ -import type { CompareKeys } from 'pretty-format' +import type { CompareKeys } from '@vitest/pretty-format' -export type DiffOptionsColor = (arg: string) => string // subset of picocolors type +export type DiffOptionsColor = (arg: string) => string export interface DiffOptions { aAnnotation?: string diff --git a/packages/utils/src/highlight.ts b/packages/utils/src/highlight.ts index 695a698e2eac..6ab5de815e61 100644 --- a/packages/utils/src/highlight.ts +++ b/packages/utils/src/highlight.ts @@ -1,8 +1,5 @@ import { type TokenColors, highlight as baseHighlight } from 'tinyhighlight' -import type { ColorName } from './colors' -import { getColors } from './colors' - -type Colors = Record string> +import c, { type Colors } from 'tinyrainbow' function getDefs(c: Colors): TokenColors { const Invalid = (text: string) => c.white(c.bgRed(c.bold(text))) @@ -45,6 +42,6 @@ export function highlight( ) { return baseHighlight(code, { jsx: options.jsx, - colors: getDefs(options.colors || getColors()), + colors: getDefs(options.colors || c), }) } diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index a95046cf4151..77b3fb30075e 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -5,7 +5,6 @@ export * from './timers' export * from './random' export * from './display' export * from './constants' -export * from './colors' export * from './base' export * from './offset' export * from './highlight' diff --git a/packages/utils/src/stringify.ts b/packages/utils/src/stringify.ts index cbb23db79a84..574d5ef70365 100644 --- a/packages/utils/src/stringify.ts +++ b/packages/utils/src/stringify.ts @@ -1,8 +1,8 @@ -import type { PrettyFormatOptions } from 'pretty-format' +import type { PrettyFormatOptions } from '@vitest/pretty-format' import { format as prettyFormat, plugins as prettyFormatPlugins, -} from 'pretty-format' +} from '@vitest/pretty-format' const { AsymmetricMatcher, diff --git a/packages/vite-node/package.json b/packages/vite-node/package.json index 1a21568c45c7..305ef3fcaaa5 100644 --- a/packages/vite-node/package.json +++ b/packages/vite-node/package.json @@ -85,7 +85,7 @@ "cac": "^6.7.14", "debug": "^4.3.5", "pathe": "^1.1.2", - "picocolors": "^1.0.1", + "tinyrainbow": "^1.1.2", "vite": "^5.0.0" }, "devDependencies": { diff --git a/packages/vite-node/src/cli.ts b/packages/vite-node/src/cli.ts index 88913197e874..cbb8c82c890a 100644 --- a/packages/vite-node/src/cli.ts +++ b/packages/vite-node/src/cli.ts @@ -1,6 +1,6 @@ import { resolve } from 'node:path' import cac from 'cac' -import c from 'picocolors' +import c from 'tinyrainbow' import { createServer, loadEnv } from 'vite' import { version } from '../package.json' import { ViteNodeServer } from './server' diff --git a/packages/vite-node/src/debug.ts b/packages/vite-node/src/debug.ts index 8db4eef05d5c..ebcdc78cb01c 100644 --- a/packages/vite-node/src/debug.ts +++ b/packages/vite-node/src/debug.ts @@ -2,7 +2,7 @@ import { existsSync, promises as fs } from 'node:fs' import { join, resolve } from 'pathe' import type { TransformResult } from 'vite' -import c from 'picocolors' +import c from 'tinyrainbow' import type { DebuggerOptions } from './types' function hashCode(s: string) { diff --git a/packages/vite-node/src/hmr/hmr.ts b/packages/vite-node/src/hmr/hmr.ts index d07f5892e75a..24709c460c69 100644 --- a/packages/vite-node/src/hmr/hmr.ts +++ b/packages/vite-node/src/hmr/hmr.ts @@ -2,7 +2,7 @@ import type { HMRPayload, Update } from 'vite/types/hmrPayload.js' import type { CustomEventMap } from 'vite/types/customEvent.js' -import c from 'picocolors' +import c from 'tinyrainbow' import createDebug from 'debug' import type { ViteNodeRunner } from '../client' import type { HotContext } from '../types' diff --git a/packages/vitest/package.json b/packages/vitest/package.json index 082808cac6e9..0ca37f9abd29 100644 --- a/packages/vitest/package.json +++ b/packages/vitest/package.json @@ -158,10 +158,10 @@ "execa": "^8.0.1", "magic-string": "^0.30.10", "pathe": "^1.1.2", - "picocolors": "^1.0.1", "std-env": "^3.7.0", "tinybench": "^2.8.0", "tinypool": "^1.0.0", + "tinyrainbow": "^1.1.2", "vite": "^5.0.0", "vite-node": "workspace:*", "why-is-node-running": "^2.2.2" diff --git a/packages/vitest/rollup.config.js b/packages/vitest/rollup.config.js index 133ec7fdab5e..76146f668528 100644 --- a/packages/vitest/rollup.config.js +++ b/packages/vitest/rollup.config.js @@ -8,7 +8,7 @@ import nodeResolve from '@rollup/plugin-node-resolve' import commonjs from '@rollup/plugin-commonjs' import json from '@rollup/plugin-json' import license from 'rollup-plugin-license' -import c from 'picocolors' +import c from 'tinyrainbow' import fg from 'fast-glob' import { defineConfig } from 'rollup' diff --git a/packages/vitest/src/create/browser/creator.ts b/packages/vitest/src/create/browser/creator.ts index cee94bbb3900..0515cdae2348 100644 --- a/packages/vitest/src/create/browser/creator.ts +++ b/packages/vitest/src/create/browser/creator.ts @@ -2,7 +2,7 @@ import { dirname, relative, resolve } from 'node:path' import { existsSync, readFileSync } from 'node:fs' import { writeFile } from 'node:fs/promises' import prompt from 'prompts' -import c from 'picocolors' +import c from 'tinyrainbow' import type { Agent } from '@antfu/install-pkg' import { detectPackageManager, installPackage } from '@antfu/install-pkg' import { findUp } from 'find-up' diff --git a/packages/vitest/src/node/cli/cac.ts b/packages/vitest/src/node/cli/cac.ts index fe3c61c8f61a..9a56c772fa0c 100644 --- a/packages/vitest/src/node/cli/cac.ts +++ b/packages/vitest/src/node/cli/cac.ts @@ -1,6 +1,6 @@ import { normalize } from 'pathe' import cac, { type CAC, type Command } from 'cac' -import c from 'picocolors' +import c from 'tinyrainbow' import { version } from '../../../package.json' with { type: 'json' } import { toArray } from '../../utils/base' import type { VitestRunMode } from '../../types' diff --git a/packages/vitest/src/node/config.ts b/packages/vitest/src/node/config.ts index 85359cc02fc5..e43a7596fde8 100644 --- a/packages/vitest/src/node/config.ts +++ b/packages/vitest/src/node/config.ts @@ -1,6 +1,6 @@ import { resolveModule } from 'local-pkg' import { normalize, relative, resolve } from 'pathe' -import c from 'picocolors' +import c from 'tinyrainbow' import type { ResolvedConfig as ResolvedViteConfig } from 'vite' import type { ApiConfig, diff --git a/packages/vitest/src/node/error.ts b/packages/vitest/src/node/error.ts index 70f9db6f2478..cfbbb3750d7e 100644 --- a/packages/vitest/src/node/error.ts +++ b/packages/vitest/src/node/error.ts @@ -2,7 +2,7 @@ import { existsSync, readFileSync } from 'node:fs' import { Writable } from 'node:stream' import { normalize, relative } from 'pathe' -import c from 'picocolors' +import c from 'tinyrainbow' import cliTruncate from 'cli-truncate' import { inspect } from '@vitest/utils' import stripAnsi from 'strip-ansi' @@ -319,7 +319,7 @@ function printErrorMessage(error: ErrorWithDiff, logger: Logger) { return } if (error.message.length > 5000) { - // Protect against infinite stack trace in picocolors + // Protect against infinite stack trace in tinyrainbow logger.error(`${c.red(c.bold(errorName))}: ${error.message}`) } else { diff --git a/packages/vitest/src/node/hoistMocks.ts b/packages/vitest/src/node/hoistMocks.ts index 0e4c48cadb2d..3305100c06ae 100644 --- a/packages/vitest/src/node/hoistMocks.ts +++ b/packages/vitest/src/node/hoistMocks.ts @@ -14,7 +14,7 @@ import type { import { findNodeAround } from 'acorn-walk' import type { PluginContext, ProgramNode } from 'rollup' import { esmWalker } from '@vitest/utils/ast' -import type { Colors } from '@vitest/utils' +import type { Colors } from 'tinyrainbow' import { highlightCode } from '../utils/colors' import { generateCodeFrame } from './error' diff --git a/packages/vitest/src/node/logger.ts b/packages/vitest/src/node/logger.ts index c2a5c6d7d6e3..b963b744d50b 100644 --- a/packages/vitest/src/node/logger.ts +++ b/packages/vitest/src/node/logger.ts @@ -1,7 +1,7 @@ import { Console } from 'node:console' import type { Writable } from 'node:stream' import { createLogUpdate } from 'log-update' -import c from 'picocolors' +import c from 'tinyrainbow' import { parseErrorStacktrace } from '@vitest/utils/source-map' import type { ErrorWithDiff, Task } from '../types' import type { TypeCheckError } from '../typecheck/typechecker' diff --git a/packages/vitest/src/node/packageInstaller.ts b/packages/vitest/src/node/packageInstaller.ts index 85ca05d3042c..5feb78fc14e2 100644 --- a/packages/vitest/src/node/packageInstaller.ts +++ b/packages/vitest/src/node/packageInstaller.ts @@ -1,6 +1,6 @@ import url from 'node:url' import { createRequire } from 'node:module' -import c from 'picocolors' +import c from 'tinyrainbow' import { isPackageExists } from 'local-pkg' import { isCI } from '../utils/env' diff --git a/packages/vitest/src/node/reporters/base.ts b/packages/vitest/src/node/reporters/base.ts index 70590fb208f5..5d6677c772a8 100644 --- a/packages/vitest/src/node/reporters/base.ts +++ b/packages/vitest/src/node/reporters/base.ts @@ -1,5 +1,5 @@ import { performance } from 'node:perf_hooks' -import c from 'picocolors' +import c from 'tinyrainbow' import { parseStacktrace } from '@vitest/utils/source-map' import { relative } from 'pathe' import type { diff --git a/packages/vitest/src/node/reporters/benchmark/table/index.ts b/packages/vitest/src/node/reporters/benchmark/table/index.ts index 3cb1f948dc17..1174a37e5259 100644 --- a/packages/vitest/src/node/reporters/benchmark/table/index.ts +++ b/packages/vitest/src/node/reporters/benchmark/table/index.ts @@ -1,5 +1,5 @@ import fs from 'node:fs' -import c from 'picocolors' +import c from 'tinyrainbow' import * as pathe from 'pathe' import type { TaskResultPack } from '@vitest/runner' import type { UserConsoleLog } from '../../../../types/general' diff --git a/packages/vitest/src/node/reporters/benchmark/table/tableRender.ts b/packages/vitest/src/node/reporters/benchmark/table/tableRender.ts index 3888f636910c..51458fb4a174 100644 --- a/packages/vitest/src/node/reporters/benchmark/table/tableRender.ts +++ b/packages/vitest/src/node/reporters/benchmark/table/tableRender.ts @@ -1,4 +1,4 @@ -import c from 'picocolors' +import c from 'tinyrainbow' import cliTruncate from 'cli-truncate' import stripAnsi from 'strip-ansi' import type { BenchmarkResult, Task } from '../../../../types' diff --git a/packages/vitest/src/node/reporters/default.ts b/packages/vitest/src/node/reporters/default.ts index a6b3dc513665..e7861377904d 100644 --- a/packages/vitest/src/node/reporters/default.ts +++ b/packages/vitest/src/node/reporters/default.ts @@ -1,4 +1,4 @@ -import c from 'picocolors' +import c from 'tinyrainbow' import type { UserConsoleLog } from '../../types/general' import { BaseReporter } from './base' import type { ListRendererOptions } from './renderers/listRenderer' diff --git a/packages/vitest/src/node/reporters/renderers/dotRenderer.ts b/packages/vitest/src/node/reporters/renderers/dotRenderer.ts index 9b39eee7b95b..2c8f6ca4da98 100644 --- a/packages/vitest/src/node/reporters/renderers/dotRenderer.ts +++ b/packages/vitest/src/node/reporters/renderers/dotRenderer.ts @@ -1,4 +1,4 @@ -import c from 'picocolors' +import c from 'tinyrainbow' import type { Task } from '../../../types' import { getTests } from '../../../utils' import type { Logger } from '../../logger' diff --git a/packages/vitest/src/node/reporters/renderers/listRenderer.ts b/packages/vitest/src/node/reporters/renderers/listRenderer.ts index 094eae57e6a7..a13841071936 100644 --- a/packages/vitest/src/node/reporters/renderers/listRenderer.ts +++ b/packages/vitest/src/node/reporters/renderers/listRenderer.ts @@ -1,4 +1,4 @@ -import c from 'picocolors' +import c from 'tinyrainbow' import cliTruncate from 'cli-truncate' import stripAnsi from 'strip-ansi' import type { diff --git a/packages/vitest/src/node/reporters/renderers/utils.ts b/packages/vitest/src/node/reporters/renderers/utils.ts index ea8cab848137..eb770bfff4da 100644 --- a/packages/vitest/src/node/reporters/renderers/utils.ts +++ b/packages/vitest/src/node/reporters/renderers/utils.ts @@ -1,5 +1,5 @@ import { basename, dirname, isAbsolute, relative } from 'pathe' -import c from 'picocolors' +import c from 'tinyrainbow' import stripAnsi from 'strip-ansi' import type { SnapshotSummary, Task } from '../../../types' import { slash } from '../../../utils/base' diff --git a/packages/vitest/src/node/reporters/verbose.ts b/packages/vitest/src/node/reporters/verbose.ts index 189834f8b46e..33b417511141 100644 --- a/packages/vitest/src/node/reporters/verbose.ts +++ b/packages/vitest/src/node/reporters/verbose.ts @@ -1,4 +1,4 @@ -import c from 'picocolors' +import c from 'tinyrainbow' import type { TaskResultPack } from '../../types' import { getFullName } from '../../utils' import { F_RIGHT } from '../../utils/figures' diff --git a/packages/vitest/src/node/stdin.ts b/packages/vitest/src/node/stdin.ts index 5ae6bc7c4aca..a8604c20a2a3 100644 --- a/packages/vitest/src/node/stdin.ts +++ b/packages/vitest/src/node/stdin.ts @@ -1,6 +1,6 @@ import readline from 'node:readline' import type { Writable } from 'node:stream' -import c from 'picocolors' +import c from 'tinyrainbow' import prompt from 'prompts' import { relative, resolve } from 'pathe' import { getTests, isWindows, stdout } from '../utils' diff --git a/packages/vitest/src/node/watch-filter.ts b/packages/vitest/src/node/watch-filter.ts index 44db2144a189..2a8d8ed9f977 100644 --- a/packages/vitest/src/node/watch-filter.ts +++ b/packages/vitest/src/node/watch-filter.ts @@ -1,6 +1,6 @@ import readline from 'node:readline' import type { Writable } from 'node:stream' -import c from 'picocolors' +import c from 'tinyrainbow' import stripAnsi from 'strip-ansi' import { createDefer } from '@vitest/utils' import { stdout as getStdout } from '../utils' diff --git a/packages/vitest/src/runtime/console.ts b/packages/vitest/src/runtime/console.ts index ebff18a7abae..376298ec5881 100644 --- a/packages/vitest/src/runtime/console.ts +++ b/packages/vitest/src/runtime/console.ts @@ -1,7 +1,8 @@ import { Writable } from 'node:stream' import { Console } from 'node:console' import { relative } from 'node:path' -import { getColors, getSafeTimers } from '@vitest/utils' +import { getSafeTimers } from '@vitest/utils' +import c from 'tinyrainbow' import { RealDate } from '../integrations/mock/date' import { getWorkerState } from '../utils' import type { WorkerGlobalState } from '../types' @@ -203,7 +204,7 @@ export function createCustomConsole(defaultState?: WorkerGlobalState) { return new Console({ stdout, stderr, - colorMode: getColors().isColorSupported, + colorMode: c.isColorSupported, groupIndentation: 2, }) } diff --git a/packages/vitest/src/runtime/runVmTests.ts b/packages/vitest/src/runtime/runVmTests.ts index a989d7d6ff46..5b3b4107501e 100644 --- a/packages/vitest/src/runtime/runVmTests.ts +++ b/packages/vitest/src/runtime/runVmTests.ts @@ -1,10 +1,8 @@ -import { isatty } from 'node:tty' import { createRequire } from 'node:module' import util from 'node:util' import timers from 'node:timers' import { performance } from 'node:perf_hooks' import { collectTests, startTests } from '@vitest/runner' -import { createColors, setupColors } from '@vitest/utils' import { installSourcemapsSupport } from 'vite-node/source-map' import { setupChaiConfig } from '../integrations/chai/config' import { @@ -35,8 +33,6 @@ export async function run( enumerable: false, }) - setupColors(createColors(isatty(1))) - if (workerState.environment.transformMode === 'web') { const _require = createRequire(import.meta.url) // always mock "required" `css` files, because we cannot process them diff --git a/packages/vitest/src/runtime/setup-node.ts b/packages/vitest/src/runtime/setup-node.ts index 662a15aed6f7..5fe45557d895 100644 --- a/packages/vitest/src/runtime/setup-node.ts +++ b/packages/vitest/src/runtime/setup-node.ts @@ -1,9 +1,7 @@ import { createRequire } from 'node:module' import util from 'node:util' import timers from 'node:timers' -import { isatty } from 'node:tty' import { installSourcemapsSupport } from 'vite-node/source-map' -import { createColors, setupColors } from '@vitest/utils' import type { EnvironmentOptions, ResolvedConfig, @@ -42,7 +40,6 @@ export async function setupGlobalEnv( } globalSetup = true - setupColors(createColors(isatty(1))) if (environment.transformMode === 'web') { const _require = createRequire(import.meta.url) diff --git a/packages/vitest/src/types/config.ts b/packages/vitest/src/types/config.ts index 7cdb181b3691..ba88445d1911 100644 --- a/packages/vitest/src/types/config.ts +++ b/packages/vitest/src/types/config.ts @@ -1,5 +1,5 @@ import type { AliasOptions, DepOptimizationConfig, ServerOptions } from 'vite' -import type { PrettyFormatOptions } from 'pretty-format' +import type { PrettyFormatOptions } from '@vitest/pretty-format' import type { FakeTimerInstallOpts } from '@sinonjs/fake-timers' import type { SequenceHooks, SequenceSetupFiles } from '@vitest/runner' import type { ViteNodeServerOptions } from 'vite-node' diff --git a/packages/vitest/src/types/global.ts b/packages/vitest/src/types/global.ts index b6c526361f64..23158dada2b8 100644 --- a/packages/vitest/src/types/global.ts +++ b/packages/vitest/src/types/global.ts @@ -1,4 +1,4 @@ -import type { Plugin as PrettyFormatPlugin } from 'pretty-format' +import type { Plugin as PrettyFormatPlugin } from '@vitest/pretty-format' import type { SnapshotState } from '@vitest/snapshot' import type { ExpectStatic, PromisifyAssertion, Tester } from '@vitest/expect' import type { UserConsoleLog } from './general' diff --git a/packages/vitest/src/types/matcher-utils.ts b/packages/vitest/src/types/matcher-utils.ts index cd5f85f2fe71..2d52466af2f3 100644 --- a/packages/vitest/src/types/matcher-utils.ts +++ b/packages/vitest/src/types/matcher-utils.ts @@ -1,4 +1,4 @@ -import type { Formatter } from 'picocolors/types' +import type { Formatter } from 'tinyrainbow' export interface MatcherHintOptions { comment?: string diff --git a/packages/vitest/src/utils/colors.ts b/packages/vitest/src/utils/colors.ts index a114fd802b3a..3e38afbbc39f 100644 --- a/packages/vitest/src/utils/colors.ts +++ b/packages/vitest/src/utils/colors.ts @@ -1,7 +1,6 @@ -import type { Colors } from '@vitest/utils' import { highlight } from '@vitest/utils' import { extname } from 'pathe' -import c from 'picocolors' +import c, { type Colors } from 'tinyrainbow' const HIGHLIGHT_SUPPORTED_EXTS = new Set( ['js', 'ts'].flatMap(lang => [ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 02e31581e52b..c1aa2d1b5684 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -514,12 +514,12 @@ importers: magicast: specifier: ^0.3.4 version: 0.3.4 - picocolors: - specifier: ^1.0.1 - version: 1.0.1 test-exclude: specifier: ^7.0.1 version: 7.0.1 + tinyrainbow: + specifier: ^1.1.2 + version: 1.1.2 devDependencies: '@types/debug': specifier: ^4.1.12 @@ -575,9 +575,6 @@ importers: magicast: specifier: ^0.3.4 version: 0.3.4 - picocolors: - specifier: ^1.0.1 - version: 1.0.1 std-env: specifier: ^3.7.0 version: 3.7.0 @@ -587,6 +584,9 @@ importers: test-exclude: specifier: ^7.0.1 version: 7.0.1 + tinyrainbow: + specifier: ^1.1.2 + version: 1.1.2 devDependencies: '@types/debug': specifier: ^4.1.12 @@ -627,6 +627,9 @@ importers: chai: specifier: ^5.1.1 version: 5.1.1 + tinyrainbow: + specifier: ^1.1.2 + version: 1.1.2 devDependencies: '@types/chai': specifier: 4.3.6 @@ -634,13 +637,20 @@ importers: '@vitest/runner': specifier: workspace:* version: link:../runner - picocolors: - specifier: ^1.0.1 - version: 1.0.1 rollup-plugin-copy: specifier: ^3.5.0 version: 3.5.0 + packages/pretty-format: + dependencies: + tinyrainbow: + specifier: ^1.1.2 + version: 1.1.2 + devDependencies: + react-is: + specifier: ^18.3.1 + version: 18.3.1 + packages/runner: dependencies: '@vitest/utils': @@ -652,15 +662,15 @@ importers: packages/snapshot: dependencies: + '@vitest/pretty-format': + specifier: workspace:* + version: link:../pretty-format magic-string: specifier: ^0.30.10 version: 0.30.10 pathe: specifier: ^1.1.2 version: 1.1.2 - pretty-format: - specifier: ^29.7.0 - version: 29.7.0 devDependencies: '@types/natural-compare': specifier: ^1.4.3 @@ -695,12 +705,12 @@ importers: pathe: specifier: ^1.1.2 version: 1.1.2 - picocolors: - specifier: ^1.0.1 - version: 1.0.1 sirv: specifier: ^2.0.4 version: 2.0.4 + tinyrainbow: + specifier: ^1.1.2 + version: 1.1.2 vitest: specifier: workspace:* version: link:../vitest @@ -792,18 +802,18 @@ importers: packages/utils: dependencies: - diff-sequences: - specifier: ^29.6.3 - version: 29.6.3 + '@vitest/pretty-format': + specifier: workspace:* + version: link:../pretty-format estree-walker: specifier: ^3.0.3 version: 3.0.3 loupe: specifier: ^3.1.1 version: 3.1.1 - pretty-format: - specifier: ^29.7.0 - version: 29.7.0 + tinyrainbow: + specifier: ^1.1.2 + version: 1.1.2 devDependencies: '@jridgewell/trace-mapping': specifier: ^0.3.25 @@ -811,6 +821,15 @@ importers: '@types/estree': specifier: ^1.0.5 version: 1.0.5 + '@types/react-is': + specifier: ^18.3.0 + version: 18.3.0 + diff-sequences: + specifier: ^29.6.3 + version: 29.6.3 + react-is: + specifier: ^18.3.1 + version: 18.3.1 tinyhighlight: specifier: ^0.3.2 version: 0.3.2 @@ -826,9 +845,9 @@ importers: pathe: specifier: ^1.1.2 version: 1.1.2 - picocolors: - specifier: ^1.0.1 - version: 1.0.1 + tinyrainbow: + specifier: ^1.1.2 + version: 1.1.2 vite: specifier: ^5.3.3 version: 5.3.3(@types/node@20.14.9) @@ -881,9 +900,6 @@ importers: pathe: specifier: ^1.1.2 version: 1.1.2 - picocolors: - specifier: ^1.0.1 - version: 1.0.1 std-env: specifier: ^3.7.0 version: 3.7.0 @@ -893,6 +909,9 @@ importers: tinypool: specifier: ^1.0.0 version: 1.0.0 + tinyrainbow: + specifier: ^1.1.2 + version: 1.1.2 vite: specifier: ^5.3.3 version: 5.3.3(@types/node@20.14.9) @@ -1166,6 +1185,9 @@ importers: sweetalert2: specifier: ^11.6.16 version: 11.6.16 + tinyrainbow: + specifier: ^1.1.2 + version: 1.1.2 tinyspy: specifier: ^1.0.2 version: 1.0.2 @@ -4271,6 +4293,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@sinclair/typebox': 0.27.8 + dev: true /@jest/types@29.0.1: resolution: {integrity: sha512-ft01rxzVsbh9qZPJ6EFgAIj3PT9FCRfBF9Xljo2/33VDOUjLZr0ZJ2oKANqh9S/K0/GERCsHDAQlBwj7RxA+9g==} @@ -4836,6 +4859,7 @@ packages: /@sinclair/typebox@0.27.8: resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + dev: true /@sindresorhus/is@5.3.0: resolution: {integrity: sha512-CX6t4SYQ37lzxicAqsBtxA3OseeoVrh9cSJ5PFYam0GksYlupRfy1A+Q4aYD3zvcfECLc0zO2u+ZnR2UYKvCrw==} @@ -5649,6 +5673,12 @@ packages: '@types/react': 18.2.79 dev: true + /@types/react-is@18.3.0: + resolution: {integrity: sha512-KZJpHUkAdzyKj/kUHJDc6N7KyidftICufJfOFpiG6haL/BDQNQt5i4n1XDUL/nDZAtGLHDSWRYpLzKTAKSvX6w==} + dependencies: + '@types/react': 18.2.79 + dev: true + /@types/react@18.2.79: resolution: {integrity: sha512-RwGAGXPl9kSXwdNTafkOEuFrTBD5SA2B3iEB96xi8+xu5ddUa/cpvyVCSNn+asgLCTHkb5ZxN8gbuibYJi4s1w==} dependencies: @@ -8662,6 +8692,7 @@ packages: /diff-sequences@29.6.3: resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dev: true /dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} @@ -13286,7 +13317,8 @@ packages: dependencies: '@jest/schemas': 29.6.3 ansi-styles: 5.2.0 - react-is: 18.2.0 + react-is: 18.3.1 + dev: true /process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} @@ -13516,8 +13548,9 @@ packages: /react-is@17.0.2: resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} - /react-is@18.2.0: - resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} + /react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + dev: true /react-refresh@0.14.0: resolution: {integrity: sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==} @@ -14977,6 +15010,10 @@ packages: engines: {node: ^18.0.0 || >=20.0.0} dev: false + /tinyrainbow@1.1.2: + resolution: {integrity: sha512-oMttVNy8VdB46XUh054C63bGS2t8WhjTWbtdJNewItd0/eZZSDLxf76eUvkysWtYAJDRgcfWBuxOq9qRn0Dm3w==} + engines: {node: '>=14.0.0'} + /tinyspy@1.0.2: resolution: {integrity: sha512-bSGlgwLBYf7PnUsQ6WOc6SJ3pGOcd+d8AA6EUnLDDM0kWEstC1JIlSZA3UNliDXhd9ABoS7hiRBDCu+XP/sf1Q==} engines: {node: '>=14.0.0'} diff --git a/test/core/package.json b/test/core/package.json index ce41f04ea557..a72945fd205a 100644 --- a/test/core/package.json +++ b/test/core/package.json @@ -25,6 +25,7 @@ "immutable": "5.0.0-beta.5", "strip-ansi": "^7.1.0", "sweetalert2": "^11.6.16", + "tinyrainbow": "^1.1.2", "tinyspy": "^1.0.2", "url": "^0.11.0", "vitest": "workspace:*", diff --git a/test/core/test/__snapshots__/jest-expect.test.ts.snap b/test/core/test/__snapshots__/jest-expect.test.ts.snap index 3e92bdac56d4..eaa80f523546 100644 --- a/test/core/test/__snapshots__/jest-expect.test.ts.snap +++ b/test/core/test/__snapshots__/jest-expect.test.ts.snap @@ -23,13 +23,13 @@ exports[`asymmetric matcher error 3`] = ` "actual": "Object { "foo": "hello", }", - "diff": "- Expected -+ Received + "diff": "- Expected ++ Received - Object { -- "foo": StringContaining "xx", -+ "foo": "hello", - }", + Object { +- "foo": StringContaining "xx", ++ "foo": "hello", + }", "expected": "Object { "foo": StringContaining "xx", }", @@ -42,13 +42,13 @@ exports[`asymmetric matcher error 4`] = ` "actual": "Object { "foo": "hello", }", - "diff": "- Expected -+ Received + "diff": "- Expected ++ Received - Object { -- "foo": StringNotContaining "ll", -+ "foo": "hello", - }", + Object { +- "foo": StringNotContaining "ll", ++ "foo": "hello", + }", "expected": "Object { "foo": StringNotContaining "ll", }", @@ -59,10 +59,10 @@ exports[`asymmetric matcher error 4`] = ` exports[`asymmetric matcher error 5`] = ` { "actual": "hello", - "diff": "- Expected: + "diff": "- Expected: stringContainingCustom -+ Received: ++ Received: "hello"", "expected": "stringContainingCustom", "message": "expected 'hello' to deeply equal stringContainingCustom", @@ -72,10 +72,10 @@ stringContainingCustom exports[`asymmetric matcher error 6`] = ` { "actual": "hello", - "diff": "- Expected: + "diff": "- Expected: not.stringContainingCustom -+ Received: ++ Received: "hello"", "expected": "not.stringContainingCustom", "message": "expected 'hello' to deeply equal not.stringContainingCustom", @@ -87,13 +87,13 @@ exports[`asymmetric matcher error 7`] = ` "actual": "Object { "foo": "hello", }", - "diff": "- Expected -+ Received + "diff": "- Expected ++ Received - Object { -- "foo": stringContainingCustom, -+ "foo": "hello", - }", + Object { +- "foo": stringContainingCustom, ++ "foo": "hello", + }", "expected": "Object { "foo": stringContainingCustom, }", @@ -106,13 +106,13 @@ exports[`asymmetric matcher error 8`] = ` "actual": "Object { "foo": "hello", }", - "diff": "- Expected -+ Received + "diff": "- Expected ++ Received - Object { -- "foo": not.stringContainingCustom, -+ "foo": "hello", - }", + Object { +- "foo": not.stringContainingCustom, ++ "foo": "hello", + }", "expected": "Object { "foo": not.stringContainingCustom, }", @@ -125,7 +125,7 @@ exports[`asymmetric matcher error 9`] = ` "actual": "undefined", "diff": undefined, "expected": "undefined", - "message": "expected "hello" to contain "xx"", + "message": "expected "hello" to contain "xx"", } `; @@ -134,17 +134,17 @@ exports[`asymmetric matcher error 10`] = ` "actual": "undefined", "diff": undefined, "expected": "undefined", - "message": "expected "hello" not to contain "ll"", + "message": "expected "hello" not to contain "ll"", } `; exports[`asymmetric matcher error 11`] = ` { "actual": "hello", - "diff": "- Expected: + "diff": "- Expected: testComplexMatcher<[object Object]> -+ Received: ++ Received: "hello"", "expected": "testComplexMatcher<[object Object]>", "message": "expected 'hello' to deeply equal testComplexMatcher<[object Object]>", @@ -157,15 +157,15 @@ exports[`asymmetric matcher error 12`] = ` "k": "v", "k2": "v2", }", - "diff": "- Expected -+ Received - -- ObjectContaining { -+ Object { - "k": "v", -- "k3": "v3", -+ "k2": "v2", - }", + "diff": "- Expected ++ Received + +- ObjectContaining { ++ Object { + "k": "v", +- "k3": "v3", ++ "k2": "v2", + }", "expected": "ObjectContaining { "k": "v", "k3": "v3", @@ -180,15 +180,15 @@ exports[`asymmetric matcher error 13`] = ` "a", "b", ]", - "diff": "- Expected -+ Received - -- ArrayContaining [ -+ Array [ - "a", -- "c", -+ "b", - ]", + "diff": "- Expected ++ Received + +- ArrayContaining [ ++ Array [ + "a", +- "c", ++ "b", + ]", "expected": "ArrayContaining [ "a", "c", @@ -209,11 +209,11 @@ exports[`asymmetric matcher error 14`] = ` exports[`asymmetric matcher error 15`] = ` { "actual": "2.5", - "diff": "- Expected -+ Received + "diff": "- Expected ++ Received -- NumberCloseTo 2 (1 digit) -+ 2.5", +- NumberCloseTo 2 (1 digit) ++ 2.5", "expected": "NumberCloseTo 2 (1 digit)", "message": "expected 2.5 to deeply equal NumberCloseTo 2 (1 digit)", } @@ -240,10 +240,10 @@ exports[`asymmetric matcher error 17`] = ` exports[`asymmetric matcher error 18`] = ` { "actual": "hello", - "diff": "- Expected: + "diff": "- Expected: stringContainingCustom -+ Received: ++ Received: "hello"", "expected": "stringContainingCustom", "message": "expected error to match asymmetric matcher", @@ -262,10 +262,10 @@ exports[`asymmetric matcher error 19`] = ` exports[`asymmetric matcher error 20`] = ` { "actual": "hello", - "diff": "- Expected: + "diff": "- Expected: stringContainingCustom -+ Received: ++ Received: "hello"", "expected": "stringContainingCustom", "message": "expected error not to match asymmetric matcher", @@ -275,10 +275,10 @@ stringContainingCustom exports[`asymmetric matcher error 21`] = ` { "actual": "[Error: hello]", - "diff": "- Expected: + "diff": "- Expected: StringContaining "ll" -+ Received: ++ Received: [Error: hello]", "expected": "StringContaining "ll"", "message": "expected error to match asymmetric matcher", @@ -288,10 +288,10 @@ StringContaining "ll" exports[`asymmetric matcher error 22`] = ` { "actual": "[Error: hello]", - "diff": "- Expected: + "diff": "- Expected: stringContainingCustom -+ Received: ++ Received: [Error: hello]", "expected": "stringContainingCustom", "message": "expected error to match asymmetric matcher", @@ -301,10 +301,10 @@ stringContainingCustom exports[`asymmetric matcher error 23`] = ` { "actual": "[Error: hello]", - "diff": "- Expected: + "diff": "- Expected: [Function MyError1] -+ Received: ++ Received: [Error: hello]", "expected": "[Function MyError1]", "message": "expected error to be instance of MyError1", @@ -316,13 +316,13 @@ exports[`toHaveBeenNthCalledWith error 1`] = ` "actual": "Array [ "Hi", ]", - "diff": "- Expected -+ Received + "diff": "- Expected ++ Received - Array [ -- "hey", -+ "Hi", - ]", + Array [ +- "hey", ++ "Hi", + ]", "expected": "Array [ "hey", ]", @@ -344,11 +344,11 @@ exports[`toHaveBeenNthCalledWith error 2`] = ` exports[`toMatch/toContain diff 1`] = ` { "actual": "hellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohello", - "diff": "- Expected -+ Received + "diff": "- Expected ++ Received -- world -+ hellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohello", +- world ++ hellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohello", "expected": "world", "message": "expected 'hellohellohellohellohellohellohellohe…' to contain 'world'", } @@ -357,11 +357,11 @@ exports[`toMatch/toContain diff 1`] = ` exports[`toMatch/toContain diff 2`] = ` { "actual": "hellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohello", - "diff": "- Expected -+ Received + "diff": "- Expected ++ Received -- world -+ hellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohello", +- world ++ hellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohello", "expected": "world", "message": "expected 'hellohellohellohellohellohellohellohe…' to match 'world'", } @@ -370,10 +370,10 @@ exports[`toMatch/toContain diff 2`] = ` exports[`toMatch/toContain diff 3`] = ` { "actual": "hellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohello", - "diff": "- Expected: + "diff": "- Expected: /world/ -+ Received: ++ Received: "hellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohello"", "expected": "/world/", "message": "expected 'hellohellohellohellohellohellohellohe…' to match /world/", diff --git a/test/core/test/__snapshots__/mocked.test.ts.snap b/test/core/test/__snapshots__/mocked.test.ts.snap index 3662a734f533..8d62b2f3967b 100644 --- a/test/core/test/__snapshots__/mocked.test.ts.snap +++ b/test/core/test/__snapshots__/mocked.test.ts.snap @@ -1,7 +1,7 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`mocked function which fails on toReturnWith > just one call 1`] = ` -[AssertionError: expected "spy" to return with: 2 at least once +"expected "spy" to return with: 2 at least once Received: @@ -12,11 +12,11 @@ Received: Number of calls: 1 -] +" `; exports[`mocked function which fails on toReturnWith > multi calls 1`] = ` -[AssertionError: expected "spy" to return with: 2 at least once +"expected "spy" to return with: 2 at least once Received: @@ -37,11 +37,11 @@ Received: Number of calls: 3 -] +" `; exports[`mocked function which fails on toReturnWith > oject type 1`] = ` -[AssertionError: expected "spy" to return with: { a: '4' } at least once +"expected "spy" to return with: { a: '4' } at least once Received: @@ -68,16 +68,16 @@ Received: Number of calls: 3 -] +" `; exports[`mocked function which fails on toReturnWith > zero call 1`] = ` -[AssertionError: expected "spy" to return with: 2 at least once +"expected "spy" to return with: 2 at least once Received: Number of calls: 0 -] +" `; diff --git a/test/core/test/diff.test.ts b/test/core/test/diff.test.ts index 99b20e123acf..1cc368fb95f6 100644 --- a/test/core/test/diff.test.ts +++ b/test/core/test/diff.test.ts @@ -1,5 +1,5 @@ import { expect, test, vi } from 'vitest' -import { getDefaultColors, setupColors } from '@vitest/utils' +import stripAnsi from 'strip-ansi' import type { DiffOptions } from '@vitest/utils/diff' import { diff, diffStringsUnified } from '@vitest/utils/diff' import { processError } from '@vitest/runner' @@ -9,9 +9,8 @@ test('displays object diff', () => { const objectA = { a: 1, b: 2 } const objectB = { a: 1, b: 3 } const console = { log: vi.fn(), error: vi.fn() } - setupColors(getDefaultColors()) displayDiff(diff(objectA, objectB), console as any) - expect(console.error.mock.calls[0][0]).toMatchInlineSnapshot(` + expect(stripAnsi(console.error.mock.calls[0][0])).toMatchInlineSnapshot(` " - Expected + Received @@ -29,9 +28,8 @@ test('display truncated object diff', () => { const objectA = { a: 1, b: 2, c: 3, d: 4, e: 5 } const objectB = { a: 1, b: 3, c: 4, d: 5, e: 6 } const console = { log: vi.fn(), error: vi.fn() } - setupColors(getDefaultColors()) displayDiff(diff(objectA, objectB, { truncateThreshold: 4 }), console as any) - expect(console.error.mock.calls[0][0]).toMatchInlineSnapshot(` + expect(stripAnsi(console.error.mock.calls[0][0])).toMatchInlineSnapshot(` " - Expected + Received @@ -51,9 +49,8 @@ test('display one line string diff', () => { const string1 = 'string1' const string2 = 'string2' const console = { log: vi.fn(), error: vi.fn() } - setupColors(getDefaultColors()) displayDiff(diff(string1, string2), console as any) - expect(console.error.mock.calls[0][0]).toMatchInlineSnapshot(` + expect(stripAnsi(console.error.mock.calls[0][0])).toMatchInlineSnapshot(` " - Expected + Received @@ -68,9 +65,8 @@ test('display one line string diff should not be affected by truncateThreshold', const string1 = 'string1' const string2 = 'string2' const console = { log: vi.fn(), error: vi.fn() } - setupColors(getDefaultColors()) displayDiff(diff(string1, string2, { truncateThreshold: 3 }), console as any) - expect(console.error.mock.calls[0][0]).toMatchInlineSnapshot(` + expect(stripAnsi(console.error.mock.calls[0][0])).toMatchInlineSnapshot(` " - Expected + Received @@ -85,9 +81,8 @@ test('display multiline string diff', () => { const string1 = 'string1\nstring2\nstring3' const string2 = 'string2\nstring2\nstring1' const console = { log: vi.fn(), error: vi.fn() } - setupColors(getDefaultColors()) displayDiff(diff(string1, string2), console as any) - expect(console.error.mock.calls[0][0]).toMatchInlineSnapshot(` + expect(stripAnsi(console.error.mock.calls[0][0])).toMatchInlineSnapshot(` " - Expected + Received @@ -105,9 +100,8 @@ test('display truncated multiline string diff', () => { const string1 = 'string1\nstring2\nstring3' const string2 = 'string2\nstring2\nstring1' const console = { log: vi.fn(), error: vi.fn() } - setupColors(getDefaultColors()) displayDiff(diff(string1, string2, { truncateThreshold: 2 }), console as any) - expect(console.error.mock.calls[0][0]).toMatchInlineSnapshot(` + expect(stripAnsi(console.error.mock.calls[0][0])).toMatchInlineSnapshot(` " - Expected + Received @@ -124,9 +118,8 @@ test('display truncated multiple items array diff', () => { const array1 = Array(45000).fill('foo') const array2 = Array(45000).fill('bar') const console = { log: vi.fn(), error: vi.fn() } - setupColors(getDefaultColors()) displayDiff(diff(array1, array2, { truncateThreshold: 3 }), console as any) - expect(console.error.mock.calls[0][0]).toMatchInlineSnapshot(` + expect(stripAnsi(console.error.mock.calls[0][0])).toMatchInlineSnapshot(` " - Expected + Received @@ -142,8 +135,7 @@ test('display truncated multiple items array diff', () => { }) test('asymmetric matcher in object', () => { - setupColors(getDefaultColors()) - expect(getErrorDiff({ x: 0, y: 'foo' }, { x: 1, y: expect.anything() })).toMatchInlineSnapshot(` + expect(stripAnsi(getErrorDiff({ x: 0, y: 'foo' }, { x: 1, y: expect.anything() }))).toMatchInlineSnapshot(` "- Expected + Received @@ -156,13 +148,12 @@ test('asymmetric matcher in object', () => { }) test('asymmetric matcher in object with truncated diff', () => { - setupColors(getDefaultColors()) expect( - getErrorDiff( + stripAnsi(getErrorDiff( { w: 'foo', x: 0, y: 'bar', z: 'baz' }, { w: expect.anything(), x: 1, y: expect.anything(), z: 'bar' }, { truncateThreshold: 3 }, - ), + )), ).toMatchInlineSnapshot(` "- Expected + Received @@ -176,8 +167,7 @@ test('asymmetric matcher in object with truncated diff', () => { }) test('asymmetric matcher in array', () => { - setupColors(getDefaultColors()) - expect(getErrorDiff([0, 'foo'], [1, expect.anything()])).toMatchInlineSnapshot(` + expect(stripAnsi(getErrorDiff([0, 'foo'], [1, expect.anything()]))).toMatchInlineSnapshot(` "- Expected + Received @@ -190,13 +180,12 @@ test('asymmetric matcher in array', () => { }) test('asymmetric matcher in array with truncated diff', () => { - setupColors(getDefaultColors()) expect( - getErrorDiff( + stripAnsi(getErrorDiff( [0, 'foo', 2], [1, expect.anything(), 3], { truncateThreshold: 2 }, - ), + )), ).toMatchInlineSnapshot(` "- Expected + Received @@ -209,12 +198,11 @@ test('asymmetric matcher in array with truncated diff', () => { }) test('asymmetric matcher in nested', () => { - setupColors(getDefaultColors()) expect( - getErrorDiff( + stripAnsi(getErrorDiff( [{ x: 0, y: 'foo' }, [0, 'bar']], [{ x: 1, y: expect.anything() }, [1, expect.anything()]], - ), + )), ).toMatchInlineSnapshot(` "- Expected + Received @@ -235,13 +223,12 @@ test('asymmetric matcher in nested', () => { }) test('asymmetric matcher in nested with truncated diff', () => { - setupColors(getDefaultColors()) expect( - getErrorDiff( + stripAnsi(getErrorDiff( [{ x: 0, y: 'foo', z: 'bar' }, [0, 'bar', 'baz']], [{ x: 1, y: expect.anything(), z: expect.anything() }, [1, expect.anything(), expect.anything()]], { truncateThreshold: 5 }, - ), + )), ).toMatchInlineSnapshot(` "- Expected + Received @@ -265,9 +252,8 @@ test('diff for multi-line string compared by characters', () => { FOO, bar, ` - setupColors(getDefaultColors()) expect( - diffStringsUnified(string1, string2), + stripAnsi(diffStringsUnified(string1, string2)), ).toMatchInlineSnapshot(` "- Expected + Received @@ -291,9 +277,8 @@ test('truncated diff for multi-line string compared by characters', () => { bar, BAZ, ` - setupColors(getDefaultColors()) expect( - diffStringsUnified(string1, string2, { truncateThreshold: 3 }), + stripAnsi(diffStringsUnified(string1, string2, { truncateThreshold: 3 })), ).toMatchInlineSnapshot(` "- Expected + Received @@ -307,7 +292,6 @@ test('truncated diff for multi-line string compared by characters', () => { }) test('getter only property', () => { - setupColors(getDefaultColors()) const x = { normalProp: 1 } const y = { normalProp: 2 } Object.defineProperty(x, 'getOnlyProp', { @@ -319,7 +303,7 @@ test('getter only property', () => { get: () => ({ a: 'b' }), }) expect( - getErrorDiff(x, y), + stripAnsi(getErrorDiff(x, y)), ).toMatchInlineSnapshot(` "- Expected + Received diff --git a/test/core/test/environments/jsdom.spec.ts b/test/core/test/environments/jsdom.spec.ts index 95f8a293c5a6..c0a32408a880 100644 --- a/test/core/test/environments/jsdom.spec.ts +++ b/test/core/test/environments/jsdom.spec.ts @@ -1,12 +1,8 @@ // @vitest-environment jsdom -import { createColors, getDefaultColors, setupColors } from '@vitest/utils' import { processError } from '@vitest/utils/error' -import { afterEach, expect, test } from 'vitest' - -afterEach(() => { - setupColors(createColors(true)) -}) +import { expect, test } from 'vitest' +import stripAnsi from 'strip-ansi' const nodeMajor = Number(process.version.slice(1).split('.')[0]) @@ -69,14 +65,12 @@ test('toContain correctly handles DOM nodes', () => { expect(wrapper.classList).toContain(2) }).toThrowErrorMatchingInlineSnapshot(`[TypeError: class name value must be string, received "number"]`) - setupColors(getDefaultColors()) - try { expect(wrapper.classList).toContain('flex-row') expect.unreachable() } catch (err: any) { - expect(processError(err).diff).toMatchInlineSnapshot(` + expect(stripAnsi(processError(err).diff)).toMatchInlineSnapshot(` "- Expected + Received @@ -90,7 +84,7 @@ test('toContain correctly handles DOM nodes', () => { expect.unreachable() } catch (err: any) { - expect(processError(err).diff).toMatchInlineSnapshot(` + expect(stripAnsi(processError(err).diff)).toMatchInlineSnapshot(` "- Expected + Received diff --git a/test/core/test/injector-mock.test.ts b/test/core/test/injector-mock.test.ts index ff0d55e94f00..3791acce4715 100644 --- a/test/core/test/injector-mock.test.ts +++ b/test/core/test/injector-mock.test.ts @@ -1,7 +1,7 @@ import { parseAst } from 'rollup/parseAst' import { describe, expect, it, test } from 'vitest' import stripAnsi from 'strip-ansi' -import { getDefaultColors } from '@vitest/utils' +import { getDefaultColors } from 'tinyrainbow' import { hoistMocks } from '../../../packages/vitest/src/node/hoistMocks' function parse(code: string, options: any) { diff --git a/test/core/test/jest-expect.test.ts b/test/core/test/jest-expect.test.ts index 71f55c483d65..77c450321b3e 100644 --- a/test/core/test/jest-expect.test.ts +++ b/test/core/test/jest-expect.test.ts @@ -1,9 +1,9 @@ /* eslint-disable no-sparse-arrays */ import { AssertionError } from 'node:assert' import { describe, expect, it, vi } from 'vitest' -import { generateToBeMessage, setupColors } from '@vitest/expect' +import { generateToBeMessage } from '@vitest/expect' import { processError } from '@vitest/utils/error' -import { getDefaultColors } from '@vitest/utils' +import stripAnsi from 'strip-ansi' class TestError extends Error {} @@ -876,10 +876,10 @@ it('correctly prints diff', () => { expect.unreachable() } catch (err) { - setupColors(getDefaultColors()) const error = processError(err) - expect(error.diff).toContain('- "a": 2') - expect(error.diff).toContain('+ "a": 1') + const diff = stripAnsi(error.diff) + expect(diff).toContain('- "a": 2') + expect(diff).toContain('+ "a": 1') } }) @@ -889,10 +889,10 @@ it('correctly prints diff for the cause', () => { expect.unreachable() } catch (err) { - setupColors(getDefaultColors()) const error = processError(new Error('wrapper', { cause: err })) - expect(error.cause.diff).toContain('- "a": 2') - expect(error.cause.diff).toContain('+ "a": 1') + const diff = stripAnsi(error.cause.diff) + expect(diff).toContain('- "a": 2') + expect(diff).toContain('+ "a": 1') } }) @@ -905,9 +905,8 @@ it('correctly prints diff with asymmetric matchers', () => { expect.unreachable() } catch (err) { - setupColors(getDefaultColors()) const error = processError(err) - expect(error.diff).toMatchInlineSnapshot(` + expect(stripAnsi(error.diff)).toMatchInlineSnapshot(` "- Expected + Received @@ -932,13 +931,11 @@ function getError(f: () => unknown) { } catch (error) { const processed = processError(error) - return [processed.message, trim(processed.diff)] + return [stripAnsi(processed.message), stripAnsi(trim(processed.diff))] } } it('toMatchObject error diff', () => { - setupColors(getDefaultColors()) - // single property on root (3 total properties, 1 expected) expect(getError(() => expect({ a: 1, b: 2, c: 3 }).toMatchObject({ c: 4 }))).toMatchInlineSnapshot(` [ @@ -1065,8 +1062,6 @@ it('toMatchObject error diff', () => { }) it('toHaveProperty error diff', () => { - setupColors(getDefaultColors()) - // non match value expect(getError(() => expect({ name: 'foo' }).toHaveProperty('name', 'bar'))).toMatchInlineSnapshot(` [ @@ -1158,8 +1153,6 @@ function snapshotError(f: () => unknown) { } it('asymmetric matcher error', () => { - setupColors(getDefaultColors()) - expect.extend({ stringContainingCustom(received: unknown, other: string) { return { diff --git a/test/core/test/jest-matcher-utils.test.ts b/test/core/test/jest-matcher-utils.test.ts index 84bb27884d99..396ad94f765d 100644 --- a/test/core/test/jest-matcher-utils.test.ts +++ b/test/core/test/jest-matcher-utils.test.ts @@ -1,4 +1,3 @@ -import { getDefaultColors, setupColors } from '@vitest/utils' import { describe, expect, it } from 'vitest' describe('jest-matcher-utils', () => { @@ -12,11 +11,15 @@ describe('jest-matcher-utils', () => { }) it('diff', () => { - setupColors(getDefaultColors()) - - expect(() => { + let error!: Error + try { // @ts-expect-error "toBeJestEqual" is a custom matcher we just created expect('a').toBeJestEqual('b') - }).toThrowError(/- b.*\+ a/s) + expect.unreachable() + } + catch (err: any) { + error = err + } + expect(error.message).toMatch(/- b.*\+ a/s) }) }) diff --git a/test/core/test/mocked.test.ts b/test/core/test/mocked.test.ts index 3b86e6851ab2..1c54d649cae2 100644 --- a/test/core/test/mocked.test.ts +++ b/test/core/test/mocked.test.ts @@ -1,8 +1,7 @@ -import { afterEach, assert, beforeEach, describe, expect, test, vi, vitest } from 'vitest' - +import { assert, describe, expect, test, vi, vitest } from 'vitest' +import stripAnsi from 'strip-ansi' // @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' @@ -137,23 +136,27 @@ test('async functions should be mocked', () => { expect(asyncFunc()).resolves.toBe('foo') }) -describe('mocked function which fails on toReturnWith', () => { - beforeEach(() => { - setupColors(getDefaultColors()) - }) - afterEach(() => { - setupColors(createColors(true)) - }) +function getError(cb: () => void): string { + try { + cb() + } + catch (e: any) { + return stripAnsi(e.message) + } + expect.unreachable() + return 'unreachable' +} +describe('mocked function which fails on toReturnWith', () => { test('zero call', () => { const mock = vi.fn(() => 1) - expect(() => expect(mock).toReturnWith(2)).toThrowErrorMatchingSnapshot() + expect(getError(() => expect(mock).toReturnWith(2))).toMatchSnapshot() }) test('just one call', () => { const mock = vi.fn(() => 1) mock() - expect(() => expect(mock).toReturnWith(2)).toThrowErrorMatchingSnapshot() + expect(getError(() => expect(mock).toReturnWith(2))).toMatchSnapshot() }) test('multi calls', () => { @@ -161,7 +164,7 @@ describe('mocked function which fails on toReturnWith', () => { mock() mock() mock() - expect(() => expect(mock).toReturnWith(2)).toThrowErrorMatchingSnapshot() + expect(getError(() => expect(mock).toReturnWith(2))).toMatchSnapshot() }) test('oject type', () => { @@ -171,7 +174,7 @@ describe('mocked function which fails on toReturnWith', () => { mock() mock() mock() - expect(() => expect(mock).toReturnWith({ a: '4' })).toThrowErrorMatchingSnapshot() + expect(getError(() => expect(mock).toReturnWith({ a: '4' }))).toMatchSnapshot() }) }) diff --git a/test/core/test/utils.spec.ts b/test/core/test/utils.spec.ts index 931a06e62bea..c7d7c4067fd6 100644 --- a/test/core/test/utils.spec.ts +++ b/test/core/test/utils.spec.ts @@ -1,5 +1,5 @@ import { beforeAll, describe, expect, test } from 'vitest' -import { assertTypes, createColors, deepClone, isNegativeNaN, objDisplay, objectAttr, toArray } from '@vitest/utils' +import { assertTypes, deepClone, isNegativeNaN, objDisplay, objectAttr, toArray } from '@vitest/utils' import { deepMerge, resetModules } from '../../../packages/vitest/src/utils' import { deepMergeSnapshot } from '../../../packages/snapshot/src/port/utils' import type { EncodedSourceMap } from '../../../packages/vite-node/src/types' @@ -286,16 +286,6 @@ describe('objDisplay', () => { }) }) -describe(createColors, () => { - test('no maximum call stack error', () => { - process.env.FORCE_COLOR = '1' - delete process.env.GITHUB_ACTIONS - const c = createColors() - expect(c.isColorSupported).toBe(true) - expect(c.blue(c.blue('x').repeat(10000))).toBeTruthy() - }) -}) - describe('isNegativeNaN', () => { test.each` value | expected diff --git a/tsconfig.base.json b/tsconfig.base.json index 976f1dd082c3..8c1186e905a6 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -7,6 +7,7 @@ "paths": { "@vitest/ws-client": ["./packages/ws-client/src/index.ts"], "@vitest/ui": ["./packages/ui/node/index.ts"], + "@vitest/pretty-format": ["./packages/pretty-format/src/index.ts"], "@vitest/utils": ["./packages/utils/src/index.ts"], "@vitest/utils/*": ["./packages/utils/src/*"], "@vitest/spy": ["./packages/spy/src/index.ts"],