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
149 lines
5.9 KiB
Rust
149 lines
5.9 KiB
Rust
//! 实例 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);
|
||
}
|