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 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)); } }