Skip to content

Instantly share code, notes, and snippets.

@Edward-Lombe
Last active March 28, 2017 18:38
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Edward-Lombe/30c1102fd319b71468b1 to your computer and use it in GitHub Desktop.
Save Edward-Lombe/30c1102fd319b71468b1 to your computer and use it in GitHub Desktop.
Strict Deep Javascript equality
beforeEach(function() {
jasmine.addMatchers({
toStrictlyEqual : function() {
return {
compare : function(actual, expected) {
var result = {};
result.pass = equals(actual, expected, function (a, b, sPath) {
result.message = 'Expected ' + sPath + ' = ' + b + ' to equal ' + a;
});
return result;
}
};
}
});
});
function equals(a, b, fnDiff, sPath) {
if (typeof fnDiff === 'undefined') {
fnDiff = function (a, b, sPath) {};
}
if (typeof sPath === 'undefined') {
sPath = '';
}
// short cut out
if (a === b) {
return true;
}
if (typeof a === typeof b) {
// object or array
if (typeof a === 'object') {
// if they are both arrays
if (Array.isArray(a) && Array.isArray(b)) {
// check that the lengths are the same
if (a.length !== b.length) {
fnDiff(a, b, sPath);
return false;
}
// recursively call the equals function on each element of the array
for (var i = a.length - 1; i >= 0; i--) {
var sNewPath = '';
if (sPath === '') {
sNewPath += '[' + i + ']';
} else {
sNewPath += sPath + '[' + i + ']';
}
if (!equals(a[i], b[i], fnDiff, sNewPath)) {
return false;
}
}
// if neither object is an array
} else if (a === null || b === null) {
if (a !== b) {
fnDiff(a, b, sPath);
return false;
}
} else if (!Array.isArray(a) && !Array.isArray(b)) {
// check that they have the same number of keys
if (Object.keys(a).length !== Object.keys(b).length) {
fnDiff(a, b, sPath);
return false;
}
for (var key in a) {
var sNewPath = '';
if (sPath === '') {
sNewPath += key;
} else {
sNewPath += sPath + '.' + key;
}
if (!b.hasOwnProperty(key)) {
fnDiff(a, b, sPath);
return false;
} else if (!equals(a[key], b[key], fnDiff, sNewPath)) {
return false;
}
}
} else {
return false;
}
} else if (typeof a === 'function') {
if (a.toString() !== b.toString()) {
fnDiff(a, b, sPath);
return false;
}
} else {
if (a !== b) {
fnDiff(a, b, sPath);
return false;
}
}
// type mismatch
} else {
fnDiff(a, b, sPath);
return false;
}
return true;
}
// may as well test it while it's in local scope
describe('the equals function', function () {
it('should act as "===" for simple literals', function () {
expect(equals(10, '10')).toBe(false);
expect(equals([1, 2, 3], [1, 2, 3])).toBe(true);
expect(equals('foo', 'Foo')).toBe(false);
expect(equals(undefined, null)).toBe(false);
expect(equals(undefined, undefined)).toBe(true);
expect(equals(null, null)).toBe(true);
});
it('should deep compare for objects', function () {
expect(equals(
{ foo : 'bar' },
{ bar : 'bar' }
)).toBe(false);
var a = { foo : { bar : { foo : { bar : { foo : 'bar ' } } } } };
var b = { foo : { bar : { foo : { bar : { foo : 'foo ' } } } } };
expect(equals(a, b)).toBe(false);
a = { foo : 'bar', bar : 'foo' };
a = { foo : 'bar', bar : 'foo', foobar : 'foobar' };
expect(equals(a, b)).toBe(false);
a = {
foo : 'bar',
array : [
{ foo : 'bar' },
{ foo : 'bar' },
{ foo : 'bar' }
]
};
b = {
foo : 'bar',
array : [
{ foo : 'bar' },
{ foo : 'bar' },
{ bar : 'bar' }
]
};
expect(equals(a, b, function (a, b, sPath) {
expect(a).toEqual({ foo: 'bar' });
expect(b).toEqual({ bar: 'bar' });
expect(sPath).toBe('array[2]');
})).toBe(false);
a = function () {
var foo = [];
for (var i = 0; i < 10; i++) {
foo.push(i);
}
};
b = function () {
var foo = [];
for (var j = 0; j < 10; j++) {
foo.push(j);
}
};
expect(equals(a, b)).toBe(false);
});
it('should show where the difference is for complex objects', function () {
var a = { foo : { bar : { foo : { bar : { foo : 3 } } } } };
var b = { foo : { bar : { foo : { bar : { foo : 2 } } } } };
equals(a, b, function (a, b, sPath) {
expect(sPath).toBe('foo.bar.foo.bar.foo');
expect(a).toBe(3);
expect(b).toBe(2);
});
a = [[[[[[[[[[[[[1]]]]]]]]]]]]];
b = [[[[[[[[[[[[[[2]]]]]]]]]]]]]];
expect(equals(a, b, function (a, b, sPath) {
expect(sPath).toBe('[0][0][0][0][0][0][0][0][0][0][0][0][0]');
expect(a).toBe(1);
expect(b).toEqual([2]);
})).toBe(false);
a = [1, 2, {
name : {
prop : ['eight', 'nine', {
foo : {
bar : 'foobar'
}
}]
}
}];
b = [1, 2, {
name : {
prop : ['eight', 'nine', {
foo : {
bar : 'Foobar'
}
}]
}
}];
expect(equals(a, b, function (a, b, sPath) {
expect(a).toBe('foobar');
expect(b).toBe('Foobar');
expect(sPath).toBe('[2].name.prop[2].foo.bar');
}));
});
it('should not use the call back if the objects are equal', function () {
var a = { foo : { bar : { foo : { bar : { foo : 'bar' } } } } };
var b = { foo : { bar : { foo : { bar : { foo : 'bar' } } } } };
var fnDiff = jasmine.createSpy('fnDiff');
equals(a, b, fnDiff);
expect(fnDiff).not.toHaveBeenCalled();
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment