mirror of
https://github.com/rio-labs/rio.git
synced 2025-12-30 09:49:44 -06:00
218 lines
7.2 KiB
Python
218 lines
7.2 KiB
Python
import rio.testing
|
|
|
|
|
|
async def test_refresh_with_nothing_to_do() -> None:
|
|
def build() -> rio.Component:
|
|
return rio.Text("Hello")
|
|
|
|
async with rio.testing.DummyClient(build) as test_client:
|
|
test_client._received_messages.clear()
|
|
await test_client.refresh()
|
|
|
|
assert not test_client._dirty_components
|
|
assert not test_client._last_updated_components
|
|
|
|
|
|
async def test_refresh_with_clean_root_component() -> None:
|
|
def build() -> rio.Component:
|
|
text_component = rio.Text("Hello")
|
|
return rio.Container(text_component)
|
|
|
|
async with rio.testing.DummyClient(build) as test_client:
|
|
text_component = test_client.get_component(rio.Text)
|
|
|
|
text_component.text = "World"
|
|
await test_client.refresh()
|
|
|
|
assert test_client._last_updated_components == {text_component}
|
|
|
|
|
|
async def test_rebuild_component_with_dead_parent() -> None:
|
|
class ChildUnmounter(rio.Component):
|
|
child: rio.Component
|
|
child_is_mounted: bool = True
|
|
|
|
def build(self) -> rio.Component:
|
|
if self.child_is_mounted:
|
|
return self.child
|
|
else:
|
|
return rio.Spacer()
|
|
|
|
class ComponentWithState(rio.Component):
|
|
state: str
|
|
|
|
def build(self) -> rio.Component:
|
|
return rio.Text(self.state)
|
|
|
|
def build() -> rio.Component:
|
|
return ChildUnmounter(ComponentWithState("Hello"))
|
|
|
|
async with rio.testing.DummyClient(
|
|
build, use_ordered_dirty_set=True
|
|
) as test_client:
|
|
# Change the component's state, but also remove it from the component
|
|
# tree
|
|
unmounter = test_client.get_component(ChildUnmounter)
|
|
component = test_client.get_component(ComponentWithState)
|
|
|
|
component.state = "Hi"
|
|
unmounter.child_is_mounted = False
|
|
|
|
await test_client.refresh()
|
|
|
|
# Make sure no data for dead components was sent to JS
|
|
assert unmounter in test_client._last_updated_components
|
|
assert component not in test_client._last_updated_components
|
|
|
|
|
|
async def test_unmount_and_remount() -> None:
|
|
class DemoComponent(rio.Component):
|
|
content: rio.Component
|
|
show_child: bool
|
|
|
|
def build(self) -> rio.Component:
|
|
children = [self.content] if self.show_child else []
|
|
return rio.Row(*children)
|
|
|
|
def build() -> rio.Component:
|
|
return DemoComponent(
|
|
rio.Text("hi"),
|
|
show_child=True,
|
|
)
|
|
|
|
async with rio.testing.DummyClient(build) as test_client:
|
|
root_component = test_client.get_component(DemoComponent)
|
|
child_component = root_component.content
|
|
row_component = test_client.get_component(rio.Row)
|
|
|
|
root_component.show_child = False
|
|
await test_client.refresh()
|
|
assert not child_component._is_in_component_tree_({})
|
|
assert test_client._last_updated_components == {
|
|
root_component,
|
|
row_component,
|
|
}
|
|
|
|
root_component.show_child = True
|
|
await test_client.refresh()
|
|
assert child_component._is_in_component_tree_({})
|
|
assert test_client._last_updated_components == {
|
|
root_component,
|
|
row_component,
|
|
child_component,
|
|
}
|
|
|
|
|
|
async def test_rebuild_component_with_dead_builder():
|
|
class ChildToggler(rio.Component):
|
|
child_is_alive: bool = True
|
|
|
|
def build(self) -> rio.Component:
|
|
if self.child_is_alive:
|
|
return Builder(StatefulComponent("hello"))
|
|
else:
|
|
return rio.Spacer()
|
|
|
|
class Builder(rio.Component):
|
|
child: rio.Component
|
|
|
|
def build(self) -> rio.Component:
|
|
return self.child
|
|
|
|
class StatefulComponent(rio.Component):
|
|
state: str = "hi"
|
|
|
|
def build(self) -> rio.Component:
|
|
return rio.Text(self.state)
|
|
|
|
async with rio.testing.DummyClient(ChildToggler) as test_client:
|
|
toggler = test_client.get_component(ChildToggler)
|
|
stateful_component = test_client.get_component(StatefulComponent)
|
|
|
|
toggler.child_is_alive = False
|
|
await test_client.refresh()
|
|
|
|
# At this point in time, the builder is dead
|
|
assert stateful_component._weak_builder_() is None
|
|
|
|
stateful_component.state = "bye"
|
|
|
|
test_client._received_messages.clear()
|
|
await test_client.refresh()
|
|
assert not test_client._received_messages
|
|
|
|
|
|
async def test_changing_children_of_not_dirty_high_level_component():
|
|
# Situation:
|
|
# HighLevelComponent1 contains HighLevelComponent2
|
|
# HighLevelComponent2 contains LowLevelContainer
|
|
# HighLevelComponent1 is rebuilt and changes the child of LowLevelContainer
|
|
# -> LowLevelContainer is reconciled and dirty (because it has new children)
|
|
# -> HighLevelComponent2 is reconciled but *not* dirty because its child was
|
|
# reconciled
|
|
# The end result is that there is a new component (the child of
|
|
# LowLevelContainer), whose builder (HighLevelComponent2) is not "dirty".
|
|
# Make sure the new component is initialized correctly despite this.
|
|
class HighLevelComponent1(rio.Component):
|
|
switch: bool = False
|
|
|
|
def build(self) -> rio.Component:
|
|
if self.switch:
|
|
child = rio.Switch()
|
|
else:
|
|
child = rio.Text("hi")
|
|
|
|
assert issubclass(
|
|
rio.Column,
|
|
rio.components.fundamental_component.FundamentalComponent,
|
|
)
|
|
return HighLevelComponent2(rio.Column(child))
|
|
|
|
class HighLevelComponent2(rio.Component):
|
|
content: rio.Component
|
|
|
|
def build(self) -> rio.Component:
|
|
return self.content
|
|
|
|
async with rio.testing.DummyClient(HighLevelComponent1) as test_client:
|
|
root_component = test_client.get_component(HighLevelComponent1)
|
|
text_component = test_client.get_component(rio.Text)
|
|
|
|
root_component.switch = True
|
|
await test_client.refresh()
|
|
|
|
# Check if the new child, a Switch, was sent to the frontend
|
|
assert any(
|
|
isinstance(component, rio.Switch)
|
|
for component in test_client._last_updated_components
|
|
), "No rio.Switch was sent to the frontend"
|
|
|
|
# Check if the old child, a Text, is dead
|
|
assert not text_component._is_in_component_tree_({}), (
|
|
"rio.Text is still in the component tree"
|
|
)
|
|
|
|
|
|
async def test_binding_doesnt_update_children() -> None:
|
|
class ComponentWithBinding(rio.Component):
|
|
text: str = ""
|
|
|
|
def build(self) -> rio.Component:
|
|
return rio.Column(
|
|
rio.Markdown("# Heading"),
|
|
rio.TextInput(self.bind().text),
|
|
rio.Text(self.text),
|
|
)
|
|
|
|
async with rio.testing.DummyClient(ComponentWithBinding) as test_client:
|
|
root_component = test_client.get_component(ComponentWithBinding)
|
|
text_input = test_client.get_component(rio.TextInput)
|
|
text = test_client.get_component(rio.Text)
|
|
|
|
# Note: `text_input._on_message_` automatically triggers a refresh
|
|
test_client._received_messages.clear()
|
|
await text_input._on_message_({"type": "confirm", "text": "hello"})
|
|
|
|
# Only the Text component has changed in this rebuild
|
|
assert test_client._last_updated_components == {root_component, text}
|