Skip to content

Commit ff528bd

Browse files
committed
test: prove shell chooser values survive relaunch bootstrap
The shell chooser looked broken from the UI because coverage stopped short of proving that a saved chooser value like "fish" survives persistence and reaches the next-launch bootstrap path. This adds focused UI-side chooser tests, runtime bootstrap tests for direct overrides and invalid fallback, and an end-to-endish settings -> persisted config -> relaunch bootstrap test for the chooser value itself. The settings copy now also makes the full-relaunch requirement explicit. Constraint: The chosen shell only affects new Taskers launches because existing panes already own live PTYs Rejected: Rely on runtime bootstrap unit tests alone | they did not prove that the chooser value itself survived persistence Rejected: Add only wording | the reported problem needed executable coverage around the relaunch path Confidence: high Scope-risk: narrow Reversibility: clean Directive: Keep one test that starts from the chooser-style value "fish" and proves it reaches relaunch bootstrap, not just absolute shell paths Tested: cargo test -p taskers-shell --lib configured_shell Tested: cargo test -p taskers runtime_bootstrap_tests --bin taskers-gtk Tested: cargo test -p taskers-shell-core configured_shell_setting_normalizes_blank_values --lib Tested: cargo test -p taskers startup_tests --bin taskers-gtk Tested: cargo install --path crates/taskers-app --force Not-tested: Manual relaunch with the settings UI set to Fish
1 parent 48e675f commit ff528bd

2 files changed

Lines changed: 160 additions & 5 deletions

File tree

crates/taskers-app/src/main.rs

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1121,6 +1121,140 @@ mod config_tests {
11211121
}
11221122
}
11231123

1124+
#[cfg(test)]
1125+
mod runtime_bootstrap_tests {
1126+
use super::{TaskersConfig, resolve_runtime_bootstrap};
1127+
use std::{env, fs, os::unix::fs::PermissionsExt, sync::Mutex};
1128+
use taskers_ghostty::EmbeddedTerminalAppearance;
1129+
use taskers_shell_core::{NotificationPreferencesSnapshot, SettingsSnapshot};
1130+
use tempfile::TempDir;
1131+
1132+
static PATH_MUTEX: Mutex<()> = Mutex::new(());
1133+
1134+
struct PathGuard(Option<std::ffi::OsString>);
1135+
1136+
impl Drop for PathGuard {
1137+
fn drop(&mut self) {
1138+
unsafe {
1139+
if let Some(path) = self.0.as_ref() {
1140+
env::set_var("PATH", path);
1141+
} else {
1142+
env::remove_var("PATH");
1143+
}
1144+
}
1145+
}
1146+
}
1147+
1148+
#[test]
1149+
fn runtime_bootstrap_uses_configured_shell_override_in_launch_spec() {
1150+
let _guard = PATH_MUTEX.lock().expect("path mutex");
1151+
let temp = TempDir::new().expect("tempdir");
1152+
let shell_path = temp.path().join("fish");
1153+
fs::write(&shell_path, "#!/bin/sh\nexit 0\n").expect("write shell");
1154+
let mut permissions = fs::metadata(&shell_path).expect("metadata").permissions();
1155+
permissions.set_mode(0o755);
1156+
fs::set_permissions(&shell_path, permissions).expect("chmod");
1157+
1158+
let runtime = resolve_runtime_bootstrap(
1159+
EmbeddedTerminalAppearance::Taskers,
1160+
Some(shell_path.to_str().expect("shell path utf8")),
1161+
None,
1162+
);
1163+
1164+
assert_eq!(
1165+
runtime
1166+
.shell_launch
1167+
.env
1168+
.get("TASKERS_REAL_SHELL")
1169+
.map(String::as_str),
1170+
Some(shell_path.to_str().expect("shell path utf8"))
1171+
);
1172+
assert!(
1173+
runtime
1174+
.shell_launch
1175+
.args
1176+
.iter()
1177+
.any(|arg| arg == "--interactive"),
1178+
"expected configured fish shell to launch interactively"
1179+
);
1180+
assert!(
1181+
runtime
1182+
.shell_launch
1183+
.args
1184+
.iter()
1185+
.any(|arg| arg.contains("taskers-hooks.fish")),
1186+
"expected fish launch spec to source the fish shell hooks"
1187+
);
1188+
}
1189+
1190+
#[test]
1191+
fn runtime_bootstrap_invalid_configured_shell_falls_back_with_note() {
1192+
let _guard = PATH_MUTEX.lock().expect("path mutex");
1193+
let runtime = resolve_runtime_bootstrap(
1194+
EmbeddedTerminalAppearance::Taskers,
1195+
Some("/definitely/missing/taskers-shell"),
1196+
None,
1197+
);
1198+
1199+
assert!(
1200+
runtime.startup_notes.iter().any(|note| {
1201+
note.contains("Configured shell '/definitely/missing/taskers-shell' is unavailable")
1202+
&& note.contains("falling back to the system default shell")
1203+
}),
1204+
"expected fallback startup note when configured shell is invalid"
1205+
);
1206+
}
1207+
1208+
#[test]
1209+
fn configured_shell_round_trips_from_settings_into_relaunch_bootstrap() {
1210+
let _guard = PATH_MUTEX.lock().expect("path mutex");
1211+
let temp = TempDir::new().expect("tempdir");
1212+
let shell_path = temp.path().join("fish");
1213+
fs::write(&shell_path, "#!/bin/sh\nexit 0\n").expect("write shell");
1214+
let mut permissions = fs::metadata(&shell_path).expect("metadata").permissions();
1215+
permissions.set_mode(0o755);
1216+
fs::set_permissions(&shell_path, permissions).expect("chmod");
1217+
let original_path = env::var_os("PATH");
1218+
let _restore_path = PathGuard(original_path);
1219+
unsafe {
1220+
env::set_var("PATH", temp.path());
1221+
}
1222+
1223+
let settings = SettingsSnapshot {
1224+
selected_theme_id: "dark".into(),
1225+
theme_options: Vec::new(),
1226+
shortcut_presets: Vec::new(),
1227+
shortcuts: Vec::new(),
1228+
configured_shell: Some(" fish ".into()),
1229+
default_shell_label: "/bin/zsh".into(),
1230+
notification_preferences: NotificationPreferencesSnapshot::default(),
1231+
render_live_surfaces_in_overview: true,
1232+
};
1233+
let next = TaskersConfig::from_settings(&settings, &TaskersConfig::default());
1234+
let persisted = serde_json::to_string(&next).expect("serialize config");
1235+
let reloaded: TaskersConfig = serde_json::from_str(&persisted).expect("reload config");
1236+
1237+
let runtime = resolve_runtime_bootstrap(
1238+
reloaded.embedded_terminal_appearance,
1239+
reloaded.configured_shell.as_deref(),
1240+
None,
1241+
);
1242+
1243+
assert_eq!(
1244+
runtime
1245+
.shell_launch
1246+
.env
1247+
.get("TASKERS_REAL_SHELL")
1248+
.map(String::as_str),
1249+
Some(shell_path.to_str().expect("shell path utf8")),
1250+
"reloaded configured_shell={:?} path={:?} startup_notes={:?}",
1251+
reloaded.configured_shell,
1252+
env::var_os("PATH"),
1253+
runtime.startup_notes
1254+
);
1255+
}
1256+
}
1257+
11241258
fn is_modifier_key(key: gdk::Key) -> bool {
11251259
matches!(
11261260
key,

crates/taskers-shell/src/lib.rs

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4196,10 +4196,11 @@ fn render_notification_row(
41964196
#[cfg(test)]
41974197
mod tests {
41984198
use super::{
4199-
SurfaceDragCandidate, SurfaceKind, attention_ring_class, select_active_surface,
4200-
show_surface_backdrop, surface_drag_threshold_reached, surface_primary_label,
4201-
surface_runtime_badge_text, surface_status_text, surface_summary_title,
4202-
vcs_commit_net_summary, vcs_diff_line_class, vcs_file_dot_class, vcs_ref_input_value,
4199+
SurfaceDragCandidate, SurfaceKind, attention_ring_class, configured_shell_is_custom,
4200+
configured_shell_matches_option, select_active_surface, show_surface_backdrop,
4201+
surface_drag_threshold_reached, surface_primary_label, surface_runtime_badge_text,
4202+
surface_status_text, surface_summary_title, vcs_commit_net_summary, vcs_diff_line_class,
4203+
vcs_file_dot_class, vcs_ref_input_value,
42034204
};
42044205
use crate::taskers_core::{
42054206
AttentionRingState, AttentionState, BrowserProfileMode, PaneId, RuntimeIdentitySnapshot,
@@ -4428,6 +4429,26 @@ mod tests {
44284429
assert_eq!(selected.id, first.id);
44294430
}
44304431

4432+
#[test]
4433+
fn configured_shell_option_matching_accepts_names_and_paths() {
4434+
assert!(configured_shell_matches_option(Some("fish"), "fish"));
4435+
assert!(configured_shell_matches_option(
4436+
Some("/usr/bin/fish"),
4437+
"fish"
4438+
));
4439+
assert!(!configured_shell_matches_option(
4440+
Some("/usr/bin/zsh"),
4441+
"fish"
4442+
));
4443+
}
4444+
4445+
#[test]
4446+
fn configured_shell_custom_detection_excludes_common_shells() {
4447+
assert!(!configured_shell_is_custom(Some("fish")));
4448+
assert!(!configured_shell_is_custom(Some("/usr/bin/zsh")));
4449+
assert!(configured_shell_is_custom(Some("/opt/homebrew/bin/nu")));
4450+
}
4451+
44314452
#[test]
44324453
fn vcs_file_dot_class_tracks_each_file_status_group() {
44334454
assert_eq!(
@@ -4652,7 +4673,7 @@ fn render_terminal_tab(settings: &SettingsSnapshot, core: SharedCore) -> Element
46524673
section { class: "settings-section",
46534674
div { class: "settings-section-heading", "Terminal" }
46544675
div { class: "settings-section-helper",
4655-
"Configure how new Taskers terminal panes launch. Shell changes apply to new Taskers launches."
4676+
"Configure how new Taskers terminal panes launch. Fully quit and relaunch Taskers to apply shell changes to new panes."
46564677
}
46574678
div { class: "settings-row",
46584679
div { class: "settings-row-copy",

0 commit comments

Comments
 (0)