test_component.py 8.8 KB

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