Skip to content

Commit ae7d5ea

Browse files
committed
test: expand workspace sidebar coverage
1 parent 0d5edf2 commit ae7d5ea

3 files changed

Lines changed: 209 additions & 15 deletions

File tree

src-tauri/src/storage.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,43 @@ pub(crate) fn write_settings(path: &PathBuf, settings: &AppSettings) -> Result<(
3838
let data = serde_json::to_string_pretty(settings).map_err(|e| e.to_string())?;
3939
std::fs::write(path, data).map_err(|e| e.to_string())
4040
}
41+
42+
#[cfg(test)]
43+
mod tests {
44+
use super::{read_workspaces, write_workspaces};
45+
use crate::types::{WorkspaceEntry, WorkspaceKind, WorkspaceSettings};
46+
use uuid::Uuid;
47+
48+
#[test]
49+
fn write_read_workspaces_persists_sort_and_group() {
50+
let temp_dir =
51+
std::env::temp_dir().join(format!("codex-monitor-test-{}", Uuid::new_v4()));
52+
std::fs::create_dir_all(&temp_dir).expect("create temp dir");
53+
let path = temp_dir.join("workspaces.json");
54+
55+
let mut settings = WorkspaceSettings::default();
56+
settings.sort_order = Some(5);
57+
settings.group_id = Some("group-42".to_string());
58+
settings.sidebar_collapsed = true;
59+
settings.git_root = Some("/tmp".to_string());
60+
61+
let entry = WorkspaceEntry {
62+
id: "w1".to_string(),
63+
name: "Workspace".to_string(),
64+
path: "/tmp".to_string(),
65+
codex_bin: None,
66+
kind: WorkspaceKind::Main,
67+
parent_id: None,
68+
worktree: None,
69+
settings: settings.clone(),
70+
};
71+
72+
write_workspaces(&path, &[entry]).expect("write workspaces");
73+
let read = read_workspaces(&path).expect("read workspaces");
74+
let stored = read.get("w1").expect("stored workspace");
75+
assert_eq!(stored.settings.sort_order, Some(5));
76+
assert_eq!(stored.settings.group_id.as_deref(), Some("group-42"));
77+
assert!(stored.settings.sidebar_collapsed);
78+
assert_eq!(stored.settings.git_root.as_deref(), Some("/tmp"));
79+
}
80+
}

src-tauri/src/types.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,7 @@ impl Default for AppSettings {
305305

306306
#[cfg(test)]
307307
mod tests {
308-
use super::{AppSettings, BackendMode, WorkspaceEntry, WorkspaceKind};
308+
use super::{AppSettings, BackendMode, WorkspaceEntry, WorkspaceKind, WorkspaceSettings};
309309

310310
#[test]
311311
fn app_settings_defaults_from_empty_json() {
@@ -337,4 +337,13 @@ mod tests {
337337
assert!(entry.settings.sort_order.is_none());
338338
assert!(entry.settings.group_id.is_none());
339339
}
340+
341+
#[test]
342+
fn workspace_settings_defaults() {
343+
let settings = WorkspaceSettings::default();
344+
assert!(!settings.sidebar_collapsed);
345+
assert!(settings.sort_order.is_none());
346+
assert!(settings.group_id.is_none());
347+
assert!(settings.git_root.is_none());
348+
}
340349
}

src-tauri/src/workspaces.rs

Lines changed: 159 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use std::collections::HashMap;
12
use std::path::PathBuf;
23

34
use ignore::WalkBuilder;
@@ -101,10 +102,27 @@ fn sort_workspaces(list: &mut Vec<WorkspaceInfo>) {
101102
list.sort_by(|a, b| {
102103
let a_order = a.settings.sort_order.unwrap_or(u32::MAX);
103104
let b_order = b.settings.sort_order.unwrap_or(u32::MAX);
104-
a_order.cmp(&b_order).then_with(|| a.name.cmp(&b.name))
105+
a_order
106+
.cmp(&b_order)
107+
.then_with(|| a.name.cmp(&b.name))
108+
.then_with(|| a.id.cmp(&b.id))
105109
});
106110
}
107111

112+
fn apply_workspace_settings_update(
113+
workspaces: &mut HashMap<String, WorkspaceEntry>,
114+
id: &str,
115+
settings: WorkspaceSettings,
116+
) -> Result<WorkspaceEntry, String> {
117+
match workspaces.get_mut(id) {
118+
Some(entry) => {
119+
entry.settings = settings.clone();
120+
Ok(entry.clone())
121+
}
122+
None => Err("workspace not found".to_string()),
123+
}
124+
}
125+
108126
async fn run_git_command(repo_path: &PathBuf, args: &[&str]) -> Result<String, String> {
109127
let output = Command::new("git")
110128
.args(args)
@@ -445,13 +463,7 @@ pub(crate) async fn update_workspace_settings(
445463
) -> Result<WorkspaceInfo, String> {
446464
let (entry_snapshot, list) = {
447465
let mut workspaces = state.workspaces.lock().await;
448-
let entry_snapshot = match workspaces.get_mut(&id) {
449-
Some(entry) => {
450-
entry.settings = settings.clone();
451-
entry.clone()
452-
}
453-
None => return Err("workspace not found".to_string()),
454-
};
466+
let entry_snapshot = apply_workspace_settings_update(&mut workspaces, &id, settings)?;
455467
let list: Vec<_> = workspaces.values().cloned().collect();
456468
(entry_snapshot, list)
457469
};
@@ -570,19 +582,43 @@ pub(crate) async fn open_workspace_in(
570582

571583
#[cfg(test)]
572584
mod tests {
573-
use super::{sanitize_worktree_name, sort_workspaces};
574-
use crate::types::{WorkspaceInfo, WorkspaceKind, WorkspaceSettings};
585+
use std::collections::HashMap;
586+
use std::path::PathBuf;
587+
588+
use super::{apply_workspace_settings_update, sanitize_worktree_name, sort_workspaces};
589+
use crate::storage::{read_workspaces, write_workspaces};
590+
use crate::types::{WorktreeInfo, WorkspaceEntry, WorkspaceInfo, WorkspaceKind, WorkspaceSettings};
591+
use uuid::Uuid;
575592

576593
fn workspace(name: &str, sort_order: Option<u32>) -> WorkspaceInfo {
594+
workspace_with_id_and_kind(name, name, sort_order, WorkspaceKind::Main)
595+
}
596+
597+
fn workspace_with_id_and_kind(
598+
name: &str,
599+
id: &str,
600+
sort_order: Option<u32>,
601+
kind: WorkspaceKind,
602+
) -> WorkspaceInfo {
603+
let (parent_id, worktree) = if kind.is_worktree() {
604+
(
605+
Some("parent".to_string()),
606+
Some(WorktreeInfo {
607+
branch: name.to_string(),
608+
}),
609+
)
610+
} else {
611+
(None, None)
612+
};
577613
WorkspaceInfo {
578-
id: name.to_string(),
614+
id: id.to_string(),
579615
name: name.to_string(),
580616
path: "/tmp".to_string(),
581617
connected: false,
582618
codex_bin: None,
583-
kind: WorkspaceKind::Main,
584-
parent_id: None,
585-
worktree: None,
619+
kind,
620+
parent_id,
621+
worktree,
586622
settings: WorkspaceSettings {
587623
sidebar_collapsed: false,
588624
sort_order,
@@ -599,6 +635,12 @@ mod tests {
599635
assert_eq!(sanitize_worktree_name("--branch--"), "branch");
600636
}
601637

638+
#[test]
639+
fn sanitize_worktree_name_allows_safe_chars() {
640+
assert_eq!(sanitize_worktree_name("release_1.2.3"), "release_1.2.3");
641+
assert_eq!(sanitize_worktree_name("feature--x"), "feature--x");
642+
}
643+
602644
#[test]
603645
fn sort_workspaces_orders_by_sort_then_name() {
604646
let mut items = vec![
@@ -613,4 +655,107 @@ mod tests {
613655
let names: Vec<_> = items.into_iter().map(|item| item.name).collect();
614656
assert_eq!(names, vec!["gamma", "delta", "alpha", "beta"]);
615657
}
658+
659+
#[test]
660+
fn sort_workspaces_places_unordered_last_and_names_tie_break() {
661+
let mut items = vec![
662+
workspace("delta", None),
663+
workspace("beta", Some(1)),
664+
workspace("alpha", Some(1)),
665+
workspace("gamma", None),
666+
];
667+
668+
sort_workspaces(&mut items);
669+
670+
let names: Vec<_> = items.into_iter().map(|item| item.name).collect();
671+
assert_eq!(names, vec!["alpha", "beta", "delta", "gamma"]);
672+
}
673+
674+
#[test]
675+
fn sort_workspaces_ignores_group_ids() {
676+
let mut first = workspace("beta", Some(2));
677+
first.settings.group_id = Some("group-b".to_string());
678+
let mut second = workspace("alpha", Some(1));
679+
second.settings.group_id = Some("group-a".to_string());
680+
let mut third = workspace("gamma", None);
681+
third.settings.group_id = Some("group-a".to_string());
682+
683+
let mut items = vec![first, second, third];
684+
sort_workspaces(&mut items);
685+
686+
let names: Vec<_> = items.into_iter().map(|item| item.name).collect();
687+
assert_eq!(names, vec!["alpha", "beta", "gamma"]);
688+
}
689+
690+
#[test]
691+
fn sort_workspaces_breaks_ties_by_id() {
692+
let mut items = vec![
693+
workspace_with_id_and_kind("alpha", "b-id", Some(1), WorkspaceKind::Main),
694+
workspace_with_id_and_kind("alpha", "a-id", Some(1), WorkspaceKind::Main),
695+
];
696+
697+
sort_workspaces(&mut items);
698+
699+
let ids: Vec<_> = items.into_iter().map(|item| item.id).collect();
700+
assert_eq!(ids, vec!["a-id", "b-id"]);
701+
}
702+
703+
#[test]
704+
fn sort_workspaces_does_not_bias_kind() {
705+
let mut items = vec![
706+
workspace_with_id_and_kind("main", "main", Some(2), WorkspaceKind::Main),
707+
workspace_with_id_and_kind("worktree", "worktree", Some(1), WorkspaceKind::Worktree),
708+
];
709+
710+
sort_workspaces(&mut items);
711+
712+
let kinds: Vec<_> = items.into_iter().map(|item| item.kind).collect();
713+
assert!(matches!(
714+
kinds.as_slice(),
715+
[WorkspaceKind::Worktree, WorkspaceKind::Main]
716+
));
717+
}
718+
719+
#[test]
720+
fn update_workspace_settings_persists_sort_and_group() {
721+
let id = "workspace-1".to_string();
722+
let entry = WorkspaceEntry {
723+
id: id.clone(),
724+
name: "Workspace".to_string(),
725+
path: "/tmp".to_string(),
726+
codex_bin: None,
727+
kind: WorkspaceKind::Main,
728+
parent_id: None,
729+
worktree: None,
730+
settings: WorkspaceSettings::default(),
731+
};
732+
let mut workspaces = HashMap::from([(id.clone(), entry)]);
733+
734+
let mut settings = WorkspaceSettings::default();
735+
settings.sort_order = Some(3);
736+
settings.group_id = Some("group-1".to_string());
737+
settings.sidebar_collapsed = true;
738+
settings.git_root = Some("/tmp".to_string());
739+
740+
let updated =
741+
apply_workspace_settings_update(&mut workspaces, &id, settings.clone()).expect("update");
742+
assert_eq!(updated.settings.sort_order, Some(3));
743+
assert_eq!(updated.settings.group_id.as_deref(), Some("group-1"));
744+
assert!(updated.settings.sidebar_collapsed);
745+
assert_eq!(updated.settings.git_root.as_deref(), Some("/tmp"));
746+
747+
let temp_dir = std::env::temp_dir()
748+
.join(format!("codex-monitor-test-{}", Uuid::new_v4()));
749+
std::fs::create_dir_all(&temp_dir).expect("create temp dir");
750+
let path = PathBuf::from(temp_dir.join("workspaces.json"));
751+
let list: Vec<_> = workspaces.values().cloned().collect();
752+
write_workspaces(&path, &list).expect("write workspaces");
753+
754+
let read = read_workspaces(&path).expect("read workspaces");
755+
let stored = read.get(&id).expect("stored workspace");
756+
assert_eq!(stored.settings.sort_order, Some(3));
757+
assert_eq!(stored.settings.group_id.as_deref(), Some("group-1"));
758+
assert!(stored.settings.sidebar_collapsed);
759+
assert_eq!(stored.settings.git_root.as_deref(), Some("/tmp"));
760+
}
616761
}

0 commit comments

Comments
 (0)