Skip to content

Commit 5bcfd0e

Browse files
committed
yew-router: Dynamic basename.
1 parent 15ac51c commit 5bcfd0e

File tree

4 files changed

+108
-14
lines changed

4 files changed

+108
-14
lines changed

packages/yew-router/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,10 @@ pub mod history {
8888
AnyHistory, BrowserHistory, HashHistory, History, HistoryError, HistoryResult, Location,
8989
MemoryHistory,
9090
};
91+
92+
pub(crate) mod query {
93+
pub(crate) use gloo::history::query::Raw;
94+
}
9195
}
9296

9397
pub mod prelude {

packages/yew-router/src/navigator.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ impl Navigator {
159159
pub(crate) fn prefix_basename<'a>(&self, route_s: &'a str) -> Cow<'a, str> {
160160
match self.basename() {
161161
Some(base) => {
162-
if route_s.is_empty() && route_s.is_empty() {
162+
if base.is_empty() && route_s.is_empty() {
163163
Cow::from("/")
164164
} else {
165165
Cow::from(format!("{base}{route_s}"))

packages/yew-router/src/router.rs

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
//! Router Component.
2+
use std::borrow::Cow;
23
use std::rc::Rc;
34

45
use yew::prelude::*;
56
use yew::virtual_dom::AttrValue;
67

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

77+
let basename = basename.map(|m| strip_slash_suffix(&m).to_string());
78+
let navigator = Navigator::new(history.clone(), basename.clone());
79+
80+
let old_basename = use_state_eq(|| Option::<String>::None);
81+
if basename != *old_basename {
82+
// If `old_basename` is `Some`, path is probably prefixed with `old_basename`.
83+
// If `old_basename` is `None`, path may or may not be prefixed with the new `basename`,
84+
// depending on whether this is the first render.
85+
let old_navigator = Navigator::new(
86+
history.clone(),
87+
old_basename.as_ref().or(basename.as_ref()).cloned(),
88+
);
89+
old_basename.set(basename.clone());
90+
let location = history.location();
91+
let stripped = old_navigator.strip_basename(Cow::from(location.path()));
92+
let prefixed = navigator.prefix_basename(&stripped);
93+
94+
if prefixed != location.path() {
95+
history
96+
.replace_with_query(prefixed, Raw(location.query_str()))
97+
.unwrap_or_else(|never| match never {});
98+
}
99+
}
100+
101+
let navi_ctx = NavigatorContext { navigator };
102+
75103
let loc_ctx = use_reducer(|| LocationContext {
76104
location: history.location(),
77105
ctr: 0,
78106
});
79107

80-
let basename = basename.map(|m| strip_slash_suffix(&m).to_string());
81-
let navi_ctx = NavigatorContext {
82-
navigator: Navigator::new(history.clone(), basename),
83-
};
84-
85108
{
86109
let loc_ctx_dispatcher = loc_ctx.dispatcher();
87110

packages/yew-router/tests/link.rs

Lines changed: 75 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -89,12 +89,11 @@ fn root_for_browser_router() -> Html {
8989
}
9090
}
9191

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

9998
sleep(Duration::ZERO).await;
10099

@@ -113,26 +112,43 @@ async fn link_in_browser_router() {
113112
"/search?q=Rust&lang=en_US",
114113
link_href("#browser-router ul > li.search-q-lang > a")
115114
);
115+
116+
handle.destroy();
117+
}
118+
119+
#[derive(PartialEq, Properties)]
120+
struct BasenameProps {
121+
basename: Option<String>,
116122
}
117123

118124
#[function_component(RootForBasename)]
119-
fn root_for_basename() -> Html {
125+
fn root_for_basename(props: &BasenameProps) -> Html {
120126
html! {
121-
<BrowserRouter basename="/base/">
127+
<BrowserRouter basename={props.basename.clone()}>
122128
<NavigationMenu />
123129
</BrowserRouter>
124130
}
125131
}
126132

127-
#[test]
128133
async fn link_with_basename() {
129134
let div = gloo::utils::document().create_element("div").unwrap();
130135
let _ = div.set_attribute("id", "with-basename");
131136
let _ = gloo::utils::body().append_child(&div);
132-
yew::Renderer::<RootForBasename>::with_root(div).render();
137+
let mut handle = yew::Renderer::<RootForBasename>::with_root_and_props(
138+
div,
139+
BasenameProps {
140+
basename: Some("/base/".to_owned()),
141+
},
142+
)
143+
.render();
133144

134145
sleep(Duration::ZERO).await;
135146

147+
assert_eq!(
148+
"/base/",
149+
gloo::utils::window().location().pathname().unwrap()
150+
);
151+
136152
assert_eq!("/base/posts", link_href("#with-basename ul > li.posts > a"));
137153
assert_eq!(
138154
"/base/posts?page=2",
@@ -151,6 +167,48 @@ async fn link_with_basename() {
151167
"/base/search?q=Rust&lang=en_US",
152168
link_href("#with-basename ul > li.search-q-lang > a")
153169
);
170+
171+
// Some(a) -> Some(b)
172+
handle.update(BasenameProps {
173+
basename: Some("/bayes/".to_owned()),
174+
});
175+
176+
sleep(Duration::ZERO).await;
177+
178+
assert_eq!(
179+
"/bayes/",
180+
gloo::utils::window().location().pathname().unwrap()
181+
);
182+
183+
assert_eq!(
184+
"/bayes/posts",
185+
link_href("#with-basename ul > li.posts > a")
186+
);
187+
188+
// Some -> None
189+
handle.update(BasenameProps { basename: None });
190+
191+
sleep(Duration::ZERO).await;
192+
193+
assert_eq!("/", gloo::utils::window().location().pathname().unwrap());
194+
195+
assert_eq!("/posts", link_href("#with-basename ul > li.posts > a"));
196+
197+
// None -> Some
198+
handle.update(BasenameProps {
199+
basename: Some("/bass/".to_string()),
200+
});
201+
202+
sleep(Duration::ZERO).await;
203+
204+
assert_eq!(
205+
"/bass/",
206+
gloo::utils::window().location().pathname().unwrap()
207+
);
208+
209+
assert_eq!("/bass/posts", link_href("#with-basename ul > li.posts > a"));
210+
211+
handle.destroy();
154212
}
155213

156214
#[function_component(RootForHashRouter)]
@@ -162,12 +220,11 @@ fn root_for_hash_router() -> Html {
162220
}
163221
}
164222

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

172229
sleep(Duration::ZERO).await;
173230

@@ -186,4 +243,14 @@ async fn link_in_hash_router() {
186243
"#/search?q=Rust&lang=en_US",
187244
link_href("#hash-router ul > li.search-q-lang > a")
188245
);
246+
247+
handle.destroy();
248+
}
249+
250+
// These cannot be run in concurrently because they all read/write the URL.
251+
#[test]
252+
async fn sequential_tests() {
253+
link_in_hash_router().await;
254+
link_in_browser_router().await;
255+
link_with_basename().await;
189256
}

0 commit comments

Comments
 (0)