Files
galaxy/crates/integration/src/test/pane_restoration.rs
T

467 lines
22 KiB
Rust

//! Integration tests for pane restoration functionality.
//! Tests the ability to restore closed panes using cmd+shift+t.
use super::{new_builder, Builder};
use std::{collections::HashMap, time::Duration};
use warp::{
cmd_or_ctrl_shift,
features::FeatureFlag,
integration_testing::{
pane_group::assert_focused_pane_index,
step::new_step_with_default_assertions,
terminal::{
execute_command, util::ExpectedExitStatus, validate_block_output_on_finished_block,
wait_until_bootstrapped_pane, wait_until_bootstrapped_single_pane_for_tab,
},
workspace::{assert_tab_count, trigger_undo_close},
},
};
/// Tests the basic pane restoration workflow:
/// 1. Split off a pane
/// 2. Run a simple command in it
/// 3. Close the pane
/// 4. Restore the pane with cmd+shift+t
/// 5. Assert the pane is restored in the correct location with previous state
pub fn test_restore_single_closed_pane() -> Builder {
FeatureFlag::UndoClosedPanes.set_enabled(true);
new_builder()
.with_step(wait_until_bootstrapped_single_pane_for_tab(0))
.with_step(
new_step_with_default_assertions("Split off a new pane to the right")
.with_keystrokes(&[cmd_or_ctrl_shift("d")])
.add_assertion(assert_focused_pane_index(0, 1)),
)
.with_step(wait_until_bootstrapped_pane(0, 1))
.with_step(execute_command(
0,
1,
"echo \"hello world\"".to_string(),
ExpectedExitStatus::Success,
(),
))
.with_step(
new_step_with_default_assertions("Close the pane")
.with_keystrokes(&[cmd_or_ctrl_shift("w")])
.add_assertion(assert_focused_pane_index(0, 0)),
)
.with_step(trigger_undo_close())
.with_step(
new_step_with_default_assertions("Verify we still have one tab after restore attempt")
.add_assertion(assert_tab_count(1)),
)
.with_step(
new_step_with_default_assertions("Verify pane was restored with correct state")
.set_pause_on_failure(std::time::Duration::from_secs(30))
.add_assertion(assert_focused_pane_index(0, 1))
.add_assertion(move |app, window_id| {
validate_block_output_on_finished_block(
&"hello world",
0, // tab index
1, // pane index - the restored pane should be at index 1
window_id,
app,
)
}),
)
}
/// Tests complex pane restoration workflow with multiple panes:
/// 1. Start with one pane, split twice to create 3 panes total
/// 2. Run unique commands in each pane
/// 3. Close two of the panes
/// 4. Restore both closed panes using undo close twice
/// 5. Assert both panes are restored correctly with their previous state
pub fn test_restore_multiple_closed_panes() -> Builder {
FeatureFlag::UndoClosedPanes.set_enabled(true);
new_builder()
.with_step(wait_until_bootstrapped_single_pane_for_tab(0))
.with_step(execute_command(
0,
0,
"echo \"pane0\"".to_string(),
ExpectedExitStatus::Success,
(),
))
.with_step(
new_step_with_default_assertions("Split off first new pane to the right")
.with_keystrokes(&[cmd_or_ctrl_shift("d")])
.add_assertion(assert_focused_pane_index(0, 1)),
)
.with_step(wait_until_bootstrapped_pane(0, 1))
.with_step(execute_command(
0,
1,
"echo \"pane1\"".to_string(),
ExpectedExitStatus::Success,
(),
))
.with_step(
new_step_with_default_assertions("Split off second new pane to the right")
.with_keystrokes(&[cmd_or_ctrl_shift("d")])
.add_assertion(assert_focused_pane_index(0, 2)),
)
.with_step(wait_until_bootstrapped_pane(0, 2))
.with_step(execute_command(
0,
2,
"echo \"pane2\"".to_string(),
ExpectedExitStatus::Success,
(),
))
.with_step(
new_step_with_default_assertions("Close pane 2")
.with_keystrokes(&[cmd_or_ctrl_shift("w")])
.add_assertion(assert_focused_pane_index(0, 1)),
)
.with_step(
new_step_with_default_assertions("Close pane 1")
.with_keystrokes(&[cmd_or_ctrl_shift("w")])
.add_assertion(assert_focused_pane_index(0, 0)),
)
.with_step(trigger_undo_close().add_assertion(assert_focused_pane_index(0, 1)))
.with_step(
new_step_with_default_assertions("Verify first restored pane has correct state")
.set_pause_on_failure(std::time::Duration::from_secs(30))
.add_assertion(move |app, window_id| {
validate_block_output_on_finished_block(
&"pane1", 0, // tab index
1, // pane index - first restored pane should be at index 1
window_id, app,
)
}),
)
.with_step(trigger_undo_close().add_assertion(assert_focused_pane_index(0, 2)))
.with_step(
new_step_with_default_assertions("Verify second restored pane has correct state")
.set_pause_on_failure(std::time::Duration::from_secs(30))
.add_assertion(move |app, window_id| {
validate_block_output_on_finished_block(
&"pane2", 0, // tab index
2, // pane index - second restored pane should be at index 2
window_id, app,
)
}),
)
.with_step(
new_step_with_default_assertions(
"Verify we still have one tab after all restore operations",
)
.add_assertion(assert_tab_count(1)),
)
.with_step(
new_step_with_default_assertions("Verify original pane (pane 0) still has its state")
.add_assertion(move |app, window_id| {
validate_block_output_on_finished_block(
&"pane0", 0, // tab index
0, // pane index - original pane should still be at index 0
window_id, app,
)
}),
)
}
/// Tests that panes are properly cleaned up after the grace period expires:
/// 1. Split off a pane and run a command in it
/// 2. Close the pane
/// 3. Wait for the grace period to expire (5 seconds in test)
/// 4. Attempt to restore the pane with cmd+shift+t
/// 5. Assert the pane is NOT restored because it was cleaned up
pub fn test_undo_close_grace_period_cleanup() -> Builder {
FeatureFlag::UndoClosedPanes.set_enabled(true);
new_builder()
.with_user_defaults(HashMap::from([(
"UndoCloseGracePeriod".to_owned(),
serde_json::to_string(&Duration::from_secs(5))
.expect("Duration should convert to JSON string"),
)]))
.with_step(wait_until_bootstrapped_single_pane_for_tab(0))
.with_step(
new_step_with_default_assertions("Split off a new pane to the right")
.with_keystrokes(&[cmd_or_ctrl_shift("d")])
.add_assertion(assert_focused_pane_index(0, 1)),
)
.with_step(wait_until_bootstrapped_pane(0, 1))
.with_step(execute_command(
0,
1,
"echo \"hello world\"".to_string(),
ExpectedExitStatus::Success,
(),
))
.with_step(
new_step_with_default_assertions("Close the pane")
.with_keystrokes(&[cmd_or_ctrl_shift("w")])
.add_assertion(assert_focused_pane_index(0, 0)),
)
.with_step(
new_step_with_default_assertions("Wait for grace period to expire")
.set_timeout(Duration::from_secs(7)), // Wait 7 seconds for 5 second grace period
)
.with_step(
new_step_with_default_assertions("Check pane count before undo close")
.add_assertion(move |app, window_id| {
let workspace_view = warp::integration_testing::view_getters::workspace_view(app, window_id);
let initial_pane_count = workspace_view.read(app, |workspace, ctx| {
let pane_group_view = workspace.get_pane_group_view(0).expect("should have tab 0");
pane_group_view.read(ctx, |pane_group, _| pane_group.pane_count())
});
warpui::async_assert_eq!(initial_pane_count, 1, "Should have exactly one pane after grace period expires - closed pane should be cleaned up")
}),
)
.with_step(trigger_undo_close()
.add_assertion(assert_focused_pane_index(0, 0)) // Should still be focused on original pane
.add_assertion(move |app, window_id| {
// Assert we still only have one pane (no restoration occurred)
let workspace_view = warp::integration_testing::view_getters::workspace_view(app, window_id);
workspace_view.read(app, |workspace, ctx| {
let pane_group_view = workspace.get_pane_group_view(0).expect("should have tab 0");
let pane_count = pane_group_view.read(ctx, |pane_group, _| pane_group.pane_count());
warpui::async_assert_eq!(pane_count, 1, "Should still have only one pane after attempted restore - no pane should be restored")
})
}),
)
.with_step(
new_step_with_default_assertions("Verify we still have one tab after failed restore attempt")
.add_assertion(assert_tab_count(1)),
)
}
/// Tests that closed panes are cleared when pane rearrangement operations begin:
/// 1. Create 3 panes and run commands in each
/// 2. Close one pane (it gets hidden for undo)
/// 3. Start a pane rearrangement operation (resize divider)
/// 4. Attempt to restore the closed pane with cmd+shift+t
/// 5. Assert the pane is NOT restored because it was cleared during rearrangement
pub fn test_closed_panes_cleared_on_rearrangement() -> Builder {
FeatureFlag::UndoClosedPanes.set_enabled(true);
new_builder()
.with_step(wait_until_bootstrapped_single_pane_for_tab(0))
.with_step(execute_command(
0,
0,
"echo \"original_pane\"".to_string(),
ExpectedExitStatus::Success,
(),
))
.with_step(
new_step_with_default_assertions("Split off first new pane to the right")
.with_keystrokes(&[cmd_or_ctrl_shift("d")])
.add_assertion(assert_focused_pane_index(0, 1)),
)
.with_step(wait_until_bootstrapped_pane(0, 1))
.with_step(execute_command(
0,
1,
"echo \"middle_pane\"".to_string(),
ExpectedExitStatus::Success,
(),
))
.with_step(
new_step_with_default_assertions("Split off second new pane to the right")
.with_keystrokes(&[cmd_or_ctrl_shift("d")])
.add_assertion(assert_focused_pane_index(0, 2)),
)
.with_step(wait_until_bootstrapped_pane(0, 2))
.with_step(execute_command(
0,
2,
"echo \"third_pane\"".to_string(),
ExpectedExitStatus::Success,
(),
))
.with_step(
// Close the middle pane by using a direct operation targeting pane index 1
warp::integration_testing::pane_group::close_pane_by_index(
0, // tab index
1, // pane index - the middle pane
),
)
.with_step(
new_step_with_default_assertions("Verify we have 2 visible panes after closing one")
.add_assertion(move |app, window_id| {
let workspace_view = warp::integration_testing::view_getters::workspace_view(app, window_id);
workspace_view.read(app, |workspace, ctx| {
let pane_group_view = workspace.get_pane_group_view(0).expect("should have tab 0");
let (visible_pane_count, total_pane_count) = pane_group_view.read(ctx, |pane_group, _| {
(pane_group.visible_pane_count(), pane_group.pane_count())
});
if visible_pane_count != 2 {
warpui::integration::AssertionOutcome::failure(format!("Should have 2 visible panes after closing one (got {visible_pane_count} visible, {total_pane_count} total)"))
} else {
warpui::integration::AssertionOutcome::Success
}
})
}),
)
.with_step(
// Trigger pane rearrangement by moving panes
warp::integration_testing::pane_group::move_pane_by_indices(
0,
0,
1,
warp::pane_group::tree::Direction::Right,
),
)
.with_step(
// Trigger undo close - should NOT restore the pane since rearrangement cleared it
trigger_undo_close()
)
.with_step(
new_step_with_default_assertions("Verify pane was NOT restored - still have same visible panes")
.add_assertion(move |app, window_id| {
let workspace_view = warp::integration_testing::view_getters::workspace_view(app, window_id);
workspace_view.read(app, |workspace, ctx| {
let pane_group_view = workspace.get_pane_group_view(0).expect("should have tab 0");
let visible_pane_count = pane_group_view.read(ctx, |pane_group, _| pane_group.visible_pane_count());
// After rearrangement, we should still have the same visible panes (no restoration)
// The exact count might vary based on how the move operation affects the layout
if visible_pane_count < 1 {
warpui::integration::AssertionOutcome::failure(format!("Should have at least 1 visible pane after undo close attempt, got {visible_pane_count}"))
} else {
warpui::integration::AssertionOutcome::Success
}
})
}),
)
.with_step(
trigger_undo_close().add_assertion(move |app, window_id| {
let workspace_view = warp::integration_testing::view_getters::workspace_view(app, window_id);
workspace_view.read(app, |workspace, ctx| {
let pane_group_view = workspace.get_pane_group_view(0).expect("should have tab 0");
let visible_pane_count = pane_group_view.read(ctx, |pane_group, _| pane_group.visible_pane_count());
// Should still have the same panes, no restoration should occur
if visible_pane_count < 1 {
warpui::integration::AssertionOutcome::failure(format!("Should have at least 1 visible pane after second undo close attempt, got {visible_pane_count}"))
} else {
warpui::integration::AssertionOutcome::Success
}
})
})
)
.with_step(
new_step_with_default_assertions("Verify remaining pane has expected state")
.add_assertion(move |app, window_id| {
let workspace_view = warp::integration_testing::view_getters::workspace_view(app, window_id);
let visible_pane_count = workspace_view.read(app, |workspace, ctx| {
let pane_group_view = workspace.get_pane_group_view(0).expect("should have tab 0");
pane_group_view.read(ctx, |pane_group, _| pane_group.visible_pane_count())
});
if visible_pane_count == 1 {
// If we have 1 pane, it should be the third_pane
validate_block_output_on_finished_block(
&"third_pane",
0, // tab index
0, // pane index - only remaining pane
window_id,
app,
)
} else if visible_pane_count == 2 {
// If we have 2 panes, check for both original and third
let original_result = validate_block_output_on_finished_block(
&"original_pane",
0, 1, window_id, app,
);
let third_result = validate_block_output_on_finished_block(
&"third_pane",
0, 0, window_id, app,
);
match (original_result, third_result) {
(warpui::integration::AssertionOutcome::Success, warpui::integration::AssertionOutcome::Success) => {
warpui::integration::AssertionOutcome::Success
}
_ => warpui::integration::AssertionOutcome::failure("Expected to find both original_pane and third_pane content".to_string())
}
} else {
warpui::integration::AssertionOutcome::failure(format!("Unexpected pane count: {visible_pane_count}"))
}
}),
)
}
/// Tests that closing the last visible pane in a tab properly closes the tab:
/// 1. Create a single pane in a tab with a command
/// 2. Create a new tab to verify multiple tabs exist
/// 3. Return to first tab and close its only pane
/// 4. Verify the tab is closed (not just showing empty)
/// 5. Restore the pane and verify it creates a new tab
pub fn test_tab_closes_when_last_visible_pane_closed() -> Builder {
FeatureFlag::UndoClosedPanes.set_enabled(true);
new_builder()
.with_step(wait_until_bootstrapped_single_pane_for_tab(0))
.with_step(execute_command(
0,
0,
"echo \"first_tab_content\"".to_string(),
ExpectedExitStatus::Success,
(),
))
.with_step(
new_step_with_default_assertions("Create a new tab")
.with_keystrokes(&[cmd_or_ctrl_shift("t")])
.add_assertion(assert_tab_count(2)),
)
.with_step(wait_until_bootstrapped_single_pane_for_tab(1))
.with_step(execute_command(
1,
0,
"echo \"second_tab_content\"".to_string(),
ExpectedExitStatus::Success,
(),
))
.with_step(
new_step_with_default_assertions("Switch back to first tab")
.with_keystrokes(&["cmdorctrl-1"])
.add_assertion(assert_focused_pane_index(0, 0)),
)
.with_step(
new_step_with_default_assertions("Close the only pane in first tab")
.with_keystrokes(&[cmd_or_ctrl_shift("w")])
.set_timeout(Duration::from_secs(10)) // Allow time for tab closure
.add_assertion(assert_tab_count(1)) // Tab should be closed, leaving only one tab
.add_assertion(assert_focused_pane_index(0, 0)), // Should be focused on the remaining tab (previously tab 1)
)
.with_step(
new_step_with_default_assertions("Verify we're now on the second tab's content")
.add_assertion(move |app, window_id| {
validate_block_output_on_finished_block(
&"second_tab_content",
0, // tab index - this is now the first (and only) tab
0, // pane index
window_id,
app,
)
}),
)
.with_step(
trigger_undo_close()
.set_timeout(Duration::from_secs(15)) // Allow extra time for tab restoration
.add_assertion(assert_tab_count(2)), // Should now have 2 tabs again
)
.with_step(
new_step_with_default_assertions("Wait for restored tab to be ready")
.set_timeout(Duration::from_secs(10)), // Allow time for tab setup
)
.with_step(
new_step_with_default_assertions("Verify restored pane has correct content in new tab")
.set_timeout(Duration::from_secs(20)) // Allow extra time for content validation
.set_pause_on_failure(std::time::Duration::from_secs(30))
.add_assertion(move |app, window_id| {
let workspace_view =
warp::integration_testing::view_getters::workspace_view(app, window_id);
workspace_view.read(app, |workspace, _ctx| {
let focused_tab_idx = workspace.active_tab_index();
// The restored tab should be the currently focused tab (which should have first_tab_content)
validate_block_output_on_finished_block(
&"first_tab_content",
focused_tab_idx, // Use the currently focused tab
0, // pane index
window_id,
app,
)
})
}),
)
}