acdream/tests/AcDream.App.Tests/UI/Layout/ChatOptionsDatDefaultsTests.cs
Erik e71e5a9614 feat(ui): Campaign OP slice OP5 — the Chat tab
Binds LayoutDesc 0x2100005C through OP2's template-list mechanism and
OP3's per-page OptionPage model: the General Options header + two
DualHash-linked opacity sliders (Option_DefaultOpacity_Property
0x10000080 / Option_ActiveOpacity_Property 0x10000081, live-apply on
drag through RetailWindowOpacityController, defaults read from the
installed DAT's DBProperties collection at DID 0x78000001 via
ChatOptionsDatDefaults), and the five per-window text-filter blocks
(main window 12 rows minus Gameplay, four floaties 13 rows each — the
byte-verified authored order cross-checked against the raw
gmChatOptionsUI::InitOptions/AddCheckboxBitfield64Option pseudo-C, not
just the research doc's own table) writing AcDream.Core.Chat.
ChatWindowState directly, the same state CH6's chat windows already
read.

AP-195 retired: ported both halves left open at the OP2 re-review —
the ALL-set LED media swap (new UiButton.FaceFileOverride, driven by
the block-level P0x10000082/P0x10000083 sprites now threaded through
ElementInfo/DatWidgetFactory) and the CreateChildren self-sizing tail
(UiCheckboxBitfield64.Height grows with its stacked row content; the
enclosing ListBox reflows around the block's FINAL height via the new
UiTemplateListBox.AddPrebuiltRow, reusing the ListBox's own stacking
rather than a third stacking path). AP-187 broadened to cover the main
window's own filter (previously only the four floaties) and the new
live-editing write path.

The main chat window's filter (retail window id 8, ChatWindowState id
0) gains its own settings.json persistence (ChatSettings.
ChatWindowMainFilter) alongside the pre-existing floaty 1-4 fields;
opacity persistence is now wired on every live slider change, not only
through the old dev-scaffold Settings panel.

Fixture regeneration (ACDREAM_REGENERATE_UI_FIXTURES=1) picked up the
new ElementInfo.LedCheckedSprite/LedUncheckedSprite fields across all
19 committed layout fixtures — purely additive, confirmed against the
live installed DAT (0x10000520's own 0x82/0x83 properties resolve to
0x06004D17/0x06004D19 exactly as AP-195 documented).

Conformance: FilterRows/FilterBlocks pinned against the byte-verified
authored order and ChatWindowState's own default constants; the AP-195
LED swap and self-sizing behavior; the DAT opacity-default extraction
against the live installed DAT; live filter/opacity writes reaching
ChatWindowState/RetailWindowOpacityController; OnShown re-seed and
Reset/Defaults ghosting per the OP4 binding-pattern discipline.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 06:25:59 +02:00

57 lines
2.2 KiB
C#

using AcDream.App.UI.Layout;
using DatReaderWriter;
using DatReaderWriter.Options;
using Xunit.Sdk;
using SysEnv = System.Environment;
namespace AcDream.App.Tests.UI.Layout;
/// <summary>
/// Campaign OP slice OP5: live-DAT proof that <see cref="ChatOptionsDatDefaults"/>
/// resolves the Chat tab's two opacity-slider defaults from the installed DAT —
/// same pattern as <c>RetailSelectionAssetTests</c> (runs against the real
/// installed <c>client_portal.dat</c>, skips gracefully when none is present, no
/// env-var gate needed since it never writes anything).
/// </summary>
public sealed class ChatOptionsDatDefaultsTests
{
[Fact]
public void InstalledDat_ResolvesTheByteVerifiedSliderDefaults()
{
string datDir = ResolveDatDir();
using var dats = new DatCollection(datDir, DatAccessType.Read);
bool resolved = ChatOptionsDatDefaults.TryRead(
dats, out float defaultOpacity, out float activeOpacity);
// Empirically confirmed against the installed DAT (this slice's own research
// trace): DID 0x78000001, Option_DefaultOpacity_Property = 0.5,
// Option_ActiveOpacity_Property = 1.0 — matching retail's ChatInterface base-
// constructor fallback exactly (a coincidence the fallback documents, not
// something this test relies on to pass).
Assert.True(resolved);
Assert.Equal(0.5f, defaultOpacity);
Assert.Equal(1.0f, activeOpacity);
}
private static string ResolveDatDir()
{
string? configured = SysEnv.GetEnvironmentVariable("ACDREAM_DAT_DIR");
if (!string.IsNullOrWhiteSpace(configured)
&& File.Exists(Path.Combine(configured, "client_portal.dat")))
return configured;
const string installed = @"C:\Turbine\Asheron's Call";
if (File.Exists(Path.Combine(installed, "client_portal.dat")))
return installed;
string conventional = Path.Combine(
SysEnv.GetFolderPath(SysEnv.SpecialFolder.UserProfile),
"Documents",
"Asheron's Call");
if (File.Exists(Path.Combine(conventional, "client_portal.dat")))
return conventional;
throw SkipException.ForSkip("Installed client_portal.dat is required.");
}
}