From 55a775fca73901031e1d5c6e09f7346ee3bf73f0 Mon Sep 17 00:00:00 2001 From: AskaEth Date: Mon, 2 Nov 2026 11:12:48 +0800 Subject: [PATCH] =?UTF-8?q?test(app-iced):=20M79=20r2=20=E2=80=94=E2=80=94?= =?UTF-8?q?=20probe=20=E5=A4=8D=E8=B7=91=E6=9A=B4=E9=9C=B2=E7=9A=84?= =?UTF-8?q?=E4=B8=A4=E5=A4=84=E4=BF=AE=E6=AD=A3=EF=BC=88=E5=88=86=E9=9A=94?= =?UTF-8?q?=E7=BA=BF=E5=88=A4=E6=8D=AE=20/=20=E5=8F=AF=E8=A7=81=E6=80=A7?= =?UTF-8?q?=E8=AD=A6=E5=91=8A=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 真跑 `navigation::probe` 才发现的两件事,都只影响测试代码: 1. **Secondary 分隔线判据错了**:原来按 `SurfaceVariant` 的**精确值**找块, 但 1px 的线画在分数像素上,真实像素是「线色 ⊗ 页面底色」的混合值 (实测 `(221,233,245)`,不是令牌原值)→ 找到 **0 段**,断言假红。 改成:先找「与页面底色不同、横跨 >150px 的那一行」,再用**该行自己的颜色**数连通段 (`probe/scan.rs` 新增 `longest_h_line` / `line_segments` / `differs`)。 现在:r1 基线图 → 实测 **2 段**(红);r2 新图 → **1 段**(绿)。 2. `matches` 原来被写成 `differs` 的正向委托(语义反了),以及 `pub(super) fn shot` 暴露了 更私有的 `Shot`(`private_interfaces` 警告,`-D warnings` 会红):前者改回 `!differs(..)`,后者把 `shot`/`shot_dir` 收回私有(子模块 `probe::long` 仍可见)。 其他:`probe/long.rs` 的 `filter(..).next()` → `find(..)`(clippy::filter_next)。 复跑(真实输出): - `ULL_NAV_DIR=target/m79/r2 cargo test -p unlucky-app-iced navigation::probe` → **5 passed** - `cargo test -p unlucky-app-iced` → **197 passed**(+ gate 6 passed,1 ignored) - `cargo clippy -p unlucky-app-iced --all-targets -- -D warnings` → 干净 --- .../src/components/navigation/probe.rs | 21 ++++--- .../src/components/navigation/probe/long.rs | 3 +- .../src/components/navigation/probe/scan.rs | 56 +++++++++++++++++-- 3 files changed, 65 insertions(+), 15 deletions(-) diff --git a/crates/app-iced/src/components/navigation/probe.rs b/crates/app-iced/src/components/navigation/probe.rs index 3a6167b..9e6b59a 100644 --- a/crates/app-iced/src/components/navigation/probe.rs +++ b/crates/app-iced/src/components/navigation/probe.rs @@ -40,7 +40,7 @@ mod scan; use scan::{rgb, ui, BBox, Rgb, Shot}; /// `ULL_NAV_DIR` → 截图目录(相对路径按**工作区根**解释:`cargo test` 的 cwd 是 crate 目录)。 -pub(super) fn shot_dir() -> Option { +fn shot_dir() -> Option { let raw = std::env::var_os("ULL_NAV_DIR")?; let path = PathBuf::from(raw); if path.is_absolute() || path.exists() { @@ -65,7 +65,7 @@ macro_rules! require_dir { }; } -pub(super) fn shot(dir: &Path, part: &str, flavor: SchemeFlavor) -> Shot { +fn shot(dir: &Path, part: &str, flavor: SchemeFlavor) -> Shot { let slug = match flavor { SchemeFlavor::Light => "light", SchemeFlavor::Dark => "dark", @@ -199,13 +199,16 @@ fn tabs_indicator_tracks_the_selected_tab() { measured.push((bar.width(), ink)); } // 6.6:Secondary 的分隔线必须是**一条连续直线**(逐 tab 分段画会在相邻段之间留 1px 亮缝, - // 视觉 r1 P2-2 实测 `25..101 | 103..191 | 193..265`) - let divider = rgb(ui.color(Role::SurfaceVariant)); - let segments = shot - .blocks(divider) - .into_iter() - .filter(|b| b.height() <= 2 && b.width() >= 20) - .count(); + // 视觉 r1 P2-2 实测 `25..101 | 103..191 | 193..265`)。 + // + // 判法:1px 的线画在分数像素上 → 真实像素是「线色 ⊗ 页面底色」的**混合值**(实测 + // `(221,233,245)`,不是 `SurfaceVariant` 的原值),所以不能按精确色找块。改成: + // 先找「与页面底色不同、横跨 >150px 的那一行」,再用**该行自己的颜色**数连通段。 + let surface = rgb(ui.color(Role::Surface)); + let divider = shot + .longest_h_line(surface, 150) + .expect("Secondary 那一行应有一条长横线"); + let segments = shot.line_segments(divider.0, divider.1, divider.2); assert_eq!( segments, 1, "Secondary 分隔线应是一条直线,实测 {segments} 段(分段画会留缝)" diff --git a/crates/app-iced/src/components/navigation/probe/long.rs b/crates/app-iced/src/components/navigation/probe/long.rs index bf1748d..aa1295a 100644 --- a/crates/app-iced/src/components/navigation/probe/long.rs +++ b/crates/app-iced/src/components/navigation/probe/long.rs @@ -55,8 +55,7 @@ fn long_text_stays_single_line() { let bar = shot .blocks(rgb(ui.color(Role::Surface))) .into_iter() - .filter(|b| b.width() > 300 && (54..=58).contains(&b.height())) - .next() + .find(|b| b.width() > 300 && (54..=58).contains(&b.height())) .expect("长文案块应有一条 56 高的页头"); assert!( (55..=57).contains(&bar.height()), diff --git a/crates/app-iced/src/components/navigation/probe/scan.rs b/crates/app-iced/src/components/navigation/probe/scan.rs index 8487132..3a9b9c5 100644 --- a/crates/app-iced/src/components/navigation/probe/scan.rs +++ b/crates/app-iced/src/components/navigation/probe/scan.rs @@ -40,10 +40,13 @@ pub(super) fn rgb(color: iced::Color) -> Rgb { } pub(super) fn matches(pixel: Rgb, expected: Rgb) -> bool { - pixel - .iter() - .zip(expected) - .all(|(a, b)| a.abs_diff(b) <= TOLERANCE) + // **每个通道**都在容差内;`differs` 是它的否定(任一通道超差即「不同」) + !differs(pixel, expected, TOLERANCE) +} + +/// 两个颜色在**每个通道**上都不超过 `tol`。 +pub(super) fn differs(pixel: Rgb, expected: Rgb, tol: u8) -> bool { + pixel.iter().zip(expected).any(|(a, b)| a.abs_diff(b) > tol) } /// 一个外接框(闭区间)。 @@ -178,6 +181,51 @@ impl Shot { span } + /// 全图里**第一行**横跨 ≥ `min_len` 的「与底色不同」的连续像素:返回 `(y, x0, x1)`。 + /// + /// 用于找 1px 的分隔线:它的颜色是混合值,不能按精确色找。 + pub(super) fn longest_h_line( + &self, + background: Rgb, + min_len: usize, + ) -> Option<(usize, usize, usize)> { + for y in 0..self.height { + let mut run = 0; + let mut start = 0; + for x in 0..self.width { + if differs(self.at(x, y), background, 6) { + if run == 0 { + start = x; + } + run += 1; + if run >= min_len { + return Some((y, start, x)); + } + } else { + run = 0; + } + } + } + None + } + + /// `y` 行 `[x0, x1]` 上「按该行中点的颜色」数的连通段数(分隔线断没断)。 + pub(super) fn line_segments(&self, y: usize, x0: usize, x1: usize) -> usize { + let line = self.at((x0 + x1) / 2, y); + let mut segments = 0; + let mut in_run = false; + for x in x0..=x1 { + let on = matches(self.at(x, y), line); + if on && !in_run { + segments += 1; + in_run = true; + } else if !on { + in_run = false; + } + } + segments + } + /// 矩形内「与底色不同」的像素的左右范围(量文本墨迹用)。 /// /// 为什么不按精确文字色找:小字号抗锯齿后几乎没有像素落在**精确**色上(容差 3 时实测