fix(app-iced): M120 r1 —— 徽标判据改回「类型是原版」;旧串黑名单翻成白名单
r1 复审 p2-1(真回归,我引入的):`loader_badge` 把「非原版 + 空版本号」也送进了
`Key::LoaderVanilla`。而 `host::game::loader_from_id("1.20.1-forge")` 这类 id 取不到版本段
(它按 `forge-` 找,id 却是 `-forge` 结尾),`LoaderInfo::badge()` 对它们给的正是品牌名
`Forge` / `NeoForge` / `OptiFine` —— 于是这三个显示成「原版 / Vanilla」。
判据改成「**加载器类型本身是原版** ⇒ 过词表,其余交给 `badge()`」:
- 修的是判据,不是加分支:函数注释本来就写着「只有原版这一支要过词表」,代码与注释相矛盾。
- 新回归测试 `the_loader_badge_keeps_the_brand_when_the_version_is_missing`:
`1.20.1-forge` / `1.20.1-neoforge` / `1.20.1-OptiFine` 三条 id 各断言列表副文案与 LOADER
卡的值都是品牌名(中英两套语言各跑一遍,品牌名两套语言相同)。
变异实测:把判据改回旧写法 ⇒ 立刻红(`left: "1.20.4 · Vanilla" / right: "1.20.4 · Forge"`,
与复审给的实证逐字相同)。
r1 复审 p2-2(不阻塞,按塔台要求顺手做):源码反扫从**黑名单 29 条旧串**翻成**白名单**。
- 先数过:五个页面源码里一共 13 个字面量,其中带 ASCII 单词的只有 6 条,全是专有名词 /
技术标识 / 占位符名 / 族名前缀(`{date} {time} UTC` / `isolated` / `shared` / `pid` /
`reason` / `Instances`),逐条写了理由。
- 判据反过来之后,往页面里**新写**一句英文也会红(复审抓到的哑点)——变异实测:
写进 `"Ready"` ⇒ 红;写回 `"ALL INSTANCES"` ⇒ 红。
- 反向锚保留:扫描器必须真扫到 ≥6 条、`Texts::of(` / `Header::new(texts.title())` /
`Key::Instances` / `Key::InstancesStage` 必须在用(防止断言空转)。
This commit is contained in:
1 parent
1a4e1fee12
commit
f86be2bd38
3 files changed
+122
-58
No files matched your search
@@ -32,17 +32,18 @@ pub(super) fn supporting_line(instance: &InstanceSummary, catalog: Catalog) -> S
|
||||
)
|
||||
}
|
||||
|
||||
/// 加载器徽标:原版走词表,其余是品牌 + 版本号(原样)。
|
||||
/// 加载器徽标:原版走词表,其余交给 `LoaderInfo::badge()`(品牌 + 版本号,原样)。
|
||||
///
|
||||
/// **判据只能是「加载器类型本身是原版」**。曾经写成「原版 **或** 版本号为空」⇒
|
||||
/// `1.20.1-forge` / `1.20.1-neoforge` / `1.20.1-OptiFine` 这类**取不到版本段**的 id(`badge()`
|
||||
/// 对它们给的就是品牌名 `Forge` / `NeoForge` / `OptiFine`)会被显示成「原版 / Vanilla」——
|
||||
/// 那是 M120 r1 的 p2-1 回归,`the_loader_badge_keeps_the_brand_when_the_version_is_missing` 守着。
|
||||
fn loader_badge(instance: &InstanceSummary, catalog: Catalog) -> String {
|
||||
// `badge()` 在「原版 / 版本号为空」两种情况都给 `kind.label()`(中文「原版」),只有这一支要过词表。
|
||||
if instance.loader.kind == LoaderKind::Vanilla || instance.loader.version.is_empty() {
|
||||
catalog.text(Key::LoaderVanilla).to_string()
|
||||
} else {
|
||||
format!(
|
||||
"{} {}",
|
||||
instance.loader.kind.label(),
|
||||
instance.loader.version
|
||||
)
|
||||
match instance.loader.kind {
|
||||
// 唯一需要过词表的一支:`LoaderKind::label()` 对原版返回的是**中文**「原版」
|
||||
LoaderKind::Vanilla => catalog.text(Key::LoaderVanilla).to_string(),
|
||||
// 其余品牌名不翻译;版本号为空时 `badge()` 自己就只给品牌名
|
||||
_ => instance.loader.badge(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
//! `app/view.rs` 的守门测试(rebase 到 5adac23 之后 view.rs 贴 400 行上限,按「测试独立成文件」拆出)。
|
||||
|
||||
use super::*;
|
||||
use unlucky_i18n::Key;
|
||||
use unlucky_presenter::dto::{
|
||||
InstanceRuntime, InstanceSummary, LaunchStage, LoaderInfo, LoaderKind, Progress,
|
||||
};
|
||||
@@ -253,6 +254,60 @@ fn the_vanilla_badge_follows_the_language() {
|
||||
assert_eq!(zh.instances.rows[0].supporting, "1.20.4 · 原版");
|
||||
}
|
||||
|
||||
/// **品牌名不许被「版本号为空」吞掉**(M120 r1 复审的 p2-1 回归)。
|
||||
///
|
||||
/// 背景:`host::game::loader_from_id("1.20.1-forge")` 这类 id 里取不到版本段(它按 `forge-`
|
||||
/// 找,而 id 的写法是 `-forge` 结尾),于是 `LoaderInfo { kind: Forge, version: "" }`。
|
||||
/// `LoaderInfo::badge()` 对这种输入给的正是品牌名 `Forge`——所以判据只能是「类型是原版」,
|
||||
/// 不能是「版本号为空」。
|
||||
///
|
||||
/// 三个 id 各一条断言(复审点名的三档);`Vanilla` 那一支由
|
||||
/// `the_vanilla_badge_follows_the_language` 守,两条合起来把 `loader_badge` 的两侧都钉住。
|
||||
///
|
||||
/// 抓得住:`loader_badge` 把「非原版 + 空版本号」也送进 `Key::LoaderVanilla`
|
||||
/// (r1 的真实形状:`left: "1.20.4 · Vanilla" / right: "1.20.4 · Forge"`);
|
||||
/// 抓不住:`loader_from_id` 认不出自家 `-fabric-` / `-quilt-` 口径这个**更老**的 bug
|
||||
/// (在 presenter 里,另一条 mission)。
|
||||
#[test]
|
||||
fn the_loader_badge_keeps_the_brand_when_the_version_is_missing() {
|
||||
// (版本 id, 期望徽标) —— id 形状取自 `host::game::loader_from_id` 的三条真实输入
|
||||
let cases = [
|
||||
("1.20.1-forge", LoaderKind::Forge, "Forge"),
|
||||
("1.20.1-neoforge", LoaderKind::NeoForge, "NeoForge"),
|
||||
("1.20.1-OptiFine", LoaderKind::OptiFine, "OptiFine"),
|
||||
];
|
||||
|
||||
for (id, kind, brand) in cases {
|
||||
let mut one = instance(InstanceRuntime::Idle);
|
||||
one.loader = LoaderInfo {
|
||||
kind,
|
||||
// `loader_from_id` 对这三条 id 取不到版本段 ⇒ 空串
|
||||
version: String::new(),
|
||||
};
|
||||
let mut state = AppState::new();
|
||||
state.apply(unlucky_presenter::dto::AppEvent::Core(
|
||||
unlucky_presenter::dto::CoreEvent::InstancesChanged(vec![one]),
|
||||
));
|
||||
state.navigate(Route::Detail("survival".to_string()));
|
||||
|
||||
// 品牌名两套语言都不翻译 ⇒ 中英逐字相同
|
||||
for lang in [Lang::En, Lang::ZhCn] {
|
||||
let model = at_lang(&state, None, lang);
|
||||
assert_eq!(
|
||||
model.instances.rows[0].supporting,
|
||||
format!("1.20.4 · {brand}"),
|
||||
"{id} 的徽标被显示成了别的东西(新增的判据「版本号为空 ⇒ 原版」就是这条要拦的)"
|
||||
);
|
||||
let detail = model.instances.detail.expect("有详情");
|
||||
assert_eq!(
|
||||
detail.meta[1].label,
|
||||
lang.catalog().text(Key::InstancesMetaLoader)
|
||||
);
|
||||
assert_eq!(detail.meta[1].value, brand, "{id} 的 LOADER 卡值");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_agent_route_has_no_page() {
|
||||
let mut state = AppState::new();
|
||||
|
||||
@@ -5,15 +5,16 @@
|
||||
//! | [`the_chinese_page_has_no_english_words`] | 目录里某条中文忘了翻(或翻完还留着英文词) |
|
||||
//! | [`every_instances_text_changes_with_the_language`] | 两套语言写成一样(= 切换语言时那一处不动,看着像「已 i18n」其实是死文案) |
|
||||
//! | [`the_running_line_keeps_its_number_and_separator`] | `Running · pid 1234` 的数字 / `pid` / `·` 被翻译走样 |
|
||||
//! | [`the_page_sources_have_no_english_baseline_left`] | 页面里又写回旧英文基线字面量(M120 之前的形状) |
|
||||
//! | [`every_english_literal_in_the_page_sources_is_whitelisted`] | 页面里写死**任何**英文(旧串或新写的,白名单判定;r1 之前是黑名单,对新写的英文是哑的) |
|
||||
//! | [`every_instances_key_is_referenced_by_the_page`] | 目录里加了 key、页面没人用(死 key,M117 那七条就是这么来的) |
|
||||
//!
|
||||
//! ## 边界(别把这张表读成「视图覆盖」)
|
||||
//!
|
||||
//! iced 0.14 的 `Element` **没有内省接口**(`instances/tests.rs` 里那条「为什么读源码」写的就是它),
|
||||
//! 所以「上屏文本」取样只能从页面取词的那**一个入口** [`Texts`] 拿:页面用它画、测试用同一个
|
||||
//! `Texts` 断言,不是另写一份平行实现。仍然抓不住的是「页面又冒出一条**新**硬编码字面量」——
|
||||
//! 那由 [`the_page_sources_have_no_english_baseline_left`] 兜已知的那批 + 出图人工看。
|
||||
//! `Texts` 断言,不是另写一份平行实现。「页面又冒出一条**新**硬编码英文字面量」由
|
||||
//! [`every_english_literal_in_the_page_sources_is_whitelisted`] 兜(白名单);**中文**写死的
|
||||
//! 字面量它看不见,那只能靠 `Texts::of(` 的反向锚与出图人工看。
|
||||
//! 「模型这一层(快照 → 状态行 / 元信息)跟着 `Lang` 变」由 `app/view/tests.rs` 守。
|
||||
|
||||
use unlucky_i18n::catalog::MISSING;
|
||||
@@ -41,42 +42,37 @@ const PAGE_SOURCES: [(&str, &str); 5] = [
|
||||
),
|
||||
];
|
||||
|
||||
/// M120 之前页面里那些**英文基线字面量**。它们现在只许活在目录里。
|
||||
/// 页面五件套里**允许出现的字符串字面量**(凡是带 ASCII 单词的都必须在这张表里)。
|
||||
///
|
||||
/// 页头标题 `Instances` 不在清单里:它在后面由结构断言「`Header::new` 的实参必须是
|
||||
/// `texts.title()`」单独守(`"Instances"` 这三个字在 `text.rs` 里是**族名前缀**,进清单会假红)。
|
||||
const OLD_LITERALS: [&str; 29] = [
|
||||
"ALL INSTANCES",
|
||||
"Local versions and their mod loaders",
|
||||
"New instance",
|
||||
"Import",
|
||||
"Back to instances",
|
||||
"Play",
|
||||
"Stop",
|
||||
"Duplicate",
|
||||
"Open folder",
|
||||
"Delete",
|
||||
"No instance selected",
|
||||
"Present on disk",
|
||||
"Missing",
|
||||
"Never",
|
||||
"Running · pid ",
|
||||
"Launch failed · ",
|
||||
"Downloading",
|
||||
"Verifying",
|
||||
"Starting",
|
||||
"Waiting for window",
|
||||
"VERSION",
|
||||
"VERSION ID",
|
||||
"LOADER",
|
||||
"ISOLATION",
|
||||
"FILES",
|
||||
"LAST PLAYED",
|
||||
"MEMORY",
|
||||
"JAVA",
|
||||
"MODS",
|
||||
/// M120 r1 复审实测出黑名单的哑点:只兜「已知的 29 条旧串」时,往页面里**新写一句英文**
|
||||
/// 照样 612/612 + 12/12 全绿。判据现在反过来——**白名单**:页面源码里每个带 ASCII 单词的
|
||||
/// 字面量都要在这里找到归宿,新写一句英文立刻红。
|
||||
///
|
||||
/// 全部 6 条都写了理由(剩下一批 `"{} · {}"` / `"{} · {:.0}%"` / `"{} {}"` 这类格式化模板
|
||||
/// **连一个词都没有**,所以不需要条目):
|
||||
///
|
||||
/// | 字面量 | 为什么它可以是英文 |
|
||||
/// |---|---|
|
||||
/// | `{date} {time} UTC` | `UTC` 是时区缩写,不是界面文案(本地时区换算属时间格式 mission) |
|
||||
/// | `isolated` / `shared` | core 的隔离档位**稳定 id**(`IsolationMode::as_str`),`isolation()` 只认这两档,其余原样上屏 |
|
||||
/// | `pid` / `reason` | `Catalog::render` 的**占位符名**(与 catalog 里的 `{pid}` / `{reason}` 对应) |
|
||||
/// | `Instances` | `Texts::keys()` 的**族名前缀**(`Key::ALL` 的过滤条件,不是文案) |
|
||||
const ALLOWED_LITERALS: [&str; 6] = [
|
||||
"{date} {time} UTC",
|
||||
"isolated",
|
||||
"shared",
|
||||
"pid",
|
||||
"reason",
|
||||
"Instances",
|
||||
];
|
||||
|
||||
/// 抽出源码里的字符串字面量(够用的实现:本仓这五个文件没有 `\"` 转义、没有多行字符串)。
|
||||
///
|
||||
/// 按 `"` 切开取**奇数段**即可:`a"b"c"d"e` → 段 1 = `b`、段 3 = `d`。
|
||||
fn string_literals(source: &str) -> Vec<&str> {
|
||||
source.split('"').skip(1).step_by(2).collect()
|
||||
}
|
||||
|
||||
/// 把 `{占位符}` 抹掉再切 ASCII 词:占位符是**运行时数据**(pid / 原因 / 时间),不是文案。
|
||||
fn ascii_words(text: &str) -> Vec<String> {
|
||||
let mut stripped = String::new();
|
||||
@@ -206,27 +202,39 @@ fn the_running_line_keeps_its_number_and_separator() {
|
||||
assert_eq!(failed_zh, "启动失败 · exit 1");
|
||||
}
|
||||
|
||||
/// **旧串不许回来**:M120 之前页面里的英文基线字面量,现在只许活在目录里。
|
||||
/// **页面里不许再写死界面文案**(白名单):带 ASCII 单词的字面量必须在 [`ALLOWED_LITERALS`] 里。
|
||||
///
|
||||
/// 为什么用「已知清单」而不是「扫所有英文串」:合法写法太多了(`Action::Import` 这类枚举、
|
||||
/// 品牌名、文档里的反引号代码),扫宽了会假红。清单里这 29 条是**这次真的搬走的那批**,
|
||||
/// 谁把它们抄回页面,这条就红——那正是「页面又变成中英混排」的唯一来源。
|
||||
/// 抓得住:①M120 之前那 29 条旧串被抄回页面(它们都带英文词 ⇒ 不在白名单 ⇒ 红);
|
||||
/// ②**任何新写的英文字面量**(r1 复审抓到的哑点:黑名单版本把 `"Ready"` 写进页面是全绿的)。
|
||||
/// 抓不住:中文写死的字面量(`"实例"` 没有 ASCII 词)——那要靠 `Texts::of(` 的反向锚与出图;
|
||||
/// 也抓不住夹具数据(`pages/instances/data.rs` 故意不在扫描范围:那是**用户数据**的样子,
|
||||
/// 不是界面文案,见该文件的模块文档)。
|
||||
///
|
||||
/// **变异实测**(M120 交付时跑过):把 `"ALL INSTANCES"` 写回 `instances.rs` 的页头,立刻红
|
||||
/// 并指出是哪个文件、哪一句。
|
||||
/// **变异实测(M120 r1 交付时跑过三条)**:写回 `"ALL INSTANCES"` ⇒ 红;写进一条全新的
|
||||
/// `"Ready"` ⇒ 红;把 `Texts::of(` 从实例页删掉 ⇒ 反向锚红。
|
||||
#[test]
|
||||
fn the_page_sources_have_no_english_baseline_left() {
|
||||
fn every_english_literal_in_the_page_sources_is_whitelisted() {
|
||||
let mut checked = 0;
|
||||
for (name, source) in PAGE_SOURCES {
|
||||
for literal in OLD_LITERALS {
|
||||
let needle = format!("\"{literal}\"");
|
||||
for literal in string_literals(source) {
|
||||
if ascii_words(literal).is_empty() {
|
||||
continue;
|
||||
}
|
||||
checked += 1;
|
||||
assert!(
|
||||
!source.contains(&needle),
|
||||
"{name} 里又写回了英文基线字面量 {literal:?}(走 Texts / 目录,别写死)"
|
||||
ALLOWED_LITERALS.contains(&literal),
|
||||
"{name} 里的字面量 {literal:?} 带 ASCII 单词但不在白名单里:界面文案要走 Texts / 目录;确属专有名词的话加进 ALLOWED_LITERALS 并写明理由"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 反向锚:这些来源**必须**真的在用 Texts / 目录,否则上面那条是空转
|
||||
// 反向锚①:扫描器真的扫到了东西(否则上面那个循环是空转)
|
||||
assert!(
|
||||
checked >= 6,
|
||||
"只扫到 {checked} 条带英文的字面量,扫描器可能失效了"
|
||||
);
|
||||
|
||||
// 反向锚②:这些来源**必须**真的在用 Texts / 目录
|
||||
assert!(
|
||||
PAGE_SOURCES[0].1.contains("Texts::of("),
|
||||
"实例页没有从 Texts 取词"
|
||||
|
||||
Reference in new issue
Block a user