Rework post page (lead, pager), add skip link, unified focus ring, reduced motion and keyboard-accessible menu
This commit is contained in:
1 parent
ae362639e3
commit
ba65b18d29
14 files changed
+544
-69
No files matched your search
@@ -60,7 +60,16 @@ public sealed partial class RouteSmokeTests : IDisposable
|
||||
/// Every route that a visitor can reach through the navigation, the FAQ hub
|
||||
/// or the news section. The post slugs match the fixture's seed.
|
||||
/// </summary>
|
||||
public static TheoryData<string> KnownRoutes =>
|
||||
public static TheoryData<string> KnownRoutes => [.. Routes];
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="KnownRoutes"/> that render an HTML page, i.e. all but the
|
||||
/// calendar feed.
|
||||
/// </summary>
|
||||
public static TheoryData<string> PageRoutes =>
|
||||
[.. Routes.Where(route => !route.EndsWith(".ics", StringComparison.Ordinal))];
|
||||
|
||||
private static readonly string[] Routes =
|
||||
[
|
||||
"/",
|
||||
"/board",
|
||||
@@ -453,6 +462,71 @@ public sealed partial class RouteSmokeTests : IDisposable
|
||||
visible.ShouldNotContain("Oktober 2026");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Post_page_has_the_title_as_its_only_h1()
|
||||
{
|
||||
var client = _factory.CreateClient();
|
||||
|
||||
var html = await client.GetStringAsync(new Uri("/posts/new-board", UriKind.Relative));
|
||||
|
||||
Regex.Count(html, "<h1[ >]").ShouldBe(1);
|
||||
html.ShouldContain(">Neuer Vorstand</h1>");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Newest_post_links_only_to_the_older_one()
|
||||
{
|
||||
// The fixture has two posts: new-board (newer) and new-hall (older).
|
||||
// "Vorheriger Beitrag" is the older post, so the newest post has no "next".
|
||||
var client = _factory.CreateClient();
|
||||
|
||||
var html = await client.GetStringAsync(new Uri("/posts/new-board", UriKind.Relative));
|
||||
|
||||
html.ShouldContain("href=\"/posts/new-hall\" rel=\"prev\"");
|
||||
html.ShouldNotContain("rel=\"next\"");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Oldest_post_links_only_to_the_newer_one()
|
||||
{
|
||||
var client = _factory.CreateClient();
|
||||
|
||||
var html = await client.GetStringAsync(new Uri("/posts/new-hall", UriKind.Relative));
|
||||
|
||||
html.ShouldContain("href=\"/posts/new-board\" rel=\"next\"");
|
||||
html.ShouldNotContain("rel=\"prev\"");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(PageRoutes))]
|
||||
public async Task Every_page_starts_with_a_skip_link_to_its_own_content(string route)
|
||||
{
|
||||
// The skip link must carry the page's own path: with <base href="/"> a bare
|
||||
// "#main" would lead back to the home page. It must also bypass Blazor's
|
||||
// enhanced navigation, which scrolls but does not move the focus.
|
||||
var client = _factory.CreateClient();
|
||||
|
||||
var html = await client.GetStringAsync(new Uri(route, UriKind.Relative));
|
||||
|
||||
html.ShouldContain($"""<a class="skip-link" href="{route}#main" data-enhance-nav="false" """);
|
||||
html.ShouldContain("Zum Inhalt springen</a>");
|
||||
html.ShouldContain("""<main id="main" """);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Menu_toggle_is_a_named_checkbox_tied_to_the_menu()
|
||||
{
|
||||
// The CSS-only hamburger: the checkbox is what keyboard and screen-reader
|
||||
// users operate, so it needs a name and must point at the menu it opens.
|
||||
var client = _factory.CreateClient();
|
||||
|
||||
var html = await client.GetStringAsync(new Uri("/", UriKind.Relative));
|
||||
|
||||
html.ShouldContain("aria-controls=\"main-nav\"");
|
||||
html.ShouldContain("Hauptmenü anzeigen</span>");
|
||||
html.ShouldContain("<nav id=\"main-nav\"");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Matches a resource reference that points at an absolute URL, with or without
|
||||
/// scheme (<c>https://</c>, <c>http://</c> or protocol-relative <c>//</c>): the
|
||||
|
||||
Reference in new issue
Block a user