Adds LetterHtml.Render, a pure composer mirroring the FE letter canvas'
class vocabulary, behind two ExcludeFromDescription()'d endpoints
(GET /brief/preview, GET /admin/org-template/{subOrgId}/preview).
Auto-resolvable placeholders pull from seed/case data; unresolved
manual ones render as "[NOG IN TE VULLEN: label]". A sent brief
archives its composed HTML (BriefEntity.ArchivedHtml) so a later
org-template republish never changes it. FE gets a hand-written fetch
adapter (text/html, not JSON) and a "Voorbeeld" button that opens the
preview in a new tab.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
103 lines
3.6 KiB
C#
103 lines
3.6 KiB
C#
using System.Text.RegularExpressions;
|
|
using BigRegister.Api.Contracts;
|
|
using BigRegister.Api.Data;
|
|
using BigRegister.Domain.Letters;
|
|
|
|
namespace BigRegister.Tests;
|
|
|
|
/// <summary>
|
|
/// WP-25's fence against drift between the backend renderer and the FE letter
|
|
/// canvas: a golden-file snapshot of a fixed brief + template, and a class-parity
|
|
/// check that every `letter`-prefixed class the renderer emits exists in the
|
|
/// shared `public/letter.css` contract. Neither test launches a browser.
|
|
/// </summary>
|
|
public class LetterHtmlTests
|
|
{
|
|
private static BriefEntity FixtureBrief() => new()
|
|
{
|
|
BriefId = "golden-brief-1",
|
|
Owner = "golden",
|
|
Beroep = "arts",
|
|
TemplateId = "besluit-arts",
|
|
DrafterId = BriefStore.DrafterId,
|
|
Placeholders = new[]
|
|
{
|
|
new PlaceholderDefDto("naam_zorgverlener", "Naam zorgverlener", true),
|
|
new PlaceholderDefDto("datum", "Datum", true),
|
|
new PlaceholderDefDto("reden_besluit", "Reden besluit", false),
|
|
},
|
|
Sections = new()
|
|
{
|
|
new("aanhef", "Aanhef", true, new List<LetterBlockDto>
|
|
{
|
|
new("freeText", "aanhef-1", new RichTextBlockDto(new[]
|
|
{
|
|
new ParagraphDto(new[]
|
|
{
|
|
new RichTextNodeDto("text", Text: "Geachte heer/mevrouw "),
|
|
new RichTextNodeDto("placeholder", Key: "naam_zorgverlener"),
|
|
new RichTextNodeDto("text", Text: ","),
|
|
}),
|
|
})),
|
|
}, Locked: true),
|
|
new("kern", "Kern van het besluit", true, new List<LetterBlockDto>
|
|
{
|
|
new("freeText", "kern-1", new RichTextBlockDto(new[]
|
|
{
|
|
new ParagraphDto(new[] { new RichTextNodeDto("text", Text: "Op ") }),
|
|
new ParagraphDto(new RichTextNodeDto[]
|
|
{
|
|
new("text", Text: "Eerste punt: "),
|
|
new("placeholder", Key: "reden_besluit"),
|
|
}, List: "bullet"),
|
|
new ParagraphDto(new RichTextNodeDto[]
|
|
{
|
|
new("text", Text: "Tweede punt"),
|
|
}, List: "bullet"),
|
|
})),
|
|
}),
|
|
new("slot", "Slot", false, new List<LetterBlockDto>
|
|
{
|
|
new("freeText", "slot-1", new RichTextBlockDto(new[]
|
|
{
|
|
new ParagraphDto(new[] { new RichTextNodeDto("text", Text: "Met vriendelijke groet,") }),
|
|
})),
|
|
}, Locked: true),
|
|
},
|
|
Status = new BriefStatusDto("draft"),
|
|
};
|
|
|
|
private static readonly OrgTemplateDto Template = new(
|
|
"cibg-registers", "BIG-register",
|
|
"Retouradres: Postbus 00000, 2500 AA Den Haag",
|
|
LogoDocumentId: null,
|
|
"BIG-register · Postbus 00000, 2500 AA Den Haag · 070 000 00 00 · info@voorbeeld.example",
|
|
"Dit is een gegenereerd voorbeelddocument uit de register-reference PoC. Alle gegevens zijn fictief.",
|
|
"A. de Vries", "Hoofd Registratie, BIG-register", "Met vriendelijke groet,",
|
|
new MarginsDto(25, 20, 20, 25), Version: 1);
|
|
|
|
private const string At = "2026-07-05T12:00:00.0000000+00:00";
|
|
|
|
private static readonly string GoldenPath = Path.Combine(AppContext.BaseDirectory, "LetterHtml.golden.html");
|
|
|
|
[Fact]
|
|
public void Render_matches_the_golden_file()
|
|
{
|
|
var html = LetterHtml.Render(FixtureBrief(), Template, At, watermark: true);
|
|
var golden = File.ReadAllText(GoldenPath);
|
|
Assert.Equal(golden, html);
|
|
}
|
|
|
|
[Fact]
|
|
public void Every_letter_prefixed_class_exists_in_letter_css()
|
|
{
|
|
var html = LetterHtml.Render(FixtureBrief(), Template, At, watermark: true);
|
|
var classes = Regex.Matches(html, "class=\"([^\"]+)\"")
|
|
.SelectMany(m => m.Groups[1].Value.Split(' '))
|
|
.Where(c => c.StartsWith("letter"))
|
|
.Distinct();
|
|
Assert.NotEmpty(classes);
|
|
Assert.All(classes, c => Assert.Contains($".{c}", LetterHtml.StyleSheet));
|
|
}
|
|
}
|