Skip to content

Commit

Permalink
use attributes dict for all link parameters
Browse files Browse the repository at this point in the history
  • Loading branch information
Archmonger committed Oct 12, 2024
1 parent 25e440f commit 8e9b6a8
Show file tree
Hide file tree
Showing 7 changed files with 82 additions and 73 deletions.
28 changes: 12 additions & 16 deletions docs/examples/python/nested-routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,30 +35,26 @@ def root():
def home():
return html.div(
html.h1("Home Page 🏠"),
link("Messages", to="/messages"),
link({"to": "/messages"}, "Messages"),
)


@component
def all_messages():
last_messages = {", ".join(msg["with"]): msg for msg in sorted(message_data, key=lambda m: m["id"])}

messages = []
for msg in last_messages.values():
_link = link(
{"to": f"/messages/with/{'-'.join(msg['with'])}"},
f"Conversation with: {', '.join(msg['with'])}",
)
msg_from = f"{'' if msg['from'] is None else '🔴'} {msg['message']}"
messages.append(html.li({"key": msg["id"]}, html.p(_link), msg_from))

return html.div(
html.h1("All Messages 💬"),
html.ul(
[
html.li(
{"key": msg["id"]},
html.p(
link(
f"Conversation with: {', '.join(msg['with'])}",
to=f"/messages/with/{'-'.join(msg['with'])}",
),
),
f"{'' if msg['from'] is None else '🔴'} {msg['message']}",
)
for msg in last_messages.values()
]
),
html.ul(messages),
)


Expand Down
3 changes: 2 additions & 1 deletion docs/examples/python/route-links.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from reactpy import component, html, run

from reactpy_router import browser_router, link, route


Expand All @@ -15,7 +16,7 @@ def root():
def home():
return html.div(
html.h1("Home Page 🏠"),
link("Messages", to="/messages"),
link({"to": "/messages"}, "Messages"),
)


Expand Down
27 changes: 11 additions & 16 deletions docs/examples/python/route-parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,30 +33,25 @@ def root():
def home():
return html.div(
html.h1("Home Page 🏠"),
link("Messages", to="/messages"),
link({"to": "/messages"}, "Messages"),
)


@component
def all_messages():
last_messages = {", ".join(msg["with"]): msg for msg in sorted(message_data, key=lambda m: m["id"])}
messages = []
for msg in last_messages.values():
msg_hyperlink = link(
{"to": f"/messages/with/{'-'.join(msg['with'])}"},
f"Conversation with: {', '.join(msg['with'])}",
)
msg_from = f"{'' if msg['from'] is None else '🔴'} {msg['message']}"
messages.append(html.li({"key": msg["id"]}, html.p(msg_hyperlink), msg_from))

return html.div(
html.h1("All Messages 💬"),
html.ul(
[
html.li(
{"key": msg["id"]},
html.p(
link(
f"Conversation with: {', '.join(msg['with'])}",
to=f"/messages/with/{'-'.join(msg['with'])}",
),
),
f"{'' if msg['from'] is None else '🔴'} {msg['message']}",
)
for msg in last_messages.values()
]
),
html.ul(messages),
)


Expand Down
2 changes: 1 addition & 1 deletion docs/examples/python/use-params.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def root():
"/",
html.div(
html.h1("Home Page 🏠"),
link("User 123", to="/user/123"),
link({"to": "/user/123"}, "User 123"),
),
),
route("/user/{id:int}", user()),
Expand Down
2 changes: 1 addition & 1 deletion docs/examples/python/use-search-params.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def root():
"/",
html.div(
html.h1("Home Page 🏠"),
link("Search", to="/search?query=reactpy"),
link({"to": "/search?query=reactpy"}, "Search"),
),
),
route("/search", search()),
Expand Down
28 changes: 17 additions & 11 deletions src/reactpy_router/components.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from reactpy import component, html
from reactpy.backend.types import Location
from reactpy.core.component import Component
from reactpy.core.types import VdomDict
from reactpy.web.module import export, module_from_file

Expand All @@ -16,32 +17,37 @@
module_from_file("reactpy-router", file=Path(__file__).parent / "static" / "bundle.js"),
("History"),
)
"""Client-side portion of history handling"""

Link = export(
module_from_file("reactpy-router", file=Path(__file__).parent / "static" / "bundle.js"),
("Link"),
)
"""Client-side portion of link handling"""


def link(attributes: dict[str, Any], *children: Any) -> Component:
"""Create a link with the given attributes and children."""
return _link(attributes, *children)


@component
def link(*attributes_and_children: Any, to: str | None = None, **kwargs: Any) -> VdomDict:
def _link(attributes: dict[str, Any], *children: Any) -> VdomDict:
"""A component that renders a link to the given path."""
if to is None:
raise ValueError("The `to` attribute is required for the `Link` component.")

attributes = attributes.copy()
uuid_string = f"link-{uuid4().hex}"
class_name = f"{uuid_string}"
set_location = _use_route_state().set_location
attributes = {}
children: tuple[Any] = attributes_and_children

if attributes_and_children and isinstance(attributes_and_children[0], dict):
attributes = attributes_and_children[0]
children = attributes_and_children[1:]
if "className" in attributes:
class_name = " ".join([attributes.pop("className"), class_name])
if "class_name" in attributes: # pragma: no cover
# TODO: This can be removed when ReactPy stops supporting underscores in attribute names
class_name = " ".join([attributes.pop("class_name"), class_name])
if "href" in attributes and "to" not in attributes:
attributes["to"] = attributes.pop("href")
if "to" not in attributes:
raise ValueError("The `to` attribute is required for the `Link` component.")
to = attributes.pop("to")

attrs = {
**attributes,
Expand All @@ -52,7 +58,7 @@ def link(*attributes_and_children: Any, to: str | None = None, **kwargs: Any) ->
def on_click(_event: dict[str, Any]) -> None:
set_location(Location(**_event))

return html._(html.a(attrs, *children, **kwargs), Link({"onClick": on_click, "linkClass": uuid_string}))
return html._(html.a(attrs, *children), Link({"onClick": on_click, "linkClass": uuid_string}))


def route(path: str, element: Any | None, *routes: Route) -> Route:
Expand Down
65 changes: 38 additions & 27 deletions tests/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,18 +85,18 @@ async def test_navigate_with_link(display: DisplayFixture):
def sample():
render_count.current += 1
return browser_router(
route("/", link("Root", to="/a", id="root")),
route("/a", link("A", to="/b", id="a")),
route("/b", link("B", to="/c", id="b")),
route("/c", link("C", to="/default", id="c")),
route("/", link({"to": "/a", "id": "root"}, "Root")),
route("/a", link({"to": "/b", "id": "a"}, "A")),
route("/b", link({"to": "/c", "id": "b"}, "B")),
route("/c", link({"to": "/default", "id": "c"}, "C")),
route("{default:any}", html.h1({"id": "default"}, "Default")),
)

await display.show(sample)

for link_selector in ["#root", "#a", "#b", "#c"]:
lnk = await display.page.wait_for_selector(link_selector)
await lnk.click(delay=CLICK_DELAY)
_link = await display.page.wait_for_selector(link_selector)
await _link.click(delay=CLICK_DELAY)

await display.page.wait_for_selector("#default")

Expand Down Expand Up @@ -164,18 +164,18 @@ async def test_browser_popstate(display: DisplayFixture):
@component
def sample():
return browser_router(
route("/", link("Root", to="/a", id="root")),
route("/a", link("A", to="/b", id="a")),
route("/b", link("B", to="/c", id="b")),
route("/c", link("C", to="/default", id="c")),
route("/", link({"to": "/a", "id": "root"}, "Root")),
route("/a", link({"to": "/b", "id": "a"}, "A")),
route("/b", link({"to": "/c", "id": "b"}, "B")),
route("/c", link({"to": "/default", "id": "c"}, "C")),
route("{default:any}", html.h1({"id": "default"}, "Default")),
)

await display.show(sample)

for link_selector in ["#root", "#a", "#b", "#c"]:
lnk = await display.page.wait_for_selector(link_selector)
await lnk.click(delay=CLICK_DELAY)
_link = await display.page.wait_for_selector(link_selector)
await _link.click(delay=CLICK_DELAY)

await display.page.wait_for_selector("#default")

Expand All @@ -196,21 +196,21 @@ async def test_relative_links(display: DisplayFixture):
@component
def sample():
return browser_router(
route("/", link("Root", to="/a", id="root")),
route("/a", link("A", to="/a/a/../b", id="a")),
route("/a/b", link("B", to="../a/b/c", id="b")),
route("/a/b/c", link("C", to="../d", id="c")),
route("/a/d", link("D", to="e", id="d")),
route("/a/e", link("E", to="/a/./f", id="e")),
route("/a/f", link("F", to="../default", id="f")),
route("/", link({"to": "a", "id": "root"}, "Root")),
route("/a", link({"to": "a/a/../b", "id": "a"}, "A")),
route("/a/b", link({"to": "../a/b/c", "id": "b"}, "B")),
route("/a/b/c", link({"to": "../d", "id": "c"}, "C")),
route("/a/d", link({"to": "e", "id": "d"}, "D")),
route("/a/e", link({"to": "/a/./f", "id": "e"}, "E")),
route("/a/f", link({"to": "../default", "id": "f"}, "F")),
route("{default:any}", html.h1({"id": "default"}, "Default")),
)

await display.show(sample)

for link_selector in ["#root", "#a", "#b", "#c", "#d", "#e", "#f"]:
lnk = await display.page.wait_for_selector(link_selector)
await lnk.click(delay=CLICK_DELAY)
_link = await display.page.wait_for_selector(link_selector)
await _link.click(delay=CLICK_DELAY)

await display.page.wait_for_selector("#default")

Expand Down Expand Up @@ -246,23 +246,34 @@ def check_search_params():
@component
def sample():
return browser_router(
route("/", link("Root", to="/a?a=1&b=2", id="root")),
route("/", link({"to": "/a?a=1&b=2", "id": "root"}, "Root")),
route("/a", check_search_params()),
)

await display.show(sample)
await display.page.wait_for_selector("#root")
lnk = await display.page.wait_for_selector("#root")
await lnk.click(delay=CLICK_DELAY)
_link = await display.page.wait_for_selector("#root")
await _link.click(delay=CLICK_DELAY)
await display.page.wait_for_selector("#success")


async def test_link_class_name(display: DisplayFixture):
@component
def sample():
return browser_router(route("/", link("Root", to="/a", id="root", className="class1")))
return browser_router(route("/", link({"to": "/a", "id": "root", "className": "class1"}, "Root")))

await display.show(sample)

lnk = await display.page.wait_for_selector("#root")
assert "class1" in await lnk.get_attribute("class")
_link = await display.page.wait_for_selector("#root")
assert "class1" in await _link.get_attribute("class")


async def test_link_href(display: DisplayFixture):
@component
def sample():
return browser_router(route("/", link({"href": "/a", "id": "root"}, "Root")))

await display.show(sample)

_link = await display.page.wait_for_selector("#root")
assert "/a" in await _link.get_attribute("href")

0 comments on commit 8e9b6a8

Please sign in to comment.