diff --git a/package.json b/package.json index b1c9842..71df66c 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "striff", "description": "Real simple string diffing.", "author": "Alex MacArthur (https://macarthur.me)", - "version": "1.0.0", + "version": "1.0.1", "main": "dist/index.umd.js", "module": "dist/index.es.js", "types": "dist/index.d.ts", diff --git a/src/index.test.ts b/src/index.test.ts index 5a576dd..47f568a 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -19,7 +19,7 @@ describe("characters are added", () => { const { added, removed } = striff("hi pal", "hi, pal"); expect(removed).toHaveLength(0); - expect(added).toEqual([{ value: ",", index: 2}]); + expect(added).toEqual([{ value: ",", index: 2 }]); }); }); @@ -53,6 +53,13 @@ describe("characters are removed", () => { }, ]); }); + + it("Correctly diffs when the same character is used multiple times.", () => { + const { added, removed } = striff("wow", "ow"); + + expect(added).toHaveLength(0); + expect(removed).toEqual([{ value: "w", index: 0 }]); + }); }); describe("characters have changed", () => { diff --git a/src/index.ts b/src/index.ts index a341368..7c136e9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,36 +1,44 @@ import { DiffResult, Character } from "./types"; import deleteRef from "./utils/deleteRef"; +import fromCharacters from "./utils/fromCharacters"; import getAllParts from "./utils/getAllParts"; import toCharacters from "./utils/toCharacters"; const getDiff = (partsArr: Character[][], arr1: Character[], arr2: Character[]) => { - let str2 = arr2.map((c: Character) => c.value).join(""); + let str2 = fromCharacters(arr2); let matched = new Map(); let matchedIndicies: {[key: string]: number} = {}; partsArr.forEach((part: Character[]) => { - let partString = part.map((c) => c.value).join(""); + let partString = fromCharacters(part); let pattern = new RegExp(partString); // If this little thing matches ANY part of the string, it's in. let result = pattern.exec(str2); - - // We found a match, so let's spread it into our 'matched' store. - let pastMatch = matchedIndicies[result?.index || ""]; + let resultIndex = result?.index; + let pastMatchLength = matchedIndicies[resultIndex ?? ""] || 0; // Give matched strings of longer length over all others. - if (result && (!pastMatch || partString.length > pastMatch)) { - matchedIndicies[result.index] = partString.length; + if (result && (partString.length > pastMatchLength)) { + matchedIndicies[resultIndex as number] = partString.length; // Since this is matched, I can safely update the second // string with symbol references. - let partIndex = 0; - for (let i = result.index; i < part.length + result.index; i++) { - arr2[i].ref = part[partIndex].ref; - partIndex++; + part.forEach((partItem, index) => { + arr2[(resultIndex as number) + index].ref = partItem.ref; + }) + + // At this point, it's possible that other parts + // already matched the string and had their items set to the + // `matched` store. We need to find those indicies and unset them. + for (let i = result.index; i < (result.index + pastMatchLength); i++) { + let refToReset = arr1[i]?.ref; + matched.delete(refToReset); } // Throw each of the matched characters into storage. + // These will always be from the FIRST string, so each + // of them will already have a filled `ref`. part.forEach((char) => matched.set(char.ref, char.value)); } }); diff --git a/src/utils/fromCharacters.test.ts b/src/utils/fromCharacters.test.ts new file mode 100644 index 0000000..931ebfc --- /dev/null +++ b/src/utils/fromCharacters.test.ts @@ -0,0 +1,14 @@ +import { Character } from "../types"; +import fromCharacters from "./fromCharacters"; + +it("converts characters to a string", () => { + let characters: Character[] = [ + { value: "b", index: 0, ref: null }, + { value: "y", index: 1, ref: null }, + { value: "e", index: 2, ref: null }, + ]; + + let result = fromCharacters(characters); + + expect(result).toEqual("bye"); +}); diff --git a/src/utils/fromCharacters.ts b/src/utils/fromCharacters.ts new file mode 100644 index 0000000..b309352 --- /dev/null +++ b/src/utils/fromCharacters.ts @@ -0,0 +1,7 @@ +import { Character } from "../types"; + +let fromCharacters = (characters: Character[]): string => { + return characters.map((c: Character) => c.value).join(""); +}; + +export default fromCharacters; diff --git a/src/utils/toCharacters.ts b/src/utils/toCharacters.ts index 3c9d78a..4d3a110 100644 --- a/src/utils/toCharacters.ts +++ b/src/utils/toCharacters.ts @@ -1,6 +1,6 @@ import { Character } from "../types"; -const toCharacters = (str: string, setRef = false): Character[] => { +let toCharacters = (str: string, setRef = false): Character[] => { return str.split("").map((char: string, index: number) => { return { value: char, index, ref: setRef ? Symbol(char) : null }; });