test_component.py 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  1. from typing import Dict, List, Type
  2. import pytest
  3. import pynecone as pc
  4. from pynecone.components.component import Component, CustomComponent, custom_component
  5. from pynecone.components.layout.box import Box
  6. from pynecone.event import EVENT_ARG, EVENT_TRIGGERS, EventHandler
  7. from pynecone.state import State
  8. from pynecone.style import Style
  9. from pynecone.utils import imports
  10. from pynecone.var import Var
  11. @pytest.fixture
  12. def TestState():
  13. class TestState(State):
  14. num: int
  15. def do_something(self):
  16. pass
  17. def do_something_arg(self, arg):
  18. pass
  19. return TestState
  20. @pytest.fixture
  21. def component1() -> Type[Component]:
  22. """A test component.
  23. Returns:
  24. A test component.
  25. """
  26. class TestComponent1(Component):
  27. # A test string prop.
  28. text: Var[str]
  29. # A test number prop.
  30. number: Var[int]
  31. def _get_imports(self) -> imports.ImportDict:
  32. return {"react": {"Component"}}
  33. def _get_custom_code(self) -> str:
  34. return "console.log('component1')"
  35. return TestComponent1
  36. @pytest.fixture
  37. def component2() -> Type[Component]:
  38. """A test component.
  39. Returns:
  40. A test component.
  41. """
  42. class TestComponent2(Component):
  43. # A test list prop.
  44. arr: Var[List[str]]
  45. @classmethod
  46. def get_controlled_triggers(cls) -> Dict[str, Var]:
  47. """Test controlled triggers.
  48. Returns:
  49. Test controlled triggers.
  50. """
  51. return {
  52. "on_open": EVENT_ARG,
  53. "on_close": EVENT_ARG,
  54. }
  55. def _get_imports(self) -> imports.ImportDict:
  56. return {"react-redux": {"connect"}}
  57. def _get_custom_code(self) -> str:
  58. return "console.log('component2')"
  59. return TestComponent2
  60. @pytest.fixture
  61. def on_click1() -> EventHandler:
  62. """A sample on click function.
  63. Returns:
  64. A sample on click function.
  65. """
  66. def on_click1():
  67. pass
  68. return EventHandler(fn=on_click1)
  69. @pytest.fixture
  70. def on_click2() -> EventHandler:
  71. """A sample on click function.
  72. Returns:
  73. A sample on click function.
  74. """
  75. def on_click2():
  76. pass
  77. return EventHandler(fn=on_click2)
  78. @pytest.fixture
  79. def my_component():
  80. """A test component function.
  81. Returns:
  82. A test component function.
  83. """
  84. def my_component(prop1: Var[str], prop2: Var[int]):
  85. return Box.create(prop1, prop2)
  86. return my_component
  87. def test_set_style_attrs(component1):
  88. """Test that style attributes are set in the dict.
  89. Args:
  90. component1: A test component.
  91. """
  92. component = component1(color="white", text_align="center")
  93. assert component.style["color"] == "white"
  94. assert component.style["textAlign"] == "center"
  95. def test_create_component(component1):
  96. """Test that the component is created correctly.
  97. Args:
  98. component1: A test component.
  99. """
  100. children = [component1() for _ in range(3)]
  101. attrs = {"color": "white", "text_align": "center"}
  102. c = component1.create(*children, **attrs)
  103. assert isinstance(c, component1)
  104. assert c.children == children
  105. assert c.style == {"color": "white", "textAlign": "center"}
  106. def test_add_style(component1, component2):
  107. """Test adding a style to a component.
  108. Args:
  109. component1: A test component.
  110. component2: A test component.
  111. """
  112. style = {
  113. component1: Style({"color": "white"}),
  114. component2: Style({"color": "black"}),
  115. }
  116. c1 = component1().add_style(style) # type: ignore
  117. c2 = component2().add_style(style) # type: ignore
  118. assert c1.style["color"] == "white"
  119. assert c2.style["color"] == "black"
  120. def test_get_imports(component1, component2):
  121. """Test getting the imports of a component.
  122. Args:
  123. component1: A test component.
  124. component2: A test component.
  125. """
  126. c1 = component1.create()
  127. c2 = component2.create(c1)
  128. assert c1.get_imports() == {"react": {"Component"}}
  129. assert c2.get_imports() == {"react-redux": {"connect"}, "react": {"Component"}}
  130. def test_get_custom_code(component1, component2):
  131. """Test getting the custom code of a component.
  132. Args:
  133. component1: A test component.
  134. component2: A test component.
  135. """
  136. # Check that the code gets compiled correctly.
  137. c1 = component1.create()
  138. c2 = component2.create()
  139. assert c1.get_custom_code() == {"console.log('component1')"}
  140. assert c2.get_custom_code() == {"console.log('component2')"}
  141. # Check that nesting components compiles both codes.
  142. c1 = component1.create(c2)
  143. assert c1.get_custom_code() == {
  144. "console.log('component1')",
  145. "console.log('component2')",
  146. }
  147. # Check that code is not duplicated.
  148. c1 = component1.create(c2, c2, c1, c1)
  149. assert c1.get_custom_code() == {
  150. "console.log('component1')",
  151. "console.log('component2')",
  152. }
  153. def test_get_props(component1, component2):
  154. """Test that the props are set correctly.
  155. Args:
  156. component1: A test component.
  157. component2: A test component.
  158. """
  159. assert component1.get_props() == {"text", "number"}
  160. assert component2.get_props() == {"arr"}
  161. @pytest.mark.parametrize(
  162. "text,number",
  163. [
  164. ("", 0),
  165. ("test", 1),
  166. ("hi", -13),
  167. ],
  168. )
  169. def test_valid_props(component1, text: str, number: int):
  170. """Test that we can construct a component with valid props.
  171. Args:
  172. component1: A test component.
  173. text: A test string.
  174. number: A test number.
  175. """
  176. c = component1.create(text=text, number=number)
  177. assert c.text == text
  178. assert c.number == number
  179. @pytest.mark.parametrize(
  180. "text,number", [("", "bad_string"), (13, 1), (None, 1), ("test", [1, 2, 3])]
  181. )
  182. def test_invalid_prop_type(component1, text: str, number: int):
  183. """Test that an invalid prop type raises an error.
  184. Args:
  185. component1: A test component.
  186. text: A test string.
  187. number: A test number.
  188. """
  189. # Check that
  190. with pytest.raises(TypeError):
  191. component1.create(text=text, number=number)
  192. def test_var_props(component1, TestState):
  193. """Test that we can set a Var prop.
  194. Args:
  195. component1: A test component.
  196. TestState: A test state.
  197. """
  198. c1 = component1.create(text="hello", number=TestState.num)
  199. assert c1.number == TestState.num
  200. def test_get_controlled_triggers(component1, component2):
  201. """Test that we can get the controlled triggers of a component.
  202. Args:
  203. component1: A test component.
  204. component2: A test component.
  205. """
  206. assert component1.get_controlled_triggers() == dict()
  207. assert set(component2.get_controlled_triggers()) == {"on_open", "on_close"}
  208. def test_get_triggers(component1, component2):
  209. """Test that we can get the triggers of a component.
  210. Args:
  211. component1: A test component.
  212. component2: A test component.
  213. """
  214. assert component1.get_triggers() == EVENT_TRIGGERS
  215. assert component2.get_triggers() == {"on_open", "on_close"} | EVENT_TRIGGERS
  216. def test_create_custom_component(my_component):
  217. """Test that we can create a custom component.
  218. Args:
  219. my_component: A test custom component.
  220. """
  221. component = CustomComponent(component_fn=my_component, prop1="test", prop2=1)
  222. assert component.tag == "MyComponent"
  223. assert component.get_props() == set()
  224. assert component.get_custom_components() == {component}
  225. def test_custom_component_hash(my_component):
  226. """Test that the hash of a custom component is correct.
  227. Args:
  228. my_component: A test custom component.
  229. """
  230. component1 = CustomComponent(component_fn=my_component, prop1="test", prop2=1)
  231. component2 = CustomComponent(component_fn=my_component, prop1="test", prop2=2)
  232. assert {component1, component2} == {component1}
  233. def test_custom_component_wrapper():
  234. """Test that the wrapper of a custom component is correct."""
  235. @custom_component
  236. def my_component(width: Var[int], color: Var[str]):
  237. return pc.box(
  238. width=width,
  239. color=color,
  240. )
  241. ccomponent = my_component(
  242. pc.text("child"), width=Var.create(1), color=Var.create("red")
  243. )
  244. assert isinstance(ccomponent, CustomComponent)
  245. assert len(ccomponent.children) == 1
  246. assert isinstance(ccomponent.children[0], pc.Text)
  247. component = ccomponent.get_component()
  248. assert isinstance(component, Box)
  249. def test_invalid_event_handler_args(component2, TestState):
  250. """Test that an invalid event handler raises an error.
  251. Args:
  252. component2: A test component.
  253. TestState: A test state.
  254. """
  255. # Uncontrolled event handlers should not take args.
  256. # This is okay.
  257. component2.create(on_click=TestState.do_something)
  258. # This is not okay.
  259. with pytest.raises(ValueError):
  260. component2.create(on_click=TestState.do_something_arg)
  261. # However lambdas are okay.
  262. component2.create(on_click=lambda: TestState.do_something_arg(1))
  263. component2.create(
  264. on_click=lambda: [TestState.do_something_arg(1), TestState.do_something]
  265. )
  266. component2.create(
  267. on_click=lambda: [TestState.do_something_arg(1), TestState.do_something()]
  268. )
  269. # Controlled event handlers should take args.
  270. # This is okay.
  271. component2.create(on_open=TestState.do_something_arg)
  272. # This is not okay.
  273. with pytest.raises(ValueError):
  274. component2.create(on_open=TestState.do_something)
  275. with pytest.raises(ValueError):
  276. component2.create(on_open=[TestState.do_something_arg, TestState.do_something])