test(instances): M302 r2 —— 四条 P2:线程判据真能红 + 两处相位守卫 + 「只报一次」改成可数
- P2-1:`spawn_scan` 的闭包提成 `scan_body`,单测可装探针 + 闸门(只拦同一数据根的扫描)。
新用例 `a_refresh_returns_while_the_scan_body_is_still_parked` 断言四件事:扫描体跑在
**另一条线程**、返回时**一趟都没跑完**、闸门**还没放行**、返回耗时 < 闸门上限的一半。
把 `spawn("instances", job)` 换成直接 `job()` ⇒ 该用例红(原文见报告);旧用例
`a_refresh_returns_before_the_scan_is_done` 的 docstring 改成诚实口径(它证明不了不在调用方线程,
inline 时它照样绿——正是复审 r2 点的那条)。
- P2-2(真缺陷):`check_scan_stall` 补上与 `refresh_instances` 同一个守卫——已就绪
(`adopted().is_some()`)之后不再报 `Stalled`,否则一轮慢刷新会把有数据的一屏换成
「扫描实例比平时久 + 重新扫描」。新用例 `a_slow_refresh_after_ready_never_reports_stalled`。
- P2-3:`a_stalled_scan_reports_once_and_a_late_snapshot_still_lands` 原来把 `drain_events` 调了
两次(第二次必空 ⇒ 断言空转)。改成数实际发出的条数:第一拍恰好 1 条,之后三拍必须 0 条。
- P2-4:新用例 `a_refresh_after_ready_emits_no_scanning_phase`(真 host:已就绪后再刷新一条相位
事件都不许发)+ app 级 `a_refresh_after_ready_keeps_the_list_and_the_tree`(相位仍 `Ready`、
真控件树的文字/按钮节点数与刷新前逐项相等、列表没被清空)。
四条变异(inline / 去 P2-2 守卫 / 去「只报一次」锁 / 去 Scanning 守卫)都实测红,原文进报告。
This commit is contained in:
1 parent
f03411d58b
commit
23c7beee8d
4 files changed
+372
-35
No files matched your search
@@ -180,3 +180,84 @@ fn retry_scan_dispatches_a_scan_only_refresh() {
|
||||
"重试不该重拉版本清单(那是一条网络请求)"
|
||||
);
|
||||
}
|
||||
|
||||
/// **已就绪之后再刷新:相位仍是 `Ready`,真控件树里不出现扫描占位**(复审 r2 P2-4)。
|
||||
///
|
||||
/// 现场:列表已经有数据,用户手动刷新 / 别处触发刷新。若那一轮把相位打回 `Scanning`,界面就把
|
||||
/// 有数据的一屏换成「正在扫描实例…」——把卡顿换成闪屏(任务 ⑤ 禁止)。
|
||||
///
|
||||
/// 抓得住:① 相位被打回 `Scanning`/`Stalled`;② 控件树变了(占位替换掉列表:文字/按钮节点数
|
||||
/// 与刷新前不再相等)。前半条的直接来源是 host 的守卫(presenter 的
|
||||
/// `a_refresh_after_ready_emits_no_scanning_phase` 用真 host 钉它),这一条验的是「守卫成立时
|
||||
/// 界面确实不动」。
|
||||
/// 抓不住:真 host 的那一轮扫描(这一条用只记账的后端:命令出去了、事件一个都没回)。
|
||||
#[test]
|
||||
fn a_refresh_after_ready_keeps_the_list_and_the_tree() {
|
||||
use iced::advanced::widget::tree::Tag;
|
||||
use iced::advanced::widget::Tree;
|
||||
use iced::widget::text;
|
||||
use iced::Element;
|
||||
|
||||
fn tree_of(el: &Element<'_, crate::pages::Message>) -> Tree {
|
||||
Tree::new(el.as_widget())
|
||||
}
|
||||
fn count(tree: &Tree, tag: Tag) -> usize {
|
||||
usize::from(tree.tag == tag)
|
||||
+ tree
|
||||
.children
|
||||
.iter()
|
||||
.map(|child| count(child, tag))
|
||||
.sum::<usize>()
|
||||
}
|
||||
/// 一屏的 `(文字节点数, 按钮数)`。
|
||||
fn counts(app: &App) -> (usize, usize) {
|
||||
let el = crate::pages::app(&app.ui, &app.view, &app.launch_form);
|
||||
let tree = tree_of(&el);
|
||||
let text_tag = tree_of(&{
|
||||
let probe: Element<'static, crate::pages::Message> = text("probe").into();
|
||||
probe
|
||||
})
|
||||
.tag;
|
||||
let button_tag = tree_of(&crate::app::components::buttons::filled(
|
||||
&app.ui,
|
||||
crate::app::components::buttons::Button::new(
|
||||
"probe",
|
||||
crate::pages::Message::Action(crate::pages::Action::Back),
|
||||
),
|
||||
))
|
||||
.tag;
|
||||
(count(&tree, text_tag), count(&tree, button_tag))
|
||||
}
|
||||
|
||||
let (mut app, recorded) = app();
|
||||
// 首份快照落地 ⇒ Ready + 有行
|
||||
app.state.apply(AppEvent::Core(CoreEvent::InstancesChanged(
|
||||
unlucky_presenter::mock_data::mock_instances(),
|
||||
)));
|
||||
app.refresh_view();
|
||||
assert_eq!(app.view.instances.scan, ScanPhase::Ready);
|
||||
assert!(!app.view.instances.rows.is_empty(), "前提:列表里有数据");
|
||||
let before = counts(&app);
|
||||
|
||||
// 再触发一次刷新:命令出去了,后端这一拍**一个事件都不回**(= 扫描在飞)
|
||||
let _ = app.on_instance_message(InstanceMessage::RetryScan);
|
||||
app.pump();
|
||||
app.refresh_view();
|
||||
|
||||
assert!(
|
||||
recorded.borrow().contains(&AppCommand::RefreshInstances),
|
||||
"前提:刷新命令真的发出去了({:?})",
|
||||
recorded.borrow()
|
||||
);
|
||||
assert_eq!(
|
||||
app.view.instances.scan,
|
||||
ScanPhase::Ready,
|
||||
"已就绪之后再刷新,相位不许被打回占位态"
|
||||
);
|
||||
assert_eq!(
|
||||
counts(&app),
|
||||
before,
|
||||
"控件树必须与刷新前同构(占位/空态会多出文字与按钮节点)"
|
||||
);
|
||||
assert!(!app.view.instances.rows.is_empty(), "列表也不许被清空");
|
||||
}
|
||||
Reference in new issue
Block a user