fix(packui): M113 r1 复审整改 —— 2 处「界面说假话」+ 7 条必修 P2
复审 r1(p1-2items / fix-then-merge)逐条整改。**P2-6(core 的本地化契约)按塔台指示本轮不动**,
已登记为独立 mission 的债。
## P1
1. **「Save to」是死输入框**:`ExportRequestView` 的唯一构造点硬编码 `target: None`,用户敲的路径
从不进请求。现在构造收成纯函数 `packui::model::export::request_of`(预检与确认共用同一份),
`target` 由字段去空白后给出(空 ⇒ `None`,交给 host 给缺省)。
2. **取消导入的 toast 撒谎**:下载页那条说「安装已取消、实例未创建」,而导入会在后台跑完并
**登记实例**。现在 `cancel_pack()` 回报「刚才有没有导入在跑」,`dispatch` 据此改发
`PackEvent::Cancelled`(不再发 `InstallCancelled`),界面与提示改成
「导入会在后台继续跑完 —— 本次结果作废;已写下的文件会留下」,并把这句话**留在屏上**
(`ImportState.cancelled` + 面板位)。
## P2 必修(7 条)
3. **`Dismiss` 误伤下载页安装**(跨功能污染,最高优先):pack 原来共用 `CoreHost::install_epoch`,
关一次导入面板会把下载页正在跑的安装静默丢掉。现在 pack 有自己的
`PACK_EPOCH`(模块级 `AtomicU64`),`handle` 的 pack 分支只比它,`Dismiss`/取消只动它。
4. **中文实例名塌成 `pack`**:新增 `convert::dir_name` —— 名字全 ASCII 安全字符时保持原样
(`Better MC` → `Better-MC`),一旦折过东西(中文/空格/符号)就补 `-<hash8>`(FNV-1a),
保证两个中文名得到不同目录。
5. **失败回滚后空目录挡住重试**:`target_of` 对「已存在但空」的目录**复用**(非空才拒),
并在失败回滚后清掉自己建的空目录;`failed_report` 的 `target` 语义改正
(以前把包路径塞进「目标实例目录」)。
6. **新实例流程下「以包为准」必然失败**:`can_overwrite` 追加 `page.instance.is_some()` ——
新建实例那条路上不再显示一个必然失败的按钮。
7. **阶段词打架且文档说修好了**:`progress.rs` 的模块文档改成与屏幕一致(粗/细两层 + 已知重叠),
并写清彻底解决要靠「安装面板提成共享组件」那条 finding。
8. **导入侧标题计数与行数对不上**:被拒 component 有了自己的标题(`The client does not support
these: {count}`);冲突标题改成中性的 `Conflicts: {count}`(逐条自带成因与处置)。
9. **预检「没进包」列表没有有界滚动**:13 条是真实形态。列表改成有界滚动(110px),
并且**把动作按钮提到摘要下面**(`Write the pack` / `Back`)—— 明细再长也顶不掉按钮。
10. **导入成功后不刷新实例列表**:`handle` 的 pack 分支在收到 `Report` 时调 `refresh_instances()`
(与 `InstallDone` 同一条),新实例不用等下一次无关的 `RefreshAll`。
## 证据
- 新增测试(都跑真管线/真函数):`the_save_to_field_really_reaches_the_request`、
`pack_epoch_is_its_own_counter`、`chinese_instance_names_get_distinct_directories`、
`a_failed_new_instance_directory_can_be_reused_for_retry`、
`overwrite_is_offered_only_when_importing_into_an_existing_instance`、
`cancelled_state_is_visible_and_says_the_import_keeps_running`。
- 出图重出 12 张(6 态 × 浅深),新增 `06-cancelled-*`;`04-export-*` 用 13 条 skipped 的真实形态。
门禁:`cargo fmt --all --check` 通过;`cargo clippy -p unlucky-app-iced -p unlucky-presenter
-p unlucky-i18n --all-targets -- -D warnings` EXIT=0;`cargo test -p unlucky-app-iced --lib`
507 passed;`cargo test -p unlucky-presenter --lib` 184 passed;`cargo test -p unlucky-i18n --lib`
24 passed;全部改动文件 ≤400 行。
This commit is contained in:
1 parent
27bbc74eaa
commit
99538e45b9
23 files changed
+474
-74
No files matched your search
@@ -261,7 +261,11 @@ fn conflicts_default_to_keeping_the_user_copy() {
|
||||
bytes_written: 0,
|
||||
rolled_back: true,
|
||||
};
|
||||
let page = PackPageState::default();
|
||||
// 「以包为准」只在导入到**已有实例**时给(复审 r1 P2-2),所以这里选一个实例
|
||||
let page = PackPageState {
|
||||
instance: Some("survival".to_string()),
|
||||
..PackPageState::default()
|
||||
};
|
||||
let block = report_of(&report, &page, catalog());
|
||||
assert_eq!(block.conflicts.len(), 1);
|
||||
assert!(!block.conflicts[0].expanded, "默认收起");
|
||||
@@ -276,7 +280,7 @@ fn conflicts_default_to_keeping_the_user_copy() {
|
||||
// 展开的那一条才画三方哈希
|
||||
let expanded = PackPageState {
|
||||
expanded: Some("config/x.json".to_string()),
|
||||
..PackPageState::default()
|
||||
..page.clone()
|
||||
};
|
||||
let block = report_of(&report, &expanded, catalog());
|
||||
assert!(block.conflicts[0].expanded);
|
||||
@@ -294,3 +298,97 @@ fn an_idle_pack_has_nothing_to_draw_but_the_entry() {
|
||||
assert!(!model.drop_hint.is_empty());
|
||||
assert!(!model.no_dialog_note.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_save_to_field_really_reaches_the_request() {
|
||||
// 复审 r1 P1-1:这个字段以前是死的(构造点硬编码 target: None)
|
||||
let page = PackPageState {
|
||||
output: " D:/packs/my.mrpack ".to_string(),
|
||||
..PackPageState::default()
|
||||
};
|
||||
let request = super::export::request_of(&page, "survival");
|
||||
assert_eq!(
|
||||
request.target.as_deref(),
|
||||
Some(std::path::Path::new("D:/packs/my.mrpack")),
|
||||
"用户敲的路径必须真的进请求(去首尾空白)"
|
||||
);
|
||||
|
||||
// 留空 ⇒ None(由 host 给缺省路径),不是空串路径
|
||||
let blank = PackPageState {
|
||||
output: " ".to_string(),
|
||||
..PackPageState::default()
|
||||
};
|
||||
assert!(super::export::request_of(&blank, "survival")
|
||||
.target
|
||||
.is_none());
|
||||
|
||||
// 包名/版本留空时的缺省值也钉一下(预检与写盘共用同一份请求,不能各算一套)
|
||||
let request = super::export::request_of(&blank, "survival");
|
||||
assert_eq!(request.name, "survival");
|
||||
assert_eq!(request.version, "1.0.0");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn overwrite_is_offered_only_when_importing_into_an_existing_instance() {
|
||||
// 复审 r1 P2-2:新建实例那条路上再点「以包为准」必然撞「directory already exists」
|
||||
let report = unlucky_presenter::dto::ImportReportView {
|
||||
status: ImportStatusView::Partial,
|
||||
format: Some(PackFormatView::Modrinth),
|
||||
pack_name: Some("Pack".to_string()),
|
||||
pack_version: None,
|
||||
game_version: None,
|
||||
loader: None,
|
||||
version_id: None,
|
||||
target: std::path::PathBuf::from("C:/tmp"),
|
||||
files: Vec::new(),
|
||||
conflicts: vec![ConflictFileView {
|
||||
path: "config/x.json".to_string(),
|
||||
reason: ConflictReasonView::UserModified,
|
||||
choice: ConflictChoiceView::KeepUser,
|
||||
pack_sha1: "abc".to_string(),
|
||||
base_sha1: None,
|
||||
disk_sha1: None,
|
||||
pack_size: 1,
|
||||
}],
|
||||
missing: Vec::new(),
|
||||
rejected: Vec::new(),
|
||||
warnings: Vec::new(),
|
||||
error: None,
|
||||
registered: None,
|
||||
bytes_written: 0,
|
||||
rolled_back: true,
|
||||
};
|
||||
|
||||
let new_instance = PackPageState::default();
|
||||
assert!(
|
||||
!super::report::report_of(&report, &new_instance, catalog()).can_overwrite,
|
||||
"新建实例流程下不该出现「以包为准」(那按钮必然失败)"
|
||||
);
|
||||
|
||||
let existing = PackPageState {
|
||||
instance: Some("survival".to_string()),
|
||||
..PackPageState::default()
|
||||
};
|
||||
assert!(
|
||||
super::report::report_of(&report, &existing, catalog()).can_overwrite,
|
||||
"导入到已有实例时才给翻档动作"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cancelled_state_is_visible_and_says_the_import_keeps_running() {
|
||||
// 复审 r1 P1-2:面板收回之后,界面上必须留一句「后台还在跑」
|
||||
let import = unlucky_presenter::dto::ImportState {
|
||||
cancelled: true,
|
||||
..unlucky_presenter::dto::ImportState::default()
|
||||
};
|
||||
let export = unlucky_presenter::dto::ExportState::default();
|
||||
let model = Pack::of(&import, &export, &PackPageState::default(), &[], catalog());
|
||||
assert!(model.cancelled);
|
||||
assert!(
|
||||
model.cancelled_note.to_lowercase().contains("background"),
|
||||
"取消提示必须说清后台还在跑:{}",
|
||||
model.cancelled_note
|
||||
);
|
||||
assert!(!model.is_idle(), "取消态不能被当成「什么都没有」");
|
||||
}
|
||||
Reference in new issue
Block a user