200 lines
8.4 KiB
Rust
200 lines
8.4 KiB
Rust
//! The Simplified Chinese UI, in its own test binary: the UI language is process-wide, so it can't
|
||
//! share one with the English UI tests. Chinese is drawn with the system's Chinese font; where
|
||
//! there is none (CI, minimal Linux installs), the UI must stay in English and say why.
|
||
//! `ARTCRAFT_LAUNCHER_SNAPSHOTS=<dir>` also saves screenshots (run with `-- --test-threads=1`).
|
||
|
||
use std::cell::{Cell, RefCell};
|
||
use std::rc::Rc;
|
||
|
||
use artcraft_launcher_core::protocol::{Backend, Capabilities, Command, Event, JobPhase, LatestRelease, Origin, PausedDownload, Source};
|
||
use artcraft_launcher_core::release::{Asset, Release};
|
||
use artcraft_launcher_core::status::InstallSource;
|
||
use artcraft_launcher_core::{Arch, Installed, Os, Platform, SUITE, version};
|
||
use artcraft_launcher_ui_egui::settings::ViewMode;
|
||
use artcraft_launcher_ui_egui::{Host, LauncherUi, Settings};
|
||
use egui_kittest::Harness;
|
||
use egui_kittest::kittest::Queryable;
|
||
|
||
const NOW: i64 = 1_791_500_000;
|
||
|
||
struct Fake {
|
||
events: Vec<Event>,
|
||
sent: Rc<RefCell<Vec<Command>>>,
|
||
manage: bool,
|
||
clock: Rc<Cell<i64>>,
|
||
}
|
||
|
||
impl Backend for Fake {
|
||
fn send(&mut self, command: Command) {
|
||
self.sent.borrow_mut().push(command);
|
||
}
|
||
fn poll(&mut self) -> Vec<Event> {
|
||
std::mem::take(&mut self.events)
|
||
}
|
||
fn capabilities(&self) -> Capabilities {
|
||
Capabilities { manage_apps: self.manage, reveal_label: "Show in Finder", self_update: self.manage }
|
||
}
|
||
fn platform(&self) -> Option<Platform> {
|
||
Some(Platform { os: Os::MacOs, arch: Arch::Arm64 })
|
||
}
|
||
fn now(&self) -> i64 {
|
||
self.clock.get()
|
||
}
|
||
}
|
||
|
||
struct NoHost;
|
||
impl Host for NoHost {}
|
||
|
||
fn release(id: &str, v: &str, at: i64) -> Release {
|
||
let name = format!("{id}-{v}-macos-universal.dmg");
|
||
Release {
|
||
tag: format!("v{v}"),
|
||
version: version::parse(v),
|
||
name: format!("v{v}"),
|
||
published_at: Some(at),
|
||
html_url: format!("https://github.com/storytold/{id}/releases/tag/v{v}"),
|
||
notes: "## What's Changed\n* Faster things by @a in https://github.com/storytold/x/pull/1".into(),
|
||
prerelease: false,
|
||
assets: vec![Asset {
|
||
name: name.clone(),
|
||
size: 150 << 20,
|
||
url: format!("https://github.com/storytold/{id}/releases/download/v{v}/{name}"),
|
||
sha256: None,
|
||
}],
|
||
}
|
||
}
|
||
|
||
/// A realistic mix: updates, up-to-date apps, a download in progress, one app with no release.
|
||
fn events() -> Vec<Event> {
|
||
let mut ev = Vec::new();
|
||
let versions = [
|
||
("photocraft", "0.5.0"),
|
||
("vectorcraft", "0.7.0"),
|
||
("filmcraft", "0.4.0"),
|
||
("lightcraft", "0.4.0"),
|
||
("pdfcraft", "0.4.0"),
|
||
("effectcraft", "0.6.0"),
|
||
("designcraft", "0.4.0"),
|
||
("wordcraft", "0.3.0"),
|
||
("deckcraft", "0.3.0"),
|
||
("gridcraft", "0.3.0"),
|
||
("cadcraft", "0.3.0"),
|
||
];
|
||
for (i, (id, v)) in versions.iter().enumerate() {
|
||
ev.push(Event::Latest {
|
||
app: (*id).into(),
|
||
latest: LatestRelease::Found(release(id, v, NOW - 86_400 - i as i64 * 600)),
|
||
from: Origin::ArtCraftApi,
|
||
});
|
||
}
|
||
ev.push(Event::Latest { app: "soundcraft".into(), latest: LatestRelease::None, from: Origin::ArtCraftApi });
|
||
let installed = [("photocraft", "0.4.1"), ("lightcraft", "0.4.0"), ("wordcraft", "0.3.0"), ("effectcraft", "0.5.0")];
|
||
for app in &SUITE {
|
||
let inst = installed.iter().find(|(id, _)| *id == app.id).map(|(_, v)| Installed {
|
||
version: version::parse(v),
|
||
source: InstallSource::MacApp,
|
||
location: format!("/Applications/{}.app", app.name),
|
||
package: None,
|
||
});
|
||
ev.push(Event::Detected { app: app.id.into(), installed: inst });
|
||
}
|
||
ev.push(Event::Progress { app: "filmcraft".into(), phase: JobPhase::Downloading { done: 48 << 20, total: 131 << 20 } });
|
||
ev.push(Event::Paused { app: "deckcraft".into(), download: Some(PausedDownload { done: 30 << 20, total: 120 << 20 }) });
|
||
ev.push(Event::Progress { app: "gridcraft".into(), phase: JobPhase::Reconnecting { done: 10 << 20, total: 90 << 20 } });
|
||
ev.push(Event::CheckFinished { at: NOW - 180, source: Source::GitHubApi, rate_limited_until: None });
|
||
ev
|
||
}
|
||
|
||
fn harness_language(
|
||
view: ViewMode,
|
||
theme: egui::Theme,
|
||
manage: bool,
|
||
language: &str,
|
||
) -> (Harness<'static, Option<LauncherUi>>, Rc<RefCell<Vec<Command>>>) {
|
||
artcraft_launcher_ui_egui::i18n::set_test_lang(artcraft_launcher_ui_egui::i18n::Lang::EN);
|
||
let sent = Rc::new(RefCell::new(Vec::new()));
|
||
let sent2 = sent.clone();
|
||
let mut backend = Some(Fake { events: events(), sent: sent2, manage, clock: Rc::new(Cell::new(NOW)) });
|
||
let settings = Settings { view, notify: false, language: language.into(), ..Settings::default() };
|
||
let harness = Harness::builder().with_size(egui::vec2(1180.0, 900.0)).with_theme(theme).build_ui_state(
|
||
move |ui, state: &mut Option<LauncherUi>| {
|
||
if state.is_none()
|
||
&& let Some(b) = backend.take()
|
||
{
|
||
let mut s = settings.clone();
|
||
s.theme = if theme == egui::Theme::Dark {
|
||
artcraft_launcher_ui_egui::theme::ThemeChoice::Dark
|
||
} else {
|
||
artcraft_launcher_ui_egui::theme::ThemeChoice::Light
|
||
};
|
||
*state = Some(LauncherUi::new(ui.ctx(), Box::new(b), s));
|
||
}
|
||
if let Some(l) = state.as_mut() {
|
||
l.logic(&ui.ctx().clone(), &mut NoHost);
|
||
l.ui(ui, &mut NoHost);
|
||
}
|
||
},
|
||
None,
|
||
);
|
||
(harness, sent)
|
||
}
|
||
|
||
fn snapshot(h: &mut Harness<'_, Option<LauncherUi>>, name: &str) {
|
||
let Some(dir) = std::env::var_os("ARTCRAFT_LAUNCHER_SNAPSHOTS") else { return };
|
||
let img = h.render().unwrap();
|
||
img.save(std::path::Path::new(&dir).join(format!("{name}.png"))).unwrap();
|
||
}
|
||
|
||
// One test: the UI language is process-wide.
|
||
#[test]
|
||
fn simplified_chinese_switches_persists_and_keeps_install_actions() {
|
||
let (mut h, sent) = harness_language(ViewMode::Grid, egui::Theme::Dark, true, "en");
|
||
h.run_steps(6);
|
||
h.get_by_label("Settings").click();
|
||
h.run_steps(3);
|
||
h.get_by_value("English").click();
|
||
h.run_steps(2);
|
||
// Opening Settings doesn't load the (large) Chinese font: the picker names it in English.
|
||
assert!(!artcraft_launcher_ui_egui::theme::cjk_font_loaded());
|
||
h.get_by_label("Simplified Chinese").click();
|
||
h.run_steps(3);
|
||
if !artcraft_launcher_ui_egui::theme::cjk_font_available() {
|
||
// No Chinese font: choosing it keeps the UI in English, and Settings says why.
|
||
assert_eq!(h.state().as_ref().unwrap().settings.language, "zh-CN");
|
||
h.get_by_label_contains("No Chinese font was found");
|
||
h.get_by_label("Notify me when an installed app has an update");
|
||
snapshot(&mut h, "settings-zh-CN-no-font");
|
||
return;
|
||
}
|
||
// Choosing it loaded the font: now the picker shows both names.
|
||
h.get_by_value("Simplified Chinese (简体中文)");
|
||
h.get_by_label("已安装应用有更新时通知我");
|
||
snapshot(&mut h, "settings-zh-CN-dark");
|
||
let settings = h.state().as_ref().unwrap().settings.clone();
|
||
assert_eq!(settings.language, "zh-CN");
|
||
let saved = serde_json::to_string(&settings).unwrap();
|
||
let restored: Settings = serde_json::from_str(&saved).unwrap();
|
||
assert_eq!(restored.language, "zh-CN");
|
||
h.get_by_label("完成").click();
|
||
h.run_steps(3);
|
||
h.get_by_label("图像编辑");
|
||
h.get_by_label_contains("上次检查:3 分钟前 · 来源:ArtCraft API");
|
||
h.get_by_label("更新至 0.5.0");
|
||
// Product names stay in English.
|
||
h.get_by_label("Launcher");
|
||
snapshot(&mut h, "grid-zh-CN-dark");
|
||
h.get_by_label("全部更新(2)").click();
|
||
h.run_steps(2);
|
||
let installs: Vec<_> = sent.borrow().iter().filter_map(|c| if let Command::Install { app } = c { Some(app.clone()) } else { None }).collect();
|
||
assert_eq!(installs, ["photocraft", "effectcraft"]);
|
||
|
||
let (mut table, _) = harness_language(ViewMode::Table, egui::Theme::Light, true, &restored.language);
|
||
table.run_steps(6);
|
||
table.get_by_label("已安装版本");
|
||
assert!(table.query_all_by_label_contains("年10月").count() >= 1);
|
||
snapshot(&mut table, "table-zh-CN-light");
|
||
let (mut web, _) = harness_language(ViewMode::Grid, egui::Theme::Dark, false, &restored.language);
|
||
web.run_steps(6);
|
||
assert!(web.query_all_by_label("下载 macOS 版").count() >= 3);
|
||
}
|