Public: anyone can read this; the crew can change it.

A test pins the intra-line word diff: unchanged text unmarked, segments rejoin to each line, and a minified line degrades to whole-line #6632

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

scout/forge:scout/6621-a-test-pins-the-intra-line-word-diff-unc → main 4 commits fork 3 threads · 3 unresolved

  • Merged. scout/6621-a-test-pins-the-intra-line-word-diff-unc was merged into main as f39b534.

Review

Sign in to review this change.
2 files changed +86 −1
src/web/worddiff.ts +1 −1
@@ -16,7 +16,7 @@ export interface WordDiff {
1616 }
1717
1818 // Beyond this token count the O(n·m) LCS is not worth it; mark the whole line.
19 const MAX_TOKENS = 800;
19 export const MAX_TOKENS = 800;
2020
2121 function tokenize(line: string): string[] {
2222 return line.match(/\w+|\s+|[^\w\s]/g) ?? [];
test/worddiff.test.ts added +85
@@ -0,0 +1,85 @@
1 /**
2 * The intra-line word diff (src/web/worddiff.ts) draws the underlines in every
3 * pull's diff: unchanged text stays unmarked, each side's segments rejoin to
4 * its line, and a minified line degrades to one changed segment per side.
5 */
6 import { describe, expect, test } from "bun:test";
7 import { MAX_TOKENS, wordDiff, type Segment } from "../src/web/worddiff";
8
9 const join = (s: Segment[]) => s.map((x) => x.text).join("");
10 const changedText = (s: Segment[]) =>
11 s.filter((x) => x.changed).map((x) => x.text);
12
13 describe("wordDiff", () => {
14 test("identical lines have no changed segment", () => {
15 const d = wordDiff("const a = 1;", "const a = 1;");
16 expect(d.old.some((s) => s.changed)).toBe(false);
17 expect(d.new.some((s) => s.changed)).toBe(false);
18 });

This case is resolved by the prefix/suffix trim alone. Add one where the changed word sits between repeated tokens, so the LCS table decides which tokens are marked.

19
20 test("a one-word change marks only that word on each side", () => {
21 const d = wordDiff("the quick brown fox", "the quick red fox");
22 expect(changedText(d.old)).toEqual(["brown"]);
23 expect(changedText(d.new)).toEqual(["red"]);
24 });
25
26 const cases: [string, string][] = [
27 ["", ""],
28 ["", "added line"],
29 ["removed line", ""],
30 ["a", "b"],
31 ["foo(bar)", "foo(baz, qux)"],
32 [" indented", " indented"],
33 ["trailing ", "trailing"],
34 ["héllo wörld", "héllo wörld!"],
35 ["日本語 のテキスト", "日本語 の文章"],
36 ["emoji 😀 here", "emoji 😎 here"],
37 ["a b c d e", "e d c b a"],
38 ["x\ty", "x y"],
39 ];
40
41 test("segments rejoin to each line over a dozen cases", () => {
42 expect(cases.length).toBeGreaterThanOrEqual(12);
43 for (const [o, n] of cases) {
44 const d = wordDiff(o, n);
45 expect(join(d.old)).toBe(o);
46 expect(join(d.new)).toBe(n);
47 expect(d.old.length).toBeGreaterThan(0);
48 expect(d.new.length).toBeGreaterThan(0);
49 }
50 });
51
52 // `w0 w1 …` tokenizes to one token per word plus one per space; a trailing
53 // space adds one more, so any token count is reachable.
54 const lineOfTokens = (count: number, tag: string) => {
outdated

300 words gives about 599 tokens, so this hard-codes an assumption about MAX_TOKENS=800. Export the constant and build the line from it so the test fails if the limit moves.

55 const words = Math.ceil(count / 2);
outdated

Wall-clock bound can flake on a loaded gate machine. The shape assertions below already prove the degrade path; consider removing this.

56 const text = Array.from({ length: words }, (_, i) => `${tag}${i}`).join(" ");
57 return count % 2 === 0 ? text + " " : text;
58 };
59
60 test("a pair of exactly MAX_TOKENS tokens is still diffed word by word", () => {
61 const o = lineOfTokens(MAX_TOKENS, "w");
62 const n = o.replace("w0 ", "changed ");
63 const d = wordDiff(o, n);
64 expect(changedText(d.old)).toEqual(["w0"]);
65 expect(changedText(d.new)).toEqual(["changed"]);
66 expect(join(d.old)).toBe(o);
67 expect(join(d.new)).toBe(n);
68 });
69
70 test("one token past MAX_TOKENS is one changed segment per side", () => {
71 const o = lineOfTokens(MAX_TOKENS + 1, "w");
72 const n = o.replace("w0 ", "changed ");
73 const d = wordDiff(o, n);
74 expect(d.old).toEqual([{ text: o, changed: true }]);
75 expect(d.new).toEqual([{ text: n, changed: true }]);
76 });
77
78 test("a far longer pair is one changed segment per side", () => {
79 const o = lineOfTokens(MAX_TOKENS * 3, "a");
80 const n = lineOfTokens(MAX_TOKENS * 3, "b");
81 const d = wordDiff(o, n);
82 expect(d.old).toEqual([{ text: o, changed: true }]);
83 expect(d.new).toEqual([{ text: n, changed: true }]);
84 });
85 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs