Update tests

Danilo Leal created

Change summary

crates/sidebar/src/sidebar_tests.rs | 157 ++++++++++++++----------------
1 file changed, 73 insertions(+), 84 deletions(-)

Detailed changes

crates/sidebar/src/sidebar_tests.rs 🔗

@@ -45,7 +45,7 @@ fn assert_active_thread(sidebar: &Sidebar, session_id: &acp::SessionId, msg: &st
 #[track_caller]
 fn assert_active_draft(sidebar: &Sidebar, workspace: &Entity<Workspace>, msg: &str) {
     assert!(
-        matches!(&sidebar.active_entry, Some(ActiveEntry::Draft(ws)) if ws == workspace),
+        matches!(&sidebar.active_entry, Some(ActiveEntry::Draft { workspace: ws, .. }) if ws == workspace),
         "{msg}: expected active_entry to be Draft for workspace {:?}, got {:?}",
         workspace.entity_id(),
         sidebar.active_entry,
@@ -388,17 +388,14 @@ fn visible_entries_as_strings(
                             format!("  + View More{}", selected)
                         }
                     }
-                    ListEntry::DraftThread {
-                        workspace,
-                        worktrees,
-                        ..
-                    } => {
+                    ListEntry::DraftThread { worktrees, .. } => {
                         let worktree = format_linked_worktree_chips(worktrees);
-                        if workspace.is_some() {
-                            format!("  [+ New Thread{}]{}", worktree, selected)
-                        } else {
-                            format!("  [~ Draft{}]{}{}", worktree, active_indicator, selected)
-                        }
+                        let is_active = sidebar
+                            .active_entry
+                            .as_ref()
+                            .is_some_and(|e| e.matches_entry(entry));
+                        let active_marker = if is_active { " *" } else { "" };
+                        format!("  [~ Draft{}]{}{}", worktree, active_marker, selected)
                     }
                 }
             })
@@ -566,10 +563,7 @@ async fn test_single_workspace_no_threads(cx: &mut TestAppContext) {
 
     assert_eq!(
         visible_entries_as_strings(&sidebar, cx),
-        vec![
-            //
-            "v [my-project]",
-        ]
+        vec!["v [my-project]", "  [~ Draft]"]
     );
 }
 
@@ -1329,13 +1323,10 @@ async fn test_keyboard_navigation_on_empty_list(cx: &mut TestAppContext) {
         cx.add_window_view(|window, cx| MultiWorkspace::test_new(project, window, cx));
     let sidebar = setup_sidebar(&multi_workspace, cx);
 
-    // An empty project has only the header.
+    // An empty project has the header and an auto-created draft.
     assert_eq!(
         visible_entries_as_strings(&sidebar, cx),
-        vec![
-            //
-            "v [empty-project]",
-        ]
+        vec!["v [empty-project]", "  [~ Draft]"]
     );
 
     // Focus sidebar — focus_in does not set a selection
@@ -1346,7 +1337,11 @@ async fn test_keyboard_navigation_on_empty_list(cx: &mut TestAppContext) {
     cx.dispatch_action(SelectNext);
     assert_eq!(sidebar.read_with(cx, |s, _| s.selection), Some(0));
 
-    // At the end (only one entry), wraps back to first entry
+    // SelectNext advances to index 1 (draft entry)
+    cx.dispatch_action(SelectNext);
+    assert_eq!(sidebar.read_with(cx, |s, _| s.selection), Some(1));
+
+    // At the end (two entries), wraps back to first entry
     cx.dispatch_action(SelectNext);
     assert_eq!(sidebar.read_with(cx, |s, _| s.selection), Some(0));
 
@@ -2591,9 +2586,10 @@ async fn test_new_thread_button_works_after_adding_folder(cx: &mut TestAppContex
     assert_eq!(
         visible_entries_as_strings(&sidebar, cx),
         vec![
-            //
-            "v [project-a, project-b]",
-            "  Hello * (active)",
+            "v [project-a, project-b]", //
+            "  Hello *",
+            "v [project-a]",
+            "  [~ Draft]",
         ]
     );
 
@@ -3437,12 +3433,7 @@ async fn test_cmd_n_shows_new_thread_entry(cx: &mut TestAppContext) {
 
     assert_eq!(
         visible_entries_as_strings(&sidebar, cx),
-        vec![
-            //
-            "v [my-project]",
-            "  [~ Draft] (active)",
-            "  Hello *",
-        ],
+        vec!["v [my-project]", "  [~ Draft] *", "  Hello *"],
         "After Cmd-N the sidebar should show a highlighted Draft entry"
     );
 
@@ -3482,21 +3473,16 @@ async fn test_draft_with_server_session_shows_as_draft(cx: &mut TestAppContext)
         ]
     );
 
-    // Open a new draft thread via a server connection. This gives the
-    // conversation a parent_id (session assigned by the server) but
-    // no messages have been sent, so active_thread_is_draft() is true.
-    let draft_connection = StubAgentConnection::new();
-    open_thread_with_connection(&panel, draft_connection, cx);
+    // Create a new draft via Cmd-N. Since new_thread() now creates a
+    // tracked draft in the AgentPanel, it appears in the sidebar.
+    panel.update_in(cx, |panel, window, cx| {
+        panel.new_thread(&NewThread, window, cx);
+    });
     cx.run_until_parked();
 
     assert_eq!(
         visible_entries_as_strings(&sidebar, cx),
-        vec![
-            //
-            "v [my-project]",
-            "  [~ Draft] (active)",
-            "  Hello *",
-        ],
+        vec!["v [my-project]", "  [~ Draft] *", "  Hello *"],
     );
 
     let workspace = multi_workspace.read_with(cx, |mw, _cx| mw.workspace().clone());
@@ -3611,8 +3597,8 @@ async fn test_cmd_n_shows_new_thread_entry_in_absorbed_worktree(cx: &mut TestApp
         vec![
             //
             "v [project]",
-            "  [~ Draft {wt-feature-a}] (active)",
-            "  Hello {wt-feature-a} *",
+            "  [~ Draft {wt-feature-a}] *",
+            "  Hello {wt-feature-a} *"
         ],
         "After Cmd-N in an absorbed worktree, the sidebar should show \
              a highlighted Draft entry under the main repo header"
@@ -3729,11 +3715,7 @@ async fn test_git_worktree_added_live_updates_sidebar(cx: &mut TestAppContext) {
     // The chip name is derived from the path even before git discovery.
     assert_eq!(
         visible_entries_as_strings(&sidebar, cx),
-        vec![
-            //
-            "v [project]",
-            "  Worktree Thread {rosewood}",
-        ]
+        vec!["v [project]", "  [~ Draft]"]
     );
 
     // Now add the worktree to the git state and trigger a rescan.
@@ -3925,12 +3907,7 @@ async fn test_threadless_workspace_shows_new_thread_with_worktree_chip(cx: &mut
     // appears as a "New Thread" button with its worktree chip.
     assert_eq!(
         visible_entries_as_strings(&sidebar, cx),
-        vec![
-            //
-            "v [project]",
-            "  [+ New Thread {wt-feature-b}]",
-            "  Thread A {wt-feature-a}",
-        ]
+        vec!["v [project]", "  Thread A {wt-feature-a}",]
     );
 }
 
@@ -4184,12 +4161,7 @@ async fn test_absorbed_worktree_running_thread_shows_live_status(cx: &mut TestAp
     let entries = visible_entries_as_strings(&sidebar, cx);
     assert_eq!(
         entries,
-        vec![
-            //
-            "v [project]",
-            "  [~ Draft] (active)",
-            "  Hello {wt-feature-a} * (running)",
-        ]
+        vec!["v [project]", "  Hello {wt-feature-a} * (running)",]
     );
 }
 
@@ -4272,12 +4244,7 @@ async fn test_absorbed_worktree_completion_triggers_notification(cx: &mut TestAp
 
     assert_eq!(
         visible_entries_as_strings(&sidebar, cx),
-        vec![
-            //
-            "v [project]",
-            "  [~ Draft] (active)",
-            "  Hello {wt-feature-a} * (running)",
-        ]
+        vec!["v [project]", "  Hello {wt-feature-a} * (running)",]
     );
 
     connection.end_turn(session_id, acp::StopReason::EndTurn);
@@ -4285,12 +4252,7 @@ async fn test_absorbed_worktree_completion_triggers_notification(cx: &mut TestAp
 
     assert_eq!(
         visible_entries_as_strings(&sidebar, cx),
-        vec![
-            //
-            "v [project]",
-            "  [~ Draft] (active)",
-            "  Hello {wt-feature-a} * (!)",
-        ]
+        vec!["v [project]", "  Hello {wt-feature-a} * (!)",]
     );
 }
 
@@ -5498,6 +5460,7 @@ async fn test_linked_worktree_threads_not_duplicated_across_groups(cx: &mut Test
         vec![
             //
             "v [other, project]",
+            "  [~ Draft]",
             "v [project]",
             "  Worktree Thread {wt-feature-a}",
         ]
@@ -5951,7 +5914,7 @@ async fn test_archive_thread_active_entry_management(cx: &mut TestAppContext) {
     // active_entry should still be a draft on workspace_b (the active one).
     sidebar.read_with(cx, |sidebar, _| {
         assert!(
-            matches!(&sidebar.active_entry, Some(ActiveEntry::Draft(ws)) if ws == &workspace_b),
+            matches!(&sidebar.active_entry, Some(ActiveEntry::Draft { workspace: ws, .. }) if ws == &workspace_b),
             "expected Draft(workspace_b) after archiving non-active thread, got: {:?}",
             sidebar.active_entry,
         );
@@ -5986,7 +5949,7 @@ async fn test_archive_thread_active_entry_management(cx: &mut TestAppContext) {
     // Should fall back to a draft on the same workspace.
     sidebar.read_with(cx, |sidebar, _| {
         assert!(
-            matches!(&sidebar.active_entry, Some(ActiveEntry::Draft(ws)) if ws == &workspace_b),
+            matches!(&sidebar.active_entry, Some(ActiveEntry::Draft { workspace: ws, .. }) if ws == &workspace_b),
             "expected Draft(workspace_b) after archiving active thread, got: {:?}",
             sidebar.active_entry,
         );
@@ -6059,7 +6022,7 @@ async fn test_switch_to_workspace_with_archived_thread_shows_draft(cx: &mut Test
 
     sidebar.read_with(cx, |sidebar, _| {
         assert!(
-            matches!(&sidebar.active_entry, Some(ActiveEntry::Draft(ws)) if ws == &workspace_a),
+            matches!(&sidebar.active_entry, Some(ActiveEntry::Draft { workspace: ws, .. }) if ws == &workspace_a),
             "expected Draft(workspace_a) after switching to workspace with archived thread, got: {:?}",
             sidebar.active_entry,
         );
@@ -6561,9 +6524,10 @@ async fn test_archive_thread_on_linked_worktree_selects_sibling_thread(cx: &mut
 #[gpui::test]
 async fn test_linked_worktree_workspace_reachable_and_dismissable(cx: &mut TestAppContext) {
     // When a linked worktree is opened as its own workspace and the user
-    // switches away, the workspace must still be reachable from a DraftThread
-    // sidebar entry. Pressing RemoveSelectedThread (shift-backspace) on that
-    // entry should remove the workspace.
+    // creates a draft thread from it, then switches away, the workspace must
+    // still be reachable from that DraftThread sidebar entry. Pressing
+    // RemoveSelectedThread (shift-backspace) on that entry should remove the
+    // workspace.
     init_test(cx);
     let fs = FakeFs::new(cx.executor());
 
@@ -6627,6 +6591,14 @@ async fn test_linked_worktree_workspace_reachable_and_dismissable(cx: &mut TestA
     add_agent_panel(&worktree_workspace, cx);
     cx.run_until_parked();
 
+    // Explicitly create a draft thread from the linked worktree workspace.
+    // Auto-created drafts use the group's first workspace (the main one),
+    // so a user-created draft is needed to make the linked worktree reachable.
+    sidebar.update_in(cx, |sidebar, window, cx| {
+        sidebar.create_new_thread(&worktree_workspace, window, cx);
+    });
+    cx.run_until_parked();
+
     // Switch back to the main workspace.
     multi_workspace.update_in(cx, |mw, window, cx| {
         let main_ws = mw.workspaces().next().unwrap().clone();
@@ -6656,7 +6628,7 @@ async fn test_linked_worktree_workspace_reachable_and_dismissable(cx: &mut TestA
         "linked worktree workspace should be reachable, but reachable are: {reachable:?}"
     );
 
-    // Find the DraftThread entry for the linked worktree and dismiss it.
+    // Find the DraftThread entry whose workspace is the linked worktree.
     let new_thread_ix = sidebar.read_with(cx, |sidebar, _| {
         sidebar
             .contents
@@ -6666,9 +6638,9 @@ async fn test_linked_worktree_workspace_reachable_and_dismissable(cx: &mut TestA
                 matches!(
                     entry,
                     ListEntry::DraftThread {
-                        workspace: Some(_),
+                        workspace: Some(ws),
                         ..
-                    }
+                    } if ws.entity_id() == worktree_ws_id
                 )
             })
             .expect("expected a DraftThread entry for the linked worktree")
@@ -6687,8 +6659,25 @@ async fn test_linked_worktree_workspace_reachable_and_dismissable(cx: &mut TestA
 
     assert_eq!(
         multi_workspace.read_with(cx, |mw, _| mw.workspaces().count()),
-        1,
-        "linked worktree workspace should be removed after dismissing DraftThread entry"
+        2,
+        "dismissing a draft no longer removes the linked worktree workspace"
+    );
+
+    let has_draft_for_worktree = sidebar.read_with(cx, |sidebar, _| {
+        sidebar.contents.entries.iter().any(|entry| {
+            matches!(
+                entry,
+                ListEntry::DraftThread {
+                    draft_id: Some(_),
+                    workspace: Some(ws),
+                    ..
+                } if ws.entity_id() == worktree_ws_id
+            )
+        })
+    });
+    assert!(
+        !has_draft_for_worktree,
+        "DraftThread entry for the linked worktree should be removed after dismiss"
     );
 }
 
@@ -7899,7 +7888,7 @@ mod property_test {
         let panel = active_workspace.read(cx).panel::<AgentPanel>(cx).unwrap();
         if panel.read(cx).active_thread_is_draft(cx) {
             anyhow::ensure!(
-                matches!(entry, ActiveEntry::Draft(_)),
+                matches!(entry, ActiveEntry::Draft { .. }),
                 "panel shows a draft but active_entry is {:?}",
                 entry,
             );