test_utils.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465
  1. import typing
  2. from typing import Any, List, Union
  3. import pytest
  4. from packaging import version
  5. from pynecone.utils import build, format, imports, prerequisites, types
  6. from pynecone.vars import Var
  7. V055 = version.parse("0.5.5")
  8. V059 = version.parse("0.5.9")
  9. V056 = version.parse("0.5.6")
  10. V0510 = version.parse("0.5.10")
  11. @pytest.mark.parametrize(
  12. "input,output",
  13. [
  14. ("", ""),
  15. ("hello", "hello"),
  16. ("Hello", "hello"),
  17. ("camelCase", "camel_case"),
  18. ("camelTwoHumps", "camel_two_humps"),
  19. ("_start_with_underscore", "_start_with_underscore"),
  20. ("__start_with_double_underscore", "__start_with_double_underscore"),
  21. ],
  22. )
  23. def test_to_snake_case(input: str, output: str):
  24. """Test converting strings to snake case.
  25. Args:
  26. input: The input string.
  27. output: The expected output string.
  28. """
  29. assert format.to_snake_case(input) == output
  30. @pytest.mark.parametrize(
  31. "input,output",
  32. [
  33. ("", ""),
  34. ("hello", "hello"),
  35. ("Hello", "Hello"),
  36. ("snake_case", "snakeCase"),
  37. ("snake_case_two", "snakeCaseTwo"),
  38. ],
  39. )
  40. def test_to_camel_case(input: str, output: str):
  41. """Test converting strings to camel case.
  42. Args:
  43. input: The input string.
  44. output: The expected output string.
  45. """
  46. assert format.to_camel_case(input) == output
  47. @pytest.mark.parametrize(
  48. "input,output",
  49. [
  50. ("", ""),
  51. ("hello", "Hello"),
  52. ("Hello", "Hello"),
  53. ("snake_case", "SnakeCase"),
  54. ("snake_case_two", "SnakeCaseTwo"),
  55. ],
  56. )
  57. def test_to_title_case(input: str, output: str):
  58. """Test converting strings to title case.
  59. Args:
  60. input: The input string.
  61. output: The expected output string.
  62. """
  63. assert format.to_title_case(input) == output
  64. @pytest.mark.parametrize(
  65. "input,output",
  66. [
  67. ("{", "}"),
  68. ("(", ")"),
  69. ("[", "]"),
  70. ("<", ">"),
  71. ('"', '"'),
  72. ("'", "'"),
  73. ],
  74. )
  75. def test_get_close_char(input: str, output: str):
  76. """Test getting the close character for a given open character.
  77. Args:
  78. input: The open character.
  79. output: The expected close character.
  80. """
  81. assert format.get_close_char(input) == output
  82. @pytest.mark.parametrize(
  83. "text,open,expected",
  84. [
  85. ("", "{", False),
  86. ("{wrap}", "{", True),
  87. ("{wrap", "{", False),
  88. ("{wrap}", "(", False),
  89. ("(wrap)", "(", True),
  90. ],
  91. )
  92. def test_is_wrapped(text: str, open: str, expected: bool):
  93. """Test checking if a string is wrapped in the given open and close characters.
  94. Args:
  95. text: The text to check.
  96. open: The open character.
  97. expected: Whether the text is wrapped.
  98. """
  99. assert format.is_wrapped(text, open) == expected
  100. @pytest.mark.parametrize(
  101. "text,open,check_first,num,expected",
  102. [
  103. ("", "{", True, 1, "{}"),
  104. ("wrap", "{", True, 1, "{wrap}"),
  105. ("wrap", "(", True, 1, "(wrap)"),
  106. ("wrap", "(", True, 2, "((wrap))"),
  107. ("(wrap)", "(", True, 1, "(wrap)"),
  108. ("{wrap}", "{", True, 2, "{wrap}"),
  109. ("(wrap)", "{", True, 1, "{(wrap)}"),
  110. ("(wrap)", "(", False, 1, "((wrap))"),
  111. ],
  112. )
  113. def test_wrap(text: str, open: str, expected: str, check_first: bool, num: int):
  114. """Test wrapping a string.
  115. Args:
  116. text: The text to wrap.
  117. open: The open character.
  118. expected: The expected output string.
  119. check_first: Whether to check if the text is already wrapped.
  120. num: The number of times to wrap the text.
  121. """
  122. assert format.wrap(text, open, check_first=check_first, num=num) == expected
  123. @pytest.mark.parametrize(
  124. "text,indent_level,expected",
  125. [
  126. ("", 2, ""),
  127. ("hello", 2, "hello"),
  128. ("hello\nworld", 2, " hello\n world\n"),
  129. ("hello\nworld", 4, " hello\n world\n"),
  130. (" hello\n world", 2, " hello\n world\n"),
  131. ],
  132. )
  133. def test_indent(text: str, indent_level: int, expected: str, windows_platform: bool):
  134. """Test indenting a string.
  135. Args:
  136. text: The text to indent.
  137. indent_level: The number of spaces to indent by.
  138. expected: The expected output string.
  139. windows_platform: Whether the system is windows.
  140. """
  141. assert format.indent(text, indent_level) == (
  142. expected.replace("\n", "\r\n") if windows_platform else expected
  143. )
  144. @pytest.mark.parametrize(
  145. "condition,true_value,false_value,expected",
  146. [
  147. ("cond", "<C1>", '""', '{isTrue(cond) ? <C1> : ""}'),
  148. ("cond", "<C1>", "<C2>", "{isTrue(cond) ? <C1> : <C2>}"),
  149. ],
  150. )
  151. def test_format_cond(condition: str, true_value: str, false_value: str, expected: str):
  152. """Test formatting a cond.
  153. Args:
  154. condition: The condition to check.
  155. true_value: The value to return if the condition is true.
  156. false_value: The value to return if the condition is false.
  157. expected: The expected output string.
  158. """
  159. assert format.format_cond(condition, true_value, false_value) == expected
  160. def test_merge_imports():
  161. """Test that imports are merged correctly."""
  162. d1 = {"react": {"Component"}}
  163. d2 = {"react": {"Component"}, "react-dom": {"render"}}
  164. d = imports.merge_imports(d1, d2)
  165. assert set(d.keys()) == {"react", "react-dom"}
  166. assert set(d["react"]) == {"Component"}
  167. assert set(d["react-dom"]) == {"render"}
  168. @pytest.mark.parametrize(
  169. "cls,expected",
  170. [
  171. (str, False),
  172. (int, False),
  173. (float, False),
  174. (bool, False),
  175. (List, True),
  176. (List[int], True),
  177. ],
  178. )
  179. def test_is_generic_alias(cls: type, expected: bool):
  180. """Test checking if a class is a GenericAlias.
  181. Args:
  182. cls: The class to check.
  183. expected: Whether the class is a GenericAlias.
  184. """
  185. assert types.is_generic_alias(cls) == expected
  186. @pytest.mark.parametrize(
  187. "route,expected",
  188. [
  189. ("", "index"),
  190. ("/", "index"),
  191. ("custom-route", "custom-route"),
  192. ("custom-route/", "custom-route"),
  193. ("/custom-route", "custom-route"),
  194. ],
  195. )
  196. def test_format_route(route: str, expected: bool):
  197. """Test formatting a route.
  198. Args:
  199. route: The route to format.
  200. expected: The expected formatted route.
  201. """
  202. assert format.format_route(route) == expected
  203. @pytest.mark.parametrize(
  204. "bun_version,is_valid, prompt_input",
  205. [
  206. (V055, False, "yes"),
  207. (V059, True, None),
  208. (V0510, False, "yes"),
  209. ],
  210. )
  211. def test_bun_validate_and_install(mocker, bun_version, is_valid, prompt_input):
  212. """Test that the bun version on host system is validated properly. Also test that
  213. the required bun version is installed should the user opt for it.
  214. Args:
  215. mocker: Pytest mocker object.
  216. bun_version: The bun version.
  217. is_valid: Whether bun version is valid for running pynecone.
  218. prompt_input: The input from user on whether to install bun.
  219. """
  220. mocker.patch(
  221. "pynecone.utils.prerequisites.get_bun_version", return_value=bun_version
  222. )
  223. mocker.patch("pynecone.utils.prerequisites.console.ask", return_value=prompt_input)
  224. bun_install = mocker.patch("pynecone.utils.prerequisites.install_bun")
  225. remove_existing_bun_installation = mocker.patch(
  226. "pynecone.utils.prerequisites.remove_existing_bun_installation"
  227. )
  228. prerequisites.validate_and_install_bun()
  229. if not is_valid:
  230. remove_existing_bun_installation.assert_called_once()
  231. bun_install.assert_called_once()
  232. def test_bun_validation_exception(mocker):
  233. """Test that an exception is thrown and program exists when user selects no when asked
  234. whether to install bun or not.
  235. Args:
  236. mocker: Pytest mocker.
  237. """
  238. mocker.patch("pynecone.utils.prerequisites.get_bun_version", return_value=V056)
  239. mocker.patch("pynecone.utils.prerequisites.console.ask", return_value="no")
  240. with pytest.raises(RuntimeError):
  241. prerequisites.validate_and_install_bun()
  242. def test_remove_existing_bun_installation(mocker, tmp_path):
  243. """Test that existing bun installation is removed.
  244. Args:
  245. mocker: Pytest mocker.
  246. tmp_path: test path.
  247. """
  248. bun_location = tmp_path / ".bun"
  249. bun_location.mkdir()
  250. mocker.patch(
  251. "pynecone.utils.prerequisites.get_package_manager",
  252. return_value=str(bun_location),
  253. )
  254. mocker.patch(
  255. "pynecone.utils.prerequisites.os.path.expandvars",
  256. return_value=str(bun_location),
  257. )
  258. prerequisites.remove_existing_bun_installation()
  259. assert not bun_location.exists()
  260. def test_setup_frontend(tmp_path, mocker):
  261. """Test checking if assets content have been
  262. copied into the .web/public folder.
  263. Args:
  264. tmp_path: root path of test case data directory
  265. mocker: mocker object to allow mocking
  266. """
  267. web_folder = tmp_path / ".web"
  268. web_public_folder = web_folder / "public"
  269. assets = tmp_path / "assets"
  270. assets.mkdir()
  271. (assets / "favicon.ico").touch()
  272. assert str(web_folder) == prerequisites.create_web_directory(tmp_path)
  273. mocker.patch("pynecone.utils.prerequisites.install_frontend_packages")
  274. build.setup_frontend(tmp_path, disable_telemetry=False)
  275. assert web_folder.exists()
  276. assert web_public_folder.exists()
  277. assert (web_public_folder / "favicon.ico").exists()
  278. @pytest.mark.parametrize(
  279. "input, output",
  280. [
  281. ("_hidden", True),
  282. ("not_hidden", False),
  283. ("__dundermethod__", False),
  284. ],
  285. )
  286. def test_is_backend_variable(input, output):
  287. assert types.is_backend_variable(input) == output
  288. @pytest.mark.parametrize(
  289. "cls, cls_check, expected",
  290. [
  291. (int, int, True),
  292. (int, float, False),
  293. (int, Union[int, float], True),
  294. (float, Union[int, float], True),
  295. (str, Union[int, float], False),
  296. (List[int], List[int], True),
  297. (List[int], List[float], True),
  298. (Union[int, float], Union[int, float], False),
  299. (Union[int, Var[int]], Var[int], False),
  300. (int, Any, True),
  301. (Any, Any, True),
  302. (Union[int, float], Any, True),
  303. ],
  304. )
  305. def test_issubclass(cls: type, cls_check: type, expected: bool):
  306. assert types._issubclass(cls, cls_check) == expected
  307. def test_format_sub_state_event(upload_sub_state_event_spec):
  308. """Test formatting an upload event spec of substate.
  309. Args:
  310. upload_sub_state_event_spec: The event spec fixture.
  311. """
  312. assert (
  313. format.format_upload_event(upload_sub_state_event_spec)
  314. == "uploadFiles(base_state, result, setResult, base_state.files, "
  315. '"base_state.sub_upload_state.handle_upload",UPLOAD)'
  316. )
  317. def test_format_upload_event(upload_event_spec):
  318. """Test formatting an upload event spec.
  319. Args:
  320. upload_event_spec: The event spec fixture.
  321. """
  322. assert (
  323. format.format_upload_event(upload_event_spec)
  324. == "uploadFiles(upload_state, result, setResult, "
  325. 'upload_state.files, "upload_state.handle_upload1",'
  326. "UPLOAD)"
  327. )
  328. @pytest.mark.parametrize(
  329. "app_name,expected_config_name",
  330. [
  331. ("appname", "AppnameConfig"),
  332. ("app_name", "AppnameConfig"),
  333. ("app-name", "AppnameConfig"),
  334. ("appname2.io", "AppnameioConfig"),
  335. ],
  336. )
  337. def test_create_config(app_name, expected_config_name, mocker):
  338. """Test templates.PCCONFIG is formatted with correct app name and config class name.
  339. Args:
  340. app_name: App name.
  341. expected_config_name: Expected config name.
  342. mocker: Mocker object.
  343. """
  344. mocker.patch("builtins.open")
  345. tmpl_mock = mocker.patch("pynecone.compiler.templates.PCCONFIG")
  346. prerequisites.create_config(app_name)
  347. tmpl_mock.render.assert_called_with(
  348. app_name=app_name, config_name=expected_config_name
  349. )
  350. @pytest.mark.parametrize(
  351. "name,expected",
  352. [
  353. ("input1", "ref_input1"),
  354. ("input 1", "ref_input_1"),
  355. ("input-1", "ref_input_1"),
  356. ("input_1", "ref_input_1"),
  357. ("a long test?1! name", "ref_a_long_test_1_name"),
  358. ],
  359. )
  360. def test_format_ref(name, expected):
  361. """Test formatting a ref.
  362. Args:
  363. name: The name to format.
  364. expected: The expected formatted name.
  365. """
  366. assert format.format_ref(name) == expected
  367. class DataFrame:
  368. """A Fake pandas DataFrame class."""
  369. pass
  370. @pytest.mark.parametrize(
  371. "class_type,expected",
  372. [
  373. (list, False),
  374. (int, False),
  375. (dict, False),
  376. (DataFrame, True),
  377. (typing.Any, False),
  378. (typing.List, False),
  379. ],
  380. )
  381. def test_is_dataframe(class_type, expected):
  382. """Test that a type name is DataFrame.
  383. Args:
  384. class_type: the class type.
  385. expected: whether type name is DataFrame
  386. """
  387. assert types.is_dataframe(class_type) == expected