Skip to content

Commit 682d8a8

Browse files
committed
chore: add tests
1 parent d2dad16 commit 682d8a8

File tree

6 files changed

+1115
-8
lines changed

6 files changed

+1115
-8
lines changed

src/actions/restore.rs

Lines changed: 105 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,16 @@ use crate::{
1818
};
1919

2020
/// Name of the placeholder session.
21+
///
22+
/// This session is created temporarily when starting tmux from outside a tmux environment.
23+
/// It's deleted after the restore completes.
2124
const PLACEHOLDER_SESSION_NAME: &str = "[placeholder]";
2225

26+
/// Check if we're currently running inside a tmux session.
27+
fn is_inside_tmux() -> bool {
28+
std::env::var("TMUX").is_ok()
29+
}
30+
2331
/// Restore all sessions, windows & panes from the backup file.
2432
pub async fn restore<P: AsRef<Path>>(backup_filepath: P) -> Result<v1::Overview> {
2533
// Prepare the temp directory with the content of the backup.
@@ -28,7 +36,7 @@ pub async fn restore<P: AsRef<Path>>(backup_filepath: P) -> Result<v1::Overview>
2836
let panes_content_dir = temp_dir.path().join("panes-content");
2937

3038
// Start tmux if needed.
31-
let not_in_tmux = std::env::var("TMUX").is_err();
39+
let not_in_tmux = !is_inside_tmux();
3240
if not_in_tmux {
3341
tmux::server::start(PLACEHOLDER_SESSION_NAME).await?;
3442
}
@@ -211,3 +219,99 @@ async fn restore_session(
211219

212220
Ok(())
213221
}
222+
223+
#[cfg(test)]
224+
mod tests {
225+
use super::*;
226+
227+
mod constants {
228+
use super::*;
229+
230+
#[test]
231+
fn placeholder_session_name_is_bracketed() {
232+
// The placeholder name uses brackets to make it visually distinct
233+
// and unlikely to collide with user session names
234+
assert!(PLACEHOLDER_SESSION_NAME.starts_with('['));
235+
assert!(PLACEHOLDER_SESSION_NAME.ends_with(']'));
236+
}
237+
238+
#[test]
239+
fn placeholder_session_name_is_not_empty() {
240+
assert!(!PLACEHOLDER_SESSION_NAME.is_empty());
241+
// Should have content between the brackets
242+
assert!(PLACEHOLDER_SESSION_NAME.len() > 2);
243+
}
244+
}
245+
246+
mod tmux_detection {
247+
use super::*;
248+
249+
#[test]
250+
fn is_inside_tmux_reflects_environment() {
251+
// This test documents the behavior - it checks the TMUX env var
252+
// The actual result depends on the test environment
253+
let expected = std::env::var("TMUX").is_ok();
254+
assert_eq!(is_inside_tmux(), expected);
255+
}
256+
}
257+
258+
mod pair_struct {
259+
use super::*;
260+
use std::path::PathBuf;
261+
use std::str::FromStr;
262+
use tmux::pane_id::PaneId;
263+
264+
fn make_test_pane(id: &str, command: &str, is_active: bool) -> Pane {
265+
Pane {
266+
id: PaneId::from_str(id).unwrap(),
267+
index: 0,
268+
is_active,
269+
title: "test".to_string(),
270+
dirpath: PathBuf::from("/tmp"),
271+
command: command.to_string(),
272+
}
273+
}
274+
275+
#[test]
276+
fn pair_can_be_cloned() {
277+
let pane = make_test_pane("%1", "zsh", true);
278+
279+
let pair = Pair {
280+
source: pane.clone(),
281+
target: PaneId::from_str("%2").unwrap(),
282+
};
283+
284+
let cloned = pair.clone();
285+
assert_eq!(cloned.source.id, pair.source.id);
286+
assert_eq!(cloned.target, pair.target);
287+
}
288+
289+
#[test]
290+
fn pair_is_debug_printable() {
291+
let pane = make_test_pane("%1", "bash", false);
292+
293+
let pair = Pair {
294+
source: pane,
295+
target: PaneId::from_str("%5").unwrap(),
296+
};
297+
298+
// Just verify it doesn't panic - Debug is derived
299+
let debug_str = format!("{:?}", pair);
300+
assert!(debug_str.contains("Pair"));
301+
}
302+
303+
#[test]
304+
fn pair_preserves_source_pane_properties() {
305+
let pane = make_test_pane("%42", "nvim", true);
306+
307+
let pair = Pair {
308+
source: pane,
309+
target: PaneId::from_str("%99").unwrap(),
310+
};
311+
312+
assert_eq!(pair.source.command, "nvim");
313+
assert!(pair.source.is_active);
314+
assert_eq!(pair.target.as_str(), "%99");
315+
}
316+
}
317+
}

src/actions/save.rs

Lines changed: 125 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ use tempfile::TempDir;
1010
use crate::{management::archive::v1, tmux, Result};
1111
use tmux_lib::utils;
1212

13+
/// Shell commands that are recognized for prompt line dropping.
14+
///
15+
/// When capturing pane content, if the active command is one of these shells,
16+
/// we can optionally drop the last N lines to avoid capturing the shell prompt.
17+
const DETECTED_SHELLS: &[&str] = &["zsh", "bash", "fish"];
18+
1319
/// Save the tmux sessions, windows and panes into a backup at `backup_dirpath`.
1420
///
1521
/// After saving, this function returns the path to the backup and the number of
@@ -88,23 +94,36 @@ pub async fn save<P: AsRef<Path>>(
8894
Ok((new_backup_filepath, overview))
8995
}
9096

97+
/// Determine if the given command is a recognized shell.
98+
///
99+
/// Used to decide whether to drop trailing lines (shell prompt) when capturing pane content.
100+
fn is_shell_command(command: &str) -> bool {
101+
DETECTED_SHELLS.contains(&command)
102+
}
103+
104+
/// Calculate how many lines to drop from pane capture based on the active command.
105+
///
106+
/// If the pane is running a recognized shell, we drop `num_lines_to_drop` lines
107+
/// to avoid capturing the shell prompt. For other commands, we keep everything.
108+
fn lines_to_drop_for_pane(pane_command: &str, num_lines_to_drop: usize) -> usize {
109+
if is_shell_command(pane_command) {
110+
num_lines_to_drop
111+
} else {
112+
0
113+
}
114+
}
115+
91116
/// For each provided pane, retrieve the content and save it into `destination_dir`.
92117
async fn save_panes_content<P: AsRef<Path>>(
93118
panes: Vec<tmux::pane::Pane>,
94119
destination_dir: P,
95120
num_lines_to_drop: usize,
96121
) -> Result<()> {
97122
let mut handles = Vec::new();
98-
let detected_shells = ["zsh", "bash", "fish"];
99123

100124
for pane in panes {
101125
let dest_dir = destination_dir.as_ref().to_path_buf();
102-
103-
let drop_n_last_lines = if detected_shells.contains(&&pane.command[..]) {
104-
num_lines_to_drop
105-
} else {
106-
0
107-
};
126+
let drop_n_last_lines = lines_to_drop_for_pane(&pane.command, num_lines_to_drop);
108127

109128
let handle = smol::spawn(async move {
110129
let stdout = pane.capture().await.unwrap();
@@ -120,3 +139,102 @@ async fn save_panes_content<P: AsRef<Path>>(
120139
join_all(handles).await;
121140
Ok(())
122141
}
142+
143+
#[cfg(test)]
144+
mod tests {
145+
use super::*;
146+
147+
mod shell_detection {
148+
use super::*;
149+
150+
#[test]
151+
fn recognizes_zsh() {
152+
assert!(is_shell_command("zsh"));
153+
}
154+
155+
#[test]
156+
fn recognizes_bash() {
157+
assert!(is_shell_command("bash"));
158+
}
159+
160+
#[test]
161+
fn recognizes_fish() {
162+
assert!(is_shell_command("fish"));
163+
}
164+
165+
#[test]
166+
fn rejects_vim() {
167+
assert!(!is_shell_command("vim"));
168+
}
169+
170+
#[test]
171+
fn rejects_nvim() {
172+
assert!(!is_shell_command("nvim"));
173+
}
174+
175+
#[test]
176+
fn rejects_python() {
177+
assert!(!is_shell_command("python"));
178+
}
179+
180+
#[test]
181+
fn rejects_empty_command() {
182+
assert!(!is_shell_command(""));
183+
}
184+
185+
#[test]
186+
fn rejects_similar_but_different() {
187+
// Shell name as substring shouldn't match
188+
assert!(!is_shell_command("zsh-5.9"));
189+
assert!(!is_shell_command("/bin/zsh"));
190+
assert!(!is_shell_command("bash-5.2"));
191+
}
192+
193+
#[test]
194+
fn case_sensitive() {
195+
assert!(!is_shell_command("ZSH"));
196+
assert!(!is_shell_command("BASH"));
197+
assert!(!is_shell_command("Fish"));
198+
}
199+
}
200+
201+
mod lines_to_drop {
202+
use super::*;
203+
204+
#[test]
205+
fn drops_lines_for_shells() {
206+
assert_eq!(lines_to_drop_for_pane("zsh", 2), 2);
207+
assert_eq!(lines_to_drop_for_pane("bash", 3), 3);
208+
assert_eq!(lines_to_drop_for_pane("fish", 1), 1);
209+
}
210+
211+
#[test]
212+
fn zero_drop_for_non_shells() {
213+
assert_eq!(lines_to_drop_for_pane("vim", 5), 0);
214+
assert_eq!(lines_to_drop_for_pane("python", 10), 0);
215+
assert_eq!(lines_to_drop_for_pane("htop", 3), 0);
216+
}
217+
218+
#[test]
219+
fn zero_requested_means_zero_dropped() {
220+
assert_eq!(lines_to_drop_for_pane("zsh", 0), 0);
221+
assert_eq!(lines_to_drop_for_pane("bash", 0), 0);
222+
}
223+
}
224+
225+
mod constants {
226+
use super::*;
227+
228+
#[test]
229+
fn detected_shells_includes_common_shells() {
230+
assert!(DETECTED_SHELLS.contains(&"zsh"));
231+
assert!(DETECTED_SHELLS.contains(&"bash"));
232+
assert!(DETECTED_SHELLS.contains(&"fish"));
233+
}
234+
235+
#[test]
236+
fn detected_shells_is_not_empty() {
237+
assert!(!DETECTED_SHELLS.is_empty());
238+
}
239+
}
240+
}

0 commit comments

Comments
 (0)