Skip to main content

allowthem_server/
nav.rs

1use serde::Serialize;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
4#[serde(rename_all = "lowercase")]
5pub enum NavGroup {
6    Account,
7    Admin,
8}
9
10#[derive(Debug, Clone, Serialize)]
11pub struct NavItem {
12    pub href: String,
13    pub label: String,
14    pub group: NavGroup,
15    pub active: bool,
16}
17
18pub fn nav_items_for(is_admin: bool, current_path: &str) -> Vec<NavItem> {
19    // "/admin/users" intentionally omitted: the route is not yet wired in
20    // binaries/standalone/main.rs. Add the entry here when the route lands,
21    // not before — a dead nav link is a user-visible regression.
22    let mut items = Vec::with_capacity(if is_admin { 5 } else { 2 });
23    if is_admin {
24        for (href, label) in [
25            ("/admin/applications", "Applications"),
26            ("/admin/sessions", "Sessions"),
27            ("/admin/audit", "Audit log"),
28        ] {
29            items.push(NavItem {
30                href: href.into(),
31                label: label.into(),
32                group: NavGroup::Admin,
33                active: current_path.starts_with(href),
34            });
35        }
36    }
37    for (href, label) in [("/settings", "Settings"), ("/logout", "Sign out")] {
38        items.push(NavItem {
39            href: href.into(),
40            label: label.into(),
41            group: NavGroup::Account,
42            active: current_path.starts_with(href),
43        });
44    }
45    items
46}
47
48#[cfg(test)]
49mod tests {
50    use super::*;
51
52    #[test]
53    fn user_gets_account_group_only_settings_active() {
54        let items = nav_items_for(false, "/settings");
55        assert_eq!(items.len(), 2);
56        assert!(items.iter().all(|i| i.group == NavGroup::Account));
57        assert!(items.iter().find(|i| i.href == "/settings").unwrap().active);
58        assert!(!items.iter().find(|i| i.href == "/logout").unwrap().active);
59    }
60
61    #[test]
62    fn admin_deep_path_highlights_applications() {
63        let items = nav_items_for(true, "/admin/applications/abc123");
64        assert_eq!(items.len(), 5);
65        let apps = items
66            .iter()
67            .find(|i| i.href == "/admin/applications")
68            .unwrap();
69        assert!(apps.active);
70        assert_eq!(apps.group, NavGroup::Admin);
71    }
72
73    #[test]
74    fn admin_nav_omits_users_until_route_lands() {
75        let items = nav_items_for(true, "/admin/applications");
76        assert!(items.iter().all(|i| i.href != "/admin/users"));
77    }
78
79    #[test]
80    fn logout_path_marks_sign_out_active() {
81        let items = nav_items_for(true, "/logout");
82        let signout = items.iter().find(|i| i.href == "/logout").unwrap();
83        assert!(signout.active);
84        assert_eq!(signout.group, NavGroup::Account);
85    }
86
87    #[test]
88    fn unrelated_path_has_no_active_item() {
89        let items = nav_items_for(true, "/totally-unrelated");
90        assert!(items.iter().all(|i| !i.active));
91    }
92}