refactor(m109): rebase 到 5adac23 后按功能拆两条超 400 行的文件
rebase 到 M107/M108 之后的 main(5adac23),并集解冲突后 `app/state.rs`(401)与 `app/view.rs`(402)双双顶破 400 硬线。按功能拆,不压注释: - `app/state/boot_probe.rs`:启动期取证两件——`app_options`(启动参数 → 后端选项)与 `log_first_frame`(首帧耗时一行)+ 它们的两个全局量 `STARTED` / `FIRST_FRAME_LOGGED`; `state.rs` 用 `pub(crate) use` 再导出,`app/boot.rs`(`use super::state::STARTED`)与 `app/view.rs`(`log_first_frame`)的路径一字未变。state.rs 401 → 376。 - `app/view/tests.rs`:`view.rs` 的守门测试独立成文件(`#[path] mod tests;`)。**内容取自 当前 main 的测试体**(M106 的 `Lang` 形参 / `at()` 入口都在),不是旧分支那一份;view.rs 402 → 255。 - `presenter/src/state/content.rs` 与 `presenter/src/state/content_tests.rs`:上一轮已拆,本轮不变。 门禁(新 tip,`-j 2`,逐条前台跑并落盘退出码):fmt 0 / clippy -D warnings 0 / `cargo test -p unlucky-app-iced --lib` 0(522 passed)/ `cargo test --workspace --exclude unlucky-app` 0。
This commit is contained in:
1 parent
42099d4336
commit
1c5283058b
6 files changed
+246
-217
No files matched your search
@@ -0,0 +1,149 @@
|
||||
//! `app/view.rs` 的守门测试(rebase 到 5adac23 之后 view.rs 贴 400 行上限,按「测试独立成文件」拆出)。
|
||||
|
||||
use super::*;
|
||||
use unlucky_presenter::dto::{InstanceSummary, LoaderInfo, LoaderKind, Progress};
|
||||
use unlucky_presenter::mock_data::mock_instances;
|
||||
|
||||
fn instance(runtime: InstanceRuntime) -> InstanceSummary {
|
||||
InstanceSummary {
|
||||
id: "survival".to_string(),
|
||||
name: "Survival".to_string(),
|
||||
mc_version: "1.20.4".to_string(),
|
||||
loader: LoaderInfo {
|
||||
kind: LoaderKind::Fabric,
|
||||
version: "0.16.5".to_string(),
|
||||
},
|
||||
last_played: Some("2026-10-29T19:22:00Z".to_string()),
|
||||
runtime,
|
||||
icon: None,
|
||||
isolation: "isolated".to_string(),
|
||||
version_id: "1.20.4".to_string(),
|
||||
version_present: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// **取证不变量(M100)**:`ForcedState` 的三档在产品路径上必须映射成「页面控件怎么画」。
|
||||
///
|
||||
/// 抓得住:① 谁把 `Rest` 映射成 Hovered/Pressed(那会让 `--screenshot` 的默认值反而画出状态层,
|
||||
/// 与「取证密闭」正好相反,而且图看起来完全正常);② 加了变体却映射到别的态这种复制粘贴错。
|
||||
/// 抓不住:`shell::overridden`(标题栏 / 导航轨按钮走的**另一处** match,断言在 `shell/tests.rs`),
|
||||
/// 也抓不住不消费 `model.forced` 的裸 `iced::button`(设置页「外观」区就是)。
|
||||
/// 测试用的薄包装:默认宽度 / 默认分类 / 空草稿。
|
||||
fn at(state: &AppState, forced: Option<crate::cli::ForcedState>) -> pages::Model {
|
||||
let (draft, lang) = (pages::InstallDraft::default(), Lang::En);
|
||||
let page = pages::content::PageState::default();
|
||||
model(
|
||||
state,
|
||||
1180.0,
|
||||
forced,
|
||||
pages::Category::All,
|
||||
&draft,
|
||||
lang,
|
||||
&page,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_forced_state_maps_to_the_expected_interaction() {
|
||||
let state = AppState::new();
|
||||
let forced = |forced| at(&state, Some(forced)).forced;
|
||||
|
||||
assert_eq!(
|
||||
forced(crate::cli::ForcedState::Rest),
|
||||
Some(Interaction::Enabled),
|
||||
"Rest = 静息态(压掉系统光标 hover)"
|
||||
);
|
||||
assert_eq!(
|
||||
forced(crate::cli::ForcedState::Hover),
|
||||
Some(Interaction::Hovered)
|
||||
);
|
||||
assert_eq!(
|
||||
forced(crate::cli::ForcedState::Pressed),
|
||||
Some(Interaction::Pressed)
|
||||
);
|
||||
assert_eq!(at(&state, None).forced, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_snapshot_becomes_the_page_model() {
|
||||
let mut state = AppState::new();
|
||||
state.apply(unlucky_presenter::dto::AppEvent::Core(
|
||||
unlucky_presenter::dto::CoreEvent::InstancesChanged(mock_instances()),
|
||||
));
|
||||
state.navigate(Route::Detail("survival".to_string()));
|
||||
|
||||
let model = at(&state, None);
|
||||
assert_eq!(model.screen, pages::Screen::Instances);
|
||||
assert_eq!(model.active, Some(pages::Screen::Instances.nav()));
|
||||
assert_eq!(model.window_width, 1180.0);
|
||||
assert_eq!(model.instances.rows.len(), 4, "mock 的四个实例");
|
||||
assert_eq!(model.instances.rows[0].id, "survival");
|
||||
|
||||
let detail = model.instances.detail.expect("路由是 Detail,要有详情");
|
||||
assert_eq!(detail.id, "survival");
|
||||
assert_eq!(detail.meta.len(), 6);
|
||||
assert!(detail.can_launch, "空闲实例可启动");
|
||||
assert!(!detail.can_stop);
|
||||
assert!(detail.note.is_none(), "真快照没有正文段");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_busy_instance_offers_stop_instead_of_play() {
|
||||
let mut state = AppState::new();
|
||||
state.apply(unlucky_presenter::dto::AppEvent::Core(
|
||||
unlucky_presenter::dto::CoreEvent::InstancesChanged(vec![instance(
|
||||
InstanceRuntime::Busy {
|
||||
stage: LaunchStage::Download,
|
||||
progress: Progress {
|
||||
done: 750,
|
||||
total: 1_500,
|
||||
},
|
||||
},
|
||||
)]),
|
||||
));
|
||||
state.navigate(Route::Detail("survival".to_string()));
|
||||
|
||||
let model = at(&state, None);
|
||||
assert_eq!(
|
||||
model.instances.rows[0].status.as_deref(),
|
||||
Some("Downloading · 50%")
|
||||
);
|
||||
let detail = model.instances.detail.expect("有详情");
|
||||
assert!(!detail.can_launch, "启动中不许再点 Play(不做乐观回显)");
|
||||
assert!(detail.can_stop, "启动中要能 Stop");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn failed_instances_can_be_retried() {
|
||||
let failed = instance(InstanceRuntime::Failed {
|
||||
reason: "exit 1".to_string(),
|
||||
});
|
||||
assert_eq!(
|
||||
runtime_status(&failed.runtime).as_deref(),
|
||||
Some("Launch failed · exit 1")
|
||||
);
|
||||
assert!(detail_of(&failed).can_launch, "失败后要能重试");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn timestamps_are_sliced_not_guessed() {
|
||||
assert_eq!(
|
||||
last_played(Some("2026-10-29T19:22:00Z")),
|
||||
"2026-10-29 19:22 UTC"
|
||||
);
|
||||
assert_eq!(last_played(None), "Never");
|
||||
// 短串 / 非 ASCII 原样返回(不 panic)
|
||||
assert_eq!(last_played(Some("2026-10")), "2026-10");
|
||||
assert_eq!(last_played(Some("昨天")), "昨天");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_agent_route_has_no_page() {
|
||||
let mut state = AppState::new();
|
||||
state.navigate(Route::Agent);
|
||||
let model = at(&state, None);
|
||||
// Agent 没有页面:模型退回实例页的空壳,view 会走外壳回退(`None` 分支)
|
||||
assert_eq!(pages::Screen::of(state.route()), None);
|
||||
assert!(model.instances.detail.is_none());
|
||||
assert_eq!(model.active, None, "AI 页不高亮任何导航项");
|
||||
}
|
||||
Reference in new issue
Block a user