P1-1:`icon()` 把 `StyleSpec.stroke` 写死 `0.0`,`IconVariant::Outlined` 的描边角色只到 `roles()` 为止、没画出来 —— Outlined 与 Plain 逐像素相同。改成「有描边角色就是 1px」, 并把「角色 → 填充/内容/描边」抽成 `icon_style_spec()`(`icon()` 直接吃它),单测因此能断言 **接线**而不只是断言 `icon_stroke()` 这个纯函数(第一版正是这样漏掉调用点的)。 回归门两条:纯函数/接线(`buttons/tests/icon.rs`,变异回 0.0 时红)+ 渲染层 (`gallery/probe/rows.rs`:图标按钮左缘 4px 带里 Outlined 有环墨、Plain 必须 0 px; 变异后重出图再跑会红)。环在软渲染里是 Outline 叠画布的抗锯齿混色(实测 110/124/110, 本体 123/138/123),所以判据是「这一带有墨/没墨」而不是颜色匹配。 P1-2:ELEVATED CARD 内距 24 + 值 16px 让「2026-10-29 19:22 UTC」折行,同排卡底不齐; 旧测试还断言 20/14(不变量 118+2×padding ≤ 160 变成 166 > 160,假绿)。 按塔裁决「收内距/字号、不破卡片宽度契约」:`PAD` 24 → 20(= card_padding)、值字级 → BodySmall 12。 量测:时戳 14px 墨水宽 138px(160 外框内宽 120 放不下)、12px 为 118px(放得下,余 2px)。 测试改成四条绑一起(字级 12 / 内距 20 / 160−2×PAD ≥ 118 / 2 列实际宽度也 ≥ 118), 并反证「14px 确实放不下」防登记被当噪音删掉;变异回 14px 时红。 P2(顺带): - 截图通道的主题档由 `--screenshot-theme` 钉死(此前读机器 launcher.json,设置页主题行会漂) - `gallery::probe` 只认 gallery sheet(必须给 `ULL_GALLERY_SECTION`,否则整页图的 chip 会被当按钮量)、 行表 8 → 9 行(四式图标按钮)、分隔线判据只在渲染了 divider 段时执行、逐行阈值 40 → 30 并加 「满档色像素必须少于 38% 像素」的反向判据、probe 拆出 `probe/rows.rs` 守 400 行 - FAB extended 的重复分支合成一处,右内距提取 `EXTENDED_PAD_END`;iced 0.14 无 `Length::Min`, 「最小宽随标签增长」退化为固定宽已登记 - 五处注释谎言改真(theme/tests、general/theme、elevated、launch_args、app/tests/theme) `buttons/tests.rs` 加用例后 402 行 ⇒ 图标按钮那组拆到 `buttons/tests/icon.rs`。
87 lines
3.6 KiB
Rust
87 lines
3.6 KiB
Rust
//! 主题三档(Ink / Silver / 跟随系统)的落点测试(M229;从 `app/tests.rs` 拆出守 400 行)。
|
||
//!
|
||
//! 这一份只管**用户点设置页那三枚 chip 之后**发生的事:换色板、同步设置组状态、落盘。
|
||
//! `Auto`(跟随系统)那条路在 `app::tests::the_system_theme_never_overrides_an_explicit_one`。
|
||
|
||
use crate::app::message::Message;
|
||
use crate::app::state::App;
|
||
use crate::cli::Options;
|
||
use crate::pages;
|
||
use crate::theme::Families;
|
||
use unlucky_design::color::SchemeFlavor;
|
||
use unlucky_presenter::mock::MockBackend;
|
||
use unlucky_presenter::prefs::ThemeMode;
|
||
use unlucky_presenter::settings_bridge::SettingsBridge;
|
||
|
||
const FAMILIES: Families = Families {
|
||
text: "Segoe UI",
|
||
display: "Segoe UI",
|
||
mono: "Consolas",
|
||
};
|
||
|
||
/// 一个把配置写在临时文件里的 App:测「落盘」必须真的过文件(内存桥读回来永远是出厂值)。
|
||
fn with_config() -> (App, std::path::PathBuf) {
|
||
let dir = std::env::temp_dir().join(format!("ull-theme-m229-{}", std::process::id()));
|
||
std::fs::create_dir_all(dir.join("config")).expect("临时目录");
|
||
let path = dir.join("config/launcher.json");
|
||
std::fs::write(&path, "{}").expect("写配置文件");
|
||
|
||
let app = App::with_presenter(
|
||
Options::default(),
|
||
FAMILIES,
|
||
SchemeFlavor::Light,
|
||
Box::new(MockBackend::new()),
|
||
SettingsBridge::with_path(&path),
|
||
);
|
||
(app, dir)
|
||
}
|
||
|
||
/// M229:设置页「通用 → 主题」三档(Ink / Silver / 跟随系统)真的换色板、真的落盘。
|
||
///
|
||
/// 抓得住:①`pages::Message::Theme` 没接进 `on_page`(点了没反应)②接了但不换 `Ui`
|
||
/// (只有选中态变了,界面颜色还是旧的)③同步设置组状态漏了(设置页首帧/刷新后画错档)
|
||
/// ④落盘漏了(重启回旧值——这里**真的过文件**:`with_config` 用 `SettingsBridge::with_path` 写临时
|
||
/// 配置,再用同一路径重新 `load()` 读回;内存桥读回来永远是出厂值,验不了落盘)。
|
||
/// 抓不住:色板好不好看、像素级差异(那要截图,见 `DevDocs/artifacts/m229/`)。
|
||
#[test]
|
||
fn the_theme_message_switches_the_board_and_persists_it() {
|
||
let (mut app, dir) = with_config();
|
||
assert_eq!(app.board_name(), "silver", "首帧是浅色主方案(Silver)");
|
||
|
||
// 选 Ink(= 深色主方案)
|
||
let _ = app.update(Message::Page(pages::Message::Theme(ThemeMode::Dark)));
|
||
assert_eq!(app.theme_mode(), ThemeMode::Dark);
|
||
assert_eq!(app.board_name(), "ink", "Ink 深板要真切过去");
|
||
assert_eq!(app.ui().flavor(), SchemeFlavor::Dark);
|
||
assert_eq!(
|
||
app.settings.language_appearance.theme,
|
||
ThemeMode::Dark,
|
||
"设置页那一行的档位要跟着改"
|
||
);
|
||
// 落盘:真的过了文件(重新打开同一个配置读回来就是 dark)
|
||
assert_eq!(
|
||
SettingsBridge::with_path(dir.join("config/launcher.json"))
|
||
.load()
|
||
.1,
|
||
ThemeMode::Dark,
|
||
"主题档要落盘"
|
||
);
|
||
|
||
// 选 Silver(= 浅色主方案)
|
||
let _ = app.update(Message::Page(pages::Message::Theme(ThemeMode::Light)));
|
||
assert_eq!(app.board_name(), "silver");
|
||
assert_eq!(app.ui().flavor(), SchemeFlavor::Light);
|
||
assert_eq!(
|
||
SettingsBridge::with_path(dir.join("config/launcher.json"))
|
||
.load()
|
||
.1,
|
||
ThemeMode::Light
|
||
);
|
||
|
||
// 同一档再点一次:不许白重建
|
||
let refreshes = app.refreshes;
|
||
let _ = app.update(Message::Page(pages::Message::Theme(ThemeMode::Light)));
|
||
assert_eq!(app.refreshes, refreshes, "同一档不该再重建视图模型");
|
||
std::fs::remove_dir_all(&dir).ok();
|
||
}
|