test_foreach.py 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. from typing import Dict, List, Set, Tuple, Union
  2. import pytest
  3. from reflex.components import box, foreach, text
  4. from reflex.components.core import Foreach
  5. from reflex.state import BaseState
  6. from reflex.vars import Var
  7. try:
  8. # When pydantic v2 is installed
  9. from pydantic.v1 import ValidationError # type: ignore
  10. except ImportError:
  11. from pydantic import ValidationError
  12. class ForEachState(BaseState):
  13. """A state for testing the ForEach component."""
  14. colors_list: List[str] = ["red", "yellow"]
  15. nested_colors_list: List[List[str]] = [["red", "yellow"], ["blue", "green"]]
  16. colors_dict_list: List[Dict[str, str]] = [
  17. {
  18. "name": "red",
  19. },
  20. {"name": "yellow"},
  21. ]
  22. colors_nested_dict_list: List[Dict[str, List[str]]] = [{"shades": ["light-red"]}]
  23. primary_color: Dict[str, str] = {"category": "primary", "name": "red"}
  24. color_with_shades: Dict[str, List[str]] = {
  25. "red": ["orange", "yellow"],
  26. "yellow": ["orange", "green"],
  27. }
  28. nested_colors_with_shades: Dict[str, Dict[str, List[Dict[str, str]]]] = {
  29. "primary": {"red": [{"shade": "dark"}]}
  30. }
  31. color_tuple: Tuple[str, str] = (
  32. "red",
  33. "yellow",
  34. )
  35. colors_set: Set[str] = {"red", "green"}
  36. bad_annotation_list: list = [["red", "orange"], ["yellow", "blue"]]
  37. color_index_tuple: Tuple[int, str] = (0, "red")
  38. def display_color(color):
  39. assert color._var_type == str
  40. return box(text(color))
  41. def display_color_name(color):
  42. assert color._var_type == Dict[str, str]
  43. return box(text(color["name"]))
  44. def display_shade(color):
  45. assert color._var_type == Dict[str, List[str]]
  46. return box(text(color["shades"][0]))
  47. def display_primary_colors(color):
  48. assert color._var_type == Tuple[str, str]
  49. return box(text(color[0]), text(color[1]))
  50. def display_color_with_shades(color):
  51. assert color._var_type == Tuple[str, List[str]]
  52. return box(text(color[0]), text(color[1][0]))
  53. def display_nested_color_with_shades(color):
  54. assert color._var_type == Tuple[str, Dict[str, List[Dict[str, str]]]]
  55. return box(text(color[0]), text(color[1]["red"][0]["shade"]))
  56. def show_shade(item):
  57. return text(item[1][0]["shade"])
  58. def display_nested_color_with_shades_v2(color):
  59. assert color._var_type == Tuple[str, Dict[str, List[Dict[str, str]]]]
  60. return box(text(foreach(color[1], show_shade)))
  61. def display_color_tuple(color):
  62. assert color._var_type == str
  63. return box(text(color, "tuple"))
  64. def display_colors_set(color):
  65. assert color._var_type == str
  66. return box(text(color, "set"))
  67. def display_nested_list_element(element: Var[str], index: Var[int]):
  68. assert element._var_type == List[str]
  69. assert index._var_type == int
  70. return box(text(element[index]))
  71. def display_color_index_tuple(color):
  72. assert color._var_type == Union[int, str]
  73. return box(text(color, "index_tuple"))
  74. seen_index_vars = set()
  75. @pytest.mark.parametrize(
  76. "state_var, render_fn, render_dict",
  77. [
  78. (
  79. ForEachState.colors_list,
  80. display_color,
  81. {
  82. "iterable_state": "for_each_state.colors_list",
  83. "iterable_type": "list",
  84. },
  85. ),
  86. (
  87. ForEachState.colors_dict_list,
  88. display_color_name,
  89. {
  90. "iterable_state": "for_each_state.colors_dict_list",
  91. "iterable_type": "list",
  92. },
  93. ),
  94. (
  95. ForEachState.colors_nested_dict_list,
  96. display_shade,
  97. {
  98. "iterable_state": "for_each_state.colors_nested_dict_list",
  99. "iterable_type": "list",
  100. },
  101. ),
  102. (
  103. ForEachState.primary_color,
  104. display_primary_colors,
  105. {
  106. "iterable_state": "for_each_state.primary_color",
  107. "iterable_type": "dict",
  108. },
  109. ),
  110. (
  111. ForEachState.color_with_shades,
  112. display_color_with_shades,
  113. {
  114. "iterable_state": "for_each_state.color_with_shades",
  115. "iterable_type": "dict",
  116. },
  117. ),
  118. (
  119. ForEachState.nested_colors_with_shades,
  120. display_nested_color_with_shades,
  121. {
  122. "iterable_state": "for_each_state.nested_colors_with_shades",
  123. "iterable_type": "dict",
  124. },
  125. ),
  126. (
  127. ForEachState.nested_colors_with_shades,
  128. display_nested_color_with_shades_v2,
  129. {
  130. "iterable_state": "for_each_state.nested_colors_with_shades",
  131. "iterable_type": "dict",
  132. },
  133. ),
  134. (
  135. ForEachState.color_tuple,
  136. display_color_tuple,
  137. {
  138. "iterable_state": "for_each_state.color_tuple",
  139. "iterable_type": "tuple",
  140. },
  141. ),
  142. (
  143. ForEachState.colors_set,
  144. display_colors_set,
  145. {
  146. "iterable_state": "for_each_state.colors_set",
  147. "iterable_type": "set",
  148. },
  149. ),
  150. (
  151. ForEachState.nested_colors_list,
  152. lambda el, i: display_nested_list_element(el, i),
  153. {
  154. "iterable_state": "for_each_state.nested_colors_list",
  155. "iterable_type": "list",
  156. },
  157. ),
  158. (
  159. ForEachState.color_index_tuple,
  160. display_color_index_tuple,
  161. {
  162. "iterable_state": "for_each_state.color_index_tuple",
  163. "iterable_type": "tuple",
  164. },
  165. ),
  166. ],
  167. )
  168. def test_foreach_render(state_var, render_fn, render_dict):
  169. """Test that the foreach component renders without error.
  170. Args:
  171. state_var: the state var.
  172. render_fn: The render callable
  173. render_dict: return dict on calling `component.render`
  174. """
  175. component = Foreach.create(state_var, render_fn)
  176. rend = component.render()
  177. assert rend["iterable_state"] == render_dict["iterable_state"]
  178. assert rend["iterable_type"] == render_dict["iterable_type"]
  179. # Make sure the index vars are unique.
  180. arg_index = rend["arg_index"]
  181. assert arg_index._var_name not in seen_index_vars
  182. assert arg_index._var_type == int
  183. seen_index_vars.add(arg_index._var_name)
  184. def test_foreach_bad_annotations():
  185. """Test that the foreach component raises a TypeError if the iterable is of type Any."""
  186. with pytest.raises(TypeError):
  187. Foreach.create(
  188. ForEachState.bad_annotation_list, # type: ignore
  189. lambda sublist: Foreach.create(sublist, lambda color: text(color)),
  190. )
  191. def test_foreach_no_param_in_signature():
  192. """Test that the foreach component raises a TypeError if no parameters are passed."""
  193. with pytest.raises(ValidationError):
  194. Foreach.create(
  195. ForEachState.colors_list, # type: ignore
  196. lambda: text("color"),
  197. )