Add ::: team block rendering board members as person cards with initials avatars
This commit is contained in:
1 parent
efb6fa9109
commit
a5a68ed122
8 files changed
+815
-10
No files matched your search
@@ -0,0 +1,161 @@
|
||||
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(4);
|
||||
TeamMember.ColorOf("Gentiana Erol").ShouldBe(5);
|
||||
|
||||
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 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-4\" 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 larger size, 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();
|
||||
}
|
||||
Reference in new issue
Block a user