From 9738666aca355bc07ffcb7f9ffd35ddf595cf983 Mon Sep 17 00:00:00 2001 From: Ayush <> Date: Sun, 22 Sep 2024 23:42:58 +0530 Subject: [PATCH 1/2] feat: add text difference checker --- .../utils/text-difference.utils.test.ts | 71 ++++++++++++ components/utils/text-difference.utils.ts | 23 ++++ components/utils/tools-list.ts | 6 + package.json | 2 + pages/utilities/text-difference.tsx | 108 ++++++++++++++++++ 5 files changed, 210 insertions(+) create mode 100644 components/utils/text-difference.utils.test.ts create mode 100644 components/utils/text-difference.utils.ts create mode 100644 pages/utilities/text-difference.tsx diff --git a/components/utils/text-difference.utils.test.ts b/components/utils/text-difference.utils.test.ts new file mode 100644 index 0000000..d5654b4 --- /dev/null +++ b/components/utils/text-difference.utils.test.ts @@ -0,0 +1,71 @@ +import { calculateDiff } from "./text-difference.utils"; + +describe("diff.utils", () => { + it("should detect line additions, deletions, and unchanged lines", () => { + const oldText = "Line one\nLine to be removed\nLine three"; + const newText = "Line one\nLine three\nLine four"; + const result = calculateDiff(oldText, newText); + expect(result).toEqual([ + { text: "Line one", type: "unchanged" }, + { text: "Line to be removed", type: "removed" }, + { text: "Line three", type: "removed" }, + { text: "Line three", type: "added" }, + { text: "Line four", type: "added" }, + ]); + }); + + it("should detect completely new lines", () => { + const oldText = ""; + const newText = "New line one\nNew line two"; + const result = calculateDiff(oldText, newText); + expect(result).toEqual([ + { text: "New line one", type: "added" }, + { text: "New line two", type: "added" }, + ]); + }); + + it("should detect completely removed lines", () => { + const oldText = "Old line one\nOld line two"; + const newText = ""; + const result = calculateDiff(oldText, newText); + expect(result).toEqual([ + { text: "Old line one", type: "removed" }, + { text: "Old line two", type: "removed" }, + ]); + }); + + it("should handle identical texts", () => { + const oldText = "Same line one\nSame line two"; + const newText = "Same line one\nSame line two"; + const result = calculateDiff(oldText, newText); + expect(result).toEqual([ + { text: "Same line one", type: "unchanged" }, + { text: "Same line two", type: "unchanged" }, + ]); + }); + + it("should handle mixed changes", () => { + const oldText = "Line one\nLine two\nLine three\nLine four"; + const newText = "Line one\nLine 2\nLine three\nLine four\nLine five"; + const result = calculateDiff(oldText, newText); + expect(result).toEqual([ + { text: "Line one", type: "unchanged" }, + { text: "Line two", type: "removed" }, + { text: "Line 2", type: "added" }, + { text: "Line three", type: "unchanged" }, + { text: "Line four", type: "removed" }, + { text: "Line four", type: "added" }, + { text: "Line five", type: "added" }, + ]); + }); + + it("should detect line changes as removals and additions", () => { + const oldText = "Hello world"; + const newText = "Hello brave new world"; + const result = calculateDiff(oldText, newText); + expect(result).toEqual([ + { text: "Hello world", type: "removed" }, + { text: "Hello brave new world", type: "added" }, + ]); + }); +}); diff --git a/components/utils/text-difference.utils.ts b/components/utils/text-difference.utils.ts new file mode 100644 index 0000000..3827e5e --- /dev/null +++ b/components/utils/text-difference.utils.ts @@ -0,0 +1,23 @@ +import { diffLines } from "diff"; + +export function calculateDiff(oldText: string, newText: string) { + const diff = diffLines(oldText, newText); + return diff + .map((part) => { + const lineType = part.added + ? "added" + : part.removed + ? "removed" + : "unchanged"; + const lines = part.value.split("\n").map((line) => ({ + text: line, + type: lineType, + })); + // Remove the last empty line if present + if (lines[lines.length - 1]?.text === "") { + lines.pop(); + } + return lines; + }) + .flat(); +} diff --git a/components/utils/tools-list.ts b/components/utils/tools-list.ts index ca17935..feaa2db 100644 --- a/components/utils/tools-list.ts +++ b/components/utils/tools-list.ts @@ -107,4 +107,10 @@ export const tools = [ "Resize images while maintaining aspect ratio and choose between PNG and JPEG formats with our free tool.", link: "/utilities/image-resizer", }, + { + title: "Text Difference Checker", + description: + "Compare two text files or strings and quickly identify differences between them.", + link: "/utilities/text-difference", + }, ]; diff --git a/package.json b/package.json index a7759ea..0969f46 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "clsx": "^2.1.1", "cmdk": "^1.0.0", "curlconverter": "^4.10.1", + "diff": "^7.0.0", "js-yaml": "^4.1.0", "lucide-react": "^0.414.0", "next": "14.2.4", @@ -37,6 +38,7 @@ "devDependencies": { "@testing-library/jest-dom": "^6.4.8", "@testing-library/react": "^16.0.0", + "@types/diff": "^5.2.2", "@types/jest": "^29.5.12", "@types/js-yaml": "^4.0.9", "@types/node": "^20", diff --git a/pages/utilities/text-difference.tsx b/pages/utilities/text-difference.tsx new file mode 100644 index 0000000..b9610b9 --- /dev/null +++ b/pages/utilities/text-difference.tsx @@ -0,0 +1,108 @@ +import { useEffect, useState } from "react"; +import { Textarea } from "@/components/ds/TextareaComponent"; +import PageHeader from "@/components/PageHeader"; +import { Card } from "@/components/ds/CardComponent"; +import { Label } from "@/components/ds/LabelComponent"; +import Header from "@/components/Header"; +import { CMDK } from "@/components/CMDK"; +import CallToActionGrid from "@/components/CallToActionGrid"; +import Meta from "@/components/Meta"; +import GitHubContribution from "@/components/GitHubContribution"; +import { calculateDiff } from "@/components/utils/text-difference.utils"; + +type DiffResult = { + text: string; + type: string; +}; + +export default function TextDifference() { + const [input1, setInput1] = useState(""); + const [input2, setInput2] = useState(""); + const [diffResults, setDiffResults] = useState([]); + + useEffect(() => { + if (input1.trim() === "" && input2.trim() === "") { + setDiffResults([]); + return; + } + try { + const results = calculateDiff(input1, input2); + setDiffResults(results); + } catch (errorMessage: unknown) { + setDiffResults([]); + } + }, [input1, input2]); + + return ( +
+ +
+ + +
+ +
+ +
+ +
+ +