kitchensink
Version:
Dispatch's awesome components and style guide
295 lines (266 loc) • 11.5 kB
JavaScript
'use strict';
var ObjectPrototype = Object.prototype;
var toStr = ObjectPrototype.toString;
var booleanValue = Boolean.prototype.valueOf;
var has = require('has');
var isArrowFunction = require('is-arrow-function');
var isBoolean = require('is-boolean-object');
var isDate = require('is-date-object');
var isGenerator = require('is-generator-function');
var isNumber = require('is-number-object');
var isRegex = require('is-regex');
var isString = require('is-string');
var isSymbol = require('is-symbol');
var isCallable = require('is-callable');
var isProto = Object.prototype.isPrototypeOf;
var foo = function foo() {};
var functionsHaveNames = foo.name === 'foo';
var symbolValue = typeof Symbol === 'function' ? Symbol.prototype.valueOf : null;
var symbolIterator = require('./getSymbolIterator')();
var collectionsForEach = require('./getCollectionsForEach')();
var getPrototypeOf = Object.getPrototypeOf;
if (!getPrototypeOf) {
/* eslint-disable no-proto */
if (typeof 'test'.__proto__ === 'object') {
getPrototypeOf = function (obj) {
return obj.__proto__;
};
} else {
getPrototypeOf = function (obj) {
var constructor = obj.constructor,
oldConstructor;
if (has(obj, 'constructor')) {
oldConstructor = constructor;
if (!(delete obj.constructor)) { // reset constructor
return null; // can't delete obj.constructor, return null
}
constructor = obj.constructor; // get real constructor
obj.constructor = oldConstructor; // restore constructor
}
return constructor ? constructor.prototype : ObjectPrototype; // needed for IE
};
}
/* eslint-enable no-proto */
}
var isArray = Array.isArray || function (value) {
return toStr.call(value) === '[object Array]';
};
var normalizeFnWhitespace = function normalizeFnWhitespace(fnStr) {
// this is needed in IE 9, at least, which has inconsistencies here.
return fnStr.replace(/^function ?\(/, 'function (').replace('){', ') {');
};
var tryMapSetEntries = function tryMapSetEntries(collection) {
var foundEntries = [];
try {
collectionsForEach.Map.call(collection, function (key, value) {
foundEntries.push([key, value]);
});
} catch (notMap) {
try {
collectionsForEach.Set.call(collection, function (value) {
foundEntries.push([value]);
});
} catch (notSet) {
return false;
}
}
return foundEntries;
};
module.exports = function whyNotEqual(value, other) {
if (value === other) { return ''; }
if (value == null || other == null) {
return value === other ? '' : String(value) + ' !== ' + String(other);
}
var valToStr = toStr.call(value);
var otherToStr = toStr.call(other);
if (valToStr !== otherToStr) {
return 'toStringTag is not the same: ' + valToStr + ' !== ' + otherToStr;
}
var valIsBool = isBoolean(value);
var otherIsBool = isBoolean(other);
if (valIsBool || otherIsBool) {
if (!valIsBool) { return 'first argument is not a boolean; second argument is'; }
if (!otherIsBool) { return 'second argument is not a boolean; first argument is'; }
var valBoolVal = booleanValue.call(value);
var otherBoolVal = booleanValue.call(other);
if (valBoolVal === otherBoolVal) { return ''; }
return 'primitive value of boolean arguments do not match: ' + valBoolVal + ' !== ' + otherBoolVal;
}
var valIsNumber = isNumber(value);
var otherIsNumber = isNumber(value);
if (valIsNumber || otherIsNumber) {
if (!valIsNumber) { return 'first argument is not a number; second argument is'; }
if (!otherIsNumber) { return 'second argument is not a number; first argument is'; }
var valNum = Number(value);
var otherNum = Number(other);
if (valNum === otherNum) { return ''; }
var valIsNaN = isNaN(value);
var otherIsNaN = isNaN(other);
if (valIsNaN && !otherIsNaN) {
return 'first argument is NaN; second is not';
} else if (!valIsNaN && otherIsNaN) {
return 'second argument is NaN; first is not';
} else if (valIsNaN && otherIsNaN) {
return '';
}
return 'numbers are different: ' + value + ' !== ' + other;
}
var valIsString = isString(value);
var otherIsString = isString(other);
if (valIsString || otherIsString) {
if (!valIsString) { return 'second argument is string; first is not'; }
if (!otherIsString) { return 'first argument is string; second is not'; }
var stringVal = String(value);
var otherVal = String(other);
if (stringVal === otherVal) { return ''; }
return 'string values are different: "' + stringVal + '" !== "' + otherVal + '"';
}
var valIsDate = isDate(value);
var otherIsDate = isDate(other);
if (valIsDate || otherIsDate) {
if (!valIsDate) { return 'second argument is Date, first is not'; }
if (!otherIsDate) { return 'first argument is Date, second is not'; }
var valTime = +value;
var otherTime = +other;
if (valTime === otherTime) { return ''; }
return 'Dates have different time values: ' + valTime + ' !== ' + otherTime;
}
var valIsRegex = isRegex(value);
var otherIsRegex = isRegex(other);
if (valIsRegex || otherIsRegex) {
if (!valIsRegex) { return 'second argument is RegExp, first is not'; }
if (!otherIsRegex) { return 'first argument is RegExp, second is not'; }
var regexStringVal = String(value);
var regexStringOther = String(other);
if (regexStringVal === regexStringOther) { return ''; }
return 'regular expressions differ: ' + regexStringVal + ' !== ' + regexStringOther;
}
var valIsArray = isArray(value);
var otherIsArray = isArray(other);
if (valIsArray || otherIsArray) {
if (!valIsArray) { return 'second argument is an Array, first is not'; }
if (!otherIsArray) { return 'first argument is an Array, second is not'; }
if (value.length !== other.length) {
return 'arrays have different length: ' + value.length + ' !== ' + other.length;
}
if (String(value) !== String(other)) { return 'stringified Arrays differ'; }
var index = value.length - 1;
var equal = '';
var valHasIndex, otherHasIndex;
while (equal === '' && index >= 0) {
valHasIndex = has(value, index);
otherHasIndex = has(other, index);
if (!valHasIndex && otherHasIndex) { return 'second argument has index ' + index + '; first does not'; }
if (valHasIndex && !otherHasIndex) { return 'first argument has index ' + index + '; second does not'; }
equal = whyNotEqual(value[index], other[index]);
index -= 1;
}
return equal;
}
var valueIsSym = isSymbol(value);
var otherIsSym = isSymbol(other);
if (valueIsSym !== otherIsSym) {
if (valueIsSym) { return 'first argument is Symbol; second is not'; }
return 'second argument is Symbol; first is not';
}
if (valueIsSym && otherIsSym) {
return symbolValue.call(value) === symbolValue.call(other) ? '' : 'first Symbol value !== second Symbol value';
}
var valueIsGen = isGenerator(value);
var otherIsGen = isGenerator(other);
if (valueIsGen !== otherIsGen) {
if (valueIsGen) { return 'first argument is a Generator; second is not'; }
return 'second argument is a Generator; first is not';
}
var valueIsArrow = isArrowFunction(value);
var otherIsArrow = isArrowFunction(other);
if (valueIsArrow !== otherIsArrow) {
if (valueIsArrow) { return 'first argument is an Arrow function; second is not'; }
return 'second argument is an Arrow function; first is not';
}
if (isCallable(value) || isCallable(other)) {
if (functionsHaveNames && whyNotEqual(value.name, other.name) !== '') {
return 'Function names differ: "' + value.name + '" !== "' + other.name + '"';
}
if (whyNotEqual(value.length, other.length) !== '') {
return 'Function lengths differ: ' + value.length + ' !== ' + other.length;
}
var valueStr = normalizeFnWhitespace(String(value));
var otherStr = normalizeFnWhitespace(String(other));
if (whyNotEqual(valueStr, otherStr) === '') { return ''; }
if (!valueIsGen && !valueIsArrow) {
return whyNotEqual(valueStr.replace(/\)\s*\{/, '){'), otherStr.replace(/\)\s*\{/, '){')) === '' ? '' : 'Function string representations differ';
}
return whyNotEqual(valueStr, otherStr) === '' ? '' : 'Function string representations differ';
}
if (typeof value === 'object' || typeof other === 'object') {
if (typeof value !== typeof other) { return 'arguments have a different typeof: ' + typeof value + ' !== ' + typeof other; }
if (isProto.call(value, other)) { return 'first argument is the [[Prototype]] of the second'; }
if (isProto.call(other, value)) { return 'second argument is the [[Prototype]] of the first'; }
if (getPrototypeOf(value) !== getPrototypeOf(other)) { return 'arguments have a different [[Prototype]]'; }
if (symbolIterator) {
var valueIteratorFn = value[symbolIterator];
var valueIsIterable = isCallable(valueIteratorFn);
var otherIteratorFn = other[symbolIterator];
var otherIsIterable = isCallable(otherIteratorFn);
if (valueIsIterable !== otherIsIterable) {
if (valueIsIterable) { return 'first argument is iterable; second is not'; }
return 'second argument is iterable; first is not';
}
if (valueIsIterable && otherIsIterable) {
var valueIterator = valueIteratorFn.call(value);
var otherIterator = otherIteratorFn.call(other);
var valueNext, otherNext, nextWhy;
do {
valueNext = valueIterator.next();
otherNext = otherIterator.next();
if (!valueNext.done && !otherNext.done) {
nextWhy = whyNotEqual(valueNext, otherNext);
if (nextWhy !== '') {
return 'iteration results are not equal: ' + nextWhy;
}
}
} while (!valueNext.done && !otherNext.done);
if (valueNext.done && !otherNext.done) { return 'first argument finished iterating before second'; }
if (!valueNext.done && otherNext.done) { return 'second argument finished iterating before first'; }
return '';
}
} else if (collectionsForEach.Map || collectionsForEach.Set) {
var valueEntries = tryMapSetEntries(value);
var otherEntries = tryMapSetEntries(other);
var valueEntriesIsArray = isArray(valueEntries);
var otherEntriesIsArray = isArray(otherEntries);
if (valueEntriesIsArray && !otherEntriesIsArray) { return 'first argument has Collection entries, second does not'; }
if (!valueEntriesIsArray && otherEntriesIsArray) { return 'second argument has Collection entries, first does not'; }
if (valueEntriesIsArray && otherEntriesIsArray) {
var entriesWhy = whyNotEqual(valueEntries, otherEntries);
return entriesWhy === '' ? '' : 'Collection entries differ: ' + entriesWhy;
}
}
var key, valueKeyIsRecursive, otherKeyIsRecursive, keyWhy;
for (key in value) {
if (has(value, key)) {
if (!has(other, key)) { return 'first argument has key "' + key + '"; second does not'; }
valueKeyIsRecursive = !!value[key] && value[key][key] === value;
otherKeyIsRecursive = !!other[key] && other[key][key] === other;
if (valueKeyIsRecursive !== otherKeyIsRecursive) {
if (valueKeyIsRecursive) { return 'first argument has a circular reference at key "' + key + '"; second does not'; }
return 'second argument has a circular reference at key "' + key + '"; first does not';
}
if (!valueKeyIsRecursive && !otherKeyIsRecursive) {
keyWhy = whyNotEqual(value[key], other[key]);
if (keyWhy !== '') {
return 'value at key "' + key + '" differs: ' + keyWhy;
}
}
}
}
for (key in other) {
if (has(other, key) && !has(value, key)) {
return 'second argument has key "' + key + '"; first does not';
}
}
return '';
}
return false;
};