test(app-iced): M102 r2 护栏改成「抓得住页面换摆法」——块偏移/行距/散文接线全部有源码结构断言

r1 复审的两条 P1 都是假护栏:把修复原样回滚(A:frame::body 换回居中 424;
C:内缩 X2→X1;D:行距加回 4px)后测试仍全绿,因为断言只喂 frame_metrics() 与常量重算,
既不观察 frame::body,也不引用页面常量。本轮把「摆法」做成可断言的东西:

- 新增 `pages/layout/block.rs`:body_width(pane 宽封顶 1440)、body_offset(恒 0 = 贴左)、
  prose_width(= metrics.column ≤424)三个纯量 + 单测。frame::body / frame::prose 从它们建盒,
  frame::body_width 改成 pub use(不另算一份)。
- 新增 `pages/layout/source.rs`(cfg(test)):把某个函数的实现文本切出来。iced 的 Element
  没有几何内省,量不到「块摆在哪」,只能断言「照哪个数建的」;仓里已有 no_hardcoded_tokens
  扫源码、download/tests 用 include_str! 扫 key 的先例。
- 新护栏(每条都做过变异实测,见报告):
  · frame_body_is_built_from_the_block_quantities(A 变异红)
  · frame_prose_is_built_from_the_prose_width(E 变异红)
  · list_column_spaces_rows_only_from_the_shared_constant(D 变异红)
  · instances 几何断言改用页面真源 LIST_BOTTOM_INSET.dp() / ROW_SPACING(C 变异红)
  · the_detail_note_goes_through_the_prose_box(散文接线)
- P2:wrap::strip 的「会删原文自带 U+200B」写成限界 + 一条断言;docs 口径从 layout.rs
  挪到 layout/block.rs(layout.rs 399 → 389)。

零像素变化:改后 9 张图与 r1 的 m102-after 逐张 cmp 完全相同;2560×760 新出三张证明块宽封顶
1440(112..1551)且仍贴左。
This commit is contained in:
AskaEth committed 2026-11-04 19:53:41 +08:00
1 parent 89d98184c1
commit 824bb36f8e
11 files changed
+469 -67

No files matched your search

+75 -6
View File
@@ -81,14 +81,15 @@ fn the_width_comparison_really_changes_the_layout() {
assert!(narrow.secondary.is_none(), "700 不许再有列表列");
}
/// **M102 的对齐判据**:正文块左缘 = 页头标题左缘,块宽 = 可用宽度(不再二次居中)。
/// **M102 的对齐判据**:正文块左缘 = 页头标题左缘(`body_offset == 0`),块宽 = pane 宽(封顶 1440)。
///
/// 页头标题的左缘 = `rail + gutter`(`frame::header_bar` 的 padding 与 `frame::page` 的
/// 内容 padding 是同一个 `metrics.gutter`);正文块宽 = `frame::body_width`。
/// 页头标题的左缘 = `rail + gutter`(`frame::header_bar` 的 padding 与 `frame::page` 的内容
/// padding 是同一个 `metrics.gutter`);正文块宽 = `frame::body_width`。
///
/// **抓得住**:谁把单列页的块宽改回 424(视觉复审 ①:左缘 422 对标题 112),
/// 或把块重新居中(左缘立刻偏离 `rail + gutter`);
/// **抓不住**:真实像素(`pages_demo` 的图与报告里的量测表负责)。
/// **抓得住**:块宽被改回 424(视觉复审 ①:左缘 422 对标题 112)、偏移不再是 0(居中/右对齐)、
/// 上限被挪动;
/// **抓不住**:`frame::body` 到底有没有照这两个数建盒子(那由
/// [`frame_body_is_built_from_the_block_quantities`] 扫源码守)。
#[test]
fn body_block_starts_at_the_header_title_and_fills_the_page() {
for width in [600.0, 840.0, 900.0, 1180.0] {
@@ -100,6 +101,11 @@ fn body_block_starts_at_the_header_title_and_fills_the_page() {
block, metrics.available,
"{width}: 单列页块宽必须是可用宽度"
);
assert_eq!(
layout::block::body_offset(metrics),
0.0,
"{width}: 块必须贴左"
);
assert!(
(title_x + block + metrics.gutter - width).abs() < f32::EPSILON,
"{width}: 块从 {title_x} 起、宽 {block},没铺到右页边距"
@@ -113,12 +119,75 @@ fn body_block_starts_at_the_header_title_and_fills_the_page() {
assert_eq!(layout::rail_width() + wide.gutter, 112.0, "页头标题左缘");
assert_eq!(frame::body_width(wide), 1044.0, "正文块宽度");
// 极宽窗:块宽封顶 1440(tower 拍板),仍然贴左 ⇒ 右侧留白而不是居中
let huge = frame_metrics(PaneKit::Single, 2560.0);
assert_eq!(frame::body_width(huge), layout::block::BODY_MAX_WIDTH);
assert_eq!(frame::body_width(huge), 1440.0);
assert_eq!(layout::block::body_offset(huge), 0.0);
assert!(112.0 + frame::body_width(huge) < 2560.0);
// 两列版面的块宽 = 主列 pane(实例页正文在 panes 的主列里,不铺整页)
let two = frame_metrics(PaneKit::ListDetail, 1180.0);
assert_eq!(frame::body_width(two), two.main_pane);
assert_eq!(two.main_pane, 1180.0 - 88.0 - 48.0 - 336.0 - 24.0);
}
/// **源码结构**:`frame::body` 的盒子必须从「块宽 + 块偏移 + 贴左」这三个数建。
///
/// 为什么读源码:iced 的 `Element` 没有几何内省接口,`frame::body` 换成
/// `centered(metrics.column, content)`(**就是视觉复审 ① 的改前写法**)不会红任何数值断言 ——
/// M102 r1 的变异实测就是这样漏掉的(`pages/tests.rs` 那条只喂 `frame_metrics()` 与
/// `body_width()`)。所以这条盯的是**摆法**:宽度来自 `body_width(metrics)`、左内边距来自
/// `body_offset(metrics)`、对齐 `Start`,且不出现居中/散文列宽。
///
/// **抓得住**:`body()` 被换回居中、`align_x` 改成 `Center`、宽度改回 `metrics.column`;
/// **抓不住**:绕过这段契约的重写(先取局部变量再建盒)——它守的是这 8 行的写法契约。
#[test]
fn frame_body_is_built_from_the_block_quantities() {
let body = layout::source::function_body(include_str!("frame.rs"), "pub fn body<'a>(");
for needle in [
"body_width(metrics)",
"body_offset(metrics)",
"Alignment::Start",
] {
assert!(
body.contains(needle),
"`frame::body` 必须从 {needle} 建盒(贴左 + 块宽 + 零偏移):\n{body}"
);
}
for forbidden in [
"centered(",
"single_column(",
"metrics.column",
"Alignment::Center",
] {
assert!(
!body.contains(forbidden),
"`frame::body` 里出现了 {forbidden:?} —— 那是居中 / 散文列宽的老写法:\n{body}"
);
}
}
/// **源码结构**:`frame::prose` 的宽度必须来自 `prose_width`(= `metrics.column`),不来自块宽。
///
/// M102 把两个量拆开后,「把散文塞进 1044 的块」不会红任何数值断言;这条盯接线。
#[test]
fn frame_prose_is_built_from_the_prose_width() {
let prose = layout::source::function_body(include_str!("frame.rs"), "pub fn prose<'a>(");
assert!(
prose.contains("prose_width(metrics)"),
"`frame::prose` 必须从 prose_width 取宽:\n{prose}"
);
for forbidden in ["body_width(", "metrics.available", "metrics.main_pane"] {
assert!(
!prose.contains(forbidden),
"`frame::prose` 里出现了 {forbidden:?} —— 散文会失去 72 字符行宽承诺:\n{prose}"
);
}
}
/// **M84 的行宽红线**:任何页、任何窗口宽度、任何版面,正文列都不许超过 424px。
///
/// **抓得住**:`frame_metrics` 的 `column`(页面唯一的列宽来源)不会越界;