test_debounce.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. """Test that DebounceInput collapses nested forms."""
  2. import pytest
  3. import reflex as rx
  4. from reflex.components.core.debounce import DEFAULT_DEBOUNCE_TIMEOUT
  5. from reflex.state import BaseState
  6. from reflex.vars import BaseVar
  7. def test_create_no_child():
  8. """DebounceInput raises RuntimeError if no child is provided."""
  9. with pytest.raises(RuntimeError):
  10. _ = rx.debounce_input()
  11. def test_create_no_child_recursive():
  12. """DebounceInput raises RuntimeError if no child is provided."""
  13. with pytest.raises(RuntimeError):
  14. _ = rx.debounce_input(rx.debounce_input(rx.debounce_input()))
  15. def test_create_many_child():
  16. """DebounceInput raises RuntimeError if more than 1 child is provided."""
  17. with pytest.raises(RuntimeError):
  18. _ = rx.debounce_input("foo", "bar")
  19. def test_create_no_on_change():
  20. """DebounceInput raises ValueError if child has no on_change handler."""
  21. with pytest.raises(ValueError):
  22. _ = rx.debounce_input(rx.input())
  23. class S(BaseState):
  24. """Example state for debounce tests."""
  25. value: str = ""
  26. def on_change(self, v: str):
  27. """Dummy on_change handler.
  28. Args:
  29. v: The changed value.
  30. """
  31. pass
  32. def test_render_child_props():
  33. """DebounceInput should render props from child component."""
  34. tag = rx.debounce_input(
  35. rx.input(
  36. foo="bar",
  37. baz="quuc",
  38. value="real",
  39. on_change=S.on_change,
  40. )
  41. )._render()
  42. assert "css" in tag.props and isinstance(tag.props["css"], rx.Var)
  43. for prop in ["foo", "bar", "baz", "quuc"]:
  44. assert prop in str(tag.props["css"])
  45. assert tag.props["value"].equals(
  46. BaseVar(
  47. _var_name="real", _var_type=str, _var_is_local=True, _var_is_string=False
  48. )
  49. )
  50. assert len(tag.props["onChange"].events) == 1
  51. assert tag.props["onChange"].events[0].handler == S.on_change
  52. assert tag.contents == ""
  53. def test_render_with_class_name():
  54. tag = rx.debounce_input(
  55. rx.input(
  56. on_change=S.on_change,
  57. class_name="foo baz",
  58. )
  59. )._render()
  60. assert isinstance(tag.props["className"], rx.Var)
  61. assert "foo baz" in str(tag.props["className"])
  62. def test_render_with_ref():
  63. tag = rx.debounce_input(
  64. rx.input(
  65. on_change=S.on_change,
  66. id="foo_bar",
  67. )
  68. )._render()
  69. assert isinstance(tag.props["inputRef"], rx.Var)
  70. assert "foo_bar" in str(tag.props["inputRef"])
  71. def test_render_with_key():
  72. tag = rx.debounce_input(
  73. rx.input(
  74. on_change=S.on_change,
  75. key="foo_bar",
  76. )
  77. )._render()
  78. assert isinstance(tag.props["key"], rx.Var)
  79. assert "foo_bar" in str(tag.props["key"])
  80. def test_render_with_special_props():
  81. special_prop = rx.Var.create_safe("{foo_bar}", _var_is_string=False)
  82. tag = rx.debounce_input(
  83. rx.input(
  84. on_change=S.on_change,
  85. special_props=[special_prop],
  86. )
  87. )._render()
  88. assert len(tag.special_props) == 1
  89. assert list(tag.special_props)[0].equals(special_prop)
  90. def test_event_triggers():
  91. debounced_input = rx.debounce_input(
  92. rx.input(
  93. on_change=S.on_change,
  94. )
  95. )
  96. assert tuple(debounced_input.get_event_triggers()) == (
  97. *rx.Component().get_event_triggers(), # default event triggers
  98. "on_change",
  99. )
  100. def test_render_child_props_recursive():
  101. """DebounceInput should render props from child component.
  102. If the child component is a DebounceInput, then props will be copied from it
  103. recursively.
  104. """
  105. tag = rx.debounce_input(
  106. rx.debounce_input(
  107. rx.debounce_input(
  108. rx.debounce_input(
  109. rx.input(
  110. foo="bar",
  111. baz="quuc",
  112. value="real",
  113. on_change=S.on_change,
  114. ),
  115. value="inner",
  116. debounce_timeout=666,
  117. force_notify_on_blur=False,
  118. ),
  119. debounce_timeout=42,
  120. ),
  121. value="outer",
  122. ),
  123. force_notify_by_enter=False,
  124. )._render()
  125. assert "css" in tag.props and isinstance(tag.props["css"], rx.Var)
  126. for prop in ["foo", "bar", "baz", "quuc"]:
  127. assert prop in str(tag.props["css"])
  128. assert tag.props["value"].equals(
  129. BaseVar(
  130. _var_name="outer", _var_type=str, _var_is_local=True, _var_is_string=False
  131. )
  132. )
  133. assert tag.props["forceNotifyOnBlur"]._var_name == "false"
  134. assert tag.props["forceNotifyByEnter"]._var_name == "false"
  135. assert tag.props["debounceTimeout"]._var_name == "42"
  136. assert len(tag.props["onChange"].events) == 1
  137. assert tag.props["onChange"].events[0].handler == S.on_change
  138. assert tag.contents == ""
  139. def test_full_control_implicit_debounce():
  140. """DebounceInput is used when value and on_change are used together."""
  141. tag = rx.input(
  142. value=S.value,
  143. on_change=S.on_change,
  144. )._render()
  145. assert tag.props["debounceTimeout"]._var_name == str(DEFAULT_DEBOUNCE_TIMEOUT)
  146. assert len(tag.props["onChange"].events) == 1
  147. assert tag.props["onChange"].events[0].handler == S.on_change
  148. assert tag.contents == ""
  149. def test_full_control_implicit_debounce_text_area():
  150. """DebounceInput is used when value and on_change are used together."""
  151. tag = rx.text_area(
  152. value=S.value,
  153. on_change=S.on_change,
  154. )._render()
  155. assert tag.props["debounceTimeout"]._var_name == str(DEFAULT_DEBOUNCE_TIMEOUT)
  156. assert len(tag.props["onChange"].events) == 1
  157. assert tag.props["onChange"].events[0].handler == S.on_change
  158. assert tag.contents == ""