test_component.py 8.9 KB

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