Skip to content

Commit

Permalink
add tests for callback identity preservation with keys
Browse files Browse the repository at this point in the history
  • Loading branch information
rmorshea committed Apr 17, 2021
1 parent c3236fe commit 72e03ec
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 6 deletions.
8 changes: 5 additions & 3 deletions docs/source/core-concepts.rst
Expand Up @@ -88,7 +88,7 @@ have to re-render the layout and see what changed:
async with idom.Layout(ClickCount(key="something")) as layout:
patch_1 = await layout.render()

fake_event = LayoutEvent("something.onClick", [{}])
fake_event = LayoutEvent("/something/onClick", [{}])
await layout.dispatch(fake_event)
patch_2 = await layout.render()

Expand Down Expand Up @@ -129,7 +129,7 @@ callback that's called by the dispatcher to events it should execute.


async def recv():
event = LayoutEvent(event_handler_id, [{}])
event = LayoutEvent("/my-component/onClick", [{}])

# We need this so we don't flood the render loop with events.
# In practice this is never an issue since events won't arrive
Expand All @@ -139,7 +139,9 @@ callback that's called by the dispatcher to events it should execute.
return event


async with SingleViewDispatcher(idom.Layout(ClickCount())) as dispatcher:
async with SingleViewDispatcher(
idom.Layout(ClickCount(key="my-component"))
) as dispatcher:
context = None # see note below
await dispatcher.run(send, recv, context)

Expand Down
4 changes: 2 additions & 2 deletions src/idom/core/layout.py
Expand Up @@ -207,7 +207,7 @@ def _render_model_children(
component_state,
cast(VdomDict, child),
patch_path=f"{patch_path}/children/{index}",
key_path=f"{key_path}/{index}",
key_path=f"{key_path}/{child.get('key') or hex(id(child))[2:]}",
)
)
elif isinstance(child, AbstractComponent):
Expand Down Expand Up @@ -250,7 +250,7 @@ def _render_model_event_targets(
handlers_by_target: Dict[str, EventHandler] = {}
model_event_targets: Dict[str, _EventTarget] = {}
for event, handler in handlers_by_event.items():
target = f"{key_path}.{event}"
target = f"{key_path}/{event}"
handlers_by_target[target] = handler
model_event_targets[event] = {
"target": target,
Expand Down
2 changes: 1 addition & 1 deletion tests/test_core/test_dispatcher.py
Expand Up @@ -19,7 +19,7 @@ async def test_shared_state_dispatcher():
changes_2 = []
key = "test-element"
event_name = "onEvent"
target_id = f"/{key}.{event_name}"
target_id = f"/{key}/{event_name}"

events_to_inject = [LayoutEvent(target=target_id, data=[])] * 4

Expand Down
84 changes: 84 additions & 0 deletions tests/test_core/test_layout.py
Expand Up @@ -275,3 +275,87 @@ def SomeComponent():
"Ignored event - handler 'missing' does not exist or its component unmounted",
next(iter(caplog.records)).msg,
)


def use_toggle(init=False):
state, set_state = idom.hooks.use_state(init)
return state, lambda: set_state(lambda old: not old)


async def test_model_key_preserves_callback_identity_for_common_elements():
called_good_trigger = idom.Ref(False)

@idom.component
def MyComponent():
reverse_children, set_reverse_children = use_toggle()

def good_trigger():
called_good_trigger.current = True
set_reverse_children()

def bad_trigger():
raise ValueError("Called bad trigger")

children = [
idom.html.button(
{"onClick": good_trigger, "id": "good"}, "good", key="good"
),
idom.html.button({"onClick": bad_trigger, "id": "bad"}, "bad", key="bad"),
]

if reverse_children:
children.reverse()

return idom.html.div(children)

async with idom.Layout(MyComponent(key="component")) as layout:
await layout.render()
for i in range(3):
event = LayoutEvent("/component/good/onClick", [])
await layout.dispatch(event)

assert called_good_trigger.current
# reset after checking
called_good_trigger.current = False

await layout.render()


async def test_model_key_preserves_callback_identity_for_components():
called_good_trigger = idom.Ref(False)

@idom.component
def RootComponent():
reverse_children, set_reverse_children = use_toggle()

children = [
Trigger(name, set_reverse_children, key=name) for name in ["good", "bad"]
]

if reverse_children:
children.reverse()

return idom.html.div(children)

@idom.component
def Trigger(name, set_reverse_children):
def callback():
if name == "good":
called_good_trigger.current = True
set_reverse_children()
else:
raise ValueError("Called bad trigger")

return idom.html.button({"onClick": callback, "id": "good"}, "good")

async with idom.Layout(RootComponent(key="root")) as layout:
await layout.render()
for i in range(3):
event = LayoutEvent("/root/good/onClick", [])
await layout.dispatch(event)

assert called_good_trigger.current
# reset after checking
called_good_trigger.current = False

await layout.render()

0 comments on commit 72e03ec

Please sign in to comment.