Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / unsafe 落点守门(集合必须恰好一个文件) (push) Canceled after 0s
Guards / design 零第三方依赖守门 (push) Canceled after 0s
Guards / presenter 零 UI 守门 (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Dev Guards / 架构与合规守门(同 main 口径) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
M187/M188 并入后全仓 fmt --check 为红(CI 的 fmt job 会红)。 本次仅 cargo fmt --all 的换行/缩进变化,无语义改动; cargo fmt --check 与 cargo check 均已复验为绿。
248 lines
11 KiB
Rust
248 lines
11 KiB
Rust
//! 「General」分组自己的断言(M106 `I18NFOLLOW` 重写:语言行换成真控件)。
|
||
//!
|
||
//! **归 language+appearance 线**。接真控件那天要改**两个**文件(M101 r2 删掉了原先的第 3 处跨
|
||
//! section 绊线):`settings/general.rs` + 本文件 `the_general_group_is_still_demo_rows`。
|
||
//!
|
||
//! M106(`I18NFOLLOW`)已经接了这一组的语言行。用户报的 bug 有两半:**界面全英文**(探测对了
|
||
//! 但没人用)与**设置页写着 `English (System)`**(一行硬编码假值)。本文件的断言守后半:那行
|
||
//! 假值不许回来,语言行的三档必须真的能在两套目录里取到词。
|
||
//!
|
||
//! 接线形状:语言行的正文由 [`general::body`] 画(入口是 `pages::settings` 的 `GENERAL_GROUP`
|
||
//! 按下标分发),`GROUP.body` 仍是 `None`——那个槽位现在带**生效语言**(`Lang`,M179)但仍是
|
||
//! `States` + 语言,装不下「当前语言档位」(`LangChoice`)。所以 M101 r2 的
|
||
//! 「本组接真控件只需改两个文件」这条口径,在语言行上**仍然是两个文件**。
|
||
|
||
use super::super::general;
|
||
use unlucky_design::icons::IconKind;
|
||
use unlucky_i18n::LangChoice;
|
||
use unlucky_i18n::{Key, Lang};
|
||
|
||
/// 这一组的**静态演示行**:语言那一行已经换真控件,所以只剩三行。
|
||
///
|
||
/// 抓得住:①四条线并行合并时把别人的行粘到本文件;②语言行被「顺手」加回静态表——
|
||
/// 那等于把用户看到的假值又写回去(它的值必须跟着语言变,静态表做不到)。
|
||
/// 抓不住:真控件画出来长什么样(那是截图验收的事,见 `DevDocs/artifacts/m106/`)。
|
||
#[test]
|
||
fn the_general_group_keeps_theme_startup_and_close_as_static_rows() {
|
||
let rows: Vec<(&str, &str)> = general::GROUP.rows.to_vec();
|
||
assert_eq!(
|
||
rows,
|
||
vec![
|
||
("Theme", "Follow system"),
|
||
("Startup page", "Instances"),
|
||
("Close button", "Exit to tray"),
|
||
]
|
||
);
|
||
}
|
||
|
||
/// **M168(D28 后半)**:中文字体下载状态行的四种状态都得有话说,且两套目录都不许 `⟪missing⟫`。
|
||
///
|
||
/// 抓得住:①只画「下载成功」一种(失败的用户会以为程序坏了);②失败态的文案写成「保持英文」
|
||
/// (D28 的实际行为是**继续用系统字体**,界面语言不变);③占位符没被替换(用户会看到 `{family}`);
|
||
/// ④新 key 只进了英文表(中文界面上出现 `⟪missing⟫`)。
|
||
/// 抓不住:真下载时的样子(那要联网)、字体有没有真的出字(截图 + 真机,见报告)。
|
||
#[test]
|
||
fn the_font_status_row_speaks_in_four_states_and_two_languages() {
|
||
use crate::fonts::install::{CjkInstall, FetchClass};
|
||
|
||
let states = [
|
||
CjkInstall::Downloading { bytes: 17_772_300 },
|
||
CjkInstall::Ready {
|
||
family: "Noto Sans SC".to_owned(),
|
||
from_cache: true,
|
||
},
|
||
CjkInstall::Failed {
|
||
class: FetchClass::Network,
|
||
message: "timeout".to_owned(),
|
||
},
|
||
CjkInstall::System {
|
||
family: "Microsoft YaHei".to_owned(),
|
||
},
|
||
CjkInstall::NotNeeded,
|
||
CjkInstall::Unprobeable,
|
||
CjkInstall::Unknown,
|
||
];
|
||
|
||
for lang in Lang::ALL {
|
||
let catalog = lang.catalog();
|
||
assert!(
|
||
catalog.get(Key::FontCjkStatusLabel).is_some(),
|
||
"{} 目录里缺中文字体那一行的标签",
|
||
lang.tag()
|
||
);
|
||
for state in &states {
|
||
let (value, hint) = general::font_status(&catalog, state);
|
||
assert!(!value.is_empty(), "{state:?} 没话说");
|
||
assert!(!value.contains('{'), "{state:?} 占位符没替换:{value}");
|
||
assert!(
|
||
!value.contains(unlucky_i18n::catalog::MISSING),
|
||
"{state:?} 在 {} 目录里缺译:{value}",
|
||
lang.tag()
|
||
);
|
||
if let Some(hint) = hint {
|
||
assert!(!hint.contains('{'), "{state:?} 的提示没替换:{hint}");
|
||
}
|
||
}
|
||
}
|
||
|
||
// 失败态必须说清三类原因之一(而不是一句「失败了」)。
|
||
let zh = Lang::ZhCn.catalog();
|
||
for (class, reason) in [
|
||
(FetchClass::Network, "网络不通"),
|
||
(FetchClass::Integrity, "镜像内容校验不通过"),
|
||
(FetchClass::Disk, "写不进数据目录"),
|
||
] {
|
||
let (value, _) = general::font_status(
|
||
&zh,
|
||
&CjkInstall::Failed {
|
||
class,
|
||
message: "x".to_owned(),
|
||
},
|
||
);
|
||
assert!(value.contains(reason), "失败原因没进文案:{value}");
|
||
}
|
||
|
||
// 下载中写的是**真实**字节数(17 772 300 B → 16.9 MiB),不是假百分比。
|
||
let (value, _) = general::font_status(&zh, &CjkInstall::Downloading { bytes: 17_772_300 });
|
||
assert!(
|
||
value.contains("16.9 MB"),
|
||
"17 772 300 B ≈ 16.9 MiB:{value}"
|
||
);
|
||
assert!(!value.contains('%'), "没有真进度就不许写百分比:{value}");
|
||
}
|
||
|
||
/// 左列图标(`IconKind::Settings`)。分组图标是左列的语义标签,换掉就是换观感。
|
||
#[test]
|
||
fn the_general_group_keeps_its_icon() {
|
||
assert_eq!(general::GROUP.icon, IconKind::Settings);
|
||
}
|
||
|
||
/// **回归绊线(用户原话)**:`"English (System)"` 那条硬编码字面量不许回来。
|
||
///
|
||
/// 抓得住:①原来那条「这一组还没接真控件」在接上之后没更新(静默变假绿);
|
||
/// ②语言行**退回静态演示行**(哪怕换个说法,只要还写着「English」就是同一个 bug 的复盘)。
|
||
/// 抓不住:界面上真控件长什么样、三档选项名对不对(下一条与截图各管一段)。
|
||
///
|
||
/// 现状(M106):`body` 仍是 `None`(语言行走 `GENERAL_GROUP` 的按下标分发),但这一组**已经**
|
||
/// 有真控件了——所以这里断言的**不再是「还没接」**,而是「不许回到静态行」。
|
||
#[test]
|
||
fn the_language_row_is_not_a_static_string() {
|
||
for (label, value) in general::GROUP.rows {
|
||
assert_ne!(
|
||
*label, "Language",
|
||
"语言行必须走真控件(pages::Language),不许回到静态演示行"
|
||
);
|
||
assert!(
|
||
!value.contains("English"),
|
||
"静态行里不许再出现写死的语言名:{value}"
|
||
);
|
||
}
|
||
}
|
||
|
||
/// 语言行**三档**,且在**两套目录**里都有词(中文界面不该出现 `⟪missing⟫`)。
|
||
///
|
||
/// 抓得住:①少一档(用户就没法切回去了);②标签 key 只加进了中文表(英文界面上一档变成
|
||
/// `⟪missing⟫`);③回显那句的占位符名字与调用点对不上(会原样印出 `{language}`)。
|
||
/// 抓不住:译文翻得对不对(金表 + `chinese_catalog_is_actually_chinese` 管那一层)。
|
||
#[test]
|
||
fn the_language_row_offers_three_choices_in_both_languages() {
|
||
assert_eq!(
|
||
LangChoice::ALL.len(),
|
||
3,
|
||
"跟随系统 / 简体中文 / 英文,少一档就切不回来"
|
||
);
|
||
|
||
for lang in Lang::ALL {
|
||
let catalog = lang.catalog();
|
||
for choice in LangChoice::ALL {
|
||
assert!(
|
||
catalog.get(choice.label_key()).is_some(),
|
||
"{} 在 {} 目录里没词",
|
||
choice.label_key().name(),
|
||
lang.tag()
|
||
);
|
||
}
|
||
|
||
// 「界面当前使用:⟨语言名⟩」——中文系统下这句必须真的出现「简体中文」。
|
||
let effective = catalog.text(LangChoice::Fixed(*lang).label_key());
|
||
let line = catalog.render(Key::SettingsLanguageEffective, &[("language", effective)]);
|
||
assert!(line.contains(effective), "回显没带上语言名:{line}");
|
||
assert!(!line.contains('{'), "占位符没被替换:{line}");
|
||
}
|
||
|
||
// 中文系统 + 跟随系统 ⇒ 回显是「简体中文」(用户报的 bug 的直接判据)。
|
||
let zh = Lang::ZhCn.catalog();
|
||
assert_eq!(
|
||
zh.render(
|
||
Key::SettingsLanguageEffective,
|
||
&[(
|
||
"language",
|
||
zh.text(LangChoice::Fixed(Lang::ZhCn).label_key())
|
||
)]
|
||
),
|
||
"界面当前使用:简体中文"
|
||
);
|
||
}
|
||
|
||
/// **绊线**:这一组仍不走 `Group.body`。
|
||
///
|
||
/// 为什么不是「接了真控件」:语言行的入口是 `pages::Language`(`GENERAL_GROUP` 那条分发),
|
||
/// 而 `Group.body` 那个槽位只拿到 `States` 与**生效语言**(`Lang`,M179)——装不下「当前语言档位」。
|
||
/// 谁把语言行塞进 `body`,就得先给它换签名(那会改到别人那个组的文件),这条断言逼他先想一遍。
|
||
#[test]
|
||
fn the_general_group_does_not_use_the_appearance_body_slot() {
|
||
assert!(
|
||
general::GROUP.body.is_none(),
|
||
"General 的真控件走 pages::Language 入口(见 settings/mod.rs 的 GENERAL_GROUP),不是 body"
|
||
);
|
||
}
|
||
|
||
/// **M188:日志文件夹那一行**的文案必须与实现一致(用户投诉「找不到崩溃日志」的直接判据)。
|
||
///
|
||
/// 抓得住:①文案里的路径写错(写成 `data/logs`、`cache/game`);②滚动上限与
|
||
/// `core::launch::launcher_log::MAX_BYTES` 脱钩(文案说 1 MiB、实现改成 4 MiB);
|
||
/// ③旧目录被静默改成「已迁移」(那会让用户以为旧日志没了);
|
||
/// ④某条 key 只进了英文表(中文界面上是 `⟪missing⟫`)。
|
||
/// 抓不住:界面画出来长什么样(截图验收管那一层)。
|
||
#[test]
|
||
fn the_logs_row_names_the_real_paths_and_the_real_limit() {
|
||
use unlucky_core::launch::launcher_log;
|
||
use unlucky_core::platform::layout::{LauncherLayout, GAME_LOG_DIR, LOGS_DIR};
|
||
|
||
// 实现侧的真值:分区名与上限都只从 core 来。
|
||
assert_eq!(LOGS_DIR, "logs");
|
||
assert_eq!(GAME_LOG_DIR, "game");
|
||
assert_eq!(launcher_log::MAX_BYTES, 1024 * 1024, "文案说 1 MiB");
|
||
let layout = LauncherLayout::new(std::path::Path::new("/tmp/ull-home"));
|
||
assert_eq!(
|
||
layout.game_logs(),
|
||
std::path::Path::new("/tmp/ull-home/logs/game")
|
||
);
|
||
|
||
for lang in Lang::ALL {
|
||
let catalog = lang.catalog();
|
||
let hint = catalog.text(Key::SettingsLogsFolderHint);
|
||
for needle in [
|
||
"launcher.log",
|
||
"1 MiB",
|
||
&format!("{LOGS_DIR}/{GAME_LOG_DIR}/"),
|
||
"data/agent/sessions",
|
||
] {
|
||
assert!(
|
||
hint.contains(needle),
|
||
"{} 缺 `{needle}`:{hint}",
|
||
lang.tag()
|
||
);
|
||
}
|
||
assert!(!hint.contains('⟪'), "{} 目录里缺词:{hint}", lang.tag());
|
||
assert!(
|
||
!catalog.text(Key::SettingsLogsFolder).contains('⟪'),
|
||
"{} 缺「日志文件夹」:{}",
|
||
lang.tag(),
|
||
catalog.text(Key::SettingsLogsFolder)
|
||
);
|
||
assert!(!catalog.text(Key::SettingsLogsCopy).contains('⟪'));
|
||
assert!(!catalog.text(Key::SettingsLogsUnknown).contains('⟪'));
|
||
}
|
||
}
|