add unit tests
This commit is contained in:
parent
76f7e4c31c
commit
1e3ccf3bc1
@ -555,6 +555,7 @@ def deploy(
|
|||||||
**extra,
|
**extra,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@cli.command()
|
@cli.command()
|
||||||
def rename(
|
def rename(
|
||||||
new_name: str = typer.Argument(..., help="The new name for the app."),
|
new_name: str = typer.Argument(..., help="The new name for the app."),
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import ast
|
|
||||||
import contextlib
|
import contextlib
|
||||||
import dataclasses
|
import dataclasses
|
||||||
import functools
|
import functools
|
||||||
@ -26,7 +25,6 @@ from pathlib import Path
|
|||||||
from types import ModuleType
|
from types import ModuleType
|
||||||
from typing import Callable, List, NamedTuple, Optional
|
from typing import Callable, List, NamedTuple, Optional
|
||||||
|
|
||||||
import astor
|
|
||||||
import httpx
|
import httpx
|
||||||
import typer
|
import typer
|
||||||
from alembic.util.exc import CommandError
|
from alembic.util.exc import CommandError
|
||||||
@ -480,9 +478,8 @@ def validate_app_name(app_name: str | None = None) -> str:
|
|||||||
return app_name
|
return app_name
|
||||||
|
|
||||||
|
|
||||||
def rename_path_up_tree(full_path, old_name, new_name):
|
def rename_path_up_tree(full_path: str | Path, old_name: str, new_name: str) -> Path:
|
||||||
"""
|
"""Rename all instances of `old_name` in the path (file and directories) to `new_name`.
|
||||||
Rename all instances of `old_name` in the path (file and directories) to `new_name`.
|
|
||||||
The renaming stops when we reach the directory containing `rxconfig.py`.
|
The renaming stops when we reach the directory containing `rxconfig.py`.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -491,7 +488,7 @@ def rename_path_up_tree(full_path, old_name, new_name):
|
|||||||
new_name: The replacement name.
|
new_name: The replacement name.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Path: The updated path after renaming.
|
The updated path after renaming.
|
||||||
"""
|
"""
|
||||||
current_path = Path(full_path)
|
current_path = Path(full_path)
|
||||||
new_path = None
|
new_path = None
|
||||||
@ -521,7 +518,9 @@ def rename_path_up_tree(full_path, old_name, new_name):
|
|||||||
def rename_app(app_name: str):
|
def rename_app(app_name: str):
|
||||||
"""Rename the app directory."""
|
"""Rename the app directory."""
|
||||||
if not constants.Config.FILE.exists():
|
if not constants.Config.FILE.exists():
|
||||||
console.error("No rxconfig.py found. Make sure you are in the root directory of your app.")
|
console.error(
|
||||||
|
"No rxconfig.py found. Make sure you are in the root directory of your app."
|
||||||
|
)
|
||||||
raise typer.Exit(1)
|
raise typer.Exit(1)
|
||||||
|
|
||||||
config = get_config()
|
config = get_config()
|
||||||
@ -534,16 +533,13 @@ def rename_app(app_name: str):
|
|||||||
console.error(f"Could not find origin for module {config.module}.")
|
console.error(f"Could not find origin for module {config.module}.")
|
||||||
raise typer.Exit(1)
|
raise typer.Exit(1)
|
||||||
|
|
||||||
process_directory(
|
process_directory(Path.cwd(), config.app_name, app_name, exclude_dirs=[".web"])
|
||||||
Path.cwd(), config.app_name, app_name, exclude_dirs=[".web"]
|
|
||||||
)
|
|
||||||
|
|
||||||
rename_path_up_tree(Path(module_path.origin), config.app_name, app_name)
|
rename_path_up_tree(Path(module_path.origin), config.app_name, app_name)
|
||||||
|
|
||||||
|
|
||||||
def rename_imports_and_app_name(file_path, old_name, new_name):
|
def rename_imports_and_app_name(file_path: str | Path, old_name: str, new_name: str):
|
||||||
"""
|
"""Rename imports and update the app_name in the file using string replacement.
|
||||||
Rename imports and update the app_name in the file using string replacement.
|
|
||||||
Handles both keyword and positional arguments for `rx.Config` and import statements.
|
Handles both keyword and positional arguments for `rx.Config` and import statements.
|
||||||
"""
|
"""
|
||||||
file_path = Path(file_path)
|
file_path = Path(file_path)
|
||||||
@ -551,15 +547,15 @@ def rename_imports_and_app_name(file_path, old_name, new_name):
|
|||||||
|
|
||||||
# Replace `from old_name.` or `from old_name` with `from new_name`
|
# Replace `from old_name.` or `from old_name` with `from new_name`
|
||||||
content = re.sub(
|
content = re.sub(
|
||||||
rf'\bfrom {re.escape(old_name)}(\b|\.|\s)',
|
rf"\bfrom {re.escape(old_name)}(\b|\.|\s)",
|
||||||
lambda match: f'from {new_name}{match.group(1)}',
|
lambda match: f"from {new_name}{match.group(1)}",
|
||||||
content,
|
content,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Replace `import old_name` with `import new_name`
|
# Replace `import old_name` with `import new_name`
|
||||||
content = re.sub(
|
content = re.sub(
|
||||||
rf'\bimport {re.escape(old_name)}\b',
|
rf"\bimport {re.escape(old_name)}\b",
|
||||||
f'import {new_name}',
|
f"import {new_name}",
|
||||||
content,
|
content,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -580,23 +576,29 @@ def rename_imports_and_app_name(file_path, old_name, new_name):
|
|||||||
file_path.write_text(content)
|
file_path.write_text(content)
|
||||||
|
|
||||||
|
|
||||||
|
def process_directory(
|
||||||
def process_directory(directory, old_name, new_name, exclude_dirs=None, extensions=None):
|
directory: str | Path,
|
||||||
"""
|
old_name: str,
|
||||||
Process files with specified extensions in a directory, excluding specified directories.
|
new_name: str,
|
||||||
|
exclude_dirs: list | None = None,
|
||||||
|
extensions: list | None = None,
|
||||||
|
):
|
||||||
|
"""Process files with specified extensions in a directory, excluding specified directories.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
directory (str or Path): The root directory to process.
|
directory: The root directory to process.
|
||||||
old_name (str): The old name to replace.
|
old_name: The old name to replace.
|
||||||
new_name (str): The new name to use.
|
new_name: The new name to use.
|
||||||
exclude_dirs (list, optional): List of directory names to exclude. Defaults to None.
|
exclude_dirs: List of directory names to exclude. Defaults to None.
|
||||||
extensions (list, optional): List of file extensions to process. Defaults to [".py"].
|
extensions: List of file extensions to process. Defaults to [".py"].
|
||||||
"""
|
"""
|
||||||
exclude_dirs = exclude_dirs or []
|
exclude_dirs = exclude_dirs or []
|
||||||
extensions = extensions or [".py", ".md"]
|
extensions = extensions or [".py", ".md"]
|
||||||
extensions_set = {ext.lstrip(".") for ext in extensions}
|
extensions_set = {ext.lstrip(".") for ext in extensions}
|
||||||
directory = Path(directory)
|
directory = Path(directory)
|
||||||
|
|
||||||
|
root_exclude_dirs = {directory / exclude_dir for exclude_dir in exclude_dirs}
|
||||||
|
|
||||||
files = (
|
files = (
|
||||||
p.resolve()
|
p.resolve()
|
||||||
for p in directory.glob("**/*")
|
for p in directory.glob("**/*")
|
||||||
@ -604,11 +606,12 @@ def process_directory(directory, old_name, new_name, exclude_dirs=None, extensio
|
|||||||
)
|
)
|
||||||
|
|
||||||
for file_path in files:
|
for file_path in files:
|
||||||
if not any(part in exclude_dirs for part in file_path.parts):
|
if not any(
|
||||||
|
file_path.is_relative_to(exclude_dir) for exclude_dir in root_exclude_dirs
|
||||||
|
):
|
||||||
rename_imports_and_app_name(file_path, old_name, new_name)
|
rename_imports_and_app_name(file_path, old_name, new_name)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def create_config(app_name: str):
|
def create_config(app_name: str):
|
||||||
"""Create a new rxconfig file.
|
"""Create a new rxconfig file.
|
||||||
|
|
||||||
|
@ -1,20 +1,29 @@
|
|||||||
|
import importlib.machinery
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
|
import shutil
|
||||||
import tempfile
|
import tempfile
|
||||||
|
from pathlib import Path
|
||||||
from unittest.mock import Mock, mock_open
|
from unittest.mock import Mock, mock_open
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from typer.testing import CliRunner
|
||||||
|
|
||||||
from reflex import constants
|
from reflex import constants
|
||||||
from reflex.config import Config
|
from reflex.config import Config
|
||||||
|
from reflex.reflex import cli
|
||||||
|
from reflex.testing import chdir
|
||||||
from reflex.utils.prerequisites import (
|
from reflex.utils.prerequisites import (
|
||||||
CpuInfo,
|
CpuInfo,
|
||||||
_update_next_config,
|
_update_next_config,
|
||||||
cached_procedure,
|
cached_procedure,
|
||||||
get_cpu_info,
|
get_cpu_info,
|
||||||
initialize_requirements_txt,
|
initialize_requirements_txt,
|
||||||
|
rename_imports_and_app_name,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
runner = CliRunner()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"config, export, expected_output",
|
"config, export, expected_output",
|
||||||
@ -224,3 +233,169 @@ def test_get_cpu_info():
|
|||||||
for attr in ("manufacturer_id", "model_name", "address_width"):
|
for attr in ("manufacturer_id", "model_name", "address_width"):
|
||||||
value = getattr(cpu_info, attr)
|
value = getattr(cpu_info, attr)
|
||||||
assert value.strip() if attr != "address_width" else value
|
assert value.strip() if attr != "address_width" else value
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def temp_directory():
|
||||||
|
"""Create a temporary directory for tests."""
|
||||||
|
temp_dir = tempfile.mkdtemp()
|
||||||
|
yield Path(temp_dir)
|
||||||
|
shutil.rmtree(temp_dir)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"config_code,expected",
|
||||||
|
[
|
||||||
|
("rx.Config(app_name='old_name')", 'rx.Config(app_name="new_name")'),
|
||||||
|
('rx.Config(app_name="old_name")', 'rx.Config(app_name="new_name")'),
|
||||||
|
("rx.Config('old_name')", 'rx.Config("new_name")'),
|
||||||
|
('rx.Config("old_name")', 'rx.Config("new_name")'),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_rename_imports_and_app_name(temp_directory, config_code, expected):
|
||||||
|
"""Test renaming imports and app_name in a file."""
|
||||||
|
file_path = temp_directory / "rxconfig.py"
|
||||||
|
content = f"""
|
||||||
|
config = {config_code}
|
||||||
|
"""
|
||||||
|
file_path.write_text(content)
|
||||||
|
|
||||||
|
rename_imports_and_app_name(file_path, "old_name", "new_name")
|
||||||
|
|
||||||
|
updated_content = file_path.read_text()
|
||||||
|
expected_content = f"""
|
||||||
|
config = {expected}
|
||||||
|
"""
|
||||||
|
assert updated_content == expected_content
|
||||||
|
|
||||||
|
|
||||||
|
def test_regex_edge_cases(temp_directory):
|
||||||
|
"""Test regex edge cases in renaming."""
|
||||||
|
file_path = temp_directory / "example.py"
|
||||||
|
content = """
|
||||||
|
from old_name.module import something
|
||||||
|
import old_name
|
||||||
|
from old_name import something_else as alias
|
||||||
|
from old_name
|
||||||
|
"""
|
||||||
|
file_path.write_text(content)
|
||||||
|
|
||||||
|
rename_imports_and_app_name(file_path, "old_name", "new_name")
|
||||||
|
|
||||||
|
updated_content = file_path.read_text()
|
||||||
|
expected_content = """
|
||||||
|
from new_name.module import something
|
||||||
|
import new_name
|
||||||
|
from new_name import something_else as alias
|
||||||
|
from new_name
|
||||||
|
"""
|
||||||
|
assert updated_content == expected_content
|
||||||
|
|
||||||
|
|
||||||
|
def test_cli_rename_command(mocker, temp_directory):
|
||||||
|
"""Test the CLI rename command."""
|
||||||
|
foo_dir = temp_directory / "foo"
|
||||||
|
foo_dir.mkdir()
|
||||||
|
(foo_dir / "__init__").touch()
|
||||||
|
(foo_dir / ".web").mkdir()
|
||||||
|
(foo_dir / "assets").mkdir()
|
||||||
|
(foo_dir / "foo").mkdir()
|
||||||
|
(foo_dir / "foo" / "__init__.py").touch()
|
||||||
|
(foo_dir / "rxconfig.py").touch()
|
||||||
|
(foo_dir / "rxconfig.py").write_text(
|
||||||
|
"""
|
||||||
|
import reflex as rx
|
||||||
|
|
||||||
|
config = rx.Config(
|
||||||
|
app_name="foo",
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
(foo_dir / "foo" / "components").mkdir()
|
||||||
|
(foo_dir / "foo" / "components" / "__init__.py").touch()
|
||||||
|
(foo_dir / "foo" / "components" / "base.py").touch()
|
||||||
|
(foo_dir / "foo" / "components" / "views.py").touch()
|
||||||
|
(foo_dir / "foo" / "components" / "base.py").write_text(
|
||||||
|
"""
|
||||||
|
import reflex as rx
|
||||||
|
from foo.components import views
|
||||||
|
from foo.components.views import *
|
||||||
|
from .base import *
|
||||||
|
|
||||||
|
def random_component():
|
||||||
|
return rx.fragment()
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
(foo_dir / "foo" / "foo.py").touch()
|
||||||
|
(foo_dir / "foo" / "foo.py").write_text(
|
||||||
|
"""
|
||||||
|
import reflex as rx
|
||||||
|
import foo.components.base
|
||||||
|
from foo.components.base import random_component
|
||||||
|
|
||||||
|
class State(rx.State):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def index():
|
||||||
|
return rx.text("Hello, World!")
|
||||||
|
|
||||||
|
app = rx.App()
|
||||||
|
app.add_page(index)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
mocker.patch(
|
||||||
|
"importlib.util.find_spec",
|
||||||
|
mocker.patch(
|
||||||
|
"importlib.util.find_spec",
|
||||||
|
return_value=importlib.machinery.ModuleSpec(
|
||||||
|
name="foo", loader=None, origin=str(Path(foo_dir / "foo" / "foo.py"))
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
with chdir(temp_directory / "foo"):
|
||||||
|
result = runner.invoke(cli, ["rename", "bar"])
|
||||||
|
|
||||||
|
assert result.exit_code == 0
|
||||||
|
assert (foo_dir / "rxconfig.py").read_text() == (
|
||||||
|
"""
|
||||||
|
import reflex as rx
|
||||||
|
|
||||||
|
config = rx.Config(
|
||||||
|
app_name="bar",
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
assert (foo_dir / "bar").exists()
|
||||||
|
assert not (foo_dir / "foo").exists()
|
||||||
|
assert (foo_dir / "bar" / "components" / "base.py").read_text() == (
|
||||||
|
"""
|
||||||
|
import reflex as rx
|
||||||
|
from bar.components import views
|
||||||
|
from bar.components.views import *
|
||||||
|
from .base import *
|
||||||
|
|
||||||
|
def random_component():
|
||||||
|
return rx.fragment()
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
assert (foo_dir / "bar" / "bar.py").exists()
|
||||||
|
assert not (foo_dir / "bar" / "foo.py").exists()
|
||||||
|
assert (foo_dir / "bar" / "bar.py").read_text() == (
|
||||||
|
"""
|
||||||
|
import reflex as rx
|
||||||
|
import bar.components.base
|
||||||
|
from bar.components.base import random_component
|
||||||
|
|
||||||
|
class State(rx.State):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def index():
|
||||||
|
return rx.text("Hello, World!")
|
||||||
|
|
||||||
|
app = rx.App()
|
||||||
|
app.add_page(index)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user