1use std::path::PathBuf;
2
3pub const DEFAULT_COLS: u16 = 80;
4pub const DEFAULT_ROWS: u16 = 30;
5pub const POLL_DELAY_MS: u64 = 50;
6pub const OPEN_READY_CAP_MS: u64 = 8_000;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum TimeoutClass {
12 Text,
14 Idle,
16 Command,
18 Exit,
20 Ready,
22}
23
24impl TimeoutClass {
25 pub fn built_in_ms(self) -> u64 {
27 match self {
28 TimeoutClass::Text | TimeoutClass::Idle => 5_000,
29 TimeoutClass::Command | TimeoutClass::Exit | TimeoutClass::Ready => 30_000,
30 }
31 }
32
33 pub fn env_var(self) -> &'static str {
35 match self {
36 TimeoutClass::Text => "TUI_TEST_TIMEOUT_TEXT_MS",
37 TimeoutClass::Idle => "TUI_TEST_TIMEOUT_IDLE_MS",
38 TimeoutClass::Command => "TUI_TEST_TIMEOUT_COMMAND_MS",
39 TimeoutClass::Exit => "TUI_TEST_TIMEOUT_EXIT_MS",
40 TimeoutClass::Ready => "TUI_TEST_TIMEOUT_READY_MS",
41 }
42 }
43
44 pub fn env_ms(self) -> Option<u64> {
46 env_timeout_ms(self.env_var())
47 }
48
49 pub fn default_ms(self) -> u64 {
51 self.env_ms().unwrap_or_else(|| self.built_in_ms())
52 }
53}
54
55fn env_timeout_ms(key: &str) -> Option<u64> {
56 parse_timeout_ms(&std::env::var(key).ok()?)
57}
58
59fn parse_timeout_ms(raw: &str) -> Option<u64> {
61 raw.trim().parse::<u64>().ok().filter(|ms| *ms > 0)
62}
63
64pub fn home_dir() -> PathBuf {
67 if let Ok(dir) = std::env::var("TUI_TEST_HOME") {
68 return PathBuf::from(dir);
69 }
70 let base = dirs::home_dir().unwrap_or_else(std::env::temp_dir);
71 base.join(".tui-test")
72}
73
74#[cfg(test)]
75mod tests {
76 use super::*;
77
78 #[test]
79 fn timeout_overrides_accept_positive_integers() {
80 assert_eq!(parse_timeout_ms("1"), Some(1));
81 assert_eq!(parse_timeout_ms("30000"), Some(30_000));
82 assert_eq!(parse_timeout_ms(" 2500 "), Some(2_500));
83 }
84
85 #[test]
86 fn timeout_overrides_reject_junk_so_the_default_wins() {
87 for raw in ["", "0", "-1", "abc", "1.5", "5s"] {
88 assert_eq!(
89 parse_timeout_ms(raw),
90 None,
91 "expected {raw:?} to be ignored"
92 );
93 }
94 }
95
96 #[test]
97 fn built_in_defaults_split_screen_from_process() {
98 assert_eq!(TimeoutClass::Text.built_in_ms(), 5_000);
99 assert_eq!(TimeoutClass::Idle.built_in_ms(), 5_000);
100 assert_eq!(TimeoutClass::Command.built_in_ms(), 30_000);
101 assert_eq!(TimeoutClass::Exit.built_in_ms(), 30_000);
102 assert_eq!(TimeoutClass::Ready.built_in_ms(), 30_000);
103 }
104
105 #[test]
106 fn each_class_has_a_distinct_env_var() {
107 let classes = [
108 TimeoutClass::Text,
109 TimeoutClass::Idle,
110 TimeoutClass::Command,
111 TimeoutClass::Exit,
112 TimeoutClass::Ready,
113 ];
114 let mut seen = std::collections::HashSet::new();
115 for class in classes {
116 let name = class.env_var();
117 assert!(name.starts_with("TUI_TEST_TIMEOUT_"));
118 assert!(name.ends_with("_MS"));
119 assert!(seen.insert(name), "duplicate env var {name}");
120 }
121 }
122
123 #[test]
124 fn class_default_falls_back_to_the_built_in() {
125 static ENV_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
126 let _guard = ENV_LOCK.lock().unwrap();
127 for class in [TimeoutClass::Text, TimeoutClass::Command] {
128 let key = class.env_var();
129 let old = std::env::var_os(key);
130 std::env::remove_var(key);
131 let result = std::panic::catch_unwind(|| {
132 assert_eq!(class.default_ms(), class.built_in_ms());
133 });
134 if let Some(value) = old {
135 std::env::set_var(key, value);
136 }
137 result.unwrap();
138 }
139 }
140}