Address findings from 2 review agents on the edit-history diff: - Perf: diffWords is O(n*m); cap at 2000 tokens/side and fall back to a coarse whole-block replaced diff above that, so a very large multi-edit message can't freeze the main thread. Memoize the per-row diff in DiffText. (Added a unit test for the coarse fallback.) - Perceivability (a11y/design): the added-word <ins> highlight was color-fill only, which is faint against the modal surface in the lotus themes. Add a Success.ContainerLine border + horizontal padding (so the rounded corners read as a chip) + box-decoration-break: clone for clean wrapping, so the "added" cue survives low fill contrast. - Consistency: a media/no-body edit now renders "(no text)" in diff mode too (matched the toggle-off view; was blank). - Softened the code comment's screen-reader claim (bare <ins>/<del> aren't announced by default). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
101 lines
3.2 KiB
TypeScript
101 lines
3.2 KiB
TypeScript
import { test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { diffWords, DiffSegment } from './textDiff';
|
|
|
|
// Helper: reconstruct the old / new text from segments to prove correctness.
|
|
const oldText = (segs: DiffSegment[]) =>
|
|
segs
|
|
.filter((s) => s.type !== 'added')
|
|
.map((s) => s.text)
|
|
.join('');
|
|
const newText = (segs: DiffSegment[]) =>
|
|
segs
|
|
.filter((s) => s.type !== 'removed')
|
|
.map((s) => s.text)
|
|
.join('');
|
|
|
|
test('identical text yields a single equal segment', () => {
|
|
const out = diffWords('hello world', 'hello world');
|
|
assert.deepEqual(out, [{ type: 'equal', text: 'hello world' }]);
|
|
});
|
|
|
|
test('pure insertion marks only the new words as added', () => {
|
|
const out = diffWords('hello world', 'hello there world');
|
|
assert.equal(oldText(out), 'hello world');
|
|
assert.equal(newText(out), 'hello there world');
|
|
assert.deepEqual(
|
|
out.filter((s) => s.type === 'added').map((s) => s.text.trim()),
|
|
['there'],
|
|
);
|
|
assert.equal(
|
|
out.some((s) => s.type === 'removed'),
|
|
false,
|
|
);
|
|
});
|
|
|
|
test('pure deletion marks only the dropped words as removed', () => {
|
|
const out = diffWords('hello there world', 'hello world');
|
|
assert.equal(
|
|
out.some((s) => s.type === 'added'),
|
|
false,
|
|
);
|
|
assert.deepEqual(
|
|
out.filter((s) => s.type === 'removed').map((s) => s.text.trim()),
|
|
['there'],
|
|
);
|
|
});
|
|
|
|
test('a word replacement is a removed run followed by an added run', () => {
|
|
const out = diffWords('hello world', 'hi world');
|
|
// Reconstructs both sides.
|
|
assert.equal(oldText(out), 'hello world');
|
|
assert.equal(newText(out), 'hi world');
|
|
const removed = out.filter((s) => s.type === 'removed').map((s) => s.text.trim());
|
|
const added = out.filter((s) => s.type === 'added').map((s) => s.text.trim());
|
|
assert.deepEqual(removed, ['hello']);
|
|
assert.deepEqual(added, ['hi']);
|
|
});
|
|
|
|
test('whitespace and newlines are preserved in reconstruction', () => {
|
|
const a = ' line one\nline two ';
|
|
const b = ' line one\nline three ';
|
|
const out = diffWords(a, b);
|
|
assert.equal(oldText(out), a);
|
|
assert.equal(newText(out), b);
|
|
});
|
|
|
|
test('empty <-> non-empty', () => {
|
|
assert.deepEqual(diffWords('', 'new text'), [{ type: 'added', text: 'new text' }]);
|
|
assert.deepEqual(diffWords('old text', ''), [{ type: 'removed', text: 'old text' }]);
|
|
assert.deepEqual(diffWords('', ''), []);
|
|
});
|
|
|
|
test('diffWords does not mutate its inputs', () => {
|
|
const a = 'alpha beta';
|
|
const b = 'alpha gamma';
|
|
diffWords(a, b);
|
|
assert.equal(a, 'alpha beta');
|
|
assert.equal(b, 'alpha gamma');
|
|
});
|
|
|
|
test('very large inputs fall back to a coarse whole-block diff', () => {
|
|
const big = Array.from({ length: 5000 }, (_, i) => `w${i}`).join(' ');
|
|
const out = diffWords(big, `${big} extra`);
|
|
// Coarse fallback: one removed block + one added block (no per-word LCS).
|
|
assert.deepEqual(
|
|
out.map((s) => s.type),
|
|
['removed', 'added'],
|
|
);
|
|
assert.equal(oldText(out), big);
|
|
assert.equal(newText(out), `${big} extra`);
|
|
});
|
|
|
|
test('word-level granularity (not character-level)', () => {
|
|
const out = diffWords('cat', 'cats');
|
|
// "cat" and "cats" are different tokens → full removed + added, no partial.
|
|
assert.deepEqual(
|
|
out.map((s) => s.type),
|
|
['removed', 'added'],
|
|
);
|
|
});
|