huskies: merge 579_bug_matrix_bot_messages_render_markdown_headings_without_line_breaks_or_formatting
This commit is contained in:
@@ -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]
|
||||
fn startup_announcement_uses_bot_name() {
|
||||
assert_eq!(format_startup_announcement("Timmy"), "Timmy is online.");
|
||||
|
||||
+50
-6
@@ -223,12 +223,24 @@ pub fn normalize_line_breaks(text: &str) -> String {
|
||||
|
||||
let prev_line = lines[i - 1];
|
||||
|
||||
// Insert a blank separator when both the current and previous lines
|
||||
// are non-empty prose (not inside a code fence, not structured Markdown).
|
||||
// ATX headings (lines starting with one or more `#` characters) always
|
||||
// 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()
|
||||
&& !prev_line.is_empty()
|
||||
&& !is_structured_line(line)
|
||||
&& !is_structured_line(prev_line);
|
||||
&& ((!is_structured_line(line) && !is_structured_line(prev_line))
|
||||
|| is_cur_heading
|
||||
|| is_prev_heading);
|
||||
|
||||
if should_double {
|
||||
result.push("");
|
||||
@@ -599,10 +611,42 @@ mod tests {
|
||||
}
|
||||
|
||||
#[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 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]
|
||||
|
||||
Reference in New Issue
Block a user