Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/vitest-dev/vitest. Next generation testing framework powered by Vite. vitest.dev
test testing-tools vite
Something went wrong. Try again.
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781/*Copyright (c) 2008-2016 Pivotal Labs
Permission is hereby granted, free of charge, to any person obtaininga copy of this software and associated documentation files (the"Software"), to deal in the Software without restriction, includingwithout limitation the rights to use, copy, modify, merge, publish,distribute, sublicense, and/or sell copies of the Software, and topermit persons to whom the Software is furnished to do so, subject tothe following conditions:
The above copyright notice and this permission notice shall beincluded in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OFMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE ANDNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BELIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTIONOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTIONWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import type { StandardSchemaV1 } from '@standard-schema/spec'import type { AsymmetricMatcher } from './jest-asymmetric-matchers'import type { Tester, TesterContext } from './types'import { isObject } from '@vitest/utils/helpers'
// Extracted out of jasmine 2.5.2export function equals( a: unknown, b: unknown, customTesters?: Array<Tester>, strictCheck?: boolean,): boolean { customTesters = customTesters || [] return eq(a, b, [], [], customTesters, strictCheck ? hasKey : hasDefinedKey)}
const functionToString = Function.prototype.toString
export function isAsymmetric(obj: any): obj is AsymmetricMatcher<any> { return ( !!obj && typeof obj === 'object' && 'asymmetricMatch' in obj && isA('Function', obj.asymmetricMatch) )}
export function hasAsymmetric(obj: any, seen: Set<any> = new Set()): boolean { if (seen.has(obj)) { return false } seen.add(obj) if (isAsymmetric(obj)) { return true } if (Array.isArray(obj)) { return obj.some((i) => hasAsymmetric(i, seen)) } if (obj instanceof Set) { return Array.from(obj).some((i) => hasAsymmetric(i, seen)) } if (isObject(obj)) { return Object.values(obj).some((v) => hasAsymmetric(v, seen)) } return false}
function asymmetricMatch(a: any, b: any, customTesters: Array<Tester>) { const asymmetricA = isAsymmetric(a) const asymmetricB = isAsymmetric(b)
if (asymmetricA && asymmetricB) { return undefined }
// Asymmetric matchers define their own subset semantics. const testers = customTesters.filter((tester) => !isSubsetEqualityTester(tester))
if (asymmetricA) { return a.asymmetricMatch(b, testers) }
if (asymmetricB) { return b.asymmetricMatch(a, testers) }}
// https://github.com/jestjs/jest/blob/905bcbced3d40cdf7aadc4cdf6fb731c4bb3dbe3/packages/expect-utils/src/utils.ts#L509export function isError(value: unknown): value is Error { if (typeof Error.isError === 'function') { return Error.isError(value) } switch (Object.prototype.toString.call(value)) { case '[object Error]': case '[object Exception]': case '[object DOMException]': return true default: return value instanceof Error }}
// Equality function lovingly adapted from isEqual in// [Underscore](http://underscorejs.org)function eq( a: any, b: any, aStack: Array<unknown>, bStack: Array<unknown>, customTesters: Array<Tester>, hasKey: any,): boolean { let result = true
const asymmetricResult = asymmetricMatch(a, b, customTesters) if (asymmetricResult !== undefined) { return asymmetricResult }
const testerContext: TesterContext = { equals } for (let i = 0; i < customTesters.length; i++) { const customTesterResult = customTesters[i].call(testerContext, a, b, customTesters) if (customTesterResult !== undefined) { return customTesterResult } }
if (typeof URL === 'function' && a instanceof URL && b instanceof URL) { return a.href === b.href }
if (Object.is(a, b)) { return true }
// A strict comparison is necessary because `null == undefined`. if (a === null || b === null) { return a === b }
const className = Object.prototype.toString.call(a) if (className !== Object.prototype.toString.call(b)) { return false }
switch (className) { case '[object Boolean]': case '[object String]': case '[object Number]': if (typeof a !== typeof b) { // One is a primitive, one a `new Primitive()` return false } else if (typeof a !== 'object' && typeof b !== 'object') { // both are proper primitives return Object.is(a, b) } else { // both are `new Primitive()`s return Object.is(a.valueOf(), b.valueOf()) } case '[object Date]': { const numA = +a const numB = +b // Coerce dates to numeric primitive values. Dates are compared by their // millisecond representations. Note that invalid dates with millisecond representations // of `NaN` are equivalent. return numA === numB || (Number.isNaN(numA) && Number.isNaN(numB)) } // RegExps are compared by their source patterns and flags. case '[object RegExp]': return a.source === b.source && a.flags === b.flags case '[object Temporal.Instant]': case '[object Temporal.ZonedDateTime]': case '[object Temporal.PlainDateTime]': case '[object Temporal.PlainDate]': case '[object Temporal.PlainTime]': case '[object Temporal.PlainYearMonth]': case '[object Temporal.PlainMonthDay]': return a.equals(b) case '[object Temporal.Duration]': return a.toString() === b.toString() } if (typeof a !== 'object' || typeof b !== 'object') { return false }
// Use DOM3 method isEqualNode (IE>=9) if (isDomNode(a) && isDomNode(b)) { return a.isEqualNode(b) }
// Used to detect circular references. let length = aStack.length while (length--) { // Linear search. Performance is inversely proportional to the number of // unique nested structures. // circular references at same depth are equal // circular reference is not equal to non-circular one if (aStack[length] === a) { return bStack[length] === b } else if (bStack[length] === b) { return false } } // Add the first object to the stack of traversed objects. aStack.push(a) bStack.push(b) // Recursively compare objects and arrays. // Compare array lengths to determine if a deep comparison is necessary. if (className === '[object Array]' && a.length !== b.length) { return false }
if (isError(a) && isError(b)) { try { return isErrorEqual(a, b, aStack, bStack, customTesters, hasKey) } finally { aStack.pop() bStack.pop() } }
// Deep compare objects. const aKeys = keys(a, hasKey) let key let size = aKeys.length
// Ensure that both objects contain the same number of properties before comparing deep equality. if (keys(b, hasKey).length !== size) { return false }
while (size--) { key = aKeys[size]
// Deep compare each member result = hasKey(b, key) && eq(a[key], b[key], aStack, bStack, customTesters, hasKey)
if (!result) { return false } } // Remove the first object from the stack of traversed objects. aStack.pop() bStack.pop()
return result}
function isErrorEqual( a: Error, b: Error, aStack: Array<unknown>, bStack: Array<unknown>, customTesters: Array<Tester>, hasKey: any,) { // https://nodejs.org/docs/latest-v22.x/api/assert.html#comparison-details // - [[Prototype]] of objects are compared using the === operator. // - Only enumerable "own" properties are considered. // - Error names, messages, causes, and errors are always compared, even if these are not enumerable properties. errors is also compared.
let result = Object.prototype.toString.call(a) === Object.prototype.toString.call(b) && a.name === b.name && a.message === b.message // check Error.cause asymmetrically if (typeof b.cause !== 'undefined') { result &&= eq(a.cause, b.cause, aStack, bStack, customTesters, hasKey) } // AggregateError.errors if (a instanceof AggregateError && b instanceof AggregateError) { result &&= eq(a.errors, b.errors, aStack, bStack, customTesters, hasKey) } // spread to compare enumerable properties result &&= eq({ ...a }, { ...b }, aStack, bStack, customTesters, hasKey) return result}
function keys(obj: object, hasKey: (obj: object, key: string) => boolean) { const keys = []
for (const key in obj) { if (hasKey(obj, key)) { keys.push(key) } } return keys.concat( (Object.getOwnPropertySymbols(obj) as Array<any>).filter( (symbol) => (Object.getOwnPropertyDescriptor(obj, symbol) as PropertyDescriptor).enumerable, ), )}
function hasDefinedKey(obj: any, key: string) { return hasKey(obj, key) && obj[key] !== undefined}
function hasKey(obj: any, key: string) { return Object.hasOwn(obj, key)}
export function isA(typeName: string, value: unknown): boolean { return Object.prototype.toString.apply(value) === `[object ${typeName}]`}
function isDomNode(obj: any): boolean { return ( obj !== null && typeof obj === 'object' && 'nodeType' in obj && typeof obj.nodeType === 'number' && 'nodeName' in obj && typeof obj.nodeName === 'string' && 'isEqualNode' in obj && typeof obj.isEqualNode === 'function' )}
export function fnNameFor(func: Function): string { if (func.name) { return func.name }
const matches = functionToString .call(func) .match(/^(?:async)?\s*function\s*(?:\*\s*)?([\w$]+)\s*\(/) return matches ? matches[1] : '<anonymous>'}
function getPrototype(obj: object) { if (Object.getPrototypeOf) { return Object.getPrototypeOf(obj) }
if (obj.constructor.prototype === obj) { return null }
return obj.constructor.prototype}
export function hasProperty(obj: object | null, property: string): boolean { if (!obj) { return false }
if (Object.hasOwn(obj, property)) { return true }
return hasProperty(getPrototype(obj), property)}
// SENTINEL constants are from https://github.com/immutable-js/immutable-jsconst IS_KEYED_SENTINEL = '@@__IMMUTABLE_KEYED__@@'const IS_SET_SENTINEL = '@@__IMMUTABLE_SET__@@'const IS_LIST_SENTINEL = '@@__IMMUTABLE_LIST__@@'const IS_ORDERED_SENTINEL = '@@__IMMUTABLE_ORDERED__@@'const IS_RECORD_SYMBOL = '@@__IMMUTABLE_RECORD__@@'
export function isImmutableUnorderedKeyed(maybeKeyed: any): boolean { return !!(maybeKeyed && maybeKeyed[IS_KEYED_SENTINEL] && !maybeKeyed[IS_ORDERED_SENTINEL])}
export function isImmutableUnorderedSet(maybeSet: any): boolean { return !!(maybeSet && maybeSet[IS_SET_SENTINEL] && !maybeSet[IS_ORDERED_SENTINEL])}
function isObjectLiteral(source: unknown): source is Record<string, unknown> { return source != null && typeof source === 'object' && !Array.isArray(source)}
function isImmutableList(source: unknown): boolean { return Boolean(source && isObjectLiteral(source) && source[IS_LIST_SENTINEL])}
function isImmutableOrderedKeyed(source: unknown): boolean { return Boolean( source && isObjectLiteral(source) && source[IS_KEYED_SENTINEL] && source[IS_ORDERED_SENTINEL], )}
function isImmutableOrderedSet(source: unknown): boolean { return Boolean( source && isObjectLiteral(source) && source[IS_SET_SENTINEL] && source[IS_ORDERED_SENTINEL], )}
function isImmutableRecord(source: unknown): boolean { return Boolean(source && isObjectLiteral(source) && source[IS_RECORD_SYMBOL])}
/** * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * */const IteratorSymbol = Symbol.iterator
function hasIterator(object: any) { return !!(object != null && object[IteratorSymbol])}
export function iterableEquality( a: any, b: any, customTesters: Array<Tester> = [], aStack: Array<any> = [], bStack: Array<any> = [],): boolean | undefined { if ( typeof a !== 'object' || typeof b !== 'object' || Array.isArray(a) || Array.isArray(b) || !hasIterator(a) || !hasIterator(b) ) { return undefined }
if (a.constructor !== b.constructor) { return false }
let length = aStack.length while (length--) { // Linear search. Performance is inversely proportional to the number of // unique nested structures. // circular references at same depth are equal // circular reference is not equal to non-circular one if (aStack[length] === a) { return bStack[length] === b } } aStack.push(a) bStack.push(b)
const filteredCustomTesters: Array<Tester> = [ ...customTesters.filter((t) => t !== iterableEquality), iterableEqualityWithStack, ]
function iterableEqualityWithStack(a: any, b: any) { return iterableEquality(a, b, [...customTesters], [...aStack], [...bStack]) }
if (a.size !== undefined) { if (a.size !== b.size) { return false } else if (isA('Set', a) || isImmutableUnorderedSet(a)) { let allFound = true for (const aValue of a) { if (!b.has(aValue)) { let has = false for (const bValue of b) { const isEqual = equals(aValue, bValue, filteredCustomTesters) if (isEqual === true) { has = true } }
if (has === false) { allFound = false break } } } // Remove the first value from the stack of traversed values. aStack.pop() bStack.pop() return allFound } else if (isA('Map', a) || isImmutableUnorderedKeyed(a)) { let allFound = true for (const aEntry of a) { if (!b.has(aEntry[0]) || !equals(aEntry[1], b.get(aEntry[0]), filteredCustomTesters)) { let has = false for (const bEntry of b) { const matchedKey = equals(aEntry[0], bEntry[0], filteredCustomTesters)
let matchedValue = false if (matchedKey === true) { matchedValue = equals(aEntry[1], bEntry[1], filteredCustomTesters) }
if (matchedValue === true) { has = true } }
if (has === false) { allFound = false break } } } // Remove the first value from the stack of traversed values. aStack.pop() bStack.pop() return allFound } }
const bIterator = b[IteratorSymbol]()
for (const aValue of a) { const nextB = bIterator.next() if (nextB.done || !equals(aValue, nextB.value, filteredCustomTesters)) { return false } } if (!bIterator.next().done) { return false }
if ( !isImmutableList(a) && !isImmutableOrderedKeyed(a) && !isImmutableOrderedSet(a) && !isImmutableRecord(a) ) { const aEntries = Object.entries(a) const bEntries = Object.entries(b) if (!equals(aEntries, bEntries, filteredCustomTesters)) { return false } }
// Remove the first value from the stack of traversed values. aStack.pop() bStack.pop() return true}
/** * Checks if `hasOwnProperty(object, key)` up the prototype chain, stopping at `Object.prototype`. */function hasPropertyInObject(object: object, key: string | symbol): boolean { const shouldTerminate = !object || typeof object !== 'object' || object === Object.prototype
if (shouldTerminate) { return false }
return Object.hasOwn(object, key) || hasPropertyInObject(Object.getPrototypeOf(object), key)}
function isObjectWithKeys(a: any) { return ( isObject(a) && !isError(a) && !Array.isArray(a) && !(a instanceof Date) && !(a instanceof Set) && !(a instanceof Map) )}
// Recursive subset testers are closures, so track them by identity.const subsetEqualityTesters = new WeakSet<Tester>()
function registerSubsetEqualityTester<T extends Tester>(tester: T): T { subsetEqualityTesters.add(tester) return tester}
function isSubsetEqualityTester(tester: Tester): boolean { return tester === subsetEquality || subsetEqualityTesters.has(tester)}
export function subsetEquality( object: unknown, subset: unknown, customTesters: Array<Tester> = [],): boolean | undefined { const filteredCustomTesters = customTesters.filter((t) => t !== subsetEquality) // subsetEquality needs to keep track of the references // it has already visited to avoid infinite loops in case // there are circular references in the subset passed to it. const subsetEqualityWithContext = (seenReferences: WeakMap<object, boolean> = new WeakMap()) => registerSubsetEqualityTester((object: any, subset: any): boolean | undefined => { if (!isObjectWithKeys(subset)) { return undefined }
return Object.keys(subset).every((key) => { if (subset[key] != null && typeof subset[key] === 'object') { if (seenReferences.has(subset[key])) { return equals(object[key], subset[key], filteredCustomTesters) }
seenReferences.set(subset[key], true) } const result = object != null && hasPropertyInObject(object, key) && equals(object[key], subset[key], [ ...filteredCustomTesters, subsetEqualityWithContext(seenReferences), ]) // The main goal of using seenReference is to avoid circular node on tree. // It will only happen within a parent and its child, not a node and nodes next to it (same level) // We should keep the reference for a parent and its child only // Thus we should delete the reference immediately so that it doesn't interfere // other nodes within the same level on tree. seenReferences.delete(subset[key]) return result }) })
return subsetEqualityWithContext()(object, subset)}
export function typeEquality(a: any, b: any): boolean | undefined { if (a == null || b == null || a.constructor === b.constructor) { return undefined }
return false}
export function arrayBufferEquality(a: unknown, b: unknown): boolean | undefined { let dataViewA = a as DataView let dataViewB = b as DataView
if (!(a instanceof DataView && b instanceof DataView)) { if (!(a instanceof ArrayBuffer) || !(b instanceof ArrayBuffer)) { return undefined }
try { dataViewA = new DataView(a) dataViewB = new DataView(b) } catch { return undefined } }
// Buffers are not equal when they do not have the same byte length if (dataViewA.byteLength !== dataViewB.byteLength) { return false }
// Check if every byte value is equal to each other for (let i = 0; i < dataViewA.byteLength; i++) { if (dataViewA.getUint8(i) !== dataViewB.getUint8(i)) { return false } }
return true}
export function sparseArrayEquality( a: unknown, b: unknown, customTesters: Array<Tester> = [],): boolean | undefined { if (!Array.isArray(a) || !Array.isArray(b)) { return undefined }
// A sparse array [, , 1] will have keys ["2"] whereas [undefined, undefined, 1] will have keys ["0", "1", "2"] const aKeys = Object.keys(a) const bKeys = Object.keys(b) const filteredCustomTesters = customTesters.filter((t) => t !== sparseArrayEquality) return equals(a, b, filteredCustomTesters, true) && equals(aKeys, bKeys)}
export function generateToBeMessage( deepEqualityName: string, expected = '#{this}', actual = '#{exp}',): string { const toBeMessage = `expected ${expected} to be ${actual} // Object.is equality`
if (['toStrictEqual', 'toEqual'].includes(deepEqualityName)) { return `${toBeMessage}\n\nIf it should pass with deep equality, replace "toBe" with "${deepEqualityName}"\n\nExpected: ${expected}\nReceived: serializes to the same string\n` }
return toBeMessage}
export function pluralize(word: string, count: number): string { return `${count} ${word}${count === 1 ? '' : 's'}`}
export function getObjectKeys(object: object): Array<string | symbol> { return [ ...Object.keys(object), ...Object.getOwnPropertySymbols(object).filter( (s) => Object.getOwnPropertyDescriptor(object, s)?.enumerable, ), ]}
export function getObjectSubset( object: any, subset: any, customTesters: Array<Tester>,): { subset: any; stripped: number } { let stripped = 0
const getObjectSubsetWithContext = (seenReferences: WeakMap<object, boolean> = new WeakMap()) => (object: any, subset: any): any => { if (Array.isArray(object)) { if (Array.isArray(subset) && subset.length === object.length) { // The map method returns correct subclass of subset. return subset.map((sub: any, i: number) => getObjectSubsetWithContext(seenReferences)(object[i], sub), ) } } else if (object instanceof Date) { return object } else if (isObject(object) && isObject(subset)) { if (equals(object, subset, [...customTesters, iterableEquality, subsetEquality])) { // return "expected" subset to avoid showing irrelevant toMatchObject diff return subset }
const trimmed: any = {} seenReferences.set(object, trimmed)
// preserve constructor for toMatchObject diff if ( typeof object.constructor === 'function' && typeof object.constructor.name === 'string' ) { Object.defineProperty(trimmed, 'constructor', { enumerable: false, value: object.constructor, }) }
for (const key of getObjectKeys(object)) { if (hasPropertyInObject(subset, key)) { trimmed[key] = seenReferences.has(object[key]) ? seenReferences.get(object[key]) : getObjectSubsetWithContext(seenReferences)(object[key], subset[key]) } else { if (!seenReferences.has(object[key])) { stripped += 1 if (isObject(object[key])) { stripped += getObjectKeys(object[key]).length }
getObjectSubsetWithContext(seenReferences)(object[key], subset[key]) } } }
if (getObjectKeys(trimmed).length > 0) { return trimmed } }
return object }
return { subset: getObjectSubsetWithContext()(object, subset), stripped }}
/** * Detects if an object is a Standard Schema V1 compatible schema */export function isStandardSchema(obj: any): obj is StandardSchemaV1 { return ( !!obj && (typeof obj === 'object' || typeof obj === 'function') && obj['~standard'] && typeof obj['~standard'].validate === 'function' )}