Home Features Docs Blog Security Examples FAQ

58 Ways to Break a VDOM (and Why Ours Didn't)

djust Team | | 5 min read
VDOM torture test results showing 58 tests passing

Why Torture Test a VDOM?

The virtual DOM is the heart of djust's reactivity system. Every time your Django view's state changes, the VDOM diff engine compares the old and new HTML, computes the minimal set of patches, and sends them to the browser over WebSocket. If the diff engine gets it wrong, users see stale content, misplaced elements, or broken layouts.

We recently completed a major round of stress testing: 58 torture tests across Rust and Python, designed to probe every edge case we could think of. The result? Every test passed. But the journey revealed important insights about how our algorithm behaves under pressure.

What We Tested

We organized our tests into categories targeting specific areas of the diff algorithm.

Deep Nesting (Up to 50 Levels)

Real-world templates can nest deeply: a page layout wrapping a card wrapping a form wrapping conditional content. We tested trees 30 and 50 levels deep, changing only the innermost leaf node.

Result: A single text change at depth 50 produces exactly 1 SetText patch with a 50-element path. The algorithm doesn't waste time on unchanged ancestors.

Wide Sibling Lists (100–500 Items)

Large lists are common in production apps: message feeds, data tables, search results. We tested:

  • Appending to 100 items → 1 InsertChild patch
  • Removing the first item from 100 unkeyed items → 99 text morphs + 1 removal (this is why you need keys!)
  • Changing one item in 500 siblings → exactly 1 SetText patch

Keyed Diffing Edge Cases

Keys (data-key attributes) let the diff engine track items by identity rather than position. We tested the extremes:

  • Full reversal of 100 keyed items → only MoveChild patches (zero inserts/removes)
  • Random shuffle of 20 items → only moves, no content recreation
  • Duplicate keys → no crash, graceful degradation (last-wins via HashMap)
  • All keys replaced → correct remove-all + insert-all behavior

Replace Mode Under Stress

The data-djust-replace attribute tells djust to skip diffing and replace all children wholesale. We verified:

  • Swapping 100 children for 100 new ones: all removes before all inserts, descending index order
  • Replace containers with 5 siblings on each side: patches only target the replace container, never leak into siblings

Diff-Apply-Verify (Gold Standard)

The strongest correctness test: diff old vs. new, apply every patch to the old tree, then structurally compare the result against the new tree. We ran this pattern across deep nesting, tag replacement, attribute changes, and empty-to-complex transitions. Every result matched.

Full LiveView Pipeline (Python)

The Python tests exercise the complete stack: Django view → template rendering → Rust VDOM parse → diff → JSON patches. We tested:

  • Rapid counter incrementing (20 sequential events)
  • Form validation clearing (5 fields simultaneously)
  • Empty state → content → empty state round-trips
  • No-change re-renders producing zero patches
  • Unicode and special characters in content

Key Insights

Use Keys for Reorderable Lists

The biggest performance difference we measured: removing the first element from a 100-item list.

  • Without keys: 99 SetText patches + 1 RemoveChild (the algorithm morphs every item)
  • With keys: 1 RemoveChild + move patches (the algorithm knows which item left)

Add data-key to any list where items can be added, removed, or reordered.

Replace Mode Is Correct

After fixing the sibling grouping bug in v0.2.2, data-djust-replace correctly isolates its patches to the target container. The ordering guarantee (all removes before inserts, descending index order) ensures the client-side batch application never hits stale indices.

dj-* Event Handlers Are Preserved

Even when conditional rendering shifts DOM structure and the diff accidentally matches unrelated elements, dj-click, dj-input, and other event handler attributes are never removed. This is an intentional safety net that prevents broken interactivity.

No-Op Re-renders Are Free

If your event handler doesn't change any state, the diff produces zero patches. No unnecessary DOM updates, no wasted bandwidth.

Running the Tests

# Rust torture tests (42 tests)
cargo test -p djust_vdom --test torture_test

# Python torture tests (16 tests)
pytest python/tests/test_vdom_torture.py -v

# All VDOM tests
cargo test -p djust_vdom

What's Next

With the VDOM battle-tested, we're turning our attention to:

  • Property-based testing with proptest/quickcheck for randomized tree generation
  • Client-side patch application tests using a headless browser
  • Performance benchmarks to establish regression baselines for diff speed

The torture test suite is included in the djust repository and runs as part of CI. If you find a VDOM edge case we missed, open an issue and we'll add it to the suite.

Share this post

Related Posts