1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
//! A tapped follow-up is recorded on its own block, not spoken by the bot (#844).
//!
//! Tapping a suggestion posted a new message containing the chosen text. The
//! Bot API has no send-as-user, so that bubble carries the bot's name, avatar
//! and badge, and a continuation the user chose reads as the bot saying it.
//!
//! #787 tried to fix this by prefixing the echo with a `>` quote. Quote
//! formatting sits inside a bubble that is still labelled as the bot, so the
//! attribution did not change. The block is now edited in place instead, which
//! reads as a selected control and drops the keyboard at the moment the rest of
//! the options stop working.
//!
//! Fixtures are synthetic and carry no user identifiers.
use crate::channels::telegram::suggest_followups::{echo_fallback, picked_block};
const CHOICE: &str = "Update the SKILL.md with the new callback routing";
#[test]
fn the_edited_block_is_not_quoted() {
// A leading `>` is what #787 added. It renders as a quote inside a bot
// bubble, which is the appearance being fixed.
let block = picked_block(CHOICE, None);
assert!(
!block.starts_with('>'),
"the in-place record must not be quoted: {block}"
);
}
#[test]
fn the_edited_block_marks_the_choice_and_keeps_the_text() {
let block = picked_block(CHOICE, None);
assert!(block.starts_with('\u{25b6}'), "must lead with the marker");
assert!(block.contains(CHOICE), "the chosen text must survive");
}
#[test]
fn the_fallback_stays_quoted() {
// Only reached when the block cannot be edited. Quoting is the weaker
// presentation, which is why it is the fallback and not the default.
let echo = echo_fallback(CHOICE, None);
assert!(
echo.starts_with("> "),
"fallback must remain a quote: {echo}"
);
assert!(echo.contains(CHOICE));
}
#[test]
fn the_two_shapes_differ() {
// If these ever converge, the fix has been undone: the whole point is
// that the in-place record does not look like the old echo.
assert_ne!(picked_block(CHOICE, None), echo_fallback(CHOICE, None));
}
#[test]
fn text_is_passed_through_untouched() {
// The suggestion may contain markdown, backticks or angle brackets. This
// layer must not mangle them; escaping is md_to_html's job at the call
// site, and doing it twice would double-escape.
for text in [
"Run `cargo clippy` and report",
"Compare <old> against <new>",
"Fix the **bold** claim",
"",
] {
assert!(
picked_block(text, None).ends_with(text),
"text was altered: {text}"
);
}
}
// ── Attribution (#893) ──────────────────────────────────────────────────────
#[test]
fn the_chooser_is_named_when_known() {
// In a group, an unattributed line says nothing about who acted.
let block = picked_block(CHOICE, Some("Daniel"));
assert!(block.contains("Daniel"), "chooser missing: {block}");
assert!(block.contains(CHOICE), "the choice must survive: {block}");
}
#[test]
fn an_unknown_chooser_falls_back_to_the_plain_record() {
// Identity is not always available; the pick must still be recorded.
for chooser in [None, Some(""), Some(" ")] {
let block = picked_block(CHOICE, chooser);
assert!(block.contains(CHOICE), "the choice was lost: {block}");
assert!(
!block.contains("—"),
"an empty name left a dangling separator"
);
}
}
#[test]
fn the_fallback_echo_names_the_chooser_too() {
let echo = echo_fallback(CHOICE, Some("Daniel"));
assert!(echo.starts_with("> "), "fallback must stay quoted: {echo}");
assert!(echo.contains("Daniel"));
assert!(echo.contains(CHOICE));
}