Files
Unlucky-Launcher/crates/presenter/src/dto/instances/tests.rs
T
AskaEth 0522a41a52
CI / fmt (rustfmt --check) (push) Waiting to run
CI / clippy (ubuntu-latest) (push) Waiting to run
CI / clippy (windows-latest) (push) Waiting to run
CI / test (ubuntu-latest) (push) Waiting to run
CI / test (windows-latest) (push) Waiting to run
CI / build release (ubuntu-latest) (push) Waiting to run
CI / build release (windows-latest) (push) Waiting to run
CI / runtime smoke (ubuntu x64, xvfb) (push) Waiting to run
CI / runtime smoke (macos arm64, probe) (push) Skipped
CI / update-e2e (windows x64) (push) Waiting to run
GPU evidence / GPU 判据自测(不需 GPU;跳过 ≠ 已验证) (push) Waiting to run
GPU evidence / GPU 证据 — 探测可用产物与硬件适配器 (push) Waiting to run
Guards / core 零 UI 守门 (push) Waiting to run
Guards / presenter 零 UI 守门 (push) Waiting to run
Guards / design 零第三方依赖守门 (push) Waiting to run
Guards / unsafe 落点守门(集合必须恰好等于期望) (push) Waiting to run
Guards / 文件行数 ≤400 守门 (push) Waiting to run
Guards / 依赖许可与来源(cargo-deny) (push) Waiting to run
Release guard / 发布标记语义守门(push tag → 稳定版) (push) Waiting to run
Signing status(播报,不是门禁) / 判据自测(三行/退出码/变异重放/接线) (push) Waiting to run
Signing status(播报,不是门禁) / 签名就绪度(播报) (push) Waiting to run
Release / 选槽位(默认四个:Windows 两档 + Linux 两档) (push) Waiting to run
release: v0.2.5
2026-11-12 12:06:59 +08:00

149 lines
5.9 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.
//! 实例 DTO 的单测(词表契约 / 徽标 / 序列化往返)。
use super::*;
use unlucky_core::game::LoaderKind as CoreLoaderKind;
use crate::dto::LaunchArgsView;
#[test]
fn loader_badges_and_ids() {
let vanilla = LoaderInfo::default();
assert_eq!(vanilla.badge(), "原版");
let fabric = LoaderInfo {
kind: LoaderKind::Fabric,
version: "0.16.5".to_string(),
};
assert_eq!(fabric.badge(), "Fabric 0.16.5");
let liteloader = LoaderInfo {
kind: LoaderKind::LiteLoader,
version: "1.12.2-SNAPSHOT".to_string(),
};
assert_eq!(liteloader.badge(), "LiteLoader 1.12.2-SNAPSHOT");
for kind in LoaderKind::wizard_choices() {
assert_eq!(LoaderKind::parse(kind.as_str()), Some(kind));
}
assert_eq!(LoaderKind::parse("fabric"), Some(LoaderKind::Fabric));
assert_eq!(LoaderKind::parse("unknown"), None);
}
/// 向导可选集:**每一支都能被 core 的安装侧接受**(OptiFine 不在其中,因为它装不了)。
///
/// 抓得住:把 OptiFine 塞进向导(选完才失败)、漏掉 LiteLoader(GUI 走不到真实路线)。
#[test]
fn wizard_choices_are_all_installable() {
let installable: Vec<&str> = LoaderKind::wizard_choices()
.iter()
.filter(|kind| **kind != LoaderKind::Vanilla)
.map(|kind| kind.as_str())
.collect();
for id in &installable {
assert!(
unlucky_core::addon::loader::LoaderKind::parse(id).is_some(),
"`{id}` 在向导里但安装侧不认"
);
}
assert_eq!(installable.len(), 5, "原版 + 五家能装的:{installable:?}");
assert!(!installable.contains(&"optifine"));
}
/// 对外契约:七个 id 在测试里**独立抄一遍**(0.2.0 已发布形状),并与 core 的 `ALL` 做
/// **集合比较**——这是对外词表的守卫(与 CLI 的 help 守卫互补)。显示路径另有守卫:
/// `presenter::host::game::every_core_loader_kind_reaches_the_tile_as_itself`(按 `ALL` 遍历)。
///
/// 为什么不是编译器管:编译期只拦得到「漏 match」,而新种类在还有 match 的消费方用最省事的
/// `=> Vanilla` 糊过去**编得过**,那时新加载器会静默显示成原版(`game::loader` 模块文档
/// 第 ③ 步)。对外词表由下面第 ② 段的集合断言守,显示路径由上面那条测试守。
///
/// 抓得住:改词表(`neoforge` → `neo_forge`);**新增一家加载器而没主动更新这份契约**
/// (core 的 `ALL` 多一支 ⇒ 集合不等);DTO 委派接错支(`OptiFine` 映到 `LiteLoader`);
/// `parse` 收进不该有的别名。
/// 抓不住:core 的解析规则(那是 `game::loader` 的
/// `every_stable_id_parses_back_to_its_kind` 等测试的职责),以及真二进制输出面的 JSON
/// (CLI 的 `json_rows_expose_loader_kind_and_version`)。
#[test]
fn loader_kind_ids_are_the_released_contract() {
const CONTRACT: [(LoaderKind, &str); 7] = [
(LoaderKind::Vanilla, "vanilla"),
(LoaderKind::Fabric, "fabric"),
(LoaderKind::Quilt, "quilt"),
(LoaderKind::Forge, "forge"),
(LoaderKind::NeoForge, "neoforge"),
(LoaderKind::OptiFine, "optifine"),
(LoaderKind::LiteLoader, "liteloader"),
];
// ① DTO 这一层:每支的 id、反查、大小写。
for (kind, id) in CONTRACT {
assert_eq!(kind.as_str(), id, "{kind:?} 的稳定 id 变了");
assert_eq!(LoaderKind::parse(id), Some(kind), "`{id}` 认不回来");
let upper = id.to_ascii_uppercase();
assert_eq!(LoaderKind::parse(&upper), Some(kind), "大写 `{upper}`");
}
// ② core 的词表 ↔ 这份契约:**集合相等**才过(多一支、少一支、改一个字都红)。
let mut actual: Vec<&str> = CoreLoaderKind::ALL
.iter()
.map(|kind| kind.as_str())
.collect();
actual.sort_unstable();
let mut expected: Vec<&str> = CONTRACT.iter().map(|(_, id)| *id).collect();
expected.sort_unstable();
assert_eq!(
actual, expected,
"core 的 `LoaderKind::ALL` 必须逐字等于对外契约:加一家加载器时先改这里 + help"
);
// GUI 输入别名(不是对外 id):不装加载器有三种写法;别名与拼法都不许混进词表。
for alias in ["none", "", " "] {
assert_eq!(
LoaderKind::parse(alias),
Some(LoaderKind::Vanilla),
"`{alias}`"
);
}
assert_eq!(LoaderKind::parse("neo-forge"), None, "别名只归安装侧");
}
#[test]
fn progress_ratio_handles_unknown_total() {
assert_eq!(Progress { done: 1, total: 0 }.ratio(), 0.0);
assert!((Progress { done: 1, total: 4 }.ratio() - 0.25).abs() < f32::EPSILON);
}
#[test]
fn create_request_defaults_name_to_version() {
let request = CreateRequest {
version: "1.20.4".to_string(),
loader: LoaderKind::Vanilla,
loader_version: String::new(),
name: " ".to_string(),
};
assert_eq!(request.instance_name(), "1.20.4");
}
#[test]
fn instance_summary_is_serde_round_trip_safe() {
let instance = InstanceSummary {
id: "inst-1".to_string(),
name: "生存".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-31T12:00:00.000Z".to_string()),
runtime: InstanceRuntime::Busy {
stage: LaunchStage::Window,
progress: Progress { done: 1, total: 2 },
},
icon: None,
isolation: "isolated".to_string(),
version_id: "1.20.4".to_string(),
version_present: true,
launch: LaunchArgsView::default(),
};
let text = serde_json::to_string(&instance).expect("序列化");
let back: InstanceSummary = serde_json::from_str(&text).expect("反序列化");
assert_eq!(back, instance);
}