Applies docs/research/2026-08-10-ch6ab-review-findings.md in full:
- BLOCKER 1: UiResizeGrip now carries its ElementInfo/resolve pair and
draws its own authored DirectState media (a synthetic parameterless
grip still draws nothing, preserving existing resize-drag tests).
DatWidgetFactory.BuildResizeGrip threads resolve through. All seven
live grips on the main chat window now resolve a non-zero sprite,
restoring the visible borders/corners CH6a silently dropped.
- SHOULD-FIX 2: ChatWindowState gains BroadcastTargetWindow, a sentinel
distinct from every real window id (0-4), fixing the bug where the
main window's explicit-addressing branch coincided with the broadcast
check (both were literal 0). SetFilter's main-window no-op is dropped
— the main window's filter is now genuinely settable. ChatWindowController
.Bind takes a ChatWindowState (the same canonical instance the floating
windows already share) and GetTranscriptLines builds a real accept
predicate instead of accept:null. Verified safe: ClientLocal (0x1A)
never reaches ChatLog (AddText routes it to the SpewBox and returns),
so nothing observable regresses.
- SHOULD-FIX 3: UiButton.SuppressSelfToggle stops the four chat-window
indicator buttons (DAT property 0x0B=true, no retail click handler)
from flipping their own Selected mirror on a stray click.
- SHOULD-FIX 4: generated and committed chat_floaty_2100005b.json from
the real installed dats; added the permanent RetailLayoutFixtureGenerator
entry. All three flagged FloatingChatWindowController assumptions
(input field, title bar, close button) are confirmed correct against
real data — no controller code changes needed. New finding: unlike the
main window, ALL EIGHT floaty border/corner elements are live Type-9
grips (the floaty's own title bar is its move handle), so a floaty
window resizes from every edge and corner.
- SHOULD-FIX 5: register row AP-189 documents the shared-500-entry/
200-line-tail vs retail's per-window 10,000-line scrollback depth gap.
- NITs 1-5: documented the filter-persistence-only-on-/saveautoui
asymmetry and the reconnect-preserves-filters intent; corrected the
research doc's modifier-mask mislabel and the "ONLY function" false
superlative; moved WrapText off ChatWindowController onto
ChatTranscriptRenderer, closing the circular dependency.
Full Release suite: 12,420 passed / 4 skipped / 0 failed (baseline
12,392/4/0 at 22020ef2; net +28 tests, zero regressions).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
89 lines
3.3 KiB
C#
89 lines
3.3 KiB
C#
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using AcDream.App.UI.Layout;
|
|
|
|
namespace AcDream.App.Tests.UI.Layout;
|
|
|
|
/// <summary>
|
|
/// <see cref="ChatTranscriptRenderer.WrapText"/> tests — moved here from
|
|
/// <c>ChatWindowControllerTests</c> (CH6a/b REJECT-review NIT 5: WrapText
|
|
/// itself moved off <c>ChatWindowController</c> onto
|
|
/// <see cref="ChatTranscriptRenderer"/> to close the circular dependency
|
|
/// where <see cref="ChatTranscriptRenderer.BuildLines"/> called back into
|
|
/// one of its own two consumers).
|
|
///
|
|
/// <para>
|
|
/// Campaign CH user-gate round 1, item F: <c>/help</c> (and "probably many
|
|
/// places") never split on embedded <c>'\n'</c> — the whole multi-line blob
|
|
/// rode the single early-out as ONE line. Split on <c>'\n'</c> first, then
|
|
/// word-wrap each segment; a single-segment text keeps the pre-existing
|
|
/// early-out behavior exactly.
|
|
/// </para>
|
|
/// </summary>
|
|
public class ChatTranscriptRendererTests
|
|
{
|
|
private static float MeasureByCharCount(string s) => s.Length;
|
|
|
|
[Fact]
|
|
public void WrapText_EmbeddedNewlines_ProduceOneRenderedLinePerSegment()
|
|
{
|
|
string text = "line one\nline two\nline three";
|
|
|
|
// maxW is generous — every segment fits without word-wrapping, so
|
|
// this isolates the newline-split behavior specifically.
|
|
var lines = new List<string>(ChatTranscriptRenderer.WrapText(text, 1000f, MeasureByCharCount));
|
|
|
|
Assert.Equal(new[] { "line one", "line two", "line three" }, lines);
|
|
}
|
|
|
|
[Fact]
|
|
public void WrapText_CarriageReturnNewline_NormalizesTheSameAsBareNewline()
|
|
{
|
|
string text = "line one\r\nline two";
|
|
|
|
var lines = new List<string>(ChatTranscriptRenderer.WrapText(text, 1000f, MeasureByCharCount));
|
|
|
|
Assert.Equal(new[] { "line one", "line two" }, lines);
|
|
}
|
|
|
|
[Fact]
|
|
public void WrapText_SegmentLongerThanMaxWidth_StillWordWraps()
|
|
{
|
|
// Each segment is independently word-wrapped by the SAME algorithm
|
|
// the single-line path always used — a multi-line server message
|
|
// whose second line overflows the window still wraps that line.
|
|
string text = "short\nthis segment is much too long to fit on one line";
|
|
|
|
var lines = new List<string>(ChatTranscriptRenderer.WrapText(text, 10f, MeasureByCharCount));
|
|
|
|
Assert.Equal("short", lines[0]);
|
|
Assert.True(lines.Count > 2, "the long second segment should have wrapped into multiple lines");
|
|
Assert.All(lines, line => Assert.True(MeasureByCharCount(line) <= 10f));
|
|
Assert.Equal(
|
|
"this segment is much too long to fit on one line",
|
|
string.Join(" ", lines.Skip(1)));
|
|
}
|
|
|
|
[Fact]
|
|
public void WrapText_SingleSegmentText_KeepsTheEarlyOutBehavior()
|
|
{
|
|
// No '\n' at all — the pre-existing single-line early-out path
|
|
// (whole text fits => returned verbatim as one fragment) is
|
|
// unchanged.
|
|
string text = "no newlines here";
|
|
|
|
var lines = new List<string>(ChatTranscriptRenderer.WrapText(text, 1000f, MeasureByCharCount));
|
|
|
|
Assert.Equal(new[] { text }, lines);
|
|
}
|
|
|
|
[Fact]
|
|
public void WrapText_ConsecutiveNewlines_ProduceABlankLine()
|
|
{
|
|
string text = "first\n\nthird";
|
|
|
|
var lines = new List<string>(ChatTranscriptRenderer.WrapText(text, 1000f, MeasureByCharCount));
|
|
|
|
Assert.Equal(new[] { "first", "", "third" }, lines);
|
|
}
|
|
}
|