test_format.py 17 KB

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