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]
|
#[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
@@ -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]
|
||||||
|
|||||||
Reference in New Issue
Block a user