huskies: merge 579_bug_matrix_bot_messages_render_markdown_headings_without_line_breaks_or_formatting

This commit is contained in:
dave
2026-04-16 08:18:22 +00:00
parent df2f20a5e5
commit 9f0274417d
2 changed files with 93 additions and 6 deletions
@@ -96,6 +96,49 @@ mod tests {
); );
} }
#[test]
fn markdown_to_html_heading_renders_as_h_tag() {
let html = markdown_to_html("## Section\nContent here.");
assert!(
html.contains("<h2>Section</h2>"),
"expected <h2> heading tag: {html}"
);
assert!(
html.contains("<p>Content here.</p>"),
"expected paragraph after heading: {html}"
);
}
#[test]
fn markdown_to_html_heading_with_preceding_prose_renders_correctly() {
let html = markdown_to_html("Intro text.\n## Section\nBody.");
assert!(
html.contains("<h2>Section</h2>"),
"expected <h2> heading tag: {html}"
);
assert!(
html.contains("<p>Intro text.</p>"),
"expected intro paragraph: {html}"
);
assert!(
html.contains("<p>Body.</p>"),
"expected body paragraph: {html}"
);
}
#[test]
fn markdown_to_html_multiple_headings_each_render_as_h_tags() {
let html = markdown_to_html("## Section 1\nContent one.\n\n## Section 2\nContent two.");
assert!(
html.contains("<h2>Section 1</h2>"),
"expected first <h2>: {html}"
);
assert!(
html.contains("<h2>Section 2</h2>"),
"expected second <h2>: {html}"
);
}
#[test] #[test]
fn startup_announcement_uses_bot_name() { fn startup_announcement_uses_bot_name() {
assert_eq!(format_startup_announcement("Timmy"), "Timmy is online."); assert_eq!(format_startup_announcement("Timmy"), "Timmy is online.");
+50 -6
View File
@@ -223,12 +223,24 @@ pub fn normalize_line_breaks(text: &str) -> String {
let prev_line = lines[i - 1]; let prev_line = lines[i - 1];
// Insert a blank separator when both the current and previous lines // ATX headings (lines starting with one or more `#` characters) always
// are non-empty prose (not inside a code fence, not structured Markdown). // need a blank line before and after them so that Matrix clients render
// the heading with visual separation. Without a blank line, a single
// newline between a heading and adjacent text is swallowed by many
// Matrix clients (including Element X), joining the heading text and
// the following content on the same line without any heading formatting.
let is_cur_heading = line.trim_start().starts_with('#');
let is_prev_heading = prev_line.trim_start().starts_with('#');
// Insert a blank separator when:
// 1. Both lines are non-empty prose (standard prose-to-prose rule).
// 2. The current line is an ATX heading (adds blank line *before* it).
// 3. The previous line was an ATX heading (adds blank line *after* it).
let should_double = !line.is_empty() let should_double = !line.is_empty()
&& !prev_line.is_empty() && !prev_line.is_empty()
&& !is_structured_line(line) && ((!is_structured_line(line) && !is_structured_line(prev_line))
&& !is_structured_line(prev_line); || is_cur_heading
|| is_prev_heading);
if should_double { if should_double {
result.push(""); result.push("");
@@ -599,10 +611,42 @@ mod tests {
} }
#[test] #[test]
fn normalize_heading_single_newline_preserved() { fn normalize_heading_followed_by_prose_gets_blank_line() {
// A blank line must be inserted after a heading so Matrix clients render
// the heading with visual separation from the following paragraph.
let input = "# My Heading\nSome text below."; let input = "# My Heading\nSome text below.";
let output = normalize_line_breaks(input); let output = normalize_line_breaks(input);
assert_eq!(output, "# My Heading\nSome text below."); assert_eq!(output, "# My Heading\n\nSome text below.");
}
#[test]
fn normalize_prose_before_heading_gets_blank_line() {
// A blank line must be inserted before a heading when prose precedes it.
let input = "Some intro text.\n## Section";
let output = normalize_line_breaks(input);
assert_eq!(output, "Some intro text.\n\n## Section");
}
#[test]
fn normalize_heading_surrounded_by_prose_gets_blank_lines_both_sides() {
let input = "Intro.\n## Heading\nContent.";
let output = normalize_line_breaks(input);
assert_eq!(output, "Intro.\n\n## Heading\n\nContent.");
}
#[test]
fn normalize_consecutive_headings_separated_by_blank_lines() {
let input = "## Section 1\n## Section 2";
let output = normalize_line_breaks(input);
assert_eq!(output, "## Section 1\n\n## Section 2");
}
#[test]
fn normalize_heading_already_separated_by_blank_line_unchanged() {
// When there is already a blank line, no extra blank is inserted.
let input = "# Heading\n\nContent.";
let output = normalize_line_breaks(input);
assert_eq!(output, "# Heading\n\nContent.");
} }
#[test] #[test]