use crate::utils::extract_react_marker;
#[test]
fn react_bare_directive() {
let (text, emoji) = extract_react_marker("<<react:👍>>");
assert_eq!(text, "");
assert_eq!(emoji.as_deref(), Some("👍"));
}
#[test]
fn react_directive_with_text() {
let (text, emoji) = extract_react_marker("Sure thing! <<react:✅>>");
assert_eq!(text, "Sure thing!");
assert_eq!(emoji.as_deref(), Some("✅"));
}
#[test]
fn react_multiple_directives_uses_first() {
let (text, emoji) = extract_react_marker("<<react:👍>> and <<react:❤️>>");
assert_eq!(text, "and");
assert_eq!(emoji.as_deref(), Some("👍"));
}
#[test]
fn react_whitespace_trimmed() {
let (text, emoji) = extract_react_marker(" <<react:🔥>> ");
assert_eq!(text, "");
assert_eq!(emoji.as_deref(), Some("🔥"));
}
#[test]
fn react_with_surrounding_newlines() {
let (text, emoji) = extract_react_marker("\n\n<<react:🔥>>\n\n");
assert_eq!(text, "");
assert_eq!(emoji.as_deref(), Some("🔥"));
}
#[test]
fn react_embedded_in_middle() {
let (text, emoji) = extract_react_marker("Hello <<react:👋>> world");
assert_eq!(text, "Hello world");
assert_eq!(emoji.as_deref(), Some("👋"));
}
#[test]
fn react_only_react_with_no_extra_text() {
let (text, emoji) = extract_react_marker("<<react:✅>>");
assert!(text.trim().is_empty());
assert_eq!(emoji.as_deref(), Some("✅"));
}
#[test]
fn react_compound_emoji_accepted() {
let (text, emoji) = extract_react_marker("<<react:👍🏽>>");
assert_eq!(text, "");
assert_eq!(emoji.as_deref(), Some("👍🏽"));
}
#[test]
fn react_escaped_single_backslash_prefix() {
let (text, emoji) = extract_react_marker(r"<\react:👍>>");
assert_eq!(text, "");
assert_eq!(emoji.as_deref(), Some("👍"));
}
#[test]
fn react_escaped_double_backslash_prefix() {
let (text, emoji) = extract_react_marker(r"<\\react:👍>>");
assert_eq!(text, "");
assert_eq!(emoji.as_deref(), Some("👍"));
}
#[test]
fn react_single_angle_prefix() {
let (text, emoji) = extract_react_marker("<react:👍>>");
assert_eq!(text, "");
assert_eq!(emoji.as_deref(), Some("👍"));
}
#[test]
fn react_escaped_prefix_with_text() {
let (text, emoji) = extract_react_marker(r"All set <\react:✅>>");
assert_eq!(text, "All set");
assert_eq!(emoji.as_deref(), Some("✅"));
}
#[test]
fn react_mangled_prefix_word_payload_stays() {
let (text, emoji) = extract_react_marker(r"<\react:emoji>>");
assert_eq!(text, r"<\react:emoji>>");
assert!(emoji.is_none());
}
#[test]
fn react_mangled_prefix_in_code_span_untouched() {
let (text, emoji) = extract_react_marker(r"use `<\react:👍>>` to react");
assert_eq!(text, r"use `<\react:👍>>` to react");
assert!(emoji.is_none());
}
#[test]
fn react_no_directive() {
let (text, emoji) = extract_react_marker("Just a normal message.");
assert_eq!(text, "Just a normal message.");
assert!(emoji.is_none());
}
#[test]
fn react_malformed_no_closing() {
let (text, emoji) = extract_react_marker("<<react:👍");
assert_eq!(text, "<<react:👍");
assert!(emoji.is_none());
}
#[test]
fn react_xml_close_tag_terminator() {
let (text, emoji) = extract_react_marker("<<react:👍</react>");
assert_eq!(text, "");
assert_eq!(emoji.as_deref(), Some("👍"));
}
#[test]
fn react_xml_close_tag_with_text() {
let (text, emoji) = extract_react_marker("All set <<react:✅</react>");
assert_eq!(text, "All set");
assert_eq!(emoji.as_deref(), Some("✅"));
}
#[test]
fn react_bare_single_bracket_terminator() {
let (text, emoji) = extract_react_marker("<<react:🔥>");
assert_eq!(text, "");
assert_eq!(emoji.as_deref(), Some("🔥"));
}
#[test]
fn react_double_bracket_preferred_over_single() {
let (text, emoji) = extract_react_marker("<<react:👍>> done");
assert_eq!(text, "done");
assert_eq!(emoji.as_deref(), Some("👍"));
}
#[test]
fn react_xml_close_word_payload_stays() {
let (text, emoji) = extract_react_marker("<<react:emoji</react>");
assert_eq!(text, "<<react:emoji</react>");
assert!(emoji.is_none());
}
#[test]
fn react_bare_terminator_in_code_span_untouched() {
let (text, emoji) = extract_react_marker("use `<<react:👍>` to react");
assert_eq!(text, "use `<<react:👍>` to react");
assert!(emoji.is_none());
}
#[test]
fn react_keywordless_double_bracket_fires() {
let (text, emoji) = extract_react_marker("<<✅>>");
assert_eq!(text, "");
assert_eq!(emoji.as_deref(), Some("✅"));
}
#[test]
fn react_keywordless_with_text() {
let (text, emoji) = extract_react_marker("Done <<🔥>>");
assert_eq!(text, "Done");
assert_eq!(emoji.as_deref(), Some("🔥"));
}
#[test]
fn react_keywordless_bare_terminator_fires() {
let (text, emoji) = extract_react_marker("<<👍>");
assert_eq!(text, "");
assert_eq!(emoji.as_deref(), Some("👍"));
}
#[test]
fn react_keywordless_single_bracket_stays_text() {
let (text, emoji) = extract_react_marker("<✅>");
assert_eq!(text, "<✅>");
assert!(emoji.is_none());
}
#[test]
fn react_keywordless_word_payload_stays_text() {
let (text, emoji) = extract_react_marker("<<word>>");
assert_eq!(text, "<<word>>");
assert!(emoji.is_none());
}
#[test]
fn react_keywordless_in_code_span_untouched() {
let (text, emoji) = extract_react_marker("use `<<✅>>` to react");
assert_eq!(text, "use `<<✅>>` to react");
assert!(emoji.is_none());
}
#[test]
fn react_keywordless_empty_payload_stays_text() {
let (text, emoji) = extract_react_marker("<<>>");
assert_eq!(text, "<<>>");
assert!(emoji.is_none());
}
#[test]
fn react_word_payload_stays_in_prose() {
let (text, emoji) = extract_react_marker("the leak where <<react:emoji>> showed as raw text");
assert_eq!(text, "the leak where <<react:emoji>> showed as raw text");
assert!(emoji.is_none());
}
#[test]
fn react_hello_payload_not_extracted() {
let (text, emoji) = extract_react_marker("<<react:hello>>");
assert_eq!(text, "<<react:hello>>");
assert!(emoji.is_none());
}
#[test]
fn react_empty_payload_not_extracted() {
let (text, emoji) = extract_react_marker("<<react:>>");
assert_eq!(text, "<<react:>>");
assert!(emoji.is_none());
}
#[test]
fn react_marker_in_code_span_untouched() {
let (text, emoji) = extract_react_marker("use `<<react:👍>>` to react");
assert_eq!(text, "use `<<react:👍>>` to react");
assert!(emoji.is_none());
}
#[test]
fn react_directive_after_closed_code_span_still_extracts() {
let (text, emoji) = extract_react_marker("see `code` here <<react:👍>>");
assert_eq!(text, "see `code` here");
assert_eq!(emoji.as_deref(), Some("👍"));
}
#[test]
fn react_long_payload_rejected() {
let (text, emoji) = extract_react_marker("<<react:🔥🔥🔥🔥🔥🔥🔥🔥🔥>>");
assert_eq!(text, "<<react:🔥🔥🔥🔥🔥🔥🔥🔥🔥>>");
assert!(emoji.is_none());
}
#[test]
fn lenient_extractor_fires_a_code_span_marker() {
use crate::utils::{extract_react_marker, extract_react_marker_lenient};
let leaky = "So I should reply with only `<<react:🙏>>`.";
let (strict_text, strict_emoji) = extract_react_marker(leaky);
assert_eq!(
strict_emoji, None,
"strict leaves a code-span marker as prose"
);
assert!(strict_text.contains("<<react:🙏>>"));
let (lenient_text, lenient_emoji) = extract_react_marker_lenient(leaky);
assert_eq!(lenient_emoji.as_deref(), Some("🙏"));
assert!(
!lenient_text.contains("<<react:"),
"lenient strips the marker: {lenient_text:?}"
);
let (_t, none) = extract_react_marker_lenient("see `<<react:emoji>>` in the docs");
assert_eq!(none, None);
}
#[test]
fn react_recovered_from_orphan_fence_single_bracket() {
let raw = "<```html\n<react:✅>> Done — all receipts verified in the last run:\n\nbody stays put\n```>";
let (text, emoji) = extract_react_marker(raw);
assert_eq!(emoji.as_deref(), Some("✅"));
assert!(!text.contains("```"), "fence lines must go: {text:?}");
assert!(!text.contains("<react:"), "marker must go: {text:?}");
assert!(text.contains("body stays put"));
}
#[test]
fn react_recovered_from_orphan_fence_double_bracket() {
let raw = "<```html\n<<react:💯>>\n\nChecked before arguing, and empirically right.\n```>";
let (text, emoji) = extract_react_marker(raw);
assert_eq!(emoji.as_deref(), Some("💯"));
assert!(!text.contains("```"));
assert!(text.contains("empirically right"));
}
#[test]
fn react_recovered_from_bare_orphan_fence() {
let raw = "<```\n<<react:🔥>>\nplain body";
let (text, emoji) = extract_react_marker(raw);
assert_eq!(emoji.as_deref(), Some("🔥"));
assert!(!text.contains("```"));
assert!(text.contains("plain body"));
}
#[test]
fn react_recovered_when_marker_shares_the_fence_line() {
let raw = "<```html <<react:👀>> visible text";
let (text, emoji) = extract_react_marker(raw);
assert_eq!(emoji.as_deref(), Some("👀"));
assert!(!text.contains("```html"));
assert!(text.contains("visible text"));
}
#[test]
fn react_recovery_strips_paired_trailing_fence_only() {
let clean = "<<react:👍>> normal turn\n```";
let (text, emoji) = extract_react_marker(clean);
assert_eq!(emoji.as_deref(), Some("👍"));
assert!(text.contains("normal turn"), "{text:?}");
}
#[test]
fn react_no_recovery_for_prose_then_fenced_example() {
let docs = "Use fences like this:\n```html\n<<react:✅>>\n```\nto react.";
let (text, emoji) = extract_react_marker(docs);
assert_eq!(emoji, None);
assert!(
text.contains("<<react:✅>>"),
"example stays as text: {text:?}"
);
}
#[test]
fn react_no_recovery_for_word_payload_in_orphan_fence() {
let raw = "<```html\n<react:hello>> not an emoji";
let (text, emoji) = extract_react_marker(raw);
assert_eq!(emoji, None);
assert_eq!(text, raw);
}
use crate::utils::strip_invalid_react_markers as strip;
#[test]
fn strip_incident_repro_empty_payload_goes_to_silence() {
assert_eq!(strip("<<react:>>"), "");
}
#[test]
fn strip_empty_payload_with_narration_keeps_the_narration() {
assert_eq!(
strip("I will not react to that. <<react:>>"),
"I will not react to that."
);
}
#[test]
fn strip_word_payload_removed() {
assert_eq!(strip("<<react:emoji>>"), "");
assert_eq!(strip("Sure <<react:hello>> done"), "Sure done");
}
#[test]
fn strip_keyword_less_noise_removed() {
assert_eq!(strip("<<hello>>"), "");
}
#[test]
fn strip_backtick_wrapped_debris_leaves_no_stray_span() {
assert_eq!(strip("`<<react:>>`"), "");
assert_eq!(strip("narration `<<react:>>` tail"), "narration tail");
}
#[test]
fn strip_valid_markers_are_left_for_the_extractor() {
assert_eq!(strip("<<react:👍>>"), "<<react:👍>>");
assert_eq!(strip("Sure <<react:✅>>"), "Sure <<react:✅>>");
}
#[test]
fn strip_unterminated_opening_is_left_as_text() {
assert_eq!(strip("<<react:"), "<<react:");
assert_eq!(strip("<<react:👍"), "<<react:👍");
}
#[test]
fn strip_plain_prose_untouched() {
assert_eq!(strip("Just a normal message."), "Just a normal message.");
assert_eq!(strip("a < b and c > d"), "a < b and c > d");
}
#[test]
fn strip_escaped_prefix_forms_removed_too() {
assert_eq!(strip("<react:>>"), "");
assert_eq!(strip("<\\react:>>"), "");
}
#[test]
fn strip_multiple_debris_all_gone() {
assert_eq!(strip("<<react:>><<react:>>"), "");
assert_eq!(strip("<<react:>>x<<react:emoji>>"), "x");
}
#[test]
fn reaction_path_wires_strip_after_extractor_and_threads_the_reply() {
let src = std::fs::read_to_string(
std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("src/channels/telegram/handler.rs"),
)
.expect("handler source");
let extract = src
.find("extract_react_marker_lenient(&text_only)")
.expect("lenient extractor call");
let strip_at = src
.find("strip_invalid_react_markers(&text_only)")
.expect("debris strip call");
assert!(
extract < strip_at,
"the strip must run on the extractor's output, not before it"
);
let deliver = src
.find("topic_id.map(|t| ThreadId(MessageId(t))),")
.expect("reaction text reply must carry the resolved topic (#1670)");
assert!(
strip_at < deliver,
"sentinel anchors to the reaction handler, not some other path"
);
}