/**
 * 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.
 *
 */ import { isPrimitive } from './get-type' ; import { equals , isA , isImmutableUnorderedKeyed , isImmutableUnorderedSet , } from './jasmineUtils' ; type GetPath = { hasEndProp ? : boolean ; lastTraversedObject : unknown ; traversedPath : Array < string > ; value ? : unknown ; } ; /**
 * Checks if `hasOwnProperty(object, key)` up the prototype chain, stopping at `Object.prototype`.
 */ const hasPropertyInObject = ( object , key ) : boolean => { const shouldTerminate = ! object || typeof object !== 'object' || object === Object . prototype ; if ( shouldTerminate ) { return false ; } return ( Object . prototype . hasOwnProperty . call ( object , key ) || hasPropertyInObject ( Object . getPrototypeOf ( object ) , key ) ) ; } ; export const getPath = ( object , propertyPath ) : GetPath => { if ( ! Array . isArray ( propertyPath ) ) { propertyPath = ( propertyPath as string ) . split ( '.' ) ; } if ( propertyPath . length ) { const lastProp = propertyPath . length === 1 ; const prop = propertyPath [ 0 ] ; const newObject = object [ prop ] ; if ( ! lastProp && ( newObject === null || newObject === undefined ) ) { return { hasEndProp : false , lastTraversedObject : object , traversedPath : [ ] , } ; } const result = getPath ( newObject , propertyPath . slice ( 1 ) ) ; if ( result . lastTraversedObject === null ) { result . lastTraversedObject = object ; } result . traversedPath . unshift ( prop ) ; if ( lastProp ) { result . hasEndProp = newObject !== undefined || ( ! isPrimitive ( object ) && prop in object ) ; if ( ! result . hasEndProp ) { result . traversedPath . shift ( ) ; } } return result ; } return { lastTraversedObject : null , traversedPath : [ ] , value : object , } ; } ; export const getObjectSubset = ( object , subset , seenReferences = new WeakMap ( ) ) : any => { if ( Array . isArray ( object ) ) { if ( Array . isArray ( subset ) && subset . length === object . length ) { return subset . map ( ( sub , i ) => getObjectSubset ( object [ i ] , sub ) ) ; } } else if ( object instanceof Date ) { return object ; } else if ( isObject ( object ) && isObject ( subset ) ) { if ( equals ( object , subset , [ iterableEquality , subsetEquality ] ) ) { return subset ; } const trimmed = { } ; seenReferences . set ( object , trimmed ) ; Object . keys ( object ) . filter ( ( key ) => hasPropertyInObject ( subset , key ) ) . forEach ( ( key ) => { trimmed [ key ] = seenReferences . has ( object [ key ] ) ? seenReferences . get ( object [ key ] ) : getObjectSubset ( object [ key ] , subset [ key ] , seenReferences ) ; } ) ; if ( Object . keys ( trimmed ) . length > 0 ) { return trimmed ; } } return object ; } ; const IteratorSymbol = Symbol . iterator ; const hasIterator = ( object ) => ! ! ( object != null && object [ IteratorSymbol ] ) ; export const iterableEquality = ( a , b , aStack = [ ] , bStack = [ ] ) : 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 -- ) { if ( aStack [ length ] === a ) { return bStack [ length ] === b ; } } aStack . push ( a ) ; bStack . push ( b ) ; const iterableEqualityWithStack = ( a , b ) => iterableEquality ( a , b , [ ... 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 , [ iterableEqualityWithStack , ] ) ; if ( isEqual === true ) { has = true ; } } if ( has === false ) { allFound = false ; break ; } } } 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 ] ) , [ iterableEqualityWithStack , ] ) ) { let has = false ; for ( const bEntry of b ) { const matchedKey = equals ( aEntry [ 0 ] , bEntry [ 0 ] , [ iterableEqualityWithStack , ] ) ; let matchedValue = false ; if ( matchedKey === true ) { matchedValue = equals ( aEntry [ 1 ] , bEntry [ 1 ] , [ iterableEqualityWithStack , ] ) ; } if ( matchedValue === true ) { has = true ; } } if ( has === false ) { allFound = false ; break ; } } } 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 , [ iterableEqualityWithStack ] ) ) { return false ; } } if ( ! bIterator . next ( ) . done ) { return false ; } aStack . pop ( ) ; bStack . pop ( ) ; return true ; } ; const isObject = ( a ) => a !== null && typeof a === 'object' ; const isObjectWithKeys = ( a ) => isObject ( a ) && ! ( a instanceof Error ) && ! ( a instanceof Array ) && ! ( a instanceof Date ) ; export const subsetEquality = ( object , subset ) : boolean | undefined => { const subsetEqualityWithContext = ( seenReferences = new WeakMap ( ) ) => ( object , subset ) : boolean | undefined => { if ( ! isObjectWithKeys ( subset ) ) { return undefined ; } return Object . keys ( subset ) . every ( ( key ) => { if ( isObjectWithKeys ( subset [ key ] ) ) { if ( seenReferences . has ( subset [ key ] ) ) { return equals ( object [ key ] , subset [ key ] , [ iterableEquality , ] ) ; } seenReferences . set ( subset [ key ] , true ) ; } const result = object != null && hasPropertyInObject ( object , key ) && equals ( object [ key ] , subset [ key ] , [ iterableEquality , subsetEqualityWithContext ( seenReferences ) , ] ) ; seenReferences . delete ( subset [ key ] ) ; return result ; } ) ; } ; return subsetEqualityWithContext ( ) ( object , subset ) ; } ; export const typeEquality = ( a , b ) : boolean | undefined => { if ( a == null || b == null || a . constructor === b . constructor ) { return undefined ; } return false ; } ; export const arrayBufferEquality = ( a , b ) : boolean | undefined => { if ( ! ( a instanceof ArrayBuffer ) || ! ( b instanceof ArrayBuffer ) ) { return undefined ; } const dataViewA = new DataView ( a ) ; const dataViewB = new DataView ( b ) ; if ( dataViewA . byteLength !== dataViewB . byteLength ) { return false ; } for ( let i = 0 ; i < dataViewA . byteLength ; i ++ ) { if ( dataViewA . getUint8 ( i ) !== dataViewB . getUint8 ( i ) ) { return false ; } } return true ; } ; export const sparseArrayEquality = ( a , b ) : boolean | undefined => { if ( ! Array . isArray ( a ) || ! Array . isArray ( b ) ) { return undefined ; } const aKeys = Object . keys ( a ) ; const bKeys = Object . keys ( b ) ; return ( equals ( a , b , [ iterableEquality , typeEquality ] , true ) && equals ( aKeys , bKeys ) ) ; } ; export const partition = < T > ( items , predicate ) : [ Array < T > , Array < T > ] => { const result = [ [ ] , [ ] ] ; items . forEach ( ( item ) => result [ predicate ( item ) ? 0 : 1 ] . push ( item ) ) ; return result ; } ; export const isError = ( value ) : value is Error => { switch ( Object . prototype . toString . call ( value ) ) { case '[object Error]' : case '[object Exception]' : case '[object DOMException]' : return true ; default : return value instanceof Error ; } } ; export function emptyObject ( obj ) { return obj && typeof obj === 'object' ? ! Object . keys ( obj ) . length : false ; } const MULTILINE_REGEXP = /[\r\n]/ ; export const isOneline = ( expected , received ) : boolean => typeof expected === 'string' && typeof received === 'string' && ( ! MULTILINE_REGEXP . test ( expected ) || ! MULTILINE_REGEXP . test ( received ) ) ;