Features: - Track attributes that depend on signals (ternary expressions) - Store element references in globals for reactive attributes - Re-evaluate and update only changed attributes when signals change - Similar to reactive conditionals but for attributes Performance: - Minimal DOM updates - only changed attributes are updated - No full re-renders, no virtual DOM diffing - Direct attribute updates via js_set_attribute Example: class_='tab active' if tab_index == 0 else 'tab' When tab_index changes, only the class attribute updates. Implementation: - Collection phase: scan for attributes with signal dependencies - Render phase: store element references to globals - Signal update: re-evaluate expression and update attribute All tests passing (55 passed, 2 skipped) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
100 lines
2.5 KiB
Python
100 lines
2.5 KiB
Python
"""Tests for reactive attributes that update when signals change."""
|
|
|
|
import pytest
|
|
from ritz.parser import parse_component
|
|
from ritz.llvm_codegen_signals import generate_llvm_ir
|
|
|
|
|
|
def test_parse_reactive_attribute():
|
|
"""Test parsing attribute with signal dependency."""
|
|
source = """
|
|
from ritz.syntax import *
|
|
|
|
@component
|
|
def Toggle():
|
|
active = signal(1)
|
|
|
|
with Button(class_='active' if active else 'inactive'):
|
|
yield "Click"
|
|
"""
|
|
ir = parse_component(source)
|
|
|
|
# Should have ternary expression in class attribute
|
|
with_stmt = [s for s in ir["body"] if s["type"] == "with"][0]
|
|
class_prop = with_stmt["element"]["props"]["class_"]
|
|
assert class_prop["type"] == "ifexp"
|
|
assert class_prop["test"]["id"] == "active"
|
|
|
|
|
|
def test_reactive_attribute_infrastructure():
|
|
"""Test that reactive attribute tracking is generated."""
|
|
source = """
|
|
from ritz.syntax import *
|
|
|
|
@component
|
|
def Toggle():
|
|
active = signal(1)
|
|
|
|
def toggle():
|
|
active = 0 if active else 1
|
|
|
|
with Button(class_='btn-active' if active else 'btn-inactive', onclick=toggle):
|
|
yield "Toggle"
|
|
"""
|
|
ir = parse_component(source)
|
|
llvm_ir = generate_llvm_ir(ir)
|
|
|
|
# Should have storage for the button element
|
|
assert "@reactive_attr_" in llvm_ir or "@elem_" in llvm_ir
|
|
|
|
# Should update attribute when signal changes in toggle()
|
|
# The toggle function should re-evaluate the class expression and update
|
|
assert "js_set_attribute" in llvm_ir
|
|
|
|
|
|
def test_multiple_reactive_attributes():
|
|
"""Test multiple elements with reactive attributes."""
|
|
source = """
|
|
from ritz.syntax import *
|
|
|
|
@component
|
|
def Tabs():
|
|
tab = signal(0)
|
|
|
|
def set_tab(value):
|
|
tab = value
|
|
|
|
with Ul():
|
|
with Li(class_='active' if tab == 0 else ''):
|
|
yield "Tab 0"
|
|
|
|
with Li(class_='active' if tab == 1 else ''):
|
|
yield "Tab 1"
|
|
"""
|
|
ir = parse_component(source)
|
|
llvm_ir = generate_llvm_ir(ir)
|
|
|
|
# Both Li elements should have reactive attributes
|
|
# When tab signal changes, both should update
|
|
assert llvm_ir.count("js_set_attribute") >= 2
|
|
|
|
|
|
def test_reactive_attribute_with_comparison():
|
|
"""Test reactive attribute with comparison in ternary."""
|
|
source = """
|
|
from ritz.syntax import *
|
|
|
|
@component
|
|
def Counter():
|
|
count = signal(5)
|
|
|
|
with Div(class_='high' if count > 10 else 'low'):
|
|
yield "Value"
|
|
"""
|
|
ir = parse_component(source)
|
|
llvm_ir = generate_llvm_ir(ir)
|
|
|
|
# Should emit comparison when evaluating class
|
|
assert "icmp" in llvm_ir
|
|
assert "select" in llvm_ir
|