diff --git a/src/lib/list-diff/index.ts b/src/lib/list-diff/index.ts index 85b2453..065fde3 100644 --- a/src/lib/list-diff/index.ts +++ b/src/lib/list-diff/index.ts @@ -83,26 +83,26 @@ export const getListDiff = ( return formatSingleListDiff(prevList as T[], LIST_STATUS.DELETED, options); } const diff: ListDiff["diff"] = []; - const prevIndexMatches: number[] = []; + const prevIndexMatches = new Set(); + nextList.forEach((nextValue, i) => { const prevIndex = prevList.findIndex((prevValue, prevIdx) => { + if (prevIndexMatches.has(prevIdx)) { + return false; + } if (isReferencedObject(prevValue, options.referenceProperty)) { if (isObject(nextValue)) { - return ( - isEqual( - prevValue[options.referenceProperty as string], - nextValue[options.referenceProperty as string], - ) && !prevIndexMatches.includes(prevIdx) + return isEqual( + prevValue[options.referenceProperty as string], + nextValue[options.referenceProperty as string], ); } return false; } - return ( - isEqual(prevValue, nextValue) && !prevIndexMatches.includes(prevIdx) - ); + return isEqual(prevValue, nextValue); }); if (prevIndex > -1) { - prevIndexMatches.push(prevIndex); + prevIndexMatches.add(prevIndex); } const indexDiff = prevIndex === -1 ? null : i - prevIndex; if (indexDiff === 0 || options.ignoreArrayOrder) { @@ -141,8 +141,8 @@ export const getListDiff = ( }); prevList.forEach((prevValue, i) => { - if (!prevIndexMatches.includes(i)) { - return diff.splice(i, 0, { + if (!prevIndexMatches.has(i)) { + return diff.push({ value: prevValue, prevIndex: i, newIndex: null, diff --git a/src/lib/list-diff/list-diff.test.ts b/src/lib/list-diff/list-diff.test.ts index 2089b0d..7481aa4 100644 --- a/src/lib/list-diff/list-diff.test.ts +++ b/src/lib/list-diff/list-diff.test.ts @@ -102,20 +102,6 @@ describe("getListDiff", () => { indexDiff: 0, status: "equal", }, - { - value: "mendes", - prevIndex: 1, - newIndex: null, - indexDiff: null, - status: "deleted", - }, - { - value: "verratti", - prevIndex: 2, - newIndex: null, - indexDiff: null, - status: "deleted", - }, { value: "messi", prevIndex: null, @@ -130,6 +116,20 @@ describe("getListDiff", () => { indexDiff: -1, status: "moved", }, + { + value: "mendes", + prevIndex: 1, + newIndex: null, + indexDiff: null, + status: "deleted", + }, + { + value: "verratti", + prevIndex: 2, + newIndex: null, + indexDiff: null, + status: "deleted", + }, ], }); }); @@ -145,20 +145,6 @@ describe("getListDiff", () => { indexDiff: 0, status: "equal", }, - { - value: 234, - prevIndex: 1, - newIndex: null, - indexDiff: null, - status: "deleted", - }, - { - value: 76, - prevIndex: 2, - newIndex: null, - indexDiff: null, - status: "deleted", - }, { value: 200, prevIndex: null, @@ -173,6 +159,20 @@ describe("getListDiff", () => { indexDiff: -1, status: "moved", }, + { + value: 234, + prevIndex: 1, + newIndex: null, + indexDiff: null, + status: "deleted", + }, + { + value: 76, + prevIndex: 2, + newIndex: null, + indexDiff: null, + status: "deleted", + }, ], }); }); @@ -194,13 +194,6 @@ describe("getListDiff", () => { type: "list", status: "updated", diff: [ - { - value: { name: "joe", age: 87 }, - prevIndex: 0, - newIndex: null, - indexDiff: null, - status: "deleted", - }, { value: { name: "paul", age: 32 }, prevIndex: 2, @@ -222,6 +215,13 @@ describe("getListDiff", () => { indexDiff: 1, status: "moved", }, + { + value: { name: "joe", age: 87 }, + prevIndex: 0, + newIndex: null, + indexDiff: null, + status: "deleted", + }, ], }); }); @@ -335,13 +335,7 @@ describe("getListDiff", () => { indexDiff: -5, status: "moved", }, - { - value: true, - prevIndex: 2, - newIndex: null, - indexDiff: null, - status: "deleted", - }, + { value: true, prevIndex: 1, @@ -370,13 +364,6 @@ describe("getListDiff", () => { indexDiff: 0, status: "equal", }, - { - value: 13, - prevIndex: 7, - newIndex: null, - indexDiff: null, - status: "deleted", - }, { value: false, prevIndex: null, @@ -391,6 +378,20 @@ describe("getListDiff", () => { indexDiff: null, status: "added", }, + { + value: true, + prevIndex: 2, + newIndex: null, + indexDiff: null, + status: "deleted", + }, + { + value: 13, + prevIndex: 7, + newIndex: null, + indexDiff: null, + status: "deleted", + }, ], }); }); @@ -423,20 +424,6 @@ describe("getListDiff", () => { type: "list", status: "updated", diff: [ - { - value: true, - prevIndex: 2, - newIndex: null, - indexDiff: null, - status: "deleted", - }, - { - value: 13, - prevIndex: 7, - newIndex: null, - indexDiff: null, - status: "deleted", - }, { value: false, prevIndex: null, @@ -451,6 +438,20 @@ describe("getListDiff", () => { indexDiff: null, status: "added", }, + { + value: true, + prevIndex: 2, + newIndex: null, + indexDiff: null, + status: "deleted", + }, + { + value: 13, + prevIndex: 7, + newIndex: null, + indexDiff: null, + status: "deleted", + }, ], }); }); @@ -536,13 +537,6 @@ describe("getListDiff", () => { type: "list", status: "updated", diff: [ - { - value: "hello", - prevIndex: 0, - newIndex: null, - indexDiff: null, - status: "deleted", - }, { value: { id: 8, age: 77 }, prevIndex: 4, @@ -572,18 +566,25 @@ describe("getListDiff", () => { status: "equal", }, { - value: { id: 55, character: { strength: 66 } }, - prevIndex: 5, + value: { id: 99, character: { strength: 69 } }, + prevIndex: null, + newIndex: 4, + indexDiff: null, + status: "added", + }, + { + value: "hello", + prevIndex: 0, newIndex: null, indexDiff: null, status: "deleted", }, { - value: { id: 99, character: { strength: 69 } }, - prevIndex: null, - newIndex: 4, + value: { id: 55, character: { strength: 66 } }, + prevIndex: 5, + newIndex: null, indexDiff: null, - status: "added", + status: "deleted", }, ], }); @@ -646,13 +647,6 @@ describe("getListDiff", () => { type: "list", status: "updated", diff: [ - { - value: "hello", - prevIndex: 0, - newIndex: null, - indexDiff: null, - status: "deleted", - }, { value: { id: 8, age: 77 }, prevIndex: 4, @@ -682,18 +676,25 @@ describe("getListDiff", () => { status: "equal", }, { - value: { id: 55, character: { strength: 66 } }, - prevIndex: 5, + value: { id: 99, character: { strength: 69 } }, + prevIndex: null, + newIndex: 4, + indexDiff: null, + status: "added", + }, + { + value: "hello", + prevIndex: 0, newIndex: null, indexDiff: null, status: "deleted", }, { - value: { id: 99, character: { strength: 69 } }, - prevIndex: null, - newIndex: 4, + value: { id: 55, character: { strength: 66 } }, + prevIndex: 5, + newIndex: null, indexDiff: null, - status: "added", + status: "deleted", }, ], }); @@ -790,3 +791,35 @@ describe("getListDiff", () => { }); }); }); + +describe("Performance", () => { + it("should correctly stream diff for 10.000 entries", () => { + const generateLargeList = (size: number, idPrefix: string) => { + return Array.from({ length: size }, (_, i) => ({ + id: `${idPrefix}-${i}`, + value: i, + })); + }; + const prevList = generateLargeList(10_000, "prev"); + const nextList = [ + ...generateLargeList(5000, "prev"), + ...generateLargeList(5000, "next"), + ]; + + const receivedChunks = getListDiff(prevList, nextList).diff; + + const deletions = receivedChunks.filter( + (diff) => diff.status === LIST_STATUS.DELETED, + ); + const additions = receivedChunks.filter( + (diff) => diff.status === LIST_STATUS.ADDED, + ); + const updates = receivedChunks.filter( + (diff) => diff.status === LIST_STATUS.EQUAL, + ); + expect(receivedChunks.length).toBe(15_000); // 5000 deletions + 5000 equal + 5000 additions + expect(deletions.length).toBe(5000); + expect(additions.length).toBe(5000); + expect(updates.length).toBe(5000); + }); +});