Skip to main content

wisp_core/
lib.rs

1mod action;
2mod candidate;
3mod domain;
4mod preview;
5mod reduce;
6mod view;
7
8pub use action::{Action, CandidateAction, ResolvedAction, resolve_action, sanitize_session_name};
9pub use candidate::{
10    Candidate, CandidateId, CandidateKind, CandidateMetadata, DirectoryMetadata, ScoreHints,
11    SessionMetadata, WindowMetadata, WorktreeMetadata, deduplicate_candidates,
12    normalize_display_path, sort_candidates,
13};
14pub use domain::{
15    AlertAggregate, AlertState, AttentionBadge, ClientFocus, ClientId, DirectoryRecord,
16    DomainConfig, DomainSnapshot, DomainState, NotificationConfig, PaneId, PaneRecord, SessionId,
17    SessionRecord, SessionSortKey, WindowId, WindowRecord, aggregate_alerts,
18};
19pub use preview::{
20    PreviewContent, PreviewKey, PreviewKind, PreviewRequest, preview_request_for_candidate,
21};
22pub use reduce::{DomainEvent, reduce_domain_event};
23pub use view::{
24    GitBranchStatus, GitBranchSync, PickerMode, SessionListItem, SessionListItemKind,
25    SessionListSortMode, StatusSessionItem, WorktreeInfo, derive_candidates, derive_session_list,
26    derive_session_list_with_worktrees, derive_status_items, sort_session_list_items,
27};
28
29#[cfg(test)]
30mod tests {
31    use std::path::PathBuf;
32
33    use crate::candidate::WorktreeMetadata;
34    use crate::{
35        Candidate, CandidateAction, CandidateId, CandidateMetadata, DirectoryMetadata, PreviewKey,
36        PreviewKind, ResolvedAction, ScoreHints, SessionMetadata, deduplicate_candidates,
37        normalize_display_path, preview_request_for_candidate, resolve_action, sort_candidates,
38    };
39
40    #[test]
41    fn normalizes_home_directory_display_paths() {
42        let path = PathBuf::from("/Users/emma/projects/wisp");
43        let home = PathBuf::from("/Users/emma");
44
45        let display = normalize_display_path(&path, Some(home.as_path()));
46
47        assert_eq!(display, "~/projects/wisp");
48    }
49
50    #[test]
51    fn deduplicates_candidates_by_identity_while_keeping_the_better_match() {
52        let duplicate_a = Candidate::directory(DirectoryMetadata {
53            full_path: PathBuf::from("/tmp/wisp"),
54            display_path: "/tmp/wisp".to_string(),
55            zoxide_score: Some(5.0),
56            git_root_hint: None,
57            exists: true,
58        });
59        let duplicate_b = Candidate {
60            score_hints: ScoreHints {
61                source_score: Some(9),
62                ..ScoreHints::default()
63            },
64            ..duplicate_a.clone()
65        };
66
67        let deduplicated = deduplicate_candidates([duplicate_a, duplicate_b]);
68
69        assert_eq!(deduplicated.len(), 1);
70        assert_eq!(deduplicated[0].score_hints.source_score, Some(9));
71    }
72
73    #[test]
74    fn keeps_worktrees_distinct_from_directories_when_deduplicating() {
75        let directory = Candidate::directory(DirectoryMetadata {
76            full_path: PathBuf::from("/tmp/wisp"),
77            display_path: "/tmp/wisp".to_string(),
78            zoxide_score: Some(5.0),
79            git_root_hint: None,
80            exists: true,
81        });
82        let worktree = Candidate::worktree(WorktreeMetadata {
83            full_path: PathBuf::from("/tmp/wisp"),
84            display_path: "/tmp/wisp".to_string(),
85            branch: Some("feature/demo".to_string()),
86        });
87
88        let deduplicated = deduplicate_candidates([directory, worktree]);
89
90        assert_eq!(deduplicated.len(), 2);
91        assert!(deduplicated.iter().any(|candidate| matches!(
92            candidate.id,
93            CandidateId::Directory(ref path) if path == &PathBuf::from("/tmp/wisp")
94        )));
95        assert!(deduplicated.iter().any(|candidate| matches!(
96            candidate.id,
97            CandidateId::Worktree(ref path) if path == &PathBuf::from("/tmp/wisp")
98        )));
99    }
100
101    #[test]
102    fn resolves_actions_from_candidate_metadata() {
103        let session = Candidate::session(SessionMetadata {
104            session_name: "workbench".to_string(),
105            attached: true,
106            current: true,
107            window_count: 4,
108            last_activity: Some(42),
109        });
110        let directory = Candidate::directory(DirectoryMetadata {
111            full_path: PathBuf::from("/Users/emma/projects/wisp"),
112            display_path: "~/projects/wisp".to_string(),
113            zoxide_score: Some(8.5),
114            git_root_hint: None,
115            exists: true,
116        });
117
118        assert_eq!(
119            resolve_action(&session),
120            Some(ResolvedAction::SwitchSession {
121                session_name: "workbench".to_string(),
122            })
123        );
124        assert_eq!(
125            resolve_action(&directory),
126            Some(ResolvedAction::CreateOrSwitchSession {
127                session_name: "wisp".to_string(),
128                directory: PathBuf::from("/Users/emma/projects/wisp"),
129            })
130        );
131    }
132
133    #[test]
134    fn derives_preview_keys_from_candidates() {
135        let candidate = Candidate::session(SessionMetadata {
136            session_name: "ops".to_string(),
137            attached: false,
138            current: false,
139            window_count: 2,
140            last_activity: None,
141        });
142
143        let request = preview_request_for_candidate(&candidate);
144
145        assert_eq!(request.key(), &PreviewKey::Session("ops".to_string()));
146        assert_eq!(request.kind(), PreviewKind::SessionSummary);
147    }
148
149    #[test]
150    fn sorts_current_and_high_signal_candidates_first() {
151        let mut candidates = vec![
152            Candidate::directory(DirectoryMetadata {
153                full_path: PathBuf::from("/tmp/zeta"),
154                display_path: "/tmp/zeta".to_string(),
155                zoxide_score: Some(1.0),
156                git_root_hint: None,
157                exists: true,
158            }),
159            Candidate::session(SessionMetadata {
160                session_name: "alpha".to_string(),
161                attached: false,
162                current: true,
163                window_count: 1,
164                last_activity: Some(1),
165            }),
166        ];
167
168        sort_candidates(&mut candidates);
169
170        assert!(matches!(
171            candidates[0].metadata,
172            CandidateMetadata::Session(SessionMetadata { current: true, .. })
173        ));
174    }
175
176    #[test]
177    fn exposes_candidate_action_on_domain_values() {
178        let candidate = Candidate::directory(DirectoryMetadata {
179            full_path: PathBuf::from("/tmp/project"),
180            display_path: "/tmp/project".to_string(),
181            zoxide_score: Some(3.0),
182            git_root_hint: None,
183            exists: true,
184        });
185
186        assert_eq!(candidate.action, CandidateAction::CreateOrSwitchSession);
187    }
188}