diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 3a7548afeff4e3aa8fd224772f7a18aa629a3b2b..c60abc187a001997ffead3cfef251c5db6e74dac 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -7826,7 +7826,7 @@ mod tests { #[gpui::test] async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) { - let mut cx = EditorTestContext::new(cx).await; + let mut cx = EditorTestContext::new(cx); cx.set_state("one «two threeˇ» four"); cx.update_editor(|editor, cx| { editor.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx); @@ -7974,7 +7974,7 @@ mod tests { #[gpui::test] async fn test_newline_below(cx: &mut gpui::TestAppContext) { - let mut cx = EditorTestContext::new(cx).await; + let mut cx = EditorTestContext::new(cx); cx.update(|cx| { cx.update_global::(|settings, _| { settings.editor_overrides.tab_size = Some(NonZeroU32::new(4).unwrap()); @@ -8050,7 +8050,7 @@ mod tests { #[gpui::test] async fn test_tab(cx: &mut gpui::TestAppContext) { - let mut cx = EditorTestContext::new(cx).await; + let mut cx = EditorTestContext::new(cx); cx.update(|cx| { cx.update_global::(|settings, _| { settings.editor_overrides.tab_size = Some(NonZeroU32::new(3).unwrap()); @@ -8081,7 +8081,7 @@ mod tests { #[gpui::test] async fn test_tab_on_blank_line_auto_indents(cx: &mut gpui::TestAppContext) { - let mut cx = EditorTestContext::new(cx).await; + let mut cx = EditorTestContext::new(cx); let language = Arc::new( Language::new( LanguageConfig::default(), @@ -8139,7 +8139,7 @@ mod tests { #[gpui::test] async fn test_indent_outdent(cx: &mut gpui::TestAppContext) { - let mut cx = EditorTestContext::new(cx).await; + let mut cx = EditorTestContext::new(cx); cx.set_state(indoc! {" «oneˇ» «twoˇ» @@ -8208,7 +8208,7 @@ mod tests { #[gpui::test] async fn test_indent_outdent_with_hard_tabs(cx: &mut gpui::TestAppContext) { - let mut cx = EditorTestContext::new(cx).await; + let mut cx = EditorTestContext::new(cx); cx.update(|cx| { cx.update_global::(|settings, _| { settings.editor_overrides.hard_tabs = Some(true); @@ -8416,7 +8416,7 @@ mod tests { #[gpui::test] async fn test_backspace(cx: &mut gpui::TestAppContext) { - let mut cx = EditorTestContext::new(cx).await; + let mut cx = EditorTestContext::new(cx); // Basic backspace cx.set_state(indoc! {" @@ -8463,7 +8463,7 @@ mod tests { #[gpui::test] async fn test_delete(cx: &mut gpui::TestAppContext) { - let mut cx = EditorTestContext::new(cx).await; + let mut cx = EditorTestContext::new(cx); cx.set_state(indoc! {" onˇe two three @@ -8800,7 +8800,7 @@ mod tests { #[gpui::test] async fn test_clipboard(cx: &mut gpui::TestAppContext) { - let mut cx = EditorTestContext::new(cx).await; + let mut cx = EditorTestContext::new(cx); cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six "); cx.update_editor(|e, cx| e.cut(&Cut, cx)); @@ -8876,7 +8876,7 @@ mod tests { #[gpui::test] async fn test_paste_multiline(cx: &mut gpui::TestAppContext) { - let mut cx = EditorTestContext::new(cx).await; + let mut cx = EditorTestContext::new(cx); let language = Arc::new(Language::new( LanguageConfig::default(), Some(tree_sitter_rust::language()), @@ -9305,7 +9305,7 @@ mod tests { #[gpui::test] async fn test_select_next(cx: &mut gpui::TestAppContext) { - let mut cx = EditorTestContext::new(cx).await; + let mut cx = EditorTestContext::new(cx); cx.set_state("abc\nˇabc abc\ndefabc\nabc"); cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx)); diff --git a/crates/editor/src/test.rs b/crates/editor/src/test.rs index 74b82a20bda6d1e783b5f1fd0c4c9951b99a259e..75bc5fe76a23e5b6b0d9d0dd4c65bff34468498f 100644 --- a/crates/editor/src/test.rs +++ b/crates/editor/src/test.rs @@ -88,7 +88,7 @@ pub struct EditorTestContext<'a> { } impl<'a> EditorTestContext<'a> { - pub async fn new(cx: &'a mut gpui::TestAppContext) -> EditorTestContext<'a> { + pub fn new(cx: &'a mut gpui::TestAppContext) -> EditorTestContext<'a> { let (window_id, editor) = cx.update(|cx| { cx.set_global(Settings::test(cx)); crate::init(cx); diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 24286a54ab662c2b96ed9c53e0cab8210f0dca48..0fd92695fbd4afa63610a67c72c5bedaab5bfc8d 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -26,7 +26,7 @@ pub fn init(cx: &mut MutableAppContext) { cx.add_action(Dock::move_dock); } -#[derive(Copy, Clone, PartialEq, Eq)] +#[derive(Copy, Clone, PartialEq, Eq, Debug)] pub enum DockPosition { Shown(DockAnchor), Hidden(DockAnchor), @@ -372,12 +372,14 @@ impl StatusItemView for ToggleDockButton { #[cfg(test)] mod tests { - use super::*; - use gpui::{TestAppContext, ViewContext}; + use std::ops::{Deref, DerefMut}; + + use gpui::{AppContext, TestAppContext, UpdateView, ViewContext}; use project::{FakeFs, Project}; use settings::Settings; - use crate::{tests::TestItem, ItemHandle, Workspace}; + use super::*; + use crate::{sidebar::Sidebar, tests::TestItem, ItemHandle, Workspace}; pub fn default_item_factory( _workspace: &mut Workspace, @@ -388,83 +390,263 @@ mod tests { #[gpui::test] async fn test_dock_hides_when_pane_empty(cx: &mut TestAppContext) { - cx.foreground().forbid_parking(); - - Settings::test_async(cx); - let fs = FakeFs::new(cx.background()); + let mut cx = DockTestContext::new(cx).await; + + // Closing the last item in the dock hides the dock + cx.move_dock(DockAnchor::Right); + let old_items = cx.dock_items(); + assert!(!old_items.is_empty()); + cx.close_dock_items().await; + cx.assert_dock_position(DockPosition::Hidden(DockAnchor::Right)); + + // Reopening the dock adds a new item + cx.move_dock(DockAnchor::Right); + let new_items = cx.dock_items(); + assert!(!new_items.is_empty()); + assert!(new_items + .into_iter() + .all(|new_item| !old_items.contains(&new_item))); + } - let project = Project::test(fs, [], cx).await; - let (_, workspace) = cx.add_window(|cx| Workspace::new(project, default_item_factory, cx)); + #[gpui::test] + async fn test_dock_panel_collisions(cx: &mut TestAppContext) { + let mut cx = DockTestContext::new(cx).await; + + // Dock closes when expanded for either panel + cx.move_dock(DockAnchor::Expanded); + cx.open_sidebar(SidebarSide::Left); + cx.assert_dock_position(DockPosition::Hidden(DockAnchor::Expanded)); + cx.close_sidebar(SidebarSide::Left); + cx.move_dock(DockAnchor::Expanded); + cx.open_sidebar(SidebarSide::Right); + cx.assert_dock_position(DockPosition::Hidden(DockAnchor::Expanded)); + + // Dock closes in the right position if the right sidebar is opened + cx.move_dock(DockAnchor::Right); + cx.open_sidebar(SidebarSide::Left); + cx.assert_dock_position(DockPosition::Shown(DockAnchor::Right)); + cx.open_sidebar(SidebarSide::Right); + cx.assert_dock_position(DockPosition::Hidden(DockAnchor::Right)); + cx.close_sidebar(SidebarSide::Right); + + // Dock in bottom position ignores sidebars + cx.move_dock(DockAnchor::Bottom); + cx.open_sidebar(SidebarSide::Left); + cx.open_sidebar(SidebarSide::Right); + cx.assert_dock_position(DockPosition::Shown(DockAnchor::Bottom)); + + // Opening the dock in the right position closes the right sidebar + cx.move_dock(DockAnchor::Right); + cx.assert_sidebar_closed(SidebarSide::Right); + } - // Open dock - workspace.update(cx, |workspace, cx| { - Dock::show(workspace, cx); - }); + #[gpui::test] + async fn test_focusing_panes_shows_and_hides_dock(cx: &mut TestAppContext) { + let mut cx = DockTestContext::new(cx).await; + + // Focusing an item not in the dock when expanded hides the dock + let center_item = cx.add_item_to_center_pane(); + cx.move_dock(DockAnchor::Expanded); + let dock_item = cx + .dock_items() + .get(0) + .cloned() + .expect("Dock should have an item at this point"); + center_item.update(&mut cx, |_, cx| cx.focus_self()); + cx.assert_dock_position(DockPosition::Hidden(DockAnchor::Expanded)); + + // Focusing an item not in the dock when not expanded, leaves the dock open but inactive + cx.move_dock(DockAnchor::Right); + center_item.update(&mut cx, |_, cx| cx.focus_self()); + cx.assert_dock_position(DockPosition::Shown(DockAnchor::Right)); + cx.assert_dock_pane_inactive(); + + // Focus dock item + dock_item.update(&mut cx, |_, cx| cx.focus_self()); + cx.assert_dock_position(DockPosition::Shown(DockAnchor::Right)); + cx.assert_dock_pane_active(); + } - // Ensure dock has an item in it - let dock_item_handle = workspace.read_with(cx, |workspace, cx| { - let dock = workspace.dock_pane().read(cx); - dock.items() - .next() - .expect("Dock should have an item in it") - .clone() - }); + struct DockTestContext<'a> { + pub cx: &'a mut TestAppContext, + pub window_id: usize, + pub workspace: ViewHandle, + } - // Close item - let close_task = workspace.update(cx, |workspace, cx| { - Pane::close_item( - workspace, - workspace.dock_pane().clone(), - dock_item_handle.id(), + impl<'a> DockTestContext<'a> { + pub async fn new(cx: &'a mut TestAppContext) -> DockTestContext<'a> { + Settings::test_async(cx); + let fs = FakeFs::new(cx.background()); + + cx.update(|cx| init(cx)); + let project = Project::test(fs, [], cx).await; + let (window_id, workspace) = + cx.add_window(|cx| Workspace::new(project, default_item_factory, cx)); + + workspace.update(cx, |workspace, cx| { + let left_panel = cx.add_view(|_| TestItem::new()); + workspace.left_sidebar().update(cx, |sidebar, cx| { + sidebar.add_item( + "icons/folder_tree_16.svg", + "Left Test Panel".to_string(), + left_panel.clone(), + cx, + ); + }); + + let right_panel = cx.add_view(|_| TestItem::new()); + workspace.right_sidebar().update(cx, |sidebar, cx| { + sidebar.add_item( + "icons/folder_tree_16.svg", + "Right Test Panel".to_string(), + right_panel.clone(), + cx, + ); + }); + }); + + Self { cx, - ) - }); - close_task.await.expect("Dock item closed successfully"); + window_id, + workspace, + } + } - // Ensure dock closes - workspace.read_with(cx, |workspace, cx| { - assert!(workspace.dock.visible_pane().is_some()) - }); + pub fn workspace(&self, read: F) -> T + where + F: FnOnce(&Workspace, &AppContext) -> T, + { + self.workspace.read_with(self.cx, read) + } - // Open again - workspace.update(cx, |workspace, cx| { - Dock::show(workspace, cx); - }); + pub fn update_workspace(&mut self, update: F) -> T + where + F: FnOnce(&mut Workspace, &mut ViewContext) -> T, + { + self.workspace.update(self.cx, update) + } - // Ensure dock has item in it - workspace.read_with(cx, |workspace, cx| { - let dock = workspace.dock_pane().read(cx); - dock.items().next().expect("Dock should have an item in it"); - }); + pub fn sidebar(&self, sidebar_side: SidebarSide, read: F) -> T + where + F: FnOnce(&Sidebar, &AppContext) -> T, + { + self.workspace(|workspace, cx| { + let sidebar = match sidebar_side { + SidebarSide::Left => workspace.left_sidebar(), + SidebarSide::Right => workspace.right_sidebar(), + } + .read(cx); + + read(sidebar, cx) + }) + } + + pub fn add_item_to_center_pane(&mut self) -> ViewHandle { + self.update_workspace(|workspace, cx| { + let item = cx.add_view(|_| TestItem::new()); + let pane = workspace + .last_active_center_pane + .clone() + .unwrap_or_else(|| workspace.center.panes()[0].clone()); + Pane::add_item( + workspace, + &pane, + Box::new(item.clone()), + true, + true, + None, + cx, + ); + item + }) + } + + pub fn dock_pane(&self, read: F) -> T + where + F: FnOnce(&Pane, &AppContext) -> T, + { + self.workspace(|workspace, cx| { + let dock_pane = workspace.dock_pane().read(cx); + read(dock_pane, cx) + }) + } + + pub fn move_dock(&self, anchor: DockAnchor) { + self.cx.dispatch_action(self.window_id, MoveDock(anchor)); + } + + pub fn open_sidebar(&mut self, sidebar_side: SidebarSide) { + if !self.sidebar(sidebar_side, |sidebar, _| sidebar.is_open()) { + self.update_workspace(|workspace, cx| workspace.toggle_sidebar(sidebar_side, cx)); + } + } + + pub fn close_sidebar(&mut self, sidebar_side: SidebarSide) { + if self.sidebar(sidebar_side, |sidebar, _| sidebar.is_open()) { + self.update_workspace(|workspace, cx| workspace.toggle_sidebar(sidebar_side, cx)); + } + } + + pub fn dock_items(&self) -> Vec> { + self.dock_pane(|pane, cx| { + pane.items() + .map(|item| { + item.act_as::(cx) + .expect("Dock Test Context uses TestItems in the dock") + }) + .collect() + }) + } + + pub async fn close_dock_items(&mut self) { + self.update_workspace(|workspace, cx| { + Pane::close_items(workspace, workspace.dock_pane().clone(), cx, |_| true) + }) + .await + .expect("Could not close dock items") + } + + pub fn assert_dock_position(&self, expected_position: DockPosition) { + self.workspace(|workspace, _| assert_eq!(workspace.dock.position, expected_position)); + } + + pub fn assert_sidebar_closed(&self, sidebar_side: SidebarSide) { + assert!(!self.sidebar(sidebar_side, |sidebar, _| sidebar.is_open())); + } + + pub fn assert_dock_pane_active(&self) { + assert!(self.dock_pane(|pane, _| pane.is_active())) + } + + pub fn assert_dock_pane_inactive(&self) { + assert!(!self.dock_pane(|pane, _| pane.is_active())) + } } - #[gpui::test] - async fn test_dock_panel_collisions(cx: &mut TestAppContext) { - // Open dock expanded - // Open left panel - // Ensure dock closes - // Open dock to the right - // Open left panel - // Ensure dock is left open - // Open right panel - // Ensure dock closes - // Open dock bottom - // Open left panel - // Open right panel - // Ensure dock still open + impl<'a> Deref for DockTestContext<'a> { + type Target = gpui::TestAppContext; + + fn deref(&self) -> &Self::Target { + self.cx + } } - #[gpui::test] - async fn test_focusing_panes_shows_and_hides_dock(cx: &mut TestAppContext) { - // Open item in center pane - // Open dock expanded - // Focus new item - // Ensure the dock gets hidden - // Open dock to the right - // Focus new item - // Ensure dock stays shown but inactive - // Add item to dock and hide it - // Focus the added item - // Ensure the dock is open + impl<'a> DerefMut for DockTestContext<'a> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.cx + } + } + + impl<'a> UpdateView for DockTestContext<'a> { + fn update_view( + &mut self, + handle: &ViewHandle, + update: &mut dyn FnMut(&mut T, &mut ViewContext) -> S, + ) -> S + where + T: View, + { + handle.update(self.cx, update) + } } } diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 3d3fb24726a4183a61bb47909ab42a47ed6c11c8..e1e2c63eef8847427b06cf451915ca194637086e 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -706,7 +706,7 @@ impl Pane { pane: ViewHandle, item_id_to_close: usize, cx: &mut ViewContext, - ) -> Task> { + ) -> Task> { Self::close_items(workspace, pane, cx, move |view_id| { view_id == item_id_to_close }) @@ -717,7 +717,7 @@ impl Pane { pane: ViewHandle, cx: &mut ViewContext, should_close: impl 'static + Fn(usize) -> bool, - ) -> Task> { + ) -> Task> { let project = workspace.project().clone(); // Find the items to close. @@ -790,7 +790,7 @@ impl Pane { } pane.update(&mut cx, |_, cx| cx.notify()); - Ok(true) + Ok(()) }) } diff --git a/crates/workspace/src/sidebar.rs b/crates/workspace/src/sidebar.rs index 804d67027398317a9529d132ae1d826ce600d161..5cf986128a5254a17fd1b93c2c918c181aed579e 100644 --- a/crates/workspace/src/sidebar.rs +++ b/crates/workspace/src/sidebar.rs @@ -11,7 +11,9 @@ pub trait SidebarItem: View { fn should_activate_item_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { false } - fn should_show_badge(&self, cx: &AppContext) -> bool; + fn should_show_badge(&self, _: &AppContext) -> bool { + false + } fn contains_focused_view(&self, _: &AppContext) -> bool { false } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 86a61141ad94b5580a9e018c5af884a60ca652e9..49d2ec327eb529e8288d3b57a81bee2118e8c4a4 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -2934,6 +2934,8 @@ fn open_new(app_state: &Arc, cx: &mut MutableAppContext) { mod tests { use std::cell::Cell; + use crate::sidebar::SidebarItem; + use super::*; use gpui::{executor::Deterministic, ModelHandle, TestAppContext, ViewContext}; use project::{FakeFs, Project, ProjectEntryId}; @@ -3724,4 +3726,6 @@ mod tests { vec![ItemEvent::UpdateTab, ItemEvent::Edit] } } + + impl SidebarItem for TestItem {} }