test_utils.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738
  1. import os
  2. import typing
  3. from pathlib import Path
  4. from typing import Any, List, Union
  5. import pytest
  6. import typer
  7. from packaging import version
  8. from reflex import constants
  9. from reflex.base import Base
  10. from reflex.components.tags import Tag
  11. from reflex.event import EVENT_ARG, EventChain, EventHandler, EventSpec
  12. from reflex.style import Style
  13. from reflex.utils import (
  14. build,
  15. format,
  16. imports,
  17. prerequisites,
  18. types,
  19. )
  20. from reflex.utils import exec as utils_exec
  21. from reflex.vars import BaseVar, Var
  22. def mock_event(arg):
  23. pass
  24. def get_above_max_version():
  25. """Get the 1 version above the max required bun version.
  26. Returns:
  27. max bun version plus one.
  28. """
  29. semantic_version_list = constants.BUN_VERSION.split(".")
  30. semantic_version_list[-1] = str(int(semantic_version_list[-1]) + 1) # type: ignore
  31. return ".".join(semantic_version_list)
  32. V055 = version.parse("0.5.5")
  33. V059 = version.parse("0.5.9")
  34. V056 = version.parse("0.5.6")
  35. VMAXPLUS1 = version.parse(get_above_max_version())
  36. @pytest.mark.parametrize(
  37. "input,output",
  38. [
  39. ("", ""),
  40. ("hello", "hello"),
  41. ("Hello", "hello"),
  42. ("camelCase", "camel_case"),
  43. ("camelTwoHumps", "camel_two_humps"),
  44. ("_start_with_underscore", "_start_with_underscore"),
  45. ("__start_with_double_underscore", "__start_with_double_underscore"),
  46. ],
  47. )
  48. def test_to_snake_case(input: str, output: str):
  49. """Test converting strings to snake case.
  50. Args:
  51. input: The input string.
  52. output: The expected output string.
  53. """
  54. assert format.to_snake_case(input) == output
  55. @pytest.mark.parametrize(
  56. "input,output",
  57. [
  58. ("", ""),
  59. ("hello", "hello"),
  60. ("Hello", "Hello"),
  61. ("snake_case", "snakeCase"),
  62. ("snake_case_two", "snakeCaseTwo"),
  63. ],
  64. )
  65. def test_to_camel_case(input: str, output: str):
  66. """Test converting strings to camel case.
  67. Args:
  68. input: The input string.
  69. output: The expected output string.
  70. """
  71. assert format.to_camel_case(input) == output
  72. @pytest.mark.parametrize(
  73. "input,output",
  74. [
  75. ("", ""),
  76. ("hello", "Hello"),
  77. ("Hello", "Hello"),
  78. ("snake_case", "SnakeCase"),
  79. ("snake_case_two", "SnakeCaseTwo"),
  80. ],
  81. )
  82. def test_to_title_case(input: str, output: str):
  83. """Test converting strings to title case.
  84. Args:
  85. input: The input string.
  86. output: The expected output string.
  87. """
  88. assert format.to_title_case(input) == output
  89. @pytest.mark.parametrize(
  90. "input,output",
  91. [
  92. ("{", "}"),
  93. ("(", ")"),
  94. ("[", "]"),
  95. ("<", ">"),
  96. ('"', '"'),
  97. ("'", "'"),
  98. ],
  99. )
  100. def test_get_close_char(input: str, output: str):
  101. """Test getting the close character for a given open character.
  102. Args:
  103. input: The open character.
  104. output: The expected close character.
  105. """
  106. assert format.get_close_char(input) == output
  107. @pytest.mark.parametrize(
  108. "text,open,expected",
  109. [
  110. ("", "{", False),
  111. ("{wrap}", "{", True),
  112. ("{wrap", "{", False),
  113. ("{wrap}", "(", False),
  114. ("(wrap)", "(", True),
  115. ],
  116. )
  117. def test_is_wrapped(text: str, open: str, expected: bool):
  118. """Test checking if a string is wrapped in the given open and close characters.
  119. Args:
  120. text: The text to check.
  121. open: The open character.
  122. expected: Whether the text is wrapped.
  123. """
  124. assert format.is_wrapped(text, open) == expected
  125. @pytest.mark.parametrize(
  126. "text,open,check_first,num,expected",
  127. [
  128. ("", "{", True, 1, "{}"),
  129. ("wrap", "{", True, 1, "{wrap}"),
  130. ("wrap", "(", True, 1, "(wrap)"),
  131. ("wrap", "(", True, 2, "((wrap))"),
  132. ("(wrap)", "(", True, 1, "(wrap)"),
  133. ("{wrap}", "{", True, 2, "{wrap}"),
  134. ("(wrap)", "{", True, 1, "{(wrap)}"),
  135. ("(wrap)", "(", False, 1, "((wrap))"),
  136. ],
  137. )
  138. def test_wrap(text: str, open: str, expected: str, check_first: bool, num: int):
  139. """Test wrapping a string.
  140. Args:
  141. text: The text to wrap.
  142. open: The open character.
  143. expected: The expected output string.
  144. check_first: Whether to check if the text is already wrapped.
  145. num: The number of times to wrap the text.
  146. """
  147. assert format.wrap(text, open, check_first=check_first, num=num) == expected
  148. @pytest.mark.parametrize(
  149. "text,indent_level,expected",
  150. [
  151. ("", 2, ""),
  152. ("hello", 2, "hello"),
  153. ("hello\nworld", 2, " hello\n world\n"),
  154. ("hello\nworld", 4, " hello\n world\n"),
  155. (" hello\n world", 2, " hello\n world\n"),
  156. ],
  157. )
  158. def test_indent(text: str, indent_level: int, expected: str, windows_platform: bool):
  159. """Test indenting a string.
  160. Args:
  161. text: The text to indent.
  162. indent_level: The number of spaces to indent by.
  163. expected: The expected output string.
  164. windows_platform: Whether the system is windows.
  165. """
  166. assert format.indent(text, indent_level) == (
  167. expected.replace("\n", "\r\n") if windows_platform else expected
  168. )
  169. @pytest.mark.parametrize(
  170. "condition,true_value,false_value,expected",
  171. [
  172. ("cond", "<C1>", '""', '{isTrue(cond) ? <C1> : ""}'),
  173. ("cond", "<C1>", "<C2>", "{isTrue(cond) ? <C1> : <C2>}"),
  174. ],
  175. )
  176. def test_format_cond(condition: str, true_value: str, false_value: str, expected: str):
  177. """Test formatting a cond.
  178. Args:
  179. condition: The condition to check.
  180. true_value: The value to return if the condition is true.
  181. false_value: The value to return if the condition is false.
  182. expected: The expected output string.
  183. """
  184. assert format.format_cond(condition, true_value, false_value) == expected
  185. def test_merge_imports():
  186. """Test that imports are merged correctly."""
  187. d1 = {"react": {"Component"}}
  188. d2 = {"react": {"Component"}, "react-dom": {"render"}}
  189. d = imports.merge_imports(d1, d2)
  190. assert set(d.keys()) == {"react", "react-dom"}
  191. assert set(d["react"]) == {"Component"}
  192. assert set(d["react-dom"]) == {"render"}
  193. @pytest.mark.parametrize(
  194. "cls,expected",
  195. [
  196. (str, False),
  197. (int, False),
  198. (float, False),
  199. (bool, False),
  200. (List, True),
  201. (List[int], True),
  202. ],
  203. )
  204. def test_is_generic_alias(cls: type, expected: bool):
  205. """Test checking if a class is a GenericAlias.
  206. Args:
  207. cls: The class to check.
  208. expected: Whether the class is a GenericAlias.
  209. """
  210. assert types.is_generic_alias(cls) == expected
  211. @pytest.mark.parametrize(
  212. "route,expected",
  213. [
  214. ("", "index"),
  215. ("/", "index"),
  216. ("custom-route", "custom-route"),
  217. ("custom-route/", "custom-route"),
  218. ("/custom-route", "custom-route"),
  219. ],
  220. )
  221. def test_format_route(route: str, expected: bool):
  222. """Test formatting a route.
  223. Args:
  224. route: The route to format.
  225. expected: The expected formatted route.
  226. """
  227. assert format.format_route(route) == expected
  228. @pytest.mark.parametrize(
  229. "prop,formatted",
  230. [
  231. ("string", '"string"'),
  232. ("{wrapped_string}", "{wrapped_string}"),
  233. (True, "{true}"),
  234. (False, "{false}"),
  235. (123, "{123}"),
  236. (3.14, "{3.14}"),
  237. ([1, 2, 3], "{[1, 2, 3]}"),
  238. (["a", "b", "c"], '{["a", "b", "c"]}'),
  239. ({"a": 1, "b": 2, "c": 3}, '{{"a": 1, "b": 2, "c": 3}}'),
  240. ({"a": 'foo "bar" baz'}, r'{{"a": "foo \"bar\" baz"}}'),
  241. (
  242. {
  243. "a": 'foo "{ "bar" }" baz',
  244. "b": BaseVar(name="val", type_="str"),
  245. },
  246. r'{{"a": "foo \"{ \"bar\" }\" baz", "b": val}}',
  247. ),
  248. (
  249. EventChain(events=[EventSpec(handler=EventHandler(fn=mock_event))]),
  250. '{_e => Event([E("mock_event", {})], _e)}',
  251. ),
  252. (
  253. EventChain(
  254. events=[
  255. EventSpec(
  256. handler=EventHandler(fn=mock_event),
  257. args=((Var.create_safe("arg"), EVENT_ARG.target.value),),
  258. )
  259. ]
  260. ),
  261. '{_e => Event([E("mock_event", {arg:_e.target.value})], _e)}',
  262. ),
  263. ({"a": "red", "b": "blue"}, '{{"a": "red", "b": "blue"}}'),
  264. (BaseVar(name="var", type_="int"), "{var}"),
  265. (
  266. BaseVar(
  267. name="_",
  268. type_=Any,
  269. state="",
  270. is_local=True,
  271. is_string=False,
  272. ),
  273. "{_}",
  274. ),
  275. (BaseVar(name='state.colors["a"]', type_="str"), '{state.colors["a"]}'),
  276. ({"a": BaseVar(name="val", type_="str")}, '{{"a": val}}'),
  277. ({"a": BaseVar(name='"val"', type_="str")}, '{{"a": "val"}}'),
  278. (
  279. {"a": BaseVar(name='state.colors["val"]', type_="str")},
  280. '{{"a": state.colors["val"]}}',
  281. ),
  282. # tricky real-world case from markdown component
  283. (
  284. {
  285. "h1": f"{{({{node, ...props}}) => <Heading {{...props}} {''.join(Tag(name='', props=Style({'as_': 'h1'})).format_props())} />}}"
  286. },
  287. '{{"h1": ({node, ...props}) => <Heading {...props} as={`h1`} />}}',
  288. ),
  289. ],
  290. )
  291. def test_format_prop(prop: Var, formatted: str):
  292. """Test that the formatted value of an prop is correct.
  293. Args:
  294. prop: The prop to test.
  295. formatted: The expected formatted value.
  296. """
  297. assert format.format_prop(prop) == formatted
  298. def test_validate_invalid_bun_path(mocker):
  299. """Test that an error is thrown when a custom specified bun path is not valid
  300. or does not exist.
  301. Args:
  302. mocker: Pytest mocker object.
  303. """
  304. mock = mocker.Mock()
  305. mocker.patch.object(mock, "bun_path", return_value="/mock/path")
  306. mocker.patch("reflex.utils.prerequisites.get_config", mock)
  307. mocker.patch("reflex.utils.prerequisites.get_bun_version", return_value=None)
  308. with pytest.raises(typer.Exit):
  309. prerequisites.validate_bun()
  310. def test_validate_bun_path_incompatible_version(mocker):
  311. """Test that an error is thrown when the bun version does not meet minimum requirements.
  312. Args:
  313. mocker: Pytest mocker object.
  314. """
  315. mock = mocker.Mock()
  316. mocker.patch.object(mock, "bun_path", return_value="/mock/path")
  317. mocker.patch("reflex.utils.prerequisites.get_config", mock)
  318. mocker.patch(
  319. "reflex.utils.prerequisites.get_bun_version",
  320. return_value=version.parse("0.6.5"),
  321. )
  322. with pytest.raises(typer.Exit):
  323. prerequisites.validate_bun()
  324. def test_remove_existing_bun_installation(mocker):
  325. """Test that existing bun installation is removed.
  326. Args:
  327. mocker: Pytest mocker.
  328. """
  329. mocker.patch("reflex.utils.prerequisites.os.path.exists", return_value=True)
  330. rm = mocker.patch("reflex.utils.prerequisites.path_ops.rm", mocker.Mock())
  331. prerequisites.remove_existing_bun_installation()
  332. rm.assert_called_once()
  333. def test_setup_frontend(tmp_path, mocker):
  334. """Test checking if assets content have been
  335. copied into the .web/public folder.
  336. Args:
  337. tmp_path: root path of test case data directory
  338. mocker: mocker object to allow mocking
  339. """
  340. web_public_folder = tmp_path / ".web" / "public"
  341. assets = tmp_path / "assets"
  342. assets.mkdir()
  343. (assets / "favicon.ico").touch()
  344. mocker.patch("reflex.utils.prerequisites.install_frontend_packages")
  345. mocker.patch("reflex.utils.build.set_env_json")
  346. build.setup_frontend(tmp_path, disable_telemetry=False)
  347. assert web_public_folder.exists()
  348. assert (web_public_folder / "favicon.ico").exists()
  349. @pytest.mark.parametrize(
  350. "input, output",
  351. [
  352. ("_hidden", True),
  353. ("not_hidden", False),
  354. ("__dundermethod__", False),
  355. ],
  356. )
  357. def test_is_backend_variable(input, output):
  358. assert types.is_backend_variable(input) == output
  359. @pytest.mark.parametrize(
  360. "cls, cls_check, expected",
  361. [
  362. (int, int, True),
  363. (int, float, False),
  364. (int, Union[int, float], True),
  365. (float, Union[int, float], True),
  366. (str, Union[int, float], False),
  367. (List[int], List[int], True),
  368. (List[int], List[float], True),
  369. (Union[int, float], Union[int, float], False),
  370. (Union[int, Var[int]], Var[int], False),
  371. (int, Any, True),
  372. (Any, Any, True),
  373. (Union[int, float], Any, True),
  374. ],
  375. )
  376. def test_issubclass(cls: type, cls_check: type, expected: bool):
  377. assert types._issubclass(cls, cls_check) == expected
  378. @pytest.mark.parametrize(
  379. "app_name,expected_config_name",
  380. [
  381. ("appname", "AppnameConfig"),
  382. ("app_name", "AppnameConfig"),
  383. ("app-name", "AppnameConfig"),
  384. ("appname2.io", "AppnameioConfig"),
  385. ],
  386. )
  387. def test_create_config(app_name, expected_config_name, mocker):
  388. """Test templates.RXCONFIG is formatted with correct app name and config class name.
  389. Args:
  390. app_name: App name.
  391. expected_config_name: Expected config name.
  392. mocker: Mocker object.
  393. """
  394. mocker.patch("builtins.open")
  395. tmpl_mock = mocker.patch("reflex.compiler.templates.RXCONFIG")
  396. prerequisites.create_config(app_name)
  397. tmpl_mock.render.assert_called_with(
  398. app_name=app_name, config_name=expected_config_name
  399. )
  400. @pytest.fixture
  401. def tmp_working_dir(tmp_path):
  402. """Create a temporary directory and chdir to it.
  403. After the test executes, chdir back to the original working directory.
  404. Args:
  405. tmp_path: pytest tmp_path fixture creates per-test temp dir
  406. Yields:
  407. subdirectory of tmp_path which is now the current working directory.
  408. """
  409. old_pwd = Path(".").resolve()
  410. working_dir = tmp_path / "working_dir"
  411. working_dir.mkdir()
  412. os.chdir(working_dir)
  413. yield working_dir
  414. os.chdir(old_pwd)
  415. def test_create_config_e2e(tmp_working_dir):
  416. """Create a new config file, exec it, and make assertions about the config.
  417. Args:
  418. tmp_working_dir: a new directory that is the current working directory
  419. for the duration of the test.
  420. """
  421. app_name = "e2e"
  422. prerequisites.create_config(app_name)
  423. eval_globals = {}
  424. exec((tmp_working_dir / constants.CONFIG_FILE).read_text(), eval_globals)
  425. config = eval_globals["config"]
  426. assert config.app_name == app_name
  427. @pytest.mark.parametrize(
  428. "name,expected",
  429. [
  430. ("input1", "ref_input1"),
  431. ("input 1", "ref_input_1"),
  432. ("input-1", "ref_input_1"),
  433. ("input_1", "ref_input_1"),
  434. ("a long test?1! name", "ref_a_long_test_1_name"),
  435. ],
  436. )
  437. def test_format_ref(name, expected):
  438. """Test formatting a ref.
  439. Args:
  440. name: The name to format.
  441. expected: The expected formatted name.
  442. """
  443. assert format.format_ref(name) == expected
  444. class DataFrame:
  445. """A Fake pandas DataFrame class."""
  446. pass
  447. @pytest.mark.parametrize(
  448. "class_type,expected",
  449. [
  450. (list, False),
  451. (int, False),
  452. (dict, False),
  453. (DataFrame, True),
  454. (typing.Any, False),
  455. (typing.List, False),
  456. ],
  457. )
  458. def test_is_dataframe(class_type, expected):
  459. """Test that a type name is DataFrame.
  460. Args:
  461. class_type: the class type.
  462. expected: whether type name is DataFrame
  463. """
  464. assert types.is_dataframe(class_type) == expected
  465. @pytest.mark.parametrize("gitignore_exists", [True, False])
  466. def test_initialize_non_existent_gitignore(tmp_path, mocker, gitignore_exists):
  467. """Test that the generated .gitignore_file file on reflex init contains the correct file
  468. names with correct formatting.
  469. Args:
  470. tmp_path: The root test path.
  471. mocker: The mock object.
  472. gitignore_exists: Whether a gitignore file exists in the root dir.
  473. """
  474. expected = constants.DEFAULT_GITIGNORE.copy()
  475. mocker.patch("reflex.constants.GITIGNORE_FILE", tmp_path / ".gitignore")
  476. gitignore_file = tmp_path / ".gitignore"
  477. if gitignore_exists:
  478. gitignore_file.touch()
  479. gitignore_file.write_text(
  480. """*.db
  481. __pycache__/
  482. """
  483. )
  484. prerequisites.initialize_gitignore()
  485. assert gitignore_file.exists()
  486. file_content = [
  487. line.strip() for line in gitignore_file.open().read().splitlines() if line
  488. ]
  489. assert set(file_content) - expected == set()
  490. def test_app_default_name(tmp_path, mocker):
  491. """Test that an error is raised if the app name is reflex.
  492. Args:
  493. tmp_path: Test working dir.
  494. mocker: Pytest mocker object.
  495. """
  496. reflex = tmp_path / "reflex"
  497. reflex.mkdir()
  498. mocker.patch("reflex.utils.prerequisites.os.getcwd", return_value=str(reflex))
  499. with pytest.raises(typer.Exit):
  500. prerequisites.get_default_app_name()
  501. def test_node_install_windows(tmp_path, mocker):
  502. """Require user to install node manually for windows if node is not installed.
  503. Args:
  504. tmp_path: Test working dir.
  505. mocker: Pytest mocker object.
  506. """
  507. fnm_root_path = tmp_path / "reflex" / "fnm"
  508. fnm_exe = fnm_root_path / "fnm.exe"
  509. mocker.patch("reflex.utils.prerequisites.constants.FNM_DIR", fnm_root_path)
  510. mocker.patch("reflex.utils.prerequisites.constants.FNM_EXE", fnm_exe)
  511. mocker.patch("reflex.utils.prerequisites.constants.IS_WINDOWS", True)
  512. mocker.patch("reflex.utils.processes.new_process")
  513. mocker.patch("reflex.utils.processes.stream_logs")
  514. class Resp(Base):
  515. status_code = 200
  516. text = "test"
  517. mocker.patch("httpx.stream", return_value=Resp())
  518. download = mocker.patch("reflex.utils.prerequisites.download_and_extract_fnm_zip")
  519. mocker.patch("reflex.utils.prerequisites.zipfile.ZipFile")
  520. mocker.patch("reflex.utils.prerequisites.path_ops.rm")
  521. prerequisites.install_node()
  522. assert fnm_root_path.exists()
  523. download.assert_called_once()
  524. @pytest.mark.parametrize(
  525. "machine, system",
  526. [
  527. ("x64", "Darwin"),
  528. ("arm64", "Darwin"),
  529. ("x64", "Windows"),
  530. ("arm64", "Windows"),
  531. ("armv7", "Linux"),
  532. ("armv8-a", "Linux"),
  533. ("armv8.1-a", "Linux"),
  534. ("armv8.2-a", "Linux"),
  535. ("armv8.3-a", "Linux"),
  536. ("armv8.4-a", "Linux"),
  537. ("aarch64", "Linux"),
  538. ("aarch32", "Linux"),
  539. ],
  540. )
  541. def test_node_install_unix(tmp_path, mocker, machine, system):
  542. fnm_root_path = tmp_path / "reflex" / "fnm"
  543. fnm_exe = fnm_root_path / "fnm"
  544. mocker.patch("reflex.utils.prerequisites.constants.FNM_DIR", fnm_root_path)
  545. mocker.patch("reflex.utils.prerequisites.constants.FNM_EXE", fnm_exe)
  546. mocker.patch("reflex.utils.prerequisites.constants.IS_WINDOWS", False)
  547. mocker.patch("reflex.utils.prerequisites.platform.machine", return_value=machine)
  548. mocker.patch("reflex.utils.prerequisites.platform.system", return_value=system)
  549. class Resp(Base):
  550. status_code = 200
  551. text = "test"
  552. mocker.patch("httpx.stream", return_value=Resp())
  553. download = mocker.patch("reflex.utils.prerequisites.download_and_extract_fnm_zip")
  554. process = mocker.patch("reflex.utils.processes.new_process")
  555. chmod = mocker.patch("reflex.utils.prerequisites.os.chmod")
  556. mocker.patch("reflex.utils.processes.stream_logs")
  557. prerequisites.install_node()
  558. assert fnm_root_path.exists()
  559. download.assert_called_once()
  560. if system == "Darwin" and machine == "arm64":
  561. process.assert_called_with(
  562. [
  563. fnm_exe,
  564. "install",
  565. "--arch=arm64",
  566. constants.NODE_VERSION,
  567. "--fnm-dir",
  568. fnm_root_path,
  569. ]
  570. )
  571. else:
  572. process.assert_called_with(
  573. [fnm_exe, "install", constants.NODE_VERSION, "--fnm-dir", fnm_root_path]
  574. )
  575. chmod.assert_called_once()
  576. def test_bun_install_without_unzip(mocker):
  577. """Test that an error is thrown when installing bun with unzip not installed.
  578. Args:
  579. mocker: Pytest mocker object.
  580. """
  581. mocker.patch("reflex.utils.path_ops.which", return_value=None)
  582. mocker.patch("os.path.exists", return_value=False)
  583. mocker.patch("reflex.utils.prerequisites.constants.IS_WINDOWS", False)
  584. with pytest.raises(FileNotFoundError):
  585. prerequisites.install_bun()
  586. @pytest.mark.parametrize("is_windows", [True, False])
  587. def test_create_reflex_dir(mocker, is_windows):
  588. """Test that a reflex directory is created on initializing frontend
  589. dependencies.
  590. Args:
  591. mocker: Pytest mocker object.
  592. is_windows: Whether platform is windows.
  593. """
  594. mocker.patch("reflex.utils.prerequisites.constants.IS_WINDOWS", is_windows)
  595. mocker.patch("reflex.utils.prerequisites.processes.run_concurrently", mocker.Mock())
  596. mocker.patch("reflex.utils.prerequisites.initialize_web_directory", mocker.Mock())
  597. mocker.patch("reflex.utils.processes.run_concurrently")
  598. mocker.patch("reflex.utils.prerequisites.validate_bun")
  599. create_cmd = mocker.patch(
  600. "reflex.utils.prerequisites.path_ops.mkdir", mocker.Mock()
  601. )
  602. prerequisites.initialize_frontend_dependencies()
  603. assert create_cmd.called
  604. def test_output_system_info(mocker):
  605. """Make sure reflex does not crash dumping system info.
  606. Args:
  607. mocker: Pytest mocker object.
  608. This test makes no assertions about the output, other than it executes
  609. without crashing.
  610. """
  611. mocker.patch("reflex.utils.console.LOG_LEVEL", constants.LogLevel.DEBUG)
  612. utils_exec.output_system_info()