test_event.py 14 KB

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