test_component.py 67 KB

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