Files
Unlucky-Launcher/crates/app-iced/src/pages/download/tests/view.rs
T
AskaEth a70d5f8010 fix(download): 窄档面板加专属预算,首屏露出 head+六枚 chip+说明、实例名回到 56px(M227)
720×760(chips 档)原先走 `TailShare::Shared`:面板与列表各吃 Fill 的一半,面板钉死区
(说明 + 菜单 + 实例名 ≈180px)把它吃穿 ⇒ 滚动区视口 0,head(版本 id + 安装按钮)、
六枚加载器 chip、说明一个像素都不在屏,实例名被挤成 35px 且降部被自身边框裁掉
(M222 D30 五审 `artifacts/m222/visual-verdict-r5-hold.md`)。

改法(把 M222 在 1180 档验证过的「面板先保预算、列表吃整行」落到窄档):

- `TailShare` 拆出第三支 `Narrow`(chips 档 + 面板占高度),`of()` 的三条 arm 与
  `Shared` 的机制注释按 r8 finding 重写(旧文档只写 M125 那一条条件);
- 新增 `CHIPS_STACK` 121 / `CHIPS_LIST_CHROME` 119 / `CHIPS_PANEL_BUDGET` 192 与
  `narrow_rows()`:720/760 下面板先拿预算,列表 = **132px = 3 整行**(M125 像素守门的下限);
- `options::section` 三分支:窄档钉死区**只留实例名**,head / 六枚 chip / 说明 / 版本菜单
  进滚动区(滚动区 120px ≥ 首屏 104,菜单露容器上沿 + 滚动条可见);
- `TailShare::list_height()` 把「列表吃多少」收到一处,两个装配点共用(三栏档行为不变)。

取舍:窄档列表 4 整行 → 3 整行;菜单从钉死改为可滚。理由(459px 里「钉住菜单」+「3 整行
列表」互斥)写在 `rows/tail.rs::CHIPS_PANEL_BUDGET`。

实测(tiny-skia 出图 + 自写 measure.py,DevDocs/artifacts/m227/):
720×760 分隔线 559→514、滚动区 0→120、head 539–578、chips 583–614(32px)、说明 623–634
(8.98:1 浅 / 10.83:1 深)、实例名 668–723 = 56px(inset 12/9/17);
700×760(options-fabric)实例名从「完全不在屏」回到 56px;四条像素守门全绿;
1180×760 浅/深/中 与 1180×900 **逐像素零变化**。
2026-11-10 23:07:19 +08:00

243 lines
8.9 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.
//! 控件树那一节:九个状态 × 四档宽度都要能构图(M105/M107 合流后 `tests.rs` 到 433 行,
//! 按 AGENTS.md §3-1 拆出来)。
//!
//! 只测「能构建出树」——**抓不住**画出来长什么样(像素由 `DevDocs/artifacts/m107/` 的图负责)。
use unlucky_i18n::Lang;
use unlucky_presenter::dto::{InstallProgressView, InstallStage, VersionKind};
use crate::pages::download::model::{Download, InstallView, VersionRow};
use crate::pages::download::{view, Category, Message};
use crate::pages::layout::{frame_metrics, PaneKit};
use super::{ui, version};
/// 九个状态都能构建出控件树,且四档宽度都能构建(不 panic、消息类型对得上)。
///
/// 抓得住:某个状态分支漏了返回值、或消息类型接错(编译期就红);空态/加载态忘了给内容。
/// 抓不住:**画出来长什么样**——像素由 `DevDocs/artifacts/m107/` 的图负责。
#[test]
fn every_state_builds_a_tree_at_every_width() {
let ui = ui();
let catalog = Lang::En.catalog();
let loading = Download::loading();
let empty = Download {
loaded: true,
..Download::loading()
};
let ready = Download {
options: None,
rows: vec![VersionRow::of(
&version("1.21.9", VersionKind::Release, false),
catalog,
)],
category: Category::Release,
selected: Some("1.21.9".to_string()),
install: None,
loaded: true,
// M107 的五个新字段:桶 / 查询 / 命中数 / 第二级选中 / 折叠态
..Download::default()
};
let installing = Download {
install: Some(InstallView::of(
&InstallProgressView {
version: "1.21.9".to_string(),
stage: InstallStage::Downloading,
files_done: 128,
files_total: 512,
..InstallProgressView::new()
},
catalog,
)),
..ready.clone()
};
let failed = Download {
install: Some(InstallView::of(
&InstallProgressView {
version: "1.21.9".to_string(),
stage: InstallStage::Failed,
..InstallProgressView::new()
},
catalog,
)),
..ready.clone()
};
// M107:搜索 / 第二级选中 / 搜不到 / 手动折叠四个新增状态的取样
let searching = Download {
query: "1.21".to_string(),
hits: 1,
..ready.clone()
};
let majored = Download {
selected_major: Some("1.21".to_string()),
..ready.clone()
};
let no_match = Download {
query: "zzz".to_string(),
hits: 0,
..ready.clone()
};
let folded = Download {
type_rail_folded: true,
..ready.clone()
};
// 四档宽度:1180 / 1024(Wide 门槛)/ 900(Narrow)/ 700(Chips)
for width in [1180.0, 1024.0, 900.0, 700.0] {
let metrics = frame_metrics(PaneKit::Single, width);
for model in [
loading.clone(),
empty.clone(),
ready.clone(),
installing.clone(),
failed.clone(),
searching.clone(),
majored.clone(),
no_match.clone(),
folded.clone(),
] {
let _: iced::Element<'_, Message> = view(&ui, metrics, &model, Lang::En);
}
}
}
/// 安装失败的模型:可读文案 + 出口都有(不静默)。
#[test]
fn a_failed_install_stays_visible() {
let model = InstallView::of(
&InstallProgressView {
version: "1.21.9".to_string(),
stage: InstallStage::Failed,
..InstallProgressView::new()
},
Lang::En.catalog(),
);
assert!(model.failed());
assert!(model.terminal());
assert!(!model.running(), "失败之后不再给「取消安装」");
assert_eq!(model.stage_label, "Install failed");
}
/// 演示数据自己也得守规矩(它是出图的基线):八行、四个主版本桶、英文目录下不出现中文类型词。
#[test]
fn the_demo_data_is_sane() {
let catalog = Lang::En.catalog();
let demo = crate::pages::download::demo(catalog);
assert!(demo.loaded);
assert_eq!(demo.rows.len(), 8);
assert_eq!(demo.category, Category::All, "混合列表没有单个分类该亮");
assert!(demo.query.is_empty(), "缺省演示没有搜索词");
assert_eq!(
demo.buckets
.iter()
.map(|bucket| bucket.key.as_deref())
.collect::<Vec<_>>(),
vec![Some("24w"), Some("1.21"), Some("1.20"), None],
"第二级侧栏的桶顺序与内容"
);
assert!(demo
.rows
.iter()
.all(|row| row.supporting.contains("released")));
assert!(demo.rows.iter().all(|row| !row.supporting.contains('版')));
}
/// 列表与尾部面板怎么分高:**档位 + 面板形态** → 三档之一(`Pinned` / `Shared` / `Narrow`)。
///
/// 修前(M123):面板一律按内容高钉在底部,700×760 的 chips 档里列表只剩 18px(0.4 行)。
/// M125 r1 修法只判档位,又踩出另一条回归:chips 档「刚进页面还没选版本」(`Hint`,面板只有
/// 一行提示)也被五五分,700×760 下只显示 **4 整行**、下半块留 ≈160px 空画布
/// (复审 P2-1 实测;main 那一态是 **7 整行**——不是 8:第 8 条墨迹其实是
/// `Pick a version, then install` 那句 hint,横向范围对不上版本号。r2 修回 7 整行)。
///
/// 现在的判据(M227 重写,旧文档只写了 M125 那一条条件 —— r8 复审 improve finding):
/// ① `Chips` **且**面板真占高度(`Options` / `Install`)⇒ `Narrow`;
/// ② **任何档位**的 `Options` 只要带加载器版本列表 ⇒ `Shared`(三栏档只走这条);
/// ③ 其余(含 `Hint`)⇒ `Pinned`。
///
/// 抓得住:①chips 档改回「Pinned」(面板把列表压成缝 ⇒ 像素守门
/// `ready::a_ready_shot_keeps_the_whole_visible_list` / `chips::a_chips_tier_shot_…` 立刻红);
/// ②三栏档也改成 `Narrow`(1180 下钉死区少了菜单与说明,`an_options_shot_…` 的量测会红)。
/// 抓不住:**实际分到多少像素**(`iced` 的 `Element` 没有几何内省,那只能出图量)。
#[test]
fn the_chips_tier_shares_the_height_only_when_the_panel_takes_room() {
use crate::pages::download::install::panel_state;
use crate::pages::download::options::{InstallDraft, Loader, OptionsView};
use crate::pages::download::rows::TailShare;
use crate::pages::layout::VersionRailTier;
use unlucky_presenter::state::AppState;
let catalog = Lang::En.catalog();
let mut app = AppState::new();
app.apply(unlucky_presenter::dto::AppEvent::Core(
unlucky_presenter::dto::CoreEvent::VersionListChanged(vec![version(
"1.21.9",
VersionKind::Release,
false,
)]),
));
let row = VersionRow::of(&version("1.21.9", VersionKind::Release, false), catalog);
// 选中版本 ⇒ 选项区(面板真的占高度)
let options = Download {
options: Some(OptionsView::of(
&row,
&app,
&InstallDraft {
selected: Some("1.21.9".to_string()),
loader: Loader::Vanilla,
loader_version: String::new(),
instance_name: String::new(),
},
catalog,
)),
install: None,
loaded: true,
..Download::default()
};
// 刚进页面 ⇒ 只有一行提示
let hint = Download {
options: None,
install: None,
loaded: true,
..Download::default()
};
assert_eq!(
TailShare::of(VersionRailTier::Chips, &panel_state(&options)),
TailShare::Narrow,
"chips 档 + 选项区展开:窄档预算(面板只钉实例名,列表 3 整行)"
);
assert_eq!(
TailShare::of(VersionRailTier::Chips, &panel_state(&hint)),
TailShare::Pinned,
"chips 档 + 只有一行提示:钉死(8 个版本要在屏)"
);
for tier in [
VersionRailTier::Wide,
VersionRailTier::Narrow,
VersionRailTier::Compact,
] {
assert_eq!(
TailShare::of(tier, &panel_state(&options)),
TailShare::Pinned,
"{tier:?}:三栏档一律钉死(面板整块在屏)"
);
assert_eq!(TailShare::of(tier, &panel_state(&hint)), TailShare::Pinned);
}
// 两个装配点(三栏装配 / chips 分支)都**按档位 + 面板形态**传
let chips_branch = include_str!("../../download.rs");
let three_column = include_str!("../rails.rs");
assert_eq!(
chips_branch.matches("TailShare::of(rails.tier,").count()
+ three_column.matches("TailShare::of(rails.tier,").count(),
2,
"三栏装配与 chips 分支都要把面板形态传进 TailShare::of"
);
}