Skip to content

Commit 9678622

Browse files
committed
fix tests using key path event targets
1 parent 75f9bd1 commit 9678622

File tree

5 files changed

+76
-25
lines changed

5 files changed

+76
-25
lines changed

src/idom/core/events.py

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ def on(
7373
stop_propagation:
7474
Block the event from propagating further up the DOM.
7575
prevent_default:
76-
Stops the default actional associate with the event from taking place.
76+
Stops the default action associate with the event from taking place.
7777
7878
Returns:
7979
A decorator which accepts an event handler function as its first argument.
@@ -133,12 +133,10 @@ class EventHandler:
133133
The event handler object acts like a coroutine when called.
134134
135135
Parameters:
136-
event_name:
137-
The camel case name of the event.
138-
target_id:
139-
A unique identifier for the event handler. This is generally used if
140-
an element has more than on event handler for the same event type. If
141-
no ID is provided one will be generated automatically.
136+
stop_propagation:
137+
Block the event from propagating further up the DOM.
138+
prevent_default:
139+
Stops the default action associate with the event from taking place.
142140
"""
143141

144142
__slots__ = (
@@ -160,13 +158,12 @@ def __init__(
160158
self._func_handlers: List[Callable[..., Any]] = []
161159

162160
def add(self, function: Callable[..., Any]) -> "EventHandler":
163-
"""Add a callback to the event handler.
161+
"""Add a callback function or coroutine to the event handler.
164162
165163
Parameters:
166164
function:
167-
The event handler function. Its parameters may indicate event attributes
168-
which should be sent back from the fronend unless otherwise specified by
169-
the ``properties`` parameter.
165+
The event handler function accepting parameters sent by the client.
166+
Typically this is a single ``event`` parameter that is a dictionary.
170167
"""
171168
if asyncio.iscoroutinefunction(function):
172169
self._coro_handlers.append(function)
@@ -175,7 +172,7 @@ def add(self, function: Callable[..., Any]) -> "EventHandler":
175172
return self
176173

177174
def remove(self, function: Callable[..., Any]) -> None:
178-
"""Remove the function from the event handler.
175+
"""Remove the given function or coroutine from this event handler.
179176
180177
Raises:
181178
ValueError: if not found
@@ -185,6 +182,11 @@ def remove(self, function: Callable[..., Any]) -> None:
185182
else:
186183
self._func_handlers.remove(function)
187184

185+
def clear(self) -> None:
186+
"""Remove all functions and coroutines from this event handler"""
187+
self._coro_handlers.clear()
188+
self._func_handlers.clear()
189+
188190
async def __call__(self, data: List[Any]) -> Any:
189191
"""Trigger all callbacks in the event handler."""
190192
if self._coro_handlers:

src/idom/core/layout.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ def _render_model_attributes(
230230

231231
model_event_handlers = new_state.model["eventHandlers"] = {}
232232
for event, handler in handlers_by_event.items():
233-
target = old_state.targets_by_event.get(event, id(handler))
233+
target = old_state.targets_by_event.get(event, hex_id(handler))
234234
new_state.targets_by_event[event] = target
235235
self._event_handlers[target] = handler
236236
model_event_handlers[event] = {

tests/general_utils.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
from weakref import ref
66

77
import idom
8+
from idom.core.events import EventHandler
9+
from idom.core.utils import hex_id
810

911

1012
@contextmanager
@@ -19,11 +21,41 @@ def patch_slots_object(obj, attr, new_value):
1921
setattr(obj, attr, old_value)
2022

2123

24+
class EventCatcher:
25+
"""Utility for capturing the target of an event handler
26+
27+
Example:
28+
.. code-block::
29+
30+
event_catcher = EventCatcher()
31+
32+
@idom.component
33+
def MyComponent():
34+
state, set_state = idom.hooks.use_state(0)
35+
handler = event_catcher.capture(lambda event: set_state(state + 1))
36+
return idom.html.button({"onClick": handler}, "Click me!")
37+
"""
38+
39+
def __init__(self):
40+
self._event_handler = EventHandler()
41+
42+
@property
43+
def target(self) -> str:
44+
return hex_id(self._event_handler)
45+
46+
def capture(self, function) -> EventHandler:
47+
"""Called within the body of a component to create a captured event handler"""
48+
self._event_handler.clear()
49+
self._event_handler.add(function)
50+
return self._event_handler
51+
52+
2253
class HookCatcher:
2354
"""Utility for capturing a LifeCycleHook from a component
2455
25-
Eleftample:
56+
Example:
2657
.. code-block::
58+
2759
component_hook = HookCatcher()
2860
2961
@idom.component

tests/test_core/test_dispatcher.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,26 @@
1010
SingleViewDispatcher,
1111
)
1212
from idom.core.layout import Layout, LayoutEvent
13-
from tests.general_utils import assert_same_items
13+
from tests.general_utils import EventCatcher, assert_same_items
1414

1515

1616
async def test_shared_state_dispatcher():
1717
done = asyncio.Event()
1818
changes_1 = []
1919
changes_2 = []
20+
2021
event_name = "onEvent"
21-
target_id = f"/{event_name}"
22+
event_catcher = EventCatcher()
2223

23-
events_to_inject = [LayoutEvent(target=target_id, data=[])] * 4
24+
events_to_inject = [LayoutEvent(target=event_catcher.target, data=[])] * 4
2425

2526
async def send_1(patch):
2627
changes_1.append(patch.changes)
2728

2829
async def recv_1():
30+
# Need this to yield control back to event loop otherwise we block indefinitely
31+
# for some reason. Realistically this await would be on some client event, so
32+
# this isn't too contrived.
2933
await asyncio.sleep(0)
3034
try:
3135
return events_to_inject.pop(0)
@@ -43,7 +47,8 @@ async def recv_2():
4347
@idom.component
4448
def Clickable():
4549
count, set_count = idom.hooks.use_state(0)
46-
return idom.html.div({event_name: lambda: set_count(count + 1), "count": count})
50+
handler = event_catcher.capture(lambda: set_count(count + 1))
51+
return idom.html.div({event_name: handler, "count": count})
4752

4853
async with SharedViewDispatcher(Layout(Clickable())) as dispatcher:
4954
await dispatcher.run(send_1, recv_1, "1")
@@ -56,7 +61,7 @@ def Clickable():
5661
"path": "/eventHandlers",
5762
"value": {
5863
event_name: {
59-
"target": target_id,
64+
"target": event_catcher.target,
6065
"preventDefault": False,
6166
"stopPropagation": False,
6267
}

tests/test_core/test_layout.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import idom
88
from idom.core.layout import LayoutEvent, LayoutUpdate
99
from idom.core.utils import hex_id
10-
from tests.general_utils import HookCatcher, assert_same_items
10+
from tests.general_utils import EventCatcher, HookCatcher, assert_same_items
1111

1212

1313
def test_layout_update_create_from_apply_to():
@@ -286,15 +286,19 @@ def use_toggle(init=False):
286286

287287
async def test_model_key_preserves_callback_identity_for_common_elements():
288288
called_good_trigger = idom.Ref(False)
289+
good_event_catcher = EventCatcher()
290+
bad_event_catcher = EventCatcher()
289291

290292
@idom.component
291293
def MyComponent():
292294
reverse_children, set_reverse_children = use_toggle()
293295

296+
@good_event_catcher.capture
294297
def good_trigger():
295298
called_good_trigger.current = True
296299
set_reverse_children()
297300

301+
@bad_event_catcher.capture
298302
def bad_trigger():
299303
raise ValueError("Called bad trigger")
300304

@@ -313,7 +317,7 @@ def bad_trigger():
313317
async with idom.Layout(MyComponent()) as layout:
314318
await layout.render()
315319
for i in range(3):
316-
event = LayoutEvent("/good/onClick", [])
320+
event = LayoutEvent(good_event_catcher.target, [])
317321
await layout.dispatch(event)
318322

319323
assert called_good_trigger.current
@@ -325,6 +329,8 @@ def bad_trigger():
325329

326330
async def test_model_key_preserves_callback_identity_for_components():
327331
called_good_trigger = idom.Ref(False)
332+
good_event_catcher = EventCatcher()
333+
bad_event_catcher = EventCatcher()
328334

329335
@idom.component
330336
def RootComponent():
@@ -339,19 +345,25 @@ def RootComponent():
339345

340346
@idom.component
341347
def Trigger(set_reverse_children, key):
342-
def callback():
343-
if key == "good":
348+
if key == "good":
349+
350+
@good_event_catcher.capture
351+
def callback():
344352
called_good_trigger.current = True
345353
set_reverse_children()
346-
else:
354+
355+
else:
356+
357+
@bad_event_catcher.capture
358+
def callback():
347359
raise ValueError("Called bad trigger")
348360

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

351363
async with idom.Layout(RootComponent()) as layout:
352364
await layout.render()
353365
for i in range(3):
354-
event = LayoutEvent("/good/onClick", [])
366+
event = LayoutEvent(good_event_catcher.target, [])
355367
await layout.dispatch(event)
356368

357369
assert called_good_trigger.current

0 commit comments

Comments
 (0)