using System.Numerics; using AcDream.App.UI; using AcDream.App.UI.Layout; namespace AcDream.App.Tests.UI.Layout; /// /// Campaign CC gate round 1 Batch C: unit tests for the shared /// escape-normalize + word-wrap + per-segment-color helper feeding /// GF-2/GF-3/GF-11a and the Summary how-to text (Commit 3). /// public class DatRichTextTests { private static readonly Vector4 White = Vector4.One; private static readonly Vector4 Green = new(0f, 1f, 0f, 1f); private static UiText MakeTarget(float width) => new() { Width = width, Height = 200f }; [Fact] public void Compose_NormalizesLiteralBackslashNEscape() { UiText target = MakeTarget(1000f); // wide enough that nothing wraps var segments = new[] { new DatRichText.Segment("line one\\nline two", White) }; var lines = DatRichText.Compose(target, segments); Assert.Equal(2, lines.Count); Assert.Equal("line one", lines[0].Text); Assert.Equal("line two", lines[1].Text); } [Fact] public void Compose_WordWrapsToTheTargetWidth() { // Bitmap-font-shaped measure: 8px/char, matching BuildText's own // authored-string fallback measure. UiText target = MakeTarget(80f); // 10 chars per line at 8px/char var segments = new[] { new DatRichText.Segment("one two three four five six seven eight", White), }; var lines = DatRichText.Compose(target, segments); Assert.True(lines.Count > 1, "a long segment must wrap to more than one line"); foreach (UiText.Line line in lines) Assert.True(line.Text.Length * 8f <= 80f, $"line '{line.Text}' overflowed the target width"); } [Fact] public void Compose_EachSegmentKeepsItsOwnColorAcrossItsWrappedLines() { UiText target = MakeTarget(1000f); var segments = new[] { new DatRichText.Segment("Header:", Green), new DatRichText.Segment("Body text.", White), }; var lines = DatRichText.Compose(target, segments); Assert.Equal(2, lines.Count); Assert.Equal(Green, lines[0].Color); Assert.Equal(White, lines[1].Color); } [Fact] public void Compose_NullOrEmptySegmentText_IsSkipped() { UiText target = MakeTarget(1000f); var segments = new[] { new DatRichText.Segment(null, White), new DatRichText.Segment(string.Empty, White), new DatRichText.Segment("real text", White), }; var lines = DatRichText.Compose(target, segments); Assert.Single(lines); Assert.Equal("real text", lines[0].Text); } [Fact] public void Compose_NoSeparatorInsertedBetweenSegments() { // Retail's own composition calls concatenate directly // (AppendStringInfoWithFont / append_n_chars, no interposed // literal) — this helper must not invent one either. UiText target = MakeTarget(1000f); var segments = new[] { new DatRichText.Segment("first", White), new DatRichText.Segment("second", White), }; var lines = DatRichText.Compose(target, segments); // Each segment still wraps independently (so "first"/"second" stay // on separate output lines, not glued into "firstsecond") — but no // BLANK line is inserted between them unless the segment's own // text carried one. Assert.Equal(2, lines.Count); Assert.Equal("first", lines[0].Text); Assert.Equal("second", lines[1].Text); } [Fact] public void Compose_WordWrapsToTheTargetWidth_MinusTheFourRetailMargins() { // R2-1 (Campaign CC gate round 1 Batch E): the wrap width must // shrink by BOTH Padding and the four retail margins (properties // 0x23-0x26 — MarginLeft's own doc comment on UiText), not just the // element's raw Width. A 100px-wide box with margL=10/margR=10 // leaves only 80px of usable width — one 10-char/8px-per-char word // ("aaaaaaaaaa", 80px) must fit on one line, but appending an 11th // 'a' (88px) must force a wrap. UiText fits = new() { Width = 100f, Height = 200f, MarginLeft = 10f, MarginRight = 10f }; var fitsLines = DatRichText.Compose(fits, [new DatRichText.Segment("aaaaaaaaaa", White)]); Assert.Single(fitsLines); UiText overflows = new() { Width = 100f, Height = 200f, MarginLeft = 10f, MarginRight = 10f }; var overflowLines = DatRichText.Compose(overflows, [new DatRichText.Segment("aaaaaaaaaaa", White)]); Assert.True(overflowLines.Count > 1, "an 88px word in an 80px content width must wrap"); } [Fact] public void PaletteColor_ReturnsAuthoredPaletteEntry_WhenPresent() { UiText target = new() { FontColorPalette = [White, Green], }; Assert.Equal(White, DatRichText.PaletteColor(target, 0, Green)); Assert.Equal(Green, DatRichText.PaletteColor(target, 1, White)); } [Fact] public void PaletteColor_FallsBack_WhenPaletteTooShortOrMissing() { UiText target = new(); // empty palette Assert.Equal(Green, DatRichText.PaletteColor(target, 1, Green)); Assert.Equal(Green, DatRichText.PaletteColor(target, -1, Green)); } }