test_foreach.py 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. from typing import Dict, List, Set, Tuple, Union
  2. import pytest
  3. from reflex import el
  4. from reflex.components.component import Component
  5. from reflex.components.core.foreach import (
  6. Foreach,
  7. ForeachRenderError,
  8. ForeachVarError,
  9. foreach,
  10. )
  11. from reflex.components.radix.themes.layout.box import box
  12. from reflex.components.radix.themes.typography.text import text
  13. from reflex.state import BaseState, ComponentState
  14. from reflex.vars import Var
  15. class ForEachState(BaseState):
  16. """A state for testing the ForEach component."""
  17. colors_list: List[str] = ["red", "yellow"]
  18. nested_colors_list: List[List[str]] = [["red", "yellow"], ["blue", "green"]]
  19. colors_dict_list: List[Dict[str, str]] = [
  20. {
  21. "name": "red",
  22. },
  23. {"name": "yellow"},
  24. ]
  25. colors_nested_dict_list: List[Dict[str, List[str]]] = [{"shades": ["light-red"]}]
  26. primary_color: Dict[str, str] = {"category": "primary", "name": "red"}
  27. color_with_shades: Dict[str, List[str]] = {
  28. "red": ["orange", "yellow"],
  29. "yellow": ["orange", "green"],
  30. }
  31. nested_colors_with_shades: Dict[str, Dict[str, List[Dict[str, str]]]] = {
  32. "primary": {"red": [{"shade": "dark"}]}
  33. }
  34. color_tuple: Tuple[str, str] = (
  35. "red",
  36. "yellow",
  37. )
  38. colors_set: Set[str] = {"red", "green"}
  39. bad_annotation_list: list = [["red", "orange"], ["yellow", "blue"]]
  40. color_index_tuple: Tuple[int, str] = (0, "red")
  41. class TestComponentState(ComponentState):
  42. """A test component state."""
  43. foo: bool
  44. @classmethod
  45. def get_component(cls, *children, **props) -> Component:
  46. """Get the component.
  47. Args:
  48. children: The children components.
  49. props: The component props.
  50. Returns:
  51. The component.
  52. """
  53. return el.div(*children, **props)
  54. def display_color(color):
  55. assert color._var_type == str
  56. return box(text(color))
  57. def display_color_name(color):
  58. assert color._var_type == Dict[str, str]
  59. return box(text(color["name"]))
  60. def display_shade(color):
  61. assert color._var_type == Dict[str, List[str]]
  62. return box(text(color["shades"][0]))
  63. def display_primary_colors(color):
  64. assert color._var_type == Tuple[str, str]
  65. return box(text(color[0]), text(color[1]))
  66. def display_color_with_shades(color):
  67. assert color._var_type == Tuple[str, List[str]]
  68. return box(text(color[0]), text(color[1][0]))
  69. def display_nested_color_with_shades(color):
  70. assert color._var_type == Tuple[str, Dict[str, List[Dict[str, str]]]]
  71. return box(text(color[0]), text(color[1]["red"][0]["shade"]))
  72. def show_shade(item):
  73. return text(item[1][0]["shade"])
  74. def display_nested_color_with_shades_v2(color):
  75. assert color._var_type == Tuple[str, Dict[str, List[Dict[str, str]]]]
  76. return box(text(foreach(color[1], show_shade)))
  77. def display_color_tuple(color):
  78. assert color._var_type == str
  79. return box(text(color))
  80. def display_colors_set(color):
  81. assert color._var_type == str
  82. return box(text(color))
  83. def display_nested_list_element(element: Var[str], index: Var[int]):
  84. assert element._var_type == List[str]
  85. assert index._var_type == int
  86. return box(text(element[index]))
  87. def display_color_index_tuple(color):
  88. assert color._var_type == Union[int, str]
  89. return box(text(color))
  90. seen_index_vars = set()
  91. @pytest.mark.parametrize(
  92. "state_var, render_fn, render_dict",
  93. [
  94. (
  95. ForEachState.colors_list,
  96. display_color,
  97. {
  98. "iterable_state": f"{ForEachState.get_full_name()}.colors_list",
  99. "iterable_type": "list",
  100. },
  101. ),
  102. (
  103. ForEachState.colors_dict_list,
  104. display_color_name,
  105. {
  106. "iterable_state": f"{ForEachState.get_full_name()}.colors_dict_list",
  107. "iterable_type": "list",
  108. },
  109. ),
  110. (
  111. ForEachState.colors_nested_dict_list,
  112. display_shade,
  113. {
  114. "iterable_state": f"{ForEachState.get_full_name()}.colors_nested_dict_list",
  115. "iterable_type": "list",
  116. },
  117. ),
  118. (
  119. ForEachState.primary_color,
  120. display_primary_colors,
  121. {
  122. "iterable_state": f"{ForEachState.get_full_name()}.primary_color",
  123. "iterable_type": "dict",
  124. },
  125. ),
  126. (
  127. ForEachState.color_with_shades,
  128. display_color_with_shades,
  129. {
  130. "iterable_state": f"{ForEachState.get_full_name()}.color_with_shades",
  131. "iterable_type": "dict",
  132. },
  133. ),
  134. (
  135. ForEachState.nested_colors_with_shades,
  136. display_nested_color_with_shades,
  137. {
  138. "iterable_state": f"{ForEachState.get_full_name()}.nested_colors_with_shades",
  139. "iterable_type": "dict",
  140. },
  141. ),
  142. (
  143. ForEachState.nested_colors_with_shades,
  144. display_nested_color_with_shades_v2,
  145. {
  146. "iterable_state": f"{ForEachState.get_full_name()}.nested_colors_with_shades",
  147. "iterable_type": "dict",
  148. },
  149. ),
  150. (
  151. ForEachState.color_tuple,
  152. display_color_tuple,
  153. {
  154. "iterable_state": f"{ForEachState.get_full_name()}.color_tuple",
  155. "iterable_type": "tuple",
  156. },
  157. ),
  158. (
  159. ForEachState.colors_set,
  160. display_colors_set,
  161. {
  162. "iterable_state": f"{ForEachState.get_full_name()}.colors_set",
  163. "iterable_type": "set",
  164. },
  165. ),
  166. (
  167. ForEachState.nested_colors_list,
  168. lambda el, i: display_nested_list_element(el, i),
  169. {
  170. "iterable_state": f"{ForEachState.get_full_name()}.nested_colors_list",
  171. "iterable_type": "list",
  172. },
  173. ),
  174. (
  175. ForEachState.color_index_tuple,
  176. display_color_index_tuple,
  177. {
  178. "iterable_state": f"{ForEachState.get_full_name()}.color_index_tuple",
  179. "iterable_type": "tuple",
  180. },
  181. ),
  182. ],
  183. )
  184. def test_foreach_render(state_var, render_fn, render_dict):
  185. """Test that the foreach component renders without error.
  186. Args:
  187. state_var: the state var.
  188. render_fn: The render callable
  189. render_dict: return dict on calling `component.render`
  190. """
  191. component = Foreach.create(state_var, render_fn)
  192. rend = component.render()
  193. assert rend["iterable_state"] == render_dict["iterable_state"]
  194. assert rend["iterable_type"] == render_dict["iterable_type"]
  195. # Make sure the index vars are unique.
  196. arg_index = rend["arg_index"]
  197. assert isinstance(arg_index, Var)
  198. assert arg_index._var_name not in seen_index_vars
  199. assert arg_index._var_type == int
  200. seen_index_vars.add(arg_index._var_name)
  201. def test_foreach_bad_annotations():
  202. """Test that the foreach component raises a ForeachVarError if the iterable is of type Any."""
  203. with pytest.raises(ForeachVarError):
  204. Foreach.create(
  205. ForEachState.bad_annotation_list,
  206. lambda sublist: Foreach.create(sublist, lambda color: text(color)),
  207. )
  208. def test_foreach_no_param_in_signature():
  209. """Test that the foreach component raises a ForeachRenderError if no parameters are passed."""
  210. with pytest.raises(ForeachRenderError):
  211. Foreach.create(
  212. ForEachState.colors_list,
  213. lambda: text("color"),
  214. )
  215. def test_foreach_too_many_params_in_signature():
  216. """Test that the foreach component raises a ForeachRenderError if too many parameters are passed."""
  217. with pytest.raises(ForeachRenderError):
  218. Foreach.create(
  219. ForEachState.colors_list,
  220. lambda color, index, extra: text(color),
  221. )
  222. def test_foreach_component_styles():
  223. """Test that the foreach component works with global component styles."""
  224. component = el.div(
  225. foreach(
  226. ForEachState.colors_list,
  227. display_color,
  228. )
  229. )
  230. component._add_style_recursive({box: {"color": "red"}})
  231. assert 'css={{"color": "red"}}' in str(component)
  232. def test_foreach_component_state():
  233. """Test that using a component state to render in the foreach raises an error."""
  234. with pytest.raises(TypeError):
  235. Foreach.create(
  236. ForEachState.colors_list,
  237. TestComponentState.create,
  238. )