//! 下载页(M94)在 app 层的接线测试:**命令出得去、状态不自己变**。 //! //! 从 `app/update.rs` 外置(那边 348 行 + 这个模块会顶破 400 行硬上限;同一句话的理由见 //! `crates/i18n/src/catalog/tests.rs`)。`use super::*` 依然指向 `crate::app::update`。 use std::cell::RefCell; use std::rc::Rc; use unlucky_presenter::dto::{AppEvent, Backend, VersionCategory}; use unlucky_presenter::settings_bridge::SettingsBridge; use super::*; /// 只记账、**不回事件**的后端(同 `app/tests/presenter.rs`):命令出了 UI 层,状态一个字节都不许变。 /// 不用 `MockBackend`:它同步回事件,「本地乐观回显」与「事件回流」终态相同,断言会**假绿**。 struct Recorder(Rc>>); impl Backend for Recorder { fn drain_events(&mut self) -> Vec { Vec::new() } fn dispatch(&mut self, command: AppCommand) { self.0.borrow_mut().push(command); } } /// 四条下载页消息:三条派真指令,`SelectVersion` 只改视图模型(P3-2 的不乐观回显)。 /// 抓得住:意图被写成「本地改状态就算完」、或派错指令;抓不住:host 收到后真的开始下载。 #[test] fn download_intents_dispatch_real_commands() { let log: Rc>> = Rc::new(RefCell::new(Vec::new())); let families = crate::theme::Families { text: "Segoe UI", display: "Segoe UI", mono: "Consolas", }; let mut app = App::with_presenter( crate::cli::Options::default(), families, SchemeFlavor::Light, Box::new(Recorder(Rc::clone(&log))), SettingsBridge::memory_only(), ); // M95 起 `on_page` 返回 `Task`(剪贴板命令要走返回的 Task):这里只关心派发与视图模型, // 任务本身没有需要断言的后效,显式丢弃。 let _ = app.on_page(pages::Message::InstallVersion("1.21.9".to_string())); let _ = app.on_page(pages::Message::CancelInstall); let _ = app.on_page(pages::Message::SetCategory(pages::Category::Release)); let _ = app.on_page(pages::Message::SelectVersion("1.21.8".to_string())); let expected = vec![ AppCommand::InstallVersion("1.21.9".to_string()), AppCommand::CancelInstall, AppCommand::ListVersions(VersionCategory::Release), ]; assert_eq!( *log.borrow(), expected, "三条各派一条;`SelectVersion` 不派" ); assert_eq!(app.view.download.selected.as_deref(), Some("1.21.8")); assert_eq!(app.view.download.category, pages::Category::Release); assert_eq!( app.view.download.rows.len(), 0, "recorder 不回事件 ⇒ 列表不该冒出来" ); // 分类是本地过滤条件:**任何一次视图重建都不许把它打回 `All`**(复审 P2-1 的回归点)。 // 抓得住:`refresh_view` 忘记把分类带回去(那一行删掉这条就红)。 // 抓不住:真 host 是否按分类拉清单(那是 presenter 的 `spawn_list`,已报 finding)。 app.refresh_view(); assert_eq!( app.view.download.category, pages::Category::Release, "重建视图模型之后分类必须还在" ); }