test_format.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581
  1. from typing import Any
  2. import pytest
  3. from reflex.components.tags.tag import Tag
  4. from reflex.event import EVENT_ARG, EventChain, EventHandler, EventSpec
  5. from reflex.style import Style
  6. from reflex.utils import format
  7. from reflex.vars import BaseVar, Var
  8. from tests.test_state import ChildState, DateTimeState, GrandchildState, TestState
  9. def mock_event(arg):
  10. pass
  11. @pytest.mark.parametrize(
  12. "input,output",
  13. [
  14. ("{", "}"),
  15. ("(", ")"),
  16. ("[", "]"),
  17. ("<", ">"),
  18. ('"', '"'),
  19. ("'", "'"),
  20. ],
  21. )
  22. def test_get_close_char(input: str, output: str):
  23. """Test getting the close character for a given open character.
  24. Args:
  25. input: The open character.
  26. output: The expected close character.
  27. """
  28. assert format.get_close_char(input) == output
  29. @pytest.mark.parametrize(
  30. "text,open,expected",
  31. [
  32. ("", "{", False),
  33. ("{wrap}", "{", True),
  34. ("{wrap", "{", False),
  35. ("{wrap}", "(", False),
  36. ("(wrap)", "(", True),
  37. ],
  38. )
  39. def test_is_wrapped(text: str, open: str, expected: bool):
  40. """Test checking if a string is wrapped in the given open and close characters.
  41. Args:
  42. text: The text to check.
  43. open: The open character.
  44. expected: Whether the text is wrapped.
  45. """
  46. assert format.is_wrapped(text, open) == expected
  47. @pytest.mark.parametrize(
  48. "text,open,check_first,num,expected",
  49. [
  50. ("", "{", True, 1, "{}"),
  51. ("wrap", "{", True, 1, "{wrap}"),
  52. ("wrap", "(", True, 1, "(wrap)"),
  53. ("wrap", "(", True, 2, "((wrap))"),
  54. ("(wrap)", "(", True, 1, "(wrap)"),
  55. ("{wrap}", "{", True, 2, "{wrap}"),
  56. ("(wrap)", "{", True, 1, "{(wrap)}"),
  57. ("(wrap)", "(", False, 1, "((wrap))"),
  58. ],
  59. )
  60. def test_wrap(text: str, open: str, expected: str, check_first: bool, num: int):
  61. """Test wrapping a string.
  62. Args:
  63. text: The text to wrap.
  64. open: The open character.
  65. expected: The expected output string.
  66. check_first: Whether to check if the text is already wrapped.
  67. num: The number of times to wrap the text.
  68. """
  69. assert format.wrap(text, open, check_first=check_first, num=num) == expected
  70. @pytest.mark.parametrize(
  71. "text,indent_level,expected",
  72. [
  73. ("", 2, ""),
  74. ("hello", 2, "hello"),
  75. ("hello\nworld", 2, " hello\n world\n"),
  76. ("hello\nworld", 4, " hello\n world\n"),
  77. (" hello\n world", 2, " hello\n world\n"),
  78. ],
  79. )
  80. def test_indent(text: str, indent_level: int, expected: str, windows_platform: bool):
  81. """Test indenting a string.
  82. Args:
  83. text: The text to indent.
  84. indent_level: The number of spaces to indent by.
  85. expected: The expected output string.
  86. windows_platform: Whether the system is windows.
  87. """
  88. assert format.indent(text, indent_level) == (
  89. expected.replace("\n", "\r\n") if windows_platform else expected
  90. )
  91. @pytest.mark.parametrize(
  92. "input,output",
  93. [
  94. ("", ""),
  95. ("hello", "hello"),
  96. ("Hello", "hello"),
  97. ("camelCase", "camel_case"),
  98. ("camelTwoHumps", "camel_two_humps"),
  99. ("_start_with_underscore", "_start_with_underscore"),
  100. ("__start_with_double_underscore", "__start_with_double_underscore"),
  101. ],
  102. )
  103. def test_to_snake_case(input: str, output: str):
  104. """Test converting strings to snake case.
  105. Args:
  106. input: The input string.
  107. output: The expected output string.
  108. """
  109. assert format.to_snake_case(input) == output
  110. @pytest.mark.parametrize(
  111. "input,output",
  112. [
  113. ("", ""),
  114. ("hello", "hello"),
  115. ("Hello", "Hello"),
  116. ("snake_case", "snakeCase"),
  117. ("snake_case_two", "snakeCaseTwo"),
  118. ],
  119. )
  120. def test_to_camel_case(input: str, output: str):
  121. """Test converting strings to camel case.
  122. Args:
  123. input: The input string.
  124. output: The expected output string.
  125. """
  126. assert format.to_camel_case(input) == output
  127. @pytest.mark.parametrize(
  128. "input,output",
  129. [
  130. ("", ""),
  131. ("hello", "Hello"),
  132. ("Hello", "Hello"),
  133. ("snake_case", "SnakeCase"),
  134. ("snake_case_two", "SnakeCaseTwo"),
  135. ],
  136. )
  137. def test_to_title_case(input: str, output: str):
  138. """Test converting strings to title case.
  139. Args:
  140. input: The input string.
  141. output: The expected output string.
  142. """
  143. assert format.to_title_case(input) == output
  144. @pytest.mark.parametrize(
  145. "input,output",
  146. [
  147. ("", ""),
  148. ("hello", "hello"),
  149. ("Hello", "hello"),
  150. ("snake_case", "snake-case"),
  151. ("snake_case_two", "snake-case-two"),
  152. ],
  153. )
  154. def test_to_kebab_case(input: str, output: str):
  155. """Test converting strings to kebab case.
  156. Args:
  157. input: the input string.
  158. output: the output string.
  159. """
  160. assert format.to_kebab_case(input) == output
  161. @pytest.mark.parametrize(
  162. "input,output",
  163. [
  164. ("", "{``}"),
  165. ("hello", "{`hello`}"),
  166. ("hello world", "{`hello world`}"),
  167. ("hello=`world`", "{`hello=\\`world\\``}"),
  168. ],
  169. )
  170. def test_format_string(input: str, output: str):
  171. """Test formating the input as JS string literal.
  172. Args:
  173. input: the input string.
  174. output: the output string.
  175. """
  176. assert format.format_string(input) == output
  177. @pytest.mark.parametrize(
  178. "input,output",
  179. [
  180. (Var.create(value="test"), "{`test`}"),
  181. (Var.create(value="test", _var_is_local=True), "{`test`}"),
  182. (Var.create(value="test", _var_is_local=False), "{test}"),
  183. (Var.create(value="test", _var_is_string=True), "{`test`}"),
  184. (Var.create(value="test", _var_is_string=False), "{`test`}"),
  185. (Var.create(value="test", _var_is_local=False, _var_is_string=False), "{test}"),
  186. ],
  187. )
  188. def test_format_var(input: Var, output: str):
  189. assert format.format_var(input) == output
  190. @pytest.mark.parametrize(
  191. "route,format_case,expected",
  192. [
  193. ("", True, "index"),
  194. ("/", True, "index"),
  195. ("custom-route", True, "custom-route"),
  196. ("custom-route", False, "custom-route"),
  197. ("custom-route/", True, "custom-route"),
  198. ("custom-route/", False, "custom-route"),
  199. ("/custom-route", True, "custom-route"),
  200. ("/custom-route", False, "custom-route"),
  201. ("/custom_route", True, "custom-route"),
  202. ("/custom_route", False, "custom_route"),
  203. ("/CUSTOM_route", True, "custom-route"),
  204. ("/CUSTOM_route", False, "CUSTOM_route"),
  205. ],
  206. )
  207. def test_format_route(route: str, format_case: bool, expected: bool):
  208. """Test formatting a route.
  209. Args:
  210. route: The route to format.
  211. format_case: Whether to change casing to snake_case.
  212. expected: The expected formatted route.
  213. """
  214. assert format.format_route(route, format_case=format_case) == expected
  215. @pytest.mark.parametrize(
  216. "condition,true_value,false_value,expected",
  217. [
  218. ("cond", "<C1>", '""', '{isTrue(cond) ? <C1> : ""}'),
  219. ("cond", "<C1>", "<C2>", "{isTrue(cond) ? <C1> : <C2>}"),
  220. ],
  221. )
  222. def test_format_cond(condition: str, true_value: str, false_value: str, expected: str):
  223. """Test formatting a cond.
  224. Args:
  225. condition: The condition to check.
  226. true_value: The value to return if the condition is true.
  227. false_value: The value to return if the condition is false.
  228. expected: The expected output string.
  229. """
  230. assert format.format_cond(condition, true_value, false_value) == expected
  231. @pytest.mark.parametrize(
  232. "prop,formatted",
  233. [
  234. ("string", '"string"'),
  235. ("{wrapped_string}", "{wrapped_string}"),
  236. (True, "{true}"),
  237. (False, "{false}"),
  238. (123, "{123}"),
  239. (3.14, "{3.14}"),
  240. ([1, 2, 3], "{[1, 2, 3]}"),
  241. (["a", "b", "c"], '{["a", "b", "c"]}'),
  242. ({"a": 1, "b": 2, "c": 3}, '{{"a": 1, "b": 2, "c": 3}}'),
  243. ({"a": 'foo "bar" baz'}, r'{{"a": "foo \"bar\" baz"}}'),
  244. (
  245. {
  246. "a": 'foo "{ "bar" }" baz',
  247. "b": BaseVar(_var_name="val", _var_type="str"),
  248. },
  249. r'{{"a": "foo \"{ \"bar\" }\" baz", "b": val}}',
  250. ),
  251. (
  252. EventChain(
  253. events=[EventSpec(handler=EventHandler(fn=mock_event))], args_spec=None
  254. ),
  255. '{_e => addEvents([Event("mock_event", {})], _e)}',
  256. ),
  257. (
  258. EventChain(
  259. events=[
  260. EventSpec(
  261. handler=EventHandler(fn=mock_event),
  262. args=((Var.create_safe("arg"), EVENT_ARG.target.value),),
  263. )
  264. ],
  265. args_spec=None,
  266. ),
  267. '{_e => addEvents([Event("mock_event", {arg:_e.target.value})], _e)}',
  268. ),
  269. ({"a": "red", "b": "blue"}, '{{"a": "red", "b": "blue"}}'),
  270. (BaseVar(_var_name="var", _var_type="int"), "{var}"),
  271. (
  272. BaseVar(
  273. _var_name="_",
  274. _var_type=Any,
  275. _var_state="",
  276. _var_is_local=True,
  277. _var_is_string=False,
  278. ),
  279. "{_}",
  280. ),
  281. (
  282. BaseVar(_var_name='state.colors["a"]', _var_type="str"),
  283. '{state.colors["a"]}',
  284. ),
  285. ({"a": BaseVar(_var_name="val", _var_type="str")}, '{{"a": val}}'),
  286. ({"a": BaseVar(_var_name='"val"', _var_type="str")}, '{{"a": "val"}}'),
  287. (
  288. {"a": BaseVar(_var_name='state.colors["val"]', _var_type="str")},
  289. '{{"a": state.colors["val"]}}',
  290. ),
  291. # tricky real-world case from markdown component
  292. (
  293. {
  294. "h1": f"{{({{node, ...props}}) => <Heading {{...props}} {''.join(Tag(name='', props=Style({'as_': 'h1'})).format_props())} />}}"
  295. },
  296. '{{"h1": ({node, ...props}) => <Heading {...props} as={`h1`} />}}',
  297. ),
  298. ],
  299. )
  300. def test_format_prop(prop: Var, formatted: str):
  301. """Test that the formatted value of an prop is correct.
  302. Args:
  303. prop: The prop to test.
  304. formatted: The expected formatted value.
  305. """
  306. assert format.format_prop(prop) == formatted
  307. @pytest.mark.parametrize(
  308. "single_props,key_value_props,output",
  309. [
  310. (["string"], {"key": 42}, ["key={42}", "string"]),
  311. ],
  312. )
  313. def test_format_props(single_props, key_value_props, output):
  314. """Test the result of formatting a set of props (both single and keyvalue).
  315. Args:
  316. single_props: the list of single props
  317. key_value_props: the dict of key value props
  318. output: the expected output
  319. """
  320. assert format.format_props(*single_props, **key_value_props) == output
  321. @pytest.mark.parametrize(
  322. "input,output",
  323. [
  324. (EventHandler(fn=mock_event), ("", "mock_event")),
  325. ],
  326. )
  327. def test_get_handler_parts(input, output):
  328. assert format.get_event_handler_parts(input) == output
  329. @pytest.mark.parametrize(
  330. "input,output",
  331. [
  332. (TestState.do_something, "test_state.do_something"),
  333. (ChildState.change_both, "test_state.child_state.change_both"),
  334. (
  335. GrandchildState.do_nothing,
  336. "test_state.child_state.grandchild_state.do_nothing",
  337. ),
  338. ],
  339. )
  340. def test_format_event_handler(input, output):
  341. """Test formatting an event handler.
  342. Args:
  343. input: The event handler input.
  344. output: The expected output.
  345. """
  346. assert format.format_event_handler(input) == output # type: ignore
  347. @pytest.mark.parametrize(
  348. "input,output",
  349. [
  350. (EventSpec(handler=EventHandler(fn=mock_event)), 'Event("mock_event", {})'),
  351. ],
  352. )
  353. def test_format_event(input, output):
  354. assert format.format_event(input) == output
  355. @pytest.mark.parametrize(
  356. "input,output",
  357. [
  358. (
  359. EventChain(
  360. events=[
  361. EventSpec(handler=EventHandler(fn=mock_event)),
  362. EventSpec(handler=EventHandler(fn=mock_event)),
  363. ],
  364. args_spec=None,
  365. ),
  366. 'addEvents([Event("mock_event", {}),Event("mock_event", {})])',
  367. ),
  368. (
  369. EventChain(
  370. events=[
  371. EventSpec(handler=EventHandler(fn=mock_event)),
  372. EventSpec(handler=EventHandler(fn=mock_event)),
  373. ],
  374. args_spec=lambda e0: [e0],
  375. ),
  376. 'addEvents([Event("mock_event", {}),Event("mock_event", {})])',
  377. ),
  378. ],
  379. )
  380. def test_format_event_chain(input, output):
  381. assert format.format_event_chain(input) == output
  382. @pytest.mark.parametrize(
  383. "input,output",
  384. [
  385. ({"query": {"k1": 1, "k2": 2}}, {"k1": 1, "k2": 2}),
  386. ({"query": {"k1": 1, "k-2": 2}}, {"k1": 1, "k_2": 2}),
  387. ],
  388. )
  389. def test_format_query_params(input, output):
  390. assert format.format_query_params(input) == output
  391. @pytest.mark.parametrize(
  392. "input, output",
  393. [
  394. (
  395. TestState().dict(), # type: ignore
  396. {
  397. "array": [1, 2, 3.14],
  398. "child_state": {
  399. "count": 23,
  400. "grandchild_state": {"value2": ""},
  401. "value": "",
  402. },
  403. "child_state2": {"value": ""},
  404. "complex": {
  405. 1: {"prop1": 42, "prop2": "hello"},
  406. 2: {"prop1": 42, "prop2": "hello"},
  407. },
  408. "dt": "1989-11-09 18:53:00+01:00",
  409. "fig": [],
  410. "is_hydrated": False,
  411. "key": "",
  412. "map_key": "a",
  413. "mapping": {"a": [1, 2, 3], "b": [4, 5, 6]},
  414. "num1": 0,
  415. "num2": 3.14,
  416. "obj": {"prop1": 42, "prop2": "hello"},
  417. "sum": 3.14,
  418. "upper": "",
  419. },
  420. ),
  421. (
  422. DateTimeState().dict(),
  423. {
  424. "d": "1989-11-09",
  425. "dt": "1989-11-09 18:53:00+01:00",
  426. "is_hydrated": False,
  427. "t": "18:53:00+01:00",
  428. "td": "11 days, 0:11:00",
  429. },
  430. ),
  431. ],
  432. )
  433. def test_format_state(input, output):
  434. """Test that the format state is correct.
  435. Args:
  436. input: The state to format.
  437. output: The expected formatted state.
  438. """
  439. assert format.format_state(input) == output
  440. @pytest.mark.parametrize(
  441. "input,output",
  442. [
  443. ("input1", "ref_input1"),
  444. ("input 1", "ref_input_1"),
  445. ("input-1", "ref_input_1"),
  446. ("input_1", "ref_input_1"),
  447. ("a long test?1! name", "ref_a_long_test_1_name"),
  448. ],
  449. )
  450. def test_format_ref(input, output):
  451. """Test formatting a ref.
  452. Args:
  453. input: The name to format.
  454. output: The expected formatted name.
  455. """
  456. assert format.format_ref(input) == output
  457. @pytest.mark.parametrize(
  458. "input,output",
  459. [
  460. (("my_array", None), "refs_my_array"),
  461. (("my_array", Var.create(0)), "refs_my_array[0]"),
  462. (("my_array", Var.create(1)), "refs_my_array[1]"),
  463. ],
  464. )
  465. def test_format_array_ref(input, output):
  466. assert format.format_array_ref(input[0], input[1]) == output
  467. @pytest.mark.parametrize(
  468. "input,output",
  469. [
  470. ("/foo", [("foo", "/foo")]),
  471. ("/foo/bar", [("foo", "/foo"), ("bar", "/foo/bar")]),
  472. (
  473. "/foo/bar/baz",
  474. [("foo", "/foo"), ("bar", "/foo/bar"), ("baz", "/foo/bar/baz")],
  475. ),
  476. ],
  477. )
  478. def test_format_breadcrumbs(input, output):
  479. assert format.format_breadcrumbs(input) == output
  480. @pytest.mark.parametrize(
  481. "input, output",
  482. [
  483. ("library@^0.1.2", "library"),
  484. ("library", "library"),
  485. ("@library@^0.1.2", "@library"),
  486. ("@library", "@library"),
  487. ],
  488. )
  489. def test_format_library_name(input: str, output: str):
  490. """Test formating a library name to remove the @version part.
  491. Args:
  492. input: the input string.
  493. output: the output string.
  494. """
  495. assert format.format_library_name(input) == output
  496. @pytest.mark.parametrize(
  497. "input,output",
  498. [
  499. (None, "null"),
  500. (True, "true"),
  501. (1, "1"),
  502. (1.0, "1.0"),
  503. ([], "[]"),
  504. ([1, 2, 3], "[1, 2, 3]"),
  505. ({}, "{}"),
  506. ({"k1": False, "k2": True}, '{"k1": false, "k2": true}'),
  507. ],
  508. )
  509. def test_json_dumps(input, output):
  510. assert format.json_dumps(input) == output