test_component.py 46 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571
  1. from typing import Any, Dict, List, Type
  2. import pytest
  3. import reflex as rx
  4. from reflex.base import Base
  5. from reflex.compiler.compiler import compile_components
  6. from reflex.components.base.bare import Bare
  7. from reflex.components.base.fragment import Fragment
  8. from reflex.components.chakra.layout.box import Box
  9. from reflex.components.component import (
  10. Component,
  11. CustomComponent,
  12. StatefulComponent,
  13. custom_component,
  14. )
  15. from reflex.constants import EventTriggers
  16. from reflex.event import EventChain, EventHandler, parse_args_spec
  17. from reflex.state import BaseState
  18. from reflex.style import Style
  19. from reflex.utils import imports
  20. from reflex.utils.imports import ImportVar
  21. from reflex.vars import Var, VarData
  22. @pytest.fixture
  23. def test_state():
  24. class TestState(BaseState):
  25. num: int
  26. def do_something(self):
  27. pass
  28. def do_something_arg(self, arg):
  29. pass
  30. return TestState
  31. @pytest.fixture
  32. def component1() -> Type[Component]:
  33. """A test component.
  34. Returns:
  35. A test component.
  36. """
  37. class TestComponent1(Component):
  38. # A test string prop.
  39. text: Var[str]
  40. # A test number prop.
  41. number: Var[int]
  42. def _get_imports(self) -> imports.ImportDict:
  43. return {"react": [ImportVar(tag="Component")]}
  44. def _get_custom_code(self) -> str:
  45. return "console.log('component1')"
  46. return TestComponent1
  47. @pytest.fixture
  48. def component2() -> Type[Component]:
  49. """A test component.
  50. Returns:
  51. A test component.
  52. """
  53. class TestComponent2(Component):
  54. # A test list prop.
  55. arr: Var[List[str]]
  56. def get_event_triggers(self) -> Dict[str, Any]:
  57. """Test controlled triggers.
  58. Returns:
  59. Test controlled triggers.
  60. """
  61. return {
  62. **super().get_event_triggers(),
  63. "on_open": lambda e0: [e0],
  64. "on_close": lambda e0: [e0],
  65. }
  66. def _get_imports(self) -> imports.ImportDict:
  67. return {"react-redux": [ImportVar(tag="connect")]}
  68. def _get_custom_code(self) -> str:
  69. return "console.log('component2')"
  70. return TestComponent2
  71. @pytest.fixture
  72. def component3() -> Type[Component]:
  73. """A test component with hook defined.
  74. Returns:
  75. A test component.
  76. """
  77. class TestComponent3(Component):
  78. def _get_hooks(self) -> str:
  79. return "const a = () => true"
  80. return TestComponent3
  81. @pytest.fixture
  82. def component4() -> Type[Component]:
  83. """A test component with hook defined.
  84. Returns:
  85. A test component.
  86. """
  87. class TestComponent4(Component):
  88. def _get_hooks(self) -> str:
  89. return "const b = () => false"
  90. return TestComponent4
  91. @pytest.fixture
  92. def component5() -> Type[Component]:
  93. """A test component.
  94. Returns:
  95. A test component.
  96. """
  97. class TestComponent5(Component):
  98. tag = "RandomComponent"
  99. _invalid_children: List[str] = ["Text"]
  100. _valid_children: List[str] = ["Text"]
  101. _valid_parents: List[str] = ["Text"]
  102. return TestComponent5
  103. @pytest.fixture
  104. def component6() -> Type[Component]:
  105. """A test component.
  106. Returns:
  107. A test component.
  108. """
  109. class TestComponent6(Component):
  110. tag = "RandomComponent"
  111. _invalid_children: List[str] = ["Text"]
  112. return TestComponent6
  113. @pytest.fixture
  114. def component7() -> Type[Component]:
  115. """A test component.
  116. Returns:
  117. A test component.
  118. """
  119. class TestComponent7(Component):
  120. tag = "RandomComponent"
  121. _valid_children: List[str] = ["Text"]
  122. return TestComponent7
  123. @pytest.fixture
  124. def on_click1() -> EventHandler:
  125. """A sample on click function.
  126. Returns:
  127. A sample on click function.
  128. """
  129. def on_click1():
  130. pass
  131. return EventHandler(fn=on_click1)
  132. @pytest.fixture
  133. def on_click2() -> EventHandler:
  134. """A sample on click function.
  135. Returns:
  136. A sample on click function.
  137. """
  138. def on_click2():
  139. pass
  140. return EventHandler(fn=on_click2)
  141. @pytest.fixture
  142. def my_component():
  143. """A test component function.
  144. Returns:
  145. A test component function.
  146. """
  147. def my_component(prop1: Var[str], prop2: Var[int]):
  148. return Box.create(prop1, prop2)
  149. return my_component
  150. def test_set_style_attrs(component1):
  151. """Test that style attributes are set in the dict.
  152. Args:
  153. component1: A test component.
  154. """
  155. component = component1(color="white", text_align="center")
  156. assert component.style["color"] == "white"
  157. assert component.style["textAlign"] == "center"
  158. def test_custom_attrs(component1):
  159. """Test that custom attributes are set in the dict.
  160. Args:
  161. component1: A test component.
  162. """
  163. component = component1(custom_attrs={"attr1": "1", "attr2": "attr2"})
  164. assert component.custom_attrs == {"attr1": "1", "attr2": "attr2"}
  165. def test_create_component(component1):
  166. """Test that the component is created correctly.
  167. Args:
  168. component1: A test component.
  169. """
  170. children = [component1() for _ in range(3)]
  171. attrs = {"color": "white", "text_align": "center"}
  172. c = component1.create(*children, **attrs)
  173. assert isinstance(c, component1)
  174. assert c.children == children
  175. assert c.style == {"color": "white", "textAlign": "center"}
  176. def test_add_style(component1, component2):
  177. """Test adding a style to a component.
  178. Args:
  179. component1: A test component.
  180. component2: A test component.
  181. """
  182. style = {
  183. component1: Style({"color": "white"}),
  184. component2: Style({"color": "black"}),
  185. }
  186. c1 = component1()._add_style_recursive(style) # type: ignore
  187. c2 = component2()._add_style_recursive(style) # type: ignore
  188. assert c1.style["color"] == "white"
  189. assert c2.style["color"] == "black"
  190. def test_add_style_create(component1, component2):
  191. """Test that adding style works with the create method.
  192. Args:
  193. component1: A test component.
  194. component2: A test component.
  195. """
  196. style = {
  197. component1.create: Style({"color": "white"}),
  198. component2.create: Style({"color": "black"}),
  199. }
  200. c1 = component1()._add_style_recursive(style) # type: ignore
  201. c2 = component2()._add_style_recursive(style) # type: ignore
  202. assert c1.style["color"] == "white"
  203. assert c2.style["color"] == "black"
  204. def test_get_imports(component1, component2):
  205. """Test getting the imports of a component.
  206. Args:
  207. component1: A test component.
  208. component2: A test component.
  209. """
  210. c1 = component1.create()
  211. c2 = component2.create(c1)
  212. assert c1._get_all_imports() == {"react": [ImportVar(tag="Component")]}
  213. assert c2._get_all_imports() == {
  214. "react-redux": [ImportVar(tag="connect")],
  215. "react": [ImportVar(tag="Component")],
  216. }
  217. def test_get_custom_code(component1, component2):
  218. """Test getting the custom code of a component.
  219. Args:
  220. component1: A test component.
  221. component2: A test component.
  222. """
  223. # Check that the code gets compiled correctly.
  224. c1 = component1.create()
  225. c2 = component2.create()
  226. assert c1._get_all_custom_code() == {"console.log('component1')"}
  227. assert c2._get_all_custom_code() == {"console.log('component2')"}
  228. # Check that nesting components compiles both codes.
  229. c1 = component1.create(c2)
  230. assert c1._get_all_custom_code() == {
  231. "console.log('component1')",
  232. "console.log('component2')",
  233. }
  234. # Check that code is not duplicated.
  235. c1 = component1.create(c2, c2, c1, c1)
  236. assert c1._get_all_custom_code() == {
  237. "console.log('component1')",
  238. "console.log('component2')",
  239. }
  240. def test_get_props(component1, component2):
  241. """Test that the props are set correctly.
  242. Args:
  243. component1: A test component.
  244. component2: A test component.
  245. """
  246. assert component1.get_props() == {"text", "number"}
  247. assert component2.get_props() == {"arr"}
  248. @pytest.mark.parametrize(
  249. "text,number",
  250. [
  251. ("", 0),
  252. ("test", 1),
  253. ("hi", -13),
  254. ],
  255. )
  256. def test_valid_props(component1, text: str, number: int):
  257. """Test that we can construct a component with valid props.
  258. Args:
  259. component1: A test component.
  260. text: A test string.
  261. number: A test number.
  262. """
  263. c = component1.create(text=text, number=number)
  264. assert c.text._decode() == text
  265. assert c.number._decode() == number
  266. @pytest.mark.parametrize(
  267. "text,number", [("", "bad_string"), (13, 1), ("test", [1, 2, 3])]
  268. )
  269. def test_invalid_prop_type(component1, text: str, number: int):
  270. """Test that an invalid prop type raises an error.
  271. Args:
  272. component1: A test component.
  273. text: A test string.
  274. number: A test number.
  275. """
  276. # Check that
  277. with pytest.raises(TypeError):
  278. component1.create(text=text, number=number)
  279. def test_var_props(component1, test_state):
  280. """Test that we can set a Var prop.
  281. Args:
  282. component1: A test component.
  283. test_state: A test state.
  284. """
  285. c1 = component1.create(text="hello", number=test_state.num)
  286. assert c1.number.equals(test_state.num)
  287. def test_get_event_triggers(component1, component2):
  288. """Test that we can get the triggers of a component.
  289. Args:
  290. component1: A test component.
  291. component2: A test component.
  292. """
  293. default_triggers = {
  294. EventTriggers.ON_FOCUS,
  295. EventTriggers.ON_BLUR,
  296. EventTriggers.ON_CLICK,
  297. EventTriggers.ON_CONTEXT_MENU,
  298. EventTriggers.ON_DOUBLE_CLICK,
  299. EventTriggers.ON_MOUSE_DOWN,
  300. EventTriggers.ON_MOUSE_ENTER,
  301. EventTriggers.ON_MOUSE_LEAVE,
  302. EventTriggers.ON_MOUSE_MOVE,
  303. EventTriggers.ON_MOUSE_OUT,
  304. EventTriggers.ON_MOUSE_OVER,
  305. EventTriggers.ON_MOUSE_UP,
  306. EventTriggers.ON_SCROLL,
  307. EventTriggers.ON_MOUNT,
  308. EventTriggers.ON_UNMOUNT,
  309. }
  310. assert set(component1().get_event_triggers().keys()) == default_triggers
  311. assert (
  312. component2().get_event_triggers().keys()
  313. == {"on_open", "on_close"} | default_triggers
  314. )
  315. @pytest.fixture
  316. def test_component() -> Type[Component]:
  317. """A test component.
  318. Returns:
  319. A test component.
  320. """
  321. class TestComponent(Component):
  322. pass
  323. return TestComponent
  324. # Write a test case to check if the create method filters out None props
  325. def test_create_filters_none_props(test_component):
  326. child1 = test_component()
  327. child2 = test_component()
  328. props = {
  329. "prop1": "value1",
  330. "prop2": None,
  331. "prop3": "value3",
  332. "prop4": None,
  333. "style": {"color": "white", "text-align": "center"}, # Adding a style prop
  334. }
  335. component = test_component.create(child1, child2, **props)
  336. # Assert that None props are not present in the component's props
  337. assert "prop2" not in component.get_props()
  338. assert "prop4" not in component.get_props()
  339. # Assert that the style prop is present in the component's props
  340. assert component.style["color"] == "white"
  341. assert component.style["text-align"] == "center"
  342. @pytest.mark.parametrize("children", [((None,),), ("foo", ("bar", (None,)))])
  343. def test_component_create_unallowed_types(children, test_component):
  344. with pytest.raises(TypeError) as err:
  345. test_component.create(*children)
  346. assert (
  347. err.value.args[0]
  348. == "Children of Reflex components must be other components, state vars, or primitive Python types. Got child None of type <class 'NoneType'>."
  349. )
  350. @pytest.mark.parametrize(
  351. "element, expected",
  352. [
  353. (
  354. (rx.text("first_text"),),
  355. {
  356. "name": "Fragment",
  357. "props": [],
  358. "contents": "",
  359. "args": None,
  360. "special_props": set(),
  361. "children": [
  362. {
  363. "name": "RadixThemesText",
  364. "props": ["as={`p`}"],
  365. "contents": "",
  366. "args": None,
  367. "special_props": set(),
  368. "children": [
  369. {
  370. "name": "",
  371. "props": [],
  372. "contents": "{`first_text`}",
  373. "args": None,
  374. "special_props": set(),
  375. "children": [],
  376. "autofocus": False,
  377. }
  378. ],
  379. "autofocus": False,
  380. }
  381. ],
  382. "autofocus": False,
  383. },
  384. ),
  385. (
  386. (rx.text("first_text"), rx.text("second_text")),
  387. {
  388. "args": None,
  389. "autofocus": False,
  390. "children": [
  391. {
  392. "args": None,
  393. "autofocus": False,
  394. "children": [
  395. {
  396. "args": None,
  397. "autofocus": False,
  398. "children": [],
  399. "contents": "{`first_text`}",
  400. "name": "",
  401. "props": [],
  402. "special_props": set(),
  403. }
  404. ],
  405. "contents": "",
  406. "name": "RadixThemesText",
  407. "props": ["as={`p`}"],
  408. "special_props": set(),
  409. },
  410. {
  411. "args": None,
  412. "autofocus": False,
  413. "children": [
  414. {
  415. "args": None,
  416. "autofocus": False,
  417. "children": [],
  418. "contents": "{`second_text`}",
  419. "name": "",
  420. "props": [],
  421. "special_props": set(),
  422. }
  423. ],
  424. "contents": "",
  425. "name": "RadixThemesText",
  426. "props": ["as={`p`}"],
  427. "special_props": set(),
  428. },
  429. ],
  430. "contents": "",
  431. "name": "Fragment",
  432. "props": [],
  433. "special_props": set(),
  434. },
  435. ),
  436. (
  437. (rx.text("first_text"), rx.box((rx.text("second_text"),))),
  438. {
  439. "args": None,
  440. "autofocus": False,
  441. "children": [
  442. {
  443. "args": None,
  444. "autofocus": False,
  445. "children": [
  446. {
  447. "args": None,
  448. "autofocus": False,
  449. "children": [],
  450. "contents": "{`first_text`}",
  451. "name": "",
  452. "props": [],
  453. "special_props": set(),
  454. }
  455. ],
  456. "contents": "",
  457. "name": "RadixThemesText",
  458. "props": ["as={`p`}"],
  459. "special_props": set(),
  460. },
  461. {
  462. "args": None,
  463. "autofocus": False,
  464. "children": [
  465. {
  466. "args": None,
  467. "autofocus": False,
  468. "children": [
  469. {
  470. "args": None,
  471. "autofocus": False,
  472. "children": [
  473. {
  474. "args": None,
  475. "autofocus": False,
  476. "children": [],
  477. "contents": "{`second_text`}",
  478. "name": "",
  479. "props": [],
  480. "special_props": set(),
  481. }
  482. ],
  483. "contents": "",
  484. "name": "RadixThemesText",
  485. "props": ["as={`p`}"],
  486. "special_props": set(),
  487. }
  488. ],
  489. "contents": "",
  490. "name": "Fragment",
  491. "props": [],
  492. "special_props": set(),
  493. }
  494. ],
  495. "contents": "",
  496. "name": "RadixThemesBox",
  497. "props": [],
  498. "special_props": set(),
  499. },
  500. ],
  501. "contents": "",
  502. "name": "Fragment",
  503. "props": [],
  504. "special_props": set(),
  505. },
  506. ),
  507. ],
  508. )
  509. def test_component_create_unpack_tuple_child(test_component, element, expected):
  510. """Test that component in tuples are unwrapped into an rx.Fragment.
  511. Args:
  512. test_component: Component fixture.
  513. element: The children to pass to the component.
  514. expected: The expected render dict.
  515. """
  516. comp = test_component.create(element)
  517. assert len(comp.children) == 1
  518. assert isinstance((fragment_wrapper := comp.children[0]), Fragment)
  519. assert fragment_wrapper.render() == expected
  520. class C1State(BaseState):
  521. """State for testing C1 component."""
  522. def mock_handler(self, _e, _bravo, _charlie):
  523. """Mock handler."""
  524. pass
  525. def test_component_event_trigger_arbitrary_args():
  526. """Test that we can define arbitrary types for the args of an event trigger."""
  527. class Obj(Base):
  528. custom: int = 0
  529. def on_foo_spec(_e, alpha: str, bravo: Dict[str, Any], charlie: Obj):
  530. return [_e.target.value, bravo["nested"], charlie.custom + 42]
  531. class C1(Component):
  532. library = "/local"
  533. tag = "C1"
  534. def get_event_triggers(self) -> Dict[str, Any]:
  535. return {
  536. **super().get_event_triggers(),
  537. "on_foo": on_foo_spec,
  538. }
  539. comp = C1.create(on_foo=C1State.mock_handler)
  540. assert comp.render()["props"][0] == (
  541. "onFoo={(__e,_alpha,_bravo,_charlie) => addEvents("
  542. '[Event("c1_state.mock_handler", {_e:__e.target.value,_bravo:_bravo["nested"],_charlie:((_charlie.custom) + (42))})], '
  543. "(__e,_alpha,_bravo,_charlie), {})}"
  544. )
  545. def test_create_custom_component(my_component):
  546. """Test that we can create a custom component.
  547. Args:
  548. my_component: A test custom component.
  549. """
  550. component = CustomComponent(component_fn=my_component, prop1="test", prop2=1)
  551. assert component.tag == "MyComponent"
  552. assert component.get_props() == set()
  553. assert component._get_all_custom_components() == {component}
  554. def test_custom_component_hash(my_component):
  555. """Test that the hash of a custom component is correct.
  556. Args:
  557. my_component: A test custom component.
  558. """
  559. component1 = CustomComponent(component_fn=my_component, prop1="test", prop2=1)
  560. component2 = CustomComponent(component_fn=my_component, prop1="test", prop2=2)
  561. assert {component1, component2} == {component1}
  562. def test_custom_component_wrapper():
  563. """Test that the wrapper of a custom component is correct."""
  564. @custom_component
  565. def my_component(width: Var[int], color: Var[str]):
  566. return rx.box(
  567. width=width,
  568. color=color,
  569. )
  570. from reflex.components.radix.themes.typography.text import Text
  571. ccomponent = my_component(
  572. rx.text("child"), width=Var.create(1), color=Var.create("red")
  573. )
  574. assert isinstance(ccomponent, CustomComponent)
  575. assert len(ccomponent.children) == 1
  576. assert isinstance(ccomponent.children[0], Text)
  577. component = ccomponent.get_component(ccomponent)
  578. assert isinstance(component, Box)
  579. def test_invalid_event_handler_args(component2, test_state):
  580. """Test that an invalid event handler raises an error.
  581. Args:
  582. component2: A test component.
  583. test_state: A test state.
  584. """
  585. # Uncontrolled event handlers should not take args.
  586. # This is okay.
  587. component2.create(on_click=test_state.do_something)
  588. # This is not okay.
  589. with pytest.raises(ValueError):
  590. component2.create(on_click=test_state.do_something_arg)
  591. component2.create(on_open=test_state.do_something)
  592. component2.create(
  593. on_open=[test_state.do_something_arg, test_state.do_something]
  594. )
  595. # However lambdas are okay.
  596. component2.create(on_click=lambda: test_state.do_something_arg(1))
  597. component2.create(
  598. on_click=lambda: [test_state.do_something_arg(1), test_state.do_something]
  599. )
  600. component2.create(
  601. on_click=lambda: [test_state.do_something_arg(1), test_state.do_something()]
  602. )
  603. # Controlled event handlers should take args.
  604. # This is okay.
  605. component2.create(on_open=test_state.do_something_arg)
  606. def test_get_hooks_nested(component1, component2, component3):
  607. """Test that a component returns hooks from child components.
  608. Args:
  609. component1: test component.
  610. component2: another component.
  611. component3: component with hooks defined.
  612. """
  613. c = component1.create(
  614. component2.create(arr=[]),
  615. component3.create(),
  616. component3.create(),
  617. component3.create(),
  618. text="a",
  619. number=1,
  620. )
  621. assert c._get_all_hooks() == component3()._get_all_hooks()
  622. def test_get_hooks_nested2(component3, component4):
  623. """Test that a component returns both when parent and child have hooks.
  624. Args:
  625. component3: component with hooks defined.
  626. component4: component with different hooks defined.
  627. """
  628. exp_hooks = {**component3()._get_all_hooks(), **component4()._get_all_hooks()}
  629. assert component3.create(component4.create())._get_all_hooks() == exp_hooks
  630. assert component4.create(component3.create())._get_all_hooks() == exp_hooks
  631. assert (
  632. component4.create(
  633. component3.create(),
  634. component4.create(),
  635. component3.create(),
  636. )._get_all_hooks()
  637. == exp_hooks
  638. )
  639. @pytest.mark.parametrize("fixture", ["component5", "component6"])
  640. def test_unsupported_child_components(fixture, request):
  641. """Test that a value error is raised when an unsupported component (a child component found in the
  642. component's invalid children list) is provided as a child.
  643. Args:
  644. fixture: the test component as a fixture.
  645. request: Pytest request.
  646. """
  647. component = request.getfixturevalue(fixture)
  648. with pytest.raises(ValueError) as err:
  649. comp = component.create(rx.text("testing component"))
  650. comp.render()
  651. assert (
  652. err.value.args[0]
  653. == f"The component `{component.__name__}` cannot have `Text` as a child component"
  654. )
  655. def test_unsupported_parent_components(component5):
  656. """Test that a value error is raised when an component is not in _valid_parents of one of its children.
  657. Args:
  658. component5: component with valid parent of "Text" only
  659. """
  660. with pytest.raises(ValueError) as err:
  661. rx.box(component5.create())
  662. assert (
  663. err.value.args[0]
  664. == f"The component `{component5.__name__}` can only be a child of the components: `{component5._valid_parents[0]}`. Got `Box` instead."
  665. )
  666. @pytest.mark.parametrize("fixture", ["component5", "component7"])
  667. def test_component_with_only_valid_children(fixture, request):
  668. """Test that a value error is raised when an unsupported component (a child component not found in the
  669. component's valid children list) is provided as a child.
  670. Args:
  671. fixture: the test component as a fixture.
  672. request: Pytest request.
  673. """
  674. component = request.getfixturevalue(fixture)
  675. with pytest.raises(ValueError) as err:
  676. comp = component.create(rx.box("testing component"))
  677. comp.render()
  678. assert (
  679. err.value.args[0]
  680. == f"The component `{component.__name__}` only allows the components: `Text` as children. "
  681. f"Got `Box` instead."
  682. )
  683. @pytest.mark.parametrize(
  684. "component,rendered",
  685. [
  686. (rx.text("hi"), "<RadixThemesText as={`p`}>\n {`hi`}\n</RadixThemesText>"),
  687. (
  688. rx.box(rx.chakra.heading("test", size="md")),
  689. "<RadixThemesBox>\n <Heading size={`md`}>\n {`test`}\n</Heading>\n</RadixThemesBox>",
  690. ),
  691. ],
  692. )
  693. def test_format_component(component, rendered):
  694. """Test that a component is formatted correctly.
  695. Args:
  696. component: The component to format.
  697. rendered: The expected rendered component.
  698. """
  699. assert str(component) == rendered
  700. def test_stateful_component(test_state):
  701. """Test that a stateful component is created correctly.
  702. Args:
  703. test_state: A test state.
  704. """
  705. text_component = rx.text(test_state.num)
  706. stateful_component = StatefulComponent.compile_from(text_component)
  707. assert isinstance(stateful_component, StatefulComponent)
  708. assert stateful_component.tag is not None
  709. assert stateful_component.tag.startswith("Text_")
  710. assert stateful_component.references == 1
  711. sc2 = StatefulComponent.compile_from(rx.text(test_state.num))
  712. assert isinstance(sc2, StatefulComponent)
  713. assert stateful_component.references == 2
  714. assert sc2.references == 2
  715. def test_stateful_component_memoize_event_trigger(test_state):
  716. """Test that a stateful component is created correctly with events.
  717. Args:
  718. test_state: A test state.
  719. """
  720. button_component = rx.button("Click me", on_click=test_state.do_something)
  721. stateful_component = StatefulComponent.compile_from(button_component)
  722. assert isinstance(stateful_component, StatefulComponent)
  723. # No event trigger? No StatefulComponent
  724. assert not isinstance(
  725. StatefulComponent.compile_from(rx.button("Click me")), StatefulComponent
  726. )
  727. def test_stateful_banner():
  728. """Test that a stateful component is created correctly with events."""
  729. connection_modal_component = rx.connection_modal()
  730. stateful_component = StatefulComponent.compile_from(connection_modal_component)
  731. assert isinstance(stateful_component, StatefulComponent)
  732. TEST_VAR = Var.create_safe("test")._replace(
  733. merge_var_data=VarData(
  734. hooks={"useTest": None},
  735. imports={"test": {ImportVar(tag="test")}},
  736. state="Test",
  737. interpolations=[],
  738. )
  739. )
  740. FORMATTED_TEST_VAR = Var.create(f"foo{TEST_VAR}bar")
  741. STYLE_VAR = TEST_VAR._replace(_var_name="style", _var_is_local=False)
  742. EVENT_CHAIN_VAR = TEST_VAR._replace(_var_type=EventChain)
  743. ARG_VAR = Var.create("arg")
  744. TEST_VAR_DICT_OF_DICT = Var.create_safe({"a": {"b": "test"}})._replace(
  745. merge_var_data=TEST_VAR._var_data
  746. )
  747. FORMATTED_TEST_VAR_DICT_OF_DICT = Var.create_safe({"a": {"b": f"footestbar"}})._replace(
  748. merge_var_data=TEST_VAR._var_data
  749. )
  750. TEST_VAR_LIST_OF_LIST = Var.create_safe([["test"]])._replace(
  751. merge_var_data=TEST_VAR._var_data
  752. )
  753. FORMATTED_TEST_VAR_LIST_OF_LIST = Var.create_safe([["footestbar"]])._replace(
  754. merge_var_data=TEST_VAR._var_data
  755. )
  756. TEST_VAR_LIST_OF_LIST_OF_LIST = Var.create_safe([[["test"]]])._replace(
  757. merge_var_data=TEST_VAR._var_data
  758. )
  759. FORMATTED_TEST_VAR_LIST_OF_LIST_OF_LIST = Var.create_safe([[["footestbar"]]])._replace(
  760. merge_var_data=TEST_VAR._var_data
  761. )
  762. TEST_VAR_LIST_OF_DICT = Var.create_safe([{"a": "test"}])._replace(
  763. merge_var_data=TEST_VAR._var_data
  764. )
  765. FORMATTED_TEST_VAR_LIST_OF_DICT = Var.create_safe([{"a": "footestbar"}])._replace(
  766. merge_var_data=TEST_VAR._var_data
  767. )
  768. class ComponentNestedVar(Component):
  769. """A component with nested Var types."""
  770. dict_of_dict: Var[Dict[str, Dict[str, str]]]
  771. list_of_list: Var[List[List[str]]]
  772. list_of_list_of_list: Var[List[List[List[str]]]]
  773. list_of_dict: Var[List[Dict[str, str]]]
  774. class EventState(rx.State):
  775. """State for testing event handlers with _get_vars."""
  776. v: int = 42
  777. def handler(self):
  778. """A handler that does nothing."""
  779. def handler2(self, arg):
  780. """A handler that takes an arg.
  781. Args:
  782. arg: An arg.
  783. """
  784. @pytest.mark.parametrize(
  785. ("component", "exp_vars"),
  786. (
  787. pytest.param(
  788. Bare.create(TEST_VAR),
  789. [TEST_VAR],
  790. id="direct-bare",
  791. ),
  792. pytest.param(
  793. Bare.create(f"foo{TEST_VAR}bar"),
  794. [FORMATTED_TEST_VAR],
  795. id="fstring-bare",
  796. ),
  797. pytest.param(
  798. rx.text(as_=TEST_VAR),
  799. [TEST_VAR],
  800. id="direct-prop",
  801. ),
  802. pytest.param(
  803. rx.heading(as_=f"foo{TEST_VAR}bar"),
  804. [FORMATTED_TEST_VAR],
  805. id="fstring-prop",
  806. ),
  807. pytest.param(
  808. rx.fragment(id=TEST_VAR),
  809. [TEST_VAR],
  810. id="direct-id",
  811. ),
  812. pytest.param(
  813. rx.fragment(id=f"foo{TEST_VAR}bar"),
  814. [FORMATTED_TEST_VAR],
  815. id="fstring-id",
  816. ),
  817. pytest.param(
  818. rx.fragment(key=TEST_VAR),
  819. [TEST_VAR],
  820. id="direct-key",
  821. ),
  822. pytest.param(
  823. rx.fragment(key=f"foo{TEST_VAR}bar"),
  824. [FORMATTED_TEST_VAR],
  825. id="fstring-key",
  826. ),
  827. pytest.param(
  828. rx.fragment(class_name=TEST_VAR),
  829. [TEST_VAR],
  830. id="direct-class_name",
  831. ),
  832. pytest.param(
  833. rx.fragment(class_name=f"foo{TEST_VAR}bar"),
  834. [FORMATTED_TEST_VAR],
  835. id="fstring-class_name",
  836. ),
  837. pytest.param(
  838. rx.fragment(special_props={TEST_VAR}),
  839. [TEST_VAR],
  840. id="direct-special_props",
  841. ),
  842. pytest.param(
  843. rx.fragment(special_props={Var.create(f"foo{TEST_VAR}bar")}),
  844. [FORMATTED_TEST_VAR],
  845. id="fstring-special_props",
  846. ),
  847. pytest.param(
  848. # custom_attrs cannot accept a Var directly as a value
  849. rx.fragment(custom_attrs={"href": f"{TEST_VAR}"}),
  850. [TEST_VAR],
  851. id="fstring-custom_attrs-nofmt",
  852. ),
  853. pytest.param(
  854. rx.fragment(custom_attrs={"href": f"foo{TEST_VAR}bar"}),
  855. [FORMATTED_TEST_VAR],
  856. id="fstring-custom_attrs",
  857. ),
  858. pytest.param(
  859. rx.fragment(background_color=TEST_VAR),
  860. [STYLE_VAR],
  861. id="direct-background_color",
  862. ),
  863. pytest.param(
  864. rx.fragment(background_color=f"foo{TEST_VAR}bar"),
  865. [STYLE_VAR],
  866. id="fstring-background_color",
  867. ),
  868. pytest.param(
  869. rx.fragment(style={"background_color": TEST_VAR}), # type: ignore
  870. [STYLE_VAR],
  871. id="direct-style-background_color",
  872. ),
  873. pytest.param(
  874. rx.fragment(style={"background_color": f"foo{TEST_VAR}bar"}), # type: ignore
  875. [STYLE_VAR],
  876. id="fstring-style-background_color",
  877. ),
  878. pytest.param(
  879. rx.fragment(on_click=EVENT_CHAIN_VAR), # type: ignore
  880. [EVENT_CHAIN_VAR],
  881. id="direct-event-chain",
  882. ),
  883. pytest.param(
  884. rx.fragment(on_click=EventState.handler),
  885. [],
  886. id="direct-event-handler",
  887. ),
  888. pytest.param(
  889. rx.fragment(on_click=EventState.handler2(TEST_VAR)), # type: ignore
  890. [ARG_VAR, TEST_VAR],
  891. id="direct-event-handler-arg",
  892. ),
  893. pytest.param(
  894. rx.fragment(on_click=EventState.handler2(EventState.v)), # type: ignore
  895. [ARG_VAR, EventState.v],
  896. id="direct-event-handler-arg2",
  897. ),
  898. pytest.param(
  899. rx.fragment(on_click=lambda: EventState.handler2(TEST_VAR)), # type: ignore
  900. [ARG_VAR, TEST_VAR],
  901. id="direct-event-handler-lambda",
  902. ),
  903. pytest.param(
  904. ComponentNestedVar.create(dict_of_dict={"a": {"b": TEST_VAR}}),
  905. [TEST_VAR_DICT_OF_DICT],
  906. id="direct-dict_of_dict",
  907. ),
  908. pytest.param(
  909. ComponentNestedVar.create(dict_of_dict={"a": {"b": f"foo{TEST_VAR}bar"}}),
  910. [FORMATTED_TEST_VAR_DICT_OF_DICT],
  911. id="fstring-dict_of_dict",
  912. ),
  913. pytest.param(
  914. ComponentNestedVar.create(list_of_list=[[TEST_VAR]]),
  915. [TEST_VAR_LIST_OF_LIST],
  916. id="direct-list_of_list",
  917. ),
  918. pytest.param(
  919. ComponentNestedVar.create(list_of_list=[[f"foo{TEST_VAR}bar"]]),
  920. [FORMATTED_TEST_VAR_LIST_OF_LIST],
  921. id="fstring-list_of_list",
  922. ),
  923. pytest.param(
  924. ComponentNestedVar.create(list_of_list_of_list=[[[TEST_VAR]]]),
  925. [TEST_VAR_LIST_OF_LIST_OF_LIST],
  926. id="direct-list_of_list_of_list",
  927. ),
  928. pytest.param(
  929. ComponentNestedVar.create(list_of_list_of_list=[[[f"foo{TEST_VAR}bar"]]]),
  930. [FORMATTED_TEST_VAR_LIST_OF_LIST_OF_LIST],
  931. id="fstring-list_of_list_of_list",
  932. ),
  933. pytest.param(
  934. ComponentNestedVar.create(list_of_dict=[{"a": TEST_VAR}]),
  935. [TEST_VAR_LIST_OF_DICT],
  936. id="direct-list_of_dict",
  937. ),
  938. pytest.param(
  939. ComponentNestedVar.create(list_of_dict=[{"a": f"foo{TEST_VAR}bar"}]),
  940. [FORMATTED_TEST_VAR_LIST_OF_DICT],
  941. id="fstring-list_of_dict",
  942. ),
  943. ),
  944. )
  945. def test_get_vars(component, exp_vars):
  946. comp_vars = sorted(component._get_vars(), key=lambda v: v._var_name)
  947. assert len(comp_vars) == len(exp_vars)
  948. for comp_var, exp_var in zip(
  949. comp_vars,
  950. sorted(exp_vars, key=lambda v: v._var_name),
  951. ):
  952. assert comp_var.equals(exp_var)
  953. def test_instantiate_all_components():
  954. """Test that all components can be instantiated."""
  955. # These components all have required arguments and cannot be trivially instantiated.
  956. untested_components = {
  957. "Card",
  958. "Cond",
  959. "DebounceInput",
  960. "Foreach",
  961. "FormControl",
  962. "Html",
  963. "Icon",
  964. "Match",
  965. "Markdown",
  966. "MultiSelect",
  967. "Option",
  968. "Popover",
  969. "Radio",
  970. "Script",
  971. "Tag",
  972. "Tfoot",
  973. "Thead",
  974. }
  975. for component_name in rx._ALL_COMPONENTS: # type: ignore
  976. if component_name in untested_components:
  977. continue
  978. component = getattr(rx, component_name)
  979. if isinstance(component, type) and issubclass(component, Component):
  980. component.create()
  981. class InvalidParentComponent(Component):
  982. """Invalid Parent Component."""
  983. ...
  984. class ValidComponent1(Component):
  985. """Test valid component."""
  986. _valid_children = ["ValidComponent2"]
  987. class ValidComponent2(Component):
  988. """Test valid component."""
  989. ...
  990. class ValidComponent3(Component):
  991. """Test valid component."""
  992. _valid_parents = ["ValidComponent2"]
  993. class ValidComponent4(Component):
  994. """Test valid component."""
  995. _invalid_children = ["InvalidComponent"]
  996. class InvalidComponent(Component):
  997. """Test invalid component."""
  998. ...
  999. valid_component1 = ValidComponent1.create
  1000. valid_component2 = ValidComponent2.create
  1001. invalid_component = InvalidComponent.create
  1002. valid_component3 = ValidComponent3.create
  1003. invalid_parent = InvalidParentComponent.create
  1004. valid_component4 = ValidComponent4.create
  1005. def test_validate_valid_children():
  1006. valid_component1(valid_component2())
  1007. valid_component1(
  1008. rx.fragment(valid_component2()),
  1009. )
  1010. valid_component1(
  1011. rx.fragment(
  1012. rx.fragment(
  1013. rx.fragment(valid_component2()),
  1014. ),
  1015. ),
  1016. )
  1017. valid_component1(
  1018. rx.cond( # type: ignore
  1019. True,
  1020. rx.fragment(valid_component2()),
  1021. rx.fragment(
  1022. rx.foreach(Var.create([1, 2, 3]), lambda x: valid_component2(x)) # type: ignore
  1023. ),
  1024. )
  1025. )
  1026. valid_component1(
  1027. rx.cond(
  1028. True,
  1029. valid_component2(),
  1030. rx.fragment(
  1031. rx.match(
  1032. "condition",
  1033. ("first", valid_component2()),
  1034. rx.fragment(valid_component2(rx.text("default"))),
  1035. )
  1036. ),
  1037. )
  1038. )
  1039. valid_component1(
  1040. rx.match(
  1041. "condition",
  1042. ("first", valid_component2()),
  1043. ("second", "third", rx.fragment(valid_component2())),
  1044. (
  1045. "fourth",
  1046. rx.cond(True, valid_component2(), rx.fragment(valid_component2())),
  1047. ),
  1048. (
  1049. "fifth",
  1050. rx.match(
  1051. "nested_condition",
  1052. ("nested_first", valid_component2()),
  1053. rx.fragment(valid_component2()),
  1054. ),
  1055. valid_component2(),
  1056. ),
  1057. )
  1058. )
  1059. def test_validate_valid_parents():
  1060. valid_component2(valid_component3())
  1061. valid_component2(
  1062. rx.fragment(valid_component3()),
  1063. )
  1064. valid_component1(
  1065. rx.fragment(
  1066. valid_component2(
  1067. rx.fragment(valid_component3()),
  1068. ),
  1069. ),
  1070. )
  1071. valid_component2(
  1072. rx.cond( # type: ignore
  1073. True,
  1074. rx.fragment(valid_component3()),
  1075. rx.fragment(
  1076. rx.foreach(
  1077. Var.create([1, 2, 3]), # type: ignore
  1078. lambda x: valid_component2(valid_component3(x)),
  1079. )
  1080. ),
  1081. )
  1082. )
  1083. valid_component2(
  1084. rx.cond(
  1085. True,
  1086. valid_component3(),
  1087. rx.fragment(
  1088. rx.match(
  1089. "condition",
  1090. ("first", valid_component3()),
  1091. rx.fragment(valid_component3(rx.text("default"))),
  1092. )
  1093. ),
  1094. )
  1095. )
  1096. valid_component2(
  1097. rx.match(
  1098. "condition",
  1099. ("first", valid_component3()),
  1100. ("second", "third", rx.fragment(valid_component3())),
  1101. (
  1102. "fourth",
  1103. rx.cond(True, valid_component3(), rx.fragment(valid_component3())),
  1104. ),
  1105. (
  1106. "fifth",
  1107. rx.match(
  1108. "nested_condition",
  1109. ("nested_first", valid_component3()),
  1110. rx.fragment(valid_component3()),
  1111. ),
  1112. valid_component3(),
  1113. ),
  1114. )
  1115. )
  1116. def test_validate_invalid_children():
  1117. with pytest.raises(ValueError):
  1118. valid_component4(invalid_component())
  1119. with pytest.raises(ValueError):
  1120. valid_component4(
  1121. rx.fragment(invalid_component()),
  1122. )
  1123. with pytest.raises(ValueError):
  1124. valid_component2(
  1125. rx.fragment(
  1126. valid_component4(
  1127. rx.fragment(invalid_component()),
  1128. ),
  1129. ),
  1130. )
  1131. with pytest.raises(ValueError):
  1132. valid_component4(
  1133. rx.cond( # type: ignore
  1134. True,
  1135. rx.fragment(invalid_component()),
  1136. rx.fragment(
  1137. rx.foreach(Var.create([1, 2, 3]), lambda x: invalid_component(x)) # type: ignore
  1138. ),
  1139. )
  1140. )
  1141. with pytest.raises(ValueError):
  1142. valid_component4(
  1143. rx.cond(
  1144. True,
  1145. invalid_component(),
  1146. rx.fragment(
  1147. rx.match(
  1148. "condition",
  1149. ("first", invalid_component()),
  1150. rx.fragment(invalid_component(rx.text("default"))),
  1151. )
  1152. ),
  1153. )
  1154. )
  1155. with pytest.raises(ValueError):
  1156. valid_component4(
  1157. rx.match(
  1158. "condition",
  1159. ("first", invalid_component()),
  1160. ("second", "third", rx.fragment(invalid_component())),
  1161. (
  1162. "fourth",
  1163. rx.cond(True, invalid_component(), rx.fragment(valid_component2())),
  1164. ),
  1165. (
  1166. "fifth",
  1167. rx.match(
  1168. "nested_condition",
  1169. ("nested_first", invalid_component()),
  1170. rx.fragment(invalid_component()),
  1171. ),
  1172. invalid_component(),
  1173. ),
  1174. )
  1175. )
  1176. def test_rename_props():
  1177. """Test that _rename_props works and is inherited."""
  1178. class C1(Component):
  1179. tag = "C1"
  1180. prop1: Var[str]
  1181. prop2: Var[str]
  1182. _rename_props = {"prop1": "renamed_prop1", "prop2": "renamed_prop2"}
  1183. class C2(C1):
  1184. tag = "C2"
  1185. prop3: Var[str]
  1186. _rename_props = {"prop2": "subclass_prop2", "prop3": "renamed_prop3"}
  1187. c1 = C1.create(prop1="prop1_1", prop2="prop2_1")
  1188. rendered_c1 = c1.render()
  1189. assert "renamed_prop1={`prop1_1`}" in rendered_c1["props"]
  1190. assert "renamed_prop2={`prop2_1`}" in rendered_c1["props"]
  1191. c2 = C2.create(prop1="prop1_2", prop2="prop2_2", prop3="prop3_2")
  1192. rendered_c2 = c2.render()
  1193. assert "renamed_prop1={`prop1_2`}" in rendered_c2["props"]
  1194. assert "subclass_prop2={`prop2_2`}" in rendered_c2["props"]
  1195. assert "renamed_prop3={`prop3_2`}" in rendered_c2["props"]
  1196. def test_deprecated_props(capsys):
  1197. """Assert that deprecated underscore suffix props are translated.
  1198. Args:
  1199. capsys: Pytest fixture for capturing stdout and stderr.
  1200. """
  1201. class C1(Component):
  1202. tag = "C1"
  1203. type: Var[str]
  1204. min: Var[str]
  1205. max: Var[str]
  1206. # No warnings are emitted when using the new prop names.
  1207. c1_1 = C1.create(type="type1", min="min1", max="max1")
  1208. out_err = capsys.readouterr()
  1209. assert not out_err.err
  1210. assert not out_err.out
  1211. c1_1_render = c1_1.render()
  1212. assert "type={`type1`}" in c1_1_render["props"]
  1213. assert "min={`min1`}" in c1_1_render["props"]
  1214. assert "max={`max1`}" in c1_1_render["props"]
  1215. # Deprecation warning is emitted with underscore suffix,
  1216. # but the component still works.
  1217. c1_2 = C1.create(type_="type2", min_="min2", max_="max2")
  1218. out_err = capsys.readouterr()
  1219. assert out_err.out.count("DeprecationWarning:") == 3
  1220. assert not out_err.err
  1221. c1_2_render = c1_2.render()
  1222. assert "type={`type2`}" in c1_2_render["props"]
  1223. assert "min={`min2`}" in c1_2_render["props"]
  1224. assert "max={`max2`}" in c1_2_render["props"]
  1225. class C2(Component):
  1226. tag = "C2"
  1227. type_: Var[str]
  1228. min_: Var[str]
  1229. max_: Var[str]
  1230. # No warnings are emitted if the actual prop has an underscore suffix
  1231. c2_1 = C2.create(type_="type1", min_="min1", max_="max1")
  1232. out_err = capsys.readouterr()
  1233. assert not out_err.err
  1234. assert not out_err.out
  1235. c2_1_render = c2_1.render()
  1236. assert "type={`type1`}" in c2_1_render["props"]
  1237. assert "min={`min1`}" in c2_1_render["props"]
  1238. assert "max={`max1`}" in c2_1_render["props"]
  1239. def test_custom_component_get_imports():
  1240. class Inner(Component):
  1241. tag = "Inner"
  1242. library = "inner"
  1243. class Other(Component):
  1244. tag = "Other"
  1245. library = "other"
  1246. @rx.memo
  1247. def wrapper():
  1248. return Inner.create()
  1249. @rx.memo
  1250. def outer(c: Component):
  1251. return Other.create(c)
  1252. custom_comp = wrapper()
  1253. # Inner is not imported directly, but it is imported by the custom component.
  1254. assert "inner" not in custom_comp._get_all_imports()
  1255. # The imports are only resolved during compilation.
  1256. _, _, imports_inner = compile_components(custom_comp._get_all_custom_components())
  1257. assert "inner" in imports_inner
  1258. outer_comp = outer(c=wrapper())
  1259. # Libraries are not imported directly, but are imported by the custom component.
  1260. assert "inner" not in outer_comp._get_all_imports()
  1261. assert "other" not in outer_comp._get_all_imports()
  1262. # The imports are only resolved during compilation.
  1263. _, _, imports_outer = compile_components(outer_comp._get_all_custom_components())
  1264. assert "inner" in imports_outer
  1265. assert "other" in imports_outer
  1266. def test_custom_component_declare_event_handlers_in_fields():
  1267. class ReferenceComponent(Component):
  1268. def get_event_triggers(self) -> Dict[str, Any]:
  1269. """Test controlled triggers.
  1270. Returns:
  1271. Test controlled triggers.
  1272. """
  1273. return {
  1274. **super().get_event_triggers(),
  1275. "on_a": lambda e0: [e0],
  1276. "on_b": lambda e0: [e0.target.value],
  1277. "on_c": lambda e0: [],
  1278. "on_d": lambda: [],
  1279. "on_e": lambda: [],
  1280. "on_f": lambda a, b, c: [c, b, a],
  1281. }
  1282. class TestComponent(Component):
  1283. on_a: EventHandler[lambda e0: [e0]]
  1284. on_b: EventHandler[lambda e0: [e0.target.value]]
  1285. on_c: EventHandler[lambda e0: []]
  1286. on_d: EventHandler[lambda: []]
  1287. on_e: EventHandler
  1288. on_f: EventHandler[lambda a, b, c: [c, b, a]]
  1289. custom_component = ReferenceComponent.create()
  1290. test_component = TestComponent.create()
  1291. custom_triggers = custom_component.get_event_triggers()
  1292. test_triggers = test_component.get_event_triggers()
  1293. assert custom_triggers.keys() == test_triggers.keys()
  1294. for trigger_name in custom_component.get_event_triggers():
  1295. for v1, v2 in zip(
  1296. parse_args_spec(test_triggers[trigger_name]),
  1297. parse_args_spec(custom_triggers[trigger_name]),
  1298. ):
  1299. assert v1.equals(v2)