Includes query parameters in rendered Link component (#2464)

* Add tests for Link component

* Include query parameters in href of Link component

* Add test cases for HashRouter and basename option

* Fix test name, format, clippy

* Ignore clippy warnings for test helpers
This commit is contained in:
Yuki Kodama 2022-03-07 22:32:01 +09:00 committed by GitHub
parent f39afdc5e1
commit e2b91caabd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 236 additions and 3 deletions

View File

@ -23,6 +23,7 @@ js-sys = "0.3"
gloo = { version = "0.6", features = ["futures"] }
route-recognizer = "0.3"
serde = "1"
serde_urlencoded = "0.7.1"
[dependencies.web-sys]
version = "0.3"

View File

@ -7,6 +7,7 @@ use yew::virtual_dom::AttrValue;
use crate::navigator::NavigatorKind;
use crate::scope_ext::RouterScopeExt;
use crate::utils;
use crate::Routable;
/// Props for [`Link`]
@ -86,6 +87,7 @@ where
let LinkProps {
classes,
to,
query,
children,
disabled,
..
@ -100,11 +102,15 @@ where
.navigator()
.expect_throw("failed to get navigator");
let href: AttrValue = {
let href = navigator.route_to_url(to);
let pathname = navigator.route_to_url(to);
let path = query
.and_then(|query| serde_urlencoded::to_string(query).ok())
.and_then(|query| utils::compose_path(&pathname, &query))
.unwrap_or_else(|| pathname.to_string());
match navigator.kind() {
NavigatorKind::Hash => format!("#{}", href).into(),
_ => href,
NavigatorKind::Hash => format!("#{}", path),
_ => path,
}
.into()
};

View File

@ -41,6 +41,18 @@ pub fn fetch_base_url() -> Option<String> {
}
}
pub fn compose_path(pathname: &str, query: &str) -> Option<String> {
gloo::utils::window()
.location()
.href()
.ok()
.and_then(|base| web_sys::Url::new_with_base(pathname, &base).ok())
.map(|url| {
url.set_search(query);
format!("{}{}", url.pathname(), url.search())
})
}
#[cfg(test)]
mod tests {
use gloo::utils::document;
@ -78,4 +90,17 @@ mod tests {
.set_inner_html(r#"<base href="/base">"#);
assert_eq!(fetch_base_url(), Some("/base".to_string()));
}
#[test]
fn test_compose_path() {
assert_eq!(compose_path("/home", ""), Some("/home".to_string()));
assert_eq!(
compose_path("/path/to", "foo=bar"),
Some("/path/to?foo=bar".to_string())
);
assert_eq!(
compose_path("/events", "from=2019&to=2021"),
Some("/events?from=2019&to=2021".to_string())
);
}
}

View File

@ -0,0 +1,188 @@
use gloo::timers::future::sleep;
use serde::{Deserialize, Serialize};
use std::time::Duration;
use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure};
use yew::functional::function_component;
use yew::prelude::*;
use yew_router::prelude::*;
mod utils;
use utils::*;
wasm_bindgen_test_configure!(run_in_browser);
#[derive(Clone, Serialize, Deserialize, PartialEq)]
struct PageParam {
page: i32,
}
#[derive(Clone, Serialize, Deserialize, PartialEq)]
struct SearchParams {
q: String,
lang: Option<String>,
}
impl SearchParams {
fn new(q: &str) -> Self {
Self {
q: q.to_string(),
lang: None,
}
}
fn new_with_lang(q: &str, lang: &str) -> Self {
Self {
q: q.to_string(),
lang: Some(lang.to_string()),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Routable)]
enum Routes {
#[at("/posts")]
Posts,
#[at("/search")]
Search,
}
#[function_component(NavigationMenu)]
fn navigation_menu() -> Html {
html! {
<ul>
<li class="posts">
<Link<Routes> to={Routes::Posts}>
{ "Posts without parameters" }
</Link<Routes>>
</li>
<li class="posts-page-2">
<Link<Routes, PageParam> to={Routes::Posts} query={Some(PageParam { page: 2 })}>
{ "Posts of 2nd page" }
</Link<Routes, PageParam>>
</li>
<li class="search">
<Link<Routes> to={Routes::Search}>
{ "Search withfout parameters" }
</Link<Routes>>
</li>
<li class="search-q">
<Link<Routes, SearchParams> to={Routes::Search} query={Some(SearchParams::new("Rust"))}>
{ "Search with keyword parameter" }
</Link<Routes, SearchParams>>
</li>
<li class="search-q-lang">
<Link<Routes, SearchParams> to={Routes::Search} query={Some(SearchParams::new_with_lang("Rust", "en_US"))}>
{ "Search with keyword and language parameters" }
</Link<Routes, SearchParams>>
</li>
</ul>
}
}
#[function_component(RootForBrowserRouter)]
fn root_for_browser_router() -> Html {
html! {
<BrowserRouter>
<NavigationMenu />
</BrowserRouter>
}
}
#[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::start_app_in_element::<RootForBrowserRouter>(div);
sleep(Duration::ZERO).await;
assert_eq!("/posts", link_href("#browser-router ul > li.posts > a"));
assert_eq!(
"/posts?page=2",
link_href("#browser-router ul > li.posts-page-2 > a")
);
assert_eq!("/search", link_href("#browser-router ul > li.search > a"));
assert_eq!(
"/search?q=Rust",
link_href("#browser-router ul > li.search-q > a")
);
assert_eq!(
"/search?q=Rust&lang=en_US",
link_href("#browser-router ul > li.search-q-lang > a")
);
}
#[function_component(RootForBasename)]
fn root_for_basename() -> Html {
html! {
<BrowserRouter basename="/base/">
<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::start_app_in_element::<RootForBasename>(div);
sleep(Duration::ZERO).await;
assert_eq!("/base/posts", link_href("#with-basename ul > li.posts > a"));
assert_eq!(
"/base/posts?page=2",
link_href("#with-basename ul > li.posts-page-2 > a")
);
assert_eq!(
"/base/search",
link_href("#with-basename ul > li.search > a")
);
assert_eq!(
"/base/search?q=Rust",
link_href("#with-basename ul > li.search-q > a")
);
assert_eq!(
"/base/search?q=Rust&lang=en_US",
link_href("#with-basename ul > li.search-q-lang > a")
);
}
#[function_component(RootForHashRouter)]
fn root_for_hash_router() -> Html {
html! {
<HashRouter>
<NavigationMenu />
</HashRouter>
}
}
#[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::start_app_in_element::<RootForHashRouter>(div);
sleep(Duration::ZERO).await;
assert_eq!("#/posts", link_href("#hash-router ul > li.posts > a"));
assert_eq!(
"#/posts?page=2",
link_href("#hash-router ul > li.posts-page-2 > a")
);
assert_eq!("#/search", link_href("#hash-router ul > li.search > a"));
assert_eq!(
"#/search?q=Rust",
link_href("#hash-router ul > li.search-q > a")
);
assert_eq!(
"#/search?q=Rust&lang=en_US",
link_href("#hash-router ul > li.search-q-lang > a")
);
}

View File

@ -1,5 +1,6 @@
use wasm_bindgen::JsCast;
#[allow(dead_code)]
pub fn obtain_result_by_id(id: &str) -> String {
gloo::utils::document()
.get_element_by_id(id)
@ -7,6 +8,7 @@ pub fn obtain_result_by_id(id: &str) -> String {
.inner_html()
}
#[allow(dead_code)]
pub fn click(selector: &str) {
gloo::utils::document()
.query_selector(selector)
@ -17,6 +19,7 @@ pub fn click(selector: &str) {
.click();
}
#[allow(dead_code)]
pub fn history_length() -> u32 {
gloo::utils::window()
.history()
@ -24,3 +27,13 @@ pub fn history_length() -> u32 {
.length()
.expect("No history length found")
}
#[allow(dead_code)]
pub fn link_href(selector: &str) -> String {
gloo::utils::document()
.query_selector(selector)
.expect("Failed to run query selector")
.unwrap_or_else(|| panic!("No such link: {}", selector))
.get_attribute("href")
.expect("No href attribute")
}