127 lines
4.7 KiB
C#
127 lines
4.7 KiB
C#
using Elternbeirat.Web.Shared;
|
|
|
|
namespace Elternbeirat.Web.Tests;
|
|
|
|
/// <summary>
|
|
/// Unit tests for the Markdown pipeline behind every editor-written body. Pure logic,
|
|
/// no PocketBase needed.
|
|
/// </summary>
|
|
public class MarkdownTests
|
|
{
|
|
[Theory]
|
|
[InlineData(null)]
|
|
[InlineData("")]
|
|
public void Empty_input_renders_nothing(string? markdown) =>
|
|
Markdown.ToHtml(markdown).ShouldBe("");
|
|
|
|
[Fact]
|
|
public void Custom_container_renders_a_div_with_its_name_as_class()
|
|
{
|
|
var html = Markdown.ToHtml("::: kennzahlen\n- **seit 1975** Gründung\n:::");
|
|
|
|
html.ShouldContain("<div class=\"kennzahlen\">");
|
|
html.ShouldContain("<strong>seit 1975</strong>");
|
|
}
|
|
|
|
[Fact]
|
|
public void Unknown_container_renders_its_content_as_normal_text()
|
|
{
|
|
// A typo in the block name must cost the styling, never the text.
|
|
var html = Markdown.ToHtml("::: gibtsnicht\nNormaler Text.\n:::");
|
|
|
|
html.ShouldContain("<p>Normaler Text.</p>");
|
|
html.ShouldNotContain(":::");
|
|
}
|
|
|
|
[Fact]
|
|
public void Raw_html_is_escaped_not_passed_through()
|
|
{
|
|
// The body is rendered as a MarkupString; a passed-through <script> would run
|
|
// in the visitor's browser.
|
|
var html = Markdown.ToHtml("Hallo <script>alert(1)</script>\n\n<div onclick=\"x()\">Box</div>");
|
|
|
|
html.ShouldNotContain("<script>");
|
|
html.ShouldNotContain("<div onclick");
|
|
html.ShouldContain("<script>");
|
|
}
|
|
|
|
[Fact]
|
|
public void Pipe_table_renders_a_table()
|
|
{
|
|
var html = Markdown.ToHtml("| Fach | Raum |\n|---|---|\n| Mathe | A1 |");
|
|
|
|
html.ShouldContain("<table>");
|
|
html.ShouldContain("<td>Mathe</td>");
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("[kontakt@example.org](mailto:kontakt@example.org)")]
|
|
[InlineData("<mailto:kontakt@example.org>")] // autolink
|
|
[InlineData("[Satzung](/dokumente/satzung.pdf) ")] // trailing whitespace ignored
|
|
public void Link_alone_in_its_paragraph_is_marked(string markdown) =>
|
|
Markdown.ToHtml(markdown).ShouldStartWith($"<p class=\"{Markdown.LoneLinkClass}\">");
|
|
|
|
[Theory]
|
|
[InlineData("Schreiben Sie an [uns](mailto:kontakt@example.org).")]
|
|
[InlineData("[eins](/a) und [zwei](/b)")]
|
|
[InlineData("")] // an image is not a link
|
|
[InlineData("Nur Text.")]
|
|
public void Paragraph_that_is_not_a_single_link_is_not_marked(string markdown) =>
|
|
Markdown.ToHtml(markdown).ShouldNotContain(Markdown.LoneLinkClass);
|
|
|
|
[Fact]
|
|
public void Lone_link_in_a_tight_list_marks_the_list_item()
|
|
{
|
|
// A tight list renders no <p>, so the class has to sit on the <li>.
|
|
var html = Markdown.ToHtml("- [Antrag](/dokumente/antrag.pdf)\n- Text mit [Link](/x)");
|
|
|
|
html.ShouldContain($"<li class=\"{Markdown.LoneLinkClass}\"><a href=\"/dokumente/antrag.pdf\">");
|
|
html.ShouldContain("<li>Text mit");
|
|
}
|
|
|
|
[Fact]
|
|
public void First_sentence_skips_the_heading_and_drops_the_markdown()
|
|
{
|
|
var sentence = Markdown.FirstSentence(
|
|
"# Vorstand\n\nWir sind **sieben** [Eltern](/board). Der Rest folgt.");
|
|
|
|
sentence.ShouldBe("Wir sind sieben Eltern.");
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("Wir helfen z. B. bei Anträgen. Mehr dazu unten.", "Wir helfen z. B. bei Anträgen.")]
|
|
[InlineData("Gewählt am 13. November 2025. Danach mehr.", "Gewählt am 13. November 2025.")]
|
|
[InlineData("Haben Sie Fragen? Schreiben Sie uns.", "Haben Sie Fragen?")]
|
|
[InlineData("Ohne Satzzeichen am Ende", "Ohne Satzzeichen am Ende")]
|
|
public void First_sentence_ends_at_a_real_sentence_end(string markdown, string expected) =>
|
|
Markdown.FirstSentence(markdown).ShouldBe(expected);
|
|
|
|
[Fact]
|
|
public void First_sentence_joins_soft_line_breaks_with_a_space()
|
|
{
|
|
// Editors wrap long lines; the wrap must not glue two words together.
|
|
Markdown.FirstSentence("Erste Zeile\nzweite Zeile.").ShouldBe("Erste Zeile zweite Zeile.");
|
|
}
|
|
|
|
[Fact]
|
|
public void First_sentence_longer_than_the_limit_is_cut_between_words()
|
|
{
|
|
var sentence = Markdown.FirstSentence("Eins zwei drei vier fünf.", maxLength: 12);
|
|
|
|
sentence.ShouldBe("Eins zwei…");
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(null)]
|
|
[InlineData("")]
|
|
[InlineData("# Nur eine Überschrift")]
|
|
[InlineData("- nur\n- eine Liste")]
|
|
[InlineData("::: hinweis\nText im Baustein.\n:::")]
|
|
public void First_sentence_is_empty_without_a_top_level_paragraph(string? markdown) =>
|
|
Markdown.FirstSentence(markdown).ShouldBe("");
|
|
|
|
[Fact]
|
|
public void First_sentence_rejects_a_limit_below_one() =>
|
|
Should.Throw<ArgumentOutOfRangeException>(() => Markdown.FirstSentence("Text.", maxLength: 0));
|
|
}
|