diff --git a/crates/app-iced/src/app/update/download.rs b/crates/app-iced/src/app/update/download.rs index 7996fcd..cff3c40 100644 --- a/crates/app-iced/src/app/update/download.rs +++ b/crates/app-iced/src/app/update/download.rs @@ -91,8 +91,19 @@ impl App { self.rebuild_after_query(String::new()); } // M107:主版本桶的选中是纯视图态,不重建、不派命令(同 `SelectVersion`)。 + // + // **搜索期间点桶 = 「我就是要看这个桶」**(复审 r2 P2-1(b)):搜索让第二级失效(P2-1 的 + // 一致性放宽),如果这里只改选择而不清查询,用户会看到「点了没反应、清空搜索后才生效」 + // 的活控件。所以搜索中点桶顺带退出搜索,按新桶过滤——一次点击做完用户想要的两件事。 pages::Message::SelectMajor(key) => { - self.view.download.selected_major = key.clone(); + if self.view.download.has_query() { + // 先退出搜索再落选择:`rebuild_after_query` 会用**全量行**重算桶并回填选择 + // (它带回 `selected_major`,且校验桶仍存在)。 + self.view.download.selected_major = key.clone(); + self.rebuild_after_query(String::new()); + } else { + self.view.download.selected_major = key.clone(); + } } // M107:类型栏的手动折叠也是纯视图态。 pages::Message::ToggleTypeRail => { @@ -297,6 +308,48 @@ mod tests { assert_eq!(app.view.download.rows.len(), 1, "列表仍收窄"); } + /// **搜索期间点主版本桶 = 退出搜索并按该桶过滤**(复审 r2 P2-1(b))。 + /// + /// 抓得住:`SelectMajor` 只改选择不清查询 —— 那时搜索让第二级失效(P2-1 的一致性放宽), + /// 用户点了当场没反应、要等清空搜索那次点击才生效,就是「可点但无反应」的活控件。 + /// 抓不住:真机点击是否走到这里(`ListItem`/`Chip` 的 `on_press` 是组件既有实现)。 + #[test] + fn clicking_a_bucket_during_a_search_leaves_the_search() { + let mut app = app(); + let _ = app.on_page(pages::Message::SetVersionQuery("1.20".to_string())); + assert_eq!(app.view.download.rows.len(), 2, "此刻在搜 1.20"); + + // 点「1.21」这个桶 + let _ = app.on_page(pages::Message::SelectMajor(Some("1.21".to_string()))); + + assert!(app.state.download_query.is_empty(), "点桶顺带退出搜索"); + assert!(!app.view.download.has_query()); + assert_eq!( + app.view.download.selected_major.as_deref(), + Some("1.21"), + "选择落在刚点的那个桶上" + ); + assert_eq!( + app.view.download.rows.len(), + 3, + "查询词清掉之后模型回到全量(桶过滤发生在渲染时的 visible 里)" + ); + assert_eq!( + app.view + .download + .buckets + .iter() + .find(|bucket| bucket.key.as_deref() == Some("1.21")) + .map(|bucket| bucket.count), + Some(1), + "1.21 桶里只有一个版本 ⇒ 列表会显示一行" + ); + + // 没有搜索词时点桶照旧只改选择(不触发多余重建) + let _ = app.on_page(pages::Message::SelectMajor(None)); + assert_eq!(app.view.download.selected_major, None); + } + /// 搜索时侧栏的**位置与折叠**不许丢(`rebuild_after_query` 的三行)。 /// /// 抓得住:删掉那两行回填(敲第一个字符,类型栏自己弹开、主版本跳回「全部」)。 diff --git a/crates/app-iced/src/pages/download.rs b/crates/app-iced/src/pages/download.rs index b207cad..96e7b3d 100644 --- a/crates/app-iced/src/pages/download.rs +++ b/crates/app-iced/src/pages/download.rs @@ -62,6 +62,7 @@ //! | 有命中 | 列表照常;侧栏**位置不动**;搜索条右侧给「N 个匹配」(跨类型读数) | //! | 一个都不命中 | [`states::no_match`]:没有匹配 + 换短片段 + 清空 | //! | 有命中但不在当前类型 | [`states::elsewhere`]:N 个在别的类型 + 「看全部类型」 | +//! | 搜索中点第二级桶 | **退出搜索 + 按那个桶过滤**(点桶 = 「我就是要看这个桶」,见 `on_download`) | //! //! 查询词写在 presenter 快照的 `download_query` 里(页面无状态,见 [`model::Download::from_snapshot`]), //! 所以「搜索 → 任何事件回来之后仍然在搜」这条不需要页面持有任何字段。 diff --git a/crates/app-iced/src/pages/download/group.rs b/crates/app-iced/src/pages/download/group.rs index ac50acf..51ff5e0 100644 --- a/crates/app-iced/src/pages/download/group.rs +++ b/crates/app-iced/src/pages/download/group.rs @@ -151,7 +151,7 @@ pub fn buckets(rows: &[super::model::VersionRow]) -> Vec { /// 放宽之后「列表行数 = 当前分类里的命中数」,侧栏也不会有「选中项不在桶列表里」的幽灵态。 /// `selected_major` 一直留在模型里,只是搜索期间不生效——清空搜索后原来的桶自动回来。 #[must_use] -pub fn active_bucket<'a>(model: &'a super::model::Download) -> Option<&'a str> { +pub fn active_bucket(model: &super::model::Download) -> Option<&str> { if model.has_query() { None } else {