From d7a9578b937c430111ae81c3a78aa0b441bad4f5 Mon Sep 17 00:00:00 2001 From: Sushanth012 Date: Tue, 26 May 2026 23:19:48 -0700 Subject: [PATCH] fix: handle non-line diff modes and add tests for +/- notation --- src/core/operations/Diff.mjs | 52 ++++-- tests/operations/tests/Diff.mjs | 313 ++++++++++++++++++++++++++++++++ 2 files changed, 348 insertions(+), 17 deletions(-) create mode 100644 tests/operations/tests/Diff.mjs diff --git a/src/core/operations/Diff.mjs b/src/core/operations/Diff.mjs index 8637e417..dd05f45b 100644 --- a/src/core/operations/Diff.mjs +++ b/src/core/operations/Diff.mjs @@ -60,6 +60,7 @@ class Diff extends Operation { }, { "name": "Show +/- notation", + "hint": "Works only in Line diff mode.", "type": "boolean", "value": false } @@ -92,6 +93,34 @@ class Diff extends Operation { throw new OperationError("Incorrect number of samples, perhaps you need to modify the sample delimiter or add more samples?"); } + // +/- notation only works properly with Line diff mode + // For other modes, it falls back to HTML format and this option is ignored (see hint). + const useNotation = showNotation && diffBy === "Line"; + + if (useNotation) { + const patch = jsdiff.createTwoFilesPatch("original", "modified", samples[0], samples[1], "", "", { + context: Infinity, + ignoreWhitespace: ignoreWhitespace, + }); + + const lines = patch.split("\n"); + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + if (line.startsWith("@@")) { + if (showSubtraction) output += line + "\n"; + } else if (line.startsWith("+") && !line.startsWith("+++")) { + if (showAdded) output += line + "\n"; + } else if (line.startsWith("-") && !line.startsWith("---")) { + if (showRemoved) output += line + "\n"; + } else if (line.startsWith("===") || line.startsWith("---") || line.startsWith("+++") || line.startsWith("\\")) { + continue; + } else if (line.length > 0 && !showSubtraction) { + output += line.substring(1) + "\n"; + } + } + return output.slice(0, -1); + } + switch (diffBy) { case "Character": diff = jsdiff.diffChars(samples[0], samples[1]); @@ -124,23 +153,12 @@ class Diff extends Operation { } for (let i = 0; i < diff.length; i++) { - if (showNotation) { - const escapedValue = Utils.escapeHtml(diff[i].value); - if (diff[i].added) { - if (showAdded) output += "+ " + escapedValue; - } else if (diff[i].removed) { - if (showRemoved) output += "- " + escapedValue; - } else if (!showSubtraction) { - output += " " + escapedValue; - } - } else { - if (diff[i].added) { - if (showAdded) output += "" + Utils.escapeHtml(diff[i].value) + ""; - } else if (diff[i].removed) { - if (showRemoved) output += "" + Utils.escapeHtml(diff[i].value) + ""; - } else if (!showSubtraction) { - output += Utils.escapeHtml(diff[i].value); - } + if (diff[i].added) { + if (showAdded) output += "" + Utils.escapeHtml(diff[i].value) + ""; + } else if (diff[i].removed) { + if (showRemoved) output += "" + Utils.escapeHtml(diff[i].value) + ""; + } else if (!showSubtraction) { + output += Utils.escapeHtml(diff[i].value); } } diff --git a/tests/operations/tests/Diff.mjs b/tests/operations/tests/Diff.mjs new file mode 100644 index 00000000..e2d08875 --- /dev/null +++ b/tests/operations/tests/Diff.mjs @@ -0,0 +1,313 @@ +/** + * @author mikecat + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + "name": "Diff: Show +/- notation - true (Line mode)", + "input": "line1\nline2\nline3\n\nline1\nline2modified\nline3", + "expectedOutput": "line1\n-line2\n+line2modified\nline3", + "recipeConfig": [ + { + "op": "Diff", + "args": [ + "\\n\\n", + "Line", + true, + true, + false, + false, + true + ], + }, + ], + }, + { + "name": "Diff: Show +/- notation - false (Line mode)", + "input": "line1\nline2\nline3\n\nline1\nline2modified\nline3", + "expectedOutput": "line1\nline2\nline2modified\nline3", + "recipeConfig": [ + { + "op": "Diff", + "args": [ + "\\n\\n", + "Line", + true, + true, + false, + false, + false + ], + }, + ], + }, + { + "name": "Diff: Show +/- notation - with showAdded false (Line mode)", + "input": "line1\nline2\nline3\n\nline1\nline2modified\nline3", + "expectedOutput": "line1\n-line2\nline3", + "recipeConfig": [ + { + "op": "Diff", + "args": [ + "\\n\\n", + "Line", + false, + true, + false, + false, + true + ], + }, + ], + }, + { + "name": "Diff: Show +/- notation - with showRemoved false (Line mode)", + "input": "line1\nline2\nline3\n\nline1\nline2modified\nline3", + "expectedOutput": "line1\n+line2modified\nline3", + "recipeConfig": [ + { + "op": "Diff", + "args": [ + "\\n\\n", + "Line", + true, + false, + false, + false, + true + ], + }, + ], + }, + { + "name": "Diff: Show +/- notation - with showSubtraction true (Line mode)", + "input": "line1\nline2\nline3\n\nline1\nline2modified\nline3", + "expectedOutput": "@@ -1,3 +1,3 @@\n-line2\n+line2modified", + "recipeConfig": [ + { + "op": "Diff", + "args": [ + "\\n\\n", + "Line", + true, + true, + true, + false, + true + ], + }, + ], + }, + { + "name": "Diff: Show +/- notation ignored for Character mode", + "input": "abc\ndef", + "expectedOutput": "abcdef", + "recipeConfig": [ + { + "op": "Diff", + "args": [ + "\\n", + "Character", + true, + true, + false, + false, + true + ], + }, + ], + }, + { + "name": "Diff: Show +/- notation ignored for Word mode", + "input": "hello world\nhello cruel world", + "expectedOutput": "hello cruel world", + "recipeConfig": [ + { + "op": "Diff", + "args": [ + "\\n", + "Word", + true, + true, + false, + false, + true + ], + }, + ], + }, + { + "name": "Diff: Show +/- notation ignored for Sentence mode", + "input": "Hello world.\nHello there.", + "expectedOutput": "Hello world.Hello there.", + "recipeConfig": [ + { + "op": "Diff", + "args": [ + "\\n", + "Sentence", + true, + true, + false, + false, + true + ], + }, + ], + }, + { + "name": "Diff: Character mode basic", + "input": "abc\nabc", + "expectedOutput": "abc", + "recipeConfig": [ + { + "op": "Diff", + "args": [ + "\\n", + "Character", + true, + true, + false, + false, + false + ], + }, + ], + }, + { + "name": "Diff: Character mode with changes", + "input": "abc\nabcdef", + "expectedOutput": "abcdef", + "recipeConfig": [ + { + "op": "Diff", + "args": [ + "\\n", + "Character", + true, + true, + false, + false, + false + ], + }, + ], + }, + { + "name": "Diff: Word mode basic", + "input": "hello world\nhello world", + "expectedOutput": "hello world", + "recipeConfig": [ + { + "op": "Diff", + "args": [ + "\\n", + "Word", + true, + true, + false, + false, + false + ], + }, + ], + }, + { + "name": "Diff: Word mode with changes", + "input": "hello world\nhello cruel world", + "expectedOutput": "hello cruel world", + "recipeConfig": [ + { + "op": "Diff", + "args": [ + "\\n", + "Word", + true, + true, + false, + false, + false + ], + }, + ], + }, + { + "name": "Diff: Line mode basic", + "input": "line1\nline2\nline3\n\nline1\nline2\nline3", + "expectedOutput": "line1\nline2\nline3", + "recipeConfig": [ + { + "op": "Diff", + "args": [ + "\\n\\n", + "Line", + true, + true, + false, + false, + false + ], + }, + ], + }, + { + "name": "Diff: Line mode with changes", + "input": "line1\nline2\nline3\n\nline1\nline4\nline3", + "expectedOutput": "line1\nline2\nline4\nline3", + "recipeConfig": [ + { + "op": "Diff", + "args": [ + "\\n\\n", + "Line", + true, + true, + false, + false, + false + ], + }, + ], + }, + { + "name": "Diff: Sentence mode basic", + "input": "Hello world.\nHello world.", + "expectedOutput": "Hello world.", + "recipeConfig": [ + { + "op": "Diff", + "args": [ + "\\n", + "Sentence", + true, + true, + false, + false, + false + ], + }, + ], + }, + { + "name": "Diff: Sentence mode with changes", + "input": "Hello world.\nHello there.", + "expectedOutput": "Hello world.Hello there.", + "recipeConfig": [ + { + "op": "Diff", + "args": [ + "\\n", + "Sentence", + true, + true, + false, + false, + false + ], + }, + ], + }, +]);