Selaa lähdekoodia

convert initialEvents to a function (#1982)

Masen Furer 1 vuosi sitten
vanhempi
säilyke
c3f5f345bb

+ 144 - 0
integration/test_login_flow.py

@@ -0,0 +1,144 @@
+"""Integration tests for client side storage."""
+from __future__ import annotations
+
+from typing import Generator
+
+import pytest
+from selenium.common.exceptions import NoSuchElementException
+from selenium.webdriver.common.by import By
+from selenium.webdriver.remote.webdriver import WebDriver
+
+from reflex.testing import AppHarness
+
+from . import utils
+
+
+def LoginSample():
+    """Sample app for testing login/logout with LocalStorage var."""
+    import reflex as rx
+
+    class State(rx.State):
+        auth_token: str = rx.LocalStorage("")
+
+        def logout(self):
+            self.set_auth_token("")
+
+        def login(self):
+            self.set_auth_token("12345")
+            yield rx.redirect("/")
+
+    def index():
+        return rx.Cond.create(
+            State.is_hydrated & State.auth_token,  # type: ignore
+            rx.vstack(
+                rx.heading(State.auth_token),
+                rx.button("Logout", on_click=State.logout),
+            ),
+            rx.button("Login", on_click=rx.redirect("/login")),
+        )
+
+    def login():
+        return rx.vstack(
+            rx.button("Do it", on_click=State.login),
+        )
+
+    app = rx.App(state=State)
+    app.add_page(index)
+    app.add_page(login)
+    app.compile()
+
+
+@pytest.fixture(scope="session")
+def login_sample(tmp_path_factory) -> Generator[AppHarness, None, None]:
+    """Start LoginSample 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("login_sample"),
+        app_source=LoginSample,  # type: ignore
+    ) as harness:
+        yield harness
+
+
+@pytest.fixture()
+def driver(login_sample: AppHarness) -> Generator[WebDriver, None, None]:
+    """Get an instance of the browser open to the login_sample app.
+
+    Args:
+        login_sample: harness for LoginSample app
+
+    Yields:
+        WebDriver instance.
+    """
+    assert login_sample.app_instance is not None, "app is not running"
+    driver = login_sample.frontend()
+    try:
+        yield driver
+    finally:
+        driver.quit()
+
+
+@pytest.fixture()
+def local_storage(driver: WebDriver) -> Generator[utils.LocalStorage, None, None]:
+    """Get an instance of the local storage helper.
+
+    Args:
+        driver: WebDriver instance.
+
+    Yields:
+        Local storage helper.
+    """
+    ls = utils.LocalStorage(driver)
+    yield ls
+    ls.clear()
+
+
+def test_login_flow(
+    login_sample: AppHarness, driver: WebDriver, local_storage: utils.LocalStorage
+):
+    """Test login flow.
+
+    Args:
+        login_sample: harness for LoginSample app.
+        driver: WebDriver instance.
+        local_storage: Local storage helper.
+    """
+    assert login_sample.frontend_url is not None
+    local_storage.clear()
+
+    with pytest.raises(NoSuchElementException):
+        driver.find_element(By.TAG_NAME, "h2")
+
+    login_button = driver.find_element(By.TAG_NAME, "button")
+    assert login_button.text == "Login"
+    with utils.poll_for_navigation(driver):
+        login_button.click()
+    assert driver.current_url.endswith("/login/")
+
+    do_it_button = driver.find_element(By.TAG_NAME, "button")
+    assert do_it_button.text == "Do it"
+    with utils.poll_for_navigation(driver):
+        do_it_button.click()
+    assert driver.current_url == login_sample.frontend_url + "/"
+
+    def check_auth_token_header():
+        try:
+            auth_token_header = driver.find_element(By.TAG_NAME, "h2")
+        except NoSuchElementException:
+            return False
+        return auth_token_header.text
+
+    assert login_sample._poll_for(check_auth_token_header) == "12345"
+
+    logout_button = driver.find_element(By.TAG_NAME, "button")
+    assert logout_button.text == "Logout"
+    logout_button.click()
+
+    assert login_sample._poll_for(lambda: local_storage["state.auth_token"] == "")
+    with pytest.raises(NoSuchElementException):
+        driver.find_element(By.TAG_NAME, "h2")

+ 1 - 1
reflex/.templates/jinja/web/pages/index.js.jinja2

@@ -25,7 +25,7 @@ export default function Component() {
 
   // Route after the initial page hydration.
   useEffect(() => {
-    const change_complete = () => addEvents(initialEvents.map((e) => ({...e})))
+    const change_complete = () => addEvents(initialEvents())
     {{const.router}}.events.on('routeChangeComplete', change_complete)
     return () => {
       {{const.router}}.events.off('routeChangeComplete', change_complete)

+ 1 - 1
reflex/.templates/jinja/web/utils/context.js.jinja2

@@ -6,7 +6,7 @@ export const ColorModeContext = createContext(null);
 export const StateContext = createContext(null);
 export const EventLoopContext = createContext(null);
 export const clientStorage = {{ client_storage|json_dumps }}
-export const initialEvents = [
+export const initialEvents = () => [
     Event('{{state_name}}.{{const.hydrate}}', hydrateClientStorage(clientStorage)),
 ]
 export const isDevMode = {{ is_dev_mode|json_dumps }}

+ 3 - 3
reflex/.templates/web/utils/state.js

@@ -468,7 +468,7 @@ const applyClientStorageDelta = (client_storage, delta) => {
 /**
  * Establish websocket event loop for a NextJS page.
  * @param initial_state The initial app state.
- * @param initial_events The initial app events.
+ * @param initial_events Function that returns the initial app events.
  * @param client_storage The client storage object from context.js
  *
  * @returns [state, addEvents, connectError] -
@@ -478,7 +478,7 @@ const applyClientStorageDelta = (client_storage, delta) => {
  */
 export const useEventLoop = (
   initial_state = {},
-  initial_events = [],
+  initial_events = () => [],
   client_storage = {},
 ) => {
   const socket = useRef(null)
@@ -496,7 +496,7 @@ export const useEventLoop = (
   // initial state hydrate
   useEffect(() => {
     if (router.isReady && !sentHydrate.current) {
-      addEvents(initial_events.map((e) => ({ ...e })))
+      addEvents(initial_events())
       sentHydrate.current = true
     }
   }, [router.isReady])