177 lines
7.1 KiB
C#
177 lines
7.1 KiB
C#
using Elternbeirat.Web.Shared;
|
|
using Markdig.Syntax;
|
|
|
|
namespace Elternbeirat.Web.Tests;
|
|
|
|
/// <summary>
|
|
/// Unit tests for the person cards of the "::: team" block: initials, avatar colour,
|
|
/// reading the list-item convention and the rendered card. Pure logic, no PocketBase
|
|
/// needed.
|
|
/// </summary>
|
|
public class TeamMemberTests
|
|
{
|
|
[Theory]
|
|
[InlineData("Jenny Reger-Stilgenbauer", "JR")] // a double name is one word
|
|
[InlineData("Özlem Ünal", "ÖÜ")] // umlauts kept, not folded to O/U
|
|
[InlineData("ömer şahin", "ÖŞ")] // lower case is raised
|
|
[InlineData("Isabell", "I")] // a single word gives one letter
|
|
[InlineData("Anna Maria Müller", "AM")] // first and last word only
|
|
[InlineData(" Maik Palm ", "MP")] // extra whitespace ignored
|
|
[InlineData("", "")]
|
|
[InlineData("123", "")] // no letter, no initials
|
|
public void Initials_are_first_and_last_word(string name, string expected) =>
|
|
TeamMember.InitialsOf(name).ShouldBe(expected);
|
|
|
|
[Fact]
|
|
public void Color_is_stable_and_in_range()
|
|
{
|
|
// Pinned values: the colour must not change between restarts or releases, so a
|
|
// change to the hash shows up here instead of recolouring the board.
|
|
TeamMember.ColorOf("Maik Palm").ShouldBe(TeamMember.ColorOf(" Maik Palm "));
|
|
TeamMember.ColorOf("Maik Palm").ShouldBe(2);
|
|
TeamMember.ColorOf("Gentiana Erol").ShouldBe(2);
|
|
TeamMember.ColorOf("Raul Chira").ShouldBe(1);
|
|
|
|
new[] { "Maik Palm", "Gentiana Erol", "Raul Chira", "Marion Leininger", "Isabell Vesper", "" }
|
|
.ShouldAllBe(name => TeamMember.ColorOf(name) >= 1 && TeamMember.ColorOf(name) <= TeamMember.ColorCount);
|
|
}
|
|
|
|
[Fact]
|
|
public void Board_uses_both_colours_about_evenly()
|
|
{
|
|
// Two colours only make sense if both show up; the real board (ten names)
|
|
// must not land mostly on one.
|
|
string[] board =
|
|
[
|
|
"Maik Palm", "Gentiana Erol", "Raul Chira", "Marion Leininger", "Jenny Reger-Stilgenbauer",
|
|
"Mariella Ianello", "Isabell Vesper", "Susanne Naumburg", "Rebecca Goldmann", "Michael Weber",
|
|
];
|
|
|
|
board.Count(name => TeamMember.ColorOf(name) == 1).ShouldBeInRange(4, 6);
|
|
}
|
|
|
|
[Fact]
|
|
public void Item_with_role_duties_and_note_is_read_in_full()
|
|
{
|
|
var (member, note) = Read("- **Maik Palm** (Vorsitzender) \n *Schulkonferenz, Mensarat , Homepage* \n Mitglied im Schulbeirat.");
|
|
|
|
member.Name.ShouldBe("Maik Palm");
|
|
member.Role.ShouldBe("Vorsitzender");
|
|
member.Duties.ShouldBe(["Schulkonferenz", "Mensarat", "Homepage"]);
|
|
Markdown.PlainText(note).ShouldBe("Mitglied im Schulbeirat.");
|
|
}
|
|
|
|
[Fact]
|
|
public void Item_without_role_still_reads_the_duties()
|
|
{
|
|
var (member, note) = Read("- **Jenny Reger-Stilgenbauer** \n *Schulkonferenz, Schaukasten*");
|
|
|
|
member.Role.ShouldBeNull();
|
|
member.Duties.ShouldBe(["Schulkonferenz", "Schaukasten"]);
|
|
note.ShouldBeEmpty();
|
|
}
|
|
|
|
[Fact]
|
|
public void Item_with_only_a_name_has_no_role_and_no_duties()
|
|
{
|
|
var (member, note) = Read("- **Isabell Vesper**");
|
|
|
|
member.Name.ShouldBe("Isabell Vesper");
|
|
member.Role.ShouldBeNull();
|
|
member.Duties.ShouldBeEmpty();
|
|
note.ShouldBeEmpty();
|
|
}
|
|
|
|
[Fact]
|
|
public void Soft_line_breaks_count_like_hard_ones()
|
|
{
|
|
// Editors forget the two trailing spaces; the card must not depend on them.
|
|
var (member, _) = Read("- **Raul Chira** (2. Stellvertreter)\n *Mensarat*");
|
|
|
|
member.Role.ShouldBe("2. Stellvertreter");
|
|
member.Duties.ShouldBe(["Mensarat"]);
|
|
}
|
|
|
|
[Fact]
|
|
public void Second_line_that_is_not_only_italic_becomes_the_note()
|
|
{
|
|
var (member, note) = Read("- **Rebecca Goldmann** \n Neu im Team, *herzlich willkommen*.");
|
|
|
|
member.Duties.ShouldBeEmpty();
|
|
Markdown.PlainText(note).ShouldBe("Neu im Team, herzlich willkommen.");
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("- Michael Weber, Beisitz")] // no bold at all
|
|
[InlineData("- Beisitz: **Michael Weber**")] // bold, but not at the start
|
|
[InlineData("- *Michael Weber*")] // italic, not bold
|
|
public void Item_without_convention_is_not_read(string markdown) =>
|
|
TeamMember.Read(FirstParagraph(markdown)).ShouldBeNull();
|
|
|
|
[Fact]
|
|
public void Team_block_renders_person_cards()
|
|
{
|
|
var html = Markdown.ToHtml(
|
|
"::: team\n- **Maik Palm** (Vorsitzender) \n *Schulkonferenz, Mensarat* \n Siehe [Kontakt](/contact).\n:::");
|
|
|
|
html.ShouldContain("<div class=\"team\">");
|
|
html.ShouldContain($"<li class=\"{TeamMember.CardClass}\">");
|
|
html.ShouldContain("<span class=\"team-avatar team-avatar-2\" aria-hidden=\"true\">MP</span>");
|
|
html.ShouldContain("<strong class=\"team-name\">Maik Palm</strong>");
|
|
html.ShouldContain("<span class=\"team-role\">Vorsitzender</span>");
|
|
html.ShouldContain("<span class=\"team-duty\">Schulkonferenz</span>");
|
|
html.ShouldContain("<span class=\"team-duty\">Mensarat</span>");
|
|
html.ShouldContain("<span class=\"team-note\">Siehe <a href=\"/contact\">Kontakt</a>.</span>"); // links survive
|
|
html.ShouldNotContain("(Vorsitzender)");
|
|
}
|
|
|
|
[Fact]
|
|
public void Item_without_convention_renders_as_plain_card_with_its_text()
|
|
{
|
|
var html = Markdown.ToHtml("::: team\n- Michael Weber, Beisitz\n:::");
|
|
|
|
html.ShouldContain($"<li class=\"{TeamMember.CardClass} {TeamMember.PlainCardClass}\">Michael Weber, Beisitz</li>");
|
|
html.ShouldNotContain("team-avatar");
|
|
}
|
|
|
|
[Fact]
|
|
public void Editor_text_in_a_card_is_escaped()
|
|
{
|
|
var html = Markdown.ToHtml("::: team\n- **<b>Name</b>** (Rolle \"x\")\n:::");
|
|
|
|
html.ShouldNotContain("<b>");
|
|
html.ShouldContain("<b>Name</b>");
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("::: team gross")]
|
|
[InlineData("::: team groß")]
|
|
[InlineData("::: team GROSS")]
|
|
public void Large_modifier_adds_its_class(string opening) =>
|
|
Markdown.ToHtml($"{opening}\n- **Maik Palm**\n:::").ShouldContain("<div class=\"team gross\">");
|
|
|
|
[Fact]
|
|
public void Mistyped_modifier_keeps_the_grid()
|
|
{
|
|
// A typo in the modifier costs the highlight, not the cards.
|
|
var html = Markdown.ToHtml("::: team grss\n- **Maik Palm**\n:::");
|
|
|
|
html.ShouldContain("<div class=\"team\">");
|
|
html.ShouldContain("team-card");
|
|
}
|
|
|
|
[Fact]
|
|
public void Lists_outside_a_team_block_are_untouched()
|
|
{
|
|
var html = Markdown.ToHtml("- **Maik Palm** (Vorsitzender)");
|
|
|
|
html.ShouldBe("<ul>\n<li><strong>Maik Palm</strong> (Vorsitzender)</li>\n</ul>\n");
|
|
}
|
|
|
|
private static (TeamMember Member, Markdig.Syntax.Inlines.Inline[] Note) Read(string markdown) =>
|
|
TeamMember.Read(FirstParagraph(markdown)) ?? throw new ShouldAssertException("The item was not read as a person.");
|
|
|
|
private static ParagraphBlock FirstParagraph(string markdown) =>
|
|
Markdown.Parse(markdown).Descendants<ParagraphBlock>().First();
|
|
}
|