Forráskód Böngészése

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

* Fix Routable::parent with hash segments and query params
Evan Almloff 5 hónapja
szülő
commit
57d637184f
2 módosított fájl, 227 hozzáadás és 20 törlés
  1. 43 20
      packages/router/src/routable.rs
  2. 184 0
      packages/router/tests/parent.rs

+ 43 - 20
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<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()
     }
 

+ 184 - 0
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::<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}" }
+    }
+}