- 仍是一个测试程序(tests/ui/main.rs,cargo test --test ui),每个文件单独成测试程序会再链接一遍 GPUI - 按功能分 11 个模块:host_dialog、host_tree、connection、terminal、workspace、settings、 sftp、transfer、forward、credential、update - 两个以上模块共用的夹具和假实现放进 support/:mod.rs 是公共的 use、种子 id 和通用助手, 其余按终端、SFTP、端口转发、测试连接、凭据分文件;只有一个模块用的留在那个模块里 - 纯搬移,测试逻辑不变:147 个测试名称与搬移前一致,全部通过 - CLAUDE.md 写明模块划分和新测试该放哪里,AGENTS.md、README 改路径
632 lines
22 KiB
Rust
632 lines
22 KiB
Rust
//! SFTP transfers: uploading, downloading, dragging between the panes and
|
|
//! the queue.
|
|
|
|
use crate::support::*;
|
|
|
|
/// Wait until the batch at the head of the transfer queue reads `status`.
|
|
async fn wait_for_head_status(
|
|
cx: &mut TestAppContext,
|
|
handle: WindowHandle<Root>,
|
|
status: &'static str,
|
|
) {
|
|
cx.wait_for(handle.into(), Duration::from_secs(2), move |window, cx| {
|
|
window.render_frame(cx);
|
|
window
|
|
.try_find("transfer-status")
|
|
.is_some_and(|head| head.label() == Some(status))
|
|
})
|
|
.await;
|
|
}
|
|
|
|
#[gpui_kit::test]
|
|
async fn sftp_multi_selection_keyboard_upload_freezes_paths_and_cancel_resumes(
|
|
cx: &mut TestAppContext,
|
|
) {
|
|
use shellrs::app::{ExplorerAction, ExplorerCommand};
|
|
let provider = Arc::new(FakeSftpProvider::default());
|
|
let (handle, workspace) = open_workspace_with_sftp(cx, provider.clone());
|
|
open_test_explorer(cx, handle).await;
|
|
// Names sort by code point: 文件 乙 comes before 文件 甲.
|
|
cx.update_window(handle.into(), |_, window, cx| {
|
|
window
|
|
.within(("local-pane", SFTP_TAB))
|
|
.click("name:文件 乙.txt", cx);
|
|
})
|
|
.unwrap();
|
|
cx.run_until_parked();
|
|
cx.update(|cx| assert_eq!(pane_selection(&workspace, false, cx), ["文件 乙.txt"]));
|
|
// Shift+↓ extends to the next row; Space takes the cursor row back out.
|
|
for (key, expected) in [
|
|
("shift-down", &["文件 乙.txt", "文件 甲.txt"][..]),
|
|
("space", &["文件 乙.txt"][..]),
|
|
] {
|
|
cx.update_window(handle.into(), |_, window, cx| {
|
|
window.render_frame(cx);
|
|
window.press(key, cx);
|
|
})
|
|
.unwrap();
|
|
cx.run_until_parked();
|
|
cx.update(|cx| assert_eq!(pane_selection(&workspace, false, cx), expected));
|
|
}
|
|
cx.update_window(handle.into(), |_, window, cx| {
|
|
window.render_frame(cx);
|
|
window.press("cmd-a", cx);
|
|
})
|
|
.unwrap();
|
|
cx.run_until_parked();
|
|
cx.update(|cx| {
|
|
assert_eq!(
|
|
workspace
|
|
.read(cx)
|
|
.explorer(ExplorerId(SFTP_TAB))
|
|
.unwrap()
|
|
.read(cx)
|
|
.local()
|
|
.read(cx)
|
|
.upload_sources(cx)
|
|
.len(),
|
|
4
|
|
);
|
|
});
|
|
cx.update_window(handle.into(), |_, window, cx| {
|
|
window.render_frame(cx);
|
|
window.press("f5", cx);
|
|
})
|
|
.unwrap();
|
|
cx.run_until_parked();
|
|
cx.update_window(handle.into(), |_, window, cx| {
|
|
window.render_frame(cx);
|
|
assert!(window.find("upload-confirm").visible());
|
|
// Counted by kind, and each item on a line of its own by name, so a
|
|
// long path cannot pass for two items.
|
|
assert!(
|
|
window
|
|
.find("transfer-summary")
|
|
.label()
|
|
.unwrap()
|
|
.starts_with("上传 4 个项目(2 个文件夹、2 个文件)到 ")
|
|
);
|
|
let source = |path: &str| {
|
|
window
|
|
.find(ElementId::Name(format!("source:{path}").into()))
|
|
.label()
|
|
.map(str::to_string)
|
|
};
|
|
assert_eq!(
|
|
source("/local/tester/文件 甲.txt").as_deref(),
|
|
Some("文件 甲.txt")
|
|
);
|
|
assert_eq!(
|
|
source("/local/tester/链接目录").as_deref(),
|
|
Some("链接目录")
|
|
);
|
|
window.click("upload-target", cx);
|
|
window.press("cmd-a", cx);
|
|
window.input("/固定目标", cx);
|
|
window.click("upload-confirm", cx);
|
|
})
|
|
.unwrap();
|
|
wait_for_head_status(cx, handle, "正在扫描").await;
|
|
assert_eq!(
|
|
provider.requests.lock().unwrap()[0].destination().as_str(),
|
|
"/固定目标"
|
|
);
|
|
assert_eq!(provider.requests.lock().unwrap()[0].sources().len(), 4);
|
|
// A batch unfolds to its item results. None has ended yet, and the list
|
|
// says so rather than opening empty; a second click folds it again.
|
|
for open in [true, false] {
|
|
cx.update_window(handle.into(), |_, window, cx| {
|
|
window.render_frame(cx);
|
|
window.click(("queue-expand", 1u64), cx);
|
|
})
|
|
.unwrap();
|
|
cx.run_until_parked();
|
|
cx.update_window(handle.into(), |_, window, cx| {
|
|
window.render_frame(cx);
|
|
assert_eq!(window.try_find("transfer-detail-empty").is_some(), open);
|
|
})
|
|
.unwrap();
|
|
}
|
|
cx.update_window(handle.into(), |_, window, cx| {
|
|
window.render_frame(cx);
|
|
window.dispatch_action(
|
|
Box::new(ExplorerAction::new(
|
|
ExplorerId(SFTP_TAB),
|
|
ExplorerCommand::Navigate {
|
|
remote: true,
|
|
path: "/other".into(),
|
|
},
|
|
)),
|
|
cx,
|
|
);
|
|
window.click("cancel-transfer", cx);
|
|
})
|
|
.unwrap();
|
|
wait_for_head_status(cx, handle, "已停止").await;
|
|
cx.update_window(handle.into(), |_, window, cx| {
|
|
window.render_frame(cx);
|
|
window.click("resume-transfer", cx);
|
|
})
|
|
.unwrap();
|
|
wait_for_head_status(cx, handle, "0%").await;
|
|
assert_eq!(provider.requests.lock().unwrap().len(), 1);
|
|
assert_eq!(
|
|
provider.requests.lock().unwrap()[0].destination().as_str(),
|
|
"/固定目标"
|
|
);
|
|
}
|
|
|
|
/// A transfer confirmed while another runs waits its turn in the queue,
|
|
/// WinSCP-style, and starts when the engine is free again.
|
|
#[gpui_kit::test]
|
|
async fn sftp_transfers_wait_their_turn_in_the_queue(cx: &mut TestAppContext) {
|
|
use shellrs::app::{ExplorerAction, ExplorerCommand};
|
|
use shellrs::sftp::{TransferPhase, TransferProgress};
|
|
let provider = Arc::new(FakeSftpProvider::default());
|
|
let (handle, _) = open_workspace_with_sftp(cx, provider.clone());
|
|
open_test_explorer(cx, handle).await;
|
|
let upload = |cx: &mut TestAppContext, name: &str| {
|
|
let paths = vec![std::path::PathBuf::from(format!("/local/tester/{name}"))];
|
|
cx.update_window(handle.into(), |_, window, cx| {
|
|
window.render_frame(cx);
|
|
window.dispatch_action(
|
|
Box::new(ExplorerAction::new(
|
|
ExplorerId(SFTP_TAB),
|
|
ExplorerCommand::UploadPaths {
|
|
paths,
|
|
target: "/home/tester".into(),
|
|
},
|
|
)),
|
|
cx,
|
|
);
|
|
})
|
|
.unwrap();
|
|
cx.run_until_parked();
|
|
};
|
|
let confirm = |cx: &mut TestAppContext, queued: bool| {
|
|
cx.update_window(handle.into(), |_, window, cx| {
|
|
window.render_frame(cx);
|
|
assert_eq!(window.try_find("transfer-queued-note").is_some(), queued);
|
|
window.click("upload-confirm", cx);
|
|
})
|
|
.unwrap();
|
|
};
|
|
let label = |cx: &mut TestAppContext, id: ElementId| {
|
|
cx.update_window(handle.into(), |_, window, cx| {
|
|
window.render_frame(cx);
|
|
window
|
|
.try_find(id)
|
|
.and_then(|row| row.label().map(str::to_string))
|
|
})
|
|
.unwrap()
|
|
};
|
|
let send = |event: SftpEvent| {
|
|
provider.events.lock().unwrap()[0]
|
|
.send_blocking(event)
|
|
.unwrap()
|
|
};
|
|
|
|
upload(cx, "文件 甲.txt");
|
|
confirm(cx, false);
|
|
wait_for_head_status(cx, handle, "正在扫描").await;
|
|
upload(cx, "文件 乙.txt");
|
|
confirm(cx, true);
|
|
cx.run_until_parked();
|
|
assert_eq!(
|
|
label(cx, ("queue-entry", 2u64).into()).as_deref(),
|
|
Some("等待中")
|
|
);
|
|
assert_eq!(provider.requests.lock().unwrap().len(), 1);
|
|
|
|
// The first one ends; the engine goes idle and takes the second.
|
|
send(SftpEvent::Progress(TransferProgress::new(
|
|
TransferPhase::Completed,
|
|
)));
|
|
send(SftpEvent::Idle);
|
|
cx.wait_for(handle.into(), Duration::from_secs(2), |window, cx| {
|
|
window.render_frame(cx);
|
|
window
|
|
.try_find(("queue-entry", 1u64))
|
|
.is_some_and(|row| row.label() == Some("已完成"))
|
|
})
|
|
.await;
|
|
assert_eq!(provider.requests.lock().unwrap().len(), 2);
|
|
assert_eq!(
|
|
provider.requests.lock().unwrap()[1].sources(),
|
|
[std::path::PathBuf::from("/local/tester/文件 乙.txt")]
|
|
);
|
|
|
|
// Its file in flight gets a row and a bar of its own.
|
|
send(SftpEvent::Progress(
|
|
TransferProgress::new(TransferPhase::Transferring)
|
|
.with_bytes(512, 1024)
|
|
.with_current(
|
|
"/local/tester/文件 乙.txt",
|
|
"/home/tester/文件 乙.txt",
|
|
256,
|
|
1024,
|
|
),
|
|
));
|
|
wait_for_head_status(cx, handle, "50%").await;
|
|
assert_eq!(
|
|
label(cx, ("queue-file", 2u64).into()).as_deref(),
|
|
Some("/local/tester/文件 乙.txt")
|
|
);
|
|
assert_eq!(
|
|
label(cx, ("queue-file-status", 2u64).into()).as_deref(),
|
|
Some("25%")
|
|
);
|
|
|
|
// Cleared once done, and the queue goes away with its last row.
|
|
let clear = |cx: &mut TestAppContext| {
|
|
cx.update_window(handle.into(), |_, window, cx| {
|
|
window.render_frame(cx);
|
|
window.click("clear-finished-transfers", cx);
|
|
})
|
|
.unwrap();
|
|
cx.run_until_parked();
|
|
};
|
|
clear(cx);
|
|
assert_eq!(label(cx, ("queue-entry", 1u64).into()), None);
|
|
send(SftpEvent::Progress(TransferProgress::new(
|
|
TransferPhase::Completed,
|
|
)));
|
|
send(SftpEvent::Idle);
|
|
cx.wait_for(handle.into(), Duration::from_secs(2), |window, cx| {
|
|
window.render_frame(cx);
|
|
window
|
|
.try_find(("queue-entry", 2u64))
|
|
.is_some_and(|row| row.label() == Some("已完成"))
|
|
})
|
|
.await;
|
|
clear(cx);
|
|
cx.update_window(handle.into(), |_, window, cx| {
|
|
window.render_frame(cx);
|
|
assert!(
|
|
window
|
|
.try_find(("transfer-queue", SFTP_TAB))
|
|
.is_none_or(|queue| !queue.visible())
|
|
);
|
|
})
|
|
.unwrap();
|
|
}
|
|
|
|
#[gpui_kit::test]
|
|
async fn sftp_native_picker_and_external_drop_share_confirmation(cx: &mut TestAppContext) {
|
|
let provider = Arc::new(FakeSftpProvider::default());
|
|
let (handle, _) = open_workspace_with_sftp(cx, provider);
|
|
open_test_explorer(cx, handle).await;
|
|
// 「选择文件上传…」 sits in the upload button's menu, which the tests do
|
|
// not open; dispatch what the item dispatches.
|
|
cx.update_window(handle.into(), |_, window, cx| {
|
|
window.render_frame(cx);
|
|
window.dispatch_action(
|
|
Box::new(shellrs::app::ExplorerAction::new(
|
|
ExplorerId(SFTP_TAB),
|
|
shellrs::app::ExplorerCommand::ChooseFiles,
|
|
)),
|
|
cx,
|
|
);
|
|
})
|
|
.unwrap();
|
|
cx.run_until_parked();
|
|
assert!(cx.did_prompt_for_paths());
|
|
cx.simulate_path_prompt_response(|options| {
|
|
assert!(options.files && options.directories && options.multiple);
|
|
Some(vec!["/tmp/甲".into(), "/tmp/目录".into()])
|
|
});
|
|
cx.run_until_parked();
|
|
cx.update_window(handle.into(), |_, window, cx| {
|
|
window.render_frame(cx);
|
|
assert!(window.find("upload-confirm").visible());
|
|
assert_eq!(window.find("upload-target").value(), Some("/home/tester"));
|
|
window.press("escape", cx);
|
|
})
|
|
.unwrap();
|
|
cx.run_until_parked();
|
|
cx.update_window(handle.into(), |_, window, cx| {
|
|
window.render_frame(cx);
|
|
window
|
|
.within(("remote-pane", SFTP_TAB))
|
|
.hover("file:目录", cx);
|
|
window.render_frame(cx);
|
|
let position = window
|
|
.within(("remote-pane", SFTP_TAB))
|
|
.find("file:目录")
|
|
.bounds()
|
|
.center();
|
|
let paths =
|
|
gpui_kit::ExternalPaths(vec![std::path::PathBuf::from("/tmp/Finder 文件")].into());
|
|
window.dispatch_event(
|
|
gpui_kit::FileDropEvent::Entered { position, paths }.to_platform_input(),
|
|
cx,
|
|
);
|
|
window.dispatch_event(
|
|
gpui_kit::FileDropEvent::Submit { position }.to_platform_input(),
|
|
cx,
|
|
);
|
|
})
|
|
.unwrap();
|
|
cx.run_until_parked();
|
|
cx.update_window(handle.into(), |_, window, cx| {
|
|
window.render_frame(cx);
|
|
assert!(window.find("upload-confirm").visible());
|
|
assert_eq!(
|
|
window.find("upload-target").value(),
|
|
Some("/home/tester/目录")
|
|
);
|
|
})
|
|
.unwrap();
|
|
}
|
|
|
|
#[gpui_kit::test]
|
|
async fn sftp_internal_drag_conflict_and_close_confirmation(cx: &mut TestAppContext) {
|
|
use shellrs::sftp::{TransferQuestion, TransferQuestionKind};
|
|
let provider = Arc::new(FakeSftpProvider::default());
|
|
let (handle, workspace) = open_workspace_with_sftp(cx, provider.clone());
|
|
open_test_explorer(cx, handle).await;
|
|
cx.update_window(handle.into(), |_, window, cx| {
|
|
window.render_frame(cx);
|
|
let from = window
|
|
.within(("local-pane", SFTP_TAB))
|
|
.find("name:文件 甲.txt")
|
|
.bounds()
|
|
.center();
|
|
let to = window
|
|
.within(("remote-pane", SFTP_TAB))
|
|
.find("file:目录")
|
|
.bounds()
|
|
.center();
|
|
window.drag(from, to, cx);
|
|
})
|
|
.unwrap();
|
|
cx.run_until_parked();
|
|
cx.update_window(handle.into(), |_, window, cx| {
|
|
window.render_frame(cx);
|
|
assert_eq!(
|
|
window.find("upload-target").value(),
|
|
Some("/home/tester/目录")
|
|
);
|
|
window.click("upload-confirm", cx);
|
|
})
|
|
.unwrap();
|
|
wait_for_head_status(cx, handle, "正在扫描").await;
|
|
provider.events.lock().unwrap()[0]
|
|
.send_blocking(SftpEvent::Question(TransferQuestion::new(
|
|
900,
|
|
TransferQuestionKind::Conflict,
|
|
"/home/tester/目录/文件 甲.txt",
|
|
"目标已存在",
|
|
)))
|
|
.unwrap();
|
|
cx.wait_for(handle.into(), Duration::from_secs(2), |window, cx| {
|
|
window.render_frame(cx);
|
|
window.try_find("transfer-question-confirm").is_some()
|
|
})
|
|
.await;
|
|
cx.update_window(handle.into(), |_, window, cx| {
|
|
window.render_frame(cx);
|
|
window.click("transfer-apply-all", cx);
|
|
window.click("transfer-question-cancel", cx);
|
|
})
|
|
.unwrap();
|
|
wait_for_head_status(cx, handle, "已停止").await;
|
|
cx.update_window(handle.into(), |_, window, cx| {
|
|
window.render_frame(cx);
|
|
window.click("resume-transfer", cx);
|
|
})
|
|
.unwrap();
|
|
wait_for_head_status(cx, handle, "0%").await;
|
|
cx.update_window(handle.into(), |_, window, cx| {
|
|
window.render_frame(cx);
|
|
window.click(("close-explorer", SFTP_TAB), cx);
|
|
})
|
|
.unwrap();
|
|
cx.run_until_parked();
|
|
cx.update_window(handle.into(), |_, window, cx| {
|
|
window.render_frame(cx);
|
|
assert!(window.find("ok").visible());
|
|
assert!(workspace.read(cx).explorer(ExplorerId(SFTP_TAB)).is_some());
|
|
window.click("ok", cx);
|
|
})
|
|
.unwrap();
|
|
cx.run_until_parked();
|
|
cx.update(|cx| assert!(workspace.read(cx).explorer(ExplorerId(SFTP_TAB)).is_none()));
|
|
}
|
|
|
|
#[gpui_kit::test]
|
|
async fn sftp_downloads_the_selection_with_f5_into_a_chosen_folder(cx: &mut TestAppContext) {
|
|
let provider = Arc::new(FakeSftpProvider::default());
|
|
let (handle, _) = open_workspace_with_sftp(cx, provider.clone());
|
|
open_test_explorer(cx, handle).await;
|
|
// F5 in the remote list downloads the selection into the local directory.
|
|
press_on_row(cx, handle, "remote-pane", "文件 甲.txt", "f5");
|
|
cx.update_window(handle.into(), |_, window, cx| {
|
|
window.render_frame(cx);
|
|
assert!(window.find("download-confirm").visible());
|
|
assert_eq!(
|
|
window.find("download-target").value(),
|
|
Some("/local/tester")
|
|
);
|
|
window.click("download-browse", cx);
|
|
})
|
|
.unwrap();
|
|
cx.run_until_parked();
|
|
assert!(cx.did_prompt_for_paths());
|
|
cx.simulate_path_prompt_response(|options| {
|
|
assert!(options.directories && !options.files && !options.multiple);
|
|
Some(vec!["/picked".into()])
|
|
});
|
|
cx.run_until_parked();
|
|
cx.update_window(handle.into(), |_, window, cx| {
|
|
window.render_frame(cx);
|
|
assert_eq!(window.find("download-target").value(), Some("/picked"));
|
|
window.click("download-confirm", cx);
|
|
})
|
|
.unwrap();
|
|
wait_for_head_status(cx, handle, "0%").await;
|
|
{
|
|
let downloads = provider.downloads.lock().unwrap();
|
|
assert_eq!(downloads.len(), 1);
|
|
assert_eq!(
|
|
downloads[0].sources(),
|
|
[RemotePath::new("/home/tester/文件 甲.txt").unwrap()]
|
|
);
|
|
assert_eq!(downloads[0].destination(), std::path::Path::new("/picked"));
|
|
}
|
|
cx.update_window(handle.into(), |_, window, cx| {
|
|
window.render_frame(cx);
|
|
assert!(window.find("cancel-transfer").visible());
|
|
// The running download's row, with the arrow pointing down.
|
|
assert_eq!(window.find(("queue-entry", 1u64)).label(), Some("0%"));
|
|
})
|
|
.unwrap();
|
|
}
|
|
|
|
#[gpui_kit::test]
|
|
async fn sftp_dragging_remote_rows_to_a_local_directory_asks_to_download(cx: &mut TestAppContext) {
|
|
let provider = Arc::new(FakeSftpProvider::default());
|
|
let (handle, workspace) = open_workspace_with_sftp(cx, provider);
|
|
open_test_explorer(cx, handle).await;
|
|
// By its name: the rest of the row draws a selection rectangle.
|
|
let drag_name = |cx: &mut TestAppContext, name: &str| {
|
|
cx.update_window(handle.into(), |_, window, cx| {
|
|
window.render_frame(cx);
|
|
let from = window
|
|
.within(("remote-pane", SFTP_TAB))
|
|
.find(ElementId::Name(format!("name:{name}").into()))
|
|
.bounds()
|
|
.center();
|
|
let to = window
|
|
.within(("local-pane", SFTP_TAB))
|
|
.find("file:目录")
|
|
.bounds()
|
|
.center();
|
|
window.drag(from, to, cx);
|
|
})
|
|
.unwrap();
|
|
cx.run_until_parked();
|
|
};
|
|
let summary = |cx: &mut TestAppContext| {
|
|
cx.update_window(handle.into(), |_, window, cx| {
|
|
window.render_frame(cx);
|
|
assert_eq!(
|
|
window.find("download-target").value(),
|
|
Some("/local/tester/目录")
|
|
);
|
|
window.find("transfer-summary").label().unwrap().to_string()
|
|
})
|
|
.unwrap()
|
|
};
|
|
|
|
// A name that is not selected is selected alone and goes alone.
|
|
cx.update_window(handle.into(), |_, window, cx| {
|
|
window
|
|
.within(("remote-pane", SFTP_TAB))
|
|
.click("name:文件 甲.txt", cx)
|
|
})
|
|
.unwrap();
|
|
cx.run_until_parked();
|
|
drag_name(cx, "文件 乙.txt");
|
|
cx.update(|cx| assert_eq!(pane_selection(&workspace, true, cx), ["文件 乙.txt"]));
|
|
assert!(summary(cx).ends_with(" 下载 1 个文件"));
|
|
cx.update_window(handle.into(), |_, window, cx| window.press("escape", cx))
|
|
.unwrap();
|
|
cx.run_until_parked();
|
|
|
|
// A selected name takes the whole selection with it.
|
|
cx.update_window(handle.into(), |_, window, cx| {
|
|
modified_click(
|
|
window,
|
|
("remote-pane", SFTP_TAB),
|
|
"name:文件 甲.txt",
|
|
gpui_kit::Modifiers::secondary_key(),
|
|
cx,
|
|
)
|
|
})
|
|
.unwrap();
|
|
cx.run_until_parked();
|
|
drag_name(cx, "文件 乙.txt");
|
|
cx.update(|cx| {
|
|
assert_eq!(
|
|
pane_selection(&workspace, true, cx),
|
|
["文件 乙.txt", "文件 甲.txt"]
|
|
)
|
|
});
|
|
assert!(summary(cx).ends_with(" 下载 2 个文件"));
|
|
}
|
|
|
|
/// A file drag shows what it will do only over the pane that takes it; over
|
|
/// its own list the pointer says no instead, as in WinSCP.
|
|
#[gpui_kit::test]
|
|
async fn sftp_a_file_drag_shows_its_preview_only_over_the_other_pane(cx: &mut TestAppContext) {
|
|
use gpui_kit::{MouseDownEvent, MouseUpEvent};
|
|
let provider = Arc::new(FakeSftpProvider::default());
|
|
let (handle, _) = open_workspace_with_sftp(cx, provider);
|
|
open_test_explorer(cx, handle).await;
|
|
let move_to = |window: &mut gpui_kit::Window, position, cx: &mut App| {
|
|
window.dispatch_event(
|
|
MouseMoveEvent {
|
|
position,
|
|
pressed_button: Some(MouseButton::Left),
|
|
modifiers: gpui_kit::Modifiers::default(),
|
|
}
|
|
.to_platform_input(),
|
|
cx,
|
|
);
|
|
window.render_frame(cx);
|
|
window
|
|
.try_find("file-drag-preview")
|
|
.and_then(|preview| preview.label().map(str::to_string))
|
|
};
|
|
cx.update_window(handle.into(), |_, window, cx| {
|
|
window.render_frame(cx);
|
|
let local = window.within(("local-pane", SFTP_TAB));
|
|
let from = local.find("name:文件 甲.txt").bounds().center();
|
|
let still_local = local.find("size:目录").bounds().center();
|
|
let remote = window
|
|
.within(("remote-pane", SFTP_TAB))
|
|
.find("size:文件 乙.txt")
|
|
.bounds()
|
|
.center();
|
|
window.dispatch_event(
|
|
MouseDownEvent {
|
|
button: MouseButton::Left,
|
|
position: from,
|
|
modifiers: gpui_kit::Modifiers::default(),
|
|
click_count: 1,
|
|
first_mouse: false,
|
|
}
|
|
.to_platform_input(),
|
|
cx,
|
|
);
|
|
// Dragging, but still over its own list: no preview.
|
|
assert_eq!(move_to(window, still_local, cx), None);
|
|
// Over the other pane it says what a drop does.
|
|
assert_eq!(
|
|
move_to(window, remote, cx).as_deref(),
|
|
Some("上传 1 个项目")
|
|
);
|
|
// And back.
|
|
assert_eq!(move_to(window, still_local, cx), None);
|
|
// Let go where nothing takes it.
|
|
window.dispatch_event(
|
|
MouseUpEvent {
|
|
button: MouseButton::Left,
|
|
position: still_local,
|
|
modifiers: gpui_kit::Modifiers::default(),
|
|
click_count: 1,
|
|
}
|
|
.to_platform_input(),
|
|
cx,
|
|
);
|
|
})
|
|
.unwrap();
|
|
cx.run_until_parked();
|
|
cx.update_window(handle.into(), |_, window, cx| {
|
|
window.render_frame(cx);
|
|
assert!(window.try_find("upload-confirm").is_none());
|
|
})
|
|
.unwrap();
|
|
}
|