test_debounce.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  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.base import LiteralVar, Var
  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. @rx.event
  27. def on_change(self, v: str):
  28. """Dummy on_change handler.
  29. Args:
  30. v: The changed value.
  31. """
  32. pass
  33. def test_render_child_props():
  34. """DebounceInput should render props from child component."""
  35. tag = rx.debounce_input(
  36. rx.input(
  37. foo="bar",
  38. baz="quuc",
  39. value="real",
  40. on_change=S.on_change,
  41. )
  42. )._render()
  43. assert "css" in tag.props and isinstance(tag.props["css"], rx.vars.Var)
  44. for prop in ["foo", "bar", "baz", "quuc"]:
  45. assert prop in str(tag.props["css"])
  46. assert tag.props["value"].equals(
  47. rx.cond(Var.create("real").is_not_none(), "real", "")
  48. )
  49. assert len(tag.props["onChange"].events) == 1
  50. assert tag.props["onChange"].events[0].handler == S.on_change
  51. assert tag.contents == ""
  52. def test_render_with_class_name():
  53. tag = rx.debounce_input(
  54. rx.input(
  55. on_change=S.on_change,
  56. class_name="foo baz",
  57. )
  58. )._render()
  59. assert isinstance(tag.props["className"], rx.vars.Var)
  60. assert "foo baz" in str(tag.props["className"])
  61. def test_render_with_ref():
  62. tag = rx.debounce_input(
  63. rx.input(
  64. on_change=S.on_change,
  65. id="foo_bar",
  66. )
  67. )._render()
  68. assert isinstance(tag.props["inputRef"], rx.vars.Var)
  69. assert "foo_bar" in str(tag.props["inputRef"])
  70. def test_render_with_key():
  71. tag = rx.debounce_input(
  72. rx.input(
  73. on_change=S.on_change,
  74. key="foo_bar",
  75. )
  76. )._render()
  77. assert isinstance(tag.props["key"], rx.vars.Var)
  78. assert "foo_bar" in str(tag.props["key"])
  79. def test_render_with_special_props():
  80. special_prop = Var(_js_expr="{foo_bar}")
  81. tag = rx.debounce_input(
  82. rx.input(
  83. on_change=S.on_change,
  84. special_props=[special_prop],
  85. )
  86. )._render()
  87. assert len(tag.special_props) == 1
  88. assert next(iter(tag.special_props)).equals(special_prop)
  89. def test_event_triggers():
  90. debounced_input = rx.debounce_input(
  91. rx.input(
  92. on_change=S.on_change,
  93. )
  94. )
  95. assert tuple(debounced_input.get_event_triggers()) == (
  96. *rx.Component.create().get_event_triggers(), # default event triggers
  97. "on_change",
  98. )
  99. def test_render_child_props_recursive():
  100. """DebounceInput should render props from child component.
  101. If the child component is a DebounceInput, then props will be copied from it
  102. recursively.
  103. """
  104. tag = rx.debounce_input(
  105. rx.debounce_input(
  106. rx.debounce_input(
  107. rx.debounce_input(
  108. rx.input(
  109. foo="bar",
  110. baz="quuc",
  111. value="real",
  112. on_change=S.on_change,
  113. ),
  114. value="inner",
  115. debounce_timeout=666,
  116. force_notify_on_blur=False,
  117. ),
  118. debounce_timeout=42,
  119. ),
  120. value="outer",
  121. ),
  122. force_notify_by_enter=False,
  123. )._render()
  124. assert "css" in tag.props and isinstance(tag.props["css"], rx.vars.Var)
  125. for prop in ["foo", "bar", "baz", "quuc"]:
  126. assert prop in str(tag.props["css"])
  127. assert tag.props["value"].equals(LiteralVar.create("outer"))
  128. assert tag.props["forceNotifyOnBlur"]._js_expr == "false"
  129. assert tag.props["forceNotifyByEnter"]._js_expr == "false"
  130. assert tag.props["debounceTimeout"]._js_expr == "42"
  131. assert len(tag.props["onChange"].events) == 1
  132. assert tag.props["onChange"].events[0].handler == S.on_change
  133. assert tag.contents == ""
  134. def test_full_control_implicit_debounce():
  135. """DebounceInput is used when value and on_change are used together."""
  136. tag = rx.input(
  137. value=S.value,
  138. on_change=S.on_change,
  139. )._render()
  140. assert tag.props["debounceTimeout"]._js_expr == str(DEFAULT_DEBOUNCE_TIMEOUT)
  141. assert len(tag.props["onChange"].events) == 1
  142. assert tag.props["onChange"].events[0].handler == S.on_change
  143. assert tag.contents == ""
  144. def test_full_control_implicit_debounce_text_area():
  145. """DebounceInput is used when value and on_change are used together."""
  146. tag = rx.text_area(
  147. value=S.value,
  148. on_change=S.on_change,
  149. )._render()
  150. assert tag.props["debounceTimeout"]._js_expr == str(DEFAULT_DEBOUNCE_TIMEOUT)
  151. assert len(tag.props["onChange"].events) == 1
  152. assert tag.props["onChange"].events[0].handler == S.on_change
  153. assert tag.contents == ""