test_event.py 15 KB

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