Skip to content

test: re-render identity preservation tests for ArrayView + React#5619

Open
Karavil wants to merge 6 commits intorocicorp:mainfrom
Karavil:test/rerender-identity-regression-tests
Open

test: re-render identity preservation tests for ArrayView + React#5619
Karavil wants to merge 6 commits intorocicorp:mainfrom
Karavil:test/rerender-identity-regression-tests

Conversation

@Karavil
Copy link
Contributor

@Karavil Karavil commented Feb 26, 2026

Summary

Adds tests verifying that the immutable applyChange pipeline preserves object identity for unchanged nodes (enabling React.memo) while correctly bubbling new references up for changed descendants.

6 of these tests fail on main. This is intentional. They document a regression introduced by expandNode in the change buffering PR. PR #5605 fixes all failures by removing expandNode. Merge both PRs together.

Test results on main

🔴 Failing: identity broken by expandNode eager materialization

expandNode recursively calls Array.from(skipYields(thunk()), expandNode) on every relationship, creating new objects for every node in the tree on every push. This destroys reference identity for all relationship data, meaning React.memo can never skip re-renders for components receiving rows with relationships.

Test Failure
flat edit: sibling identity expandNode allocates new objects for all nodes, not just the edited one
child edit bubbles to parent Eager expansion breaks the identity chain: parent, children array, and sibling child all get new refs when they shouldn't
child add bubbles to parent Same: expansion allocates new relationship arrays
child remove bubbles to parent Same
grandchild edit bubbles 3 levels Same: entire tree gets new objects regardless of what changed
singular relationship: child edit Same: .one() relationship child gets new ref from expansion

🟢 Passing: no relationship expansion involved

Test What it verifies
flat add: existing rows keep reference No relationships, so expandNode doesn't interfere
flat remove: remaining rows keep reference Same
multiple pushes before flush Flat list only
listener fires once per flush Behavioral, not identity
.data reflects changes immediately Behavioral

🟢 Passing: React layer (ViewWrapper, not ArrayView)

These use mock views, so they test the ViewWrapper/useSyncExternalStore contract independently of the IVM pipeline.

Test What it verifies
getSnapshot returns same ref without changes useSyncExternalStore contract: no spurious re-renders
empty snapshots use sentinel objects Predefined empty tuples prevent re-renders from new empty arrays
row identity preserved in snapshot ViewWrapper passes through row references from the view
snapshot never goes empty between updates No data flash between successive data pushes
stale snapshot preserved after destroy No empty flash on strict mode remount
legitimate empty transition is visible Server genuinely returns empty then data: transition is not masked
React.memo: only changed row re-renders (mock) Parent re-renders, unchanged React.memo child skips

🟢 Passing: end-to-end integration (real IVM to React.memo)

Uses QueryDelegateImpl with the test schema (issueowner, comments) to build a real SourceJoinArrayView pipeline, wires it directly to useSyncExternalStore, renders React.memo components, then edits a comment and verifies only the affected issue's component re-renders.

issue1 ─── owner:Alice        edit comment1       issue1' ─── owner:Alice (same ref)
        ├── comment1            ──────────►               ├── comment1' (new ref)
        └── comment2                                      └── comment2 (same ref)
issue2 ─── owner:Bob                               issue2 (same ref, unrelated)
        └── comment3                                     └── comment3 (same ref)

<IssueRow issue={issue1}>  re-renders (descendant changed)
<IssueRow issue={issue2}>  skips (React.memo, same ref)
Test What it verifies
editing a comment only re-renders the parent issue Full chain: Source push → ArrayView applyChange → identity preservation → useSyncExternalStore → React.memo skip for unrelated rows

Test plan

Adds comprehensive tests verifying that the immutable applyChange pipeline
preserves object identity for unchanged nodes (enabling React.memo) while
correctly bubbling new references up for changed descendants.

NOTE: Several tests fail on main due to expandNode eagerly materializing
all relationships (breaking identity). These pass with PR rocicorp#5605 which
removes expandNode. The two PRs should be merged together.

* ArrayView flat list: edit/add/remove preserve sibling identity
* Relationship propagation: child edit/add/remove bubble new refs to parent
* 3-level deep: grandchild edit bubbles through entire ancestor chain
* React snapshot identity: getSnapshot stability, sentinel reuse
* React.memo render counting: only changed row children re-render
* Data flash prevention: no empty snapshots between data updates
@vercel
Copy link

vercel bot commented Feb 26, 2026

Someone is attempting to deploy a commit to the Rocicorp Team on Vercel.

A member of the Team first needs to authorize it.

Alp added 5 commits February 26, 2026 12:05
* Add ASCII tree diagrams to every test explaining before/after
* Add singular relationship identity test (.one() queries)
* Add legitimate empty transition test (server returns empty then data)
* Fix batched edit test to track refB and assert new reference
* Extract shared types for readability in grandchild test
Uses QueryDelegateImpl with the test schema (issue → owner, comments)
to build a real Source → Join → ArrayView pipeline, wires it to
useSyncExternalStore, renders React.memo components, then edits a
comment and verifies only the affected issue's component re-renders.
* Pass callGot: true to QueryDelegateImpl so the query reaches
  'complete' result type (production-like lifecycle)
* Move view.destroy() into afterEach so cleanup runs even if
  assertions fail mid-test
* Extract emit() helper to replace getListeners().forEach() pattern
* Extract snapData/snapLength helpers for snapshot casting
* Merge mock and integration React tests into single describe with
  shared DOM lifecycle and cleanups array
* Inline MockView creation into newMockZero (only consumer)
* Remove unnecessary unique counter
@Karavil Karavil force-pushed the test/rerender-identity-regression-tests branch from 501a481 to 8d61b90 Compare February 26, 2026 21:30
Sign up for free to 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