Skip to main content

fission_core/
navigation.rs

1//! Shell-neutral navigation requests and hyperlink activation.
2
3use crate::{Action, ActionId};
4use fission_ir::Hyperlink;
5use lazy_static::lazy_static;
6use serde::{Deserialize, Serialize};
7
8/// A navigation operation requested by application code or semantic activation.
9#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
10pub enum NavigationCommand {
11    /// Add a logical route to the active history stack.
12    Push(String),
13    /// Replace the active history entry with a logical route.
14    Replace(String),
15    /// Activate a complete hyperlink, including its browsing-context target.
16    Open(Hyperlink),
17    /// Move one entry backward in the active history.
18    Back,
19    /// Move one entry forward in the active history.
20    Forward,
21    /// Move by a signed number of entries in the active history.
22    Go(i32),
23    /// Reload the current browser document or rebuild the current native route.
24    Reload,
25}
26
27/// Built-in action used by semantic links to enter the normal effect pipeline.
28#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
29pub struct NavigationRequested {
30    /// Operation for the active shell to apply.
31    pub command: NavigationCommand,
32}
33
34impl NavigationRequested {
35    pub fn new(command: NavigationCommand) -> Self {
36        Self { command }
37    }
38}
39
40impl Action for NavigationRequested {
41    fn static_id() -> ActionId {
42        lazy_static! {
43            static ref ID: ActionId = ActionId::from_name("fission_core::NavigationRequested");
44        }
45        *ID
46    }
47}
48
49#[cfg(test)]
50mod tests {
51    use super::*;
52    use crate::{LinkTarget, Runtime, WidgetId};
53
54    #[test]
55    fn built_in_navigation_action_queues_without_an_application_reducer() {
56        let mut runtime = Runtime::default();
57        let command =
58            NavigationCommand::Open(Hyperlink::new("/projects/42").target(LinkTarget::NewWindow));
59
60        runtime
61            .dispatch(
62                NavigationRequested::new(command.clone()).into(),
63                WidgetId::explicit("projects.link"),
64            )
65            .expect("built-in navigation action should dispatch");
66
67        assert_eq!(runtime.take_pending_navigation(), vec![command]);
68    }
69
70    #[test]
71    fn navigation_action_round_trips_complete_hyperlink_metadata() {
72        let request = NavigationRequested::new(NavigationCommand::Open(
73            Hyperlink::new("/report")
74                .target(LinkTarget::Named("preview".into()))
75                .rel("alternate")
76                .download("report.pdf"),
77        ));
78        let envelope: crate::ActionEnvelope = request.clone().into();
79
80        assert_eq!(
81            serde_json::from_slice::<NavigationRequested>(&envelope.payload).unwrap(),
82            request
83        );
84    }
85}