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 let mut items = Vec::with_capacity(if is_admin { 6 } else { 2 });
20 if is_admin {
21 for (href, label) in [
22 ("/admin/applications", "Applications"),
23 ("/admin/users", "Users"),
24 ("/admin/sessions", "Sessions"),
25 ("/admin/audit", "Audit log"),
26 ] {
27 items.push(NavItem {
28 href: href.into(),
29 label: label.into(),
30 group: NavGroup::Admin,
31 active: current_path.starts_with(href),
32 });
33 }
34 }
35 for (href, label) in [("/settings", "Settings"), ("/logout", "Sign out")] {
36 items.push(NavItem {
37 href: href.into(),
38 label: label.into(),
39 group: NavGroup::Account,
40 active: current_path.starts_with(href),
41 });
42 }
43 items
44}
45
46#[cfg(test)]
47mod tests {
48 use super::*;
49
50 #[test]
51 fn user_gets_account_group_only_settings_active() {
52 let items = nav_items_for(false, "/settings");
53 assert_eq!(items.len(), 2);
54 assert!(items.iter().all(|i| i.group == NavGroup::Account));
55 assert!(items.iter().find(|i| i.href == "/settings").unwrap().active);
56 assert!(!items.iter().find(|i| i.href == "/logout").unwrap().active);
57 }
58
59 #[test]
60 fn admin_deep_path_highlights_applications() {
61 let items = nav_items_for(true, "/admin/applications/abc123");
62 assert_eq!(items.len(), 6);
63 let apps = items
64 .iter()
65 .find(|i| i.href == "/admin/applications")
66 .unwrap();
67 assert!(apps.active);
68 assert_eq!(apps.group, NavGroup::Admin);
69 }
70
71 #[test]
72 fn admin_nav_includes_users_after_route_lands() {
73 let items = nav_items_for(true, "/admin/applications");
74 assert!(items.iter().any(|i| i.href == "/admin/users"));
75 }
76
77 #[test]
78 fn logout_path_marks_sign_out_active() {
79 let items = nav_items_for(true, "/logout");
80 let signout = items.iter().find(|i| i.href == "/logout").unwrap();
81 assert!(signout.active);
82 assert_eq!(signout.group, NavGroup::Account);
83 }
84
85 #[test]
86 fn unrelated_path_has_no_active_item() {
87 let items = nav_items_for(true, "/totally-unrelated");
88 assert!(items.iter().all(|i| !i.active));
89 }
90}