Skip to content

Commit a33737e

Browse files
committed
fix(review): 全量回归 7 例测试纠偏与 title 分隔符缺陷(前站即修)
1 parent 46f6868 commit a33737e

3 files changed

Lines changed: 28 additions & 12 deletions

File tree

‎app/src-tauri/src/frame_liveness.rs‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,9 +131,12 @@ mod tests {
131131
fn fresh_samples_clear_stall_and_recover_edges() {
132132
let now = t0();
133133
let mut lv = FrameLiveness::new();
134-
// 连续无帧 → 5s 进入停更边沿;恢复新帧 → recover 边沿 + 停更清零
134+
// 连续无帧(采样频率保持新鲜窗口内)→ 5s 进入停更边沿;恢复新帧 →
135+
// recover 边沿 + 停更清零。判停时刻必须在 SAMPLE_FRESH_MS 内有采样
135136
lv.observe(now, false);
136137
lv.observe(now + Duration::from_millis(500), false);
138+
lv.observe(now + Duration::from_secs(3), false);
139+
lv.observe(now + Duration::from_secs(5), false); // 维持新鲜度(距判停 ≤1.5s)
137140
let stalled_at = now + Duration::from_secs(6);
138141
assert!(lv.stall_edge(stalled_at), "≥5s 无帧应触发停更边沿");
139142
assert_eq!(lv.stall_secs(stalled_at), Some(6));
@@ -165,10 +168,15 @@ mod tests {
165168
let mut lv = FrameLiveness::new();
166169
lv.observe(now, false);
167170
lv.observe(now + Duration::from_secs(1), false);
171+
lv.observe(now + Duration::from_secs(5), false); // 维持新鲜度
168172
let due = now + Duration::from_secs(6);
169173
assert!(lv.recreate_due(due));
170174
lv.mark_recreate(due);
175+
// 节流窗内:保持采样新鲜,重建仍被 30s 最小间隔挡住
176+
lv.observe(due + Duration::from_secs(9), false);
171177
assert!(!lv.recreate_due(due + Duration::from_secs(10)), "30s 节流内不重复重建");
178+
// 节流窗外:到期且新鲜 → 允许重建
179+
lv.observe(due + Duration::from_secs(30), false);
172180
assert!(lv.recreate_due(due + RECREATE_MIN_INTERVAL + Duration::from_secs(1)));
173181
}
174182

‎app/src-tauri/src/ndjson_feed.rs‎

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,19 @@ mod tests {
4242
fn chunk_split_across_lines_accumulates() {
4343
let mut pending = String::new();
4444
let mut sink = Vec::new();
45-
// 第 1 个对象被切成两半 + 第 2 个对象完整
46-
feed_ndjson(&mut pending, r#"{"heading":"节A","blocks":[]}"#, &mut sink);
45+
// 第 1 个对象被切成两半(半个 JSON 分两次到达),第 2 个对象随后完整到达
46+
feed_ndjson(&mut pending, r#"{"heading":"节A","b"#, &mut sink);
47+
assert_eq!(sink.len(), 0, "对象残缺未换行前不解析");
48+
feed_ndjson(&mut pending, r#"locks":[]}"#, &mut sink);
4749
feed_ndjson(&mut pending, "\n", &mut sink);
50+
assert_eq!(sink.len(), 1, "首行补全 + 换行后才解析");
4851
feed_ndjson(&mut pending, r#"{"heading":"节B","blocks":[]}"#, &mut sink);
49-
assert_eq!(sink.len(), 0, "首行未换行前不解析");
52+
assert_eq!(sink.len(), 1, "第二节未换行前不解析");
5053
feed_ndjson(&mut pending, "\n", &mut sink);
5154
assert_eq!(sink.len(), 2);
5255
assert_eq!(sink[0], sec("节A"));
5356
assert_eq!(sink[1], sec("节B"));
57+
assert!(pending.is_empty());
5458
}
5559

5660
#[test]
@@ -59,11 +63,12 @@ mod tests {
5963
let mut sink = Vec::new();
6064
feed_ndjson(&mut pending, "{\"heading\":\"A\",\"blocks\":[]}\r\n{\"heading\":\"B\",\"blocks\":[]}\n", &mut sink);
6165
assert_eq!(sink.len(), 2, "CRLF 尾部 \\r 应被 trim 忽略");
62-
// 末行无换行 + 尾随解释文本 → flush 只取可解析对象
63-
feed_ndjson(&mut pending, "{\"heading\":\"C\",\"blocks\":[]}", &mut sink);
66+
// 末行 C 带换行到达(正常解析);随后无换行的尾随解释文本 → flush 忽略垃圾
67+
feed_ndjson(&mut pending, "{\"heading\":\"C\",\"blocks\":[]}\n", &mut sink);
68+
assert_eq!(sink.len(), 3);
6469
feed_ndjson(&mut pending, "(以上为整理结果)", &mut sink);
6570
flush_ndjson(&mut pending, &mut sink);
66-
assert_eq!(sink.len(), 3);
71+
assert_eq!(sink.len(), 3, "垃圾尾串不产出伪节");
6772
assert_eq!(sink[2], sec("C"));
6873
assert!(pending.is_empty());
6974
}

‎app/src-tauri/src/title_rules.rs‎

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
1313
/// 平台尾缀品牌(v0.19.2 评分表同源白名单;只收"标题固定无内容"的平台)。
1414
const BRAND_SUFFIXES: &[&str] = &["抖音", "douyin", "Douyin", "抖音短视频", "快手", "kuaishou"];
15-
/// 品牌前分隔符(含空格变体)。
16-
const BRAND_SEPARATORS: &[&str] = &[" - ", "-", "|", "|", "_"];
15+
/// 品牌前分隔符(含空格变体——实测窗口标题 "视频 | 快手" 半角竖线带空格)。
16+
const BRAND_SEPARATORS: &[&str] = &[" - ", " | ", "-", "|", "|", "_"];
1717

1818
/// 剥离平台尾缀:仅当标题以「分隔符+品牌」结尾时剥除(`视频名 - 抖音` →
1919
/// `视频名`;标题恰为「抖音」时原样返回——去重层负责加序号)。
@@ -130,8 +130,10 @@ mod tests {
130130

131131
#[test]
132132
fn dedupe_ignores_unrelated_prefixes() {
133-
// 「抖音官方账号」不应被当作已用序号
134-
assert_eq!(dedupe_title(&s(&["抖音官方账号", "抖音极速版"]), "抖音"), "抖音 #2");
133+
// 「抖音官方账号/抖音极速版」≠ base「抖音」——无关前缀不占号,
134+
// base 空闲 → 原样返回(契约:仅"恰好等于 base"才触发编号)
135+
assert_eq!(dedupe_title(&s(&["抖音官方账号", "抖音极速版"]), "抖音"), "抖音");
136+
assert_eq!(dedupe_title(&s(&["抖音", "抖音官方账号"]), "抖音"), "抖音 #2");
135137
}
136138

137139
#[test]
@@ -145,7 +147,8 @@ mod tests {
145147

146148
#[test]
147149
fn first_line_truncates_long_text_at_40() {
148-
let long = "这是一段非常非常非常非常非常非常非常非常非常长的开场白句子用来测试标题截断逻辑";
150+
// 输入必须 >40 字才会触发截断(旧测试 39 字 < 上限——断言必败的测试缺陷)
151+
let long = "这是一段非常非常非常非常非常非常非常非常非常非常非常长的开场白句子用来测试标题截断逻辑";
149152
let out = first_line_title(std::iter::once(long)).unwrap();
150153
assert!(out.chars().count() == TITLE_MAX_CHARS + 1); // 40 + '…'
151154
assert!(out.ends_with('…'));

0 commit comments

Comments
 (0)