test_input.py 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. """Integration tests for text input and related components."""
  2. import time
  3. from typing import Generator
  4. import pytest
  5. from selenium.webdriver.common.by import By
  6. from selenium.webdriver.common.keys import Keys
  7. from reflex.testing import AppHarness
  8. def FullyControlledInput():
  9. """App using a fully controlled input with debounce wrapper."""
  10. import reflex as rx
  11. class State(rx.State):
  12. text: str = "initial"
  13. app = rx.App(state=State)
  14. @app.add_page
  15. def index():
  16. return rx.fragment(
  17. rx.debounce_input(
  18. rx.input(
  19. on_change=State.set_text, id="debounce_input_input" # type: ignore
  20. ),
  21. value=State.text,
  22. debounce_timeout=0,
  23. ),
  24. rx.input(value=State.text, id="value_input"),
  25. rx.input(on_change=State.set_text, id="on_change_input"), # type: ignore
  26. rx.button("CLEAR", on_click=rx.set_value("on_change_input", "")),
  27. )
  28. app.compile()
  29. @pytest.fixture()
  30. def fully_controlled_input(tmp_path) -> Generator[AppHarness, None, None]:
  31. """Start FullyControlledInput app at tmp_path via AppHarness.
  32. Args:
  33. tmp_path: pytest tmp_path fixture
  34. Yields:
  35. running AppHarness instance
  36. """
  37. with AppHarness.create(
  38. root=tmp_path,
  39. app_source=FullyControlledInput, # type: ignore
  40. ) as harness:
  41. yield harness
  42. @pytest.mark.asyncio
  43. async def test_fully_controlled_input(fully_controlled_input: AppHarness):
  44. """Type text after moving cursor. Update text on backend.
  45. Args:
  46. fully_controlled_input: harness for FullyControlledInput app
  47. """
  48. assert fully_controlled_input.app_instance is not None, "app is not running"
  49. driver = fully_controlled_input.frontend()
  50. # get a reference to the connected client
  51. assert len(fully_controlled_input.poll_for_clients()) == 1
  52. token, backend_state = list(
  53. fully_controlled_input.app_instance.state_manager.states.items()
  54. )[0]
  55. # find the input and wait for it to have the initial state value
  56. debounce_input = driver.find_element(By.ID, "debounce_input_input")
  57. value_input = driver.find_element(By.ID, "value_input")
  58. on_change_input = driver.find_element(By.ID, "on_change_input")
  59. clear_button = driver.find_element(By.TAG_NAME, "button")
  60. assert fully_controlled_input.poll_for_value(debounce_input) == "initial"
  61. assert fully_controlled_input.poll_for_value(value_input) == "initial"
  62. # move cursor to home, then to the right and type characters
  63. debounce_input.send_keys(Keys.HOME, Keys.ARROW_RIGHT)
  64. debounce_input.send_keys("foo")
  65. assert debounce_input.get_attribute("value") == "ifoonitial"
  66. assert backend_state.text == "ifoonitial"
  67. assert fully_controlled_input.poll_for_value(value_input) == "ifoonitial"
  68. # clear the input on the backend
  69. backend_state.text = ""
  70. fully_controlled_input.app_instance.state_manager.set_state(token, backend_state)
  71. await fully_controlled_input.emit_state_updates()
  72. assert backend_state.text == ""
  73. assert (
  74. fully_controlled_input.poll_for_value(
  75. debounce_input, exp_not_equal="ifoonitial"
  76. )
  77. == ""
  78. )
  79. # type more characters
  80. debounce_input.send_keys("getting testing done")
  81. time.sleep(0.1)
  82. assert debounce_input.get_attribute("value") == "getting testing done"
  83. assert backend_state.text == "getting testing done"
  84. assert fully_controlled_input.poll_for_value(value_input) == "getting testing done"
  85. # type into the on_change input
  86. on_change_input.send_keys("overwrite the state")
  87. time.sleep(0.1)
  88. assert debounce_input.get_attribute("value") == "overwrite the state"
  89. assert on_change_input.get_attribute("value") == "overwrite the state"
  90. assert backend_state.text == "overwrite the state"
  91. assert fully_controlled_input.poll_for_value(value_input) == "overwrite the state"
  92. clear_button.click()
  93. time.sleep(0.1)
  94. assert on_change_input.get_attribute("value") == ""
  95. # potential bug: clearing the on_change field doesn't itself trigger on_change
  96. # assert backend_state.text == ""
  97. # assert debounce_input.get_attribute("value") == ""
  98. # assert value_input.get_attribute("value") == ""