- 仍是一个测试程序(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 改路径
320 lines
11 KiB
Rust
320 lines
11 KiB
Rust
//! 设置: the tab, 外观, 外部 CLI and the title bar's theme switch.
|
||
|
||
use crate::support::*;
|
||
|
||
#[gpui_kit::test]
|
||
fn theme_toggle_flips_mode(cx: &mut TestAppContext) {
|
||
let (handle, _) = open_workspace(cx);
|
||
let before = cx.update(|cx| {
|
||
let theme = cx.theme();
|
||
assert_eq!(theme.list_hover, theme.tokens.list_hover.color);
|
||
assert!(theme.list_hover.a > 0.9);
|
||
theme.is_dark()
|
||
});
|
||
|
||
cx.update_window(handle.into(), |_, window, cx| {
|
||
window.render_frame(cx);
|
||
window.click("theme-toggle", cx);
|
||
})
|
||
.unwrap();
|
||
cx.run_until_parked();
|
||
|
||
let after = cx.update(|cx| {
|
||
let theme = cx.theme();
|
||
assert_eq!(theme.list_hover, theme.tokens.list_hover.color);
|
||
assert!(theme.list_hover.a > 0.9);
|
||
theme.is_dark()
|
||
});
|
||
assert_ne!(before, after);
|
||
}
|
||
|
||
#[gpui_kit::test]
|
||
fn settings_open_from_the_host_list_as_one_tab(cx: &mut TestAppContext) {
|
||
let (handle, workspace) = open_workspace(cx);
|
||
|
||
cx.update_window(handle.into(), |_, window, cx| {
|
||
window.render_frame(cx);
|
||
window.click("open-settings", cx);
|
||
})
|
||
.unwrap();
|
||
cx.run_until_parked();
|
||
|
||
cx.update_window(handle.into(), |_, window, cx| {
|
||
window.render_frame(cx);
|
||
assert!(window.find("settings").visible());
|
||
assert_eq!(window.find("settings").focused(), Some(true));
|
||
assert_eq!(window.find("settings-tab").label(), Some("设置"));
|
||
// Two columns: the first category, 外观, is shown until another is
|
||
// picked. `Settings` names its category rows by position.
|
||
assert_eq!(appearance_dropdown(window, 0).as_deref(), Some("简体中文"));
|
||
assert!(window.try_find("terminal-font-preview").is_none());
|
||
window.within("settings").click("0-1", cx);
|
||
})
|
||
.unwrap();
|
||
cx.run_until_parked();
|
||
|
||
cx.update_window(handle.into(), |_, window, cx| {
|
||
window.render_frame(cx);
|
||
// 终端 is the second.
|
||
assert!(window.find("terminal-font-preview").visible());
|
||
window.click(("terminal-tab", INITIAL_WEB_TERMINAL), cx);
|
||
})
|
||
.unwrap();
|
||
cx.run_until_parked();
|
||
|
||
cx.update_window(handle.into(), |_, window, cx| {
|
||
window.render_frame(cx);
|
||
assert!(window.find(("terminal", INITIAL_WEB_TERMINAL)).visible());
|
||
assert!(window.try_find("settings").is_none());
|
||
// Opening settings again brings the same tab forward.
|
||
#[cfg(target_os = "macos")]
|
||
window.press("cmd-,", cx);
|
||
#[cfg(not(target_os = "macos"))]
|
||
window.press("ctrl-,", cx);
|
||
})
|
||
.unwrap();
|
||
cx.run_until_parked();
|
||
|
||
cx.update_window(handle.into(), |_, window, cx| {
|
||
window.render_frame(cx);
|
||
assert!(window.find("settings").visible());
|
||
assert_eq!(window.find("settings").focused(), Some(true));
|
||
})
|
||
.unwrap();
|
||
cx.update(|cx| {
|
||
let settings = workspace.read(cx).settings_tab().expect("settings open");
|
||
let group = settings.read(cx).tab_group().unwrap().upgrade().unwrap();
|
||
// The two host terminals and a single settings tab.
|
||
assert_eq!(group.read(cx).panels().len(), 3);
|
||
});
|
||
|
||
// Opening it while it is displayed changes nothing.
|
||
cx.update_window(handle.into(), |_, window, cx| {
|
||
window.render_frame(cx);
|
||
window.click("open-settings", cx);
|
||
})
|
||
.unwrap();
|
||
cx.run_until_parked();
|
||
cx.update_window(handle.into(), |_, window, cx| {
|
||
window.render_frame(cx);
|
||
assert!(window.find("settings").visible());
|
||
assert_eq!(window.find("settings").focused(), Some(true));
|
||
// ⌘W closes it like any other tab.
|
||
window.press("cmd-w", cx);
|
||
})
|
||
.unwrap();
|
||
cx.run_until_parked();
|
||
|
||
cx.update_window(handle.into(), |_, window, cx| {
|
||
window.render_frame(cx);
|
||
assert!(window.try_find("settings-tab").is_none());
|
||
assert!(window.try_find("settings").is_none());
|
||
})
|
||
.unwrap();
|
||
cx.update(|cx| assert!(workspace.read(cx).settings_tab().is_none()));
|
||
}
|
||
|
||
/// The label of one dropdown in the 外观 page's 常规 group. `Settings` names
|
||
/// its groups, items and dropdown buttons by position.
|
||
/// Open the settings tab on 外部 CLI, the third category.
|
||
fn open_external_cli_settings(cx: &mut TestAppContext, handle: WindowHandle<Root>) {
|
||
cx.update_window(handle.into(), |_, window, cx| {
|
||
window.render_frame(cx);
|
||
window.click("open-settings", cx);
|
||
})
|
||
.unwrap();
|
||
cx.run_until_parked();
|
||
cx.update_window(handle.into(), |_, window, cx| {
|
||
window.render_frame(cx);
|
||
window.within("settings").click("0-2", cx);
|
||
})
|
||
.unwrap();
|
||
cx.run_until_parked();
|
||
}
|
||
|
||
#[gpui_kit::test]
|
||
fn the_external_cli_switch_is_off_until_turned_on(cx: &mut TestAppContext) {
|
||
let (handle, workspace) = open_workspace(cx);
|
||
let enabled = |cx: &mut TestAppContext| {
|
||
workspace.read_with(cx, |workspace, cx| {
|
||
workspace
|
||
.settings()
|
||
.read(cx)
|
||
.settings()
|
||
.external_cli
|
||
.enabled
|
||
})
|
||
};
|
||
open_external_cli_settings(cx, handle);
|
||
assert!(!enabled(cx));
|
||
|
||
cx.update_window(handle.into(), |_, window, cx| {
|
||
window.render_frame(cx);
|
||
// 访问控制 › 启用外部 CLI.
|
||
let switch = window
|
||
.within("settings")
|
||
.within("group-0")
|
||
.within("item-0")
|
||
.find("check");
|
||
assert_eq!(switch.checked(), Some(false));
|
||
window
|
||
.within("settings")
|
||
.within("group-0")
|
||
.within("item-0")
|
||
.click("check", cx);
|
||
})
|
||
.unwrap();
|
||
cx.run_until_parked();
|
||
assert!(enabled(cx));
|
||
}
|
||
|
||
#[gpui_kit::test]
|
||
fn without_a_home_to_install_into_the_external_cli_offers_nothing(cx: &mut TestAppContext) {
|
||
let (handle, _) = open_workspace(cx);
|
||
open_external_cli_settings(cx, handle);
|
||
cx.update_window(handle.into(), |_, window, cx| {
|
||
window.render_frame(cx);
|
||
assert_eq!(
|
||
window.find("skill-status-codex").label(),
|
||
Some("此系统暂不支持安装")
|
||
);
|
||
assert!(window.try_find("skill-codex").is_none());
|
||
assert!(window.try_find("cli-binary").is_none());
|
||
// Copying the skill needs no home directory.
|
||
window.click("copy-agent-skill", cx);
|
||
})
|
||
.unwrap();
|
||
cx.run_until_parked();
|
||
assert_eq!(
|
||
cx.read_from_clipboard().and_then(|item| item.text()),
|
||
Some(shellrs::cli::SKILL.to_string())
|
||
);
|
||
}
|
||
|
||
#[gpui_kit::test]
|
||
fn agent_skills_install_where_each_agent_looks_and_come_off_again(cx: &mut TestAppContext) {
|
||
let (handle, workspace) = open_workspace(cx);
|
||
let root = tempfile::tempdir().unwrap();
|
||
let exe = root.path().join("app").join("shellrs");
|
||
std::fs::create_dir_all(exe.parent().unwrap()).unwrap();
|
||
std::fs::write(&exe, "binary").unwrap();
|
||
let paths = IntegrationPaths {
|
||
home: root.path().join("home"),
|
||
bin_link: root.path().join("bin").join("shellrs"),
|
||
exe,
|
||
user_path: None,
|
||
};
|
||
let codex = paths.skill_file(AgentKind::Codex);
|
||
workspace.update(cx, |workspace, cx| {
|
||
workspace.cli_integration().update(cx, |integration, cx| {
|
||
integration.set_paths(Some(paths.clone()), cx)
|
||
})
|
||
});
|
||
open_external_cli_settings(cx, handle);
|
||
|
||
let status = |cx: &mut TestAppContext, id: &'static str| {
|
||
cx.update_window(handle.into(), |_, window, cx| {
|
||
window.render_frame(cx);
|
||
window.find(id).label().map(str::to_string)
|
||
})
|
||
.unwrap()
|
||
};
|
||
assert_eq!(
|
||
status(cx, "skill-status-codex").as_deref(),
|
||
Some(format!("未安装({})", codex.display()).as_str())
|
||
);
|
||
|
||
// The row's button dispatches the install.
|
||
cx.update_window(handle.into(), |_, window, cx| {
|
||
window.render_frame(cx);
|
||
window.click("skill-codex", cx);
|
||
})
|
||
.unwrap();
|
||
cx.run_until_parked();
|
||
assert_eq!(
|
||
std::fs::read_to_string(&codex).unwrap(),
|
||
shellrs::cli::SKILL
|
||
);
|
||
assert_eq!(
|
||
status(cx, "skill-status-codex").as_deref(),
|
||
Some(format!("已安装于 {}", codex.display()).as_str())
|
||
);
|
||
// Only Codex's.
|
||
assert!(!paths.skill_file(AgentKind::ClaudeCode).exists());
|
||
|
||
cx.update_window(handle.into(), |_, window, cx| {
|
||
window.dispatch_action(Box::new(RemoveAgentSkill(AgentKind::Codex)), cx);
|
||
window.dispatch_action(Box::new(InstallCliCommand), cx);
|
||
})
|
||
.unwrap();
|
||
cx.run_until_parked();
|
||
assert!(!codex.exists());
|
||
assert_eq!(
|
||
status(cx, "cli-binary-status").as_deref(),
|
||
Some(format!("已安装于 {}", paths.bin_link.display()).as_str())
|
||
);
|
||
}
|
||
|
||
/// Menus are not driven here: a dropdown's item writes the settings store,
|
||
/// so the test writes it the same way and checks what follows.
|
||
#[gpui_kit::test]
|
||
fn the_appearance_setting_drives_the_theme_and_the_title_bar_switch(cx: &mut TestAppContext) {
|
||
let (handle, workspace) = open_workspace(cx);
|
||
let settings = cx.update(|cx| workspace.read(cx).settings().clone());
|
||
|
||
cx.update_window(handle.into(), |_, window, cx| {
|
||
window.render_frame(cx);
|
||
window.click("open-settings", cx);
|
||
})
|
||
.unwrap();
|
||
cx.run_until_parked();
|
||
cx.update_window(handle.into(), |_, window, cx| {
|
||
window.render_frame(cx);
|
||
// 外观 is the first category.
|
||
window.within("settings").click("0-0", cx);
|
||
})
|
||
.unwrap();
|
||
cx.run_until_parked();
|
||
|
||
cx.update_window(handle.into(), |_, window, cx| {
|
||
window.render_frame(cx);
|
||
assert_eq!(appearance_dropdown(window, 0).as_deref(), Some("简体中文"));
|
||
assert_eq!(appearance_dropdown(window, 1).as_deref(), Some("跟随系统"));
|
||
})
|
||
.unwrap();
|
||
|
||
cx.update(|cx| {
|
||
settings.update(cx, |settings, cx| {
|
||
settings.update(|settings| settings.appearance = Appearance::Dark, cx)
|
||
})
|
||
});
|
||
cx.run_until_parked();
|
||
cx.update(|cx| {
|
||
let theme = cx.theme();
|
||
assert!(theme.is_dark());
|
||
// Applied like the title bar switch always did it.
|
||
assert_eq!(theme.list_hover, theme.tokens.list_hover.color);
|
||
assert!(theme.list_hover.a > 0.9);
|
||
});
|
||
cx.update_window(handle.into(), |_, window, cx| {
|
||
window.render_frame(cx);
|
||
assert_eq!(appearance_dropdown(window, 1).as_deref(), Some("深色"));
|
||
// The title bar's switch picks the other appearance outright.
|
||
window.click("theme-toggle", cx);
|
||
})
|
||
.unwrap();
|
||
cx.run_until_parked();
|
||
|
||
cx.update(|cx| {
|
||
assert!(!cx.theme().is_dark());
|
||
let chosen = settings.read(cx).settings();
|
||
assert_eq!(chosen.appearance, Appearance::Light);
|
||
assert_eq!(chosen.language, InterfaceLanguage::SimplifiedChinese);
|
||
});
|
||
cx.update_window(handle.into(), |_, window, cx| {
|
||
window.render_frame(cx);
|
||
assert_eq!(appearance_dropdown(window, 1).as_deref(), Some("浅色"));
|
||
})
|
||
.unwrap();
|
||
}
|