test_component.py 70 KB

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