
* add support for lifespan tasks * allow passing args to lifespan task * add message to the cancel call * allow asynccontextmanager as lifespan tasks * Fix integration.utils.SessionStorage Previously the SessionStorage util was just looking in localStorage, but the tests didn't catch it because they were asserting the token was not None, rather than asserting it was truthy. Fixed here, because I'm using this structure in the new lifespan test. * If the lifespan task or context takes "app" parameter, pass the FastAPI instance. * test_lifespan: end to end test for register_lifespan_task * In py3.8, Task.cancel takes no args * test_lifespan: use polling to make the test more robust Fix CI failure * Do not allow task_args for better composability --------- Co-authored-by: Masen Furer <m_github@0x26.net>
108 lines
3.0 KiB
Python
108 lines
3.0 KiB
Python
"""Test that per-component state scaffold works and operates independently."""
|
|
|
|
from typing import Generator
|
|
|
|
import pytest
|
|
from selenium.webdriver.common.by import By
|
|
|
|
from reflex.testing import AppHarness
|
|
|
|
from . import utils
|
|
|
|
|
|
def ComponentStateApp():
|
|
"""App using per component state."""
|
|
import reflex as rx
|
|
|
|
class MultiCounter(rx.ComponentState):
|
|
count: int = 0
|
|
|
|
def increment(self):
|
|
self.count += 1
|
|
|
|
@classmethod
|
|
def get_component(cls, *children, **props):
|
|
return rx.vstack(
|
|
*children,
|
|
rx.heading(cls.count, id=f"count-{props.get('id', 'default')}"),
|
|
rx.button(
|
|
"Increment",
|
|
on_click=cls.increment,
|
|
id=f"button-{props.get('id', 'default')}",
|
|
),
|
|
**props,
|
|
)
|
|
|
|
app = rx.App(state=rx.State) # noqa
|
|
|
|
@rx.page()
|
|
def index():
|
|
mc_a = MultiCounter.create(id="a")
|
|
mc_b = MultiCounter.create(id="b")
|
|
assert mc_a.State != mc_b.State
|
|
return rx.vstack(
|
|
mc_a,
|
|
mc_b,
|
|
rx.button(
|
|
"Inc A",
|
|
on_click=mc_a.State.increment, # type: ignore
|
|
id="inc-a",
|
|
),
|
|
)
|
|
|
|
|
|
@pytest.fixture()
|
|
def component_state_app(tmp_path) -> Generator[AppHarness, None, None]:
|
|
"""Start ComponentStateApp app at tmp_path via AppHarness.
|
|
|
|
Args:
|
|
tmp_path: pytest tmp_path fixture
|
|
|
|
Yields:
|
|
running AppHarness instance
|
|
"""
|
|
with AppHarness.create(
|
|
root=tmp_path,
|
|
app_source=ComponentStateApp, # type: ignore
|
|
) as harness:
|
|
yield harness
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_component_state_app(component_state_app: AppHarness):
|
|
"""Increment counters independently.
|
|
|
|
Args:
|
|
component_state_app: harness for ComponentStateApp app
|
|
"""
|
|
assert component_state_app.app_instance is not None, "app is not running"
|
|
driver = component_state_app.frontend()
|
|
|
|
ss = utils.SessionStorage(driver)
|
|
assert AppHarness._poll_for(lambda: ss.get("token") is not None), "token not found"
|
|
|
|
count_a = driver.find_element(By.ID, "count-a")
|
|
count_b = driver.find_element(By.ID, "count-b")
|
|
button_a = driver.find_element(By.ID, "button-a")
|
|
button_b = driver.find_element(By.ID, "button-b")
|
|
button_inc_a = driver.find_element(By.ID, "inc-a")
|
|
|
|
assert count_a.text == "0"
|
|
|
|
button_a.click()
|
|
assert component_state_app.poll_for_content(count_a, exp_not_equal="0") == "1"
|
|
|
|
button_a.click()
|
|
assert component_state_app.poll_for_content(count_a, exp_not_equal="1") == "2"
|
|
|
|
button_inc_a.click()
|
|
assert component_state_app.poll_for_content(count_a, exp_not_equal="2") == "3"
|
|
|
|
assert count_b.text == "0"
|
|
|
|
button_b.click()
|
|
assert component_state_app.poll_for_content(count_b, exp_not_equal="0") == "1"
|
|
|
|
button_b.click()
|
|
assert component_state_app.poll_for_content(count_b, exp_not_equal="1") == "2"
|