123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332 |
- import importlib.util
- import os
- from pathlib import Path
- import pytest
- from reflex import constants
- from reflex.compiler import compiler, utils
- from reflex.constants.compiler import PageNames
- from reflex.utils.imports import ImportVar, ParsedImportDict
- @pytest.mark.parametrize(
- "fields,test_default,test_rest",
- [
- (
- [ImportVar(tag="axios", is_default=True)],
- "axios",
- [],
- ),
- (
- [ImportVar(tag="foo"), ImportVar(tag="bar")],
- "",
- ["bar", "foo"],
- ),
- (
- [
- ImportVar(tag="axios", is_default=True),
- ImportVar(tag="foo"),
- ImportVar(tag="bar"),
- ],
- "axios",
- ["bar", "foo"],
- ),
- ],
- )
- def test_compile_import_statement(
- fields: list[ImportVar], test_default: str, test_rest: str
- ):
- """Test the compile_import_statement function.
- Args:
- fields: The fields to import.
- test_default: The expected output of default library.
- test_rest: The expected output rest libraries.
- """
- default, rest = utils.compile_import_statement(fields)
- assert default == test_default
- assert sorted(rest) == test_rest
- @pytest.mark.parametrize(
- "import_dict,test_dicts",
- [
- ({}, []),
- (
- {"axios": [ImportVar(tag="axios", is_default=True)]},
- [{"lib": "axios", "default": "axios", "rest": []}],
- ),
- (
- {"axios": [ImportVar(tag="foo"), ImportVar(tag="bar")]},
- [{"lib": "axios", "default": "", "rest": ["bar", "foo"]}],
- ),
- (
- {
- "axios": [
- ImportVar(tag="axios", is_default=True),
- ImportVar(tag="foo"),
- ImportVar(tag="bar"),
- ],
- "react": [ImportVar(tag="react", is_default=True)],
- },
- [
- {"lib": "axios", "default": "axios", "rest": ["bar", "foo"]},
- {"lib": "react", "default": "react", "rest": []},
- ],
- ),
- (
- {"": [ImportVar(tag="lib1.js"), ImportVar(tag="lib2.js")]},
- [
- {"lib": "lib1.js", "default": "", "rest": []},
- {"lib": "lib2.js", "default": "", "rest": []},
- ],
- ),
- (
- {
- "": [ImportVar(tag="lib1.js"), ImportVar(tag="lib2.js")],
- "axios": [ImportVar(tag="axios", is_default=True)],
- },
- [
- {"lib": "lib1.js", "default": "", "rest": []},
- {"lib": "lib2.js", "default": "", "rest": []},
- {"lib": "axios", "default": "axios", "rest": []},
- ],
- ),
- ],
- )
- def test_compile_imports(import_dict: ParsedImportDict, test_dicts: list[dict]):
- """Test the compile_imports function.
- Args:
- import_dict: The import dictionary.
- test_dicts: The expected output.
- """
- imports = utils.compile_imports(import_dict)
- for import_dict, test_dict in zip(imports, test_dicts, strict=True):
- assert import_dict["lib"] == test_dict["lib"]
- assert import_dict["default"] == test_dict["default"]
- assert (
- sorted(
- import_dict["rest"],
- key=lambda i: i if isinstance(i, str) else (i.tag or ""),
- )
- == test_dict["rest"]
- )
- def test_compile_stylesheets(tmp_path: Path, mocker):
- """Test that stylesheets compile correctly.
- Args:
- tmp_path: The test directory.
- mocker: Pytest mocker object.
- """
- project = tmp_path / "test_project"
- project.mkdir()
- assets_dir = project / "assets"
- assets_dir.mkdir()
- (assets_dir / "style.css").write_text(
- "button.rt-Button {\n\tborder-radius:unset !important;\n}"
- )
- mocker.patch("reflex.compiler.compiler.Path.cwd", return_value=project)
- mocker.patch(
- "reflex.compiler.compiler.get_web_dir",
- return_value=project / constants.Dirs.WEB,
- )
- mocker.patch(
- "reflex.compiler.utils.get_web_dir", return_value=project / constants.Dirs.WEB
- )
- stylesheets = [
- "https://fonts.googleapis.com/css?family=Sofia&effect=neon|outline|emboss|shadow-multiple",
- "https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css",
- "/style.css",
- "https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap-theme.min.css",
- ]
- assert compiler.compile_root_stylesheet(stylesheets) == (
- str(
- project
- / constants.Dirs.WEB
- / "styles"
- / (PageNames.STYLESHEET_ROOT + ".css")
- ),
- "@import url('./tailwind.css'); \n"
- "@import url('https://fonts.googleapis.com/css?family=Sofia&effect=neon|outline|emboss|shadow-multiple'); \n"
- "@import url('https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css'); \n"
- "@import url('https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap-theme.min.css'); \n"
- "@import url('./style.css'); \n",
- )
- assert (project / constants.Dirs.WEB / "styles" / "style.css").read_text() == (
- assets_dir / "style.css"
- ).read_text()
- def test_compile_stylesheets_scss_sass(tmp_path: Path, mocker):
- if importlib.util.find_spec("sass") is None:
- pytest.skip(
- 'The `libsass` package is required to compile sass/scss stylesheet files. Run `pip install "libsass>=0.23.0"`.'
- )
- if os.name == "nt":
- pytest.skip("Skipping test on Windows")
- project = tmp_path / "test_project"
- project.mkdir()
- assets_dir = project / "assets"
- assets_dir.mkdir()
- assets_preprocess_dir = assets_dir / "preprocess"
- assets_preprocess_dir.mkdir()
- (assets_dir / "style.css").write_text(
- "button.rt-Button {\n\tborder-radius:unset !important;\n}"
- )
- (assets_preprocess_dir / "styles_a.sass").write_text(
- "button.rt-Button\n\tborder-radius:unset !important"
- )
- (assets_preprocess_dir / "styles_b.scss").write_text(
- "button.rt-Button {\n\tborder-radius:unset !important;\n}"
- )
- mocker.patch("reflex.compiler.compiler.Path.cwd", return_value=project)
- mocker.patch(
- "reflex.compiler.compiler.get_web_dir",
- return_value=project / constants.Dirs.WEB,
- )
- mocker.patch(
- "reflex.compiler.utils.get_web_dir", return_value=project / constants.Dirs.WEB
- )
- stylesheets = [
- "/style.css",
- "/preprocess/styles_a.sass",
- "/preprocess/styles_b.scss",
- ]
- assert compiler.compile_root_stylesheet(stylesheets) == (
- str(
- project
- / constants.Dirs.WEB
- / "styles"
- / (PageNames.STYLESHEET_ROOT + ".css")
- ),
- "@import url('./tailwind.css'); \n"
- "@import url('./style.css'); \n"
- f"@import url('./{Path('preprocess') / Path('styles_a.css')!s}'); \n"
- f"@import url('./{Path('preprocess') / Path('styles_b.css')!s}'); \n",
- )
- stylesheets = [
- "/style.css",
- "/preprocess", # this is a folder containing "styles_a.sass" and "styles_b.scss"
- ]
- assert compiler.compile_root_stylesheet(stylesheets) == (
- str(
- project
- / constants.Dirs.WEB
- / "styles"
- / (PageNames.STYLESHEET_ROOT + ".css")
- ),
- "@import url('./tailwind.css'); \n"
- "@import url('./style.css'); \n"
- f"@import url('./{Path('preprocess') / Path('styles_a.css')!s}'); \n"
- f"@import url('./{Path('preprocess') / Path('styles_b.css')!s}'); \n",
- )
- assert (project / constants.Dirs.WEB / "styles" / "style.css").read_text() == (
- assets_dir / "style.css"
- ).read_text()
- expected_result = "button.rt-Button{border-radius:unset !important}\n"
- assert (
- project / constants.Dirs.WEB / "styles" / "preprocess" / "styles_a.css"
- ).read_text() == expected_result
- assert (
- project / constants.Dirs.WEB / "styles" / "preprocess" / "styles_b.css"
- ).read_text() == expected_result
- def test_compile_stylesheets_exclude_tailwind(tmp_path, mocker):
- """Test that Tailwind is excluded if tailwind config is explicitly set to None.
- Args:
- tmp_path: The test directory.
- mocker: Pytest mocker object.
- """
- project = tmp_path / "test_project"
- project.mkdir()
- assets_dir = project / "assets"
- assets_dir.mkdir()
- mock = mocker.Mock()
- mocker.patch.object(mock, "tailwind", None)
- mocker.patch("reflex.compiler.compiler.get_config", return_value=mock)
- (assets_dir / "style.css").touch()
- mocker.patch("reflex.compiler.compiler.Path.cwd", return_value=project)
- stylesheets = [
- "/style.css",
- ]
- assert compiler.compile_root_stylesheet(stylesheets) == (
- str(Path(".web") / "styles" / (PageNames.STYLESHEET_ROOT + ".css")),
- "@import url('./style.css'); \n",
- )
- def test_compile_nonexistent_stylesheet(tmp_path, mocker):
- """Test that an error is thrown for non-existent stylesheets.
- Args:
- tmp_path: The test directory.
- mocker: Pytest mocker object.
- """
- project = tmp_path / "test_project"
- project.mkdir()
- assets_dir = project / "assets"
- assets_dir.mkdir()
- mocker.patch("reflex.compiler.compiler.Path.cwd", return_value=project)
- stylesheets = ["/style.css"]
- with pytest.raises(FileNotFoundError):
- compiler.compile_root_stylesheet(stylesheets)
- def test_create_document_root():
- """Test that the document root is created correctly."""
- # Test with no components.
- root = utils.create_document_root()
- root.render()
- assert isinstance(root, utils.Html)
- assert isinstance(root.children[0], utils.DocumentHead)
- # Default language.
- assert root.lang == "en" # pyright: ignore [reportAttributeAccessIssue]
- # No children in head.
- assert len(root.children[0].children) == 0
- # Test with components.
- comps = [
- utils.NextScript.create(src="foo.js"),
- utils.NextScript.create(src="bar.js"),
- ]
- root = utils.create_document_root(
- head_components=comps,
- html_lang="rx",
- html_custom_attrs={"project": "reflex"},
- )
- # Two children in head.
- assert isinstance(root, utils.Html)
- assert len(root.children[0].children) == 2
- assert root.lang == "rx" # pyright: ignore [reportAttributeAccessIssue]
- assert isinstance(root.custom_attrs, dict)
- assert root.custom_attrs == {"project": "reflex"}
|