diff --git a/reflex/istate/data.py b/reflex/istate/data.py index 71d93576636..ee99eab4952 100644 --- a/reflex/istate/data.py +++ b/reflex/istate/data.py @@ -7,6 +7,7 @@ from urllib.parse import _NetlocResultMixinStr, parse_qsl, urlsplit from reflex_base import constants +from reflex_base.config import get_config from reflex_base.utils import console, format from reflex_base.utils.serializers import serializer from reflex_base.vars.base import ( @@ -360,7 +361,12 @@ def from_router_data(cls, router_data: dict) -> "PageData": """ host = router_data.get(constants.RouteVar.HEADERS, {}).get("origin", "") path = router_data.get(constants.RouteVar.PATH, "") - raw_path = router_data.get(constants.RouteVar.ORIGIN, "") + # raw_path is the browser-visible URL path, so it includes the configured + # frontend_path prefix. path remains the matched route pattern, which is + # defined without the prefix. + raw_path = get_config().prepend_frontend_path( + router_data.get(constants.RouteVar.ORIGIN, "") + ) return cls( host=host, path=path, @@ -447,7 +453,9 @@ def from_router_data(cls, router_data: dict) -> "RouterData": _page=PageData.from_router_data(router_data), url=ReflexURL( router_data.get(constants.RouteVar.HEADERS, {}).get("origin", "") - + router_data.get(constants.RouteVar.ORIGIN, "") + + get_config().prepend_frontend_path( + router_data.get(constants.RouteVar.ORIGIN, "") + ) ), route_id=router_data.get(constants.RouteVar.PATH, ""), ) diff --git a/tests/integration/tests_playwright/test_frontend_path.py b/tests/integration/tests_playwright/test_frontend_path.py index e4e52aba22b..d9709d84653 100644 --- a/tests/integration/tests_playwright/test_frontend_path.py +++ b/tests/integration/tests_playwright/test_frontend_path.py @@ -9,6 +9,7 @@ from __future__ import annotations +import re from collections.abc import Generator import httpx @@ -107,6 +108,25 @@ def _chunk(chunk_type: bytes, data: bytes) -> bytes: # ---- Pages ---- + def _router_info(): + return rx.fragment( + rx.input( + value=FPState.router.url, + read_only=True, + id="router-url", + ), + rx.input( + value=FPState.router.url.path, + read_only=True, + id="router-url-path", + ), + rx.input( + value=FPState.router.page.raw_path, # pyright: ignore[reportDeprecated] + read_only=True, + id="router-page-raw-path", + ), + ) + @rx.page("/", on_load=FPState.on_load_index) def index(): return rx.box( @@ -117,6 +137,7 @@ def index(): read_only=True, id="token", ), + _router_info(), # Links to app-relative paths. rx.link("go to static", href="/static-page", id="link-static"), rx.link("go to dynamic 7", href="/dynamic/7", id="link-dynamic"), @@ -169,6 +190,7 @@ def static_page(): read_only=True, id="token", ), + _router_info(), rx.link("go home", href="/", id="link-home"), rx.link("go to dynamic 7", href="/dynamic/7", id="link-dynamic"), rx.box( @@ -186,6 +208,7 @@ def dynamic_page(): read_only=True, id="token", ), + _router_info(), rx.link("go home", href="/", id="link-home"), rx.link("go to static", href="/static-page", id="link-static"), rx.box( @@ -458,6 +481,56 @@ def test_navigate_back_and_forth(frontend_path_app: AppHarness, page: Page): expect(log).to_contain_text("dynamic-7") +def test_router_includes_frontend_path( + frontend_path_app: AppHarness, page: Page, frontend_path: str +): + """State.router.url and State.router.page expose paths prefixed with frontend_path.""" + base = _navigate(frontend_path_app, page) + + prefix = "/" + frontend_path.strip("/") if frontend_path else "" + + # Index page: in-app path is "/". + expected_path = f"{prefix}/" + expect(page.locator("#router-url-path")).to_have_value(expected_path) + expect(page.locator("#router-page-raw-path")).to_have_value(expected_path) + expect(page.locator("#router-url")).to_have_value( + re.compile(rf".+{re.escape(expected_path)}$") + ) + expect(page.locator("#router-url")).to_have_value(f"{base}/") + + # Client-side navigation to static page. + page.click("#link-static") + expect(page.locator("#page-id")).to_have_text("static page") + expected_path = f"{prefix}/static-page" + expect(page.locator("#router-url-path")).to_have_value(expected_path) + expect(page.locator("#router-page-raw-path")).to_have_value(expected_path) + expect(page.locator("#router-url")).to_have_value( + re.compile(rf".+{re.escape(expected_path)}$") + ) + expect(page.locator("#router-url")).to_have_value(f"{base}/static-page") + + # Client-side navigation to dynamic page. + page.click("#link-dynamic") + expect(page.locator("#page-id")).to_contain_text("dynamic page") + expected_path = f"{prefix}/dynamic/7" + expect(page.locator("#router-url-path")).to_have_value(expected_path) + expect(page.locator("#router-page-raw-path")).to_have_value(expected_path) + expect(page.locator("#router-url")).to_have_value( + re.compile(rf".+{re.escape(expected_path)}$") + ) + expect(page.locator("#router-url")).to_have_value(f"{base}/dynamic/7") + + # Direct (full-page-load) navigation to dynamic page with different id. + _navigate(frontend_path_app, page, "/dynamic/42") + expected_path = f"{prefix}/dynamic/42" + expect(page.locator("#router-url-path")).to_have_value(expected_path) + expect(page.locator("#router-page-raw-path")).to_have_value(expected_path) + expect(page.locator("#router-url")).to_have_value( + re.compile(rf".+{re.escape(expected_path)}$") + ) + expect(page.locator("#router-url")).to_have_value(f"{base}/dynamic/42") + + def test_frontend_url_format(frontend_path_app: AppHarness, frontend_path: str): """Verify that the frontend_url correctly incorporates the frontend_path.""" url = frontend_path_app.frontend_url