Files
ersatztv/ErsatzTV.Tests/Infrastructure/GraphicsElementSeederTests.cs
T
timothyandClaude Opus 4.8 892f4e354b
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 13s
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 16s
PR Gates / Docs update reminder (pull_request) Successful in 17s
PR Gates / decisions lifecycle (pull_request) Successful in 21s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 1m28s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 15m46s
Build ErsatzTV Image / Functional E2E (curl contracts) (pull_request) Successful in 15m50s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 20m18s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
fix(570): On Now/Next overlay YAML renders — font_family + format_datetime
The #74 seeded on-now-next.yml never rendered at transcode time:
- format_datetime was called with 2 args; it needs 3 (DateTimeOffset, timeZoneId,
  format) and does the tz conversion itself, so the Scriban render threw. Drop the
  NEXT start-time (avoids hardcoding a timezone in a shipped default).
- styles had no font_family; CustomFontMapper.TypefaceFromStyle crashes on a null
  family before its default-font fallback. Add font_family: Noto Sans.
Verified live on ersatztv-test via frame capture. Adds a seeder test that
deserializes the YAML and asserts base_style resolves + every style sets a font.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 23:10:35 +02:00

96 lines
3.5 KiB
C#

using System.IO.Abstractions;
using Testably.Abstractions.Testing;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Infrastructure.Data;
using ErsatzTV.Infrastructure.Streaming.Graphics;
using ErsatzTV.Tests.Support;
using Microsoft.EntityFrameworkCore;
using NUnit.Framework;
using Shouldly;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
using ErsatzTV.Core.Graphics;
namespace ErsatzTV.Tests.Infrastructure;
[TestFixture]
public class GraphicsElementSeederTests
{
private InMemoryTvContext _db = null!;
private string _seededPath = null!;
[SetUp]
public async Task SetUp()
{
_db = await InMemoryTvContext.CreateAsync();
_seededPath = Path.Combine(FileSystemLayout.GraphicsElementsTextTemplatesFolder, "on-now-next.yml");
}
[TearDown]
public async Task TearDown() => await _db.DisposeAsync();
[Test]
public async Task Seeds_File_And_Marker_When_Absent()
{
var fs = new MockFileSystem();
await using TvContext context = _db.CreateContext();
await GraphicsElementSeeder.SeedOnNowNext(context, fs, CancellationToken.None);
fs.File.Exists(_seededPath).ShouldBeTrue();
fs.File.ReadAllText(_seededPath).ShouldContain("epg_entries: 2");
(await context.ConfigElements.AnyAsync(c => c.Key == ConfigElementKey.GraphicsOnNowNextSeeded.Key)).ShouldBeTrue();
}
[Test]
public async Task Adopts_Existing_File_Untouched()
{
var fs = new MockFileSystem();
fs.Directory.CreateDirectory(FileSystemLayout.GraphicsElementsTextTemplatesFolder);
await fs.File.WriteAllTextAsync(_seededPath, "name: Operator Custom\nepg_entries: 2\n");
await using TvContext context = _db.CreateContext();
await GraphicsElementSeeder.SeedOnNowNext(context, fs, CancellationToken.None);
fs.File.ReadAllText(_seededPath).ShouldContain("Operator Custom");
}
[Test]
public async Task Does_Not_Resurrect_After_Delete()
{
var fs = new MockFileSystem();
await using TvContext context = _db.CreateContext();
await GraphicsElementSeeder.SeedOnNowNext(context, fs, CancellationToken.None);
fs.File.Delete(_seededPath);
await GraphicsElementSeeder.SeedOnNowNext(context, fs, CancellationToken.None);
fs.File.Exists(_seededPath).ShouldBeFalse();
}
// Guards the #570 render bugs: the seeded YAML must deserialize into a TextGraphicsElement whose
// base_style names a real style and whose every style sets a font_family — a null font_family
// crashes CustomFontMapper before its default-font fallback, so the overlay silently never renders.
[Test]
public async Task Seeded_Element_Deserializes_With_Resolvable_BaseStyle_And_Font_Families()
{
var fs = new MockFileSystem();
await using TvContext context = _db.CreateContext();
await GraphicsElementSeeder.SeedOnNowNext(context, fs, CancellationToken.None);
string yaml = await fs.File.ReadAllTextAsync(_seededPath);
IDeserializer deserializer = new DeserializerBuilder()
.WithNamingConvention(CamelCaseNamingConvention.Instance)
.IgnoreUnmatchedProperties()
.Build();
var element = deserializer.Deserialize<TextGraphicsElement>(yaml);
element.ShouldNotBeNull();
element.Styles.ShouldNotBeEmpty();
element.Styles.Select(s => s.Name).ShouldContain(element.BaseStyle);
element.Styles.ShouldAllBe(s => !string.IsNullOrWhiteSpace(s.FontFamily));
}
}