Merge branch 'main' into lendemor/fix_order_of_hooks_and_memoized_evt_trigger
This commit is contained in:
commit
126a97191d
@ -947,12 +947,12 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
is not None
|
is not None
|
||||||
):
|
):
|
||||||
executor = concurrent.futures.ProcessPoolExecutor(
|
executor = concurrent.futures.ProcessPoolExecutor(
|
||||||
max_workers=number_of_processes,
|
max_workers=number_of_processes or None,
|
||||||
mp_context=multiprocessing.get_context("fork"),
|
mp_context=multiprocessing.get_context("fork"),
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
executor = concurrent.futures.ThreadPoolExecutor(
|
executor = concurrent.futures.ThreadPoolExecutor(
|
||||||
max_workers=environment.REFLEX_COMPILE_THREADS.get()
|
max_workers=environment.REFLEX_COMPILE_THREADS.get() or None
|
||||||
)
|
)
|
||||||
|
|
||||||
for route, component in zip(self.pages, page_components):
|
for route, component in zip(self.pages, page_components):
|
||||||
|
@ -5,7 +5,7 @@ from pathlib import Path
|
|||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from reflex import constants
|
from reflex import constants
|
||||||
from reflex.utils.exec import is_backend_only
|
from reflex.config import EnvironmentVariables
|
||||||
|
|
||||||
|
|
||||||
def asset(
|
def asset(
|
||||||
@ -52,7 +52,7 @@ def asset(
|
|||||||
The relative URL to the asset.
|
The relative URL to the asset.
|
||||||
"""
|
"""
|
||||||
assets = constants.Dirs.APP_ASSETS
|
assets = constants.Dirs.APP_ASSETS
|
||||||
backend_only = is_backend_only()
|
backend_only = EnvironmentVariables.REFLEX_BACKEND_ONLY.get()
|
||||||
|
|
||||||
# Local asset handling
|
# Local asset handling
|
||||||
if not shared:
|
if not shared:
|
||||||
|
@ -266,6 +266,7 @@ class Theme(RadixThemesComponent):
|
|||||||
_js_expr="{...theme.styles.global[':root'], ...theme.styles.global.body}"
|
_js_expr="{...theme.styles.global[':root'], ...theme.styles.global.body}"
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
tag.remove_props("appearance")
|
||||||
return tag
|
return tag
|
||||||
|
|
||||||
|
|
||||||
|
@ -97,6 +97,7 @@ from reflex.utils.exceptions import (
|
|||||||
ReflexRuntimeError,
|
ReflexRuntimeError,
|
||||||
SetUndefinedStateVarError,
|
SetUndefinedStateVarError,
|
||||||
StateSchemaMismatchError,
|
StateSchemaMismatchError,
|
||||||
|
StateSerializationError,
|
||||||
StateTooLargeError,
|
StateTooLargeError,
|
||||||
)
|
)
|
||||||
from reflex.utils.exec import is_testing_env
|
from reflex.utils.exec import is_testing_env
|
||||||
@ -2193,8 +2194,12 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
The serialized state.
|
The serialized state.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
StateSerializationError: If the state cannot be serialized.
|
||||||
"""
|
"""
|
||||||
payload = b""
|
payload = b""
|
||||||
|
error = ""
|
||||||
try:
|
try:
|
||||||
payload = pickle.dumps((self._to_schema(), self))
|
payload = pickle.dumps((self._to_schema(), self))
|
||||||
except HANDLED_PICKLE_ERRORS as og_pickle_error:
|
except HANDLED_PICKLE_ERRORS as og_pickle_error:
|
||||||
@ -2214,8 +2219,13 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|||||||
except HANDLED_PICKLE_ERRORS as ex:
|
except HANDLED_PICKLE_ERRORS as ex:
|
||||||
error += f"Dill was also unable to pickle the state: {ex}"
|
error += f"Dill was also unable to pickle the state: {ex}"
|
||||||
console.warn(error)
|
console.warn(error)
|
||||||
|
|
||||||
if environment.REFLEX_PERF_MODE.get() != PerformanceMode.OFF:
|
if environment.REFLEX_PERF_MODE.get() != PerformanceMode.OFF:
|
||||||
self._check_state_size(len(payload))
|
self._check_state_size(len(payload))
|
||||||
|
|
||||||
|
if not payload:
|
||||||
|
raise StateSerializationError(error)
|
||||||
|
|
||||||
return payload
|
return payload
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -155,6 +155,10 @@ class StateTooLargeError(ReflexError):
|
|||||||
"""Raised when the state is too large to be serialized."""
|
"""Raised when the state is too large to be serialized."""
|
||||||
|
|
||||||
|
|
||||||
|
class StateSerializationError(ReflexError):
|
||||||
|
"""Raised when the state cannot be serialized."""
|
||||||
|
|
||||||
|
|
||||||
class SystemPackageMissingError(ReflexError):
|
class SystemPackageMissingError(ReflexError):
|
||||||
"""Raised when a system package is missing."""
|
"""Raised when a system package is missing."""
|
||||||
|
|
||||||
|
218
tests/integration/tests_playwright/test_appearance.py
Normal file
218
tests/integration/tests_playwright/test_appearance.py
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
from typing import Generator
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from playwright.sync_api import Page, expect
|
||||||
|
|
||||||
|
from reflex.testing import AppHarness
|
||||||
|
|
||||||
|
|
||||||
|
def DefaultLightModeApp():
|
||||||
|
import reflex as rx
|
||||||
|
from reflex.style import color_mode
|
||||||
|
|
||||||
|
app = rx.App(theme=rx.theme(appearance="light"))
|
||||||
|
|
||||||
|
@app.add_page
|
||||||
|
def index():
|
||||||
|
return rx.text(color_mode)
|
||||||
|
|
||||||
|
|
||||||
|
def DefaultDarkModeApp():
|
||||||
|
import reflex as rx
|
||||||
|
from reflex.style import color_mode
|
||||||
|
|
||||||
|
app = rx.App(theme=rx.theme(appearance="dark"))
|
||||||
|
|
||||||
|
@app.add_page
|
||||||
|
def index():
|
||||||
|
return rx.text(color_mode)
|
||||||
|
|
||||||
|
|
||||||
|
def DefaultSystemModeApp():
|
||||||
|
import reflex as rx
|
||||||
|
from reflex.style import color_mode
|
||||||
|
|
||||||
|
app = rx.App()
|
||||||
|
|
||||||
|
@app.add_page
|
||||||
|
def index():
|
||||||
|
return rx.text(color_mode)
|
||||||
|
|
||||||
|
|
||||||
|
def ColorToggleApp():
|
||||||
|
import reflex as rx
|
||||||
|
from reflex.style import color_mode, resolved_color_mode, set_color_mode
|
||||||
|
|
||||||
|
app = rx.App(theme=rx.theme(appearance="light"))
|
||||||
|
|
||||||
|
@app.add_page
|
||||||
|
def index():
|
||||||
|
return rx.box(
|
||||||
|
rx.segmented_control.root(
|
||||||
|
rx.segmented_control.item(
|
||||||
|
rx.icon(tag="monitor", size=20),
|
||||||
|
value="system",
|
||||||
|
),
|
||||||
|
rx.segmented_control.item(
|
||||||
|
rx.icon(tag="sun", size=20),
|
||||||
|
value="light",
|
||||||
|
),
|
||||||
|
rx.segmented_control.item(
|
||||||
|
rx.icon(tag="moon", size=20),
|
||||||
|
value="dark",
|
||||||
|
),
|
||||||
|
on_change=set_color_mode,
|
||||||
|
variant="classic",
|
||||||
|
radius="large",
|
||||||
|
value=color_mode,
|
||||||
|
),
|
||||||
|
rx.text(color_mode, id="current_color_mode"),
|
||||||
|
rx.text(resolved_color_mode, id="resolved_color_mode"),
|
||||||
|
rx.text(rx.color_mode_cond("LightMode", "DarkMode"), id="color_mode_cond"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def light_mode_app(tmp_path_factory) -> Generator[AppHarness, None, None]:
|
||||||
|
"""Start DefaultLightMode app at tmp_path via AppHarness.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tmp_path_factory: pytest tmp_path_factory fixture
|
||||||
|
|
||||||
|
Yields:
|
||||||
|
running AppHarness instance
|
||||||
|
|
||||||
|
"""
|
||||||
|
with AppHarness.create(
|
||||||
|
root=tmp_path_factory.mktemp("appearance_app"),
|
||||||
|
app_source=DefaultLightModeApp, # type: ignore
|
||||||
|
) as harness:
|
||||||
|
assert harness.app_instance is not None, "app is not running"
|
||||||
|
yield harness
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def dark_mode_app(tmp_path_factory) -> Generator[AppHarness, None, None]:
|
||||||
|
"""Start DefaultDarkMode app at tmp_path via AppHarness.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tmp_path_factory: pytest tmp_path_factory fixture
|
||||||
|
|
||||||
|
Yields:
|
||||||
|
running AppHarness instance
|
||||||
|
|
||||||
|
"""
|
||||||
|
with AppHarness.create(
|
||||||
|
root=tmp_path_factory.mktemp("appearance_app"),
|
||||||
|
app_source=DefaultDarkModeApp, # type: ignore
|
||||||
|
) as harness:
|
||||||
|
assert harness.app_instance is not None, "app is not running"
|
||||||
|
yield harness
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def system_mode_app(tmp_path_factory) -> Generator[AppHarness, None, None]:
|
||||||
|
"""Start DefaultSystemMode app at tmp_path via AppHarness.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tmp_path_factory: pytest tmp_path_factory fixture
|
||||||
|
|
||||||
|
Yields:
|
||||||
|
running AppHarness instance
|
||||||
|
|
||||||
|
"""
|
||||||
|
with AppHarness.create(
|
||||||
|
root=tmp_path_factory.mktemp("appearance_app"),
|
||||||
|
app_source=DefaultSystemModeApp, # type: ignore
|
||||||
|
) as harness:
|
||||||
|
assert harness.app_instance is not None, "app is not running"
|
||||||
|
yield harness
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def color_toggle_app(tmp_path_factory) -> Generator[AppHarness, None, None]:
|
||||||
|
"""Start ColorToggle app at tmp_path via AppHarness.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tmp_path_factory: pytest tmp_path_factory fixture
|
||||||
|
|
||||||
|
Yields:
|
||||||
|
running AppHarness instance
|
||||||
|
|
||||||
|
"""
|
||||||
|
with AppHarness.create(
|
||||||
|
root=tmp_path_factory.mktemp("appearance_app"),
|
||||||
|
app_source=ColorToggleApp, # type: ignore
|
||||||
|
) as harness:
|
||||||
|
assert harness.app_instance is not None, "app is not running"
|
||||||
|
yield harness
|
||||||
|
|
||||||
|
|
||||||
|
def test_appearance_light_mode(light_mode_app: AppHarness, page: Page):
|
||||||
|
assert light_mode_app.frontend_url is not None
|
||||||
|
page.goto(light_mode_app.frontend_url)
|
||||||
|
|
||||||
|
expect(page.get_by_text("light")).to_be_visible()
|
||||||
|
|
||||||
|
|
||||||
|
def test_appearance_dark_mode(dark_mode_app: AppHarness, page: Page):
|
||||||
|
assert dark_mode_app.frontend_url is not None
|
||||||
|
page.goto(dark_mode_app.frontend_url)
|
||||||
|
|
||||||
|
expect(page.get_by_text("dark")).to_be_visible()
|
||||||
|
|
||||||
|
|
||||||
|
def test_appearance_system_mode(system_mode_app: AppHarness, page: Page):
|
||||||
|
assert system_mode_app.frontend_url is not None
|
||||||
|
page.goto(system_mode_app.frontend_url)
|
||||||
|
|
||||||
|
expect(page.get_by_text("system")).to_be_visible()
|
||||||
|
|
||||||
|
|
||||||
|
def test_appearance_color_toggle(color_toggle_app: AppHarness, page: Page):
|
||||||
|
assert color_toggle_app.frontend_url is not None
|
||||||
|
page.goto(color_toggle_app.frontend_url)
|
||||||
|
|
||||||
|
# Radio buttons locators.
|
||||||
|
radio_system = page.get_by_role("radio").nth(0)
|
||||||
|
radio_light = page.get_by_role("radio").nth(1)
|
||||||
|
radio_dark = page.get_by_role("radio").nth(2)
|
||||||
|
|
||||||
|
# Text locators to check.
|
||||||
|
current_color_mode = page.locator("id=current_color_mode")
|
||||||
|
resolved_color_mode = page.locator("id=resolved_color_mode")
|
||||||
|
color_mode_cond = page.locator("id=color_mode_cond")
|
||||||
|
root_body = page.locator('div[data-is-root-theme="true"]')
|
||||||
|
|
||||||
|
# Background colors.
|
||||||
|
dark_background = "rgb(17, 17, 19)" # value based on dark native appearance, can change depending on the browser
|
||||||
|
light_background = "rgb(255, 255, 255)"
|
||||||
|
|
||||||
|
# check initial state
|
||||||
|
expect(current_color_mode).to_have_text("light")
|
||||||
|
expect(resolved_color_mode).to_have_text("light")
|
||||||
|
expect(color_mode_cond).to_have_text("LightMode")
|
||||||
|
expect(root_body).to_have_css("background-color", light_background)
|
||||||
|
|
||||||
|
# click dark mode
|
||||||
|
radio_dark.click()
|
||||||
|
expect(current_color_mode).to_have_text("dark")
|
||||||
|
expect(resolved_color_mode).to_have_text("dark")
|
||||||
|
expect(color_mode_cond).to_have_text("DarkMode")
|
||||||
|
expect(root_body).to_have_css("background-color", dark_background)
|
||||||
|
|
||||||
|
# click light mode
|
||||||
|
radio_light.click()
|
||||||
|
expect(current_color_mode).to_have_text("light")
|
||||||
|
expect(resolved_color_mode).to_have_text("light")
|
||||||
|
expect(color_mode_cond).to_have_text("LightMode")
|
||||||
|
expect(root_body).to_have_css("background-color", light_background)
|
||||||
|
page.reload()
|
||||||
|
expect(root_body).to_have_css("background-color", light_background)
|
||||||
|
|
||||||
|
# click system mode
|
||||||
|
radio_system.click()
|
||||||
|
expect(current_color_mode).to_have_text("system")
|
||||||
|
expect(resolved_color_mode).to_have_text("light")
|
||||||
|
expect(color_mode_cond).to_have_text("LightMode")
|
||||||
|
expect(root_body).to_have_css("background-color", light_background)
|
@ -55,7 +55,11 @@ from reflex.state import (
|
|||||||
)
|
)
|
||||||
from reflex.testing import chdir
|
from reflex.testing import chdir
|
||||||
from reflex.utils import format, prerequisites, types
|
from reflex.utils import format, prerequisites, types
|
||||||
from reflex.utils.exceptions import ReflexRuntimeError, SetUndefinedStateVarError
|
from reflex.utils.exceptions import (
|
||||||
|
ReflexRuntimeError,
|
||||||
|
SetUndefinedStateVarError,
|
||||||
|
StateSerializationError,
|
||||||
|
)
|
||||||
from reflex.utils.format import json_dumps
|
from reflex.utils.format import json_dumps
|
||||||
from reflex.vars.base import Var, computed_var
|
from reflex.vars.base import Var, computed_var
|
||||||
from tests.units.states.mutation import MutableSQLAModel, MutableTestState
|
from tests.units.states.mutation import MutableSQLAModel, MutableTestState
|
||||||
@ -3433,8 +3437,9 @@ def test_fallback_pickle():
|
|||||||
# Some object, like generator, are still unpicklable with dill.
|
# Some object, like generator, are still unpicklable with dill.
|
||||||
state3 = DillState(_reflex_internal_init=True) # type: ignore
|
state3 = DillState(_reflex_internal_init=True) # type: ignore
|
||||||
state3._g = (i for i in range(10))
|
state3._g = (i for i in range(10))
|
||||||
pk3 = state3._serialize()
|
|
||||||
assert len(pk3) == 0
|
with pytest.raises(StateSerializationError):
|
||||||
|
_ = state3._serialize()
|
||||||
|
|
||||||
|
|
||||||
def test_typed_state() -> None:
|
def test_typed_state() -> None:
|
||||||
|
Loading…
Reference in New Issue
Block a user