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>
120 lines
5.6 KiB
C#
120 lines
5.6 KiB
C#
using AcDream.App.UI;
|
|
using AcDream.App.UI.Layout;
|
|
|
|
namespace AcDream.App.Tests.UI.Layout;
|
|
|
|
/// <summary>
|
|
/// Dat-free conformance tests for the committed <c>chat_floaty_2100005b.json</c>
|
|
/// golden fixture — retail's ACTUAL floating chat window (LayoutDesc
|
|
/// <c>0x2100005B</c>, window root <c>0x100004F7</c>, authored 250x108; CH6a/b
|
|
/// REJECT-review SHOULD-FIX 4,
|
|
/// <c>docs/research/2026-08-10-ch6ab-review-findings.md</c>). Pins the resolved
|
|
/// widget types <see cref="FloatingChatWindowController"/> assumes at
|
|
/// <c>Bind</c> time — before this fixture existed those assumptions were
|
|
/// untested against real dat data.
|
|
/// </summary>
|
|
public class FloatingChatLayoutConformanceTests
|
|
{
|
|
private static ElementInfo? Find(ElementInfo n, uint id)
|
|
{
|
|
if (n.Id == id) return n;
|
|
foreach (var c in n.Children)
|
|
{
|
|
var f = Find(c, id);
|
|
if (f is not null) return f;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
[Fact]
|
|
public void FloatyFixture_ResolvesKnownElements()
|
|
{
|
|
var root = FixtureLoader.LoadFloatyChatInfos();
|
|
Assert.NotNull(Find(root, 0x10000011u)); // transcript
|
|
Assert.NotNull(Find(root, 0x10000016u)); // input
|
|
Assert.NotNull(Find(root, 0x10000012u)); // scrollbar track
|
|
Assert.NotNull(Find(root, 0x10000019u)); // send button
|
|
Assert.NotNull(Find(root, 0x100004D9u)); // title bar
|
|
Assert.NotNull(Find(root, 0x1000052Au)); // close button
|
|
Assert.NotNull(Find(root, 0x10000529u)); // title-bar drag handle
|
|
// No talk-focus menu, no max/min button, no 1-4 indicators — a floaty
|
|
// window has none of these (research doc §2.2).
|
|
Assert.Null(Find(root, 0x10000014u));
|
|
Assert.Null(Find(root, 0x1000046Fu));
|
|
Assert.Null(Find(root, 0x10000522u));
|
|
}
|
|
|
|
[Fact]
|
|
public void FloatyFixture_ResolvedTypes_MatchRetailRegistry()
|
|
{
|
|
// CH6a/b REJECT-review SHOULD-FIX 4's three flagged untested
|
|
// assumptions: the chat entry field, the title bar, and the close
|
|
// button. All three are confirmed correct against the real fixture.
|
|
var root = FixtureLoader.LoadFloatyChatInfos();
|
|
Assert.Equal(12u, Find(root, 0x10000016u)!.Type); // Text/style-prototype (input) + Editable 0x16
|
|
Assert.True(Find(root, 0x10000016u)!.TryGetEffectiveBool(0x16u, out bool editable) && editable);
|
|
Assert.Equal(12u, Find(root, 0x100004D9u)!.Type); // Text (title bar) — no Editable
|
|
Assert.False(Find(root, 0x100004D9u)!.TryGetEffectiveBool(0x16u, out bool titleEditable) && titleEditable);
|
|
Assert.Equal(1u, Find(root, 0x10000019u)!.Type); // Button (Send)
|
|
Assert.Equal(1u, Find(root, 0x1000052Au)!.Type); // Button (Close)
|
|
Assert.Equal(11u, Find(root, 0x10000012u)!.Type); // Scrollbar
|
|
Assert.Equal(2u, Find(root, 0x10000529u)!.Type); // Dragbar (title-bar move handle)
|
|
}
|
|
|
|
[Fact]
|
|
public void MountedFloatyWindow_ResolvesToTheExpectedWidgetTypes()
|
|
{
|
|
// The resolved WIDGET types (post-DatWidgetFactory), not just the raw
|
|
// dat Type numbers above — this is what FloatingChatWindowController.Bind
|
|
// actually casts against.
|
|
var layout = FixtureLoader.LoadFloatyChat();
|
|
Assert.IsType<UiField>(layout.FindElement(0x10000016u)); // Editable 0x16 -> UiField
|
|
Assert.IsType<UiText>(layout.FindElement(0x100004D9u)); // no Editable -> UiText
|
|
Assert.IsType<UiButton>(layout.FindElement(0x10000019u)); // Send
|
|
Assert.IsType<UiButton>(layout.FindElement(0x1000052Au)); // Close
|
|
Assert.IsType<UiScrollbar>(layout.FindElement(0x10000012u));
|
|
}
|
|
|
|
// ── Border/corner grips — CH6a/b SHOULD-FIX 4's "also determines whether
|
|
// floaties resize from real grips" ────────────────────────────────────────
|
|
|
|
[Theory]
|
|
[InlineData(0x100004FCu)] // TL corner
|
|
[InlineData(0x1000000Fu)] // top edge
|
|
[InlineData(0x100004FEu)] // TR corner
|
|
[InlineData(0x100004D2u)] // left edge
|
|
[InlineData(0x10000501u)] // BL corner
|
|
[InlineData(0x100004D4u)] // bottom edge
|
|
[InlineData(0x10000503u)] // BR corner
|
|
[InlineData(0x100004D3u)] // right edge
|
|
public void FloatyFixture_EveryBorderElement_IsALiveResizeGrip(uint elementId)
|
|
{
|
|
// Unlike the main window (whose plain top EDGE strip is a Type-2
|
|
// Dragbar move handle, not a grip — the main window has no title bar
|
|
// so the top strip doubles as the move handle), the floaty window's
|
|
// move handle is its OWN title-bar group (0x10000529, Type 2) — so
|
|
// ALL EIGHT border/corner elements here are genuine Type-9 Resizebar
|
|
// grips. A floaty window resizes from every edge and every corner.
|
|
var root = FixtureLoader.LoadFloatyChatInfos();
|
|
Assert.Equal(9u, Find(root, elementId)!.Type);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(0x100004FCu)]
|
|
[InlineData(0x1000000Fu)]
|
|
[InlineData(0x100004FEu)]
|
|
[InlineData(0x100004D2u)]
|
|
[InlineData(0x10000501u)]
|
|
[InlineData(0x100004D4u)]
|
|
[InlineData(0x10000503u)]
|
|
[InlineData(0x100004D3u)]
|
|
public void MountedFloatyWindow_EveryLiveGrip_ResolvesNonZeroSprite(uint elementId)
|
|
{
|
|
// BLOCKER 1's regression guard extended to the floaty layout: every
|
|
// one of the eight live grips must carry real authored border/corner
|
|
// media, not draw nothing.
|
|
var layout = FixtureLoader.LoadFloatyChat();
|
|
var grip = Assert.IsType<UiResizeGrip>(layout.FindElement(elementId));
|
|
Assert.NotEqual(0u, grip.SpriteFile);
|
|
}
|
|
}
|