test_event.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  1. import json
  2. from typing import List
  3. import pytest
  4. from reflex import event
  5. from reflex.event import Event, EventHandler, EventSpec, call_event_handler, fix_events
  6. from reflex.state import BaseState
  7. from reflex.utils import format
  8. from reflex.vars import Var
  9. def make_var(value) -> Var:
  10. """Make a variable.
  11. Args:
  12. value: The value of the var.
  13. Returns:
  14. The var.
  15. """
  16. var = Var.create(value)
  17. assert var is not None
  18. return var
  19. def test_create_event():
  20. """Test creating an event."""
  21. event = Event(token="token", name="state.do_thing", payload={"arg": "value"})
  22. assert event.token == "token"
  23. assert event.name == "state.do_thing"
  24. assert event.payload == {"arg": "value"}
  25. def test_call_event_handler():
  26. """Test that calling an event handler creates an event spec."""
  27. def test_fn():
  28. pass
  29. test_fn.__qualname__ = "test_fn"
  30. def test_fn_with_args(_, arg1, arg2):
  31. pass
  32. test_fn_with_args.__qualname__ = "test_fn_with_args"
  33. handler = EventHandler(fn=test_fn)
  34. event_spec = handler()
  35. assert event_spec.handler == handler
  36. assert event_spec.args == ()
  37. assert format.format_event(event_spec) == 'Event("test_fn", {})'
  38. handler = EventHandler(fn=test_fn_with_args)
  39. event_spec = handler(make_var("first"), make_var("second"))
  40. # Test passing vars as args.
  41. assert event_spec.handler == handler
  42. assert event_spec.args[0][0].equals(Var.create_safe("arg1"))
  43. assert event_spec.args[0][1].equals(Var.create_safe("first"))
  44. assert event_spec.args[1][0].equals(Var.create_safe("arg2"))
  45. assert event_spec.args[1][1].equals(Var.create_safe("second"))
  46. assert (
  47. format.format_event(event_spec)
  48. == 'Event("test_fn_with_args", {arg1:first,arg2:second})'
  49. )
  50. # Passing args as strings should format differently.
  51. event_spec = handler("first", "second") # type: ignore
  52. assert (
  53. format.format_event(event_spec)
  54. == 'Event("test_fn_with_args", {arg1:`first`,arg2:`second`})'
  55. )
  56. first, second = 123, "456"
  57. handler = EventHandler(fn=test_fn_with_args)
  58. event_spec = handler(first, second) # type: ignore
  59. assert (
  60. format.format_event(event_spec)
  61. == 'Event("test_fn_with_args", {arg1:123,arg2:`456`})'
  62. )
  63. assert event_spec.handler == handler
  64. assert event_spec.args[0][0].equals(Var.create_safe("arg1"))
  65. assert event_spec.args[0][1].equals(Var.create_safe(first))
  66. assert event_spec.args[1][0].equals(Var.create_safe("arg2"))
  67. assert event_spec.args[1][1].equals(Var.create_safe(second))
  68. handler = EventHandler(fn=test_fn_with_args)
  69. with pytest.raises(TypeError):
  70. handler(test_fn) # type: ignore
  71. def test_call_event_handler_partial():
  72. """Calling an EventHandler with incomplete args returns an EventSpec that can be extended."""
  73. def test_fn_with_args(_, arg1, arg2):
  74. pass
  75. test_fn_with_args.__qualname__ = "test_fn_with_args"
  76. def spec(a2: str) -> List[str]:
  77. return [a2]
  78. handler = EventHandler(fn=test_fn_with_args)
  79. event_spec = handler(make_var("first"))
  80. event_spec2 = call_event_handler(event_spec, spec)
  81. assert event_spec.handler == handler
  82. assert len(event_spec.args) == 1
  83. assert event_spec.args[0][0].equals(Var.create_safe("arg1"))
  84. assert event_spec.args[0][1].equals(Var.create_safe("first"))
  85. assert format.format_event(event_spec) == 'Event("test_fn_with_args", {arg1:first})'
  86. assert event_spec2 is not event_spec
  87. assert event_spec2.handler == handler
  88. assert len(event_spec2.args) == 2
  89. assert event_spec2.args[0][0].equals(Var.create_safe("arg1"))
  90. assert event_spec2.args[0][1].equals(Var.create_safe("first"))
  91. assert event_spec2.args[1][0].equals(Var.create_safe("arg2"))
  92. assert event_spec2.args[1][1].equals(Var.create_safe("_a2"))
  93. assert (
  94. format.format_event(event_spec2)
  95. == 'Event("test_fn_with_args", {arg1:first,arg2:_a2})'
  96. )
  97. @pytest.mark.parametrize(
  98. ("arg1", "arg2"),
  99. (
  100. (1, 2),
  101. (1, "2"),
  102. ({"a": 1}, {"b": 2}),
  103. ),
  104. )
  105. def test_fix_events(arg1, arg2):
  106. """Test that chaining an event handler with args formats the payload correctly.
  107. Args:
  108. arg1: The first arg passed to the handler.
  109. arg2: The second arg passed to the handler.
  110. """
  111. def test_fn_with_args(_, arg1, arg2):
  112. pass
  113. test_fn_with_args.__qualname__ = "test_fn_with_args"
  114. handler = EventHandler(fn=test_fn_with_args)
  115. event_spec = handler(arg1, arg2)
  116. event = fix_events([event_spec], token="foo")[0]
  117. assert event.name == test_fn_with_args.__qualname__
  118. assert event.token == "foo"
  119. assert event.payload == {"arg1": arg1, "arg2": arg2}
  120. @pytest.mark.parametrize(
  121. "input,output",
  122. [
  123. (
  124. ("/path", None, None),
  125. 'Event("_redirect", {path:`/path`,external:false,replace:false})',
  126. ),
  127. (
  128. ("/path", True, None),
  129. 'Event("_redirect", {path:`/path`,external:true,replace:false})',
  130. ),
  131. (
  132. ("/path", False, None),
  133. 'Event("_redirect", {path:`/path`,external:false,replace:false})',
  134. ),
  135. (
  136. (Var.create_safe("path"), None, None),
  137. 'Event("_redirect", {path:path,external:false,replace:false})',
  138. ),
  139. (
  140. ("/path", None, True),
  141. 'Event("_redirect", {path:`/path`,external:false,replace:true})',
  142. ),
  143. (
  144. ("/path", True, True),
  145. 'Event("_redirect", {path:`/path`,external:true,replace:true})',
  146. ),
  147. ],
  148. )
  149. def test_event_redirect(input, output):
  150. """Test the event redirect function.
  151. Args:
  152. input: The input for running the test.
  153. output: The expected output to validate the test.
  154. """
  155. path, external, replace = input
  156. kwargs = {}
  157. if external is not None:
  158. kwargs["external"] = external
  159. if replace is not None:
  160. kwargs["replace"] = replace
  161. spec = event.redirect(path, **kwargs)
  162. assert isinstance(spec, EventSpec)
  163. assert spec.handler.fn.__qualname__ == "_redirect"
  164. # this asserts need comment about what it's testing (they fail with Var as input)
  165. # assert spec.args[0][0].equals(Var.create_safe("path"))
  166. # assert spec.args[0][1].equals(Var.create_safe("/path"))
  167. assert format.format_event(spec) == output
  168. def test_event_console_log():
  169. """Test the event console log function."""
  170. spec = event.console_log("message")
  171. assert isinstance(spec, EventSpec)
  172. assert spec.handler.fn.__qualname__ == "_console"
  173. assert spec.args[0][0].equals(Var.create_safe("message"))
  174. assert spec.args[0][1].equals(Var.create_safe("message"))
  175. assert format.format_event(spec) == 'Event("_console", {message:`message`})'
  176. spec = event.console_log(Var.create_safe("message"))
  177. assert format.format_event(spec) == 'Event("_console", {message:message})'
  178. def test_event_window_alert():
  179. """Test the event window alert function."""
  180. spec = event.window_alert("message")
  181. assert isinstance(spec, EventSpec)
  182. assert spec.handler.fn.__qualname__ == "_alert"
  183. assert spec.args[0][0].equals(Var.create_safe("message"))
  184. assert spec.args[0][1].equals(Var.create_safe("message"))
  185. assert format.format_event(spec) == 'Event("_alert", {message:`message`})'
  186. spec = event.window_alert(Var.create_safe("message"))
  187. assert format.format_event(spec) == 'Event("_alert", {message:message})'
  188. def test_set_focus():
  189. """Test the event set focus function."""
  190. spec = event.set_focus("input1")
  191. assert isinstance(spec, EventSpec)
  192. assert spec.handler.fn.__qualname__ == "_set_focus"
  193. assert spec.args[0][0].equals(Var.create_safe("ref"))
  194. assert spec.args[0][1].equals(Var.create_safe("ref_input1"))
  195. assert format.format_event(spec) == 'Event("_set_focus", {ref:`ref_input1`})'
  196. spec = event.set_focus("input1")
  197. assert format.format_event(spec) == 'Event("_set_focus", {ref:`ref_input1`})'
  198. def test_set_value():
  199. """Test the event window alert function."""
  200. spec = event.set_value("input1", "")
  201. assert isinstance(spec, EventSpec)
  202. assert spec.handler.fn.__qualname__ == "_set_value"
  203. assert spec.args[0][0].equals(Var.create_safe("ref"))
  204. assert spec.args[0][1].equals(Var.create_safe("ref_input1"))
  205. assert spec.args[1][0].equals(Var.create_safe("value"))
  206. assert spec.args[1][1].equals(Var.create_safe(""))
  207. assert (
  208. format.format_event(spec) == 'Event("_set_value", {ref:`ref_input1`,value:``})'
  209. )
  210. spec = event.set_value("input1", Var.create_safe("message"))
  211. assert (
  212. format.format_event(spec)
  213. == 'Event("_set_value", {ref:`ref_input1`,value:message})'
  214. )
  215. def test_remove_cookie():
  216. """Test the event remove_cookie."""
  217. spec = event.remove_cookie("testkey")
  218. assert isinstance(spec, EventSpec)
  219. assert spec.handler.fn.__qualname__ == "_remove_cookie"
  220. assert spec.args[0][0].equals(Var.create_safe("key"))
  221. assert spec.args[0][1].equals(Var.create_safe("testkey"))
  222. assert spec.args[1][0].equals(Var.create_safe("options"))
  223. assert spec.args[1][1].equals(Var.create_safe({"path": "/"}))
  224. assert (
  225. format.format_event(spec)
  226. == 'Event("_remove_cookie", {key:`testkey`,options:{"path": "/"}})'
  227. )
  228. def test_remove_cookie_with_options():
  229. """Test the event remove_cookie with options."""
  230. options = {
  231. "path": "/foo",
  232. "domain": "example.com",
  233. "secure": True,
  234. "sameSite": "strict",
  235. }
  236. spec = event.remove_cookie("testkey", options)
  237. assert isinstance(spec, EventSpec)
  238. assert spec.handler.fn.__qualname__ == "_remove_cookie"
  239. assert spec.args[0][0].equals(Var.create_safe("key"))
  240. assert spec.args[0][1].equals(Var.create_safe("testkey"))
  241. assert spec.args[1][0].equals(Var.create_safe("options"))
  242. assert spec.args[1][1].equals(Var.create_safe(options))
  243. assert (
  244. format.format_event(spec)
  245. == f'Event("_remove_cookie", {{key:`testkey`,options:{json.dumps(options)}}})'
  246. )
  247. def test_clear_local_storage():
  248. """Test the event clear_local_storage."""
  249. spec = event.clear_local_storage()
  250. assert isinstance(spec, EventSpec)
  251. assert spec.handler.fn.__qualname__ == "_clear_local_storage"
  252. assert not spec.args
  253. assert format.format_event(spec) == 'Event("_clear_local_storage", {})'
  254. def test_remove_local_storage():
  255. """Test the event remove_local_storage."""
  256. spec = event.remove_local_storage("testkey")
  257. assert isinstance(spec, EventSpec)
  258. assert spec.handler.fn.__qualname__ == "_remove_local_storage"
  259. assert spec.args[0][0].equals(Var.create_safe("key"))
  260. assert spec.args[0][1].equals(Var.create_safe("testkey"))
  261. assert (
  262. format.format_event(spec) == 'Event("_remove_local_storage", {key:`testkey`})'
  263. )
  264. def test_event_actions():
  265. """Test DOM event actions, like stopPropagation and preventDefault."""
  266. # EventHandler
  267. handler = EventHandler(fn=lambda: None)
  268. assert not handler.event_actions
  269. sp_handler = handler.stop_propagation
  270. assert handler is not sp_handler
  271. assert sp_handler.event_actions == {"stopPropagation": True}
  272. pd_handler = handler.prevent_default
  273. assert handler is not pd_handler
  274. assert pd_handler.event_actions == {"preventDefault": True}
  275. both_handler = sp_handler.prevent_default
  276. assert both_handler is not sp_handler
  277. assert both_handler.event_actions == {
  278. "stopPropagation": True,
  279. "preventDefault": True,
  280. }
  281. throttle_handler = handler.throttle(300)
  282. assert handler is not throttle_handler
  283. assert throttle_handler.event_actions == {"throttle": 300}
  284. debounce_handler = handler.debounce(300)
  285. assert handler is not debounce_handler
  286. assert debounce_handler.event_actions == {"debounce": 300}
  287. all_handler = handler.stop_propagation.prevent_default.throttle(200).debounce(100)
  288. assert handler is not all_handler
  289. assert all_handler.event_actions == {
  290. "stopPropagation": True,
  291. "preventDefault": True,
  292. "throttle": 200,
  293. "debounce": 100,
  294. }
  295. assert not handler.event_actions
  296. # Convert to EventSpec should carry event actions
  297. sp_handler2 = handler.stop_propagation.throttle(200)
  298. spec = sp_handler2()
  299. assert spec.event_actions == {"stopPropagation": True, "throttle": 200}
  300. assert spec.event_actions == sp_handler2.event_actions
  301. assert spec.event_actions is not sp_handler2.event_actions
  302. # But it should be a copy!
  303. assert spec.event_actions is not sp_handler2.event_actions
  304. spec2 = spec.prevent_default
  305. assert spec is not spec2
  306. assert spec2.event_actions == {
  307. "stopPropagation": True,
  308. "preventDefault": True,
  309. "throttle": 200,
  310. }
  311. assert spec2.event_actions != spec.event_actions
  312. # The original handler should still not be touched.
  313. assert not handler.event_actions
  314. def test_event_actions_on_state():
  315. class EventActionState(BaseState):
  316. def handler(self):
  317. pass
  318. handler = EventActionState.handler
  319. assert isinstance(handler, EventHandler)
  320. assert not handler.event_actions
  321. sp_handler = EventActionState.handler.stop_propagation
  322. assert sp_handler.event_actions == {"stopPropagation": True}
  323. # should NOT affect other references to the handler
  324. assert not handler.event_actions