Skip to content

Commit

Permalink
yew-router: Dynamic basename.
Browse files Browse the repository at this point in the history
  • Loading branch information
finnbear committed Sep 12, 2024
1 parent 15ac51c commit 5bcfd0e
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 14 deletions.
4 changes: 4 additions & 0 deletions packages/yew-router/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@ pub mod history {
AnyHistory, BrowserHistory, HashHistory, History, HistoryError, HistoryResult, Location,
MemoryHistory,
};

pub(crate) mod query {
pub(crate) use gloo::history::query::Raw;
}
}

pub mod prelude {
Expand Down
2 changes: 1 addition & 1 deletion packages/yew-router/src/navigator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ impl Navigator {
pub(crate) fn prefix_basename<'a>(&self, route_s: &'a str) -> Cow<'a, str> {
match self.basename() {
Some(base) => {
if route_s.is_empty() && route_s.is_empty() {
if base.is_empty() && route_s.is_empty() {
Cow::from("/")
} else {
Cow::from(format!("{base}{route_s}"))
Expand Down
33 changes: 28 additions & 5 deletions packages/yew-router/src/router.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
//! Router Component.
use std::borrow::Cow;
use std::rc::Rc;

use yew::prelude::*;
use yew::virtual_dom::AttrValue;

use crate::history::query::Raw;
use crate::history::{AnyHistory, BrowserHistory, HashHistory, History, Location};
use crate::navigator::Navigator;
use crate::utils::{base_url, strip_slash_suffix};
Expand Down Expand Up @@ -72,16 +74,37 @@ fn base_router(props: &RouterProps) -> Html {
basename,
} = props.clone();

let basename = basename.map(|m| strip_slash_suffix(&m).to_string());
let navigator = Navigator::new(history.clone(), basename.clone());

let old_basename = use_state_eq(|| Option::<String>::None);
if basename != *old_basename {
// If `old_basename` is `Some`, path is probably prefixed with `old_basename`.
// If `old_basename` is `None`, path may or may not be prefixed with the new `basename`,
// depending on whether this is the first render.
let old_navigator = Navigator::new(
history.clone(),
old_basename.as_ref().or(basename.as_ref()).cloned(),
);
old_basename.set(basename.clone());
let location = history.location();
let stripped = old_navigator.strip_basename(Cow::from(location.path()));
let prefixed = navigator.prefix_basename(&stripped);

if prefixed != location.path() {
history
.replace_with_query(prefixed, Raw(location.query_str()))
.unwrap_or_else(|never| match never {});
}
}

let navi_ctx = NavigatorContext { navigator };

let loc_ctx = use_reducer(|| LocationContext {
location: history.location(),
ctr: 0,
});

let basename = basename.map(|m| strip_slash_suffix(&m).to_string());
let navi_ctx = NavigatorContext {
navigator: Navigator::new(history.clone(), basename),
};

{
let loc_ctx_dispatcher = loc_ctx.dispatcher();

Expand Down
83 changes: 75 additions & 8 deletions packages/yew-router/tests/link.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,12 +89,11 @@ fn root_for_browser_router() -> Html {
}
}

#[test]
async fn link_in_browser_router() {
let div = gloo::utils::document().create_element("div").unwrap();
let _ = div.set_attribute("id", "browser-router");
let _ = gloo::utils::body().append_child(&div);
yew::Renderer::<RootForBrowserRouter>::with_root(div).render();
let handle = yew::Renderer::<RootForBrowserRouter>::with_root(div).render();

sleep(Duration::ZERO).await;

Expand All @@ -113,26 +112,43 @@ async fn link_in_browser_router() {
"/search?q=Rust&lang=en_US",
link_href("#browser-router ul > li.search-q-lang > a")
);

handle.destroy();
}

#[derive(PartialEq, Properties)]
struct BasenameProps {
basename: Option<String>,
}

#[function_component(RootForBasename)]
fn root_for_basename() -> Html {
fn root_for_basename(props: &BasenameProps) -> Html {
html! {
<BrowserRouter basename="/base/">
<BrowserRouter basename={props.basename.clone()}>
<NavigationMenu />
</BrowserRouter>
}
}

#[test]
async fn link_with_basename() {
let div = gloo::utils::document().create_element("div").unwrap();
let _ = div.set_attribute("id", "with-basename");
let _ = gloo::utils::body().append_child(&div);
yew::Renderer::<RootForBasename>::with_root(div).render();
let mut handle = yew::Renderer::<RootForBasename>::with_root_and_props(
div,
BasenameProps {
basename: Some("/base/".to_owned()),
},
)
.render();

sleep(Duration::ZERO).await;

assert_eq!(
"/base/",
gloo::utils::window().location().pathname().unwrap()
);

assert_eq!("/base/posts", link_href("#with-basename ul > li.posts > a"));
assert_eq!(
"/base/posts?page=2",
Expand All @@ -151,6 +167,48 @@ async fn link_with_basename() {
"/base/search?q=Rust&lang=en_US",
link_href("#with-basename ul > li.search-q-lang > a")
);

// Some(a) -> Some(b)
handle.update(BasenameProps {
basename: Some("/bayes/".to_owned()),
});

sleep(Duration::ZERO).await;

assert_eq!(
"/bayes/",
gloo::utils::window().location().pathname().unwrap()
);

assert_eq!(
"/bayes/posts",
link_href("#with-basename ul > li.posts > a")
);

// Some -> None
handle.update(BasenameProps { basename: None });

sleep(Duration::ZERO).await;

assert_eq!("/", gloo::utils::window().location().pathname().unwrap());

assert_eq!("/posts", link_href("#with-basename ul > li.posts > a"));

// None -> Some
handle.update(BasenameProps {
basename: Some("/bass/".to_string()),
});

sleep(Duration::ZERO).await;

assert_eq!(
"/bass/",
gloo::utils::window().location().pathname().unwrap()
);

assert_eq!("/bass/posts", link_href("#with-basename ul > li.posts > a"));

handle.destroy();
}

#[function_component(RootForHashRouter)]
Expand All @@ -162,12 +220,11 @@ fn root_for_hash_router() -> Html {
}
}

#[test]
async fn link_in_hash_router() {
let div = gloo::utils::document().create_element("div").unwrap();
let _ = div.set_attribute("id", "hash-router");
let _ = gloo::utils::body().append_child(&div);
yew::Renderer::<RootForHashRouter>::with_root(div).render();
let handle = yew::Renderer::<RootForHashRouter>::with_root(div).render();

sleep(Duration::ZERO).await;

Expand All @@ -186,4 +243,14 @@ async fn link_in_hash_router() {
"#/search?q=Rust&lang=en_US",
link_href("#hash-router ul > li.search-q-lang > a")
);

handle.destroy();
}

// These cannot be run in concurrently because they all read/write the URL.
#[test]
async fn sequential_tests() {
link_in_hash_router().await;
link_in_browser_router().await;
link_with_basename().await;
}

0 comments on commit 5bcfd0e

Please sign in to comment.