用户令(信息架构):这一页从「下载」改称「版本管理」,版面从「一排 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 张:三档 × 浅深 + 搜索命中 + 空 + 异地 + 远古版)
148 lines
4.8 KiB
Rust
148 lines
4.8 KiB
Rust
//! 控件树那一节:九个状态 × 四档宽度都要能构图(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('版')));
|
||
}
|