test_match.py 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. import re
  2. from typing import Tuple
  3. import pytest
  4. import reflex as rx
  5. from reflex.components.core.match import match
  6. from reflex.state import BaseState
  7. from reflex.utils.exceptions import MatchTypeError
  8. from reflex.vars.base import Var
  9. class MatchState(BaseState):
  10. """A test state."""
  11. value: int = 0
  12. num: int = 5
  13. string: str = "random string"
  14. @pytest.mark.parametrize(
  15. "cases, expected",
  16. [
  17. (
  18. (
  19. (1, "first"),
  20. (2, 3, "second value"),
  21. ([1, 2], "third-value"),
  22. ("random", "fourth_value"),
  23. ({"foo": "bar"}, "fifth value"),
  24. (MatchState.num + 1, "sixth value"),
  25. (f"{MatchState.value} - string", MatchState.string),
  26. (MatchState.string, f"{MatchState.value} - string"),
  27. "default value",
  28. ),
  29. f'(() => {{ switch (JSON.stringify({MatchState.get_name()}.value)) {{case JSON.stringify(1): return ("first"); break;case JSON.stringify(2): case JSON.stringify(3): return '
  30. '("second value"); break;case JSON.stringify([1, 2]): return ("third-value"); break;case JSON.stringify("random"): '
  31. 'return ("fourth_value"); break;case JSON.stringify(({ ["foo"] : "bar" })): return ("fifth value"); '
  32. f'break;case JSON.stringify(({MatchState.get_name()}.num + 1)): return ("sixth value"); break;case JSON.stringify(({MatchState.get_name()}.value+" - string")): '
  33. f'return ({MatchState.get_name()}.string); break;case JSON.stringify({MatchState.get_name()}.string): return (({MatchState.get_name()}.value+" - string")); break;default: '
  34. 'return ("default value"); break;};})()',
  35. ),
  36. (
  37. (
  38. (1, "first"),
  39. (2, 3, "second value"),
  40. ([1, 2], "third-value"),
  41. ("random", "fourth_value"),
  42. ({"foo": "bar"}, "fifth value"),
  43. (MatchState.num + 1, "sixth value"),
  44. (f"{MatchState.value} - string", MatchState.string),
  45. (MatchState.string, f"{MatchState.value} - string"),
  46. MatchState.string,
  47. ),
  48. f'(() => {{ switch (JSON.stringify({MatchState.get_name()}.value)) {{case JSON.stringify(1): return ("first"); break;case JSON.stringify(2): case JSON.stringify(3): return '
  49. '("second value"); break;case JSON.stringify([1, 2]): return ("third-value"); break;case JSON.stringify("random"): '
  50. 'return ("fourth_value"); break;case JSON.stringify(({ ["foo"] : "bar" })): return ("fifth value"); '
  51. f'break;case JSON.stringify(({MatchState.get_name()}.num + 1)): return ("sixth value"); break;case JSON.stringify(({MatchState.get_name()}.value+" - string")): '
  52. f'return ({MatchState.get_name()}.string); break;case JSON.stringify({MatchState.get_name()}.string): return (({MatchState.get_name()}.value+" - string")); break;default: '
  53. f"return ({MatchState.get_name()}.string); break;}};}})()",
  54. ),
  55. ],
  56. )
  57. def test_match_vars(cases, expected):
  58. """Test matching cases with return values as Vars.
  59. Args:
  60. cases: The match cases.
  61. expected: The expected var full name.
  62. """
  63. match_comp = match(MatchState.value, *cases) # pyright: ignore[reportCallIssue]
  64. assert isinstance(match_comp, Var)
  65. assert str(match_comp) == expected
  66. def test_match_on_component_without_default():
  67. """Test that matching cases with return values as components returns a Fragment
  68. as the default case if not provided.
  69. """
  70. match_case_tuples = (
  71. (1, rx.text("first value")),
  72. (2, 3, rx.text("second value")),
  73. )
  74. match_comp = match(MatchState.value, *match_case_tuples)
  75. assert isinstance(match_comp, Var)
  76. def test_match_on_var_no_default():
  77. """Test that an error is thrown when cases with return Values as Var do not have a default case."""
  78. match_case_tuples = (
  79. (1, "red"),
  80. (2, 3, "blue"),
  81. ([1, 2], "green"),
  82. )
  83. with pytest.raises(
  84. ValueError,
  85. match="For cases with return types as Vars, a default case must be provided",
  86. ):
  87. match(MatchState.value, *match_case_tuples)
  88. @pytest.mark.parametrize(
  89. "match_case",
  90. [
  91. (
  92. (1, "red"),
  93. (2, 3, "blue"),
  94. "black",
  95. ([1, 2], "green"),
  96. ),
  97. (
  98. (1, rx.text("first value")),
  99. (2, 3, rx.text("second value")),
  100. ([1, 2], rx.text("third value")),
  101. rx.text("default value"),
  102. ("random", rx.text("fourth value")),
  103. ({"foo": "bar"}, rx.text("fifth value")),
  104. (MatchState.num + 1, rx.text("sixth value")),
  105. ),
  106. ],
  107. )
  108. def test_match_default_not_last_arg(match_case):
  109. """Test that an error is thrown when the default case is not the last arg.
  110. Args:
  111. match_case: The cases to match.
  112. """
  113. with pytest.raises(
  114. ValueError,
  115. match="rx.match should have tuples of cases and a default case as the last argument.",
  116. ):
  117. match(MatchState.value, *match_case) # pyright: ignore[reportCallIssue]
  118. @pytest.mark.parametrize(
  119. "match_case",
  120. [
  121. (
  122. (1, "red"),
  123. (2, 3, "blue"),
  124. ("green",),
  125. "black",
  126. ),
  127. (
  128. (1, rx.text("first value")),
  129. (2, 3, rx.text("second value")),
  130. ([1, 2],),
  131. rx.text("default value"),
  132. ),
  133. ],
  134. )
  135. def test_match_case_tuple_elements(match_case):
  136. """Test that a match has at least 2 elements(a condition and a return value).
  137. Args:
  138. match_case: The cases to match.
  139. """
  140. with pytest.raises(
  141. ValueError,
  142. match="A case tuple should have at least a match case element and a return value.",
  143. ):
  144. match(MatchState.value, *match_case) # pyright: ignore[reportCallIssue]
  145. @pytest.mark.parametrize(
  146. "cases, error_msg",
  147. [
  148. (
  149. (
  150. (1, rx.text("first value")),
  151. (2, 3, rx.text("second value")),
  152. ([1, 2], rx.text("third value")),
  153. ("random", "red"),
  154. ({"foo": "bar"}, "green"),
  155. (MatchState.num + 1, "black"),
  156. rx.text("default value"),
  157. ),
  158. "Match cases should have the same return types. Expected return types to be of type Component or Var[Component]. Return type of case 3 is <class 'str'>. Return type of case 4 is <class 'str'>. Return type of case 5 is <class 'str'>",
  159. ),
  160. (
  161. (
  162. ("random", "red"),
  163. ({"foo": "bar"}, "green"),
  164. (MatchState.num + 1, "black"),
  165. (1, rx.text("first value")),
  166. (2, 3, rx.text("second value")),
  167. ([1, 2], rx.text("third value")),
  168. rx.text("default value"),
  169. ),
  170. "Match cases should have the same return types. Expected return types to be of type Component or Var[Component]. Return type of case 0 is <class 'str'>. Return type of case 1 is <class 'str'>. Return type of case 2 is <class 'str'>",
  171. ),
  172. ],
  173. )
  174. def test_match_different_return_types(cases: Tuple, error_msg: str):
  175. """Test that an error is thrown when the return values are of different types.
  176. Args:
  177. cases: The match cases.
  178. error_msg: Expected error message.
  179. """
  180. with pytest.raises(MatchTypeError, match=re.escape(error_msg)):
  181. match(MatchState.value, *cases) # pyright: ignore[reportCallIssue]
  182. @pytest.mark.parametrize(
  183. "match_case",
  184. [
  185. (
  186. (1, "red"),
  187. (2, 3, "blue"),
  188. ([1, 2], "green"),
  189. "black",
  190. "white",
  191. ),
  192. (
  193. (1, rx.text("first value")),
  194. (2, 3, rx.text("second value")),
  195. ([1, 2], rx.text("third value")),
  196. ("random", rx.text("fourth value")),
  197. ({"foo": "bar"}, rx.text("fifth value")),
  198. (MatchState.num + 1, rx.text("sixth value")),
  199. rx.text("default value"),
  200. rx.text("another default value"),
  201. ),
  202. ],
  203. )
  204. def test_match_multiple_default_cases(match_case):
  205. """Test that there is only one default case.
  206. Args:
  207. match_case: the cases to match.
  208. """
  209. with pytest.raises(ValueError, match="rx.match can only have one default case."):
  210. match(MatchState.value, *match_case) # pyright: ignore[reportCallIssue]
  211. def test_match_no_cond():
  212. with pytest.raises(ValueError):
  213. _ = match(None) # pyright: ignore[reportCallIssue]