Test moving panels

Nathan Sobo and Julia Risley created

Co-Authored-By: Julia Risley <julia@zed.dev>

Change summary

crates/welcome/src/welcome.rs             |  2 
crates/workspace/src/dock.rs              | 71 +++++++++++++++++
crates/workspace/src/persistence.rs       |  6 -
crates/workspace/src/persistence/model.rs |  4 
crates/workspace/src/workspace.rs         | 98 ++++++++++++++++++++++++
crates/zed/src/main.rs                    |  3 
6 files changed, 169 insertions(+), 15 deletions(-)

Detailed changes

crates/welcome/src/welcome.rs πŸ”—

@@ -10,7 +10,7 @@ use gpui::{
 use settings::{settings_file::SettingsFile, Settings};
 
 use workspace::{
-    item::Item, open_new, dock::DockPosition, AppState, PaneBackdrop, Welcome, Workspace,
+    dock::DockPosition, item::Item, open_new, AppState, PaneBackdrop, Welcome, Workspace,
     WorkspaceId,
 };
 

crates/workspace/src/dock.rs πŸ”—

@@ -202,6 +202,8 @@ impl Dock {
             if panel_ix == self.active_panel_index {
                 self.active_panel_index = 0;
                 self.set_open(false, cx);
+            } else if panel_ix < self.active_panel_index {
+                self.active_panel_index -= 1;
             }
             self.panel_entries.remove(panel_ix);
             cx.notify();
@@ -228,7 +230,9 @@ impl Dock {
 
     pub fn active_panel(&self) -> Option<&Rc<dyn PanelHandle>> {
         if self.is_open {
-            self.panel_entries.get(self.active_panel_index).map(|entry| &entry.panel)
+            self.panel_entries
+                .get(self.active_panel_index)
+                .map(|entry| &entry.panel)
         } else {
             None
         }
@@ -416,3 +420,68 @@ impl StatusItemView for PanelButtons {
     ) {
     }
 }
+
+#[cfg(test)]
+pub(crate) mod test {
+    use super::*;
+    use gpui::Entity;
+
+    pub enum TestPanelEvent {
+        PositionChanged,
+        Activated,
+        Closed,
+    }
+
+    pub struct TestPanel {
+        pub position: DockPosition,
+    }
+
+    impl Entity for TestPanel {
+        type Event = TestPanelEvent;
+    }
+
+    impl View for TestPanel {
+        fn ui_name() -> &'static str {
+            "TestPanel"
+        }
+
+        fn render(&mut self, _: &mut ViewContext<'_, '_, Self>) -> AnyElement<Self> {
+            Empty::new().into_any()
+        }
+    }
+
+    impl Panel for TestPanel {
+        fn position(&self, _: &gpui::WindowContext) -> super::DockPosition {
+            self.position
+        }
+
+        fn position_is_valid(&self, _: super::DockPosition) -> bool {
+            true
+        }
+
+        fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>) {
+            self.position = position;
+            cx.emit(TestPanelEvent::PositionChanged);
+        }
+
+        fn icon_path(&self) -> &'static str {
+            "icons/test_panel.svg"
+        }
+
+        fn icon_tooltip(&self) -> String {
+            "Test Panel".into()
+        }
+
+        fn should_change_position_on_event(event: &Self::Event) -> bool {
+            matches!(event, TestPanelEvent::PositionChanged)
+        }
+
+        fn should_activate_on_event(&self, event: &Self::Event, _: &gpui::AppContext) -> bool {
+            matches!(event, TestPanelEvent::Activated)
+        }
+
+        fn should_close_on_event(&self, event: &Self::Event, _: &gpui::AppContext) -> bool {
+            matches!(event, TestPanelEvent::Closed)
+        }
+    }
+}

crates/workspace/src/persistence.rs πŸ”—

@@ -214,10 +214,7 @@ impl WorkspaceDb {
                         workspace_location = ?2,
                         left_sidebar_open = ?3,
                         timestamp = CURRENT_TIMESTAMP
-                ))?((
-                    workspace.id,
-                    &workspace.location,
-                ))
+                ))?((workspace.id, &workspace.location))
                 .context("Updating workspace")?;
 
                 // Save center pane group
@@ -454,7 +451,6 @@ impl WorkspaceDb {
 #[cfg(test)]
 mod tests {
 
-
     use db::open_test_db;
 
     use super::*;

crates/workspace/src/persistence/model.rs πŸ”—

@@ -1,6 +1,4 @@
-use crate::{
-    ItemDeserializers, Member, Pane, PaneAxis, Workspace, WorkspaceId,
-};
+use crate::{ItemDeserializers, Member, Pane, PaneAxis, Workspace, WorkspaceId};
 use anyhow::{anyhow, Context, Result};
 use async_recursion::async_recursion;
 use db::sqlez::{

crates/workspace/src/workspace.rs πŸ”—

@@ -859,7 +859,7 @@ impl Workspace {
                         was_visible = dock.is_open()
                             && dock
                                 .active_panel()
-                                .map_or(false, |item| item.as_any().is::<T>());
+                                .map_or(false, |active_panel| active_panel.id() == panel.id());
                         dock.remove_panel(&panel, cx);
                     });
                     dock = match panel.read(cx).position(cx) {
@@ -2688,7 +2688,11 @@ impl View for Workspace {
                                                 .flex(1., true),
                                             )
                                             .with_children(
-                                                if self.bottom_dock.read(cx).active_panel().is_some()
+                                                if self
+                                                    .bottom_dock
+                                                    .read(cx)
+                                                    .active_panel()
+                                                    .is_some()
                                                 {
                                                     Some(ChildView::new(&self.bottom_dock, cx))
                                                 } else {
@@ -3074,7 +3078,10 @@ fn parse_pixel_position_env_var(value: &str) -> Option<Vector2F> {
 mod tests {
     use std::{cell::RefCell, rc::Rc};
 
-    use crate::item::test::{TestItem, TestItemEvent, TestProjectItem};
+    use crate::{
+        dock::test::TestPanel,
+        item::test::{TestItem, TestItemEvent, TestProjectItem},
+    };
 
     use super::*;
     use fs::FakeFs;
@@ -3644,4 +3651,89 @@ mod tests {
             assert!(pane.can_navigate_forward());
         });
     }
+
+    #[gpui::test]
+    async fn test_panels(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
+        deterministic.forbid_parking();
+        Settings::test_async(cx);
+        let fs = FakeFs::new(cx.background());
+
+        let project = Project::test(fs, [], cx).await;
+        let (_window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
+
+        let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
+            // Add panel_1 on the left, panel_2 on the right.
+            let panel_1 = cx.add_view(|_| TestPanel {
+                position: DockPosition::Left,
+            });
+            workspace.add_panel(panel_1.clone(), cx);
+            workspace
+                .left_dock()
+                .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
+            let panel_2 = cx.add_view(|_| TestPanel {
+                position: DockPosition::Right,
+            });
+            workspace.add_panel(panel_2.clone(), cx);
+            workspace
+                .right_dock()
+                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
+
+            assert_eq!(
+                workspace.left_dock().read(cx).active_panel().unwrap().id(),
+                panel_1.id()
+            );
+            assert_eq!(
+                workspace.right_dock().read(cx).active_panel().unwrap().id(),
+                panel_2.id()
+            );
+
+            (panel_1, panel_2)
+        });
+
+        // Move panel_1 to the right
+        panel_1.update(cx, |panel_1, cx| {
+            panel_1.set_position(DockPosition::Right, cx)
+        });
+
+        workspace.update(cx, |workspace, cx| {
+            // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
+            // Since it was the only panel on the left, the left dock should now be closed.
+            assert!(!workspace.left_dock().read(cx).is_open());
+            assert!(workspace.left_dock().read(cx).active_panel().is_none());
+            assert_eq!(
+                workspace.right_dock().read(cx).active_panel().unwrap().id(),
+                panel_1.id()
+            );
+
+            // Now we move panel_2Β to the left
+            panel_2.set_position(DockPosition::Left, cx);
+        });
+
+        workspace.update(cx, |workspace, cx| {
+            // Since panel_2 was not visible on the right, we don't open the left dock.
+            assert!(!workspace.left_dock().read(cx).is_open());
+            // And the right dock is unaffected in it's displaying of panel_1
+            assert!(workspace.right_dock().read(cx).is_open());
+            assert_eq!(
+                workspace.right_dock().read(cx).active_panel().unwrap().id(),
+                panel_1.id()
+            );
+        });
+
+        // Move panel_1 back to the left
+        panel_1.update(cx, |panel_1, cx| {
+            panel_1.set_position(DockPosition::Left, cx)
+        });
+
+        workspace.update(cx, |workspace, cx| {
+            // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
+            assert!(workspace.left_dock().read(cx).is_open());
+            assert_eq!(
+                workspace.left_dock().read(cx).active_panel().unwrap().id(),
+                panel_1.id()
+            );
+            // And right the dock should be closed as it no longer has any panels.
+            assert!(!workspace.right_dock().read(cx).is_open());
+        });
+    }
 }

crates/zed/src/main.rs πŸ”—

@@ -52,8 +52,7 @@ use staff_mode::StaffMode;
 use theme::ThemeRegistry;
 use util::{channel::RELEASE_CHANNEL, paths, ResultExt, TryFutureExt};
 use workspace::{
-    item::ItemHandle, notifications::NotifyResultExt, AppState, OpenSettings,
-    Workspace,
+    item::ItemHandle, notifications::NotifyResultExt, AppState, OpenSettings, Workspace,
 };
 use zed::{self, build_window_options, initialize_workspace, languages, menus};