test_event.py 13 KB

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