You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

218 lines
4.5 KiB

2 years ago
// Smart comparison of three.js objects.
// Identifies significant differences between two objects.
// Performs deep comparison.
// Comparison stops after the first difference is found.
// Provides an explanation for the failure.
function SmartComparer() {
'use strict';
// Diagnostic message, when comparison fails.
var message;
return {
areEqual: areEqual,
getDiagnostic: function () {
return message;
}
};
// val1 - first value to compare (typically the actual value)
// val2 - other value to compare (typically the expected value)
function areEqual( val1, val2 ) {
// Values are strictly equal.
if ( val1 === val2 ) return true;
// Null or undefined values.
/* jshint eqnull:true */
if ( val1 == null || val2 == null ) {
if ( val1 != val2 ) {
return makeFail( 'One value is undefined or null', val1, val2 );
}
// Both null / undefined.
return true;
}
// Don't compare functions.
if ( isFunction( val1 ) && isFunction( val2 ) ) return true;
// Array comparison.
var arrCmp = compareArrays( val1, val2 );
if ( arrCmp !== undefined ) return arrCmp;
// Has custom equality comparer.
if ( val1.equals ) {
if ( val1.equals( val2 ) ) return true;
return makeFail( 'Comparison with .equals method returned false' );
}
// Object comparison.
var objCmp = compareObjects( val1, val2 );
if ( objCmp !== undefined ) return objCmp;
// if (JSON.stringify( val1 ) == JSON.stringify( val2 ) ) return true;
// Object differs (unknown reason).
return makeFail( 'Values differ', val1, val2 );
}
function isFunction( value ) {
// The use of `Object#toString` avoids issues with the `typeof` operator
// in Safari 8 which returns 'object' for typed array constructors, and
// PhantomJS 1.9 which returns 'function' for `NodeList` instances.
var tag = isObject( value ) ? Object.prototype.toString.call( value ) : '';
return tag == '[object Function]' || tag == '[object GeneratorFunction]';
}
function isObject( value ) {
// Avoid a V8 JIT bug in Chrome 19-20.
// See https://code.google.com/p/v8/issues/detail?id=2291 for more details.
var type = typeof value;
return !! value && ( type == 'object' || type == 'function' );
}
function compareArrays( val1, val2 ) {
var isArr1 = Array.isArray( val1 );
var isArr2 = Array.isArray( val2 );
// Compare type.
if ( isArr1 !== isArr2 ) return makeFail( 'Values are not both arrays' );
// Not arrays. Continue.
if ( ! isArr1 ) return undefined;
// Compare length.
var N1 = val1.length;
var N2 = val2.length;
if ( N1 !== val2.length ) return makeFail( 'Array length differs', N1, N2 );
// Compare content at each index.
for ( var i = 0; i < N1; i ++ ) {
var cmp = areEqual( val1[ i ], val2[ i ] );
if ( ! cmp ) return addContext( 'array index "' + i + '"' );
}
// Arrays are equal.
return true;
}
function compareObjects( val1, val2 ) {
var isObj1 = isObject( val1 );
var isObj2 = isObject( val2 );
// Compare type.
if ( isObj1 !== isObj2 ) return makeFail( 'Values are not both objects' );
// Not objects. Continue.
if ( ! isObj1 ) return undefined;
// Compare keys.
var keys1 = Object.keys( val1 );
var keys2 = Object.keys( val2 );
for ( var i = 0, l = keys1.length; i < l; i ++ ) {
if ( keys2.indexOf( keys1[ i ] ) < 0 ) {
return makeFail( 'Property "' + keys1[ i ] + '" is unexpected.' );
}
}
for ( var i = 0, l = keys2.length; i < l; i ++ ) {
if ( keys1.indexOf( keys2[ i ] ) < 0 ) {
return makeFail( 'Property "' + keys2[ i ] + '" is missing.' );
}
}
// Keys are the same. For each key, compare content until a difference is found.
var hadDifference = false;
for ( var i = 0, l = keys1.length; i < l; i ++ ) {
var key = keys1[ i ];
if ( key === 'uuid' || key === 'id' ) {
continue;
}
var prop1 = val1[ key ];
var prop2 = val2[ key ];
// Compare property content.
var eq = areEqual( prop1, prop2 );
// In case of failure, an message should already be set.
// Add context to low level message.
if ( ! eq ) {
addContext( 'property "' + key + '"' );
hadDifference = true;
}
}
return ! hadDifference;
}
function makeFail( msg, val1, val2 ) {
message = msg;
if ( arguments.length > 1 ) message += ' (' + val1 + ' vs ' + val2 + ')';
return false;
}
function addContext( msg ) {
// There should already be a validation message. Add more context to it.
message = message || 'Error';
message += ', at ' + msg;
return false;
}
}
export { SmartComparer };