fix(app-iced): blend 用例端点改用真实令牌色(M288)
`blend_hits_both_endpoints_bit_for_bit` 原先用 `Color::from_rgb(0.1, 0.2, 0.3)` / `(0.7, 0.8, 0.9)` 当端点,撞上 `tests/no_hardcoded_tokens.rs` 的色值守门(它扫 `src/**`, 测试模块也算在里面),使 base 上「全量测试绿」永不可达。 修法在**被判的那一侧**:端点取状态层真正会插值的一对角色——`Role::Surface`(容器)与 `Role::OnSurface`(内容)——经产品同一条令牌路径(`BrandBoard::for_flavor(..).scheme()` → `to_iced`)取值,浅/深两套板各跑一遍;判据值全部现算(每通道取两端 min/max)。 三条判据一条不少:端点逐位相等、中点**严格**落在两端之间(比原先的闭区间更强)、α 全程 1.0。 守卫文件一行未改(仍 400 行),扫描范围没有放宽。
This commit is contained in:
1 parent
224aa7d9ff
commit
9780fca1c7
1 file changed
+81
-9
@@ -8,6 +8,8 @@
|
|||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::app::components::motion::FRAME_MS;
|
use crate::app::components::motion::FRAME_MS;
|
||||||
|
use crate::theme::to_iced;
|
||||||
|
use unlucky_design::color::{BrandBoard, Role, SchemeFlavor};
|
||||||
use unlucky_design::motion::MotionScale;
|
use unlucky_design::motion::MotionScale;
|
||||||
|
|
||||||
const STANDARD: MotionScale = MotionScale::STANDARD;
|
const STANDARD: MotionScale = MotionScale::STANDARD;
|
||||||
@@ -123,23 +125,93 @@ fn current_from(layer: &Layer) -> (Interaction, Interaction, f32) {
|
|||||||
with_current(*layer, || current().expect("刚压进去的层必须在栈顶"))
|
with_current(*layer, || current().expect("刚压进去的层必须在栈顶"))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 两个不透明色的插值:**端点逐位相等**,中点严格落在两者之间。
|
/// 两个不透明令牌色的插值:**端点逐位相等**,中点严格落在两者之间。
|
||||||
|
///
|
||||||
|
/// ## 端点为什么是角色,而不是 `Color::from_rgb(…)` 写死的数值
|
||||||
|
///
|
||||||
|
/// 这两行原先随手写了 `from_rgb(0.1, 0.2, 0.3)` / `from_rgb(0.7, 0.8, 0.9)`,撞上
|
||||||
|
/// `tests/no_hardcoded_tokens.rs` 的色值守门(它扫 `src/**`,测试模块也算在里面)。修法是
|
||||||
|
/// **改测试、不动守门**:端点取状态层真正会插值的那一对——容器 [`Role::Surface`] 与内容
|
||||||
|
/// [`Role::OnSurface`]——经产品同一条令牌路径(`Scheme::get` → [`to_iced`])翻成 iced 色。
|
||||||
|
///
|
||||||
|
/// ⚠️ 本文件里连**注释**都不许出现「构造名 + 数字」的写法:守门按行做子串匹配,不区分代码与
|
||||||
|
/// 文档(`the_gate_itself_catches_a_planted_violation` 也把这条钉住了)。要举例就写 `from_rgb(…)`。
|
||||||
|
///
|
||||||
|
/// ## 为什么这不算「削弱守卫」(将来别顺手把测试全排除)
|
||||||
|
///
|
||||||
|
/// 守卫的规则是「组件层的颜色一律来自 `unlucky_design` 的 `Role`」,它不区分测试代码与产品
|
||||||
|
/// 代码——也不该区分:测试里的色字面量**就是**组件层源码里长出来的一串数字,例外一旦开在
|
||||||
|
/// 「测试」名下,下一个「临时」的 `from_rgb` 只会多一个借口。所以这里不削弱、不豁免、不给守卫
|
||||||
|
/// 加任何例外:**扫描范围一行没改**,只是把被它正确抓到的那两行换成令牌色。
|
||||||
|
///
|
||||||
|
/// 反过来说,按路径排除 `*/tests.rs` 也排不干净:`#[cfg(test)] mod tests` 与产品代码**同文件**
|
||||||
|
/// (本模块就是 `layer.rs` 末尾的 `#[cfg(test)] mod tests;`),路径分不开「同文件里的测试」与
|
||||||
|
/// 「同文件里的产品代码」;而任何文件都能被改名叫 `tests.rs` 来藏字面量。结论:守卫保持全量
|
||||||
|
/// 覆盖,误判在**被判的那一侧**修。
|
||||||
|
///
|
||||||
|
/// ## 判据(三条一条不少)
|
||||||
|
///
|
||||||
|
/// 1. 端点逐位相等(`p = 0 / 1` 直接返回原值,不许「差不多」);
|
||||||
|
/// 2. 中点**严格**落在两端之间(每个通道用两端现算的 `min`/`max`,不是抄来的数字);
|
||||||
|
/// 3. α 全程 1.0(D27 的「两个后端逐像素一致」就靠这条守着)。
|
||||||
|
///
|
||||||
|
/// 浅/深两套品牌板各跑一遍,判据值全部现算 ⇒ 换板、换品牌色都不会让这条用例变哑。
|
||||||
///
|
///
|
||||||
/// 抓得住:①把端点写成「差不多」(`p == 0` 时返回插值结果 0.999…)——逐位断言红;
|
/// 抓得住:①把端点写成「差不多」(`p == 0` 时返回插值结果 0.999…)——逐位断言红;
|
||||||
/// ②引入 α(`a` 不再是 1.0)——D27 的「两个后端逐像素一致」就靠这条守着。
|
/// ②引入 α(`a` 不再是 1.0)——第 3 条红;③插值写成线性之外的东西(端点仍对、中点越界)——第 2 条红。
|
||||||
|
/// 抓不住:色值本身好不好看(那是角色映射的事)。
|
||||||
#[test]
|
#[test]
|
||||||
fn blend_hits_both_endpoints_bit_for_bit() {
|
fn blend_hits_both_endpoints_bit_for_bit() {
|
||||||
let from = iced::Color::from_rgb(0.1, 0.2, 0.3);
|
for flavor in [SchemeFlavor::Light, SchemeFlavor::Dark] {
|
||||||
let to = iced::Color::from_rgb(0.7, 0.8, 0.9);
|
let from = board_color(flavor, Role::Surface);
|
||||||
|
let to = board_color(flavor, Role::OnSurface);
|
||||||
|
|
||||||
assert_eq!(blend(from, to, 0.0), from);
|
// 「严格介于」的前提:两端不透明,且每个通道真的不同(分量可排序)。
|
||||||
assert_eq!(blend(from, to, 1.0), to);
|
assert_eq!(
|
||||||
|
(from.a, to.a),
|
||||||
|
(1.0, 1.0),
|
||||||
|
"{flavor:?}:Surface / OnSurface 必须都不透明(D27)"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
differs_in_every_channel(from, to),
|
||||||
|
"{flavor:?}:两端有通道相同({from:?} / {to:?}),中点不可能严格介于——换一对角色"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
blend(from, to, 0.0),
|
||||||
|
from,
|
||||||
|
"{flavor:?}:p = 0 必须逐位等于起点"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
blend(from, to, 1.0),
|
||||||
|
to,
|
||||||
|
"{flavor:?}:p = 1 必须逐位等于终点"
|
||||||
|
);
|
||||||
|
|
||||||
for p in [0.25, 0.5, 0.75] {
|
for p in [0.25, 0.5, 0.75] {
|
||||||
let mid = blend(from, to, p);
|
let mid = blend(from, to, p);
|
||||||
assert!((0.1..=0.7).contains(&mid.r), "r 必须落在两端之间");
|
for (name, value, start, end) in [
|
||||||
assert!((0.2..=0.8).contains(&mid.g));
|
("r", mid.r, from.r, to.r),
|
||||||
assert!((0.3..=0.9).contains(&mid.b));
|
("g", mid.g, from.g, to.g),
|
||||||
|
("b", mid.b, from.b, to.b),
|
||||||
|
] {
|
||||||
|
let (lo, hi) = (start.min(end), start.max(end));
|
||||||
|
assert!(
|
||||||
|
value > lo && value < hi,
|
||||||
|
"{flavor:?} p = {p}:{name} 必须**严格**落在两端之间({lo} < {value} < {hi})"
|
||||||
|
);
|
||||||
|
}
|
||||||
assert_eq!(mid.a, 1.0, "中间态也不许引入半透明(D27)");
|
assert_eq!(mid.a, 1.0, "中间态也不许引入半透明(D27)");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 品牌板上某个角色的 iced 颜色(与产品同一条令牌路径)。
|
||||||
|
fn board_color(flavor: SchemeFlavor, role: Role) -> iced::Color {
|
||||||
|
to_iced(BrandBoard::for_flavor(flavor).scheme().get(role))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 三个通道是否都不同——只有全不同,「严格介于」才有定义。
|
||||||
|
fn differs_in_every_channel(from: iced::Color, to: iced::Color) -> bool {
|
||||||
|
from.r != to.r && from.g != to.g && from.b != to.b
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user