diff --git a/CHANGELOG.md b/CHANGELOG.md index 977c85eb..de6c4d35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ Using the following categories, list your changes in this order: ### Added - An "offline component" can now be displayed when the client disconnects from the server. +- URL router now supports a `*` wildcard to create default routes. ## [3.6.0] - 2024-01-10 diff --git a/docs/examples/python/django-router.py b/docs/examples/python/django-router.py index 714ca585..5c845967 100644 --- a/docs/examples/python/django-router.py +++ b/docs/examples/python/django-router.py @@ -13,5 +13,6 @@ def my_component(): route("/router/slug//", html.div("Example 5")), route("/router/string//", html.div("Example 6")), route("/router/uuid//", html.div("Example 7")), - route("/router/two_values///", html.div("Example 9")), + route("/router/two_values///", html.div("Example 8")), + route("/router/*", html.div("Fallback")), ) diff --git a/src/reactpy_django/router/__init__.py b/src/reactpy_django/router/__init__.py index ea3e1d3b..3c48e1ab 100644 --- a/src/reactpy_django/router/__init__.py +++ b/src/reactpy_django/router/__init__.py @@ -1,3 +1,5 @@ -from reactpy_django.router.components import django_router +from reactpy_router.core import create_router -__all__ = ["django_router"] +from reactpy_django.router.resolvers import DjangoResolver + +django_router = create_router(DjangoResolver) diff --git a/src/reactpy_django/router/components.py b/src/reactpy_django/router/resolvers.py similarity index 87% rename from src/reactpy_django/router/components.py rename to src/reactpy_django/router/resolvers.py index 60aca8fd..9732ba37 100644 --- a/src/reactpy_django/router/components.py +++ b/src/reactpy_django/router/resolvers.py @@ -3,7 +3,6 @@ import re from typing import Any -from reactpy_router.core import create_router from reactpy_router.simple import ConverterMapping from reactpy_router.types import Route @@ -33,6 +32,7 @@ def resolve(self, path: str) -> tuple[Any, dict[str, Any]] | None: # TODO: Make reactpy_router's parse_path generic enough to where we don't have to define our own def parse_path(path: str) -> tuple[re.Pattern[str], ConverterMapping]: + # Convert path to regex pattern, and make sure to interpret the registered converters (ex. ) pattern = "^" last_match_end = 0 converters: ConverterMapping = {} @@ -50,7 +50,9 @@ def parse_path(path: str) -> tuple[re.Pattern[str], ConverterMapping]: converters[param_name] = param_conv["func"] last_match_end = match.end() pattern += f"{re.escape(path[last_match_end:])}$" - return re.compile(pattern), converters + # Replace literal `*` with "match anything" regex pattern, if it's at the end of the path + if pattern.endswith("\*$"): + pattern = f"{pattern[:-3]}.*$" -django_router = create_router(DjangoResolver) + return re.compile(pattern), converters diff --git a/tests/test_app/router/components.py b/tests/test_app/router/components.py index 8f020990..19eee0ba 100644 --- a/tests/test_app/router/components.py +++ b/tests/test_app/router/components.py @@ -4,20 +4,14 @@ @component -def display_params(*args): - params = use_params() - return html._( - html.div(f"Params: {params}"), - *args, - ) - - -@component -def main(): +def display_params(string: str): location = use_location() query = use_query() + params = use_params() - route_info = html._( + return html._( + html.div({"id": "router-string"}, string), + html.div(f"Params: {params}"), html.div( {"id": "router-path", "data-path": location.pathname}, f"Path Name: {location.pathname}", @@ -26,17 +20,26 @@ def main(): html.div(f"Query: {query}"), ) + +@component +def main(): return django_router( - route("/router/", html.div("Path 1", route_info)), - route("/router/any//", display_params("Path 2", route_info)), - route("/router/integer//", display_params("Path 3", route_info)), - route("/router/path//", display_params("Path 4", route_info)), - route("/router/slug//", display_params("Path 5", route_info)), - route("/router/string//", display_params("Path 6", route_info)), - route("/router/uuid//", display_params("Path 7", route_info)), - route("/router/", None, route("abc/", display_params("Path 8", route_info))), + route("/router/", display_params("Path 1")), + route("/router/any//", display_params("Path 2")), + route("/router/integer//", display_params("Path 3")), + route("/router/path//", display_params("Path 4")), + route("/router/slug//", display_params("Path 5")), + route("/router/string//", display_params("Path 6")), + route("/router/uuid//", display_params("Path 7")), + route("/router/", None, route("abc/", display_params("Path 8"))), route( "/router/two///", - display_params("Path 9", route_info), + display_params("Path 9"), + ), + route( + "/router/star/", + None, + route("one/", display_params("Path 11")), + route("*", display_params("Path 12")), ), ) diff --git a/tests/test_app/tests/test_components.py b/tests/test_app/tests/test_components.py index 61a0ca86..15d5b553 100644 --- a/tests/test_app/tests/test_components.py +++ b/tests/test_app/tests/test_components.py @@ -606,6 +606,21 @@ def test_url_router(self): path = new_page.wait_for_selector("#router-path") self.assertIn("/router/two/123/abc/", path.get_attribute("data-path")) + new_page.goto(f"{self.live_server_url}/router/star/one/") + path = new_page.wait_for_selector("#router-path") + self.assertIn("/router/star/one/", path.get_attribute("data-path")) + + new_page.goto( + f"{self.live_server_url}/router/star/adslkjgklasdjhfah/6789543256/" + ) + path = new_page.wait_for_selector("#router-path") + self.assertIn( + "/router/star/adslkjgklasdjhfah/6789543256/", + path.get_attribute("data-path"), + ) + string = new_page.query_selector("#router-string") + self.assertEquals("Path 12", string.text_content()) + finally: new_page.close()