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}