diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-04-08 17:24:33 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-04-08 17:24:33 +0300 |
| commit | 3689515f6a11d0f7635067de2f964b63288c645b (patch) | |
| tree | 640baa1e16c1c5c88550df5d5a0a219c688de947 /src | |
| parent | 46f9651dfa78044c7ccc7bc2b9884194ffa22dc8 (diff) | |
test: strengthen session snapshot capture contract
Diffstat (limited to 'src')
| -rw-r--r-- | src/app/input.rs | 95 | ||||
| -rw-r--r-- | src/persist.rs | 193 |
2 files changed, 288 insertions, 0 deletions
diff --git a/src/app/input.rs b/src/app/input.rs index 56dc97c9..b9ddc890 100644 --- a/src/app/input.rs +++ b/src/app/input.rs @@ -3038,6 +3038,24 @@ mod tests { } } + fn capture_snapshot(state: &AppState) -> crate::persist::SessionSnapshot { + crate::persist::capture( + &state.workspaces, + state.active, + state.selected, + state.agent_panel_scope, + state.sidebar_width, + state.sidebar_section_split, + ) + } + + fn root_layout_ratio(snapshot: &crate::persist::SessionSnapshot) -> Option<f32> { + match &snapshot.workspaces.first()?.tabs.first()?.layout { + crate::persist::LayoutSnapshot::Split { ratio, .. } => Some(*ratio), + crate::persist::LayoutSnapshot::Pane(_) => None, + } + } + #[test] fn custom_rename_key_enters_rename_mode() { let mut state = state_with_workspaces(&["test"]); @@ -3270,6 +3288,11 @@ mod tests { ); assert_eq!(state.mode, Mode::Terminal); assert_eq!(state.workspaces[0].display_name(), "renamed"); + let snapshot = capture_snapshot(&state); + assert_eq!( + snapshot.workspaces[0].custom_name.as_deref(), + Some("renamed") + ); state.view.sidebar_rect = Rect::new(0, 0, 26, 20); state.view.terminal_area = Rect::new(26, 0, 80, 20); @@ -3282,6 +3305,24 @@ mod tests { } #[test] + fn tab_rename_updates_captured_snapshot() { + let mut state = state_with_workspaces(&["test"]); + state.mode = Mode::RenameTab; + state.name_input = "logs".into(); + + handle_rename_key( + &mut state, + KeyEvent::new(KeyCode::Enter, KeyModifiers::empty()), + ); + + let snapshot = capture_snapshot(&state); + assert_eq!( + snapshot.workspaces[0].tabs[0].custom_name.as_deref(), + Some("logs") + ); + } + + #[test] fn rename_cancel_returns_to_terminal_when_workspace_is_active() { let mut state = state_with_workspaces(&["test"]); state.mode = Mode::RenameTab; @@ -3691,6 +3732,12 @@ mod tests { second_pane ); assert_eq!(app.state.mode, Mode::Terminal); + let snapshot = capture_snapshot(&app.state); + assert_eq!(snapshot.workspaces[0].active_tab, second_tab); + assert_eq!( + snapshot.workspaces[0].tabs[second_tab].focused, + Some(second_pane.raw()) + ); } #[test] @@ -3715,6 +3762,8 @@ mod tests { assert_eq!(app.state.agent_panel_scope, AgentPanelScope::AllWorkspaces); assert_eq!(app.state.agent_panel_scroll, 0); + let snapshot = capture_snapshot(&app.state); + assert_eq!(snapshot.agent_panel_scope, AgentPanelScope::AllWorkspaces); } #[test] @@ -3936,6 +3985,9 @@ mod tests { assert_eq!(app.state.active, Some(1)); assert_eq!(app.state.selected, 1); assert!(app.state.workspace_press.is_none()); + let snapshot = capture_snapshot(&app.state); + assert_eq!(snapshot.active, Some(1)); + assert_eq!(snapshot.selected, 1); } #[test] @@ -3989,6 +4041,13 @@ mod tests { assert_eq!(app.state.selected, 2); assert_eq!(app.state.workspaces[0].id, active_id); assert_eq!(app.state.workspaces[2].id, selected_id); + let snapshot = capture_snapshot(&app.state); + let captured_names: Vec<_> = snapshot + .workspaces + .iter() + .map(|ws| ws.custom_name.clone().unwrap()) + .collect(); + assert_eq!(captured_names, vec!["b", "a", "c"]); } #[test] @@ -4034,6 +4093,8 @@ mod tests { app.handle_mouse(mouse(MouseEventKind::Drag(MouseButton::Left), 30, 5)); assert_eq!(app.state.sidebar_width, 31); + let snapshot = capture_snapshot(&app.state); + assert_eq!(snapshot.sidebar_width, Some(31)); } #[test] @@ -4056,6 +4117,38 @@ mod tests { )); assert!(app.state.sidebar_section_split > 0.5); + let snapshot = capture_snapshot(&app.state); + assert_eq!( + snapshot.sidebar_section_split, + Some(app.state.sidebar_section_split) + ); + } + + #[test] + fn dragging_pane_split_updates_captured_layout_ratio() { + let mut app = app_for_mouse_test(); + app.state.workspaces = vec![Workspace::test_new("test")]; + app.state.active = Some(0); + app.state.selected = 0; + app.state.workspaces[0].test_split(Direction::Horizontal); + crate::ui::compute_view(&mut app.state, Rect::new(0, 0, 106, 20)); + let border = app.state.view.split_borders[0].clone(); + let before = capture_snapshot(&app.state); + let drag_row = border.area.y.saturating_add(1); + + app.handle_mouse(mouse( + MouseEventKind::Down(MouseButton::Left), + border.pos, + drag_row, + )); + app.handle_mouse(mouse( + MouseEventKind::Drag(MouseButton::Left), + border.pos.saturating_add(6), + drag_row, + )); + + let after = capture_snapshot(&app.state); + assert_ne!(root_layout_ratio(&before), root_layout_ratio(&after)); } #[test] @@ -4070,6 +4163,8 @@ mod tests { assert_eq!(app.state.sidebar_width, 26); assert!(app.state.drag.is_none()); + let snapshot = capture_snapshot(&app.state); + assert_eq!(snapshot.sidebar_width, Some(26)); } #[test] diff --git a/src/persist.rs b/src/persist.rs index 06c5af61..ccb44ace 100644 --- a/src/persist.rs +++ b/src/persist.rs @@ -564,6 +564,11 @@ pub fn load() -> Option<SessionSnapshot> { #[cfg(test)] mod tests { use super::*; + use ratatui::layout::Rect; + + use crate::app::{state::AgentPanelScope, AppState, Mode}; + use crate::layout::NavDirection; + use crate::workspace::Workspace; fn session_fixture(name: &str) -> &'static str { match name { @@ -785,6 +790,194 @@ mod tests { assert_eq!(unique.len(), 3); } + fn state_with_workspaces(names: &[&str]) -> AppState { + let mut state = AppState::test_new(); + state.workspaces = names.iter().map(|name| Workspace::test_new(name)).collect(); + if !state.workspaces.is_empty() { + state.active = Some(0); + state.selected = 0; + state.mode = Mode::Terminal; + } + state + } + + fn capture_from_state(state: &AppState) -> SessionSnapshot { + capture( + &state.workspaces, + state.active, + state.selected, + state.agent_panel_scope, + state.sidebar_width, + state.sidebar_section_split, + ) + } + + fn root_split_ratio(tab: &TabSnapshot) -> Option<f32> { + match &tab.layout { + LayoutSnapshot::Split { ratio, .. } => Some(*ratio), + LayoutSnapshot::Pane(_) => None, + } + } + + #[test] + fn capture_contract_tracks_workspace_order_active_and_selected() { + let mut state = state_with_workspaces(&["a", "b", "c"]); + state.active = Some(1); + state.selected = 2; + + state.move_workspace(1, 0); + + let snapshot = capture_from_state(&state); + let ids: Vec<_> = state.workspaces.iter().map(|ws| ws.id.clone()).collect(); + let captured_ids: Vec<_> = snapshot + .workspaces + .iter() + .map(|ws| ws.id.clone().unwrap()) + .collect(); + assert_eq!(captured_ids, ids); + assert_eq!(snapshot.active, state.active); + assert_eq!(snapshot.selected, state.selected); + } + + #[test] + fn capture_contract_tracks_workspace_and_tab_names_and_active_tab() { + let mut state = state_with_workspaces(&["one"]); + state.workspaces[0].set_custom_name("renamed-workspace".into()); + let second_tab = state.workspaces[0].test_add_tab(Some("logs")); + state.workspaces[0].switch_tab(second_tab); + state.workspaces[0].tabs[0].set_custom_name("main".into()); + + let snapshot = capture_from_state(&state); + let workspace = &snapshot.workspaces[0]; + assert_eq!(workspace.custom_name.as_deref(), Some("renamed-workspace")); + assert_eq!(workspace.active_tab, second_tab); + assert_eq!(workspace.tabs[0].custom_name.as_deref(), Some("main")); + assert_eq!(workspace.tabs[1].custom_name.as_deref(), Some("logs")); + } + + #[test] + fn capture_contract_tracks_workspace_closure() { + let mut state = state_with_workspaces(&["one", "two"]); + state.selected = 1; + state.active = Some(1); + + state.close_selected_workspace(); + + let snapshot = capture_from_state(&state); + assert_eq!(snapshot.workspaces.len(), 1); + assert_eq!(snapshot.workspaces[0].custom_name.as_deref(), Some("one")); + assert_eq!(snapshot.active, Some(0)); + assert_eq!(snapshot.selected, 0); + } + + #[test] + fn capture_contract_tracks_sidebar_state() { + let mut state = state_with_workspaces(&["one"]); + state.sidebar_width = 31; + state.sidebar_section_split = 0.4; + state.agent_panel_scope = AgentPanelScope::AllWorkspaces; + + let snapshot = capture_from_state(&state); + assert_eq!(snapshot.sidebar_width, Some(31)); + assert_eq!(snapshot.sidebar_section_split, Some(0.4)); + assert_eq!(snapshot.agent_panel_scope, AgentPanelScope::AllWorkspaces); + } + + #[test] + fn capture_contract_tracks_layout_focus_zoom_and_root_pane() { + let mut state = state_with_workspaces(&["one"]); + let root = state.workspaces[0].tabs[0].root_pane; + let second = state.workspaces[0].test_split(Direction::Horizontal); + state.workspaces[0].tabs[0].layout.focus_pane(second); + state.toggle_fullscreen(); + + let snapshot = capture_from_state(&state); + let tab = &snapshot.workspaces[0].tabs[0]; + assert!(matches!(tab.layout, LayoutSnapshot::Split { .. })); + assert_eq!(tab.focused, Some(second.raw())); + assert_eq!(tab.root_pane, Some(root.raw())); + assert!(tab.zoomed); + assert_eq!(tab.panes.len(), 2); + } + + #[test] + fn capture_contract_tracks_focus_navigation() { + let mut state = state_with_workspaces(&["one"]); + let root = state.workspaces[0].tabs[0].root_pane; + let second = state.workspaces[0].test_split(Direction::Horizontal); + crate::ui::compute_view(&mut state, Rect::new(0, 0, 106, 20)); + + state.navigate_pane(NavDirection::Right); + + let snapshot = capture_from_state(&state); + assert_eq!(snapshot.workspaces[0].tabs[0].focused, Some(second.raw())); + assert_ne!(snapshot.workspaces[0].tabs[0].focused, Some(root.raw())); + } + + #[test] + fn capture_contract_tracks_resize_ratio_changes() { + let mut state = state_with_workspaces(&["one"]); + state.workspaces[0].test_split(Direction::Horizontal); + crate::ui::compute_view(&mut state, Rect::new(0, 0, 106, 20)); + let before = capture_from_state(&state); + + state.resize_pane(NavDirection::Right); + + let after = capture_from_state(&state); + let before_ratio = root_split_ratio(&before.workspaces[0].tabs[0]).unwrap(); + let after_ratio = root_split_ratio(&after.workspaces[0].tabs[0]).unwrap(); + assert_ne!(before_ratio, after_ratio); + } + + #[test] + fn capture_contract_tracks_tab_closure() { + let mut state = state_with_workspaces(&["one"]); + let second_tab = state.workspaces[0].test_add_tab(Some("logs")); + state.switch_tab(second_tab); + + state.close_tab(); + + let snapshot = capture_from_state(&state); + let workspace = &snapshot.workspaces[0]; + assert_eq!(workspace.tabs.len(), 1); + assert_eq!(workspace.active_tab, 0); + assert!(workspace.tabs[0].custom_name.is_none()); + } + + #[test] + fn capture_contract_tracks_pane_closure() { + let mut state = state_with_workspaces(&["one"]); + state.workspaces[0].test_split(Direction::Horizontal); + + state.close_pane(); + + let snapshot = capture_from_state(&state); + let tab = &snapshot.workspaces[0].tabs[0]; + assert_eq!(tab.panes.len(), 1); + assert!(matches!(tab.layout, LayoutSnapshot::Pane(_))); + assert!(!tab.zoomed); + } + + #[test] + fn capture_contract_tracks_workspace_identity_and_pane_cwds() { + let mut state = state_with_workspaces(&["one"]); + let root = state.workspaces[0].tabs[0].root_pane; + state.workspaces[0].tabs[0] + .pane_cwds + .insert(root, PathBuf::from("/tmp/pion")); + let second = state.workspaces[0].test_split(Direction::Horizontal); + state.workspaces[0].tabs[0] + .pane_cwds + .insert(second, PathBuf::from("/tmp/herdr")); + + let snapshot = capture_from_state(&state); + let workspace = &snapshot.workspaces[0]; + let tab = &workspace.tabs[0]; + assert_eq!(workspace.identity_cwd, PathBuf::from("/tmp/pion")); + assert_eq!(tab.panes[&root.raw()].cwd, PathBuf::from("/tmp/pion")); + assert_eq!(tab.panes[&second.raw()].cwd, PathBuf::from("/tmp/herdr")); + } + #[test] fn old_unversioned_snapshot_loads_as_version_0() { // Simulate a snapshot from before versioning was added |
