(function (root, factory) { if (typeof define === 'function' && define.amd) { // eslint-disable-line no-undef define(['deep-diff', 'expect.js'], factory);// eslint-disable-line no-undef } else if (typeof module === 'object' && module.exports) { module.exports = factory(require('../'), require('expect.js')); } else { root.returnExports = factory(root.DeepDiff, root.expect); } // eslint-disable-next-line no-undef }(typeof self !== 'undefined' ? self : this, function (deep, expect) { describe('deep-diff', function () { var empty = {}; describe('A target that has no properties', function () { it('shows no differences when compared to another empty object', function () { expect(deep.diff(empty, {})).to.be.an('undefined'); }); describe('when compared to a different type of keyless object', function () { var comparandTuples = [ ['an array', { key: [] }], ['an object', { key: {} }], ['a date', { key: new Date() }], ['a null', { key: null }], ['a regexp literal', { key: /a/ }], ['Math', { key: Math }] ]; comparandTuples.forEach(function (lhsTuple) { comparandTuples.forEach(function (rhsTuple) { if (lhsTuple[0] === rhsTuple[0]) { return; } it('shows differences when comparing ' + lhsTuple[0] + ' to ' + rhsTuple[0], function () { var diff = deep.diff(lhsTuple[1], rhsTuple[1]); expect(diff).to.be.ok(); expect(diff.length).to.be(1); expect(diff[0]).to.have.property('kind'); expect(diff[0].kind).to.be('E'); }); }); }); }); describe('when compared with an object having other properties', function () { var comparand = { other: 'property', another: 13.13 }; var diff = deep.diff(empty, comparand); it('the differences are reported', function () { expect(diff).to.be.ok(); expect(diff.length).to.be(2); expect(diff[0]).to.have.property('kind'); expect(diff[0].kind).to.be('N'); expect(diff[0]).to.have.property('path'); expect(diff[0].path).to.be.an(Array); expect(diff[0].path[0]).to.eql('other'); expect(diff[0]).to.have.property('rhs'); expect(diff[0].rhs).to.be('property'); expect(diff[1]).to.have.property('kind'); expect(diff[1].kind).to.be('N'); expect(diff[1]).to.have.property('path'); expect(diff[1].path).to.be.an(Array); expect(diff[1].path[0]).to.eql('another'); expect(diff[1]).to.have.property('rhs'); expect(diff[1].rhs).to.be(13.13); }); }); }); describe('A target that has one property', function () { var lhs = { one: 'property' }; it('shows no differences when compared to itself', function () { expect(deep.diff(lhs, lhs)).to.be.an('undefined'); }); it('shows the property as removed when compared to an empty object', function () { var diff = deep.diff(lhs, empty); expect(diff).to.be.ok(); expect(diff.length).to.be(1); expect(diff[0]).to.have.property('kind'); expect(diff[0].kind).to.be('D'); }); it('shows the property as edited when compared to an object with null', function () { var diff = deep.diff(lhs, { one: null }); expect(diff).to.be.ok(); expect(diff.length).to.be(1); expect(diff[0]).to.have.property('kind'); expect(diff[0].kind).to.be('E'); }); it('shows the property as edited when compared to an array', function () { var diff = deep.diff(lhs, ['one']); expect(diff).to.be.ok(); expect(diff.length).to.be(1); expect(diff[0]).to.have.property('kind'); expect(diff[0].kind).to.be('E'); }); }); describe('A target that has null value', function () { var lhs = { key: null }; it('shows no differences when compared to itself', function () { expect(deep.diff(lhs, lhs)).to.be.an('undefined'); }); it('shows the property as removed when compared to an empty object', function () { var diff = deep.diff(lhs, empty); expect(diff).to.be.ok(); expect(diff.length).to.be(1); expect(diff[0]).to.have.property('kind'); expect(diff[0].kind).to.be('D'); }); it('shows the property is changed when compared to an object that has value', function () { var diff = deep.diff(lhs, { key: 'value' }); expect(diff).to.be.ok(); expect(diff.length).to.be(1); expect(diff[0]).to.have.property('kind'); expect(diff[0].kind).to.be('E'); }); it('shows that an object property is changed when it is set to null', function () { lhs.key = { nested: 'value' }; var diff = deep.diff(lhs, { key: null }); expect(diff).to.be.ok(); expect(diff.length).to.be(1); expect(diff[0]).to.have.property('kind'); expect(diff[0].kind).to.be('E'); }); }); describe('A target that has a date value', function () { var lhs = { key: new Date(555555555555) }; it('shows the property is changed with a new date value', function () { var diff = deep.diff(lhs, { key: new Date(777777777777) }); expect(diff).to.be.ok(); expect(diff.length).to.be(1); expect(diff[0]).to.have.property('kind'); expect(diff[0].kind).to.be('E'); }); }); describe('A target that has a NaN', function () { var lhs = { key: NaN }; it('shows the property is changed when compared to another number', function () { var diff = deep.diff(lhs, { key: 0 }); expect(diff).to.be.ok(); expect(diff.length).to.be(1); expect(diff[0]).to.have.property('kind'); expect(diff[0].kind).to.be('E'); }); it('shows no differences when compared to another NaN', function () { var diff = deep.diff(lhs, { key: NaN }); expect(diff).to.be.an('undefined'); }); }); describe('can revert namespace using noConflict', function () { if (deep.noConflict) { deep = deep.noConflict(); it('conflict is restored (when applicable)', function () { // In node there is no global conflict. if (typeof globalConflict !== 'undefined') { expect(DeepDiff).to.be(deep); // eslint-disable-line no-undef } }); it('DeepDiff functionality available through result of noConflict()', function () { expect(deep.applyDiff).to.be.a('function'); }); } }); describe('When filtering keys', function () { var lhs = { enhancement: 'Filter/Ignore Keys?', numero: 11, submittedBy: 'ericclemmons', supportedBy: ['ericclemmons'], status: 'open' }; var rhs = { enhancement: 'Filter/Ignore Keys?', numero: 11, submittedBy: 'ericclemmons', supportedBy: [ 'ericclemmons', 'TylerGarlick', 'flitbit', 'ergdev' ], status: 'closed', fixedBy: 'flitbit' }; describe('if the filtered property is an array', function () { it('changes to the array do not appear as a difference', function () { var prefilter = function (path, key) { return key === 'supportedBy'; }; var diff = deep(lhs, rhs, prefilter); expect(diff).to.be.ok(); expect(diff.length).to.be(2); expect(diff[0]).to.have.property('kind'); expect(diff[0].kind).to.be('E'); expect(diff[1]).to.have.property('kind'); expect(diff[1].kind).to.be('N'); }); }); describe('if the filtered property is not an array', function () { it('changes do not appear as a difference', function () { var prefilter = function (path, key) { return key === 'fixedBy'; }; var diff = deep(lhs, rhs, prefilter); expect(diff).to.be.ok(); expect(diff.length).to.be(4); expect(diff[0]).to.have.property('kind'); expect(diff[0].kind).to.be('A'); expect(diff[1]).to.have.property('kind'); expect(diff[1].kind).to.be('A'); expect(diff[2]).to.have.property('kind'); expect(diff[2].kind).to.be('A'); expect(diff[3]).to.have.property('kind'); expect(diff[3].kind).to.be('E'); }); }); }); describe('A target that has nested values', function () { var nestedOne = { noChange: 'same', levelOne: { levelTwo: 'value' }, arrayOne: [{ objValue: 'value' }] }; var nestedTwo = { noChange: 'same', levelOne: { levelTwo: 'another value' }, arrayOne: [{ objValue: 'new value' }, { objValue: 'more value' }] }; it('shows no differences when compared to itself', function () { expect(deep.diff(nestedOne, nestedOne)).to.be.an('undefined'); }); it('shows the property as removed when compared to an empty object', function () { var diff = deep(nestedOne, empty); expect(diff).to.be.ok(); expect(diff.length).to.be(3); expect(diff[0]).to.have.property('kind'); expect(diff[0].kind).to.be('D'); expect(diff[1]).to.have.property('kind'); expect(diff[1].kind).to.be('D'); }); it('shows the property is changed when compared to an object that has value', function () { var diff = deep.diff(nestedOne, nestedTwo); expect(diff).to.be.ok(); expect(diff.length).to.be(3); }); it('shows the property as added when compared to an empty object on left', function () { var diff = deep.diff(empty, nestedOne); expect(diff).to.be.ok(); expect(diff.length).to.be(3); expect(diff[0]).to.have.property('kind'); expect(diff[0].kind).to.be('N'); }); describe('when diff is applied to a different empty object', function () { var diff = deep.diff(nestedOne, nestedTwo); it('has result with nested values', function () { var result = {}; deep.applyChange(result, nestedTwo, diff[0]); expect(result.levelOne).to.be.ok(); expect(result.levelOne).to.be.an('object'); expect(result.levelOne.levelTwo).to.be.ok(); expect(result.levelOne.levelTwo).to.eql('another value'); }); it('has result with array object values', function () { var result = {}; deep.applyChange(result, nestedTwo, diff[2]); expect(result.arrayOne).to.be.ok(); expect(result.arrayOne).to.be.an('array'); expect(result.arrayOne[0]).to.be.ok(); expect(result.arrayOne[0].objValue).to.be.ok(); expect(result.arrayOne[0].objValue).to.equal('new value'); }); it('has result with added array objects', function () { var result = {}; deep.applyChange(result, nestedTwo, diff[1]); expect(result.arrayOne).to.be.ok(); expect(result.arrayOne).to.be.an('array'); expect(result.arrayOne[1]).to.be.ok(); expect(result.arrayOne[1].objValue).to.be.ok(); expect(result.arrayOne[1].objValue).to.equal('more value'); }); }); }); describe('regression test for bug #10, ', function () { var lhs = { id: 'Release', phases: [{ id: 'Phase1', tasks: [{ id: 'Task1' }, { id: 'Task2' }] }, { id: 'Phase2', tasks: [{ id: 'Task3' }] }] }; var rhs = { id: 'Release', phases: [{ // E: Phase1 -> Phase2 id: 'Phase2', tasks: [{ id: 'Task3' }] }, { id: 'Phase1', tasks: [{ id: 'Task1' }, { id: 'Task2' }] }] }; describe('differences in nested arrays are detected', function () { var diff = deep.diff(lhs, rhs); // there should be differences expect(diff).to.be.ok(); expect(diff.length).to.be(6); it('differences can be applied', function () { var applied = deep.applyDiff(lhs, rhs); it('and the result equals the rhs', function () { expect(applied).to.eql(rhs); }); }); }); }); describe('regression test for bug #35', function () { var lhs = ['a', 'a', 'a']; var rhs = ['a']; it('can apply diffs between two top level arrays', function () { var differences = deep.diff(lhs, rhs); differences.forEach(function (it) { deep.applyChange(lhs, true, it); }); expect(lhs).to.eql(['a']); }); }); describe('Objects from different frames', function () { if (typeof globalConflict === 'undefined') { return; } // eslint-disable-next-line no-undef var frame = document.createElement('iframe'); // eslint-disable-next-line no-undef document.body.appendChild(frame); var lhs = new frame.contentWindow.Date(2010, 1, 1); var rhs = new frame.contentWindow.Date(2010, 1, 1); it('can compare date instances from a different frame', function () { var differences = deep.diff(lhs, rhs); expect(differences).to.be(undefined); }); }); describe('Comparing regexes should work', function () { var lhs = /foo/; var rhs = /foo/i; it('can compare regex instances', function () { var diff = deep.diff(lhs, rhs); expect(diff.length).to.be(1); expect(diff[0].kind).to.be('E'); expect(diff[0].path).to.not.be.ok(); expect(diff[0].lhs).to.be('/foo/'); expect(diff[0].rhs).to.be('/foo/i'); }); }); describe('subject.toString is not a function', function () { var lhs = { left: 'yes', right: 'no', }; var rhs = { left: { toString: true, }, right: 'no', }; it('should not throw a TypeError', function () { var diff = deep.diff(lhs, rhs); expect(diff.length).to.be(1); }); }); describe('regression test for issue #83', function () { var lhs = { date: null }; var rhs = { date: null }; it('should not detect a difference', function () { expect(deep.diff(lhs, rhs)).to.be(undefined); }); }); describe('regression test for issue #70', function () { it('should detect a difference with undefined property on lhs', function () { var diff = deep.diff({ foo: undefined }, {}); expect(diff).to.be.an(Array); expect(diff.length).to.be(1); expect(diff[0].kind).to.be('D'); expect(diff[0].path).to.be.an('array'); expect(diff[0].path).to.have.length(1); expect(diff[0].path[0]).to.be('foo'); expect(diff[0].lhs).to.be(undefined); }); it('should detect a difference with undefined property on rhs', function () { var diff = deep.diff({}, { foo: undefined }); expect(diff).to.be.an(Array); expect(diff.length).to.be(1); expect(diff[0].kind).to.be('N'); expect(diff[0].path).to.be.an('array'); expect(diff[0].path).to.have.length(1); expect(diff[0].path[0]).to.be('foo'); expect(diff[0].rhs).to.be(undefined); }); }); describe('regression test for issue #98', function () { var lhs = { foo: undefined }; var rhs = { foo: undefined }; it('should not detect a difference with two undefined property values', function () { var diff = deep.diff(lhs, rhs); expect(diff).to.be(undefined); }); }); describe('regression tests for issue #102', function () { it('should not throw a TypeError', function () { var diff = deep.diff(null, undefined); expect(diff).to.be.an(Array); expect(diff.length).to.be(1); expect(diff[0].kind).to.be('D'); expect(diff[0].lhs).to.be(null); }); it('should not throw a TypeError', function () { var diff = deep.diff(Object.create(null), { foo: undefined }); expect(diff).to.be.an(Array); expect(diff.length).to.be(1); expect(diff[0].kind).to.be('N'); expect(diff[0].rhs).to.be(undefined); }); }); describe('Order independent hash testing', function () { function sameHash(a, b) { expect(deep.orderIndepHash(a)).to.equal(deep.orderIndepHash(b)); } function differentHash(a, b) { expect(deep.orderIndepHash(a)).to.not.equal(deep.orderIndepHash(b)); } describe('Order indepdendent hash function should give different values for different objects', function () { it('should give different values for different "simple" types', function () { differentHash(1, -20); differentHash('foo', 45); differentHash('pie', 'something else'); differentHash(1.3332, 1); differentHash(1, null); differentHash('this is kind of a long string, don\'t you think?', 'the quick brown fox jumped over the lazy doge'); differentHash(true, 2); differentHash(false, 'flooog'); }); it('should give different values for string and object with string', function () { differentHash('some string', { key: 'some string' }); }); it('should give different values for number and array', function () { differentHash(1, [1]); }); it('should give different values for string and array of string', function () { differentHash('string', ['string']); }); it('should give different values for boolean and object with boolean', function () { differentHash(true, { key: true }); }); it('should give different values for different arrays', function () { differentHash([1, 2, 3], [1, 2]); differentHash([1, 4, 5, 6], ['foo', 1, true, undefined]); differentHash([1, 4, 6], [1, 4, 7]); differentHash([1, 3, 5], ['1', '3', '5']); }); it('should give different values for different objects', function () { differentHash({ key: 'value' }, { other: 'value' }); differentHash({ a: { b: 'c' } }, { a: 'b' }); }); it('should differentiate between arrays and objects', function () { differentHash([1, true, '1'], { a: 1, b: true, c: '1' }); }); }); describe('Order independent hash function should work in pathological cases', function () { it('should work in funky javascript cases', function () { differentHash(undefined, null); differentHash(0, undefined); differentHash(0, null); differentHash(0, false); differentHash(0, []); differentHash('', []); differentHash(3.22, '3.22'); differentHash(true, 'true'); differentHash(false, 0); }); it('should work on empty array and object', function () { differentHash([], {}); }); it('should work on empty object and undefined', function () { differentHash({}, undefined); }); it('should work on empty array and array with 0', function () { differentHash([], [0]); }); }); describe('Order independent hash function should be order independent', function () { it('should not care about array order', function () { sameHash([1, 2, 3], [3, 2, 1]); sameHash(['hi', true, 9.4], [true, 'hi', 9.4]); }); it('should not care about key order in an object', function () { sameHash({ foo: 'bar', foz: 'baz' }, { foz: 'baz', foo: 'bar' }); }); it('should work with complicated objects', function () { var obj1 = { foo: 'bar', faz: [ 1, 'pie', { food: 'yum' } ] }; var obj2 = { faz: [ 'pie', { food: 'yum' }, 1 ], foo: 'bar' }; sameHash(obj1, obj2); }); }); }); describe('Order indepedent array comparison should work', function () { it('can compare simple arrays in an order independent fashion', function () { var lhs = [1, 2, 3]; var rhs = [1, 3, 2]; var diff = deep.orderIndependentDiff(lhs, rhs); expect(diff).to.be(undefined); }); it('still works with repeated elements', function () { var lhs = [1, 1, 2]; var rhs = [1, 2, 1]; var diff = deep.orderIndependentDiff(lhs, rhs); expect(diff).to.be(undefined); }); it('works on complex objects', function () { var obj1 = { foo: 'bar', faz: [ 1, 'pie', { food: 'yum' } ] }; var obj2 = { faz: [ 'pie', { food: 'yum' }, 1 ], foo: 'bar' }; var diff = deep.orderIndependentDiff(obj1, obj2); expect(diff).to.be(undefined); }); it('should report some difference in non-equal arrays', function () { var lhs = [1, 2, 3]; var rhs = [2, 2, 3]; var diff = deep.orderIndependentDiff(lhs, rhs); expect(diff.length).to.be.ok(); }); }); }); }));