Files
Unlucky-Launcher/crates/app-iced/src/pages/download/tests/options_screen.rs
T
AskaEth 4a41263357 test(app-iced): M123 安装选项区 × 三栏同屏的宽度与行裁剪守门
安装选项区(`download/options`)与三栏(`download/rails`)各自都有断言,
但它们**同屏**这件事从来没验过:选项区一高列表就矮,窗口一窄两条栏一收列表
就窄,这两种挤压都不会让任何既有断言变红。

- `tests/options_screen.rs`:七档宽度(1180/1024/900/880/812/736/700)× 三种
  选项态(原版 / Fabric / NeoForge 不兼容)。三栏数字按 `layout/rails.rs` 的
  数值表逐格钉死;列表列在每一档都 ≥ 424(行内四样仍在一行);两条栏与选项
  面板在同一份模型里、树能一次构建。
- `tests/options_shot.rs`:真 PNG 上的像素断言(需 `ULL_DL_OPTIONS_SHOT`)。
  两栏同屏且像素宽度 = 裁决值 140/140、栏间 24 的缝真空着、第一行版本行整行
  44px、选项区最后一行墨迹离底边还有余量。另有一条 `#[ignore]` 的 Chips 档
  回归:700×760 下列表被压成 18px(现状红,缺陷已单列报告与 finding)。

出图(38 张)在 `DevDocs/artifacts/m123/`(git 忽略,不入库);旧的
`DevDocs/artifacts/m109/`(内容库页早于 M108 改名,导航轨写的是 `Download`)
已按任务 ② 删除,内容库 12 态在 m123 里按当前 tip 重出。

不动产品行为:本提交只有测试与模块声明。
2026-11-05 15:46:26 +08:00

189 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.
//! 安装选项区 × 三栏**同屏**(M123 ①):宽度怎么分、窄窗怎么退化、选项区在不在同一棵树里。
//!
//! ## 为什么这条测试必须存在
//!
//! 「安装选项区」(`download/options`)与「三栏」(`download/rails`)各自都出过图、也各自都有
//! 断言,但它们**同屏**这件事从来没被验过。选项区挤在列表列的最底下:它一高,列表就矮;
//! 窗口一窄,两条栏一收,列表就窄。这两种挤压都不会让任何既有断言变红——那些断言分别只看
//! 「宽度裁决的数字」「选项区的纯决策」「树能构建出来」,没有一条把它们放在同一屏里看。
//!
//! ## 抓得住什么 / 抓不住什么
//!
//! 抓得住:①七档宽度下 `VersionRails` 的三栏数字与 `pages/layout/rails.rs` 模块文档的表逐格一致;
//! ②选项区的三种状态(原版 / Fabric / 不兼容)在**每一档**上都能与两份侧栏的数据一起构建出树,
//! 而且列表列**从不低于** [`version_list_min_width`](= 424:行内「版本号 + 类型 + 日期 + 安装」
//! 四样仍在一行,不会因为宽度而折行/裁字);③`Chips` 档两条栏归零、列表吃满可用宽(退化是
//! 逐档下沉,不是「挤一挤」)。
//!
//! 抓不住:**画出来长什么样**——两栏是不是真同屏、选项区有没有被窗口底边切掉、某一格文本会不会
//! 仍被裁。前两条由同目录的 `tests/options_shot.rs`(像素断言,需 `ULL_DL_OPTIONS_SHOT`)在真 PNG
//! 上验证,文本本身由 `DevDocs/artifacts/m123/` 的图人工看。
//! 更根本的一条:**没有 iced 的渲染器就拿不到布局**(`Element::layout` 要 `&Renderer`,而本 crate
//! 的 `Renderer` 在 `gpu` feature 下是 wgpu/tiny-skia 的 `fallback::Renderer`,测试里构造不出来),
//! 所以这里断的是**喂给树的模型 + 裁决出来的数字**,不是 iced 亲手算的矩形。
use iced::Element;
use unlucky_i18n::Lang;
use unlucky_presenter::dto::{AncientKind, AppEvent, CoreEvent, VersionKind, VersionSummary};
use unlucky_presenter::state::AppState;
use crate::pages::download::model::{Category, Download};
use crate::pages::download::{view, InstallDraft, Loader, Message};
use crate::pages::layout::rails::version_list_min_width;
use crate::pages::layout::{frame_metrics, PaneKit, VersionRailTier, VersionRails};
use super::ui;
/// 一档宽度的期望(数字来源:`pages/layout/rails.rs` 模块文档 §4 的数值表)。
///
/// 表里每一格都写死,**不**从被策源反算——否则「裁决算错了」与「表抄错了」会互相掩护。
const WIDTHS: [(f32, VersionRailTier, f32, f32, f32); 7] = [
(1180.0, VersionRailTier::Wide, 140.0, 140.0, 716.0),
(1024.0, VersionRailTier::Wide, 140.0, 140.0, 560.0),
(900.0, VersionRailTier::Narrow, 64.0, 140.0, 512.0),
(880.0, VersionRailTier::Narrow, 64.0, 140.0, 492.0),
// 812 = `Narrow` 档的门槛:类型栏已收成 64,列表仍停在 424
(812.0, VersionRailTier::Narrow, 64.0, 140.0, 424.0),
// 736 = `Compact` 档:两条栏都收成 64,列表仍停在 424
(736.0, VersionRailTier::Compact, 64.0, 64.0, 424.0),
// 700 = `Chips` 档:三栏不成立,列表吃满可用宽
(700.0, VersionRailTier::Chips, 0.0, 0.0, 564.0),
];
/// 出图那一屏的演示版本表(三种形态就够:正式 / 预览 / 远古,两条侧栏都有内容)。
fn versions() -> Vec<VersionSummary> {
fn row(id: &str, kind: VersionKind) -> VersionSummary {
VersionSummary {
id: id.to_string(),
kind,
released: "2026-09-20".to_string(),
installed: false,
}
}
vec![
row("1.21.9", VersionKind::Release),
row("1.20.4", VersionKind::Release),
row("24w14a", VersionKind::Snapshot),
row("rd-132211", VersionKind::Ancient(AncientKind::Classic)),
]
}
/// 选项态模型:`AppState` + 草稿 → **产品那条真转换**(与 `demo::options` 同一条路径)。
///
/// `cached` = 加载器版本列表(Fabric 那一屏才有;它是选项区里最高的一段,也正是「面板一高
/// 列表就矮」的那一态)。
fn options_model(version: &str, loader: Loader, cached: Option<&[&str]>) -> Download {
let catalog = Lang::En.catalog();
let mut state = AppState::new();
state.apply(AppEvent::Core(CoreEvent::VersionListChanged(versions())));
if let Some(items) = cached {
state.apply(AppEvent::Core(CoreEvent::LoaderVersions {
kind: loader.kind(),
mc_version: version.to_string(),
result: Ok(items.iter().map(|item| (*item).to_string()).collect()),
}));
}
let draft = InstallDraft {
selected: Some(version.to_string()),
loader,
..InstallDraft::default()
};
Download::from_snapshot(&state, Category::All, &draft, catalog)
}
/// 选项区的三种取样(原版 / Fabric 带版本列表 / NeoForge 对远古版不兼容)。
fn samples() -> [(&'static str, Download); 3] {
[
("options", options_model("1.21.9", Loader::Vanilla, None)),
(
"options-fabric",
options_model(
"1.21.9",
Loader::Fabric,
Some(&["0.16.5", "0.16.4", "0.15.11"]),
),
),
(
"options-incompat",
options_model("rd-132211", Loader::NeoForge, None),
),
]
}
/// 七档宽度 × 三种选项态:三栏数字一致、树能构建、列表列不被挤到 424 以下。
///
/// 424 这条是「不裁字」的宽度侧前提:行内四样的最小宽 `layout::rails::version_row_min_width()`
/// = 412 < 424,所以只要列表列 ≥ 424,版本号那一格就还有 12px 余量,四样不会折行。
#[test]
fn the_options_state_keeps_all_three_columns_at_every_width() {
let ui = ui();
let floor = version_list_min_width();
let samples = samples();
for (width, tier, type_rail, major_rail, list) in WIDTHS {
let metrics = frame_metrics(PaneKit::Single, width);
let rails = VersionRails::of_metrics(metrics);
assert_eq!(rails.tier, tier, "{width} 的档位");
assert!((rails.type_rail - type_rail).abs() < 0.01, "{width} 类型栏");
assert!(
(rails.major_rail - major_rail).abs() < 0.01,
"{width} 主版本栏"
);
assert!((rails.list - list).abs() < 0.01, "{width} 列表列");
assert!(
rails.list >= floor - 0.01,
"{width}:列表列 {} 低于行宽下限 {floor} ⇒ 行内四样会折行/裁字",
rails.list
);
// 三栏档两条栏都 >0(同屏的前提);Chips 档两条都归零、列表吃满(逐档下沉)
if rails.tier.is_three_column() {
assert!(rails.type_rail > 0.0 && rails.major_rail > 0.0, "{width}");
} else {
assert_eq!((rails.type_rail, rails.major_rail), (0.0, 0.0), "{width}");
}
for (name, model) in &samples {
// 同一份模型里:两条栏的内容(4 个类型 / 主版本桶)+ 行 + 选项面板都在
assert_eq!(Category::ALL.len(), 4, "第一栏是四个固定项");
assert!(!model.buckets.is_empty(), "{name}@{width} 第二栏没有桶");
assert!(!model.rows.is_empty(), "{name}@{width} 列表没有行");
assert!(
model.options.is_some(),
"{name}@{width} 面板不是选项态——这张图就不再是「选项区 × 三栏」"
);
// 树真的建得出来(两栏 + 列表 + 面板同一次装配)
let _: Element<'_, Message> = view(&ui, metrics, model, Lang::En);
}
}
}
/// 窄窗退化是**逐档下沉**:类型栏先成图标条、再两条都成图标条,列表列在两种情形下都不低于 424。
///
/// 抓得住:某天有人为了「窄窗也放得下两条栏」把 64 改成 48 或把列表下限往下调 —— 那时这里的
/// 逐档数字会先红。抓不住:图标条里的 tooltip 在真机上是否弹得出来(静态图拍不到 hover 层)。
#[test]
fn the_narrow_tiers_step_down_before_they_squeeze_the_list() {
let mut previous = VersionRails::of_metrics(frame_metrics(PaneKit::Single, 2400.0));
assert_eq!(previous.tier, VersionRailTier::Wide);
for width in [1600.0, 1180.0, 1024.0, 900.0, 812.0, 736.0, 700.0, 600.0] {
let rails = VersionRails::of_metrics(frame_metrics(PaneKit::Single, width));
// 档位只降不升,且降档时**收的是侧栏**,列表不会在降档的那一格突然变窄再变宽
assert!(
rails.list >= version_list_min_width() - 0.01,
"{width}:列表列 {} 跌破 424",
rails.list
);
if rails.tier.is_three_column() && previous.tier.is_three_column() {
assert!(
rails.type_rail <= previous.type_rail + 0.01
&& rails.major_rail <= previous.major_rail + 0.01,
"{width}:侧栏在宽窗上反而变宽了"
);
}
previous = rails;
}
}