From ad7431fbd7eccc45482d889071558a1ba89636f9 Mon Sep 17 00:00:00 2001
From: DoneDeal0 <ap.lanoe@outlook.com>
Date: Fri, 13 Jan 2023 21:11:45 +0100
Subject: [PATCH] feat: suppport duplicated values in arrays

---
 README.md              |   4 +-
 src/list-diff.ts       |  11 ++-
 test/list-diff.test.ts | 175 ++++++++++++++++++++++++++++++++++++++++-
 3 files changed, 182 insertions(+), 8 deletions(-)

diff --git a/README.md b/README.md
index 4217121..abab8ee 100644
--- a/README.md
+++ b/README.md
@@ -178,8 +178,8 @@ Compares two arrays and return a diff for each value:
 - index change: `prevIndex`, `newIndex`, `indexDiff`
 - status: `added`, `deleted`, `equal`, `moved`, `updated`
 - value
-- supports array of primitive values and objects
-- ⚠️ doesn't support duplicated values comparison yet (but will soon)
+- supports arrays of primitive values and objects
+- supports arrays with duplicated values
 
 format:
 
diff --git a/src/list-diff.ts b/src/list-diff.ts
index f8e33d9..9ca25c5 100644
--- a/src/list-diff.ts
+++ b/src/list-diff.ts
@@ -42,10 +42,15 @@ export const getListDiff = (
     return formatSingleListDiff(prevList as ListData, STATUS.DELETED);
   }
   const diff: ListDiff["diff"] = [];
+  const prevIndexMatches: number[] = [];
   nextList.forEach((nextValue, i) => {
-    const prevIndex = prevList.findIndex((prevValue) =>
-      isEqual(prevValue, nextValue)
+    const prevIndex = prevList.findIndex(
+      (prevValue, prevIdx) =>
+        isEqual(prevValue, nextValue) && !prevIndexMatches.includes(prevIdx)
     );
+    if (prevIndex > -1) {
+      prevIndexMatches.push(prevIndex);
+    }
     const indexDiff = prevIndex === -1 ? null : i - prevIndex;
     if (indexDiff === 0) {
       return diff.push({
@@ -75,7 +80,7 @@ export const getListDiff = (
   });
 
   prevList.forEach((prevValue, i) => {
-    if (!nextList.some((nextValue) => isEqual(nextValue, prevValue))) {
+    if (!prevIndexMatches.includes(i)) {
       return diff.splice(i, 0, {
         value: prevValue,
         prevIndex: i,
diff --git a/test/list-diff.test.ts b/test/list-diff.test.ts
index 16fb98b..6ce2b7d 100644
--- a/test/list-diff.test.ts
+++ b/test/list-diff.test.ts
@@ -84,7 +84,7 @@ describe("getListDiff", () => {
       ],
     });
   });
-  it("detects changed values in a string list", () => {
+  it("detects changes between string lists", () => {
     expect(
       getListDiff(
         ["mbappe", "mendes", "verratti", "ruiz"],
@@ -132,7 +132,7 @@ describe("getListDiff", () => {
       ],
     });
   });
-  it("detects changed values in a number list", () => {
+  it("detects changes between number lists", () => {
     expect(getListDiff([54, 234, 76, 0], [54, 200, 0])).toStrictEqual({
       type: "list",
       status: "updated",
@@ -175,7 +175,7 @@ describe("getListDiff", () => {
       ],
     });
   });
-  it("detects changed values in an object list", () => {
+  it("detects changes between object lists", () => {
     expect(
       getListDiff(
         [
@@ -224,4 +224,173 @@ describe("getListDiff", () => {
       ],
     });
   });
+  it("detects changes between lists containing duplicated values", () => {
+    expect(
+      getListDiff(["mbappe", "messi"], ["mbappe", "mbappe", "messi"])
+    ).toStrictEqual({
+      type: "list",
+      status: "updated",
+      diff: [
+        {
+          value: "mbappe",
+          prevIndex: 0,
+          newIndex: 0,
+          indexDiff: 0,
+          status: "equal",
+        },
+        {
+          value: "mbappe",
+          prevIndex: null,
+          newIndex: 1,
+          indexDiff: null,
+          status: "added",
+        },
+        {
+          value: "messi",
+          prevIndex: 1,
+          newIndex: 2,
+          indexDiff: 1,
+          status: "moved",
+        },
+      ],
+    });
+    expect(
+      getListDiff(
+        ["mbappe", "messi", "messi", "mbappe"],
+        ["mbappe", "messi", "messi"]
+      )
+    ).toStrictEqual({
+      type: "list",
+      status: "updated",
+      diff: [
+        {
+          value: "mbappe",
+          prevIndex: 0,
+          newIndex: 0,
+          indexDiff: 0,
+          status: "equal",
+        },
+        {
+          value: "messi",
+          prevIndex: 1,
+          newIndex: 1,
+          indexDiff: 0,
+          status: "equal",
+        },
+        {
+          value: "messi",
+          prevIndex: 2,
+          newIndex: 2,
+          indexDiff: 0,
+          status: "equal",
+        },
+        {
+          value: "mbappe",
+          prevIndex: 3,
+          newIndex: null,
+          indexDiff: null,
+          status: "deleted",
+        },
+      ],
+    });
+    expect(
+      getListDiff(
+        [
+          false,
+          true,
+          true,
+          undefined,
+          "hello",
+          { name: "joe", age: 88 },
+          false,
+          13,
+        ],
+        [
+          false,
+          false,
+          true,
+          undefined,
+          "hello",
+          { name: "joe", age: 88 },
+          false,
+          { name: "joe", age: 88 },
+        ]
+      )
+    ).toStrictEqual({
+      type: "list",
+      status: "updated",
+      diff: [
+        {
+          value: false,
+          prevIndex: 0,
+          newIndex: 0,
+          indexDiff: 0,
+          status: "equal",
+        },
+        {
+          value: false,
+          prevIndex: 6,
+          newIndex: 1,
+          indexDiff: -5,
+          status: "moved",
+        },
+        {
+          value: true,
+          prevIndex: 2,
+          newIndex: null,
+          indexDiff: null,
+          status: "deleted",
+        },
+        {
+          value: true,
+          prevIndex: 1,
+          newIndex: 2,
+          indexDiff: 1,
+          status: "moved",
+        },
+        {
+          value: undefined,
+          prevIndex: 3,
+          newIndex: 3,
+          indexDiff: 0,
+          status: "equal",
+        },
+        {
+          value: "hello",
+          prevIndex: 4,
+          newIndex: 4,
+          indexDiff: 0,
+          status: "equal",
+        },
+        {
+          value: { name: "joe", age: 88 },
+          prevIndex: 5,
+          newIndex: 5,
+          indexDiff: 0,
+          status: "equal",
+        },
+        {
+          value: 13,
+          prevIndex: 7,
+          newIndex: null,
+          indexDiff: null,
+          status: "deleted",
+        },
+        {
+          value: false,
+          prevIndex: null,
+          newIndex: 6,
+          indexDiff: null,
+          status: "added",
+        },
+        {
+          value: { name: "joe", age: 88 },
+          prevIndex: null,
+          newIndex: 7,
+          indexDiff: null,
+          status: "added",
+        },
+      ],
+    });
+  });
 });