Skip to main content

yew_nav_link/hooks/navigation/
use_navigation.rs

1// SPDX-FileCopyrightText: RAprogramm <andrey.rozanov.vl@gmail.com>
2// SPDX-License-Identifier: MIT
3
4//! Programmatic navigation built on top of yew-router's `Navigator`.
5//!
6//! [`use_navigation`] returns a [`Navigation<R>`] handle. `go_back` and
7//! `go_forward` are ready-made `Callback<()>` values; `push_callback`,
8//! `replace_callback`, and `go_callback` build a `Callback<()>` from an
9//! argument. Every one routes through the router's [`Navigator`], so the
10//! configured basename is honored — the same path [`NavLink`](crate::NavLink)
11//! takes.
12
13use std::marker::PhantomData;
14
15use yew::prelude::*;
16use yew_router::prelude::*;
17
18/// Handle for programmatic route manipulation, created by [`use_navigation`].
19///
20/// `go_back` and `go_forward` are ready-made callbacks. `push_callback`,
21/// `replace_callback`, and `go_callback` build a callback from an argument.
22/// All of them route through the captured [`Navigator`], so the router
23/// basename is honored.
24#[derive(Clone, Debug)]
25pub struct Navigation<R>
26where
27    R: Routable + Clone + 'static
28{
29    navigator:      Option<Navigator>,
30    /// Callback to navigate back in history.
31    pub go_back:    Callback<()>,
32    /// Callback to navigate forward in history.
33    pub go_forward: Callback<()>,
34    marker:         PhantomData<R>
35}
36
37impl<R> Navigation<R>
38where
39    R: Routable + Clone + 'static
40{
41    /// Create a callback that pushes `route` onto the history stack.
42    ///
43    /// The push goes through the router's [`Navigator`], so the configured
44    /// basename is prepended.
45    pub fn push_callback(&self, route: R) -> Callback<()> {
46        let navigator = self.navigator.clone();
47        Callback::from(move |()| {
48            if let Some(navigator) = &navigator {
49                navigator.push(&route);
50            }
51        })
52    }
53
54    /// Create a callback that replaces the current history entry with `route`.
55    ///
56    /// The replace goes through the router's [`Navigator`], so the configured
57    /// basename is prepended.
58    pub fn replace_callback(&self, route: R) -> Callback<()> {
59        let navigator = self.navigator.clone();
60        Callback::from(move |()| {
61            if let Some(navigator) = &navigator {
62                navigator.replace(&route);
63            }
64        })
65    }
66
67    /// Create a callback that moves `delta` entries through history.
68    #[must_use]
69    pub fn go_callback(&self, delta: isize) -> Callback<()> {
70        let navigator = self.navigator.clone();
71        Callback::from(move |()| {
72            if let Some(navigator) = &navigator {
73                navigator.go(delta);
74            }
75        })
76    }
77}
78
79/// Returns a [`Navigation`] handle for programmatic navigation.
80///
81/// ```rust,ignore
82/// use yew::prelude::*;
83/// use yew_nav_link::hooks::use_navigation;
84/// use yew_router::prelude::*;
85///
86/// #[derive(Clone, PartialEq, Debug, Routable)]
87/// enum Route {
88///     #[at("/")]
89///     Home
90/// }
91///
92/// #[component]
93/// fn MyComponent() -> Html {
94///     let navigation = use_navigation::<Route>();
95///
96///     html! {
97///         <div>
98///             <button onclick={navigation.go_back.clone()}>Back</button>
99///             <button onclick={navigation.go_forward.clone()}>Forward</button>
100///             <button onclick={navigation.push_callback(Route::Home)}>
101///                 { "Go Home" }
102///             </button>
103///         </div>
104///     }
105/// }
106/// ```
107#[hook]
108pub fn use_navigation<R>() -> Navigation<R>
109where
110    R: Routable + Clone + 'static
111{
112    let navigator = use_navigator();
113
114    let go_back = {
115        let navigator = navigator.clone();
116        Callback::from(move |()| {
117            if let Some(navigator) = &navigator {
118                navigator.back();
119            }
120        })
121    };
122
123    let go_forward = {
124        let navigator = navigator.clone();
125        Callback::from(move |()| {
126            if let Some(navigator) = &navigator {
127                navigator.forward();
128            }
129        })
130    };
131
132    Navigation {
133        navigator,
134        go_back,
135        go_forward,
136        marker: PhantomData
137    }
138}
139
140#[cfg(test)]
141mod tests {
142    use std::marker::PhantomData;
143
144    use super::*;
145
146    #[test]
147    fn navigation_struct_creation() {
148        #[derive(Clone, PartialEq, Debug, Routable)]
149        enum TestRoute {
150            #[at("/test")]
151            Test,
152            #[at("/")]
153            Home
154        }
155
156        let nav = Navigation::<TestRoute> {
157            go_back:    Callback::from(|()| {}),
158            go_forward: Callback::from(|()| {}),
159            navigator:  None,
160            marker:     PhantomData
161        };
162
163        let _ = nav.go_back;
164        let _ = nav.go_forward;
165    }
166
167    #[test]
168    fn navigation_clone() {
169        #[derive(Clone, PartialEq, Debug, Routable)]
170        enum TestRoute {
171            #[at("/test")]
172            Test
173        }
174
175        let nav1 = Navigation::<TestRoute> {
176            go_back:    Callback::from(|()| {}),
177            go_forward: Callback::from(|()| {}),
178            navigator:  None,
179            marker:     PhantomData
180        };
181
182        let nav2 = nav1;
183        let _ = nav2.go_back;
184        let _ = nav2.go_forward;
185    }
186
187    #[test]
188    fn navigation_debug() {
189        #[derive(Clone, PartialEq, Debug, Routable)]
190        enum TestRoute {
191            #[at("/test")]
192            Test
193        }
194
195        let nav = Navigation::<TestRoute> {
196            go_back:    Callback::from(|()| {}),
197            go_forward: Callback::from(|()| {}),
198            navigator:  None,
199            marker:     PhantomData
200        };
201
202        let debug_str = format!("{nav:?}");
203        assert!(debug_str.contains("Navigation"));
204    }
205
206    #[test]
207    fn navigation_partial_eq() {
208        #[derive(Clone, PartialEq, Debug, Routable)]
209        enum TestRoute {
210            #[at("/test")]
211            Test
212        }
213
214        let nav1 = Navigation::<TestRoute> {
215            go_back:    Callback::from(|()| {}),
216            go_forward: Callback::from(|()| {}),
217            navigator:  None,
218            marker:     PhantomData
219        };
220        let nav2 = nav1;
221        let _ = nav1.go_back;
222        let _ = nav2.go_back;
223    }
224
225    #[test]
226    fn navigation_push_callback() {
227        #[derive(Clone, PartialEq, Debug, Routable)]
228        enum TestRoute {
229            #[at("/")]
230            Home
231        }
232
233        let nav = Navigation::<TestRoute> {
234            go_back:    Callback::from(|()| {}),
235            go_forward: Callback::from(|()| {}),
236            navigator:  None,
237            marker:     PhantomData
238        };
239        let _ = nav.push_callback(TestRoute::Home);
240    }
241
242    #[test]
243    fn navigation_replace_callback() {
244        #[derive(Clone, PartialEq, Debug, Routable)]
245        enum TestRoute {
246            #[at("/")]
247            Home
248        }
249
250        let nav = Navigation::<TestRoute> {
251            go_back:    Callback::from(|()| {}),
252            go_forward: Callback::from(|()| {}),
253            navigator:  None,
254            marker:     PhantomData
255        };
256        let _ = nav.replace_callback(TestRoute::Home);
257    }
258
259    #[test]
260    fn navigation_go_callback() {
261        #[derive(Clone, PartialEq, Debug, Routable)]
262        enum TestRoute {
263            #[at("/")]
264            Home
265        }
266
267        let nav = Navigation::<TestRoute> {
268            go_back:    Callback::from(|()| {}),
269            go_forward: Callback::from(|()| {}),
270            navigator:  None,
271            marker:     PhantomData
272        };
273        let _ = nav.go_callback(-1);
274    }
275
276    #[test]
277    fn navigation_go_callback_with_positive_delta() {
278        #[derive(Clone, PartialEq, Debug, Routable)]
279        enum TestRoute {
280            #[at("/")]
281            Home
282        }
283
284        let nav = Navigation::<TestRoute> {
285            go_back:    Callback::from(|()| {}),
286            go_forward: Callback::from(|()| {}),
287            navigator:  None,
288            marker:     PhantomData
289        };
290        let callback = nav.go_callback(1);
291        let _ = callback;
292    }
293
294    #[test]
295    fn navigation_go_callback_with_zero_delta() {
296        #[derive(Clone, PartialEq, Debug, Routable)]
297        enum TestRoute {
298            #[at("/")]
299            Home
300        }
301
302        let nav = Navigation::<TestRoute> {
303            go_back:    Callback::from(|()| {}),
304            go_forward: Callback::from(|()| {}),
305            navigator:  None,
306            marker:     PhantomData
307        };
308        let callback = nav.go_callback(0);
309        let _ = callback;
310    }
311
312    #[test]
313    fn navigation_go_callback_with_large_negative_delta() {
314        #[derive(Clone, PartialEq, Debug, Routable)]
315        enum TestRoute {
316            #[at("/")]
317            Home
318        }
319
320        let nav = Navigation::<TestRoute> {
321            go_back:    Callback::from(|()| {}),
322            go_forward: Callback::from(|()| {}),
323            navigator:  None,
324            marker:     PhantomData
325        };
326        let callback = nav.go_callback(-10);
327        let _ = callback;
328    }
329
330    #[test]
331    fn navigation_go_callback_with_large_positive_delta() {
332        #[derive(Clone, PartialEq, Debug, Routable)]
333        enum TestRoute {
334            #[at("/")]
335            Home
336        }
337
338        let nav = Navigation::<TestRoute> {
339            go_back:    Callback::from(|()| {}),
340            go_forward: Callback::from(|()| {}),
341            navigator:  None,
342            marker:     PhantomData
343        };
344        let callback = nav.go_callback(10);
345        let _ = callback;
346    }
347
348    #[test]
349    fn navigation_push_callback_with_struct_route() {
350        #[derive(Clone, PartialEq, Debug, Routable)]
351        enum TestRoute {
352            #[at("/users/:id")]
353            User { id: String }
354        }
355
356        let nav = Navigation::<TestRoute> {
357            go_back:    Callback::from(|()| {}),
358            go_forward: Callback::from(|()| {}),
359            navigator:  None,
360            marker:     PhantomData
361        };
362
363        let route = TestRoute::User {
364            id: "123".to_string()
365        };
366        let callback = nav.push_callback(route);
367        let _ = callback;
368    }
369
370    #[test]
371    fn navigation_push_callback_with_tuple_route() {
372        #[derive(Clone, PartialEq, Debug, Routable)]
373        enum TestRoute {
374            #[at("/posts/:year")]
375            Post { year: u32 }
376        }
377
378        let nav = Navigation::<TestRoute> {
379            go_back:    Callback::from(|()| {}),
380            go_forward: Callback::from(|()| {}),
381            navigator:  None,
382            marker:     PhantomData
383        };
384
385        let route = TestRoute::Post {
386            year: 2024
387        };
388        let callback = nav.push_callback(route);
389        let _ = callback;
390    }
391
392    #[test]
393    fn navigation_replace_callback_with_struct_route() {
394        #[derive(Clone, PartialEq, Debug, Routable)]
395        enum TestRoute {
396            #[at("/settings/:section")]
397            Settings { section: String }
398        }
399
400        let nav = Navigation::<TestRoute> {
401            go_back:    Callback::from(|()| {}),
402            go_forward: Callback::from(|()| {}),
403            navigator:  None,
404            marker:     PhantomData
405        };
406
407        let route = TestRoute::Settings {
408            section: "profile".to_string()
409        };
410        let callback = nav.replace_callback(route);
411        let _ = callback;
412    }
413
414    #[test]
415    fn navigation_replace_callback_with_tuple_route() {
416        #[derive(Clone, PartialEq, Debug, Routable)]
417        enum TestRoute {
418            #[at("/api/:version")]
419            Api { version: u32 }
420        }
421
422        let nav = Navigation::<TestRoute> {
423            go_back:    Callback::from(|()| {}),
424            go_forward: Callback::from(|()| {}),
425            navigator:  None,
426            marker:     PhantomData
427        };
428
429        let route = TestRoute::Api {
430            version: 1
431        };
432        let callback = nav.replace_callback(route);
433        let _ = callback;
434    }
435
436    #[test]
437    fn navigation_callbacks_are_distinct() {
438        #[derive(Clone, PartialEq, Debug, Routable)]
439        enum TestRoute {
440            #[at("/")]
441            Home,
442            #[at("/about")]
443            About
444        }
445
446        let nav = Navigation::<TestRoute> {
447            go_back:    Callback::from(|()| {}),
448            go_forward: Callback::from(|()| {}),
449            navigator:  None,
450            marker:     PhantomData
451        };
452
453        let push_callback = nav.push_callback(TestRoute::Home);
454        let replace_callback = nav.replace_callback(TestRoute::About);
455        let go_callback = nav.go_callback(-1);
456
457        let _ = push_callback;
458        let _ = replace_callback;
459        let _ = go_callback;
460    }
461
462    #[test]
463    fn navigation_multiple_callbacks_same_route() {
464        #[derive(Clone, PartialEq, Debug, Routable)]
465        enum TestRoute {
466            #[at("/")]
467            Home
468        }
469
470        let nav = Navigation::<TestRoute> {
471            go_back:    Callback::from(|()| {}),
472            go_forward: Callback::from(|()| {}),
473            navigator:  None,
474            marker:     PhantomData
475        };
476
477        let callback1 = nav.push_callback(TestRoute::Home);
478        let callback2 = nav.push_callback(TestRoute::Home);
479        let callback3 = nav.push_callback(TestRoute::Home);
480
481        let _ = callback1;
482        let _ = callback2;
483        let _ = callback3;
484    }
485
486    #[test]
487    fn navigation_callbacks_with_complex_route() {
488        #[derive(Clone, PartialEq, Debug, Routable)]
489        enum ComplexRoute {
490            #[at("/")]
491            Home,
492            #[at("/users")]
493            Users,
494            #[at("/admin")]
495            Admin,
496            #[at("/date")]
497            Date
498        }
499
500        let nav = Navigation::<ComplexRoute> {
501            go_back:    Callback::from(|()| {}),
502            go_forward: Callback::from(|()| {}),
503            navigator:  None,
504            marker:     PhantomData
505        };
506
507        let _ = nav.push_callback(ComplexRoute::Home);
508        let _ = nav.push_callback(ComplexRoute::Users);
509        let _ = nav.push_callback(ComplexRoute::Admin);
510        let _ = nav.push_callback(ComplexRoute::Date);
511    }
512
513    #[test]
514    fn navigation_callbacks_preserve_route_data() {
515        #[derive(Clone, PartialEq, Debug, Routable)]
516        enum TestRoute {
517            #[at("/search/:query")]
518            Search { query: String }
519        }
520
521        let nav = Navigation::<TestRoute> {
522            go_back:    Callback::from(|()| {}),
523            go_forward: Callback::from(|()| {}),
524            navigator:  None,
525            marker:     PhantomData
526        };
527
528        let query = "rust programming".to_string();
529        let route = TestRoute::Search {
530            query: query.clone()
531        };
532        let _callback = nav.push_callback(route);
533
534        assert_eq!(query, "rust programming");
535    }
536
537    #[test]
538    fn navigation_go_back_callback_exists() {
539        #[derive(Clone, PartialEq, Debug, Routable)]
540        enum TestRoute {
541            #[at("/")]
542            Home
543        }
544
545        let nav = Navigation::<TestRoute> {
546            go_back:    Callback::from(|()| {}),
547            go_forward: Callback::from(|()| {}),
548            navigator:  None,
549            marker:     PhantomData
550        };
551
552        let _ = nav.go_back;
553    }
554
555    #[test]
556    fn navigation_go_forward_callback_exists() {
557        #[derive(Clone, PartialEq, Debug, Routable)]
558        enum TestRoute {
559            #[at("/")]
560            Home
561        }
562
563        let nav = Navigation::<TestRoute> {
564            go_back:    Callback::from(|()| {}),
565            go_forward: Callback::from(|()| {}),
566            navigator:  None,
567            marker:     PhantomData
568        };
569
570        let _ = nav.go_forward;
571    }
572
573    #[test]
574    fn navigation_struct_with_multiple_variants() {
575        #[derive(Clone, PartialEq, Debug, Routable)]
576        enum MultiVariantRoute {
577            #[at("/")]
578            Home,
579            #[at("/about")]
580            About,
581            #[at("/contact")]
582            Contact,
583            #[at("/products")]
584            Products,
585            #[at("/services")]
586            Services,
587            #[at("/blog")]
588            Blog,
589            #[at("/faq")]
590            Faq
591        }
592
593        let nav = Navigation::<MultiVariantRoute> {
594            go_back:    Callback::from(|()| {}),
595            go_forward: Callback::from(|()| {}),
596            navigator:  None,
597            marker:     PhantomData
598        };
599
600        let _ = nav.push_callback(MultiVariantRoute::Home);
601        let _ = nav.push_callback(MultiVariantRoute::About);
602        let _ = nav.push_callback(MultiVariantRoute::Contact);
603        let _ = nav.push_callback(MultiVariantRoute::Products);
604        let _ = nav.push_callback(MultiVariantRoute::Services);
605        let _ = nav.push_callback(MultiVariantRoute::Blog);
606        let _ = nav.push_callback(MultiVariantRoute::Faq);
607    }
608
609    #[test]
610    fn navigation_go_callback_edge_cases() {
611        #[derive(Clone, PartialEq, Debug, Routable)]
612        enum TestRoute {
613            #[at("/")]
614            Home
615        }
616
617        let nav = Navigation::<TestRoute> {
618            go_back:    Callback::from(|()| {}),
619            go_forward: Callback::from(|()| {}),
620            navigator:  None,
621            marker:     PhantomData
622        };
623
624        let min_delta = nav.go_callback(isize::MIN);
625        let max_delta = nav.go_callback(isize::MAX);
626        let neg_one = nav.go_callback(-1);
627        let pos_one = nav.go_callback(1);
628
629        let _ = min_delta;
630        let _ = max_delta;
631        let _ = neg_one;
632        let _ = pos_one;
633    }
634
635    #[test]
636    fn navigation_callbacks_with_empty_string_route() {
637        #[derive(Clone, PartialEq, Debug, Routable)]
638        enum TestRoute {
639            #[at("/item/:name")]
640            Item { name: String }
641        }
642
643        let nav = Navigation::<TestRoute> {
644            go_back:    Callback::from(|()| {}),
645            go_forward: Callback::from(|()| {}),
646            navigator:  None,
647            marker:     PhantomData
648        };
649
650        let route = TestRoute::Item {
651            name: String::new()
652        };
653        let callback = nav.push_callback(route);
654        let _ = callback;
655    }
656
657    #[test]
658    fn navigation_callbacks_with_special_characters() {
659        #[derive(Clone, PartialEq, Debug, Routable)]
660        enum TestRoute {
661            #[at("/tag/:name")]
662            Tag { name: String }
663        }
664
665        let nav = Navigation::<TestRoute> {
666            go_back:    Callback::from(|()| {}),
667            go_forward: Callback::from(|()| {}),
668            navigator:  None,
669            marker:     PhantomData
670        };
671
672        let route = TestRoute::Tag {
673            name: "rust-lang".to_string()
674        };
675        let callback = nav.push_callback(route);
676        let _ = callback;
677    }
678
679    #[test]
680    fn navigation_callbacks_with_unicode() {
681        #[derive(Clone, PartialEq, Debug, Routable)]
682        enum TestRoute {
683            #[at("/emoji/:emoji")]
684            Emoji { emoji: String }
685        }
686
687        let nav = Navigation::<TestRoute> {
688            go_back:    Callback::from(|()| {}),
689            go_forward: Callback::from(|()| {}),
690            navigator:  None,
691            marker:     PhantomData
692        };
693
694        let route = TestRoute::Emoji {
695            emoji: "🦀".to_string()
696        };
697        let callback = nav.push_callback(route);
698        let _ = callback;
699    }
700
701    #[test]
702    fn navigation_callbacks_with_numbers() {
703        #[derive(Clone, PartialEq, Debug, Routable)]
704        enum TestRoute {
705            #[at("/number/:num")]
706            Number { num: u64 }
707        }
708
709        let nav = Navigation::<TestRoute> {
710            go_back:    Callback::from(|()| {}),
711            go_forward: Callback::from(|()| {}),
712            navigator:  None,
713            marker:     PhantomData
714        };
715
716        let route = TestRoute::Number {
717            num: 0
718        };
719        let callback = nav.push_callback(route);
720        let _ = callback;
721
722        let route = TestRoute::Number {
723            num: u64::MAX
724        };
725        let callback = nav.push_callback(route);
726        let _ = callback;
727    }
728
729    #[test]
730    fn navigation_struct_debug_format() {
731        #[derive(Clone, PartialEq, Debug, Routable)]
732        enum TestRoute {
733            #[at("/")]
734            Home
735        }
736
737        let nav = Navigation::<TestRoute> {
738            go_back:    Callback::from(|()| {}),
739            go_forward: Callback::from(|()| {}),
740            navigator:  None,
741            marker:     PhantomData
742        };
743
744        let debug_str = format!("{nav:?}");
745        assert!(debug_str.contains("Navigation"));
746        assert!(debug_str.contains("go_back"));
747        assert!(debug_str.contains("go_forward"));
748    }
749
750    #[test]
751    fn navigation_callbacks_chainability() {
752        #[derive(Clone, PartialEq, Debug, Routable)]
753        enum TestRoute {
754            #[at("/")]
755            Home,
756            #[at("/step1")]
757            Step1,
758            #[at("/step2")]
759            Step2,
760            #[at("/step3")]
761            Step3
762        }
763
764        let nav = Navigation::<TestRoute> {
765            go_back:    Callback::from(|()| {}),
766            go_forward: Callback::from(|()| {}),
767            navigator:  None,
768            marker:     PhantomData
769        };
770
771        let step1_callback = nav.push_callback(TestRoute::Step1);
772        let step2_callback = nav.push_callback(TestRoute::Step2);
773        let step3_callback = nav.push_callback(TestRoute::Step3);
774        let back_callback = nav.go_callback(-1);
775
776        let _ = step1_callback;
777        let _ = step2_callback;
778        let _ = step3_callback;
779        let _ = back_callback;
780    }
781
782    #[test]
783    fn navigation_replace_vs_push_same_route() {
784        #[derive(Clone, PartialEq, Debug, Routable)]
785        enum TestRoute {
786            #[at("/")]
787            Home
788        }
789
790        let nav = Navigation::<TestRoute> {
791            go_back:    Callback::from(|()| {}),
792            go_forward: Callback::from(|()| {}),
793            navigator:  None,
794            marker:     PhantomData
795        };
796
797        let push_cb = nav.push_callback(TestRoute::Home);
798        let replace_cb = nav.replace_callback(TestRoute::Home);
799
800        let _ = push_cb;
801        let _ = replace_cb;
802    }
803
804    #[test]
805    fn navigation_callbacks_with_nested_route_params() {
806        #[derive(Clone, PartialEq, Debug, Routable)]
807        enum TestRoute {
808            #[at("/org/:org_id/team/:team_id/member/:member_id")]
809            Member {
810                org_id:    String,
811                team_id:   String,
812                member_id: String
813            }
814        }
815
816        let nav = Navigation::<TestRoute> {
817            go_back:    Callback::from(|()| {}),
818            go_forward: Callback::from(|()| {}),
819            navigator:  None,
820            marker:     PhantomData
821        };
822
823        let route = TestRoute::Member {
824            org_id:    "org1".to_string(),
825            team_id:   "team1".to_string(),
826            member_id: "member1".to_string()
827        };
828        let callback = nav.push_callback(route);
829        let _ = callback;
830    }
831
832    #[test]
833    fn navigation_callbacks_with_optional_like_params() {
834        #[derive(Clone, PartialEq, Debug, Routable)]
835        enum TestRoute {
836            #[at("/filter/:min/:max")]
837            Filter { min: u32, max: u32 }
838        }
839
840        let nav = Navigation::<TestRoute> {
841            go_back:    Callback::from(|()| {}),
842            go_forward: Callback::from(|()| {}),
843            navigator:  None,
844            marker:     PhantomData
845        };
846
847        let route = TestRoute::Filter {
848            min: 0, max: 100
849        };
850        let callback = nav.push_callback(route);
851        let _ = callback;
852    }
853
854    #[test]
855    fn navigation_go_callback_returns_valid_callback() {
856        #[derive(Clone, PartialEq, Debug, Routable)]
857        enum TestRoute {
858            #[at("/")]
859            Home
860        }
861
862        let nav = Navigation::<TestRoute> {
863            go_back:    Callback::from(|()| {}),
864            go_forward: Callback::from(|()| {}),
865            navigator:  None,
866            marker:     PhantomData
867        };
868
869        let callback = nav.go_callback(-5);
870        let _ = callback;
871    }
872
873    #[test]
874    fn navigation_struct_fields_accessible() {
875        #[derive(Clone, PartialEq, Debug, Routable)]
876        enum TestRoute {
877            #[at("/")]
878            Home
879        }
880
881        let nav = Navigation::<TestRoute> {
882            go_back:    Callback::from(|()| {}),
883            go_forward: Callback::from(|()| {}),
884            navigator:  None,
885            marker:     PhantomData
886        };
887
888        let _nav_back = &nav.go_back;
889        let _nav_forward = &nav.go_forward;
890        let _ = nav.marker;
891    }
892
893    #[test]
894    fn navigation_with_multiple_routable_types() {
895        #[derive(Clone, PartialEq, Debug, Routable)]
896        enum Route1 {
897            #[at("/")]
898            Home
899        }
900
901        #[derive(Clone, PartialEq, Debug, Routable)]
902        enum Route2 {
903            #[at("/")]
904            Page
905        }
906
907        let nav1 = Navigation::<Route1> {
908            go_back:    Callback::from(|()| {}),
909            go_forward: Callback::from(|()| {}),
910            navigator:  None,
911            marker:     PhantomData
912        };
913
914        let nav2 = Navigation::<Route2> {
915            go_back:    Callback::from(|()| {}),
916            go_forward: Callback::from(|()| {}),
917            navigator:  None,
918            marker:     PhantomData
919        };
920
921        let _ = nav1.push_callback(Route1::Home);
922        let _ = nav2.push_callback(Route2::Page);
923    }
924
925    #[test]
926    fn navigation_push_callback_multiple_times() {
927        #[derive(Clone, PartialEq, Debug, Routable)]
928        enum TestRoute {
929            #[at("/")]
930            Home
931        }
932
933        let nav = Navigation::<TestRoute> {
934            go_back:    Callback::from(|()| {}),
935            go_forward: Callback::from(|()| {}),
936            navigator:  None,
937            marker:     PhantomData
938        };
939
940        let _ = nav.push_callback(TestRoute::Home);
941        let _ = nav.push_callback(TestRoute::Home);
942        let _ = nav.push_callback(TestRoute::Home);
943    }
944
945    #[test]
946    fn navigation_replace_callback_multiple_times() {
947        #[derive(Clone, PartialEq, Debug, Routable)]
948        enum TestRoute {
949            #[at("/")]
950            Home
951        }
952
953        let nav = Navigation::<TestRoute> {
954            go_back:    Callback::from(|()| {}),
955            go_forward: Callback::from(|()| {}),
956            navigator:  None,
957            marker:     PhantomData
958        };
959
960        let _ = nav.replace_callback(TestRoute::Home);
961        let _ = nav.replace_callback(TestRoute::Home);
962        let _ = nav.replace_callback(TestRoute::Home);
963    }
964}