feat(app-iced,pages): M100 取证密闭 + harness 参数拆分 + 设置页测试按 section 下放

四件前置事,都是为了设置页正文四条 section 线能并行(不写任何 section 正文):

1. 取证密闭(两条通道各有各的机制,证据分开算)
   - 产品 bin:`cli::ForcedState` 加 `Rest`(静息态),给了 `--screenshot` 且没显式
     `--force-state` 时默认 `Some(Rest)`;交互模式仍是 `None`(`shell::overridden`
     与 `app/view.rs` 的 `model()` 各补一支)。
   - harness:截图窗口开 `window::enable_mouse_passthrough`(winit 的
     `set_cursor_hittest(false)`)⇒ 窗口收不到指针事件 ⇒ 全树控件恒为 Active。
     为什么用通道级而不是逐控件灌 forced:设置页「外观」区的滑块/文本框/裸 button 都不消费
     forced,逐控件做要改十几个组件文件,而新产品组件随时会漏。
   - harness 同时数指针事件并打进日志(`指针事件=N`):让「这张图见过几个鼠标事件」变成
     可读数字,替代「连跑几次 hash 一样」那种恒真的假绿。

2. harness 的外观真状态入口:`--appearance=real` 让设置页走 `appearance: Some(..)`
   (工厂态 + `ULL_APPEARANCE_*`),新入口 `pages::app_with_appearance`。
   `--screen=settings` 之前就有了,但 `appearance` 恒为 None,D30 要的那一屏根本不存在。

3. `pages/harness.rs` 400 行 → 拆 `pages/harness/request.rs`(`#[path]` 范式),
   纯搬移部分 240 行 + 新增密闭代码。拆分等价性:同一 argv 的 PNG sha256 与拆分前一致。

4. `pages/settings/tests.rs` 按 section 下放成 `tests/{general,appearance,java,download}_section.rs`,
   mod 层只留跨 section 断言(每条写清守什么、为什么不能下放)。
   给某组加一条新断言 = 只改它自己那个文件。

顺带:`shell/mod.rs` 在 HEAD 就 399 行、`cli.rs` 394 行,两处都按同一范式把 tests 搬成
`shell/tests.rs` / `cli/tests.rs`(断言一字不动,逐字节可 diff)。

变异验证:把四处实现各改坏一处(默认 Rest、passthrough 默认值、overridden 的 Rest 分支、
view 的 Rest 映射),对应 4 条断言全红(390 passed / 4 failed),恢复后 394 passed。
This commit is contained in:
AskaEth committed 2026-11-03 14:22:40 +08:00
1 parent 2ad808d55c
commit 2061296615
16 files changed
+1152 -469

No files matched your search

@@ -0,0 +1,48 @@
//! 「Appearance」分组自己的断言(M100 从 `settings/tests.rs` 下放)。
//!
//! **归 language+appearance 线**:这是设置页唯一接了真控件的一组,真控件实现全在
//! `pages/appearance/section.rs`;本文件只守「注册表这一侧的接线」。
use super::super::appearance;
use unlucky_design::icons::IconKind;
/// 这一组的**演示行**(没有外观运行期状态时才画)。
///
/// 抓得住:四条线并行合并时把别人的行粘到本文件(四份 `GROUP` 长得几乎一样);
/// 漏掉/多一条行;标签与值错位。
/// 抓不住:真控件那一屏(那是 `pages/appearance` 的事,靠出图判)。
#[test]
fn the_appearance_group_shows_density_corner_motion_and_text_scale() {
let rows: Vec<(&str, &str)> = appearance::GROUP.rows.to_vec();
assert_eq!(
rows,
vec![
("Density", "Comfortable"),
("Corner style", "Rounded"),
("Motion", "Full"),
("Text scale", "100%"),
]
);
}
/// 左列图标(`IconKind::Sidebar`)。
#[test]
fn the_appearance_group_keeps_its_icon() {
assert_eq!(appearance::GROUP.icon, IconKind::Sidebar);
}
/// **本组的绊线**:外观分组必须一直是「挂了真控件」的那一组。
///
/// 抓得住:language+appearance 线把 `body` 换回 `None`(真控件消失、退回演示行,而截图对这种
/// 「只在有外观状态时出现」的差别不一定抓得住)。
/// 抓不住:`body` 到底指向哪个渲染器——**函数指针相等性断言不了**(rustc
/// `unpredictable_function_pointer_comparisons`:fn 地址不保证唯一,比出来的结果没有意义),
/// 所以「指向 `pages::appearance::section`」这件事只能由
/// `settings/appearance.rs` 那一行源码 + 两张图的 sha256 不同来担保。
#[test]
fn the_appearance_group_keeps_a_real_control_body() {
assert!(
appearance::GROUP.body.is_some(),
"外观分组必须是挂了真控件的那一组(`body = Some(crate::pages::appearance::section)`)"
);
}