From 7d6cda12355f30221b868ee7f849c4b3838a0c06 Mon Sep 17 00:00:00 2001 From: nieyuyao Date: Wed, 27 Apr 2022 00:46:36 +0800 Subject: [PATCH 1/2] fix: replace actual and expected when process err --- packages/vitest/src/runtime/error.ts | 52 +++++++++++++++++++++++++- packages/vitest/src/runtime/mocker.ts | 26 ++----------- packages/vitest/src/utils/base.ts | 37 +++++++++++------- test/core/test/replace-matcher.test.ts | 45 ++++++++++++++++++++++ 4 files changed, 123 insertions(+), 37 deletions(-) create mode 100644 test/core/test/replace-matcher.test.ts diff --git a/packages/vitest/src/runtime/error.ts b/packages/vitest/src/runtime/error.ts index b530a21df236..26b12f29f952 100644 --- a/packages/vitest/src/runtime/error.ts +++ b/packages/vitest/src/runtime/error.ts @@ -1,5 +1,8 @@ import { format } from 'util' +import { util } from 'chai' import { stringify } from '../integrations/chai/jest-matcher-utils' +import { clone } from '../utils' +import { getType } from './mocker' const OBJECT_PROTO = Object.getPrototypeOf({}) @@ -57,11 +60,18 @@ export function processError(err: any) { if (err.name) err.nameStr = String(err.name) + const clonedActual = clone(err.actual) + const clonedExpected = clone(err.expected) + + const { replacedActual, replacedExpected } = replaceAsymmetricMatcher(clonedActual, clonedExpected) + + err.actual = replacedActual + err.expected = replacedExpected + if (typeof err.expected !== 'string') err.expected = stringify(err.expected) if (typeof err.actual !== 'string') err.actual = stringify(err.actual) - try { return serializeError(err) } @@ -69,3 +79,43 @@ export function processError(err: any) { return serializeError(new Error(`Failed to fully serialize error: ${e?.message}.\nInner error message: ${err?.message}`)) } } + +function isAsymmetricMatcher(data: any) { + const type = getType(data) + return type === 'Object' && typeof data.asymmetricMatch === 'function' +} + +function isReplaceable(obj1: any, obj2: any) { + const obj1Type = getType(obj1) + const obj2Type = getType(obj2) + return obj1Type === obj2Type && obj1Type === 'Object' +} + +export function replaceAsymmetricMatcher(actual: any, expected: any) { + if (!isReplaceable(actual, expected)) + return { replacedActual: actual, replacedExpected: expected } + util.getOwnEnumerableProperties(expected).forEach((key) => { + const expectedValue = expected[key] + const actualValue = actual[key] + if (isAsymmetricMatcher(expectedValue)) { + if (expectedValue.asymmetricMatch(actualValue)) + actual[key] = expectedValue + } + else if (isAsymmetricMatcher(actualValue)) { + if (actualValue.asymmetricMatch(expectedValue)) + expected[key] = actualValue + } + else if (isReplaceable(actualValue, expectedValue)) { + const replaced = replaceAsymmetricMatcher( + actualValue, + expectedValue, + ) + actual[key] = replaced.replacedActual + expected[key] = replaced.replacedExpected + } + }) + return { + replacedActual: actual, + replacedExpected: expected, + } +} diff --git a/packages/vitest/src/runtime/mocker.ts b/packages/vitest/src/runtime/mocker.ts index ab9933f0e54c..ff54883c3228 100644 --- a/packages/vitest/src/runtime/mocker.ts +++ b/packages/vitest/src/runtime/mocker.ts @@ -3,35 +3,17 @@ import { isNodeBuiltin } from 'mlly' import { basename, dirname, resolve } from 'pathe' import { normalizeRequestId, toFilePath } from 'vite-node/utils' import type { ModuleCacheMap } from 'vite-node/client' -import { getWorkerState, isWindows, mergeSlashes, slash } from '../utils' +import { getAllProperties, getWorkerState, isWindows, mergeSlashes, slash } from '../utils' import { distDir } from '../constants' import type { PendingSuiteMock } from '../types/mocker' import type { ExecuteOptions } from './execute' type Callback = (...args: any[]) => unknown -function getType(value: unknown): string { +export function getType(value: unknown): string { return Object.prototype.toString.apply(value).slice(8, -1) } -function getAllProperties(obj: any) { - const allProps = new Set() - let curr = obj - do { - // we don't need propterties from these - if (curr === Object.prototype || curr === Function.prototype || curr === RegExp.prototype) - break - const props = Object.getOwnPropertyNames(curr) - const symbs = Object.getOwnPropertySymbols(curr) - - props.forEach(prop => allProps.add(prop)) - symbs.forEach(symb => allProps.add(symb)) - - // eslint-disable-next-line no-cond-assign - } while (curr = Object.getPrototypeOf(curr)) - return Array.from(allProps) -} - export class VitestMocker { private static pendingIds: PendingSuiteMock[] = [] private static spyModule?: typeof import('../integrations/spy') @@ -174,9 +156,9 @@ export class VitestMocker { const newObj: Record = {} - const proproperties = getAllProperties(value) + const proprieties = getAllProperties(value) - for (const k of proproperties) { + for (const k of proprieties) { newObj[k] = this.mockValue(value[k]) const type = getType(value[k]) diff --git a/packages/vitest/src/utils/base.ts b/packages/vitest/src/utils/base.ts index 957f100bd19c..46358460460b 100644 --- a/packages/vitest/src/utils/base.ts +++ b/packages/vitest/src/utils/base.ts @@ -1,5 +1,23 @@ import type { Arrayable, DeepMerge, Nullable } from '../types' +export function getAllProperties(obj: any) { + const allProps = new Set() + let curr = obj + do { + // we don't need propterties from these + if (curr === Object.prototype || curr === Function.prototype || curr === RegExp.prototype) + break + const props = Object.getOwnPropertyNames(curr) + const symbs = Object.getOwnPropertySymbols(curr) + + props.forEach(prop => allProps.add(prop)) + symbs.forEach(symb => allProps.add(symb)) + + // eslint-disable-next-line no-cond-assign + } while (curr = Object.getPrototypeOf(curr)) + return Array.from(allProps) +} + export function notNullish(v: T | null | undefined): v is NonNullable { return v != null } @@ -26,20 +44,11 @@ export function clone(val: T): T { } if (Object.prototype.toString.call(val) === '[object Object]') { - out = {} // null - for (k in val) { - if (k === '__proto__') { - Object.defineProperty(out, k, { - value: clone((val as any)[k]), - configurable: true, - enumerable: true, - writable: true, - }) - } - else { - // eslint-disable-next-line no-cond-assign - out[k] = (tmp = (val as any)[k]) && typeof tmp === 'object' ? clone(tmp) : tmp - } + out = Object.create(Object.getPrototypeOf(val)) + const props = getAllProperties(val) + for (const k of props) { + // eslint-disable-next-line no-cond-assign + out[k] = (tmp = (val as any)[k]) && typeof tmp === 'object' ? clone(tmp) : tmp } return out } diff --git a/test/core/test/replace-matcher.test.ts b/test/core/test/replace-matcher.test.ts new file mode 100644 index 000000000000..ef68bd17e144 --- /dev/null +++ b/test/core/test/replace-matcher.test.ts @@ -0,0 +1,45 @@ +import { describe, expect, it } from 'vitest' +import { replaceAsymmetricMatcher } from '../../../packages/vitest/src/runtime/error' + +describe('replace asymmetric matcher', () => { + const expectReplaceAsymmetricMatcher = (actual: any, expected: any) => { + const replaced = replaceAsymmetricMatcher(actual, expected) + expect(replaced.replacedActual).toEqual(replaced.replacedExpected) + } + it('should works', () => { + expectReplaceAsymmetricMatcher(null, null) + expectReplaceAsymmetricMatcher(undefined, undefined) + expectReplaceAsymmetricMatcher(() => {}, expect.any(Function)) + expectReplaceAsymmetricMatcher(false, expect.any(Boolean)) + expectReplaceAsymmetricMatcher(false, expect.anything()) + expectReplaceAsymmetricMatcher(Symbol, expect.anything()) + expectReplaceAsymmetricMatcher({ + str: 'string', + arr: [1, 2], + }, { + str: expect.any(String), + arr: expect.anything(), + }) + expectReplaceAsymmetricMatcher({ + str: expect.any(String), + arr: expect.anything(), + }, { + str: expect.any(String), + arr: expect.anything(), + }) + expectReplaceAsymmetricMatcher({ + str: 'world', + arr: [1, 2], + }, { + str: expect.any(String), + arr: [1, expect.anything()], + }) + expectReplaceAsymmetricMatcher({ + str: 'world', + bool: false, + }, { + str: expect.any(String), + bool: expect.anything(), + }) + }) +}) From 214ea2e2ba909790db9a3b92bb0514e6ec45623f Mon Sep 17 00:00:00 2001 From: nieyuyao Date: Fri, 29 Apr 2022 00:48:21 +0800 Subject: [PATCH 2/2] fix: ci error and typo --- packages/vitest/src/runtime/error.ts | 3 +-- packages/vitest/src/runtime/mocker.ts | 10 +++------- packages/vitest/src/utils/base.ts | 4 ++++ test/core/test/replace-matcher.test.ts | 24 ++++++++++++++---------- 4 files changed, 22 insertions(+), 19 deletions(-) diff --git a/packages/vitest/src/runtime/error.ts b/packages/vitest/src/runtime/error.ts index 26b12f29f952..7d48fab57396 100644 --- a/packages/vitest/src/runtime/error.ts +++ b/packages/vitest/src/runtime/error.ts @@ -1,8 +1,7 @@ import { format } from 'util' import { util } from 'chai' import { stringify } from '../integrations/chai/jest-matcher-utils' -import { clone } from '../utils' -import { getType } from './mocker' +import { clone, getType } from '../utils' const OBJECT_PROTO = Object.getPrototypeOf({}) diff --git a/packages/vitest/src/runtime/mocker.ts b/packages/vitest/src/runtime/mocker.ts index ff54883c3228..48993c1e8d75 100644 --- a/packages/vitest/src/runtime/mocker.ts +++ b/packages/vitest/src/runtime/mocker.ts @@ -3,17 +3,13 @@ import { isNodeBuiltin } from 'mlly' import { basename, dirname, resolve } from 'pathe' import { normalizeRequestId, toFilePath } from 'vite-node/utils' import type { ModuleCacheMap } from 'vite-node/client' -import { getAllProperties, getWorkerState, isWindows, mergeSlashes, slash } from '../utils' +import { getAllProperties, getType, getWorkerState, isWindows, mergeSlashes, slash } from '../utils' import { distDir } from '../constants' import type { PendingSuiteMock } from '../types/mocker' import type { ExecuteOptions } from './execute' type Callback = (...args: any[]) => unknown -export function getType(value: unknown): string { - return Object.prototype.toString.apply(value).slice(8, -1) -} - export class VitestMocker { private static pendingIds: PendingSuiteMock[] = [] private static spyModule?: typeof import('../integrations/spy') @@ -156,9 +152,9 @@ export class VitestMocker { const newObj: Record = {} - const proprieties = getAllProperties(value) + const properties = getAllProperties(value) - for (const k of proprieties) { + for (const k of properties) { newObj[k] = this.mockValue(value[k]) const type = getType(value[k]) diff --git a/packages/vitest/src/utils/base.ts b/packages/vitest/src/utils/base.ts index 46358460460b..c231f1b69431 100644 --- a/packages/vitest/src/utils/base.ts +++ b/packages/vitest/src/utils/base.ts @@ -32,6 +32,10 @@ export function mergeSlashes(str: string) { export const noop = () => { } +export function getType(value: unknown): string { + return Object.prototype.toString.apply(value).slice(8, -1) +} + export function clone(val: T): T { let k: any, out: any, tmp: any diff --git a/test/core/test/replace-matcher.test.ts b/test/core/test/replace-matcher.test.ts index ef68bd17e144..d61a461fa47a 100644 --- a/test/core/test/replace-matcher.test.ts +++ b/test/core/test/replace-matcher.test.ts @@ -6,15 +6,26 @@ describe('replace asymmetric matcher', () => { const replaced = replaceAsymmetricMatcher(actual, expected) expect(replaced.replacedActual).toEqual(replaced.replacedExpected) } - it('should works', () => { + it('should work when various types are passed in', () => { expectReplaceAsymmetricMatcher(null, null) expectReplaceAsymmetricMatcher(undefined, undefined) + expectReplaceAsymmetricMatcher({}, {}) + expectReplaceAsymmetricMatcher([1, 2], [1, 2]) + expectReplaceAsymmetricMatcher({}, expect.any(Object)) expectReplaceAsymmetricMatcher(() => {}, expect.any(Function)) + expectReplaceAsymmetricMatcher(Promise, expect.any(Function)) expectReplaceAsymmetricMatcher(false, expect.any(Boolean)) + expectReplaceAsymmetricMatcher([1, 2], [1, expect.any(Number)]) expectReplaceAsymmetricMatcher(false, expect.anything()) + expectReplaceAsymmetricMatcher({}, expect.anything()) expectReplaceAsymmetricMatcher(Symbol, expect.anything()) + expectReplaceAsymmetricMatcher(Promise, expect.anything()) + expectReplaceAsymmetricMatcher(new Map([['a', 1]]), expect.anything()) + expectReplaceAsymmetricMatcher(new Set([1, 2]), expect.anything()) + expectReplaceAsymmetricMatcher(new ArrayBuffer(8), expect.anything()) + expectReplaceAsymmetricMatcher([1, 2], [1, expect.anything()]) expectReplaceAsymmetricMatcher({ - str: 'string', + str: 'a', arr: [1, 2], }, { str: expect.any(String), @@ -28,18 +39,11 @@ describe('replace asymmetric matcher', () => { arr: expect.anything(), }) expectReplaceAsymmetricMatcher({ - str: 'world', + str: 'a', arr: [1, 2], }, { str: expect.any(String), arr: [1, expect.anything()], }) - expectReplaceAsymmetricMatcher({ - str: 'world', - bool: false, - }, { - str: expect.any(String), - bool: expect.anything(), - }) }) })