diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..13c87af --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,9 @@ +# Please see the documentation for all configuration options: +# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "daily" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..18e4064 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,20 @@ +name: Checks + +on: [push] + +jobs: + ci: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [12, 14, 16, 18, 20, 22, 24] + + steps: + - uses: actions/checkout@v2 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - run: npm install + - run: npm run ci diff --git a/lib/arrayChanges.js b/lib/arrayChanges.js index 6174d1c..cd51900 100644 --- a/lib/arrayChanges.js +++ b/lib/arrayChanges.js @@ -59,6 +59,13 @@ module.exports = function arrayChanges(actual, expected, equal, similar, options return i; } + + function findMoveTargetWithId(id) { + return mutatedArray.find(function (diffItem) { + return diffItem.type === 'moveTarget' && diffItem.id === id; + }); + } + var removes = itemsDiff.filter(function (diffItem) { return diffItem.type === 'remove'; }); @@ -76,15 +83,13 @@ module.exports = function arrayChanges(actual, expected, equal, similar, options return diffItem.type === 'move'; }); - + var ids = 0; moves.forEach(function (diffItem) { var moveFromIndex = offsetIndex(diffItem.from + 1) - 1; var removed = mutatedArray.slice(moveFromIndex, diffItem.howMany + moveFromIndex); removed.forEach(function (v, index) { v.type = 'moveSource'; - v.expectedIndex = offsetIndex(diffItem.to + index); - v.expected = expected[v.expectedIndex]; - v.equal = equal(v.value, v.expected); + v.id = ids++; }); var added = removed.map(function (v, index) { return extend({}, v, { last: false, type: 'moveTarget' }); @@ -93,7 +98,6 @@ module.exports = function arrayChanges(actual, expected, equal, similar, options Array.prototype.splice.apply(mutatedArray, [insertIndex, 0].concat(added)); }); - var inserts = itemsDiff.filter(function (diffItem) { return diffItem.type === 'insert'; }); @@ -114,7 +118,11 @@ module.exports = function arrayChanges(actual, expected, equal, similar, options var offset = 0; mutatedArray.forEach(function (diffItem, index) { var type = diffItem.type; - if (type === 'remove' || type === 'moveSource') { + if (type === 'moveTarget') { + diffItem.expected = expected[offset + index]; + diffItem.expectedIndex = offset + index; + diffItem.equal = equal(diffItem.value, diffItem.expected); + } else if (type === 'remove' || type === 'moveSource') { offset -= 1; } else if (type === 'similar') { diffItem.expected = expected[offset + index]; @@ -122,6 +130,15 @@ module.exports = function arrayChanges(actual, expected, equal, similar, options } }); + mutatedArray.forEach(function (diffItem) { + if (diffItem.type === 'moveSource') { + var moveTarget = findMoveTargetWithId(diffItem.id); + diffItem.expected = moveTarget.expected; + diffItem.expectedIndex = moveTarget.expectedIndex; + diffItem.equal = moveTarget.equal; + } + }); + var conflicts = mutatedArray.reduce(function (conflicts, item) { return item.type === 'similar' || item.type === 'moveSource' || item.type === 'moveTarget' ? conflicts : conflicts + 1; }, 0); diff --git a/package.json b/package.json index 7d97860..ac2f04f 100644 --- a/package.json +++ b/package.json @@ -5,8 +5,8 @@ "main": "./lib/arrayChanges.js", "scripts": { "lint": "eslint .", - "test": "mocha && npm run lint", - "travis": "npm test && npm run coverage && ( array-changes.js" }, diff --git a/test/arrayChanges.spec.js b/test/arrayChanges.spec.js index 08f9aef..7f4dd39 100644 --- a/test/arrayChanges.spec.js +++ b/test/arrayChanges.spec.js @@ -109,11 +109,11 @@ describe('array-changes', function () { expect(arrayChanges([1, 2, 3, 0], [0, 1, 2, 3], function (a, b) { return a === b; }), 'to equal', [ - { type: 'moveTarget', value: 0, actualIndex: 3, expected: 0, expectedIndex: 0, equal: true, last: false }, + { type: 'moveTarget', value: 0, actualIndex: 3, expected: 0, expectedIndex: 0, id: 0, equal: true, last: false }, { type: 'equal', value: 1, actualIndex: 0, expected: 1, expectedIndex: 1 }, { type: 'equal', value: 2, actualIndex: 1, expected: 2, expectedIndex: 2 }, { type: 'equal', value: 3, actualIndex: 2, expected: 3, expectedIndex: 3 }, - { type: 'moveSource', value: 0, actualIndex: 3, expected: 0, expectedIndex: 0, equal: true, last: true } + { type: 'moveSource', value: 0, actualIndex: 3, expected: 0, expectedIndex: 0, id: 0, equal: true, last: true } ]); }); @@ -122,9 +122,9 @@ describe('array-changes', function () { return a === b; }), 'to equal', [ { type: 'equal', value: 0, actualIndex: 0, expected: 0, expectedIndex: 0 }, - { type: 'moveTarget', value: 2, actualIndex: 2, expected: 2, expectedIndex: 1, equal: true, last: false }, + { type: 'moveTarget', value: 2, actualIndex: 2, expected: 2, expectedIndex: 1, id: 0, equal: true, last: false }, { type: 'equal', value: 1, actualIndex: 1, expected: 1, expectedIndex: 2 }, - { type: 'moveSource', value: 2, actualIndex: 2, expected: 2, expectedIndex: 1, equal: true }, + { type: 'moveSource', value: 2, actualIndex: 2, expected: 2, expectedIndex: 1, id: 0, equal: true }, { type: 'equal', value: 3, actualIndex: 3, expected: 3, expectedIndex: 3, last: true } ]); }); @@ -451,6 +451,7 @@ describe('array-changes', function () { expected: { kind: 1, type: 'tag', name: 'p', children: [{ data: 'Hello world 2025', type: 'text' }], attribs: {} }, actualIndex: 1, expectedIndex: 0, + id: 0, equal: false, last: false }, @@ -467,9 +468,46 @@ describe('array-changes', function () { expected: { kind: 1, type: 'tag', name: 'p', children: [{ data: 'Hello world 2025', type: 'text' }], attribs: {} }, actualIndex: 1, expectedIndex: 0, + id: 0, equal: false, last: true } ]); }); + + it("handles moves where no items are similar", function () { + var a = ['a', 'b', 'c']; + var b = ['c', 'b', 'a']; + + expect(arrayChanges(a, b, function (a, b) { + return expect.equal(a, b); + }, function () { + return false; + }), 'to equal', [ + { type: 'moveTarget', value: 'c', actualIndex: 2, last: false, expected: 'c', expectedIndex: 0, id: 1, equal: true }, + { type: 'moveTarget', value: 'b', actualIndex: 1, last: false, expected: 'b', expectedIndex: 1, id: 0, equal: true }, + { type: 'equal', value: 'a', actualIndex: 0, expected: 'a', expectedIndex: 2 }, + { type: 'moveSource', value: 'b', actualIndex: 1, expected: 'b', expectedIndex: 1, id: 0, equal: true }, + { type: 'moveSource', value: 'c', actualIndex: 2, expected: 'c', expectedIndex: 0, id: 1, equal: true, last: true } + ]); + }); + + it("handles moves with a mix of equal and similar items", function () { + var a = ['aaa', 'bbb', 'ccc', 'dddd']; + var b = ['ddd', 'ccc', 'bbb', 'aaa']; + + expect(arrayChanges(a, b, function (a, b) { + return expect.equal(a, b); + }, function (a, b) { + return expect.equal(a.slice(0, 3), b.slice(0, 3)); + }), 'to equal', [ + { type: 'moveTarget', value: 'dddd', actualIndex: 3, id: 2, last: false, expected: 'ddd', expectedIndex: 0, equal: false }, + { type: 'moveTarget', value: 'ccc', actualIndex: 2, id: 1, last: false, expected: 'ccc', expectedIndex: 1, equal: true }, + { type: 'moveTarget', value: 'bbb', actualIndex: 1, id: 0, last: false, expected: 'bbb', expectedIndex: 2, equal: true }, + { type: 'equal', value: 'aaa', actualIndex: 0, expected: 'aaa', expectedIndex: 3 }, + { type: 'moveSource', value: 'bbb', actualIndex: 1, id: 0, expected: 'bbb', expectedIndex: 2, equal: true }, + { type: 'moveSource', value: 'ccc', actualIndex: 2, id: 1, expected: 'ccc', expectedIndex: 1, equal: true }, + { type: 'moveSource', value: 'dddd', actualIndex: 3, id: 2, expected: 'ddd', expectedIndex: 0, equal: false, last: true } + ]); + }); }); diff --git a/test/arraydiff.spec.js b/test/arraydiff.spec.js index c0332fd..71e093b 100644 --- a/test/arraydiff.spec.js +++ b/test/arraydiff.spec.js @@ -27,7 +27,6 @@ describe('arraydiff', function () { var out = before.slice(); for (var i = 0; i < diff.length; i++) { var item = diff[i]; - // console.log 'applying:', out, item if (item.type === 'insert') { insert(out, item.index, item.values); } else if (item.type === 'remove') {