Add the design foundation: self-hosted Nunito, type scale, new tokens and an Icon component
This commit is contained in:
1 parent
3530167cc3
commit
33fa1e4e93
9 files changed
+401
-10
No files matched your search
@@ -0,0 +1,30 @@
|
||||
using Elternbeirat.Web.Shared;
|
||||
|
||||
namespace Elternbeirat.Web.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Unit tests for the icon lookup behind the Icon component. Pure logic, no
|
||||
/// PocketBase needed.
|
||||
/// </summary>
|
||||
public class IconSetTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("calendar")]
|
||||
[InlineData("map-pin")]
|
||||
[InlineData("help-circle")] // kept under its old name although Lucide renamed it
|
||||
public void Known_name_returns_svg_markup(string name)
|
||||
{
|
||||
var markup = IconSet.Find(name);
|
||||
|
||||
markup.ShouldNotBeNullOrWhiteSpace();
|
||||
markup.ShouldStartWith("<"); // SVG child elements, ready to inline
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("does-not-exist")]
|
||||
[InlineData("Calendar")] // lookup is case-sensitive, names are code identifiers
|
||||
[InlineData("")]
|
||||
[InlineData(null)]
|
||||
public void Unknown_name_returns_null(string? name) =>
|
||||
IconSet.Find(name).ShouldBeNull();
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Net;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.AspNetCore.Mvc.Testing;
|
||||
|
||||
namespace Elternbeirat.Web.Tests;
|
||||
@@ -15,7 +16,7 @@ namespace Elternbeirat.Web.Tests;
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Collection(PocketBaseTestGroup.Name)]
|
||||
public sealed class RouteSmokeTests : IDisposable
|
||||
public sealed partial class RouteSmokeTests : IDisposable
|
||||
{
|
||||
// WithWebHostBuilder returns a new factory that wraps the base one; both are
|
||||
// disposable, so both are held and disposed to avoid leaking either.
|
||||
@@ -158,6 +159,39 @@ public sealed class RouteSmokeTests : IDisposable
|
||||
events.ShouldContain("Sitzungen und Veranstaltungen auf einen Blick."); // the page body
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("/app.css")]
|
||||
[InlineData("/")]
|
||||
public async Task No_resource_is_loaded_from_a_foreign_host(string route)
|
||||
{
|
||||
// Privacy rule (docs/recht.md): nothing may make the visitor's browser contact
|
||||
// a third-party host -- no Google Fonts, no CDN. Check the stylesheet and the
|
||||
// rendered home page for anything that LOADS from an absolute URL (src,
|
||||
// srcset, <link href>, CSS url() and @import). Plain <a href> links are left
|
||||
// alone on purpose: an editor may link to another site, and a link loads
|
||||
// nothing until the visitor clicks it.
|
||||
var client = _factory.CreateClient();
|
||||
|
||||
var body = await client.GetStringAsync(new Uri(route, UriKind.Relative));
|
||||
|
||||
ForeignResource().Matches(body).Select(match => match.Value).ShouldBeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Site_font_is_self_hosted()
|
||||
{
|
||||
// The @font-face must point at our own wwwroot with a relative URL, and the
|
||||
// file must actually be served -- otherwise every visitor silently falls back
|
||||
// to the system font.
|
||||
var client = _factory.CreateClient();
|
||||
|
||||
var css = await client.GetStringAsync(new Uri("/app.css", UriKind.Relative));
|
||||
css.ShouldContain("url(\"fonts/nunito-latin-wght.woff2\")");
|
||||
|
||||
var font = await client.GetAsync(new Uri("/fonts/nunito-latin-wght.woff2", UriKind.Relative));
|
||||
font.StatusCode.ShouldBe(HttpStatusCode.OK);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Health_returns_200_when_PocketBase_is_reachable()
|
||||
{
|
||||
@@ -187,4 +221,13 @@ public sealed class RouteSmokeTests : IDisposable
|
||||
|
||||
response.StatusCode.ShouldBe(HttpStatusCode.ServiceUnavailable);
|
||||
}
|
||||
|
||||
/// <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
|
||||
/// <c>src</c>/<c>srcset</c> attributes, a <c><link></c>'s <c>href</c>, CSS
|
||||
/// <c>url()</c> and <c>@import</c>.
|
||||
/// </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();
|
||||
}
|
||||
Reference in new issue
Block a user