Files
Unlucky-Launcher/crates/design/tests/token_pins.rs
T
AskaEth a5d1dec495
CI / fmt (rustfmt --check) (push) Waiting to run
CI / clippy (ubuntu-latest) (push) Waiting to run
CI / clippy (windows-latest) (push) Waiting to run
CI / test (ubuntu-latest) (push) Waiting to run
CI / test (windows-latest) (push) Waiting to run
CI / build release (ubuntu-latest) (push) Waiting to run
CI / build release (windows-latest) (push) Waiting to run
CI / runtime smoke (ubuntu x64, xvfb) (push) Waiting to run
CI / runtime smoke (macos arm64, probe) (push) Skipped
CI / update-e2e (windows x64) (push) Waiting to run
GPU evidence / GPU 判据自测(不需 GPU;跳过 ≠ 已验证) (push) Waiting to run
GPU evidence / GPU 证据 — 探测可用产物与硬件适配器 (push) Waiting to run
Guards / core 零 UI 守门 (push) Waiting to run
Guards / presenter 零 UI 守门 (push) Waiting to run
Guards / design 零第三方依赖守门 (push) Waiting to run
Guards / unsafe 落点守门(集合必须恰好等于期望) (push) Waiting to run
Guards / 文件行数 ≤400 守门 (push) Waiting to run
Guards / 依赖许可与来源(cargo-deny) (push) Waiting to run
Release guard / 发布标记语义守门(push tag → 稳定版) (push) Waiting to run
Signing status(播报,不是门禁) / 判据自测(三行/退出码/变异重放/接线) (push) Waiting to run
Signing status(播报,不是门禁) / 签名就绪度(播报) (push) Waiting to run
Release / 选槽位(默认四个:Windows 两档 + Linux 两档) (push) Waiting to run
release: v0.2.4 里程碑快照(孤儿提交,无父;树 = dev 68362e5)
本批内容见 CHANGELOG.md 的 0.2.4 段。按 D41 用孤儿快照推 main,保留「公开史只有里程碑」的模型。
2026-11-11 21:41:38 +08:00

253 lines
9.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.
//! 官方数值钉死(二):**色彩 tone 表 49 条**、7 条 easing 曲线、16 档时长、28 个图标下标、
//! 形状阶梯,以及「同族容器不撞色」的回归(reviewer P3-2)。
//!
//! 期望值全部独立于实现重写;实现改一个数、表不改,这里就红。
use unlucky_design::color::{Role, Scheme, SchemeFlavor};
use unlucky_design::icons::IconKind;
use unlucky_design::motion::{DurationToken, Easing};
use unlucky_design::shape::ShapeScale;
fn expect_tone(role: Role, light: f64, dark: f64) {
assert_eq!(
Scheme::tone_of(role, SchemeFlavor::Light),
light,
"{} 的 light tone 被改动了",
role.name()
);
assert_eq!(
Scheme::tone_of(role, SchemeFlavor::Dark),
dark,
"{} 的 dark tone 被改动了",
role.name()
);
}
#[test]
fn all_forty_nine_tones_are_pinned() {
expect_tone(Role::Primary, 40.0, 80.0);
expect_tone(Role::OnPrimary, 100.0, 20.0);
expect_tone(Role::PrimaryContainer, 90.0, 30.0);
expect_tone(Role::OnPrimaryContainer, 30.0, 90.0);
expect_tone(Role::Secondary, 40.0, 80.0);
expect_tone(Role::OnSecondary, 100.0, 20.0);
expect_tone(Role::SecondaryContainer, 90.0, 30.0);
expect_tone(Role::OnSecondaryContainer, 30.0, 90.0);
expect_tone(Role::Tertiary, 40.0, 80.0);
expect_tone(Role::OnTertiary, 100.0, 20.0);
expect_tone(Role::TertiaryContainer, 90.0, 30.0);
expect_tone(Role::OnTertiaryContainer, 30.0, 90.0);
expect_tone(Role::Error, 40.0, 80.0);
expect_tone(Role::OnError, 100.0, 20.0);
expect_tone(Role::ErrorContainer, 90.0, 30.0);
expect_tone(Role::OnErrorContainer, 30.0, 90.0);
expect_tone(Role::Surface, 98.0, 6.0);
expect_tone(Role::OnSurface, 10.0, 90.0);
expect_tone(Role::SurfaceVariant, 90.0, 30.0);
expect_tone(Role::OnSurfaceVariant, 30.0, 80.0);
expect_tone(Role::Background, 98.0, 6.0);
expect_tone(Role::OnBackground, 10.0, 90.0);
expect_tone(Role::SurfaceDim, 87.0, 6.0);
expect_tone(Role::SurfaceBright, 98.0, 24.0);
expect_tone(Role::SurfaceContainerLowest, 100.0, 4.0);
expect_tone(Role::SurfaceContainerLow, 96.0, 10.0);
expect_tone(Role::SurfaceContainer, 94.0, 12.0);
expect_tone(Role::SurfaceContainerHigh, 92.0, 17.0);
expect_tone(Role::SurfaceContainerHighest, 90.0, 22.0);
expect_tone(Role::Outline, 50.0, 60.0);
expect_tone(Role::OutlineVariant, 80.0, 30.0);
expect_tone(Role::InverseSurface, 20.0, 90.0);
expect_tone(Role::InverseOnSurface, 95.0, 20.0);
expect_tone(Role::InversePrimary, 80.0, 40.0);
expect_tone(Role::Shadow, 0.0, 0.0);
expect_tone(Role::Scrim, 0.0, 0.0);
expect_tone(Role::SurfaceTint, 40.0, 80.0);
expect_tone(Role::PrimaryFixed, 90.0, 90.0);
expect_tone(Role::PrimaryFixedDim, 80.0, 80.0);
expect_tone(Role::OnPrimaryFixed, 10.0, 10.0);
expect_tone(Role::OnPrimaryFixedVariant, 30.0, 30.0);
expect_tone(Role::SecondaryFixed, 90.0, 90.0);
expect_tone(Role::SecondaryFixedDim, 80.0, 80.0);
expect_tone(Role::OnSecondaryFixed, 10.0, 10.0);
expect_tone(Role::OnSecondaryFixedVariant, 30.0, 30.0);
expect_tone(Role::TertiaryFixed, 90.0, 90.0);
expect_tone(Role::TertiaryFixedDim, 80.0, 80.0);
expect_tone(Role::OnTertiaryFixed, 10.0, 10.0);
expect_tone(Role::OnTertiaryFixedVariant, 30.0, 30.0);
}
#[test]
fn easing_curves_are_pinned() {
let cases: [(Easing, f32, f32, f32, f32); 7] = [
(Easing::Linear, 0.0, 0.0, 1.0, 1.0),
(Easing::Standard, 0.2, 0.0, 0.0, 1.0),
(Easing::StandardDecelerate, 0.0, 0.0, 0.0, 1.0),
(Easing::StandardAccelerate, 0.3, 0.0, 1.0, 1.0),
(Easing::Emphasized, 0.2, 0.0, 0.0, 1.0),
(Easing::EmphasizedDecelerate, 0.05, 0.7, 0.1, 1.0),
(Easing::EmphasizedAccelerate, 0.3, 0.0, 0.8, 0.15),
];
for (easing, x1, y1, x2, y2) in cases {
let curve = easing.curve();
assert_eq!(
(curve.x1, curve.y1, curve.x2, curve.y2),
(x1, y1, x2, y2),
"{easing:?} 的曲线参数被改动了"
);
}
}
#[test]
fn durations_are_pinned() {
let cases: [(DurationToken, u32); 16] = [
(DurationToken::Short1, 50),
(DurationToken::Short2, 100),
(DurationToken::Short3, 150),
(DurationToken::Short4, 200),
(DurationToken::Medium1, 250),
(DurationToken::Medium2, 300),
(DurationToken::Medium3, 350),
(DurationToken::Medium4, 400),
(DurationToken::Long1, 450),
(DurationToken::Long2, 500),
(DurationToken::Long3, 550),
(DurationToken::Long4, 600),
(DurationToken::ExtraLong1, 700),
(DurationToken::ExtraLong2, 800),
(DurationToken::ExtraLong3, 900),
(DurationToken::ExtraLong4, 1000),
];
for (token, ms) in cases {
assert_eq!(token.ms(), ms, "{} 的毫秒值被改动了", token.name());
}
}
#[test]
fn spring_presets_are_pinned() {
use unlucky_design::motion::{
spring, MotionSchemeKind, Spring, SpringCategory, SpringSpeed, SPRING_EFFECTS_DEFAULT,
SPRING_EFFECTS_FAST, SPRING_EFFECTS_SLOW, SPRING_EXPRESSIVE_SPATIAL_DEFAULT,
SPRING_EXPRESSIVE_SPATIAL_FAST, SPRING_EXPRESSIVE_SPATIAL_SLOW,
SPRING_STANDARD_SPATIAL_DEFAULT, SPRING_STANDARD_SPATIAL_FAST,
SPRING_STANDARD_SPATIAL_SLOW,
};
// A 级:StandardMotionTokens / ExpressiveMotionTokens(damping = 阻尼比 ζ,stiffness = k)。
let cases: [(Spring, f32, f32); 9] = [
// standard 方案 spatial:ζ=0.9(保守)
(SPRING_STANDARD_SPATIAL_FAST, 1400.0, 0.9),
(SPRING_STANDARD_SPATIAL_DEFAULT, 700.0, 0.9),
(SPRING_STANDARD_SPATIAL_SLOW, 300.0, 0.9),
// expressive 方案 spatial:ζ=0.6/0.8(回弹明显)
(SPRING_EXPRESSIVE_SPATIAL_FAST, 800.0, 0.6),
(SPRING_EXPRESSIVE_SPATIAL_DEFAULT, 380.0, 0.8),
(SPRING_EXPRESSIVE_SPATIAL_SLOW, 200.0, 0.8),
// effects 两套同值:ζ=1.0(不过冲)
(SPRING_EFFECTS_FAST, 3800.0, 1.0),
(SPRING_EFFECTS_DEFAULT, 1600.0, 1.0),
(SPRING_EFFECTS_SLOW, 800.0, 1.0),
];
for (entry, stiffness, damping_ratio) in cases {
assert_eq!(entry.stiffness, stiffness, "弹簧刚度被改动");
assert_eq!(entry.damping_ratio, damping_ratio, "弹簧阻尼比被改动");
}
// 二维表查询与常量必须一致(改了表结构也别想绕过 pin)。
assert_eq!(
spring(
MotionSchemeKind::Expressive,
SpringCategory::Spatial,
SpringSpeed::Default
),
SPRING_EXPRESSIVE_SPATIAL_DEFAULT
);
assert_eq!(
spring(
MotionSchemeKind::Standard,
SpringCategory::Spatial,
SpringSpeed::Fast
),
SPRING_STANDARD_SPATIAL_FAST
);
}
#[test]
fn icon_indices_are_pinned() {
let names = [
"instances",
"download",
"logs",
"settings",
"ai",
"play",
"stop",
"search",
"plus",
"close",
"minimize",
"maximize",
"chevron-left",
"chevron-right",
"chevron-down",
"expand",
"send",
"check",
"warn",
"error",
"more",
"sidebar",
"restore",
"user",
"refresh",
"filter",
"folder",
"trash",
];
assert_eq!(IconKind::ALL.len(), names.len());
for (i, kind) in IconKind::ALL.iter().enumerate() {
assert_eq!(
kind.name(),
names[i],
"第 {i} 个图标被换位/改名了(序号即持久化 id)"
);
}
}
#[test]
fn shape_ladder_is_pinned() {
let cases: [(ShapeScale, f32); 9] = [
(ShapeScale::None, 0.0),
(ShapeScale::ExtraSmall, 4.0),
(ShapeScale::Small, 8.0),
(ShapeScale::Medium, 12.0),
(ShapeScale::Large, 16.0),
(ShapeScale::LargeIncreased, 20.0),
(ShapeScale::ExtraLarge, 28.0),
(ShapeScale::ExtraLargeIncreased, 32.0),
(ShapeScale::ExtraExtraLarge, 48.0),
];
for (scale, dp) in cases {
assert_eq!(scale.dp(), dp, "{scale:?} 的半径档位被改动了");
}
assert_eq!(ShapeScale::Full.radius_px(64.0), 32.0);
}
#[test]
fn same_family_containers_do_not_collide() {
// P3-2 回归:light 模式下 primary-container / secondary-container 曾经被 gamut 裁剪成同色。
let light = Scheme::baseline(SchemeFlavor::Light);
let primary = light.get(Role::PrimaryContainer);
let secondary = light.get(Role::SecondaryContainer);
assert_ne!(
primary, secondary,
"同族容器撞色(彩度压缩没生效):{primary:?}"
);
// secondary-container 必须比 primary-container 更灰(M3 的「次级」语义)。
let c_primary = unlucky_design::color::Hct::from_color(primary).chroma;
let c_secondary = unlucky_design::color::Hct::from_color(secondary).chroma;
assert!(
c_secondary < c_primary,
"secondary-container 彩度 {c_secondary} 应低于 primary-container {c_primary}"
);
// tertiary 与 primary 也该可区分(不同色相)。
assert_ne!(light.get(Role::TertiaryContainer), primary);
}