Skip to content

Commit de53beb

Browse files
committed
fix a handful of test errors
1 parent bee18a5 commit de53beb

File tree

11 files changed

+82
-44
lines changed

11 files changed

+82
-44
lines changed

MANIFEST.in

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
include src/reactpy_router/bundle.js
1+
recursive-include src/reactpy_router/static *
22
include src/reactpy_router/py.typed

docs/examples/python/nested-routes.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,10 @@ def home():
4040

4141
@component
4242
def all_messages():
43-
last_messages = {", ".join(msg["with"]): msg for msg in sorted(message_data, key=lambda m: m["id"])}
43+
last_messages = {
44+
", ".join(msg["with"]): msg
45+
for msg in sorted(message_data, key=lambda m: m["id"])
46+
}
4447
return html.div(
4548
html.h1("All Messages 💬"),
4649
html.ul(

docs/examples/python/route-parameters.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,10 @@ def home():
3939

4040
@component
4141
def all_messages():
42-
last_messages = {", ".join(msg["with"]): msg for msg in sorted(message_data, key=lambda m: m["id"])}
42+
last_messages = {
43+
", ".join(msg["with"]): msg
44+
for msg in sorted(message_data, key=lambda m: m["id"])
45+
}
4346
return html.div(
4447
html.h1("All Messages 💬"),
4548
html.ul(

src/js/src/index.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export function bind(node) {
1313
}
1414

1515
export function History({ onChange }) {
16-
// capture changes to the browser's history
16+
// Capture changes to the browser's history
1717
React.useEffect(() => {
1818
const listener = () => {
1919
onChange({

src/reactpy_router/__init__.py

+8-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,14 @@
33

44

55
from .converters import CONVERTERS
6-
from .core import create_router, link, route, router_component, use_params, use_search_params
6+
from .core import (
7+
create_router,
8+
link,
9+
route,
10+
router_component,
11+
use_params,
12+
use_search_params,
13+
)
714
from .resolvers import Resolver
815
from .routers import browser_router
916
from .types import Route, RouteCompiler, RouteResolver

src/reactpy_router/core.py

+31-15
Original file line numberDiff line numberDiff line change
@@ -62,15 +62,23 @@ def router_component(
6262

6363
match = use_memo(lambda: _match_route(resolvers, location, select))
6464

65-
if match is not None:
66-
element, params = match
67-
return html.div(
68-
ConnectionContext(
69-
_route_state_context(element, value=_RouteState(set_location, params)),
70-
value=Connection(old_conn.scope, location, old_conn.carrier),
65+
if match:
66+
route_elements = [
67+
_route_state_context(
68+
html.div(
69+
element, # type: ignore
70+
key=f"{location.pathname}{select}{params}{element}{id(element)}",
71+
),
72+
value=_RouteState(set_location, params),
73+
)
74+
for element, params in match
75+
]
76+
return ConnectionContext(
77+
History( # type: ignore
78+
{"on_change": lambda event: set_location(Location(**event))}
7179
),
72-
History({"on_change": lambda event: set_location(Location(**event))}),
73-
key=location.pathname + select,
80+
route_elements,
81+
value=Connection(old_conn.scope, location, old_conn.carrier),
7482
)
7583

7684
return None
@@ -87,7 +95,9 @@ def link(*children: VdomChild, to: str, **attributes: Any) -> VdomDict:
8795
"onClick": lambda event: set_location(Location(**event)),
8896
"id": uuid,
8997
}
90-
return html._(html.a(attrs, *children), html.script(link_js_content.replace("UUID", uuid)))
98+
return html._(
99+
html.a(attrs, *children), html.script(link_js_content.replace("UUID", uuid))
100+
)
91101

92102

93103
def use_params() -> dict[str, Any]:
@@ -136,28 +146,34 @@ def _iter_routes(routes: Sequence[R]) -> Iterator[R]:
136146

137147

138148
def _match_route(
139-
compiled_routes: Sequence[RouteResolver], location: Location, select: Literal["first", "all"]
140-
) -> tuple[Any, dict[str, Any]] | None:
149+
compiled_routes: Sequence[RouteResolver],
150+
location: Location,
151+
select: Literal["first", "all"],
152+
) -> list[tuple[Any, dict[str, Any]]]:
141153
matches = []
142154

143155
for resolver in compiled_routes:
144156
match = resolver.resolve(location.pathname)
145157
if match is not None:
146158
if select == "first":
147-
return match
159+
return [match]
148160
matches.append(match)
149161

150162
if not matches:
151163
_logger.debug("No matching route found for %s", location.pathname)
152164

153-
return matches or None
165+
return matches
154166

155167

156168
History = export(
157-
module_from_file("reactpy-router", file=Path(__file__).parent / "bundle.js"),
169+
module_from_file(
170+
"reactpy-router", file=Path(__file__).parent / "static" / "bundle.js"
171+
),
158172
("History"),
159173
)
160-
link_js_content = (Path(__file__).parent / "js" / "Link.js").read_text(encoding="utf-8")
174+
link_js_content = (Path(__file__).parent / "static" / "link.js").read_text(
175+
encoding="utf-8"
176+
)
161177

162178

163179
@dataclass

src/reactpy_router/resolvers.py

+6-3
Original file line numberDiff line numberDiff line change
@@ -19,22 +19,25 @@ def __init__(
1919
) -> None:
2020
self.element = route.element
2121
self.pattern, self.converter_mapping = self.parse_path(route.path)
22-
self.converters = converters or CONVERTERS
22+
self.registered_converters = converters or CONVERTERS
2323
self.key = self.pattern.pattern
2424
self.param_regex = re.compile(param_pattern)
2525
self.match_any = match_any_identifier
2626

2727
def parse_path(self, path: str) -> tuple[re.Pattern[str], ConverterMapping]:
28+
# Convert path to regex pattern, then interpret using registered converters
2829
pattern = "^"
2930
last_match_end = 0
3031
converter_mapping: ConverterMapping = {}
3132
for match in self.param_regex.finditer(path):
3233
param_name = match.group("name")
3334
param_type = (match.group("type") or "str").strip(":")
3435
try:
35-
param_conv = self.converter_mapping[param_type]
36+
param_conv = self.registered_converters[param_type]
3637
except KeyError as e:
37-
raise ValueError(f"Unknown conversion type {param_type!r} in {path!r}") from e
38+
raise ValueError(
39+
f"Unknown conversion type {param_type!r} in {path!r}"
40+
) from e
3841
pattern += re.escape(path[last_match_end : match.start()])
3942
pattern += f"(?P<{param_name}>{param_conv['regex']})"
4043
converter_mapping[param_name] = param_conv["func"]
File renamed without changes.

src/reactpy_router/types.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,9 @@ def __hash__(self) -> int:
3838
class Router(Protocol[R_contra]):
3939
"""Return a component that renders the first matching route"""
4040

41-
def __call__(self, *routes: R_contra, select: Literal["first", "all"]) -> ComponentType:
41+
def __call__(
42+
self, *routes: R_contra, select: Literal["first", "all"] = "first"
43+
) -> ComponentType:
4244
...
4345

4446

tests/test_core.py

+10-10
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from reactpy import Ref, component, html, use_location
44
from reactpy.testing import DisplayFixture
5-
from reactpy_router import link, route, routers, use_params, use_search_params
5+
from reactpy_router import browser_router, link, route, use_params, use_search_params
66

77

88
async def test_simple_router(display: DisplayFixture):
@@ -18,7 +18,7 @@ def check_location():
1818

1919
@component
2020
def sample():
21-
return routers.browser_router(
21+
return browser_router(
2222
make_location_check("/a"),
2323
make_location_check("/b"),
2424
make_location_check("/c"),
@@ -40,8 +40,8 @@ def sample():
4040
root_element = await display.root_element()
4141
except AttributeError:
4242
root_element = await display.page.wait_for_selector(
43-
f"#display-{display._next_view_id}",
44-
state="attached", # type: ignore
43+
f"#display-{display._next_view_id}", # type: ignore
44+
state="attached",
4545
)
4646

4747
assert not await root_element.inner_html()
@@ -50,7 +50,7 @@ def sample():
5050
async def test_nested_routes(display: DisplayFixture):
5151
@component
5252
def sample():
53-
return routers.browser_router(
53+
return browser_router(
5454
route(
5555
"/a",
5656
html.h1({"id": "a"}, "A"),
@@ -79,7 +79,7 @@ async def test_navigate_with_link(display: DisplayFixture):
7979
@component
8080
def sample():
8181
render_count.current += 1
82-
return routers.browser_router(
82+
return browser_router(
8383
route("/", link("Root", to="/a", id="root")),
8484
route("/a", link("A", to="/b", id="a")),
8585
route("/b", link("B", to="/c", id="b")),
@@ -110,7 +110,7 @@ def check_params():
110110

111111
@component
112112
def sample():
113-
return routers.browser_router(
113+
return browser_router(
114114
route(
115115
"/first/{first:str}",
116116
check_params(),
@@ -146,7 +146,7 @@ def check_query():
146146

147147
@component
148148
def sample():
149-
return routers.browser_router(route("/", check_query()))
149+
return browser_router(route("/", check_query()))
150150

151151
await display.show(sample)
152152

@@ -158,7 +158,7 @@ def sample():
158158
async def test_browser_popstate(display: DisplayFixture):
159159
@component
160160
def sample():
161-
return routers.browser_router(
161+
return browser_router(
162162
route("/", link("Root", to="/a", id="root")),
163163
route("/a", link("A", to="/b", id="a")),
164164
route("/b", link("B", to="/c", id="b")),
@@ -190,7 +190,7 @@ def sample():
190190
async def test_relative_links(display: DisplayFixture):
191191
@component
192192
def sample():
193-
return routers.browser_router(
193+
return browser_router(
194194
route("/", link("Root", to="/a", id="root")),
195195
route("/a", link("A", to="/a/b", id="a")),
196196
route("/a/b", link("B", to="../a/b/c", id="b")),

tests/test_simple.py renamed to tests/test_resolver.py

+14-10
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,32 @@
22
import uuid
33

44
import pytest
5-
from reactpy_router.routers import parse_path
5+
from reactpy_router import Resolver, route
66

77

88
def test_parse_path():
9-
assert parse_path("/a/b/c") == (re.compile("^/a/b/c$"), {})
10-
assert parse_path("/a/{b}/c") == (
9+
resolver = Resolver(route("/", None))
10+
assert resolver.parse_path("/a/b/c") == (re.compile("^/a/b/c$"), {})
11+
assert resolver.parse_path("/a/{b}/c") == (
1112
re.compile(r"^/a/(?P<b>[^/]+)/c$"),
1213
{"b": str},
1314
)
14-
assert parse_path("/a/{b:int}/c") == (
15+
assert resolver.parse_path("/a/{b:int}/c") == (
1516
re.compile(r"^/a/(?P<b>\d+)/c$"),
1617
{"b": int},
1718
)
18-
assert parse_path("/a/{b:int}/{c:float}/c") == (
19+
assert resolver.parse_path("/a/{b:int}/{c:float}/c") == (
1920
re.compile(r"^/a/(?P<b>\d+)/(?P<c>\d+(\.\d+)?)/c$"),
2021
{"b": int, "c": float},
2122
)
22-
assert parse_path("/a/{b:int}/{c:float}/{d:uuid}/c") == (
23+
assert resolver.parse_path("/a/{b:int}/{c:float}/{d:uuid}/c") == (
2324
re.compile(
2425
r"^/a/(?P<b>\d+)/(?P<c>\d+(\.\d+)?)/(?P<d>[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-["
2526
r"0-9a-f]{4}-[0-9a-f]{12})/c$"
2627
),
2728
{"b": int, "c": float, "d": uuid.UUID},
2829
)
29-
assert parse_path("/a/{b:int}/{c:float}/{d:uuid}/{e:path}/c") == (
30+
assert resolver.parse_path("/a/{b:int}/{c:float}/{d:uuid}/{e:path}/c") == (
3031
re.compile(
3132
r"^/a/(?P<b>\d+)/(?P<c>\d+(\.\d+)?)/(?P<d>[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-["
3233
r"0-9a-f]{4}-[0-9a-f]{12})/(?P<e>.+)/c$"
@@ -36,18 +37,21 @@ def test_parse_path():
3637

3738

3839
def test_parse_path_unkown_conversion():
40+
resolver = Resolver(route("/", None))
3941
with pytest.raises(ValueError):
40-
parse_path("/a/{b:unknown}/c")
42+
resolver.parse_path("/a/{b:unknown}/c")
4143

4244

4345
def test_parse_path_re_escape():
4446
"""Check that we escape regex characters in the path"""
45-
assert parse_path("/a/{b:int}/c.d") == (
47+
resolver = Resolver(route("/", None))
48+
assert resolver.parse_path("/a/{b:int}/c.d") == (
4649
# ^ regex character
4750
re.compile(r"^/a/(?P<b>\d+)/c\.d$"),
4851
{"b": int},
4952
)
5053

5154

5255
def test_match_star_path():
53-
assert parse_path("*") == (re.compile("^.*$"), {})
56+
resolver = Resolver(route("/", None))
57+
assert resolver.parse_path("*") == (re.compile("^.*$"), {})

0 commit comments

Comments
 (0)