diff --git a/README.md b/README.md index d228c66..f4c848b 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,54 @@ const difference = diffler(before, after); console.log(difference); // { location: { from: "London", to: "Melbourne" } } ``` +## Options + +> Experimental + +```shell +npm i diffler@next +``` + +### `respectArrayOrder` defaults to `true` + +If you don't care about the order of an array you can disable like so: + +```js +const diffler = require("diffler"); + +const before = { name: "omgaz", locations: ["London", "Melbourne"] }; +const after = { name: "omgaz", locations: ["Melbourne", "London"] }; + +const difference = diffler(before, after, { respectArrayOrder: false }); +console.log(difference); // {} +``` + +However, be aware that additions and removals change the overall shape of the array and so will be seen as an entire array change AFTER the addition and removal. + +```js +const diffler = require("diffler"); + +const before = { name: "omgaz", locations: ["London", "Hong Kong", "Melbourne"] }; +const after = { name: "omgaz", locations: ["London", "Melbourne", "Hong Kong" ] }; + +const difference = diffler(before, after, { respectArrayOrder: false }); +console.log(difference); // { locations: { 1: { from: "Hong Kong", to: "Melbourne" }, 2: { from: "Melbourne", to: "Hong Kong" } +``` + +My advice is to use associative arrays if you do not care about order. + +```js +const diffler = require("diffler"); + +const before = { name: "omgaz", locations: { Melbourne: "Melbourne", London: "London" } }; +const after = { name: "omgaz", locations: { London: "London", Melbourne: "Melbourne" } }; + +const difference = diffler(before, after, { respectArrayOrder: false }); +console.log(difference); // { } +``` + +Maybe in the future, diffler will do this internally and be able to return array new array indexes for these values, it is still my current belief that arrays are ordered and should preserve order. + ## Tests [![Build Status](https://travis-ci.org/omgaz/diffler.svg?branch=master)](https://travis-ci.org/omgaz/diffler) diff --git a/dist/index.js b/dist/index.js index eaceadd..38a496a 100644 --- a/dist/index.js +++ b/dist/index.js @@ -2,4 +2,4 @@ * @license MIT https://github.com/omgaz/diffler * Author: Gary Chisholm @omgaz */ -function diffler(o,f){var n,r,t,e,i={};for(n in o){o.hasOwnProperty(n)&&"function"!=typeof o[n]&&(t=o[n],e=f[n],n in f?"object"==typeof t?(r=diffler(t,e),0 0) { if (tempDiff) { diff[key] = tempDiff; @@ -49,7 +83,7 @@ function diffler(obj1, obj2) { // Iterate over obj2 looking for any new additions for (key in obj2) { - if (obj2.hasOwnProperty(key) && typeof obj2[key] !== 'function') { + if (obj2.hasOwnProperty(key) && typeof obj2[key] !== "function") { var obj1Val = obj1[key], obj2Val = obj2[key]; diff --git a/tests/index.js b/tests/index.js index ff69419..265d8cf 100644 --- a/tests/index.js +++ b/tests/index.js @@ -1,124 +1,257 @@ -const assert = require('assert'); -const diffler = require('../src'); - -describe('getDiff', () => { - describe('simple checks', () => { - it('returns false when no changes detected', () => { - const testObjectA = { name: 'gary' }; - const testObjectB = { name: 'gary' }; +const assert = require("assert"); +const diffler = require("../src"); + +describe("getDiff", () => { + describe("simple checks", () => { + it("returns false when no changes detected", () => { + const testObjectA = { name: "gary" }; + const testObjectB = { name: "gary" }; const difference = diffler(testObjectA, testObjectB); - assert.equal(Object.keys(difference).length, 0); - assert.deepEqual(difference, {}); + assert.strictEqual(Object.keys(difference).length, 0); + assert.deepStrictEqual(difference, {}); }); - it('should detect a single property change', () => { - const testObjectA = { name: 'gary' }; - const testObjectB = { name: 'cindy' }; + it("should detect a single property change", () => { + const testObjectA = { name: "gary" }; + const testObjectB = { name: "cindy" }; const difference = diffler(testObjectA, testObjectB); - assert.equal(Object.keys(difference).length, 1); - assert.equal(Object.keys(difference.name).length, 2); + assert.strictEqual(Object.keys(difference).length, 1); + assert.strictEqual(Object.keys(difference.name).length, 2); - assert.equal(difference.name.from, 'gary'); - assert.equal(difference.name.to, 'cindy'); + assert.strictEqual(difference.name.from, "gary"); + assert.strictEqual(difference.name.to, "cindy"); }); - it('should detect no changes', () => { - const testObjectA = { name: 'gary' }; - const testObjectB = { name: 'gary' }; + it("should detect no changes", () => { + const testObjectA = { name: "gary" }; + const testObjectB = { name: "gary" }; const difference = diffler(testObjectA, testObjectB); - assert.equal(Object.keys(difference).length, 0); + assert.strictEqual(Object.keys(difference).length, 0); }); - it('should detect type changes', () => { - const testObjectA = { name: '1' }; + it("should detect type changes", () => { + const testObjectA = { name: "1" }; const testObjectB = { name: 1 }; const difference = diffler(testObjectA, testObjectB); - assert.equal(Object.keys(difference).length, 1); - assert.equal(Object.keys(difference.name).length, 2); + assert.strictEqual(Object.keys(difference).length, 1); + assert.strictEqual(Object.keys(difference.name).length, 2); + + assert.strictEqual(difference.name.from, "1"); + assert.strictEqual(difference.name.to, 1); + }); + }); + + describe("array checks", () => { + it("returns false when no changes detected", () => { + const testObjectA = { arr: ["one", "two"] }; + const testObjectB = { arr: ["one", "two"] }; + const difference = diffler(testObjectA, testObjectB); + assert.strictEqual(Object.keys(difference).length, 0); + assert.deepStrictEqual(difference, {}); + }); + + it("returns change when array order shifted", () => { + const testObjectA = { arr: ["one", "two", "three"] }; + const testObjectB = { arr: ["three", "two", "one"] }; + const difference = diffler(testObjectA, testObjectB); + assert.strictEqual(Object.keys(difference).length, 1); + assert.deepStrictEqual(difference, { + arr: { + 0: { from: "one", to: "three" }, + 2: { from: "three", to: "one" }, + }, + }); + }); + + it("returns false when array order shifted but respectArrayOrder is false", () => { + const testObjectA = { arr: ["one", "two", "three"] }; + const testObjectB = { arr: ["three", "two", "one"] }; + const difference = diffler(testObjectA, testObjectB, { + respectArrayOrder: false, + }); + assert.strictEqual(Object.keys(difference).length, 0); + assert.deepStrictEqual(difference, {}); + }); + + it("returns false when array order shifted but respectArrayOrder is false as numbers", () => { + const testObjectA = { arr: [1, 2, 3] }; + const testObjectB = { arr: [2, 3, 1] }; + const difference = diffler(testObjectA, testObjectB, { + respectArrayOrder: false, + }); + assert.strictEqual(Object.keys(difference).length, 0); + assert.deepStrictEqual(difference, {}); + }); + + it("returns false when array order shifted but respectArrayOrder is false as mixed", () => { + const testObjectA = { arr: [1, "two", 3] }; + const testObjectB = { arr: ["two", 3, 1] }; + const difference = diffler(testObjectA, testObjectB, { + respectArrayOrder: false, + }); + assert.strictEqual(Object.keys(difference).length, 0); + assert.deepStrictEqual(difference, {}); + }); + + it("throws an error if arrays contain primitives and objects", () => { + const testObjectA = { + myArray: ["a string", { foo: "bar" }, 1, { baz: "bat" }], + }; - assert.equal(difference.name.from, '1'); - assert.equal(difference.name.to, 1); + const testObjectB = { + myArray: [{ baz: "bat" }, { foo: "bar" }, 1, "a string"], + }; + assert.throws(() => diffler(testObjectA, testObjectB)); + }); + + // this behaviour seems odd if we don't care about array ordering, I'm not sure if adding one item + // should then break the entire object? + it("returns a single addition when respectArrayOrder is false and one item added", () => { + const testObjectA = { + myArray: ["a string", "item", 4], + }; + + const testObjectB = { + myArray: ["item", 4, 1, "a string"], + }; + const difference = diffler(testObjectA, testObjectB, { + respectArrayOrder: false, + }); + assert.strictEqual(Object.keys(difference).length, 1); + assert.deepStrictEqual(difference, { + myArray: { + 0: { + from: 4, + to: 1, + }, + 1: { + from: "a string", + to: 4, + }, + 2: { + from: "item", + to: "a string", + }, + 3: { + from: null, + to: "item", + }, + }, + }); + }); + + it("returns change when array item added", () => { + const testObjectA = { arr: ["one", "two"] }; + const testObjectB = { arr: ["one", "two", "three"] }; + const difference = diffler(testObjectA, testObjectB); + assert.strictEqual(Object.keys(difference).length, 1); + assert.deepStrictEqual(difference, { + arr: { 2: { from: null, to: "three" } }, + }); + }); + + it("returns change when array item removed", () => { + const testObjectA = { arr: ["one", "two", "three"] }; + const testObjectB = { arr: ["one", "two"] }; + const difference = diffler(testObjectA, testObjectB); + assert.strictEqual(Object.keys(difference).length, 1); + assert.deepStrictEqual(difference, { + arr: { 2: { from: "three", to: null } }, + }); + }); + + it("returns change and removal when array item removed from middle", () => { + const testObjectA = { arr: ["one", "two", "three"] }; + const testObjectB = { arr: ["one", "three"] }; + const difference = diffler(testObjectA, testObjectB); + assert.strictEqual(Object.keys(difference).length, 1); + assert.deepStrictEqual(difference, { + arr: { + 1: { from: "two", to: "three" }, + 2: { from: "three", to: null }, + }, + }); }); }); - describe('multiple checks', () => { - it('should detect a nested property change', () => { + describe("multiple checks", () => { + it("should detect a nested property change", () => { const testObjectA = { - name: 'gary', + name: "gary", age: 33, - weight: { unit: 'kg', value: 80 }, + weight: { unit: "kg", value: 80 }, }; const testObjectB = { - name: 'gary', + name: "gary", age: 33, - weight: { unit: 'kg', value: 79 }, + weight: { unit: "kg", value: 79 }, }; const difference = diffler(testObjectA, testObjectB); - assert.equal(Object.keys(difference).length, 1); - assert.equal(Object.keys(difference.weight.value).length, 2); + assert.strictEqual(Object.keys(difference).length, 1); + assert.strictEqual(Object.keys(difference.weight.value).length, 2); - assert.equal(difference.weight.value.from, 80); - assert.equal(difference.weight.value.to, 79); + assert.strictEqual(difference.weight.value.from, 80); + assert.strictEqual(difference.weight.value.to, 79); }); - it('should detect multiple nested property change', () => { + it("should detect multiple nested property change", () => { const testObjectA = { - name: 'gary', + name: "gary", age: 33, - weight: { unit: 'kg', value: 80 }, + weight: { unit: "kg", value: 80 }, }; const testObjectB = { - name: 'gary', + name: "gary", age: 34, - weight: { unit: 'stone', value: 12.4 }, + weight: { unit: "stone", value: 12.4 }, }; const difference = diffler(testObjectA, testObjectB); - assert.equal(Object.keys(difference).length, 2); - assert.equal(Object.keys(difference.weight).length, 2); + assert.strictEqual(Object.keys(difference).length, 2); + assert.strictEqual(Object.keys(difference.weight).length, 2); - assert.equal(difference.age.from, 33); - assert.equal(difference.age.to, 34); + assert.strictEqual(difference.age.from, 33); + assert.strictEqual(difference.age.to, 34); - assert.equal(difference.weight.value.from, 80); - assert.equal(difference.weight.value.to, 12.4); + assert.strictEqual(difference.weight.value.from, 80); + assert.strictEqual(difference.weight.value.to, 12.4); - assert.equal(difference.weight.unit.from, 'kg'); - assert.equal(difference.weight.unit.to, 'stone'); + assert.strictEqual(difference.weight.unit.from, "kg"); + assert.strictEqual(difference.weight.unit.to, "stone"); }); }); - describe('property removals', () => { - it('should detect a single property removal as null', () => { - const testObjectA = { name: 'gary' }; + describe("property removals", () => { + it("should detect a single property removal as null", () => { + const testObjectA = { name: "gary" }; const testObjectB = {}; const difference = diffler(testObjectA, testObjectB); - assert.equal(Object.keys(difference).length, 1); - assert.equal(Object.keys(difference.name).length, 2); + assert.strictEqual(Object.keys(difference).length, 1); + assert.strictEqual(Object.keys(difference.name).length, 2); - assert.equal(difference.name.from, 'gary'); - assert.equal(difference.name.to, null); + assert.strictEqual(difference.name.from, "gary"); + assert.strictEqual(difference.name.to, null); }); - it('should detect a nested property removal as null', () => { + it("should detect a nested property removal as null", () => { const testObjectA = { - name: 'gary', + name: "gary", age: 33, - weight: { unit: 'kg', value: 80 }, + weight: { unit: "kg", value: 80 }, }; - const testObjectB = { name: 'gary', age: 33 }; + const testObjectB = { name: "gary", age: 33 }; const difference = diffler(testObjectA, testObjectB); - assert.equal(Object.keys(difference).length, 1); - assert.equal(Object.keys(difference.weight).length, 2); + assert.strictEqual(Object.keys(difference).length, 1); + assert.strictEqual(Object.keys(difference.weight).length, 2); - assert.deepEqual(difference.weight.from, { unit: 'kg', value: 80 }); - assert.equal(difference.weight.to, null); + assert.deepStrictEqual(difference.weight.from, { unit: "kg", value: 80 }); + assert.strictEqual(difference.weight.to, null); }); }); });