test_computed_vars.py 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. """Test computed vars."""
  2. from __future__ import annotations
  3. import time
  4. from typing import Generator
  5. import pytest
  6. from selenium.webdriver.common.by import By
  7. from reflex.testing import DEFAULT_TIMEOUT, AppHarness, WebDriver
  8. def ComputedVars():
  9. """Test app for computed vars."""
  10. import reflex as rx
  11. class StateMixin(rx.State, mixin=True):
  12. pass
  13. class State(StateMixin, rx.State):
  14. count: int = 0
  15. # cached var with dep on count
  16. @rx.cached_var(interval=15)
  17. def count1(self) -> int:
  18. return self.count
  19. # same as above, different notation
  20. @rx.var(interval=15, cache=True)
  21. def count2(self) -> int:
  22. return self.count
  23. # explicit disabled auto_deps
  24. @rx.var(interval=15, cache=True, auto_deps=False)
  25. def count3(self) -> int:
  26. # this will not add deps, because auto_deps is False
  27. print(self.count1)
  28. print(self.count2)
  29. return self.count
  30. # explicit dependency on count var
  31. @rx.var(cache=True, deps=["count"], auto_deps=False)
  32. def depends_on_count(self) -> int:
  33. return self.count
  34. # explicit dependency on count1 var
  35. @rx.var(cache=True, deps=[count1], auto_deps=False)
  36. def depends_on_count1(self) -> int:
  37. return self.count
  38. @rx.var(deps=[count3], auto_deps=False, cache=True)
  39. def depends_on_count3(self) -> int:
  40. return self.count
  41. def increment(self):
  42. self.count += 1
  43. def mark_dirty(self):
  44. self._mark_dirty()
  45. assert State.backend_vars == {}
  46. def index() -> rx.Component:
  47. return rx.center(
  48. rx.vstack(
  49. rx.input(
  50. id="token",
  51. value=State.router.session.client_token,
  52. is_read_only=True,
  53. ),
  54. rx.button("Increment", on_click=State.increment, id="increment"),
  55. rx.button("Do nothing", on_click=State.mark_dirty, id="mark_dirty"),
  56. rx.text("count:"),
  57. rx.text(State.count, id="count"),
  58. rx.text("count1:"),
  59. rx.text(State.count1, id="count1"),
  60. rx.text("count2:"),
  61. rx.text(State.count2, id="count2"),
  62. rx.text("count3:"),
  63. rx.text(State.count3, id="count3"),
  64. rx.text("depends_on_count:"),
  65. rx.text(
  66. State.depends_on_count,
  67. id="depends_on_count",
  68. ),
  69. rx.text("depends_on_count1:"),
  70. rx.text(
  71. State.depends_on_count1,
  72. id="depends_on_count1",
  73. ),
  74. rx.text("depends_on_count3:"),
  75. rx.text(
  76. State.depends_on_count3,
  77. id="depends_on_count3",
  78. ),
  79. ),
  80. )
  81. # raise Exception(State.count3._deps(objclass=State))
  82. app = rx.App()
  83. app.add_page(index)
  84. @pytest.fixture(scope="module")
  85. def computed_vars(
  86. tmp_path_factory: pytest.TempPathFactory,
  87. ) -> Generator[AppHarness, None, None]:
  88. """Start ComputedVars app at tmp_path via AppHarness.
  89. Args:
  90. tmp_path_factory: pytest tmp_path_factory fixture
  91. Yields:
  92. running AppHarness instance
  93. """
  94. with AppHarness.create(
  95. root=tmp_path_factory.mktemp(f"computed_vars"),
  96. app_source=ComputedVars, # type: ignore
  97. ) as harness:
  98. yield harness
  99. @pytest.fixture
  100. def driver(computed_vars: AppHarness) -> Generator[WebDriver, None, None]:
  101. """Get an instance of the browser open to the computed_vars app.
  102. Args:
  103. computed_vars: harness for ComputedVars app
  104. Yields:
  105. WebDriver instance.
  106. """
  107. assert computed_vars.app_instance is not None, "app is not running"
  108. driver = computed_vars.frontend()
  109. try:
  110. yield driver
  111. finally:
  112. driver.quit()
  113. @pytest.fixture()
  114. def token(computed_vars: AppHarness, driver: WebDriver) -> str:
  115. """Get a function that returns the active token.
  116. Args:
  117. computed_vars: harness for ComputedVars app.
  118. driver: WebDriver instance.
  119. Returns:
  120. The token for the connected client
  121. """
  122. assert computed_vars.app_instance is not None
  123. token_input = driver.find_element(By.ID, "token")
  124. assert token_input
  125. # wait for the backend connection to send the token
  126. token = computed_vars.poll_for_value(token_input, timeout=DEFAULT_TIMEOUT * 2)
  127. assert token is not None
  128. return token
  129. def test_computed_vars(
  130. computed_vars: AppHarness,
  131. driver: WebDriver,
  132. token: str,
  133. ):
  134. """Test that computed vars are working as expected.
  135. Args:
  136. computed_vars: harness for ComputedVars app.
  137. driver: WebDriver instance.
  138. token: The token for the connected client.
  139. """
  140. assert computed_vars.app_instance is not None
  141. count = driver.find_element(By.ID, "count")
  142. assert count
  143. assert count.text == "0"
  144. count1 = driver.find_element(By.ID, "count1")
  145. assert count1
  146. assert count1.text == "0"
  147. count2 = driver.find_element(By.ID, "count2")
  148. assert count2
  149. assert count2.text == "0"
  150. count3 = driver.find_element(By.ID, "count3")
  151. assert count3
  152. assert count3.text == "0"
  153. depends_on_count = driver.find_element(By.ID, "depends_on_count")
  154. assert depends_on_count
  155. assert depends_on_count.text == "0"
  156. depends_on_count1 = driver.find_element(By.ID, "depends_on_count1")
  157. assert depends_on_count1
  158. assert depends_on_count1.text == "0"
  159. depends_on_count3 = driver.find_element(By.ID, "depends_on_count3")
  160. assert depends_on_count3
  161. assert depends_on_count3.text == "0"
  162. increment = driver.find_element(By.ID, "increment")
  163. assert increment.is_enabled()
  164. mark_dirty = driver.find_element(By.ID, "mark_dirty")
  165. assert mark_dirty.is_enabled()
  166. mark_dirty.click()
  167. increment.click()
  168. assert computed_vars.poll_for_content(count, timeout=2, exp_not_equal="0") == "1"
  169. assert computed_vars.poll_for_content(count1, timeout=2, exp_not_equal="0") == "1"
  170. assert computed_vars.poll_for_content(count2, timeout=2, exp_not_equal="0") == "1"
  171. assert (
  172. computed_vars.poll_for_content(depends_on_count, timeout=2, exp_not_equal="0")
  173. == "1"
  174. )
  175. mark_dirty.click()
  176. with pytest.raises(TimeoutError):
  177. _ = computed_vars.poll_for_content(count3, timeout=5, exp_not_equal="0")
  178. time.sleep(10)
  179. assert count3.text == "0"
  180. assert depends_on_count3.text == "0"
  181. mark_dirty.click()
  182. assert computed_vars.poll_for_content(count3, timeout=2, exp_not_equal="0") == "1"
  183. assert depends_on_count3.text == "1"