mirror of
https://github.com/zed-industries/zed.git
synced 2026-06-01 03:14:56 +07:00
Tighten worktree drag-and-drop tests and docs
- Add `test_move_worktrees_to_end` to the project integration tests, covering non-contiguous reorder, the already-suffix no-op, single source, and the missing-source error path. - Add `test_copy_drag_root_onto_blank_area_is_no_op` exercising the end-to-end behaviour through `drag_onto`. - Trim the implementation-leaky doc on `Project::move_worktrees_to_end` and reflow the blank-area drop-handler comment.
This commit is contained in:
parent
0611b29559
commit
465e096e15
4 changed files with 163 additions and 13 deletions
|
|
@ -4594,8 +4594,7 @@ impl Project {
|
|||
}
|
||||
|
||||
/// Moves multiple worktrees to the end of the worktree list, preserving
|
||||
/// their relative order. Used for "drop on the blank area below the
|
||||
/// project panel" gestures.
|
||||
/// their relative order.
|
||||
pub fn move_worktrees_to_end(
|
||||
&mut self,
|
||||
sources: &[WorktreeId],
|
||||
|
|
|
|||
|
|
@ -8946,6 +8946,96 @@ async fn test_reordering_worktrees(cx: &mut gpui::TestAppContext) {
|
|||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_move_worktrees_to_end(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
"/dir",
|
||||
json!({
|
||||
"a.rs": "",
|
||||
"b.rs": "",
|
||||
"c.rs": "",
|
||||
"d.rs": "",
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
let project = Project::test(
|
||||
fs,
|
||||
[
|
||||
"/dir/a.rs".as_ref(),
|
||||
"/dir/b.rs".as_ref(),
|
||||
"/dir/c.rs".as_ref(),
|
||||
"/dir/d.rs".as_ref(),
|
||||
],
|
||||
cx,
|
||||
)
|
||||
.await;
|
||||
|
||||
let (a_id, b_id, c_id, d_id) = project.update(cx, |project, cx| {
|
||||
let worktrees = project.visible_worktrees(cx).collect::<Vec<_>>();
|
||||
(
|
||||
worktrees[0].read(cx).id(),
|
||||
worktrees[1].read(cx).id(),
|
||||
worktrees[2].read(cx).id(),
|
||||
worktrees[3].read(cx).id(),
|
||||
)
|
||||
});
|
||||
|
||||
// Non-contiguous selection [a, c] → group lands at end as [b, d, a, c].
|
||||
project
|
||||
.update(cx, |project, cx| {
|
||||
project.move_worktrees_to_end(&[a_id, c_id], cx)
|
||||
})
|
||||
.expect("moving non-contiguous group to end");
|
||||
project.update(cx, |project, cx| {
|
||||
let order: Vec<_> = project
|
||||
.visible_worktrees(cx)
|
||||
.map(|wt| wt.read(cx).id())
|
||||
.collect();
|
||||
assert_eq!(order, vec![b_id, d_id, a_id, c_id]);
|
||||
});
|
||||
|
||||
// Already-suffix selection [a, c] → no-op (current order is [b, d, a, c]).
|
||||
project
|
||||
.update(cx, |project, cx| {
|
||||
project.move_worktrees_to_end(&[a_id, c_id], cx)
|
||||
})
|
||||
.expect("contiguous-suffix call should still succeed");
|
||||
project.update(cx, |project, cx| {
|
||||
let order: Vec<_> = project
|
||||
.visible_worktrees(cx)
|
||||
.map(|wt| wt.read(cx).id())
|
||||
.collect();
|
||||
assert_eq!(order, vec![b_id, d_id, a_id, c_id]);
|
||||
});
|
||||
|
||||
// Single source [b] → moves to end: [d, a, c, b].
|
||||
project
|
||||
.update(cx, |project, cx| {
|
||||
project.move_worktrees_to_end(&[b_id], cx)
|
||||
})
|
||||
.expect("moving a single source to end");
|
||||
project.update(cx, |project, cx| {
|
||||
let order: Vec<_> = project
|
||||
.visible_worktrees(cx)
|
||||
.map(|wt| wt.read(cx).id())
|
||||
.collect();
|
||||
assert_eq!(order, vec![d_id, a_id, c_id, b_id]);
|
||||
});
|
||||
|
||||
// Invalid source id → error.
|
||||
project.update(cx, |project, cx| {
|
||||
let invalid = worktree::WorktreeId::from_usize(99_999);
|
||||
assert!(
|
||||
project.move_worktrees_to_end(&[invalid], cx).is_err(),
|
||||
"moving an unknown source should error"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_move_worktree_with_invalid_source_errors(cx: &mut gpui::TestAppContext) {
|
||||
use worktree::WorktreeId;
|
||||
|
|
|
|||
|
|
@ -7165,17 +7165,13 @@ impl Render for ProjectPanel {
|
|||
move |this, selections: &DraggedSelection, window, cx| {
|
||||
this.drag_target_entry = None;
|
||||
this.hover_scroll_task.take();
|
||||
// A pure-root drag that contains the
|
||||
// last worktree would otherwise hit
|
||||
// the self-drop guard in
|
||||
// `move_worktrees` and become a no-op.
|
||||
// Send the group to the end — but
|
||||
// only for move gestures; copy mode
|
||||
// can't operate on roots and should
|
||||
// continue to no-op via `drag_onto`.
|
||||
let is_copy_mode = Self::is_copy_modifier_set(
|
||||
&window.modifiers(),
|
||||
);
|
||||
let is_copy_mode =
|
||||
Self::is_copy_modifier_set(&window.modifiers());
|
||||
// For move drags whose root group includes the last
|
||||
// worktree, route to the move-to-end path so we don't
|
||||
// hit the self-drop guard in `move_worktrees`. Copy
|
||||
// drags fall through to `drag_onto`, which will
|
||||
// filter the roots out as a no-op.
|
||||
if !is_copy_mode
|
||||
&& this.drag_includes_last_worktree(selections, cx)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -4510,6 +4510,71 @@ async fn test_drag_root_group_with_last_worktree_to_blank_area(
|
|||
);
|
||||
}
|
||||
|
||||
// Holding the copy modifier and dropping a pure-root drag onto the blank
|
||||
// area below the panel should be a no-op rather than reordering — copy
|
||||
// can't operate on worktree roots.
|
||||
#[gpui::test]
|
||||
async fn test_copy_drag_root_onto_blank_area_is_no_op(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree("/root1", json!({ "a.txt": "" })).await;
|
||||
fs.insert_tree("/root2", json!({ "b.txt": "" })).await;
|
||||
fs.insert_tree("/root3", json!({ "c.txt": "" })).await;
|
||||
|
||||
let project = Project::test(
|
||||
fs.clone(),
|
||||
["/root1".as_ref(), "/root2".as_ref(), "/root3".as_ref()],
|
||||
cx,
|
||||
)
|
||||
.await;
|
||||
let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = window
|
||||
.read_with(cx, |mw, _| mw.workspace().clone())
|
||||
.unwrap();
|
||||
let cx = &mut VisualTestContext::from_window(window.into(), cx);
|
||||
let panel = workspace.update_in(cx, ProjectPanel::new);
|
||||
cx.run_until_parked();
|
||||
|
||||
cx.simulate_modifiers_change(gpui::Modifiers {
|
||||
alt: true,
|
||||
control: true,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
// Drag root1 onto root3's root (the blank-area handler resolves to
|
||||
// `last_worktree_root_id`); copy mode should make this a no-op.
|
||||
panel.update_in(cx, |panel, window, cx| {
|
||||
let project = panel.project.read(cx);
|
||||
let worktrees = project.visible_worktrees(cx).collect::<Vec<_>>();
|
||||
let r1 = worktrees[0].read(cx);
|
||||
let r3 = worktrees[2].read(cx);
|
||||
let r1_entry = SelectedEntry {
|
||||
worktree_id: r1.id(),
|
||||
entry_id: r1.root_entry().unwrap().id,
|
||||
};
|
||||
let drag = DraggedSelection {
|
||||
active_selection: r1_entry,
|
||||
marked_selections: Arc::new([r1_entry]),
|
||||
};
|
||||
panel.drag_onto(&drag, r3.root_entry().unwrap().id, false, window, cx);
|
||||
});
|
||||
cx.run_until_parked();
|
||||
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 0..20, cx),
|
||||
&[
|
||||
"v root1",
|
||||
" a.txt",
|
||||
"v root2",
|
||||
" b.txt",
|
||||
"v root3",
|
||||
" c.txt",
|
||||
],
|
||||
"copy-drag of a worktree root should not reorder worktrees"
|
||||
);
|
||||
}
|
||||
|
||||
// Dropping a multi-root selection onto one of its own members has no
|
||||
// well-defined intent, so it should leave the order untouched rather than
|
||||
// reorder the remaining roots around the destination.
|
||||
|
|
|
|||
Loading…
Reference in a new issue