Files
Unlucky-Launcher/crates/app-iced/src/pages/download/tests/view.rs
T
AskaEth 89a51a63b1 feat(app-iced): M107 DLVRAIL —— 版本管理页三栏侧栏 + 跨类型搜索
用户令(信息架构):这一页从「下载」改称「版本管理」,版面从「一排 tab + 平铺列表」
改成**页面内三栏**:版本类型栏 + 主版本栏 + 列表/详情,并加搜索框。

- **三栏几何**(`pages/layout/rails.rs`,纯函数 + 数值表):类型栏 140/64、主版本栏 140/64、
  间距 24、列表列下限 424(行宽红线)。四档由**可用宽**裁决:Wide ≥888 / Narrow ≥676 /
  Compact ≥600 / Chips <600,三个门槛都是 `栏宽 + 间距 + 424` 算出来的,不是拍的
- **主版本分组规则**(`download/group.rs`):正式版/预发布按 `major.minor`(`1.21.9-pre3` → `1.21`)、
  周更按年桶(`24w14a` → `24w`)、远古版**没有主版本**(`rd-132211`/`a1.2.6` → 第二级只有「全部」);
  判据用真实版本样本钉住,不按「第一个点」切
- **跨类型搜索**(`search_bar` + 清空 + 命中数):查询词写进 `AppState.download_query`(页面无状态,
  只能这么活),`hits` 是**跨分类**读数;三条口径分开:「有命中」/「没有匹配」/「命中在别的类型里」
- 两级侧栏都用 `navigation::list_item` / `tabs` / `chip` / `buttons::icon`:Tab 可达、hover/pressed/
  选中态全部来自组件,页面不自己写状态层
- 右栏展开(M104 `content_width` 852)⇒ 可用宽 716 ⇒ Narrow 档,列表 464(任务书那句 492 漏扣
  96px 的页边距与栏间距,报告里给了完整数值表)
- 文案 11 条中英进 `crates/i18n`(新域宏接在装配链末尾 ⇒ `Key::ALL` 只追加,金表只追加)
- 护栏:两层侧栏都存在 + 列表按第二级选中态过滤(变异可证)、三档门槛、424 下限扫描、
  版本行单行宽账、搜索条真的装着
- `install.rs` 只动了「已装」分支(加一句「已安装」解释禁用按钮),没碰 M105 的选项区

取证:`DevDocs/artifacts/m107/`(10 张:三档 × 浅深 + 搜索命中 + 空 + 异地 + 远古版)
2026-11-05 01:59:13 +08:00

148 lines
4.8 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('版')));
}