Fix reactive conditional rendering with proper branch swapping
Implements efficient reactive conditionals that pre-render branches once and swap DOM nodes without recreation, enabling fast switching with thousands of elements. Key Fixes: - Runtime now calls reevaluate_conditional_*() on signal changes - Match/case swap logic uses phi nodes to load correct old case - Previously hardcoded case_0 as old value, causing accumulation - Branch render functions now wrap all content in container div Implementation: - Pre-render both if/else branches at init - Store roots in globals (@cond_N_then_root, @cond_N_else_root) - On signal change, swap DOM nodes via js_replace_at_marker() - Match statements use switch/phi pattern to select old case Tests: - Added test_reactive_conditionals.py (5 new tests) - Updated test_llvm_codegen_conditional.py for reactive infrastructure - All 35 tests passing Performance: No re-rendering on switches, perfect for dynamic content. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
1 parent
ac7726df46
commit
e686903170
4 files changed
+272
-41
No files matched your search
@@ -0,0 +1,169 @@
|
||||
"""Tests for reactive conditional rendering with signal updates."""
|
||||
|
||||
import pytest
|
||||
from ritz.parser import parse_component
|
||||
from ritz.llvm_codegen_signals import generate_llvm_ir
|
||||
|
||||
|
||||
def test_reactive_conditional_infrastructure():
|
||||
"""Test that reactive conditional infrastructure is generated."""
|
||||
source = """
|
||||
from ritz.syntax import *
|
||||
|
||||
@component
|
||||
def Toggle():
|
||||
visible = signal(1)
|
||||
|
||||
if visible:
|
||||
with P():
|
||||
yield "Visible"
|
||||
else:
|
||||
with P():
|
||||
yield "Hidden"
|
||||
"""
|
||||
ir = parse_component(source)
|
||||
llvm_ir = generate_llvm_ir(ir)
|
||||
|
||||
# Should have marker global
|
||||
assert "@cond_0_marker = global i32 0" in llvm_ir
|
||||
|
||||
# Should have current state global
|
||||
assert "@cond_0_current = global i32 0" in llvm_ir
|
||||
|
||||
# Should have root globals for both branches
|
||||
assert "@cond_0_then_root = global i32 0" in llvm_ir
|
||||
assert "@cond_0_else_root = global i32 0" in llvm_ir
|
||||
|
||||
# Should have init function
|
||||
assert "define void @_init_conditional_0()" in llvm_ir
|
||||
|
||||
# Should have reevaluate function (for signal updates)
|
||||
assert "define void @reevaluate_conditional_0()" in llvm_ir
|
||||
|
||||
# Should have branch render functions
|
||||
assert "define i32 @_render_cond_0_then()" in llvm_ir
|
||||
assert "define i32 @_render_cond_0_else()" in llvm_ir
|
||||
|
||||
|
||||
def test_reactive_conditional_init_logic():
|
||||
"""Test that init function pre-renders both branches."""
|
||||
source = """
|
||||
from ritz.syntax import *
|
||||
|
||||
@component
|
||||
def Toggle():
|
||||
visible = signal(1)
|
||||
|
||||
if visible:
|
||||
with P():
|
||||
yield "Shown"
|
||||
else:
|
||||
with P():
|
||||
yield "Hidden"
|
||||
"""
|
||||
ir = parse_component(source)
|
||||
llvm_ir = generate_llvm_ir(ir)
|
||||
|
||||
# Init should call both render functions
|
||||
assert "call i32 @_render_cond_0_then()" in llvm_ir
|
||||
assert "call i32 @_render_cond_0_else()" in llvm_ir
|
||||
|
||||
# Init should store both roots
|
||||
assert "store i32" in llvm_ir and "@cond_0_then_root" in llvm_ir
|
||||
assert "store i32" in llvm_ir and "@cond_0_else_root" in llvm_ir
|
||||
|
||||
# Init should insert active branch at marker
|
||||
assert "call void @js_replace_at_marker" in llvm_ir
|
||||
|
||||
|
||||
def test_reactive_conditional_swap_logic():
|
||||
"""Test that reevaluate function swaps branches when condition changes."""
|
||||
source = """
|
||||
from ritz.syntax import *
|
||||
|
||||
@component
|
||||
def Toggle():
|
||||
visible = signal(0)
|
||||
|
||||
if visible:
|
||||
with P():
|
||||
yield "On"
|
||||
else:
|
||||
with P():
|
||||
yield "Off"
|
||||
"""
|
||||
ir = parse_component(source)
|
||||
llvm_ir = generate_llvm_ir(ir)
|
||||
|
||||
# Reevaluate should re-evaluate the condition
|
||||
assert "reevaluate_conditional_0" in llvm_ir
|
||||
|
||||
# Should check if condition changed
|
||||
assert "icmp ne" in llvm_ir
|
||||
|
||||
# Should have swap logic
|
||||
assert "swap:" in llvm_ir or "swap_to_then:" in llvm_ir
|
||||
assert "swap_to_then:" in llvm_ir
|
||||
assert "swap_to_else:" in llvm_ir
|
||||
|
||||
|
||||
def test_multiple_reactive_conditionals():
|
||||
"""Test multiple if statements in same component get unique IDs."""
|
||||
source = """
|
||||
from ritz.syntax import *
|
||||
|
||||
@component
|
||||
def MultiToggle():
|
||||
flag1 = signal(1)
|
||||
flag2 = signal(0)
|
||||
|
||||
if flag1:
|
||||
with P():
|
||||
yield "First on"
|
||||
|
||||
if flag2:
|
||||
with P():
|
||||
yield "Second on"
|
||||
"""
|
||||
ir = parse_component(source)
|
||||
llvm_ir = generate_llvm_ir(ir)
|
||||
|
||||
# Should have two separate conditionals
|
||||
assert "@cond_0_marker" in llvm_ir
|
||||
assert "@cond_1_marker" in llvm_ir
|
||||
|
||||
# Should have init functions for both
|
||||
assert "define void @_init_conditional_0()" in llvm_ir
|
||||
assert "define void @_init_conditional_1()" in llvm_ir
|
||||
|
||||
# Should have reevaluate functions for both
|
||||
assert "define void @reevaluate_conditional_0()" in llvm_ir
|
||||
assert "define void @reevaluate_conditional_1()" in llvm_ir
|
||||
|
||||
|
||||
def test_reactive_conditional_with_comparison():
|
||||
"""Test reactive conditional with comparison operator."""
|
||||
source = """
|
||||
from ritz.syntax import *
|
||||
|
||||
@component
|
||||
def Counter():
|
||||
count = signal(0)
|
||||
|
||||
if count > 5:
|
||||
with P():
|
||||
yield "High"
|
||||
else:
|
||||
with P():
|
||||
yield "Low"
|
||||
"""
|
||||
ir = parse_component(source)
|
||||
llvm_ir = generate_llvm_ir(ir)
|
||||
|
||||
# Should generate icmp for comparison
|
||||
assert "icmp" in llvm_ir
|
||||
assert "sgt" in llvm_ir # signed greater than
|
||||
|
||||
# Should still have reactive infrastructure
|
||||
assert "@_init_conditional_0()" in llvm_ir
|
||||
assert "@reevaluate_conditional_0()" in llvm_ir
|
||||
Reference in new issue
Block a user