test_state_inheritance.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  1. """Test state inheritance."""
  2. import time
  3. from typing import Generator
  4. import pytest
  5. from selenium.webdriver.common.by import By
  6. from reflex.testing import DEFAULT_TIMEOUT, AppHarness, WebDriver
  7. def raises_alert(driver: WebDriver, element: str) -> None:
  8. """Click an element and check that an alert is raised.
  9. Args:
  10. driver: WebDriver instance.
  11. element: The element to click.
  12. """
  13. btn = driver.find_element(By.ID, element)
  14. btn.click()
  15. time.sleep(0.2) # wait for the alert to appear
  16. alert = driver.switch_to.alert
  17. assert alert.text == "clicked"
  18. alert.accept()
  19. def StateInheritance():
  20. """Test that state inheritance works as expected."""
  21. import reflex as rx
  22. class ChildMixin:
  23. # mixin basevars only work with pydantic/rx.Base models
  24. # child_mixin: str = "child_mixin"
  25. @rx.var
  26. def computed_child_mixin(self) -> str:
  27. return "computed_child_mixin"
  28. class Mixin(ChildMixin):
  29. # mixin basevars only work with pydantic/rx.Base models
  30. # mixin: str = "mixin"
  31. @rx.var
  32. def computed_mixin(self) -> str:
  33. return "computed_mixin"
  34. def on_click_mixin(self):
  35. return rx.call_script("alert('clicked')")
  36. class OtherMixin(rx.Base):
  37. other_mixin: str = "other_mixin"
  38. other_mixin_clicks: int = 0
  39. @rx.var
  40. def computed_other_mixin(self) -> str:
  41. return self.other_mixin
  42. def on_click_other_mixin(self):
  43. self.other_mixin_clicks += 1
  44. self.other_mixin = (
  45. f"{self.__class__.__name__}.clicked.{self.other_mixin_clicks}"
  46. )
  47. class Base1(rx.State, Mixin):
  48. base1: str = "base1"
  49. @rx.var
  50. def computed_basevar(self) -> str:
  51. return "computed_basevar1"
  52. class Base2(rx.State):
  53. base2: str = "base2"
  54. @rx.var
  55. def computed_basevar(self) -> str:
  56. return "computed_basevar2"
  57. class Child1(Base1, OtherMixin):
  58. pass
  59. class Child2(Base2, Mixin, OtherMixin):
  60. pass
  61. class Child3(Child2):
  62. child3: str = "child3"
  63. @rx.var
  64. def computed_childvar(self) -> str:
  65. return "computed_childvar"
  66. def index() -> rx.Component:
  67. return rx.vstack(
  68. rx.chakra.input(
  69. id="token", value=Base1.router.session.client_token, is_read_only=True
  70. ),
  71. # Base 1
  72. rx.heading(Base1.computed_mixin, id="base1-computed_mixin"),
  73. rx.heading(Base1.computed_basevar, id="base1-computed_basevar"),
  74. rx.heading(Base1.computed_child_mixin, id="base1-child-mixin"),
  75. rx.heading(Base1.base1, id="base1-base1"),
  76. rx.button(
  77. "Base1.on_click_mixin",
  78. on_click=Base1.on_click_mixin, # type: ignore
  79. id="base1-mixin-btn",
  80. ),
  81. # Base 2
  82. rx.heading(Base2.computed_basevar, id="base2-computed_basevar"),
  83. rx.heading(Base2.base2, id="base2-base2"),
  84. # Child 1
  85. rx.heading(Child1.computed_basevar, id="child1-computed_basevar"),
  86. rx.heading(Child1.computed_mixin, id="child1-computed_mixin"),
  87. rx.heading(Child1.computed_other_mixin, id="child1-other-mixin"),
  88. rx.heading(Child1.computed_child_mixin, id="child1-child-mixin"),
  89. rx.heading(Child1.base1, id="child1-base1"),
  90. rx.heading(Child1.other_mixin, id="child1-other_mixin"),
  91. rx.button(
  92. "Child1.on_click_other_mixin",
  93. on_click=Child1.on_click_other_mixin, # type: ignore
  94. id="child1-other-mixin-btn",
  95. ),
  96. # Child 2
  97. rx.heading(Child2.computed_basevar, id="child2-computed_basevar"),
  98. rx.heading(Child2.computed_mixin, id="child2-computed_mixin"),
  99. rx.heading(Child2.computed_other_mixin, id="child2-other-mixin"),
  100. rx.heading(Child2.computed_child_mixin, id="child2-child-mixin"),
  101. rx.heading(Child2.base2, id="child2-base2"),
  102. rx.heading(Child2.other_mixin, id="child2-other_mixin"),
  103. rx.button(
  104. "Child2.on_click_mixin",
  105. on_click=Child2.on_click_mixin, # type: ignore
  106. id="child2-mixin-btn",
  107. ),
  108. rx.button(
  109. "Child2.on_click_other_mixin",
  110. on_click=Child2.on_click_other_mixin, # type: ignore
  111. id="child2-other-mixin-btn",
  112. ),
  113. # Child 3
  114. rx.heading(Child3.computed_basevar, id="child3-computed_basevar"),
  115. rx.heading(Child3.computed_mixin, id="child3-computed_mixin"),
  116. rx.heading(Child3.computed_other_mixin, id="child3-other-mixin"),
  117. rx.heading(Child3.computed_childvar, id="child3-computed_childvar"),
  118. rx.heading(Child3.computed_child_mixin, id="child3-child-mixin"),
  119. rx.heading(Child3.child3, id="child3-child3"),
  120. rx.heading(Child3.base2, id="child3-base2"),
  121. rx.heading(Child3.other_mixin, id="child3-other_mixin"),
  122. rx.button(
  123. "Child3.on_click_mixin",
  124. on_click=Child3.on_click_mixin, # type: ignore
  125. id="child3-mixin-btn",
  126. ),
  127. rx.button(
  128. "Child3.on_click_other_mixin",
  129. on_click=Child3.on_click_other_mixin, # type: ignore
  130. id="child3-other-mixin-btn",
  131. ),
  132. )
  133. app = rx.App()
  134. app.add_page(index)
  135. @pytest.fixture(scope="session")
  136. def state_inheritance(
  137. tmp_path_factory,
  138. ) -> Generator[AppHarness, None, None]:
  139. """Start StateInheritance app at tmp_path via AppHarness.
  140. Args:
  141. tmp_path_factory: pytest tmp_path_factory fixture
  142. Yields:
  143. running AppHarness instance
  144. """
  145. with AppHarness.create(
  146. root=tmp_path_factory.mktemp(f"state_inheritance"),
  147. app_source=StateInheritance, # type: ignore
  148. ) as harness:
  149. yield harness
  150. @pytest.fixture
  151. def driver(state_inheritance: AppHarness) -> Generator[WebDriver, None, None]:
  152. """Get an instance of the browser open to the state_inheritance app.
  153. Args:
  154. state_inheritance: harness for StateInheritance app
  155. Yields:
  156. WebDriver instance.
  157. """
  158. assert state_inheritance.app_instance is not None, "app is not running"
  159. driver = state_inheritance.frontend()
  160. try:
  161. yield driver
  162. finally:
  163. driver.quit()
  164. @pytest.fixture()
  165. def token(state_inheritance: AppHarness, driver: WebDriver) -> str:
  166. """Get a function that returns the active token.
  167. Args:
  168. state_inheritance: harness for StateInheritance app.
  169. driver: WebDriver instance.
  170. Returns:
  171. The token for the connected client
  172. """
  173. assert state_inheritance.app_instance is not None
  174. token_input = driver.find_element(By.ID, "token")
  175. assert token_input
  176. # wait for the backend connection to send the token
  177. token = state_inheritance.poll_for_value(token_input, timeout=DEFAULT_TIMEOUT * 2)
  178. assert token is not None
  179. return token
  180. def test_state_inheritance(
  181. state_inheritance: AppHarness,
  182. driver: WebDriver,
  183. token: str,
  184. ):
  185. """Test that background tasks work as expected.
  186. Args:
  187. state_inheritance: harness for StateInheritance app.
  188. driver: WebDriver instance.
  189. token: The token for the connected client.
  190. """
  191. assert state_inheritance.app_instance is not None
  192. # Initial State values Test
  193. # Base 1
  194. base1_mixin = driver.find_element(By.ID, "base1-computed_mixin")
  195. assert base1_mixin.text == "computed_mixin"
  196. base1_computed_basevar = driver.find_element(By.ID, "base1-computed_basevar")
  197. assert base1_computed_basevar.text == "computed_basevar1"
  198. base1_computed_child_mixin = driver.find_element(By.ID, "base1-child-mixin")
  199. assert base1_computed_child_mixin.text == "computed_child_mixin"
  200. base1_base1 = driver.find_element(By.ID, "base1-base1")
  201. assert base1_base1.text == "base1"
  202. # Base 2
  203. base2_computed_basevar = driver.find_element(By.ID, "base2-computed_basevar")
  204. assert base2_computed_basevar.text == "computed_basevar2"
  205. base2_base2 = driver.find_element(By.ID, "base2-base2")
  206. assert base2_base2.text == "base2"
  207. # Child 1
  208. child1_computed_basevar = driver.find_element(By.ID, "child1-computed_basevar")
  209. assert child1_computed_basevar.text == "computed_basevar1"
  210. child1_mixin = driver.find_element(By.ID, "child1-computed_mixin")
  211. assert child1_mixin.text == "computed_mixin"
  212. child1_computed_other_mixin = driver.find_element(By.ID, "child1-other-mixin")
  213. assert child1_computed_other_mixin.text == "other_mixin"
  214. child1_computed_child_mixin = driver.find_element(By.ID, "child1-child-mixin")
  215. assert child1_computed_child_mixin.text == "computed_child_mixin"
  216. child1_base1 = driver.find_element(By.ID, "child1-base1")
  217. assert child1_base1.text == "base1"
  218. child1_other_mixin = driver.find_element(By.ID, "child1-other_mixin")
  219. assert child1_other_mixin.text == "other_mixin"
  220. # Child 2
  221. child2_computed_basevar = driver.find_element(By.ID, "child2-computed_basevar")
  222. assert child2_computed_basevar.text == "computed_basevar2"
  223. child2_mixin = driver.find_element(By.ID, "child2-computed_mixin")
  224. assert child2_mixin.text == "computed_mixin"
  225. child2_computed_other_mixin = driver.find_element(By.ID, "child2-other-mixin")
  226. assert child2_computed_other_mixin.text == "other_mixin"
  227. child2_computed_child_mixin = driver.find_element(By.ID, "child2-child-mixin")
  228. assert child2_computed_child_mixin.text == "computed_child_mixin"
  229. child2_base2 = driver.find_element(By.ID, "child2-base2")
  230. assert child2_base2.text == "base2"
  231. child2_other_mixin = driver.find_element(By.ID, "child2-other_mixin")
  232. assert child2_other_mixin.text == "other_mixin"
  233. # Child 3
  234. child3_computed_basevar = driver.find_element(By.ID, "child3-computed_basevar")
  235. assert child3_computed_basevar.text == "computed_basevar2"
  236. child3_mixin = driver.find_element(By.ID, "child3-computed_mixin")
  237. assert child3_mixin.text == "computed_mixin"
  238. child3_computed_other_mixin = driver.find_element(By.ID, "child3-other-mixin")
  239. assert child3_computed_other_mixin.text == "other_mixin"
  240. child3_computed_childvar = driver.find_element(By.ID, "child3-computed_childvar")
  241. assert child3_computed_childvar.text == "computed_childvar"
  242. child3_computed_child_mixin = driver.find_element(By.ID, "child3-child-mixin")
  243. assert child3_computed_child_mixin.text == "computed_child_mixin"
  244. child3_child3 = driver.find_element(By.ID, "child3-child3")
  245. assert child3_child3.text == "child3"
  246. child3_base2 = driver.find_element(By.ID, "child3-base2")
  247. assert child3_base2.text == "base2"
  248. child3_other_mixin = driver.find_element(By.ID, "child3-other_mixin")
  249. assert child3_other_mixin.text == "other_mixin"
  250. # Event Handler Tests
  251. raises_alert(driver, "base1-mixin-btn")
  252. raises_alert(driver, "child2-mixin-btn")
  253. raises_alert(driver, "child3-mixin-btn")
  254. child1_other_mixin_btn = driver.find_element(By.ID, "child1-other-mixin-btn")
  255. child1_other_mixin_btn.click()
  256. child1_other_mixin_value = state_inheritance.poll_for_content(
  257. child1_other_mixin, exp_not_equal="other_mixin"
  258. )
  259. child1_computed_mixin_value = state_inheritance.poll_for_content(
  260. child1_computed_other_mixin, exp_not_equal="other_mixin"
  261. )
  262. assert child1_other_mixin_value == "Child1.clicked.1"
  263. assert child1_computed_mixin_value == "Child1.clicked.1"
  264. child2_other_mixin_btn = driver.find_element(By.ID, "child2-other-mixin-btn")
  265. child2_other_mixin_btn.click()
  266. child2_other_mixin_value = state_inheritance.poll_for_content(
  267. child2_other_mixin, exp_not_equal="other_mixin"
  268. )
  269. child2_computed_mixin_value = state_inheritance.poll_for_content(
  270. child2_computed_other_mixin, exp_not_equal="other_mixin"
  271. )
  272. child3_other_mixin_value = state_inheritance.poll_for_content(
  273. child3_other_mixin, exp_not_equal="other_mixin"
  274. )
  275. child3_computed_mixin_value = state_inheritance.poll_for_content(
  276. child3_computed_other_mixin, exp_not_equal="other_mixin"
  277. )
  278. assert child2_other_mixin_value == "Child2.clicked.1"
  279. assert child2_computed_mixin_value == "Child2.clicked.1"
  280. assert child3_other_mixin_value == "Child2.clicked.1"
  281. assert child3_computed_mixin_value == "Child2.clicked.1"
  282. child3_other_mixin_btn = driver.find_element(By.ID, "child3-other-mixin-btn")
  283. child3_other_mixin_btn.click()
  284. child2_other_mixin_value = state_inheritance.poll_for_content(
  285. child2_other_mixin, exp_not_equal="other_mixin"
  286. )
  287. child2_computed_mixin_value = state_inheritance.poll_for_content(
  288. child2_computed_other_mixin, exp_not_equal="other_mixin"
  289. )
  290. child3_other_mixin_value = state_inheritance.poll_for_content(
  291. child3_other_mixin, exp_not_equal="other_mixin"
  292. )
  293. child3_computed_mixin_value = state_inheritance.poll_for_content(
  294. child3_computed_other_mixin, exp_not_equal="other_mixin"
  295. )
  296. assert child2_other_mixin_value == "Child2.clicked.2"
  297. assert child2_computed_mixin_value == "Child2.clicked.2"
  298. assert child3_other_mixin.text == "Child2.clicked.2"
  299. assert child3_computed_other_mixin.text == "Child2.clicked.2"