Skip to content

Commit b294646

Browse files
committed
add tests for callback identity preservation with keys
1 parent 376c059 commit b294646

File tree

4 files changed

+92
-6
lines changed

4 files changed

+92
-6
lines changed

docs/source/core-concepts.rst

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ have to re-render the layout and see what changed:
8888
async with idom.Layout(ClickCount(key="something")) as layout:
8989
patch_1 = await layout.render()
9090

91-
fake_event = LayoutEvent("something.onClick", [{}])
91+
fake_event = LayoutEvent("/something/onClick", [{}])
9292
await layout.dispatch(fake_event)
9393
patch_2 = await layout.render()
9494

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

130130

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

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

141141

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

src/idom/core/layout.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ def _render_model_children(
207207
component_state,
208208
cast(VdomDict, child),
209209
patch_path=f"{patch_path}/children/{index}",
210-
key_path=f"{key_path}/{index}",
210+
key_path=f"{key_path}/{child.get('key') or hex(id(child))[2:]}",
211211
)
212212
)
213213
elif isinstance(child, AbstractComponent):
@@ -250,7 +250,7 @@ def _render_model_event_targets(
250250
handlers_by_target: Dict[str, EventHandler] = {}
251251
model_event_targets: Dict[str, _EventTarget] = {}
252252
for event, handler in handlers_by_event.items():
253-
target = f"{key_path}.{event}"
253+
target = f"{key_path}/{event}"
254254
handlers_by_target[target] = handler
255255
model_event_targets[event] = {
256256
"target": target,

tests/test_core/test_dispatcher.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ async def test_shared_state_dispatcher():
1919
changes_2 = []
2020
key = "test-element"
2121
event_name = "onEvent"
22-
target_id = f"/{key}.{event_name}"
22+
target_id = f"/{key}/{event_name}"
2323

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

tests/test_core/test_layout.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,3 +275,87 @@ def SomeComponent():
275275
"Ignored event - handler 'missing' does not exist or its component unmounted",
276276
next(iter(caplog.records)).msg,
277277
)
278+
279+
280+
def use_toggle(init=False):
281+
state, set_state = idom.hooks.use_state(init)
282+
return state, lambda: set_state(lambda old: not old)
283+
284+
285+
async def test_model_key_preserves_callback_identity_for_common_elements():
286+
called_good_trigger = idom.Ref(False)
287+
288+
@idom.component
289+
def MyComponent():
290+
reverse_children, set_reverse_children = use_toggle()
291+
292+
def good_trigger():
293+
called_good_trigger.current = True
294+
set_reverse_children()
295+
296+
def bad_trigger():
297+
raise ValueError("Called bad trigger")
298+
299+
children = [
300+
idom.html.button(
301+
{"onClick": good_trigger, "id": "good"}, "good", key="good"
302+
),
303+
idom.html.button({"onClick": bad_trigger, "id": "bad"}, "bad", key="bad"),
304+
]
305+
306+
if reverse_children:
307+
children.reverse()
308+
309+
return idom.html.div(children)
310+
311+
async with idom.Layout(MyComponent(key="component")) as layout:
312+
await layout.render()
313+
for i in range(3):
314+
event = LayoutEvent("/component/good/onClick", [])
315+
await layout.dispatch(event)
316+
317+
assert called_good_trigger.current
318+
# reset after checking
319+
called_good_trigger.current = False
320+
321+
await layout.render()
322+
323+
324+
async def test_model_key_preserves_callback_identity_for_components():
325+
called_good_trigger = idom.Ref(False)
326+
327+
@idom.component
328+
def RootComponent():
329+
reverse_children, set_reverse_children = use_toggle()
330+
331+
children = [
332+
Trigger(name, set_reverse_children, key=name) for name in ["good", "bad"]
333+
]
334+
335+
if reverse_children:
336+
children.reverse()
337+
338+
return idom.html.div(children)
339+
340+
@idom.component
341+
def Trigger(name, set_reverse_children):
342+
def callback():
343+
if name == "good":
344+
called_good_trigger.current = True
345+
set_reverse_children()
346+
else:
347+
raise ValueError("Called bad trigger")
348+
349+
return idom.html.button({"onClick": callback, "id": "good"}, "good")
350+
351+
async with idom.Layout(RootComponent(key="root")) as layout:
352+
await layout.render()
353+
for i in range(3):
354+
event = LayoutEvent("/root/good/onClick", [])
355+
await layout.dispatch(event)
356+
357+
assert called_good_trigger.current
358+
# reset after checking
359+
called_good_trigger.current = False
360+
361+
await layout.render()

0 commit comments

Comments
 (0)