1
0

test_event.py 17 KB

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