use super::*; use editor::Editor; use gpui::{TestAppContext, VisualTestContext}; use menu::SelectPrev; use project::{Project, ProjectPath}; use serde_json::json; use std::path::Path; use workspace::{AppState, Workspace}; #[ctor::ctor] fn init_logger() { if std::env::var("RUST_LOG").is_ok() { env_logger::init(); } } #[gpui::test] async fn test_open_with_prev_tab_selected_and_cycle_on_toggle_action( cx: &mut gpui::TestAppContext, ) { let app_state = init_test(cx); app_state .fs .as_fake() .insert_tree( "/root", json!({ "1.txt": "First file", "2.txt": "Second file", "3.txt": "Third file", "4.txt": "Fourth file", }), ) .await; let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx)); let tab_1 = open_buffer("1.txt", &workspace, cx).await; let tab_2 = open_buffer("2.txt", &workspace, cx).await; let tab_3 = open_buffer("3.txt", &workspace, cx).await; let tab_4 = open_buffer("4.txt", &workspace, cx).await; // Starts with the previously opened item selected let tab_switcher = open_tab_switcher(false, &workspace, cx); tab_switcher.update(cx, |tab_switcher, _| { assert_eq!(tab_switcher.delegate.matches.len(), 4); assert_match_at_position(tab_switcher, 0, tab_4.boxed_clone()); assert_match_selection(tab_switcher, 1, tab_3.boxed_clone()); assert_match_at_position(tab_switcher, 2, tab_2.boxed_clone()); assert_match_at_position(tab_switcher, 3, tab_1.boxed_clone()); }); cx.dispatch_action(Toggle { select_last: false }); cx.dispatch_action(Toggle { select_last: false }); tab_switcher.update(cx, |tab_switcher, _| { assert_eq!(tab_switcher.delegate.matches.len(), 4); assert_match_at_position(tab_switcher, 0, tab_4.boxed_clone()); assert_match_at_position(tab_switcher, 1, tab_3.boxed_clone()); assert_match_at_position(tab_switcher, 2, tab_2.boxed_clone()); assert_match_selection(tab_switcher, 3, tab_1.boxed_clone()); }); cx.dispatch_action(SelectPrev); tab_switcher.update(cx, |tab_switcher, _| { assert_eq!(tab_switcher.delegate.matches.len(), 4); assert_match_at_position(tab_switcher, 0, tab_4.boxed_clone()); assert_match_at_position(tab_switcher, 1, tab_3.boxed_clone()); assert_match_selection(tab_switcher, 2, tab_2.boxed_clone()); assert_match_at_position(tab_switcher, 3, tab_1.boxed_clone()); }); } #[gpui::test] async fn test_open_with_last_tab_selected(cx: &mut gpui::TestAppContext) { let app_state = init_test(cx); app_state .fs .as_fake() .insert_tree( "/root", json!({ "1.txt": "First file", "2.txt": "Second file", "3.txt": "Third file", }), ) .await; let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx)); let tab_1 = open_buffer("1.txt", &workspace, cx).await; let tab_2 = open_buffer("2.txt", &workspace, cx).await; let tab_3 = open_buffer("3.txt", &workspace, cx).await; // Starts with the last item selected let tab_switcher = open_tab_switcher(true, &workspace, cx); tab_switcher.update(cx, |tab_switcher, _| { assert_eq!(tab_switcher.delegate.matches.len(), 3); assert_match_at_position(tab_switcher, 0, tab_3); assert_match_at_position(tab_switcher, 1, tab_2); assert_match_selection(tab_switcher, 2, tab_1); }); } #[gpui::test] async fn test_open_item_on_modifiers_release(cx: &mut gpui::TestAppContext) { let app_state = init_test(cx); app_state .fs .as_fake() .insert_tree( "/root", json!({ "1.txt": "First file", "2.txt": "Second file", }), ) .await; let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx)); let tab_1 = open_buffer("1.txt", &workspace, cx).await; let tab_2 = open_buffer("2.txt", &workspace, cx).await; cx.simulate_modifiers_change(Modifiers::control()); let tab_switcher = open_tab_switcher(false, &workspace, cx); tab_switcher.update(cx, |tab_switcher, _| { assert_eq!(tab_switcher.delegate.matches.len(), 2); assert_match_at_position(tab_switcher, 0, tab_2.boxed_clone()); assert_match_selection(tab_switcher, 1, tab_1.boxed_clone()); }); cx.simulate_modifiers_change(Modifiers::none()); cx.read(|cx| { let active_editor = workspace.read(cx).active_item_as::(cx).unwrap(); assert_eq!(active_editor.read(cx).title(cx), "1.txt"); }); assert_tab_switcher_is_closed(workspace, cx); } #[gpui::test] async fn test_open_on_empty_pane(cx: &mut gpui::TestAppContext) { let app_state = init_test(cx); app_state.fs.as_fake().insert_tree("/root", json!({})).await; let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx)); cx.simulate_modifiers_change(Modifiers::control()); let tab_switcher = open_tab_switcher(false, &workspace, cx); tab_switcher.update(cx, |tab_switcher, _| { assert!(tab_switcher.delegate.matches.is_empty()); }); cx.simulate_modifiers_change(Modifiers::none()); assert_tab_switcher_is_closed(workspace, cx); } #[gpui::test] async fn test_open_with_single_item(cx: &mut gpui::TestAppContext) { let app_state = init_test(cx); app_state .fs .as_fake() .insert_tree("/root", json!({"1.txt": "Single file"})) .await; let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx)); let tab = open_buffer("1.txt", &workspace, cx).await; let tab_switcher = open_tab_switcher(false, &workspace, cx); tab_switcher.update(cx, |tab_switcher, _| { assert_eq!(tab_switcher.delegate.matches.len(), 1); assert_match_selection(tab_switcher, 0, tab); }); } #[gpui::test] async fn test_close_selected_item(cx: &mut gpui::TestAppContext) { let app_state = init_test(cx); app_state .fs .as_fake() .insert_tree( "/root", json!({ "1.txt": "First file", "2.txt": "Second file", }), ) .await; let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx)); let tab_1 = open_buffer("1.txt", &workspace, cx).await; let tab_2 = open_buffer("2.txt", &workspace, cx).await; cx.simulate_modifiers_change(Modifiers::control()); let tab_switcher = open_tab_switcher(false, &workspace, cx); tab_switcher.update(cx, |tab_switcher, _| { assert_eq!(tab_switcher.delegate.matches.len(), 2); assert_match_at_position(tab_switcher, 0, tab_2.boxed_clone()); assert_match_selection(tab_switcher, 1, tab_1.boxed_clone()); }); cx.simulate_modifiers_change(Modifiers::control()); cx.dispatch_action(CloseSelectedItem); tab_switcher.update(cx, |tab_switcher, _| { assert_eq!(tab_switcher.delegate.matches.len(), 1); assert_match_selection(tab_switcher, 0, tab_2); }); // Still switches tab on modifiers release cx.simulate_modifiers_change(Modifiers::none()); cx.read(|cx| { let active_editor = workspace.read(cx).active_item_as::(cx).unwrap(); assert_eq!(active_editor.read(cx).title(cx), "2.txt"); }); assert_tab_switcher_is_closed(workspace, cx); } fn init_test(cx: &mut TestAppContext) -> Arc { cx.update(|cx| { let state = AppState::test(cx); theme::init(theme::LoadThemes::JustBase, cx); language::init(cx); super::init(cx); editor::init(cx); workspace::init_settings(cx); Project::init_settings(cx); state }) } #[track_caller] fn open_tab_switcher( select_last: bool, workspace: &View, cx: &mut VisualTestContext, ) -> View> { cx.dispatch_action(Toggle { select_last }); get_active_tab_switcher(workspace, cx) } #[track_caller] fn get_active_tab_switcher( workspace: &View, cx: &mut VisualTestContext, ) -> View> { workspace.update(cx, |workspace, cx| { workspace .active_modal::(cx) .expect("tab switcher is not open") .read(cx) .picker .clone() }) } async fn open_buffer( file_path: &str, workspace: &View, cx: &mut gpui::VisualTestContext, ) -> Box { let project = workspace.update(cx, |workspace, _| workspace.project().clone()); let worktree_id = project.update(cx, |project, cx| { let worktree = project.worktrees(cx).last().expect("worktree not found"); worktree.read(cx).id() }); let project_path = ProjectPath { worktree_id, path: Arc::from(Path::new(file_path)), }; workspace .update(cx, move |workspace, cx| { workspace.open_path(project_path, None, true, cx) }) .await .unwrap() } #[track_caller] fn assert_match_selection( tab_switcher: &Picker, expected_selection_index: usize, expected_item: Box, ) { assert_eq!( tab_switcher.delegate.selected_index(), expected_selection_index, "item is not selected" ); assert_match_at_position(tab_switcher, expected_selection_index, expected_item); } #[track_caller] fn assert_match_at_position( tab_switcher: &Picker, match_index: usize, expected_item: Box, ) { let match_item = tab_switcher .delegate .matches .get(match_index) .unwrap_or_else(|| panic!("Tab Switcher has no match for index {match_index}")); assert_eq!(match_item.item.item_id(), expected_item.item_id()); } #[track_caller] fn assert_tab_switcher_is_closed(workspace: View, cx: &mut VisualTestContext) { workspace.update(cx, |workspace, cx| { assert!( workspace.active_modal::(cx).is_none(), "tab switcher is still open" ); }); }