test_component.py 61 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097
  1. from contextlib import nullcontext
  2. from typing import Any, Dict, List, Optional, Type, Union
  3. import pytest
  4. import reflex_chakra as rc
  5. from reflex_chakra.components.layout.box import Box
  6. import reflex as rx
  7. from reflex.base import Base
  8. from reflex.compiler.compiler import compile_components
  9. from reflex.components.base.bare import Bare
  10. from reflex.components.base.fragment import Fragment
  11. from reflex.components.component import (
  12. Component,
  13. CustomComponent,
  14. StatefulComponent,
  15. custom_component,
  16. )
  17. from reflex.constants import EventTriggers
  18. from reflex.event import EventChain, EventHandler, parse_args_spec
  19. from reflex.ivars.base import LiteralVar
  20. from reflex.state import BaseState
  21. from reflex.style import Style
  22. from reflex.utils import imports
  23. from reflex.utils.imports import ImportDict, ImportVar, ParsedImportDict, parse_imports
  24. from reflex.vars import BaseVar, Var, VarData
  25. @pytest.fixture
  26. def test_state():
  27. class TestState(BaseState):
  28. num: int
  29. def do_something(self):
  30. pass
  31. def do_something_arg(self, arg):
  32. pass
  33. return TestState
  34. @pytest.fixture
  35. def component1() -> Type[Component]:
  36. """A test component.
  37. Returns:
  38. A test component.
  39. """
  40. class TestComponent1(Component):
  41. # A test string prop.
  42. text: Var[str]
  43. # A test number prop.
  44. number: Var[int]
  45. # A test string/number prop.
  46. text_or_number: Var[Union[int, str]]
  47. def _get_imports(self) -> ParsedImportDict:
  48. return {"react": [ImportVar(tag="Component")]}
  49. def _get_custom_code(self) -> str:
  50. return "console.log('component1')"
  51. return TestComponent1
  52. @pytest.fixture
  53. def component2() -> Type[Component]:
  54. """A test component.
  55. Returns:
  56. A test component.
  57. """
  58. class TestComponent2(Component):
  59. # A test list prop.
  60. arr: Var[List[str]]
  61. def get_event_triggers(self) -> Dict[str, Any]:
  62. """Test controlled triggers.
  63. Returns:
  64. Test controlled triggers.
  65. """
  66. return {
  67. **super().get_event_triggers(),
  68. "on_open": lambda e0: [e0],
  69. "on_close": lambda e0: [e0],
  70. }
  71. def _get_imports(self) -> ParsedImportDict:
  72. return {"react-redux": [ImportVar(tag="connect")]}
  73. def _get_custom_code(self) -> str:
  74. return "console.log('component2')"
  75. return TestComponent2
  76. @pytest.fixture
  77. def component3() -> Type[Component]:
  78. """A test component with hook defined.
  79. Returns:
  80. A test component.
  81. """
  82. class TestComponent3(Component):
  83. def _get_hooks(self) -> str:
  84. return "const a = () => true"
  85. return TestComponent3
  86. @pytest.fixture
  87. def component4() -> Type[Component]:
  88. """A test component with hook defined.
  89. Returns:
  90. A test component.
  91. """
  92. class TestComponent4(Component):
  93. def _get_hooks(self) -> str:
  94. return "const b = () => false"
  95. return TestComponent4
  96. @pytest.fixture
  97. def component5() -> Type[Component]:
  98. """A test component.
  99. Returns:
  100. A test component.
  101. """
  102. class TestComponent5(Component):
  103. tag = "RandomComponent"
  104. _invalid_children: List[str] = ["Text"]
  105. _valid_children: List[str] = ["Text"]
  106. _valid_parents: List[str] = ["Text"]
  107. return TestComponent5
  108. @pytest.fixture
  109. def component6() -> Type[Component]:
  110. """A test component.
  111. Returns:
  112. A test component.
  113. """
  114. class TestComponent6(Component):
  115. tag = "RandomComponent"
  116. _invalid_children: List[str] = ["Text"]
  117. return TestComponent6
  118. @pytest.fixture
  119. def component7() -> Type[Component]:
  120. """A test component.
  121. Returns:
  122. A test component.
  123. """
  124. class TestComponent7(Component):
  125. tag = "RandomComponent"
  126. _valid_children: List[str] = ["Text"]
  127. return TestComponent7
  128. @pytest.fixture
  129. def on_click1() -> EventHandler:
  130. """A sample on click function.
  131. Returns:
  132. A sample on click function.
  133. """
  134. def on_click1():
  135. pass
  136. return EventHandler(fn=on_click1)
  137. @pytest.fixture
  138. def on_click2() -> EventHandler:
  139. """A sample on click function.
  140. Returns:
  141. A sample on click function.
  142. """
  143. def on_click2():
  144. pass
  145. return EventHandler(fn=on_click2)
  146. @pytest.fixture
  147. def my_component():
  148. """A test component function.
  149. Returns:
  150. A test component function.
  151. """
  152. def my_component(prop1: Var[str], prop2: Var[int]):
  153. return Box.create(prop1, prop2)
  154. return my_component
  155. def test_set_style_attrs(component1):
  156. """Test that style attributes are set in the dict.
  157. Args:
  158. component1: A test component.
  159. """
  160. component = component1(color="white", text_align="center")
  161. assert str(component.style["color"]) == '"white"'
  162. assert str(component.style["textAlign"]) == '"center"'
  163. def test_custom_attrs(component1):
  164. """Test that custom attributes are set in the dict.
  165. Args:
  166. component1: A test component.
  167. """
  168. component = component1(custom_attrs={"attr1": "1", "attr2": "attr2"})
  169. assert component.custom_attrs == {"attr1": "1", "attr2": "attr2"}
  170. def test_create_component(component1):
  171. """Test that the component is created correctly.
  172. Args:
  173. component1: A test component.
  174. """
  175. children = [component1() for _ in range(3)]
  176. attrs = {"color": "white", "text_align": "center"}
  177. c = component1.create(*children, **attrs)
  178. assert isinstance(c, component1)
  179. assert c.children == children
  180. assert (
  181. str(LiteralVar.create(c.style))
  182. == '({ ["color"] : "white", ["textAlign"] : "center" })'
  183. )
  184. @pytest.mark.parametrize(
  185. "prop_name,var,expected",
  186. [
  187. pytest.param(
  188. "text",
  189. Var.create("hello"),
  190. None,
  191. id="text",
  192. ),
  193. pytest.param(
  194. "text",
  195. BaseVar(_var_name="hello", _var_type=Optional[str]),
  196. None,
  197. id="text-optional",
  198. ),
  199. pytest.param(
  200. "text",
  201. BaseVar(_var_name="hello", _var_type=Union[str, None]),
  202. None,
  203. id="text-union-str-none",
  204. ),
  205. pytest.param(
  206. "text",
  207. BaseVar(_var_name="hello", _var_type=Union[None, str]),
  208. None,
  209. id="text-union-none-str",
  210. ),
  211. pytest.param(
  212. "text",
  213. Var.create(1),
  214. TypeError,
  215. id="text-int",
  216. ),
  217. pytest.param(
  218. "number",
  219. Var.create(1),
  220. None,
  221. id="number",
  222. ),
  223. pytest.param(
  224. "number",
  225. BaseVar(_var_name="1", _var_type=Optional[int]),
  226. None,
  227. id="number-optional",
  228. ),
  229. pytest.param(
  230. "number",
  231. BaseVar(_var_name="1", _var_type=Union[int, None]),
  232. None,
  233. id="number-union-int-none",
  234. ),
  235. pytest.param(
  236. "number",
  237. BaseVar(_var_name="1", _var_type=Union[None, int]),
  238. None,
  239. id="number-union-none-int",
  240. ),
  241. pytest.param(
  242. "number",
  243. Var.create("1"),
  244. TypeError,
  245. id="number-str",
  246. ),
  247. pytest.param(
  248. "text_or_number",
  249. Var.create("hello"),
  250. None,
  251. id="text_or_number-str",
  252. ),
  253. pytest.param(
  254. "text_or_number",
  255. Var.create(1),
  256. None,
  257. id="text_or_number-int",
  258. ),
  259. pytest.param(
  260. "text_or_number",
  261. BaseVar(_var_name="hello", _var_type=Optional[str]),
  262. None,
  263. id="text_or_number-optional-str",
  264. ),
  265. pytest.param(
  266. "text_or_number",
  267. BaseVar(_var_name="hello", _var_type=Union[str, None]),
  268. None,
  269. id="text_or_number-union-str-none",
  270. ),
  271. pytest.param(
  272. "text_or_number",
  273. BaseVar(_var_name="hello", _var_type=Union[None, str]),
  274. None,
  275. id="text_or_number-union-none-str",
  276. ),
  277. pytest.param(
  278. "text_or_number",
  279. BaseVar(_var_name="1", _var_type=Optional[int]),
  280. None,
  281. id="text_or_number-optional-int",
  282. ),
  283. pytest.param(
  284. "text_or_number",
  285. BaseVar(_var_name="1", _var_type=Union[int, None]),
  286. None,
  287. id="text_or_number-union-int-none",
  288. ),
  289. pytest.param(
  290. "text_or_number",
  291. BaseVar(_var_name="1", _var_type=Union[None, int]),
  292. None,
  293. id="text_or_number-union-none-int",
  294. ),
  295. pytest.param(
  296. "text_or_number",
  297. Var.create(1.0),
  298. TypeError,
  299. id="text_or_number-float",
  300. ),
  301. pytest.param(
  302. "text_or_number",
  303. BaseVar(_var_name="hello", _var_type=Optional[Union[str, int]]),
  304. None,
  305. id="text_or_number-optional-union-str-int",
  306. ),
  307. ],
  308. )
  309. def test_create_component_prop_validation(
  310. component1: Type[Component],
  311. prop_name: str,
  312. var: Union[Var, str, int],
  313. expected: Type[Exception],
  314. ):
  315. """Test that component props are validated correctly.
  316. Args:
  317. component1: A test component.
  318. prop_name: The name of the prop.
  319. var: The value of the prop.
  320. expected: The expected exception.
  321. """
  322. ctx = pytest.raises(expected) if expected else nullcontext()
  323. kwargs = {prop_name: var}
  324. with ctx:
  325. c = component1.create(**kwargs)
  326. assert isinstance(c, component1)
  327. assert c.children == []
  328. assert c.style == {}
  329. def test_add_style(component1, component2):
  330. """Test adding a style to a component.
  331. Args:
  332. component1: A test component.
  333. component2: A test component.
  334. """
  335. style = {
  336. component1: Style({"color": "white"}),
  337. component2: Style({"color": "black"}),
  338. }
  339. c1 = component1()._add_style_recursive(style) # type: ignore
  340. c2 = component2()._add_style_recursive(style) # type: ignore
  341. assert str(c1.style["color"]) == '"white"'
  342. assert str(c2.style["color"]) == '"black"'
  343. def test_add_style_create(component1, component2):
  344. """Test that adding style works with the create method.
  345. Args:
  346. component1: A test component.
  347. component2: A test component.
  348. """
  349. style = {
  350. component1.create: Style({"color": "white"}),
  351. component2.create: Style({"color": "black"}),
  352. }
  353. c1 = component1()._add_style_recursive(style) # type: ignore
  354. c2 = component2()._add_style_recursive(style) # type: ignore
  355. assert str(c1.style["color"]) == '"white"'
  356. assert str(c2.style["color"]) == '"black"'
  357. def test_get_imports(component1, component2):
  358. """Test getting the imports of a component.
  359. Args:
  360. component1: A test component.
  361. component2: A test component.
  362. """
  363. c1 = component1.create()
  364. c2 = component2.create(c1)
  365. assert c1._get_all_imports() == {"react": [ImportVar(tag="Component")]}
  366. assert c2._get_all_imports() == {
  367. "react-redux": [ImportVar(tag="connect")],
  368. "react": [ImportVar(tag="Component")],
  369. }
  370. def test_get_custom_code(component1, component2):
  371. """Test getting the custom code of a component.
  372. Args:
  373. component1: A test component.
  374. component2: A test component.
  375. """
  376. # Check that the code gets compiled correctly.
  377. c1 = component1.create()
  378. c2 = component2.create()
  379. assert c1._get_all_custom_code() == {"console.log('component1')"}
  380. assert c2._get_all_custom_code() == {"console.log('component2')"}
  381. # Check that nesting components compiles both codes.
  382. c1 = component1.create(c2)
  383. assert c1._get_all_custom_code() == {
  384. "console.log('component1')",
  385. "console.log('component2')",
  386. }
  387. # Check that code is not duplicated.
  388. c1 = component1.create(c2, c2, c1, c1)
  389. assert c1._get_all_custom_code() == {
  390. "console.log('component1')",
  391. "console.log('component2')",
  392. }
  393. def test_get_props(component1, component2):
  394. """Test that the props are set correctly.
  395. Args:
  396. component1: A test component.
  397. component2: A test component.
  398. """
  399. assert component1.get_props() == {"text", "number", "text_or_number"}
  400. assert component2.get_props() == {"arr"}
  401. @pytest.mark.parametrize(
  402. "text,number",
  403. [
  404. ("", 0),
  405. ("test", 1),
  406. ("hi", -13),
  407. ],
  408. )
  409. def test_valid_props(component1, text: str, number: int):
  410. """Test that we can construct a component with valid props.
  411. Args:
  412. component1: A test component.
  413. text: A test string.
  414. number: A test number.
  415. """
  416. c = component1.create(text=text, number=number)
  417. assert c.text._decode() == text
  418. assert c.number._decode() == number
  419. @pytest.mark.parametrize(
  420. "text,number", [("", "bad_string"), (13, 1), ("test", [1, 2, 3])]
  421. )
  422. def test_invalid_prop_type(component1, text: str, number: int):
  423. """Test that an invalid prop type raises an error.
  424. Args:
  425. component1: A test component.
  426. text: A test string.
  427. number: A test number.
  428. """
  429. # Check that
  430. with pytest.raises(TypeError):
  431. component1.create(text=text, number=number)
  432. def test_var_props(component1, test_state):
  433. """Test that we can set a Var prop.
  434. Args:
  435. component1: A test component.
  436. test_state: A test state.
  437. """
  438. c1 = component1.create(text="hello", number=test_state.num)
  439. assert c1.number.equals(test_state.num)
  440. def test_get_event_triggers(component1, component2):
  441. """Test that we can get the triggers of a component.
  442. Args:
  443. component1: A test component.
  444. component2: A test component.
  445. """
  446. default_triggers = {
  447. EventTriggers.ON_FOCUS,
  448. EventTriggers.ON_BLUR,
  449. EventTriggers.ON_CLICK,
  450. EventTriggers.ON_CONTEXT_MENU,
  451. EventTriggers.ON_DOUBLE_CLICK,
  452. EventTriggers.ON_MOUSE_DOWN,
  453. EventTriggers.ON_MOUSE_ENTER,
  454. EventTriggers.ON_MOUSE_LEAVE,
  455. EventTriggers.ON_MOUSE_MOVE,
  456. EventTriggers.ON_MOUSE_OUT,
  457. EventTriggers.ON_MOUSE_OVER,
  458. EventTriggers.ON_MOUSE_UP,
  459. EventTriggers.ON_SCROLL,
  460. EventTriggers.ON_MOUNT,
  461. EventTriggers.ON_UNMOUNT,
  462. }
  463. assert component1().get_event_triggers().keys() == default_triggers
  464. assert (
  465. component2().get_event_triggers().keys()
  466. == {"on_open", "on_close"} | default_triggers
  467. )
  468. @pytest.fixture
  469. def test_component() -> Type[Component]:
  470. """A test component.
  471. Returns:
  472. A test component.
  473. """
  474. class TestComponent(Component):
  475. pass
  476. return TestComponent
  477. # Write a test case to check if the create method filters out None props
  478. def test_create_filters_none_props(test_component):
  479. child1 = test_component()
  480. child2 = test_component()
  481. props = {
  482. "prop1": "value1",
  483. "prop2": None,
  484. "prop3": "value3",
  485. "prop4": None,
  486. "style": {"color": "white", "text-align": "center"}, # Adding a style prop
  487. }
  488. component = test_component.create(child1, child2, **props)
  489. # Assert that None props are not present in the component's props
  490. assert "prop2" not in component.get_props()
  491. assert "prop4" not in component.get_props()
  492. # Assert that the style prop is present in the component's props
  493. assert str(component.style["color"]) == '"white"'
  494. assert str(component.style["text-align"]) == '"center"'
  495. @pytest.mark.parametrize("children", [((None,),), ("foo", ("bar", (None,)))])
  496. def test_component_create_unallowed_types(children, test_component):
  497. with pytest.raises(TypeError) as err:
  498. test_component.create(*children)
  499. assert (
  500. err.value.args[0]
  501. == "Children of Reflex components must be other components, state vars, or primitive Python types. Got child None of type <class 'NoneType'>."
  502. )
  503. @pytest.mark.parametrize(
  504. "element, expected",
  505. [
  506. (
  507. (rx.text("first_text"),),
  508. {
  509. "name": "Fragment",
  510. "props": [],
  511. "contents": "",
  512. "args": None,
  513. "special_props": set(),
  514. "children": [
  515. {
  516. "name": "RadixThemesText",
  517. "props": ['as={"p"}'],
  518. "contents": "",
  519. "args": None,
  520. "special_props": set(),
  521. "children": [
  522. {
  523. "name": "",
  524. "props": [],
  525. "contents": '{"first_text"}',
  526. "args": None,
  527. "special_props": set(),
  528. "children": [],
  529. "autofocus": False,
  530. }
  531. ],
  532. "autofocus": False,
  533. }
  534. ],
  535. "autofocus": False,
  536. },
  537. ),
  538. (
  539. (rx.text("first_text"), rx.text("second_text")),
  540. {
  541. "args": None,
  542. "autofocus": False,
  543. "children": [
  544. {
  545. "args": None,
  546. "autofocus": False,
  547. "children": [
  548. {
  549. "args": None,
  550. "autofocus": False,
  551. "children": [],
  552. "contents": '{"first_text"}',
  553. "name": "",
  554. "props": [],
  555. "special_props": set(),
  556. }
  557. ],
  558. "contents": "",
  559. "name": "RadixThemesText",
  560. "props": ['as={"p"}'],
  561. "special_props": set(),
  562. },
  563. {
  564. "args": None,
  565. "autofocus": False,
  566. "children": [
  567. {
  568. "args": None,
  569. "autofocus": False,
  570. "children": [],
  571. "contents": '{"second_text"}',
  572. "name": "",
  573. "props": [],
  574. "special_props": set(),
  575. }
  576. ],
  577. "contents": "",
  578. "name": "RadixThemesText",
  579. "props": ['as={"p"}'],
  580. "special_props": set(),
  581. },
  582. ],
  583. "contents": "",
  584. "name": "Fragment",
  585. "props": [],
  586. "special_props": set(),
  587. },
  588. ),
  589. (
  590. (rx.text("first_text"), rx.box((rx.text("second_text"),))),
  591. {
  592. "args": None,
  593. "autofocus": False,
  594. "children": [
  595. {
  596. "args": None,
  597. "autofocus": False,
  598. "children": [
  599. {
  600. "args": None,
  601. "autofocus": False,
  602. "children": [],
  603. "contents": '{"first_text"}',
  604. "name": "",
  605. "props": [],
  606. "special_props": set(),
  607. }
  608. ],
  609. "contents": "",
  610. "name": "RadixThemesText",
  611. "props": ['as={"p"}'],
  612. "special_props": set(),
  613. },
  614. {
  615. "args": None,
  616. "autofocus": False,
  617. "children": [
  618. {
  619. "args": None,
  620. "autofocus": False,
  621. "children": [
  622. {
  623. "args": None,
  624. "autofocus": False,
  625. "children": [
  626. {
  627. "args": None,
  628. "autofocus": False,
  629. "children": [],
  630. "contents": '{"second_text"}',
  631. "name": "",
  632. "props": [],
  633. "special_props": set(),
  634. }
  635. ],
  636. "contents": "",
  637. "name": "RadixThemesText",
  638. "props": ['as={"p"}'],
  639. "special_props": set(),
  640. }
  641. ],
  642. "contents": "",
  643. "name": "Fragment",
  644. "props": [],
  645. "special_props": set(),
  646. }
  647. ],
  648. "contents": "",
  649. "name": "RadixThemesBox",
  650. "props": [],
  651. "special_props": set(),
  652. },
  653. ],
  654. "contents": "",
  655. "name": "Fragment",
  656. "props": [],
  657. "special_props": set(),
  658. },
  659. ),
  660. ],
  661. )
  662. def test_component_create_unpack_tuple_child(test_component, element, expected):
  663. """Test that component in tuples are unwrapped into an rx.Fragment.
  664. Args:
  665. test_component: Component fixture.
  666. element: The children to pass to the component.
  667. expected: The expected render dict.
  668. """
  669. comp = test_component.create(element)
  670. assert len(comp.children) == 1
  671. assert isinstance((fragment_wrapper := comp.children[0]), Fragment)
  672. assert fragment_wrapper.render() == expected
  673. class C1State(BaseState):
  674. """State for testing C1 component."""
  675. def mock_handler(self, _e, _bravo, _charlie):
  676. """Mock handler."""
  677. pass
  678. def test_component_event_trigger_arbitrary_args():
  679. """Test that we can define arbitrary types for the args of an event trigger."""
  680. class Obj(Base):
  681. custom: int = 0
  682. def on_foo_spec(_e, alpha: str, bravo: Dict[str, Any], charlie: Obj):
  683. return [_e.target.value, bravo["nested"], charlie.custom + 42]
  684. class C1(Component):
  685. library = "/local"
  686. tag = "C1"
  687. def get_event_triggers(self) -> Dict[str, Any]:
  688. return {
  689. **super().get_event_triggers(),
  690. "on_foo": on_foo_spec,
  691. }
  692. comp = C1.create(on_foo=C1State.mock_handler)
  693. assert comp.render()["props"][0] == (
  694. "onFoo={((__e, _alpha, _bravo, _charlie) => ((addEvents("
  695. f'[(Event("{C1State.get_full_name()}.mock_handler", ({{ ["_e"] : __e["target"]["value"], ["_bravo"] : _bravo["nested"], ["_charlie"] : (_charlie["custom"] + 42) }})))], '
  696. "[__e, _alpha, _bravo, _charlie], ({ })))))}"
  697. )
  698. def test_create_custom_component(my_component):
  699. """Test that we can create a custom component.
  700. Args:
  701. my_component: A test custom component.
  702. """
  703. component = CustomComponent(component_fn=my_component, prop1="test", prop2=1)
  704. assert component.tag == "MyComponent"
  705. assert component.get_props() == set()
  706. assert component._get_all_custom_components() == {component}
  707. def test_custom_component_hash(my_component):
  708. """Test that the hash of a custom component is correct.
  709. Args:
  710. my_component: A test custom component.
  711. """
  712. component1 = CustomComponent(component_fn=my_component, prop1="test", prop2=1)
  713. component2 = CustomComponent(component_fn=my_component, prop1="test", prop2=2)
  714. assert {component1, component2} == {component1}
  715. def test_custom_component_wrapper():
  716. """Test that the wrapper of a custom component is correct."""
  717. @custom_component
  718. def my_component(width: Var[int], color: Var[str]):
  719. return rx.box(
  720. width=width,
  721. color=color,
  722. )
  723. from reflex.components.radix.themes.typography.text import Text
  724. ccomponent = my_component(
  725. rx.text("child"), width=Var.create(1), color=Var.create("red")
  726. )
  727. assert isinstance(ccomponent, CustomComponent)
  728. assert len(ccomponent.children) == 1
  729. assert isinstance(ccomponent.children[0], Text)
  730. component = ccomponent.get_component(ccomponent)
  731. assert isinstance(component, Box)
  732. def test_invalid_event_handler_args(component2, test_state):
  733. """Test that an invalid event handler raises an error.
  734. Args:
  735. component2: A test component.
  736. test_state: A test state.
  737. """
  738. # Uncontrolled event handlers should not take args.
  739. # This is okay.
  740. component2.create(on_click=test_state.do_something)
  741. # This is not okay.
  742. with pytest.raises(ValueError):
  743. component2.create(on_click=test_state.do_something_arg)
  744. component2.create(on_open=test_state.do_something)
  745. component2.create(
  746. on_open=[test_state.do_something_arg, test_state.do_something]
  747. )
  748. # However lambdas are okay.
  749. component2.create(on_click=lambda: test_state.do_something_arg(1))
  750. component2.create(
  751. on_click=lambda: [test_state.do_something_arg(1), test_state.do_something]
  752. )
  753. component2.create(
  754. on_click=lambda: [test_state.do_something_arg(1), test_state.do_something()]
  755. )
  756. # Controlled event handlers should take args.
  757. # This is okay.
  758. component2.create(on_open=test_state.do_something_arg)
  759. def test_get_hooks_nested(component1, component2, component3):
  760. """Test that a component returns hooks from child components.
  761. Args:
  762. component1: test component.
  763. component2: another component.
  764. component3: component with hooks defined.
  765. """
  766. c = component1.create(
  767. component2.create(arr=[]),
  768. component3.create(),
  769. component3.create(),
  770. component3.create(),
  771. text="a",
  772. number=1,
  773. )
  774. assert c._get_all_hooks() == component3()._get_all_hooks()
  775. def test_get_hooks_nested2(component3, component4):
  776. """Test that a component returns both when parent and child have hooks.
  777. Args:
  778. component3: component with hooks defined.
  779. component4: component with different hooks defined.
  780. """
  781. exp_hooks = {**component3()._get_all_hooks(), **component4()._get_all_hooks()}
  782. assert component3.create(component4.create())._get_all_hooks() == exp_hooks
  783. assert component4.create(component3.create())._get_all_hooks() == exp_hooks
  784. assert (
  785. component4.create(
  786. component3.create(),
  787. component4.create(),
  788. component3.create(),
  789. )._get_all_hooks()
  790. == exp_hooks
  791. )
  792. @pytest.mark.parametrize("fixture", ["component5", "component6"])
  793. def test_unsupported_child_components(fixture, request):
  794. """Test that a value error is raised when an unsupported component (a child component found in the
  795. component's invalid children list) is provided as a child.
  796. Args:
  797. fixture: the test component as a fixture.
  798. request: Pytest request.
  799. """
  800. component = request.getfixturevalue(fixture)
  801. with pytest.raises(ValueError) as err:
  802. comp = component.create(rx.text("testing component"))
  803. comp.render()
  804. assert (
  805. err.value.args[0]
  806. == f"The component `{component.__name__}` cannot have `Text` as a child component"
  807. )
  808. def test_unsupported_parent_components(component5):
  809. """Test that a value error is raised when an component is not in _valid_parents of one of its children.
  810. Args:
  811. component5: component with valid parent of "Text" only
  812. """
  813. with pytest.raises(ValueError) as err:
  814. rx.box(component5.create())
  815. assert (
  816. err.value.args[0]
  817. == f"The component `{component5.__name__}` can only be a child of the components: `{component5._valid_parents[0]}`. Got `Box` instead."
  818. )
  819. @pytest.mark.parametrize("fixture", ["component5", "component7"])
  820. def test_component_with_only_valid_children(fixture, request):
  821. """Test that a value error is raised when an unsupported component (a child component not found in the
  822. component's valid children list) is provided as a child.
  823. Args:
  824. fixture: the test component as a fixture.
  825. request: Pytest request.
  826. """
  827. component = request.getfixturevalue(fixture)
  828. with pytest.raises(ValueError) as err:
  829. comp = component.create(rx.box("testing component"))
  830. comp.render()
  831. assert (
  832. err.value.args[0]
  833. == f"The component `{component.__name__}` only allows the components: `Text` as children. "
  834. f"Got `Box` instead."
  835. )
  836. @pytest.mark.parametrize(
  837. "component,rendered",
  838. [
  839. (rx.text("hi"), '<RadixThemesText as={"p"}>\n {"hi"}\n</RadixThemesText>'),
  840. (
  841. rx.box(rc.heading("test", size="md")),
  842. '<RadixThemesBox>\n <Heading size={"md"}>\n {"test"}\n</Heading>\n</RadixThemesBox>',
  843. ),
  844. ],
  845. )
  846. def test_format_component(component, rendered):
  847. """Test that a component is formatted correctly.
  848. Args:
  849. component: The component to format.
  850. rendered: The expected rendered component.
  851. """
  852. assert str(component) == rendered
  853. def test_stateful_component(test_state):
  854. """Test that a stateful component is created correctly.
  855. Args:
  856. test_state: A test state.
  857. """
  858. text_component = rx.text(test_state.num)
  859. stateful_component = StatefulComponent.compile_from(text_component)
  860. assert isinstance(stateful_component, StatefulComponent)
  861. assert stateful_component.tag is not None
  862. assert stateful_component.tag.startswith("Text_")
  863. assert stateful_component.references == 1
  864. sc2 = StatefulComponent.compile_from(rx.text(test_state.num))
  865. assert isinstance(sc2, StatefulComponent)
  866. assert stateful_component.references == 2
  867. assert sc2.references == 2
  868. def test_stateful_component_memoize_event_trigger(test_state):
  869. """Test that a stateful component is created correctly with events.
  870. Args:
  871. test_state: A test state.
  872. """
  873. button_component = rx.button("Click me", on_click=test_state.do_something)
  874. stateful_component = StatefulComponent.compile_from(button_component)
  875. assert isinstance(stateful_component, StatefulComponent)
  876. # No event trigger? No StatefulComponent
  877. assert not isinstance(
  878. StatefulComponent.compile_from(rx.button("Click me")), StatefulComponent
  879. )
  880. def test_stateful_banner():
  881. """Test that a stateful component is created correctly with events."""
  882. connection_modal_component = rx.connection_modal()
  883. stateful_component = StatefulComponent.compile_from(connection_modal_component)
  884. assert isinstance(stateful_component, StatefulComponent)
  885. TEST_VAR = LiteralVar.create("test")._replace(
  886. merge_var_data=VarData(
  887. hooks={"useTest": None},
  888. imports={"test": [ImportVar(tag="test")]},
  889. state="Test",
  890. interpolations=[],
  891. )
  892. )
  893. FORMATTED_TEST_VAR = LiteralVar.create(f"foo{TEST_VAR}bar")
  894. STYLE_VAR = TEST_VAR._replace(_var_name="style")
  895. EVENT_CHAIN_VAR = TEST_VAR._replace(_var_type=EventChain)
  896. ARG_VAR = Var.create("arg")
  897. TEST_VAR_DICT_OF_DICT = LiteralVar.create({"a": {"b": "test"}})._replace(
  898. merge_var_data=TEST_VAR._var_data
  899. )
  900. FORMATTED_TEST_VAR_DICT_OF_DICT = LiteralVar.create(
  901. {"a": {"b": f"footestbar"}}
  902. )._replace(merge_var_data=TEST_VAR._var_data)
  903. TEST_VAR_LIST_OF_LIST = LiteralVar.create([["test"]])._replace(
  904. merge_var_data=TEST_VAR._var_data
  905. )
  906. FORMATTED_TEST_VAR_LIST_OF_LIST = LiteralVar.create([["footestbar"]])._replace(
  907. merge_var_data=TEST_VAR._var_data
  908. )
  909. TEST_VAR_LIST_OF_LIST_OF_LIST = LiteralVar.create([[["test"]]])._replace(
  910. merge_var_data=TEST_VAR._var_data
  911. )
  912. FORMATTED_TEST_VAR_LIST_OF_LIST_OF_LIST = LiteralVar.create(
  913. [[["footestbar"]]]
  914. )._replace(merge_var_data=TEST_VAR._var_data)
  915. TEST_VAR_LIST_OF_DICT = LiteralVar.create([{"a": "test"}])._replace(
  916. merge_var_data=TEST_VAR._var_data
  917. )
  918. FORMATTED_TEST_VAR_LIST_OF_DICT = LiteralVar.create([{"a": "footestbar"}])._replace(
  919. merge_var_data=TEST_VAR._var_data
  920. )
  921. class ComponentNestedVar(Component):
  922. """A component with nested Var types."""
  923. dict_of_dict: Var[Dict[str, Dict[str, str]]]
  924. list_of_list: Var[List[List[str]]]
  925. list_of_list_of_list: Var[List[List[List[str]]]]
  926. list_of_dict: Var[List[Dict[str, str]]]
  927. class EventState(rx.State):
  928. """State for testing event handlers with _get_vars."""
  929. v: int = 42
  930. def handler(self):
  931. """A handler that does nothing."""
  932. def handler2(self, arg):
  933. """A handler that takes an arg.
  934. Args:
  935. arg: An arg.
  936. """
  937. @pytest.mark.parametrize(
  938. ("component", "exp_vars"),
  939. (
  940. pytest.param(
  941. Bare.create(TEST_VAR),
  942. [TEST_VAR],
  943. id="direct-bare",
  944. ),
  945. pytest.param(
  946. Bare.create(f"foo{TEST_VAR}bar"),
  947. [FORMATTED_TEST_VAR],
  948. id="fstring-bare",
  949. ),
  950. pytest.param(
  951. rx.text(as_=TEST_VAR),
  952. [TEST_VAR],
  953. id="direct-prop",
  954. ),
  955. pytest.param(
  956. rx.heading(as_=f"foo{TEST_VAR}bar"),
  957. [FORMATTED_TEST_VAR],
  958. id="fstring-prop",
  959. ),
  960. pytest.param(
  961. rx.fragment(id=TEST_VAR),
  962. [TEST_VAR],
  963. id="direct-id",
  964. ),
  965. pytest.param(
  966. rx.fragment(id=f"foo{TEST_VAR}bar"),
  967. [FORMATTED_TEST_VAR],
  968. id="fstring-id",
  969. ),
  970. pytest.param(
  971. rx.fragment(key=TEST_VAR),
  972. [TEST_VAR],
  973. id="direct-key",
  974. ),
  975. pytest.param(
  976. rx.fragment(key=f"foo{TEST_VAR}bar"),
  977. [FORMATTED_TEST_VAR],
  978. id="fstring-key",
  979. ),
  980. pytest.param(
  981. rx.fragment(class_name=TEST_VAR),
  982. [TEST_VAR],
  983. id="direct-class_name",
  984. ),
  985. pytest.param(
  986. rx.fragment(class_name=f"foo{TEST_VAR}bar"),
  987. [FORMATTED_TEST_VAR],
  988. id="fstring-class_name",
  989. ),
  990. pytest.param(
  991. rx.fragment(special_props={TEST_VAR}),
  992. [TEST_VAR],
  993. id="direct-special_props",
  994. ),
  995. pytest.param(
  996. rx.fragment(special_props={LiteralVar.create(f"foo{TEST_VAR}bar")}),
  997. [FORMATTED_TEST_VAR],
  998. id="fstring-special_props",
  999. ),
  1000. pytest.param(
  1001. # custom_attrs cannot accept a Var directly as a value
  1002. rx.fragment(custom_attrs={"href": f"{TEST_VAR}"}),
  1003. [TEST_VAR],
  1004. id="fstring-custom_attrs-nofmt",
  1005. ),
  1006. pytest.param(
  1007. rx.fragment(custom_attrs={"href": f"foo{TEST_VAR}bar"}),
  1008. [FORMATTED_TEST_VAR],
  1009. id="fstring-custom_attrs",
  1010. ),
  1011. pytest.param(
  1012. rx.fragment(background_color=TEST_VAR),
  1013. [STYLE_VAR],
  1014. id="direct-background_color",
  1015. ),
  1016. pytest.param(
  1017. rx.fragment(background_color=f"foo{TEST_VAR}bar"),
  1018. [STYLE_VAR],
  1019. id="fstring-background_color",
  1020. ),
  1021. pytest.param(
  1022. rx.fragment(style={"background_color": TEST_VAR}), # type: ignore
  1023. [STYLE_VAR],
  1024. id="direct-style-background_color",
  1025. ),
  1026. pytest.param(
  1027. rx.fragment(style={"background_color": f"foo{TEST_VAR}bar"}), # type: ignore
  1028. [STYLE_VAR],
  1029. id="fstring-style-background_color",
  1030. ),
  1031. pytest.param(
  1032. rx.fragment(on_click=EVENT_CHAIN_VAR), # type: ignore
  1033. [EVENT_CHAIN_VAR],
  1034. id="direct-event-chain",
  1035. ),
  1036. pytest.param(
  1037. rx.fragment(on_click=EventState.handler),
  1038. [],
  1039. id="direct-event-handler",
  1040. ),
  1041. pytest.param(
  1042. rx.fragment(on_click=EventState.handler2(TEST_VAR)), # type: ignore
  1043. [ARG_VAR, TEST_VAR],
  1044. id="direct-event-handler-arg",
  1045. ),
  1046. pytest.param(
  1047. rx.fragment(on_click=EventState.handler2(EventState.v)), # type: ignore
  1048. [ARG_VAR, EventState.v],
  1049. id="direct-event-handler-arg2",
  1050. ),
  1051. pytest.param(
  1052. rx.fragment(on_click=lambda: EventState.handler2(TEST_VAR)), # type: ignore
  1053. [ARG_VAR, TEST_VAR],
  1054. id="direct-event-handler-lambda",
  1055. ),
  1056. pytest.param(
  1057. ComponentNestedVar.create(dict_of_dict={"a": {"b": TEST_VAR}}),
  1058. [TEST_VAR_DICT_OF_DICT],
  1059. id="direct-dict_of_dict",
  1060. ),
  1061. pytest.param(
  1062. ComponentNestedVar.create(dict_of_dict={"a": {"b": f"foo{TEST_VAR}bar"}}),
  1063. [FORMATTED_TEST_VAR_DICT_OF_DICT],
  1064. id="fstring-dict_of_dict",
  1065. ),
  1066. pytest.param(
  1067. ComponentNestedVar.create(list_of_list=[[TEST_VAR]]),
  1068. [TEST_VAR_LIST_OF_LIST],
  1069. id="direct-list_of_list",
  1070. ),
  1071. pytest.param(
  1072. ComponentNestedVar.create(list_of_list=[[f"foo{TEST_VAR}bar"]]),
  1073. [FORMATTED_TEST_VAR_LIST_OF_LIST],
  1074. id="fstring-list_of_list",
  1075. ),
  1076. pytest.param(
  1077. ComponentNestedVar.create(list_of_list_of_list=[[[TEST_VAR]]]),
  1078. [TEST_VAR_LIST_OF_LIST_OF_LIST],
  1079. id="direct-list_of_list_of_list",
  1080. ),
  1081. pytest.param(
  1082. ComponentNestedVar.create(list_of_list_of_list=[[[f"foo{TEST_VAR}bar"]]]),
  1083. [FORMATTED_TEST_VAR_LIST_OF_LIST_OF_LIST],
  1084. id="fstring-list_of_list_of_list",
  1085. ),
  1086. pytest.param(
  1087. ComponentNestedVar.create(list_of_dict=[{"a": TEST_VAR}]),
  1088. [TEST_VAR_LIST_OF_DICT],
  1089. id="direct-list_of_dict",
  1090. ),
  1091. pytest.param(
  1092. ComponentNestedVar.create(list_of_dict=[{"a": f"foo{TEST_VAR}bar"}]),
  1093. [FORMATTED_TEST_VAR_LIST_OF_DICT],
  1094. id="fstring-list_of_dict",
  1095. ),
  1096. ),
  1097. )
  1098. def test_get_vars(component, exp_vars):
  1099. comp_vars = sorted(component._get_vars(), key=lambda v: v._var_name)
  1100. assert len(comp_vars) == len(exp_vars)
  1101. for comp_var, exp_var in zip(
  1102. comp_vars,
  1103. sorted(exp_vars, key=lambda v: v._var_name),
  1104. ):
  1105. # print(str(comp_var), str(exp_var))
  1106. # print(comp_var._get_all_var_data(), exp_var._get_all_var_data())
  1107. assert comp_var.equals(exp_var)
  1108. def test_instantiate_all_components():
  1109. """Test that all components can be instantiated."""
  1110. # These components all have required arguments and cannot be trivially instantiated.
  1111. untested_components = {
  1112. "Card",
  1113. "Cond",
  1114. "DebounceInput",
  1115. "Foreach",
  1116. "FormControl",
  1117. "Html",
  1118. "Icon",
  1119. "Match",
  1120. "Markdown",
  1121. "MultiSelect",
  1122. "Option",
  1123. "Popover",
  1124. "Radio",
  1125. "Script",
  1126. "Tag",
  1127. "Tfoot",
  1128. "Thead",
  1129. }
  1130. component_nested_list = [
  1131. *rx.RADIX_MAPPING.values(),
  1132. *rx.COMPONENTS_BASE_MAPPING.values(),
  1133. *rx.COMPONENTS_CORE_MAPPING.values(),
  1134. ]
  1135. for component_name in [
  1136. comp_name
  1137. for submodule_list in component_nested_list
  1138. for comp_name in submodule_list
  1139. ]: # type: ignore
  1140. if component_name in untested_components:
  1141. continue
  1142. component = getattr(
  1143. rx,
  1144. (
  1145. component_name
  1146. if not isinstance(component_name, tuple)
  1147. else component_name[1]
  1148. ),
  1149. )
  1150. if isinstance(component, type) and issubclass(component, Component):
  1151. component.create()
  1152. class InvalidParentComponent(Component):
  1153. """Invalid Parent Component."""
  1154. ...
  1155. class ValidComponent1(Component):
  1156. """Test valid component."""
  1157. _valid_children = ["ValidComponent2"]
  1158. class ValidComponent2(Component):
  1159. """Test valid component."""
  1160. ...
  1161. class ValidComponent3(Component):
  1162. """Test valid component."""
  1163. _valid_parents = ["ValidComponent2"]
  1164. class ValidComponent4(Component):
  1165. """Test valid component."""
  1166. _invalid_children = ["InvalidComponent"]
  1167. class InvalidComponent(Component):
  1168. """Test invalid component."""
  1169. ...
  1170. valid_component1 = ValidComponent1.create
  1171. valid_component2 = ValidComponent2.create
  1172. invalid_component = InvalidComponent.create
  1173. valid_component3 = ValidComponent3.create
  1174. invalid_parent = InvalidParentComponent.create
  1175. valid_component4 = ValidComponent4.create
  1176. def test_validate_valid_children():
  1177. valid_component1(valid_component2())
  1178. valid_component1(
  1179. rx.fragment(valid_component2()),
  1180. )
  1181. valid_component1(
  1182. rx.fragment(
  1183. rx.fragment(
  1184. rx.fragment(valid_component2()),
  1185. ),
  1186. ),
  1187. )
  1188. valid_component1(
  1189. rx.cond( # type: ignore
  1190. True,
  1191. rx.fragment(valid_component2()),
  1192. rx.fragment(
  1193. rx.foreach(Var.create([1, 2, 3]), lambda x: valid_component2(x)) # type: ignore
  1194. ),
  1195. )
  1196. )
  1197. valid_component1(
  1198. rx.cond(
  1199. True,
  1200. valid_component2(),
  1201. rx.fragment(
  1202. rx.match(
  1203. "condition",
  1204. ("first", valid_component2()),
  1205. rx.fragment(valid_component2(rx.text("default"))),
  1206. )
  1207. ),
  1208. )
  1209. )
  1210. valid_component1(
  1211. rx.match(
  1212. "condition",
  1213. ("first", valid_component2()),
  1214. ("second", "third", rx.fragment(valid_component2())),
  1215. (
  1216. "fourth",
  1217. rx.cond(True, valid_component2(), rx.fragment(valid_component2())),
  1218. ),
  1219. (
  1220. "fifth",
  1221. rx.match(
  1222. "nested_condition",
  1223. ("nested_first", valid_component2()),
  1224. rx.fragment(valid_component2()),
  1225. ),
  1226. valid_component2(),
  1227. ),
  1228. )
  1229. )
  1230. def test_validate_valid_parents():
  1231. valid_component2(valid_component3())
  1232. valid_component2(
  1233. rx.fragment(valid_component3()),
  1234. )
  1235. valid_component1(
  1236. rx.fragment(
  1237. valid_component2(
  1238. rx.fragment(valid_component3()),
  1239. ),
  1240. ),
  1241. )
  1242. valid_component2(
  1243. rx.cond( # type: ignore
  1244. True,
  1245. rx.fragment(valid_component3()),
  1246. rx.fragment(
  1247. rx.foreach(
  1248. Var.create([1, 2, 3]), # type: ignore
  1249. lambda x: valid_component2(valid_component3(x)),
  1250. )
  1251. ),
  1252. )
  1253. )
  1254. valid_component2(
  1255. rx.cond(
  1256. True,
  1257. valid_component3(),
  1258. rx.fragment(
  1259. rx.match(
  1260. "condition",
  1261. ("first", valid_component3()),
  1262. rx.fragment(valid_component3(rx.text("default"))),
  1263. )
  1264. ),
  1265. )
  1266. )
  1267. valid_component2(
  1268. rx.match(
  1269. "condition",
  1270. ("first", valid_component3()),
  1271. ("second", "third", rx.fragment(valid_component3())),
  1272. (
  1273. "fourth",
  1274. rx.cond(True, valid_component3(), rx.fragment(valid_component3())),
  1275. ),
  1276. (
  1277. "fifth",
  1278. rx.match(
  1279. "nested_condition",
  1280. ("nested_first", valid_component3()),
  1281. rx.fragment(valid_component3()),
  1282. ),
  1283. valid_component3(),
  1284. ),
  1285. )
  1286. )
  1287. def test_validate_invalid_children():
  1288. with pytest.raises(ValueError):
  1289. valid_component4(invalid_component())
  1290. with pytest.raises(ValueError):
  1291. valid_component4(
  1292. rx.fragment(invalid_component()),
  1293. )
  1294. with pytest.raises(ValueError):
  1295. valid_component2(
  1296. rx.fragment(
  1297. valid_component4(
  1298. rx.fragment(invalid_component()),
  1299. ),
  1300. ),
  1301. )
  1302. with pytest.raises(ValueError):
  1303. valid_component4(
  1304. rx.cond( # type: ignore
  1305. True,
  1306. rx.fragment(invalid_component()),
  1307. rx.fragment(
  1308. rx.foreach(Var.create([1, 2, 3]), lambda x: invalid_component(x)) # type: ignore
  1309. ),
  1310. )
  1311. )
  1312. with pytest.raises(ValueError):
  1313. valid_component4(
  1314. rx.cond(
  1315. True,
  1316. invalid_component(),
  1317. rx.fragment(
  1318. rx.match(
  1319. "condition",
  1320. ("first", invalid_component()),
  1321. rx.fragment(invalid_component(rx.text("default"))),
  1322. )
  1323. ),
  1324. )
  1325. )
  1326. with pytest.raises(ValueError):
  1327. valid_component4(
  1328. rx.match(
  1329. "condition",
  1330. ("first", invalid_component()),
  1331. ("second", "third", rx.fragment(invalid_component())),
  1332. (
  1333. "fourth",
  1334. rx.cond(True, invalid_component(), rx.fragment(valid_component2())),
  1335. ),
  1336. (
  1337. "fifth",
  1338. rx.match(
  1339. "nested_condition",
  1340. ("nested_first", invalid_component()),
  1341. rx.fragment(invalid_component()),
  1342. ),
  1343. invalid_component(),
  1344. ),
  1345. )
  1346. )
  1347. def test_rename_props():
  1348. """Test that _rename_props works and is inherited."""
  1349. class C1(Component):
  1350. tag = "C1"
  1351. prop1: Var[str]
  1352. prop2: Var[str]
  1353. _rename_props = {"prop1": "renamed_prop1", "prop2": "renamed_prop2"}
  1354. class C2(C1):
  1355. tag = "C2"
  1356. prop3: Var[str]
  1357. _rename_props = {"prop2": "subclass_prop2", "prop3": "renamed_prop3"}
  1358. c1 = C1.create(prop1="prop1_1", prop2="prop2_1")
  1359. rendered_c1 = c1.render()
  1360. assert 'renamed_prop1={"prop1_1"}' in rendered_c1["props"]
  1361. assert 'renamed_prop2={"prop2_1"}' in rendered_c1["props"]
  1362. c2 = C2.create(prop1="prop1_2", prop2="prop2_2", prop3="prop3_2")
  1363. rendered_c2 = c2.render()
  1364. assert 'renamed_prop1={"prop1_2"}' in rendered_c2["props"]
  1365. assert 'subclass_prop2={"prop2_2"}' in rendered_c2["props"]
  1366. assert 'renamed_prop3={"prop3_2"}' in rendered_c2["props"]
  1367. def test_custom_component_get_imports():
  1368. class Inner(Component):
  1369. tag = "Inner"
  1370. library = "inner"
  1371. class Other(Component):
  1372. tag = "Other"
  1373. library = "other"
  1374. @rx.memo
  1375. def wrapper():
  1376. return Inner.create()
  1377. @rx.memo
  1378. def outer(c: Component):
  1379. return Other.create(c)
  1380. custom_comp = wrapper()
  1381. # Inner is not imported directly, but it is imported by the custom component.
  1382. assert "inner" not in custom_comp._get_all_imports()
  1383. # The imports are only resolved during compilation.
  1384. _, _, imports_inner = compile_components(custom_comp._get_all_custom_components())
  1385. assert "inner" in imports_inner
  1386. outer_comp = outer(c=wrapper())
  1387. # Libraries are not imported directly, but are imported by the custom component.
  1388. assert "inner" not in outer_comp._get_all_imports()
  1389. assert "other" not in outer_comp._get_all_imports()
  1390. # The imports are only resolved during compilation.
  1391. _, _, imports_outer = compile_components(outer_comp._get_all_custom_components())
  1392. assert "inner" in imports_outer
  1393. assert "other" in imports_outer
  1394. def test_custom_component_declare_event_handlers_in_fields():
  1395. class ReferenceComponent(Component):
  1396. def get_event_triggers(self) -> Dict[str, Any]:
  1397. """Test controlled triggers.
  1398. Returns:
  1399. Test controlled triggers.
  1400. """
  1401. return {
  1402. **super().get_event_triggers(),
  1403. "on_a": lambda e0: [e0],
  1404. "on_b": lambda e0: [e0.target.value],
  1405. "on_c": lambda e0: [],
  1406. "on_d": lambda: [],
  1407. "on_e": lambda: [],
  1408. "on_f": lambda a, b, c: [c, b, a],
  1409. }
  1410. class TestComponent(Component):
  1411. on_a: EventHandler[lambda e0: [e0]]
  1412. on_b: EventHandler[lambda e0: [e0.target.value]]
  1413. on_c: EventHandler[lambda e0: []]
  1414. on_d: EventHandler[lambda: []]
  1415. on_e: EventHandler
  1416. on_f: EventHandler[lambda a, b, c: [c, b, a]]
  1417. custom_component = ReferenceComponent.create()
  1418. test_component = TestComponent.create()
  1419. custom_triggers = custom_component.get_event_triggers()
  1420. test_triggers = test_component.get_event_triggers()
  1421. assert custom_triggers.keys() == test_triggers.keys()
  1422. for trigger_name in custom_component.get_event_triggers():
  1423. for v1, v2 in zip(
  1424. parse_args_spec(test_triggers[trigger_name]),
  1425. parse_args_spec(custom_triggers[trigger_name]),
  1426. ):
  1427. assert v1.equals(v2)
  1428. def test_invalid_event_trigger():
  1429. class TriggerComponent(Component):
  1430. on_push: Var[bool]
  1431. def get_event_triggers(self) -> Dict[str, Any]:
  1432. """Test controlled triggers.
  1433. Returns:
  1434. Test controlled triggers.
  1435. """
  1436. return {
  1437. **super().get_event_triggers(),
  1438. "on_a": lambda: [],
  1439. }
  1440. trigger_comp = TriggerComponent.create
  1441. # test that these do not throw errors.
  1442. trigger_comp(on_push=True)
  1443. trigger_comp(on_a=rx.console_log("log"))
  1444. with pytest.raises(ValueError):
  1445. trigger_comp(on_b=rx.console_log("log"))
  1446. @pytest.mark.parametrize(
  1447. "tags",
  1448. (
  1449. ["Component"],
  1450. ["Component", "useState"],
  1451. [ImportVar(tag="Component")],
  1452. [ImportVar(tag="Component"), ImportVar(tag="useState")],
  1453. ["Component", ImportVar(tag="useState")],
  1454. ),
  1455. )
  1456. def test_component_add_imports(tags):
  1457. class BaseComponent(Component):
  1458. def _get_imports(self) -> ImportDict:
  1459. return {}
  1460. class Reference(Component):
  1461. def _get_imports(self) -> ParsedImportDict:
  1462. return imports.merge_imports(
  1463. super()._get_imports(),
  1464. parse_imports({"react": tags}),
  1465. {"foo": [ImportVar(tag="bar")]},
  1466. )
  1467. class TestBase(Component):
  1468. def add_imports(
  1469. self,
  1470. ) -> Dict[str, Union[str, ImportVar, List[str], List[ImportVar]]]:
  1471. return {"foo": "bar"}
  1472. class Test(TestBase):
  1473. def add_imports(
  1474. self,
  1475. ) -> Dict[str, Union[str, ImportVar, List[str], List[ImportVar]]]:
  1476. return {"react": (tags[0] if len(tags) == 1 else tags)}
  1477. baseline = Reference.create()
  1478. test = Test.create()
  1479. assert baseline._get_all_imports() == parse_imports(
  1480. {
  1481. "react": tags,
  1482. "foo": [ImportVar(tag="bar")],
  1483. }
  1484. )
  1485. assert test._get_all_imports() == baseline._get_all_imports()
  1486. def test_component_add_hooks():
  1487. class BaseComponent(Component):
  1488. def _get_hooks(self):
  1489. return "const hook1 = 42"
  1490. class ChildComponent1(BaseComponent):
  1491. pass
  1492. class GrandchildComponent1(ChildComponent1):
  1493. def add_hooks(self):
  1494. return [
  1495. "const hook2 = 43",
  1496. "const hook3 = 44",
  1497. ]
  1498. class GreatGrandchildComponent1(GrandchildComponent1):
  1499. def add_hooks(self):
  1500. return [
  1501. "const hook4 = 45",
  1502. ]
  1503. class GrandchildComponent2(ChildComponent1):
  1504. def _get_hooks(self):
  1505. return "const hook5 = 46"
  1506. class GreatGrandchildComponent2(GrandchildComponent2):
  1507. def add_hooks(self):
  1508. return [
  1509. "const hook2 = 43",
  1510. "const hook6 = 47",
  1511. ]
  1512. assert list(BaseComponent()._get_all_hooks()) == ["const hook1 = 42"]
  1513. assert list(ChildComponent1()._get_all_hooks()) == ["const hook1 = 42"]
  1514. assert list(GrandchildComponent1()._get_all_hooks()) == [
  1515. "const hook1 = 42",
  1516. "const hook2 = 43",
  1517. "const hook3 = 44",
  1518. ]
  1519. assert list(GreatGrandchildComponent1()._get_all_hooks()) == [
  1520. "const hook1 = 42",
  1521. "const hook2 = 43",
  1522. "const hook3 = 44",
  1523. "const hook4 = 45",
  1524. ]
  1525. assert list(GrandchildComponent2()._get_all_hooks()) == ["const hook5 = 46"]
  1526. assert list(GreatGrandchildComponent2()._get_all_hooks()) == [
  1527. "const hook5 = 46",
  1528. "const hook2 = 43",
  1529. "const hook6 = 47",
  1530. ]
  1531. assert list(
  1532. BaseComponent.create(
  1533. GrandchildComponent1.create(GreatGrandchildComponent2()),
  1534. GreatGrandchildComponent1(),
  1535. )._get_all_hooks(),
  1536. ) == [
  1537. "const hook1 = 42",
  1538. "const hook2 = 43",
  1539. "const hook3 = 44",
  1540. "const hook5 = 46",
  1541. "const hook6 = 47",
  1542. "const hook4 = 45",
  1543. ]
  1544. assert list(
  1545. Fragment.create(
  1546. GreatGrandchildComponent2(),
  1547. GreatGrandchildComponent1(),
  1548. )._get_all_hooks()
  1549. ) == [
  1550. "const hook5 = 46",
  1551. "const hook2 = 43",
  1552. "const hook6 = 47",
  1553. "const hook1 = 42",
  1554. "const hook3 = 44",
  1555. "const hook4 = 45",
  1556. ]
  1557. def test_component_add_custom_code():
  1558. class BaseComponent(Component):
  1559. def _get_custom_code(self):
  1560. return "const custom_code1 = 42"
  1561. class ChildComponent1(BaseComponent):
  1562. pass
  1563. class GrandchildComponent1(ChildComponent1):
  1564. def add_custom_code(self):
  1565. return [
  1566. "const custom_code2 = 43",
  1567. "const custom_code3 = 44",
  1568. ]
  1569. class GreatGrandchildComponent1(GrandchildComponent1):
  1570. def add_custom_code(self):
  1571. return [
  1572. "const custom_code4 = 45",
  1573. ]
  1574. class GrandchildComponent2(ChildComponent1):
  1575. def _get_custom_code(self):
  1576. return "const custom_code5 = 46"
  1577. class GreatGrandchildComponent2(GrandchildComponent2):
  1578. def add_custom_code(self):
  1579. return [
  1580. "const custom_code2 = 43",
  1581. "const custom_code6 = 47",
  1582. ]
  1583. assert BaseComponent()._get_all_custom_code() == {"const custom_code1 = 42"}
  1584. assert ChildComponent1()._get_all_custom_code() == {"const custom_code1 = 42"}
  1585. assert GrandchildComponent1()._get_all_custom_code() == {
  1586. "const custom_code1 = 42",
  1587. "const custom_code2 = 43",
  1588. "const custom_code3 = 44",
  1589. }
  1590. assert GreatGrandchildComponent1()._get_all_custom_code() == {
  1591. "const custom_code1 = 42",
  1592. "const custom_code2 = 43",
  1593. "const custom_code3 = 44",
  1594. "const custom_code4 = 45",
  1595. }
  1596. assert GrandchildComponent2()._get_all_custom_code() == {"const custom_code5 = 46"}
  1597. assert GreatGrandchildComponent2()._get_all_custom_code() == {
  1598. "const custom_code2 = 43",
  1599. "const custom_code5 = 46",
  1600. "const custom_code6 = 47",
  1601. }
  1602. assert BaseComponent.create(
  1603. GrandchildComponent1.create(GreatGrandchildComponent2()),
  1604. GreatGrandchildComponent1(),
  1605. )._get_all_custom_code() == {
  1606. "const custom_code1 = 42",
  1607. "const custom_code2 = 43",
  1608. "const custom_code3 = 44",
  1609. "const custom_code4 = 45",
  1610. "const custom_code5 = 46",
  1611. "const custom_code6 = 47",
  1612. }
  1613. assert Fragment.create(
  1614. GreatGrandchildComponent2(),
  1615. GreatGrandchildComponent1(),
  1616. )._get_all_custom_code() == {
  1617. "const custom_code1 = 42",
  1618. "const custom_code2 = 43",
  1619. "const custom_code3 = 44",
  1620. "const custom_code4 = 45",
  1621. "const custom_code5 = 46",
  1622. "const custom_code6 = 47",
  1623. }
  1624. def test_component_add_hooks_var():
  1625. class HookComponent(Component):
  1626. def add_hooks(self):
  1627. return [
  1628. "const hook3 = useRef(null)",
  1629. "const hook1 = 42",
  1630. Var.create(
  1631. "useEffect(() => () => {}, [])",
  1632. _var_data=VarData(
  1633. hooks={
  1634. "const hook2 = 43": None,
  1635. "const hook3 = useRef(null)": None,
  1636. },
  1637. imports={"react": [ImportVar(tag="useEffect")]},
  1638. ),
  1639. ),
  1640. Var.create(
  1641. "const hook3 = useRef(null)",
  1642. _var_data=VarData(
  1643. imports={"react": [ImportVar(tag="useRef")]},
  1644. ),
  1645. ),
  1646. ]
  1647. assert list(HookComponent()._get_all_hooks()) == [
  1648. "const hook3 = useRef(null)",
  1649. "const hook1 = 42",
  1650. "const hook2 = 43",
  1651. "useEffect(() => () => {}, [])",
  1652. ]
  1653. imports = HookComponent()._get_all_imports()
  1654. assert len(imports) == 1
  1655. assert "react" in imports
  1656. assert len(imports["react"]) == 2
  1657. assert ImportVar(tag="useRef") in imports["react"]
  1658. assert ImportVar(tag="useEffect") in imports["react"]
  1659. def test_add_style_embedded_vars(test_state: BaseState):
  1660. """Test that add_style works with embedded vars when returning a plain dict.
  1661. Args:
  1662. test_state: A test state.
  1663. """
  1664. v0 = LiteralVar.create("parent")._replace(
  1665. merge_var_data=VarData(hooks={"useParent": None}), # type: ignore
  1666. )
  1667. v1 = rx.color("plum", 10)
  1668. v2 = LiteralVar.create("text")._replace(
  1669. merge_var_data=VarData(hooks={"useText": None}), # type: ignore
  1670. )
  1671. class ParentComponent(Component):
  1672. def add_style(self):
  1673. return Style(
  1674. {
  1675. "fake_parent": v0,
  1676. }
  1677. )
  1678. class StyledComponent(ParentComponent):
  1679. tag = "StyledComponent"
  1680. def add_style(self):
  1681. return {
  1682. "color": v1,
  1683. "fake": v2,
  1684. "margin": f"{test_state.num}%",
  1685. }
  1686. page = rx.vstack(StyledComponent.create())
  1687. page._add_style_recursive(Style())
  1688. assert (
  1689. f"const {test_state.get_name()} = useContext(StateContexts.{test_state.get_name()})"
  1690. in page._get_all_hooks_internal()
  1691. )
  1692. assert "useText" in page._get_all_hooks_internal()
  1693. assert "useParent" in page._get_all_hooks_internal()
  1694. assert (
  1695. str(page).count(
  1696. f'css={{({{ ["fakeParent"] : "parent", ["color"] : "var(--plum-10)", ["fake"] : "text", ["margin"] : ({test_state.get_name()}.num+"%") }})}}'
  1697. )
  1698. == 1
  1699. )
  1700. def test_add_style_foreach():
  1701. class StyledComponent(Component):
  1702. tag = "StyledComponent"
  1703. ix: Var[int]
  1704. def add_style(self):
  1705. return Style({"color": "red"})
  1706. page = rx.vstack(rx.foreach(Var.range(3), lambda i: StyledComponent.create(i)))
  1707. page._add_style_recursive(Style())
  1708. # Expect only a single child of the foreach on the python side
  1709. assert len(page.children[0].children) == 1
  1710. # Expect the style to be added to the child of the foreach
  1711. assert 'css={({ ["color"] : "red" })}' in str(page.children[0].children[0])
  1712. # Expect only one instance of this CSS dict in the rendered page
  1713. assert str(page).count('css={({ ["color"] : "red" })}') == 1
  1714. class TriggerState(rx.State):
  1715. """Test state with event handlers."""
  1716. def do_something(self):
  1717. """Sample event handler."""
  1718. pass
  1719. @pytest.mark.parametrize(
  1720. "component, output",
  1721. [
  1722. (rx.box(rx.text("random text")), False),
  1723. (
  1724. rx.box(rx.text("random text", on_click=rx.console_log("log"))),
  1725. False,
  1726. ),
  1727. (
  1728. rx.box(
  1729. rx.text("random text", on_click=TriggerState.do_something),
  1730. rx.text(
  1731. "random text",
  1732. on_click=BaseVar(_var_name="toggleColorMode", _var_type=EventChain),
  1733. ),
  1734. ),
  1735. True,
  1736. ),
  1737. (
  1738. rx.box(
  1739. rx.text("random text", on_click=rx.console_log("log")),
  1740. rx.text(
  1741. "random text",
  1742. on_click=BaseVar(_var_name="toggleColorMode", _var_type=EventChain),
  1743. ),
  1744. ),
  1745. False,
  1746. ),
  1747. (
  1748. rx.box(rx.text("random text", on_click=TriggerState.do_something)),
  1749. True,
  1750. ),
  1751. (
  1752. rx.box(
  1753. rx.text(
  1754. "random text",
  1755. on_click=[rx.console_log("log"), rx.window_alert("alert")],
  1756. ),
  1757. ),
  1758. False,
  1759. ),
  1760. (
  1761. rx.box(
  1762. rx.text(
  1763. "random text",
  1764. on_click=[rx.console_log("log"), TriggerState.do_something],
  1765. ),
  1766. ),
  1767. True,
  1768. ),
  1769. (
  1770. rx.box(
  1771. rx.text(
  1772. "random text",
  1773. on_blur=lambda: TriggerState.do_something,
  1774. ),
  1775. ),
  1776. True,
  1777. ),
  1778. ],
  1779. )
  1780. def test_has_state_event_triggers(component, output):
  1781. assert component._has_stateful_event_triggers() == output