diff --git a/README.md b/README.md index 5aa47a5..a96c84f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Diff-Match-Patch Unicode [![JSR](https://jsr.io/badges/@clearlylocal/diff-match-patch-unicode)](https://jsr.io/@clearlylocal/diff-match-patch-unicode) -Modern JS/TS and Unicode-friendly version of [Neil Fraser](https://github.com/NeilFraser)’s [`diff-match-patch`](https://github.com/google/diff-match-patch). +Modern JS/TS and Unicode-friendly version of [Neil Fraser](https://github.com/NeilFraser)’s [`diff-match-patch`](https://github.com/google/diff-match-patch). Currently only supports diffing; matching and patching may be added in the future, depending on need. ## Usage diff --git a/src/Diff.test.ts b/src/Diff.test.ts new file mode 100644 index 0000000..db556f6 --- /dev/null +++ b/src/Diff.test.ts @@ -0,0 +1,55 @@ +import { assert, assertEquals } from '@std/assert' +import { Diff, DiffOperation } from './Diff.ts' + +Deno.test(Diff.name, async (t) => { + await t.step('TypeScript validates inputs', () => { + ;(() => { + new Diff(0, 'a') + new Diff(1, 'a') + new Diff(-1, 'a') + + // @ts-expect-error wrong order + new Diff('a', 1) + // @ts-expect-error 2 is not a valid op + new Diff(2, 'a') + // @ts-expect-error -2 is not a valid op + new Diff(-2, 'a') + })() + }) + + await t.step('diff.op', () => { + assertEquals(new Diff(DiffOperation.Delete, 'a').op, -1) + assertEquals(new Diff(DiffOperation.Equal, 'a').op, 0) + assertEquals(new Diff(DiffOperation.Insert, 'a').op, 1) + }) + + await t.step('other getters', () => { + const diff = new Diff(DiffOperation.Equal, 'a') + assertEquals(diff.text, 'a') + assertEquals(diff.length, 2) + }) + + await t.step('JSON.stringify', () => { + const diff = new Diff(DiffOperation.Equal, 'a') + assertEquals(JSON.stringify(diff), '[0,"a"]') + }) + + await t.step('iterate', () => { + const diff = new Diff(DiffOperation.Equal, 'a') + assertEquals([...diff], [0, 'a']) + }) + + await t.step('inspect', () => { + const diff = new Diff(DiffOperation.Equal, 'a') + assertEquals(Deno.inspect(diff, { colors: false }), 'Diff #[ 0, "a" ]') + assertEquals(Deno.inspect(diff, { colors: true }), 'Diff #[ \x1b[33m0\x1b[39m, \x1b[32m"a"\x1b[39m ]') + }) + + await t.step('clone', () => { + const diff = new Diff(DiffOperation.Equal, 'a') + // value-equal + assertEquals(diff, diff.clone()) + // not reference-equal + assert(diff !== diff.clone()) + }) +}) diff --git a/src/Differ.ts b/src/Differ.ts index 58cc27d..be4b60a 100644 --- a/src/Differ.ts +++ b/src/Differ.ts @@ -137,14 +137,14 @@ export class Differ { * ``` */ diff(before: string, after: string, options?: Partial): Diff[] { - if (before === after) { - // no need to go any further if both strings are the same - return before ? [new Diff(DiffOperation.Equal, before)] : [] - } - const opts = { ...defaultDiffOptions, ...options, maxBefore: MAX_SEGMENTS_2_3, maxAfter: MAX_SEGMENTS } const { join } = opts + if (before === after && join) { + // no need to go any further if both strings are the same (unless `join` is false) + return before ? [new Diff(DiffOperation.Equal, before)] : [] + } + const { encodedDiffs, decode } = this.#diffInternal(before, after, opts) if (!join) { diff --git a/src/_DiffMatchPatch.ts b/src/_DiffMatchPatch.ts index 92f1d5c..d074d2a 100644 --- a/src/_DiffMatchPatch.ts +++ b/src/_DiffMatchPatch.ts @@ -61,7 +61,7 @@ export class DiffMatchPatch { if (this.Diff_Timeout <= 0) { opt_deadline = Number.MAX_VALUE } else { - opt_deadline = (new Date()).getTime() + this.Diff_Timeout * 1000 + opt_deadline = Date.now() + this.Diff_Timeout * 1000 } } const deadline = opt_deadline @@ -279,7 +279,7 @@ export class DiffMatchPatch { let k2end = 0 for (let d = 0; d < max_d; d++) { // Bail out if deadline is reached. - if ((new Date()).getTime() > deadline) { + if (Date.now() > deadline) { break } diff --git a/src/utils.test.ts b/src/utils.test.ts new file mode 100644 index 0000000..27e3b20 --- /dev/null +++ b/src/utils.test.ts @@ -0,0 +1,27 @@ +import { assert, assertInstanceOf, assertThrows } from '@std/assert' +import { makeDiff } from './utils.ts' +import { Diff } from './Diff.ts' + +Deno.test(makeDiff.name, async (t) => { + await t.step('instanceof', () => { + assertInstanceOf(makeDiff([0, 'a']), Diff) + }) + + await t.step('returns `Diff` input unchanged', () => { + const diff = makeDiff([0, 'a']) + // reference-equal + assert(diff === diff) + }) + + await t.step('validates inputs at runtime', () => { + makeDiff([0, 'a']) + makeDiff([1, 'a']) + makeDiff([-1, 'a']) + + assertThrows(() => makeDiff([1, 1]), Error, 'Invalid text') + assertThrows(() => makeDiff(['a', '1']), Error, 'Invalid op') + assertThrows(() => makeDiff(['a', 1]), Error, 'Invalid text') + assertThrows(() => makeDiff([2, 'a']), Error, 'Invalid op') + assertThrows(() => makeDiff([-2, 'a']), Error, 'Invalid op') + }) +}) diff --git a/src/utils.ts b/src/utils.ts index 15268a4..3c7820b 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,4 +1,4 @@ -import { Diff, DiffOperation } from './Diff.ts' +import { Diff } from './Diff.ts' import { assert } from '@std/assert/assert' /** @@ -19,7 +19,7 @@ export function makeDiffs(arr: readonly DiffLike[]): Diff[] { export function makeDiff(d: DiffLike): Diff { if (d instanceof Diff) return d const [op, text] = d - assert(typeof text === 'string') - assert(op === DiffOperation.Delete || op === DiffOperation.Insert || op === DiffOperation.Equal) + assert(typeof text === 'string', `Invalid text: type is ${typeof text}; expected string`) + assert(op === -1 || op === 0 || op === 1, `Invalid op: ${op}; expected -1, 0, or 1`) return new Diff(op, text) }