Files
Unlucky-Launcher/crates/app-iced/src/pages/layout/source.rs
T
AskaEth 824bb36f8e 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)且仍贴左。
2026-11-04 19:53:41 +08:00

40 lines
2.1 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! **源码结构断言**的小工具:把某个函数的实现文本切出来(只给测试用)。
//!
//! ## 为什么要读源码(而不是量 Element)
//!
//! iced 的 `Element` 没有几何内省接口(`size()` 只是 `Size<Length>`,取不到布局结果),
//! 所以「页面把块摆在哪 / 有没有在行之间叠间距」在单测里**量不到**。M102 r1 的教训正是这个:
//! 护栏只喂 `frame_metrics()` 与常量重算,把 `frame::body` 换回「居中 424」的实现,
//! 测试照样全绿——钉住的是公式,不是摆法。
//!
//! 本仓已有同一条纪律的先例:`tests/no_hardcoded_tokens.rs` 直接扫 `src/**`,
//! `pages/download/tests.rs::every_download_key_is_referenced_by_the_page` 用 `include_str!`
//! 扫页面源码找 key 引用。这里只是把「切出某个函数」这件小事做成一个共用工具,
//! 让护栏能断言「盒子是不是照那个数建的」。
//!
//! ## 边界(它抓不住什么)
//!
//! 它读的是**文本**:换个写法(先赋值给局部变量再 `.width(...)`)就可能绕过。
//! 所以它只配一条**明确的契约**用:`frame::body` / `frame::prose` / `list_column` 这几处的
//! 写法本身就是契约的一部分(它们各自只有 5~10 行),不是为了抠实现细节。
//! 真要守像素,还是得靠软件渲染金图(`shot::diff`,绑 Windows + 字体栈,属另一个决定)。
/// 把 `source` 里第一个 `signature` 起始、到该函数**顶层**右花括号为止的文本切出来。
///
/// 「顶层」= 行首的 `}`(本仓函数都是顶格收尾,内层块都是缩进收尾),所以不会在内层 `}` 提前断。
///
/// # Panics
/// 签名不存在时 panic(护栏自己的前置条件坏了要立刻红,不能静默放行)。
pub(crate) fn function_body<'a>(source: &'a str, signature: &str) -> &'a str {
let start = source
.find(signature)
.unwrap_or_else(|| panic!("源码里找不到签名 {signature:?}"));
let rest = &source[start..];
let end = rest.find("\n}").map_or(rest.len(), |index| index + 2);
&rest[..end]
}
#[cfg(test)]
mod tests;