Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Routable::parent with hash segments and query params #3484

Merged
merged 2 commits into from
Jan 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 43 additions & 20 deletions packages/router/src/routable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -625,17 +625,43 @@ pub trait Routable: FromStr + Display + Clone + 'static {
/// ```
fn is_child_of(&self, other: &Self) -> bool {
let self_str = self.to_string();
let self_str = self_str.trim_matches('/');
let self_str = self_str
.split_once('#')
.map(|(route, _)| route)
.unwrap_or(&self_str);
let self_str = self_str
.split_once('?')
.map(|(route, _)| route)
.unwrap_or(self_str);
let self_str = self_str.trim_end_matches('/');
let other_str = other.to_string();
let other_str = other_str.trim_matches('/');
if other_str.is_empty() {
return true;
}
let self_segments = self_str.split('/');
let other_segments = other_str.split('/');
for (self_seg, other_seg) in self_segments.zip(other_segments) {
if self_seg != other_seg {
return false;
let other_str = other_str
.split_once('#')
.map(|(route, _)| route)
.unwrap_or(&other_str);
let other_str = other_str
.split_once('?')
.map(|(route, _)| route)
.unwrap_or(other_str);
let other_str = other_str.trim_end_matches('/');

let mut self_segments = self_str.split('/');
let mut other_segments = other_str.split('/');
loop {
match (self_segments.next(), other_segments.next()) {
// If the two routes are the same length, or this route has less segments, then this segment
// cannot be the child of the other segment
(None, Some(_)) | (None, None) => {
return false;
}
// If two segments are not the same, then this segment cannot be the child of the other segment
(Some(self_seg), Some(other_seg)) => {
if self_seg != other_seg {
return false;
}
}
// If the other route has less segments, then this route is the child of the other route
(Some(_), None) => break,
}
}
true
Expand Down Expand Up @@ -667,17 +693,14 @@ pub trait Routable: FromStr + Display + Clone + 'static {
/// ```
fn parent(&self) -> Option<Self> {
let as_str = self.to_string();
let as_str = as_str.trim_matches('/');
let segments = as_str.split('/');
let (route_and_query, _) = as_str.split_once('#').unwrap_or((&as_str, ""));
let (route, _) = route_and_query
.split_once('?')
.unwrap_or((route_and_query, ""));
let route = route.trim_end_matches('/');
let segments = route.split_inclusive('/');
let segment_count = segments.clone().count();
let new_route = segments
.take(segment_count - 1)
.fold(String::new(), |mut acc, segment| {
acc.push('/');
acc.push_str(segment);
acc
});

let new_route: String = segments.take(segment_count.saturating_sub(1)).collect();
Self::from_str(&new_route).ok()
}

Expand Down
184 changes: 184 additions & 0 deletions packages/router/tests/parent.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
#![allow(unused)]

use std::rc::Rc;

use dioxus::prelude::*;

#[derive(Routable, Clone, PartialEq, Debug)]
#[rustfmt::skip]
enum Route {
#[route("/")]
RootIndex {},
#[nest("/fixed")]
#[layout(Fixed)]
#[route("/")]
FixedIndex {},
#[route("/fixed")]
FixedFixed {},
#[end_layout]
#[end_nest]
#[nest("/:id")]
#[layout(Parameter)]
#[route("/")]
ParameterIndex { id: u8 },
#[route("/fixed")]
ParameterFixed { id: u8 },
#[end_layout]
#[end_nest]
#[nest("/hash")]
#[route("/")]
HashIndex {},
#[nest("/:id")]
#[route("/?:query")]
HashId { id: u8, query: String },
#[layout(Parameter)]
#[route("/path/?:query#:hash")]
HashQuery { id: u8, query: String, hash: String },
}

#[test]
fn get_parent() {
assert_eq!(Route::RootIndex {}.parent(), None);
assert_eq!(Route::FixedIndex {}.parent(), Some(Route::RootIndex {}));
assert_eq!(Route::FixedFixed {}.parent(), Some(Route::FixedIndex {}));
assert_eq!(
Route::ParameterIndex { id: 0 }.parent(),
Some(Route::RootIndex {})
);
assert_eq!(
Route::ParameterFixed { id: 0 }.parent(),
Some(Route::ParameterIndex { id: 0 })
);
assert_eq!(
Route::HashQuery {
id: 0,
query: "query".into(),
hash: "hash".into()
}
.parent(),
Some(Route::HashId {
id: 0,
query: "".into()
})
);
assert_eq!(
Route::HashId {
id: 0,
query: "query".into()
}
.parent(),
Some(Route::HashIndex {})
);
assert_eq!(Route::HashIndex {}.parent(), Some(Route::RootIndex {}));
}

#[test]
fn is_child() {
assert!(!Route::RootIndex {}.is_child_of(&Route::RootIndex {}));
assert!(Route::FixedIndex {}.is_child_of(&Route::RootIndex {}));
assert!(!Route::FixedIndex {}.is_child_of(&Route::FixedIndex {}));
assert!(Route::FixedFixed {}.is_child_of(&Route::FixedIndex {}));
assert!(!Route::FixedFixed {}.is_child_of(&Route::FixedFixed {}));
assert!(Route::ParameterIndex { id: 0 }.is_child_of(&Route::RootIndex {}));
assert!(!Route::ParameterIndex { id: 0 }.is_child_of(&Route::ParameterIndex { id: 0 }));
assert!(Route::ParameterFixed { id: 0 }.is_child_of(&Route::ParameterIndex { id: 0 }));
assert!(!Route::ParameterFixed { id: 0 }.is_child_of(&Route::ParameterFixed { id: 0 }));
assert!(Route::HashQuery {
id: 0,
query: "query".into(),
hash: "hash".into()
}
.is_child_of(&Route::HashId {
id: 0,
query: "query".into()
}));
assert!(!Route::HashQuery {
id: 0,
query: "query".into(),
hash: "hash".into()
}
.is_child_of(&Route::HashQuery {
id: 0,
query: "query".into(),
hash: "hash".into()
}));
assert!(Route::HashId {
id: 0,
query: "query".into()
}
.is_child_of(&Route::HashIndex {}));
assert!(!Route::HashId {
id: 0,
query: "query".into()
}
.is_child_of(&Route::HashId {
id: 0,
query: "query".into()
}));
assert!(Route::HashIndex {}.is_child_of(&Route::RootIndex {}));
assert!(!Route::HashIndex {}.is_child_of(&Route::HashIndex {}));
}

#[component]
fn RootIndex() -> Element {
rsx! { h2 { "Root Index" } }
}

#[component]
fn Fixed() -> Element {
rsx! {
h2 { "Fixed" }
Outlet::<Route> { }
}
}

#[component]
fn FixedIndex() -> Element {
rsx! { h3 { "Fixed - Index" } }
}

#[component]
fn FixedFixed() -> Element {
rsx! { h3 { "Fixed - Fixed"} }
}

#[component]
fn Parameter(id: u8) -> Element {
rsx! {
h2 { "Parameter {id}" }
Outlet::<Route> { }
}
}

#[component]
fn ParameterIndex(id: u8) -> Element {
rsx! { h3 { "Parameter - Index" } }
}

#[component]
fn ParameterFixed(id: u8) -> Element {
rsx! { h3 { "Parameter - Fixed" } }
}

#[component]
fn HashQuery(id: u8, query: String, hash: String) -> Element {
rsx! {
h2 { "Hash Query" }
h3 { "id: {id}" }
h3 { "query: {query}" }
h3 { "hash: {hash}" }
}
}

#[component]
fn HashIndex() -> Element {
rsx! { h3 { "Hash Index" } }
}

#[component]
fn HashId(id: u8, query: String) -> Element {
rsx! {
h3 { "Hash Id {id}" }
h3 { "query: {query}" }
}
}
Loading