test(app-iced): M102 r3 护栏补到「页面调用点」——回退 body/软断点接线一律红,等价重构不再假红
r2 复审抓的两条 p1 都是「守卫只到函数内部」:把页面调用点改回 frame::single_column(变体 G)、
或把日志页两处 wrap::breakable 接线摘掉(变体 I),420 条全绿,而视觉复审 ① / ③ 原样回来。
r3 用 r2 自建的 layout/source.rs 把守卫推进到调用点,并把三条 p2 的假红/假强一并修掉:
- 新增 `single_column_pages_call_the_body_box_not_the_centred_one`:四个页面文件
(download / logs / instances / instances/detail,共 7 个调用点)必须出现 `frame::body(`、
不许出现 `frame::single_column(`;narrow_detail 单独再断言一次。(变体 G → 红)
- 新增 `the_page_wires_the_soft_breaks_into_both_rendering_paths`:扫 `lines::entry` 与
`diagnostic::summary_block`,必须含 `wrap::breakable(`(变体 I → 红);
同时把 `log_text_carries_soft_breaks_but_keeps_the_original` 的 docstring 改成它实际守的东西
(断点表 + 原文可还原),不再冒充「抓得住接线」。
- p2-1:`frame_body_is_built_from_the_block_quantities` 改用新的
`source::function_region`(函数体 + 本文件内被转调的函数,最多两层)⇒ 把 body 拆成
`body` → `body_impl` 的等价重构不再假红(H1 → 绿),换回 centered 仍红(A → 红)。
顺带修掉 region 的泛型签名 bug:早先拿 `fn {name}(` 当定义 needle,
遇到 `fn body_impl<'a>(` 会 panic(护栏自己假红);现在按 `fn {name}` 找定义,并补了一条
`the_region_follows_delegation_into_a_generic_function` 自测。
- p2-2:`list_column_spaces_rows_only_from_the_shared_constant` 的禁词收窄到
`let mut rows = column(…)` **这一条**构建链(新工具 `source::statement`,停在第一个 `;`),
于是 `column![head, scroller]` 上加合法间距不再假红(J → 绿),行容器叠间距仍红(D → 红)。
- p2-3:`frame::body` 的 align_x 改成「出现就必须是 `.align_x(Alignment::Start)`」,
不再被 `.align_y(Alignment::Start)` 顺手满足;「干脆不写 align_x」(默认 Start)视为等价重构放行。
- p2-4(措辞):`layout/block.rs` 文档写明 1440 上限是**有意行为变化**(≥1576 窗宽),
1180/900 上才是零像素变化。
变异矩阵(本轮自跑,逐条回滚,`git status` 已核):A/C/D/E/G/I 全红且各自只红对应那一条;
H1(body_impl 转调)/H2b(删显式 align_x)/H3(ROW_SPACING 经局部变量)/J(head/scroller 加合法间距)
全绿。基线 `cargo test -p unlucky-app-iced --lib` = 426 passed / 0 failed。
This commit is contained in:
1 parent
824bb36f8e
commit
f499961e5b
6 files changed
+238
-21
No files matched your search
@@ -27,6 +27,13 @@
|
||||
//! 贴左不变:块左缘仍是 `rail 88 + gutter 24 = 112`,封顶后**右侧留白**(不是居中)。
|
||||
//! 这是有意的:页头标题在左,正文块跟着左缘;把块居中又会回到视觉复审 ① 的「内容缩在中间」。
|
||||
//!
|
||||
//! ## 这一层唯一的行为变化,以及它从哪一版开始
|
||||
//!
|
||||
//! 封顶是 M102 r2 引入的**有意行为变化**:`≥ 1576` 窗宽(`112 + 1440 + 24`)下块宽不再跟随可用宽度。
|
||||
//! 在 1180 / 900 上它**不生效**——那两张验收图与 r1 逐字节相同(9/9 `cmp`)。所以对外口径是
|
||||
//! 「**≤1440 窗宽零像素变化;更宽的窗口按本上限有意改变**」,不是笼统的「零变化」。
|
||||
//! (`frame::body` 那行 `padding(left(body_offset))` 在同一轮加进来,值恒 0 ⇒ 像素中性。)
|
||||
//!
|
||||
//! ## 偏移为什么是个常量函数而不是 `align_x`
|
||||
//!
|
||||
//! iced 的 `Element` **没有几何内省接口**,「块摆在哪」在单测里量不到。所以把「贴左」写成一个
|
||||
|
||||
@@ -35,5 +35,70 @@ pub(crate) fn function_body<'a>(source: &'a str, signature: &str) -> &'a str {
|
||||
&rest[..end]
|
||||
}
|
||||
|
||||
/// 从 `start` 起到**第一个 `;`** 为止的一条语句(构建链断言用)。
|
||||
///
|
||||
/// 例如 `statement(instances.rs, "let mut rows = column(")` 拿到的是
|
||||
/// `let mut rows = column(...).padding(...).width(Length::Fill);` 这一整条链——
|
||||
/// 于是「这条链上不许叠间距」可以精确到**这一个**容器,不会连带禁掉旁边 `column![head, scroller]`
|
||||
/// 上的合法间距。
|
||||
///
|
||||
/// # Panics
|
||||
/// `start` 不存在时 panic(守卫的前置条件坏了要立刻红)。
|
||||
pub(crate) fn statement<'a>(source: &'a str, start: &str) -> &'a str {
|
||||
let from = source
|
||||
.find(start)
|
||||
.unwrap_or_else(|| panic!("源码里找不到语句起点 {start:?}"));
|
||||
|
||||
let rest = &source[from..];
|
||||
let end = rest.find(';').map_or(rest.len(), |index| index + 1);
|
||||
&rest[..end]
|
||||
}
|
||||
|
||||
/// `signature` 的函数体 **+ 它内部转调的同文件函数体**(最多两层)。
|
||||
///
|
||||
/// 为什么不只切一个函数:**等价重构不该假红**。把 `frame::body` 拆成
|
||||
/// `body` → `body_impl` 两层再转调,盒子一个像素都没变,`function_body` 却会拿到三行转调桩、
|
||||
/// needle 全缺(r2 复审的变异 H1 就是这样假红的)。这里把「本文件内被它调用的函数」也算进契约,
|
||||
/// 于是「拆一层再转调」放行,「换成居中盒子」照样红。
|
||||
///
|
||||
/// 边界:只跟**同一文件内**的转调(跨文件转调、先赋局部变量再建盒不在覆盖内,见调用点 docstring)。
|
||||
pub(crate) fn function_region(source: &str, signature: &str) -> String {
|
||||
let mut region = function_body(source, signature).to_owned();
|
||||
|
||||
for _ in 0..2 {
|
||||
let mut grew = false;
|
||||
let mut cursor = 0;
|
||||
|
||||
while let Some(found) = source[cursor..].find("fn ") {
|
||||
let at = cursor + found + "fn ".len();
|
||||
cursor = at;
|
||||
|
||||
let name: String = source[at..]
|
||||
.chars()
|
||||
.take_while(|ch| ch.is_alphanumeric() || *ch == '_')
|
||||
.collect();
|
||||
if name.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let called = format!("{name}(");
|
||||
let defined = format!("fn {name}");
|
||||
if region.contains(&called) && !region.contains(&defined) && source.contains(&defined) {
|
||||
region.push('\n');
|
||||
// `defined` 不带 `(`:签名可能带泛型(`fn body_impl<'a>(`),
|
||||
// 带括号的 needle 会找不到定义、直接把护栏自己搞 panic。
|
||||
region.push_str(function_body(source, &defined));
|
||||
grew = true;
|
||||
}
|
||||
}
|
||||
|
||||
if !grew {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
region
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
@@ -41,8 +41,70 @@ fn the_helper_also_works_on_signatures_with_generics_and_multiline_params() {
|
||||
assert!(gamma.trim_end().ends_with('}'), "{gamma}");
|
||||
}
|
||||
|
||||
/// `statement` 停在第一个 `;`:构建链断言靠它精确到一个容器。
|
||||
#[test]
|
||||
fn a_statement_stops_at_the_first_semicolon() {
|
||||
let source = "\
|
||||
let mut rows = column(entries)
|
||||
.padding(Padding::from([0.0, ui.space(Space::X2)]))
|
||||
.width(Length::Fill);
|
||||
let other = 1;
|
||||
";
|
||||
let chain = statement(source, "let mut rows = column(");
|
||||
assert!(chain.starts_with("let mut rows"), "{chain}");
|
||||
assert!(chain.ends_with(".width(Length::Fill);"), "{chain}");
|
||||
assert!(!chain.contains("let other"), "越过语句边界:{chain}");
|
||||
}
|
||||
|
||||
/// `function_region` 把**本文件内被调用的函数**也算进契约(等价重构不假红)。
|
||||
#[test]
|
||||
fn the_region_follows_one_level_of_delegation() {
|
||||
let source = "\
|
||||
pub fn outer(a: u8) -> u8 {
|
||||
inner(a) + 1
|
||||
}
|
||||
|
||||
fn inner(a: u8) -> u8 {
|
||||
a * 2
|
||||
}
|
||||
|
||||
fn unrelated(a: u8) -> u8 {
|
||||
a - 9
|
||||
}
|
||||
";
|
||||
let region = function_region(source, "pub fn outer(");
|
||||
assert!(region.contains("inner(a) + 1"), "{region}");
|
||||
assert!(region.contains("a * 2"), "转调目标没进来:{region}");
|
||||
assert!(!region.contains("a - 9"), "把无关函数也扫进来了:{region}");
|
||||
}
|
||||
|
||||
/// 转调目标带**泛型签名**时也要跟进去(`frame::body` → `body_impl<'a>` 就是这种形状)。
|
||||
///
|
||||
/// 这条是变异 H1 抓出来的:早先的实现拿 `fn {name}(` 当定义 needle,遇到
|
||||
/// `fn body_impl<'a>(` 找不到 ⇒ 护栏自己 panic ⇒ 对等价重构**假红**。
|
||||
#[test]
|
||||
fn the_region_follows_delegation_into_a_generic_function() {
|
||||
let source = "\
|
||||
pub fn wrapper(v: u8) -> u8 {
|
||||
helper(v) + 1
|
||||
}
|
||||
|
||||
fn helper<'a>(v: u8) -> u8 {
|
||||
v * 3
|
||||
}
|
||||
";
|
||||
let region = function_region(source, "pub fn wrapper(");
|
||||
assert!(region.contains("v * 3"), "泛型签名没跟进去:{region}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "找不到签名")]
|
||||
fn a_missing_signature_fails_loudly() {
|
||||
let _ = function_body(SAMPLE, "pub fn nope");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "找不到语句起点")]
|
||||
fn a_missing_statement_start_fails_loudly() {
|
||||
let _ = statement(SAMPLE, "let nope = 1");
|
||||
}
|
||||
Reference in new issue
Block a user