convert initialEvents to a function (#1982)
This commit is contained in:
parent
b1bab1206d
commit
c3f5f345bb
144
integration/test_login_flow.py
Normal file
144
integration/test_login_flow.py
Normal file
@ -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")
|
@ -25,7 +25,7 @@ export default function Component() {
|
|||||||
|
|
||||||
// Route after the initial page hydration.
|
// Route after the initial page hydration.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const change_complete = () => addEvents(initialEvents.map((e) => ({...e})))
|
const change_complete = () => addEvents(initialEvents())
|
||||||
{{const.router}}.events.on('routeChangeComplete', change_complete)
|
{{const.router}}.events.on('routeChangeComplete', change_complete)
|
||||||
return () => {
|
return () => {
|
||||||
{{const.router}}.events.off('routeChangeComplete', change_complete)
|
{{const.router}}.events.off('routeChangeComplete', change_complete)
|
||||||
|
@ -6,7 +6,7 @@ export const ColorModeContext = createContext(null);
|
|||||||
export const StateContext = createContext(null);
|
export const StateContext = createContext(null);
|
||||||
export const EventLoopContext = createContext(null);
|
export const EventLoopContext = createContext(null);
|
||||||
export const clientStorage = {{ client_storage|json_dumps }}
|
export const clientStorage = {{ client_storage|json_dumps }}
|
||||||
export const initialEvents = [
|
export const initialEvents = () => [
|
||||||
Event('{{state_name}}.{{const.hydrate}}', hydrateClientStorage(clientStorage)),
|
Event('{{state_name}}.{{const.hydrate}}', hydrateClientStorage(clientStorage)),
|
||||||
]
|
]
|
||||||
export const isDevMode = {{ is_dev_mode|json_dumps }}
|
export const isDevMode = {{ is_dev_mode|json_dumps }}
|
||||||
|
@ -468,7 +468,7 @@ const applyClientStorageDelta = (client_storage, delta) => {
|
|||||||
/**
|
/**
|
||||||
* Establish websocket event loop for a NextJS page.
|
* Establish websocket event loop for a NextJS page.
|
||||||
* @param initial_state The initial app state.
|
* @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
|
* @param client_storage The client storage object from context.js
|
||||||
*
|
*
|
||||||
* @returns [state, addEvents, connectError] -
|
* @returns [state, addEvents, connectError] -
|
||||||
@ -478,7 +478,7 @@ const applyClientStorageDelta = (client_storage, delta) => {
|
|||||||
*/
|
*/
|
||||||
export const useEventLoop = (
|
export const useEventLoop = (
|
||||||
initial_state = {},
|
initial_state = {},
|
||||||
initial_events = [],
|
initial_events = () => [],
|
||||||
client_storage = {},
|
client_storage = {},
|
||||||
) => {
|
) => {
|
||||||
const socket = useRef(null)
|
const socket = useRef(null)
|
||||||
@ -496,7 +496,7 @@ export const useEventLoop = (
|
|||||||
// initial state hydrate
|
// initial state hydrate
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (router.isReady && !sentHydrate.current) {
|
if (router.isReady && !sentHydrate.current) {
|
||||||
addEvents(initial_events.map((e) => ({ ...e })))
|
addEvents(initial_events())
|
||||||
sentHydrate.current = true
|
sentHydrate.current = true
|
||||||
}
|
}
|
||||||
}, [router.isReady])
|
}, [router.isReady])
|
||||||
|
Loading…
Reference in New Issue
Block a user