feature: Phase 22 SEO metadata component for public site
One presentational SeoHead renders the full OG/Twitter/JSON-LD head surface at prerender via typed schema.org builders. Per-medium release schema, config-sourced canonicals, 404 noindex. Zero CMS change.
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
using DeepDrftPublic.Client.Common;
|
||||
|
||||
namespace DeepDrftTests;
|
||||
|
||||
/// <summary>
|
||||
/// Unit tests for <see cref="SeoUrls"/> — the absolute-URL composition shared by the SeoModel factories
|
||||
/// and SeoHead (Phase 22). Origin always comes from config (never a browser API), so these pin the
|
||||
/// slash-join, the cover-vs-default fallback (C6/AC4), and the ISO-8601 duration edge cases.
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
public class SeoUrlsTests
|
||||
{
|
||||
private static readonly SeoOptions Options = new()
|
||||
{
|
||||
BaseUrl = "https://deepdrft.com",
|
||||
DefaultImageUrl = "/img/og-default.png",
|
||||
};
|
||||
|
||||
[TestCase("/cuts/key", "https://deepdrft.com/cuts/key")]
|
||||
[TestCase("cuts/key", "https://deepdrft.com/cuts/key")]
|
||||
[TestCase("/", "https://deepdrft.com/")]
|
||||
[TestCase("", "https://deepdrft.com")]
|
||||
public void Absolute_JoinsOriginAndPath_WithoutDoublingOrDroppingSlash(string path, string expected)
|
||||
{
|
||||
Assert.That(SeoUrls.Absolute(Options, path), Is.EqualTo(expected));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Absolute_TrimsTrailingSlashOnBaseUrl()
|
||||
{
|
||||
var withSlash = Options with { BaseUrl = "https://deepdrft.com/" };
|
||||
Assert.That(SeoUrls.Absolute(withSlash, "/cuts"), Is.EqualTo("https://deepdrft.com/cuts"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CoverOrDefault_WithCover_BuildsEscapedImageRoute()
|
||||
{
|
||||
Assert.That(SeoUrls.CoverOrDefault(Options, "my cover.jpg"),
|
||||
Is.EqualTo("https://deepdrft.com/api/image/my%20cover.jpg"));
|
||||
}
|
||||
|
||||
[TestCase(null)]
|
||||
[TestCase("")]
|
||||
[TestCase(" ")]
|
||||
public void CoverOrDefault_WithoutCover_FallsBackToDefaultImage(string? image)
|
||||
{
|
||||
Assert.That(SeoUrls.CoverOrDefault(Options, image),
|
||||
Is.EqualTo("https://deepdrft.com/img/og-default.png"));
|
||||
}
|
||||
|
||||
[TestCase(30.0, "PT30S")]
|
||||
[TestCase(90.0, "PT1M30S")]
|
||||
[TestCase(3723.0, "PT1H2M3S")]
|
||||
public void IsoDuration_PositiveSeconds_RendersIso8601(double seconds, string expected)
|
||||
{
|
||||
Assert.That(SeoUrls.IsoDuration(seconds), Is.EqualTo(expected));
|
||||
}
|
||||
|
||||
[TestCase(null)]
|
||||
[TestCase(0.0)]
|
||||
[TestCase(-5.0)]
|
||||
[TestCase(double.NaN)]
|
||||
[TestCase(double.PositiveInfinity)]
|
||||
public void IsoDuration_NonPositiveOrNonFinite_ReturnsNull(double? seconds)
|
||||
{
|
||||
Assert.That(SeoUrls.IsoDuration(seconds), Is.Null);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user