Files
shell-rs/tests/ui/settings.rs
T
Xuz 388a1aa68a UI 测试按功能拆成模块:tests/ui.rs → tests/ui/
- 仍是一个测试程序(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 改路径
2026-10-01 14:53:33 +08:00

320 lines
11 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 设置: 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();
}