Skip to content

Add UnifiedDiffView tests for the diff annotation surface - #931

Merged
selfcontained merged 2 commits into
mainfrom
agt_9157499a672d/job-test-enforcer-93d5f522
Aug 11, 2026
Merged

Add UnifiedDiffView tests for the diff annotation surface#931
selfcontained merged 2 commits into
mainfrom
agt_9157499a672d/job-test-enforcer-93d5f522

Conversation

@selfcontained

Copy link
Copy Markdown
Owner

Recurring Test Enforcer run. The local suite was green on first pass (23rd consecutive run), so this PR is coverage only — no product code changed.

Local validation

LaneResult
pnpm run check
pnpm run test✅ server 150 files / 2605 tests (8 skip), web 734 → 765, ext 60, scripts 4
pnpm run test:e2e✅ 178 passed / 12 skipped
pnpm run test:e2e:live✅ 11 passed in 18.5s, zero leaked e2e-* tmux sessions (14th consecutive)
pnpm run finalize:web
CI scan (gh run list --limit 50)✅ 50/50 success, no cross-branch flakes

Both entries on the flake list are recognition-only records of already-fixed infra problems; neither recurred. The server-tests-isolated.sh wrapper from #922 provisioned its own Postgres and left nothing behind, and the three orphaned dispatch-postgres-test-* containers noted last run are gone.

What this adds

apps/web/src/components/app/unified-diff-view.test.tsx — 31 tests covering two modules that had no direct coverage:

  • use-diff-widgets.tsx — builds the changeKey → ReactElement map of inline annotations (review feedback, draft comments, the inline comment form).
  • unified-diff-view.tsx — renders the diff, owns gutter-click line-selection semantics, and feeds the widget map to react-diff-view.

Between them they decide where every review annotation lands, which is exactly the kind of wiring a stubbed test would miss. So the tests mount the real tree — react-diff-view, the real hook, and the real InlineFeedbackAnnotation / InlineDraftAnnotation / InlineCommentForm — with only the HTTP seam and the animation layer mocked. Rows are located by their code text and widgets by row adjacency, so no change keys are hardcoded and a mis-keyed widget cannot pass.

Pinned behavior:

  • widget anchoring to the last changed line in a range (multi-line and single-line)
  • feedback skip rules — no start line (even with an end line set, where ln >= null would otherwise coerce to ln >= 0), and ranges covering no rendered change
  • multiple feedback items sharing an anchor grouped into one widget; distinct anchors kept separate
  • drafts composing under feedback on a shared anchor rather than replacing it
  • the three-way gating of the comment form (selection + agentId + commentOpen) and its cancel / add-draft / start-review callbacks, including comment trimming and the whitespace-only no-op
  • gutter selection: single click, shift-extend below and above the anchor, click-inside-to-clear, and the delete-line no-op
  • selected-gutter marking and the floating comment affordance's show/hide/click contract

Mutation battery: 25/25 behavior-changing mutants killed across both source files.

Follow-up filed, not fixed here

DIS-168 — opening the inline comment form hides any feedback or draft annotation anchored to the same line. The draft pass explicitly composes with what is already at a key; the comment-form pass assigns unconditionally and clobbers it. The mechanical fix is one line, but whether the form should take over the row is a UX call, so current behavior is pinned with a comment saying to invert that assertion once it is decided.

Also noted while writing these: parseDiff returns one hunk-less file even for junk input, so the view's "Unable to parse diff" fallback only fires if parseDiff throws. What an unparseable payload actually renders is an empty table — pinned as-is with a comment.

Review

One code-review persona pass (review 651): 3 items, all test hygiene, all resolved.

  • restored the scrollIntoView prototype patch via its captured property descriptor (vi.spyOn can't be used — jsdom never defines the method, so there is nothing to wrap)
  • the floating-affordance lookup now throws when more than one direct-child button exists, so a future sibling button fails loudly instead of silently returning the wrong element
  • widget adjacency kept strict on purpose, with the rationale written down: immediate succession is how react-diff-view expresses "anchored to this line", and a nearby-row scan would let a widget anchored one line off still pass

🤖 Generated with Claude Code

useDiffWidgets and UnifiedDiffView had no direct coverage. Between them
they own where every review annotation lands: the hook keys widgets by
react-diff-view change key, the view turns those keys back into rows, and
the gutter handler owns the line-selection semantics behind the comment
affordance.
The tests mount the real tree (react-diff-view, the hook, and the three
inline annotation components) with only the HTTP seam and the animation
layer mocked, so a mis-keyed widget cannot pass. They pin widget
anchoring to the last changed line in a range, the feedback grouping and
skip rules, draft composition on a shared anchor, the three-way gating
and callback contract of the inline comment form, and gutter selection
(single, shift-extend in both directions, click-to-clear, delete-line
no-op).
One current behavior is pinned rather than fixed: the comment form
replaces any feedback or draft annotation sharing its anchor instead of
composing with it. That is a product/UX call, tracked as DIS-168.
Mutation battery: 25/25 mutants killed.
Capture the original scrollIntoView property descriptor and put it back in
afterEach so the prototype patch cannot outlive the file (vi.spyOn cannot
be used — jsdom never defines the method, so there is nothing to wrap).
The floating comment affordance lookup now throws when more than one
direct-child button exists instead of silently returning the first, so a
future sibling button fails loudly rather than turning these assertions
into false negatives.
Widget adjacency is left strict on purpose, with a comment explaining
why: immediate succession is how react-diff-view expresses "anchored to
this line", which is the contract under test.
@selfcontained
selfcontained merged commit 9319fca into mainAug 11, 2026
1 check passed
@selfcontained
selfcontained deleted the agt_9157499a672d/job-test-enforcer-93d5f522 branch August 11, 2026 02:28
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@selfcontained