diff --git a/packages/router/src/routable.rs b/packages/router/src/routable.rs index fc7acc86c..23cfd9c46 100644 --- a/packages/router/src/routable.rs +++ b/packages/router/src/routable.rs @@ -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 @@ -667,17 +693,14 @@ pub trait Routable: FromStr + Display + Clone + 'static { /// ``` fn parent(&self) -> Option { 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() } diff --git a/packages/router/tests/parent.rs b/packages/router/tests/parent.rs new file mode 100644 index 000000000..176bee135 --- /dev/null +++ b/packages/router/tests/parent.rs @@ -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:: { } + } +} + +#[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:: { } + } +} + +#[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}" } + } +}