Skip to content

Commit

Permalink
wip: more dynamic jinja contexts, tests for minification
Browse files Browse the repository at this point in the history
  • Loading branch information
benedikt-bartscher committed Aug 4, 2024
1 parent 3c1946d commit bf6ec96
Show file tree
Hide file tree
Showing 18 changed files with 374 additions and 180 deletions.
27 changes: 23 additions & 4 deletions integration/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
import os
import re
from pathlib import Path
from typing import Generator, Type

import pytest

import reflex.constants
from reflex.testing import AppHarness, AppHarnessProd

DISPLAY = None
Expand Down Expand Up @@ -63,15 +65,32 @@ def pytest_exception_interact(node, call, report):


@pytest.fixture(
scope="session", params=[AppHarness, AppHarnessProd], ids=["dev", "prod"]
scope="session",
params=[
AppHarness,
AppHarnessProd,
],
ids=[
reflex.constants.Env.DEV.value,
reflex.constants.Env.PROD.value,
],
)
def app_harness_env(request):
def app_harness_env(
request: pytest.FixtureRequest,
) -> Generator[Type[AppHarness], None, None]:
"""Parametrize the AppHarness class to use for the test, either dev or prod.
Args:
request: The pytest fixture request object.
Returns:
Yields:
The AppHarness class to use for the test.
"""
return request.param
harness: Type[AppHarness] = request.param
if issubclass(harness, AppHarnessProd):
os.environ[reflex.constants.base.ENV_MODE_ENV_VAR] = (
reflex.constants.base.Env.PROD.value
)
yield harness
if issubclass(harness, AppHarnessProd):
_ = os.environ.pop(reflex.constants.base.ENV_MODE_ENV_VAR, None)
1 change: 0 additions & 1 deletion integration/test_computed_vars.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,6 @@ def index() -> rx.Component:
),
)

# raise Exception(State.count3._deps(objclass=State))
app = rx.App()
app.add_page(index)

Expand Down
100 changes: 87 additions & 13 deletions integration/test_minified_states.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,24 @@

from __future__ import annotations

import time
from typing import Generator, Type
import os
from functools import partial
from typing import Generator, Optional, Type

import pytest
from selenium.webdriver.common.by import By
from selenium.webdriver.remote.webdriver import WebDriver
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait

from reflex.testing import AppHarness
from reflex.constants.compiler import ENV_MINIFY_STATES
from reflex.testing import AppHarness, AppHarnessProd


def TestApp():
"""A test app for minified state names."""
def TestApp(minify: bool | None) -> None:
"""A test app for minified state names.
Args:
minify: whether to minify state names
"""
import reflex as rx

class TestAppState(rx.State):
Expand All @@ -25,35 +29,71 @@ class TestAppState(rx.State):

app = rx.App()

@app.add_page
def index():
return rx.vstack(
rx.input(
value=TestAppState.router.session.client_token,
is_read_only=True,
id="token",
),
rx.text(f"minify: {minify}", id="minify"),
rx.text(TestAppState.get_name(), id="state_name"),
rx.text(TestAppState.get_full_name(), id="state_full_name"),
)

app.add_page(index)


@pytest.fixture(
params=[
pytest.param(False),
pytest.param(True),
pytest.param(None),
],
)
def minify_state_env(
request: pytest.FixtureRequest,
) -> Generator[Optional[bool], None, None]:
"""Set the environment variable to minify state names.
Args:
request: pytest fixture request
Yields:
minify_states: whether to minify state names
"""
minify_states: Optional[bool] = request.param
if minify_states is None:
_ = os.environ.pop(ENV_MINIFY_STATES, None)
else:
os.environ[ENV_MINIFY_STATES] = str(minify_states).lower()
yield minify_states
if minify_states is not None:
os.environ.pop(ENV_MINIFY_STATES, None)


@pytest.fixture(scope="module")
@pytest.fixture
def test_app(
app_harness_env: Type[AppHarness], tmp_path_factory: pytest.TempPathFactory
app_harness_env: Type[AppHarness],
tmp_path_factory: pytest.TempPathFactory,
minify_state_env: Optional[bool],
) -> Generator[AppHarness, None, None]:
"""Start TestApp app at tmp_path via AppHarness.
Args:
app_harness_env: either AppHarness (dev) or AppHarnessProd (prod)
tmp_path_factory: pytest tmp_path_factory fixture
minify_state_env: need to request this fixture to set env before the app starts
Yields:
running AppHarness instance
"""
name = f"testapp_{app_harness_env.__name__.lower()}"
with app_harness_env.create(
root=tmp_path_factory.mktemp("test_app"),
app_name=f"testapp_{app_harness_env.__name__.lower()}",
app_source=TestApp, # type: ignore
root=tmp_path_factory.mktemp(name),
app_name=name,
app_source=partial(TestApp, minify=minify_state_env), # pyright: ignore[reportArgumentType]
) as harness:
yield harness

Expand All @@ -80,20 +120,54 @@ def driver(test_app: AppHarness) -> Generator[WebDriver, None, None]:
def test_minified_states(
test_app: AppHarness,
driver: WebDriver,
minify_state_env: Optional[bool],
) -> None:
"""Test minified state names.
Args:
test_app: harness for TestApp
driver: WebDriver instance.
minify_state_env: whether state minification is enabled by env var.
"""
assert test_app.app_instance is not None, "app is not running"

is_prod = isinstance(test_app, AppHarnessProd)

# default to minifying in production
should_minify: bool = is_prod

# env overrides default
if minify_state_env is not None:
should_minify = minify_state_env

# TODO: reload internal states, or refactor VarData to reference state object instead of name
if should_minify:
pytest.skip(
"minify tests are currently not working, because _var_set_states writes the state names during import time"
)

# get a reference to the connected client
token_input = driver.find_element(By.ID, "token")
assert token_input

# wait for the backend connection to send the token
token = test_app.poll_for_value(token_input)
assert token

state_name_text = driver.find_element(By.ID, "state_name")
assert state_name_text
state_name = state_name_text.text

state_full_name_text = driver.find_element(By.ID, "state_full_name")
assert state_full_name_text
_ = state_full_name_text.text

assert test_app.app_module
module_state_prefix = test_app.app_module.__name__.replace(".", "___")
# prod_module_suffix = "prod" if is_prod else ""

if should_minify:
assert len(state_name) == 1
else:
assert state_name == f"{module_state_prefix}____test_app_state"
4 changes: 2 additions & 2 deletions reflex/.templates/web/utils/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ export const isStateful = () => {
if (event_queue.length === 0) {
return false;
}
return event_queue.some(event => event.name.startsWith("reflex___state"));
return event_queue.some(event => event.name.includes("___"));
}

/**
Expand Down Expand Up @@ -768,7 +768,7 @@ export const useEventLoop = (
const vars = {};
vars[storage_to_state_map[e.key]] = e.newValue;
const event = Event(
`${state_name}.reflex___state____update_vars_internal_state.update_vars_internal`,
`${state_name}.{{ update_vars_internal }}`,
{ vars: vars }
);
addEvents([event], e);
Expand Down
22 changes: 11 additions & 11 deletions reflex/compiler/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def _compile_document_root(root: Component) -> str:
Returns:
The compiled document root.
"""
return templates.DOCUMENT_ROOT.render(
return templates.document_root().render(
imports=utils.compile_imports(root._get_all_imports()),
document=root.render(),
)
Expand All @@ -49,7 +49,7 @@ def _compile_app(app_root: Component) -> str:
Returns:
The compiled app.
"""
return templates.APP_ROOT.render(
return templates.app_root().render(
imports=utils.compile_imports(app_root._get_all_imports()),
custom_codes=app_root._get_all_custom_code(),
hooks={**app_root._get_all_hooks_internal(), **app_root._get_all_hooks()},
Expand All @@ -66,7 +66,7 @@ def _compile_theme(theme: dict) -> str:
Returns:
The compiled theme.
"""
return templates.THEME.render(theme=theme)
return templates.theme().render(theme=theme)


def _compile_contexts(state: Optional[Type[BaseState]], theme: Component | None) -> str:
Expand All @@ -85,7 +85,7 @@ def _compile_contexts(state: Optional[Type[BaseState]], theme: Component | None)

last_compiled_time = str(datetime.now())
return (
templates.CONTEXT.render(
templates.context().render(
initial_state=utils.compile_state(state),
state_name=state.get_name(),
client_storage=utils.compile_client_storage(state),
Expand All @@ -94,7 +94,7 @@ def _compile_contexts(state: Optional[Type[BaseState]], theme: Component | None)
default_color_mode=appearance,
)
if state
else templates.CONTEXT.render(
else templates.context().render(
is_dev_mode=not is_prod_mode(),
default_color_mode=appearance,
last_compiled_time=last_compiled_time,
Expand All @@ -121,7 +121,7 @@ def _compile_page(
# Compile the code to render the component.
kwargs = {"state_name": state.get_name()} if state else {}

return templates.PAGE.render(
return templates.page().render(
imports=imports,
dynamic_imports=component._get_all_dynamic_imports(),
custom_codes=component._get_all_custom_code(),
Expand Down Expand Up @@ -177,7 +177,7 @@ def _compile_root_stylesheet(stylesheets: list[str]) -> str:
)
stylesheet = f"../{constants.Dirs.PUBLIC}/{stylesheet.strip('/')}"
sheets.append(stylesheet) if stylesheet not in sheets else None
return templates.STYLE.render(stylesheets=sheets)
return templates.style().render(stylesheets=sheets)


def _compile_component(component: Component | StatefulComponent) -> str:
Expand All @@ -189,7 +189,7 @@ def _compile_component(component: Component | StatefulComponent) -> str:
Returns:
The compiled component.
"""
return templates.COMPONENT.render(component=component)
return templates.component().render(component=component)


def _compile_components(
Expand Down Expand Up @@ -217,7 +217,7 @@ def _compile_components(

# Compile the components page.
return (
templates.COMPONENTS.render(
templates.components().render(
imports=utils.compile_imports(imports),
components=component_renders,
),
Expand Down Expand Up @@ -295,7 +295,7 @@ def get_shared_components_recursive(component: BaseComponent):
f"/{constants.Dirs.UTILS}/{constants.PageNames.STATEFUL_COMPONENTS}", None
)

return templates.STATEFUL_COMPONENTS.render(
return templates.stateful_components().render(
imports=utils.compile_imports(all_imports),
memoized_code="\n".join(rendered_components),
)
Expand All @@ -312,7 +312,7 @@ def _compile_tailwind(
Returns:
The compiled Tailwind config.
"""
return templates.TAILWIND_CONFIG.render(
return templates.tailwind_config().render(
**config,
)

Expand Down
Loading

0 comments on commit bf6ec96

Please sign in to comment.