Refresh header and footer with signet, gradient, nav pills and three-column footer; fix box-sizing of design boxes
This commit is contained in:
1 parent
a641beab16
commit
d539fde09d
10 files changed
+407
-59
No files matched your search
@@ -0,0 +1,27 @@
|
||||
using Elternbeirat.Web.Features.Layout;
|
||||
|
||||
namespace Elternbeirat.Web.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Unit tests for the footer's copyright year. Pure logic, no PocketBase needed.
|
||||
/// </summary>
|
||||
public class FooterYearTests
|
||||
{
|
||||
[Fact]
|
||||
public void New_year_in_Berlin_wins_over_the_UTC_year()
|
||||
{
|
||||
// 23:30 UTC on New Year's Eve is already 00:30 in Berlin (winter time, +1),
|
||||
// so the footer must show the new year although the server clock says the old.
|
||||
var now = new DateTimeOffset(2026, 12, 31, 23, 30, 0, TimeSpan.Zero);
|
||||
|
||||
FooterYear.For(now).ShouldBe(2027);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Ordinary_day_keeps_its_year()
|
||||
{
|
||||
var now = new DateTimeOffset(2026, 10, 1, 12, 0, 0, TimeSpan.Zero);
|
||||
|
||||
FooterYear.For(now).ShouldBe(2026);
|
||||
}
|
||||
}
|
||||
@@ -43,6 +43,15 @@ public class NavBuilderTests
|
||||
footer.Select(link => link.Title).ShouldBe(["Impressum", "Datenschutz"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void No_pages_give_empty_menus()
|
||||
{
|
||||
// PocketBase down or nothing published yet: both menus are empty lists, not
|
||||
// null, so the layout can simply skip a column instead of crashing.
|
||||
NavBuilder.Header([]).ShouldBeEmpty();
|
||||
NavBuilder.Footer([]).ShouldBeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Home_page_maps_to_root_every_other_slug_to_its_path()
|
||||
{
|
||||
|
||||
@@ -115,6 +115,30 @@ public sealed partial class RouteSmokeTests : IDisposable
|
||||
html.ShouldNotContain("href=\"/draft\""); // public=false, filtered out
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Footer_links_every_public_header_and_footer_page_in_order()
|
||||
{
|
||||
// The footer repeats the header pages as "Schnellzugriff" and then lists the
|
||||
// footer pages, each column in its order field. Only the <footer> is checked,
|
||||
// so the header menu cannot make the test pass on its own. The expected list
|
||||
// mirrors the fixture's pages; the draft (public=false) must be missing.
|
||||
var client = _factory.CreateClient();
|
||||
|
||||
var html = await client.GetStringAsync(new Uri("/", UriKind.Relative));
|
||||
|
||||
var footer = FooterElement().Match(html);
|
||||
footer.Success.ShouldBeTrue();
|
||||
var hrefs = Href().Matches(footer.Value).Select(match => match.Groups[1].Value);
|
||||
|
||||
hrefs.ShouldBe(
|
||||
[
|
||||
"/", "/board", "/patrons", "/faqs", "/downloads", "/posts", "/events",
|
||||
"/contact", "/imprint", "/privacy", "/faq-lunch",
|
||||
]);
|
||||
footer.Value.ShouldContain("Schnellzugriff");
|
||||
footer.Value.ShouldContain("Weiteres");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Home_renders_the_embeds_its_page_opts_into()
|
||||
{
|
||||
@@ -245,4 +269,17 @@ public sealed partial class RouteSmokeTests : IDisposable
|
||||
/// </summary>
|
||||
[GeneratedRegex("""(?:\b(?:src|srcset)\s*=\s*|<link\b[^>]*\bhref\s*=\s*|url\(\s*|@import\s+)["']?\s*(?:https?:)?//""", RegexOptions.IgnoreCase)]
|
||||
private static partial Regex ForeignResource();
|
||||
|
||||
/// <summary>
|
||||
/// Matches the page's <c><footer></c> element, from its opening tag to the
|
||||
/// closing one. The layout renders exactly one footer and nests no other.
|
||||
/// </summary>
|
||||
[GeneratedRegex("""<footer\b.*?</footer>""", RegexOptions.Singleline)]
|
||||
private static partial Regex FooterElement();
|
||||
|
||||
/// <summary>
|
||||
/// Matches an <c>href</c> attribute and captures its value.
|
||||
/// </summary>
|
||||
[GeneratedRegex(@"\bhref=""([^""]*)""")]
|
||||
private static partial Regex Href();
|
||||
}
|
||||
Reference in new issue
Block a user