using AcDream.App.UI;
using AcDream.App.UI.Layout;
using AcDream.Core.Net.Messages;
using AcDream.Core.Social;
using AcDream.Runtime;
namespace AcDream.App.Tests.UI.Layout;
///
/// Controller-level tests for against the
/// committed social_panel_2100006E_1000018F.json fixture (Campaign FA
/// slice FA3) — the SAME LayoutImporter.ImportInfos(dats, 0x2100006Eu,
/// 0x1000018Fu) catalog import
/// performs against real DATs. No DAT access, no live runtime — dat-free per
/// the established pattern.
///
public sealed class SocialPanelControllerTests
{
private static SocialPanelController.Callbacks MakeCallbacks(
List? calls = null,
RuntimeFellowshipSnapshot fellowship = default,
RuntimeAllegianceSnapshot allegiance = default,
FriendsState? friends = null,
SquelchState? squelch = null)
{
calls ??= new List();
return new SocialPanelController.Callbacks(
Toggle: () => calls.Add("toggle"),
FellowshipSnapshot: () => fellowship,
AllegianceSnapshot: () => allegiance,
Friends: friends ?? new FriendsState(),
Squelch: squelch ?? new SquelchState(),
TemplateResolver: FakeRowTemplateResolver);
}
/// Mirrors the real Friends/Squelch row templates' own two-deep
/// nested-text shape (FA3 live-mount probe) so
/// has something realistic
/// to resolve, without needing the templates' own (uncommitted, separate
/// LayoutDesc) fixtures.
private static UiElement? FakeRowTemplateResolver(uint layoutId, uint elementId)
{
var outer = new UiText();
var inner = new UiText();
outer.AddChild(inner);
return outer;
}
// ── Mount conformance ────────────────────────────────────────────────────
[Fact]
public void Bind_RootBuildsAsUiTabPanel()
{
ImportedLayout layout = FixtureLoader.LoadSocialPanelHost();
Assert.IsType(layout.Root);
}
///
/// Pins the authored 0x2E tab table exactly as read from the live
/// DATs (FA3 live-mount probe) — CORRECTING the coordinator addendum's
/// x-order guess (see 's own class
/// doc). Four entries; Allegiance is the sole default.
///
[Fact]
public void TabTable_MatchesLiveDatPairing_AllegianceIsDefault()
{
ImportedLayout layout = FixtureLoader.LoadSocialPanelHost();
var tabs = Assert.IsType(layout.Root);
Assert.Equal(4, tabs.Tabs.Count);
Assert.Contains(tabs.Tabs, e =>
e.ButtonElementId == 0x1000028Cu && e.PageElementId == 0x10000291u && e.IsDefault);
Assert.Contains(tabs.Tabs, e =>
e.ButtonElementId == 0x1000028Eu && e.PageElementId == 0x10000292u && !e.IsDefault);
Assert.Contains(tabs.Tabs, e =>
e.ButtonElementId == 0x10000512u && e.PageElementId == 0x10000513u && !e.IsDefault);
Assert.Contains(tabs.Tabs, e =>
e.ButtonElementId == 0x1000053Bu && e.PageElementId == 0x1000054Au && !e.IsDefault);
Assert.Single(tabs.Tabs, e => e.IsDefault);
}
[Fact]
public void Bind_Succeeds_AndActivateTabs_SelectsTheAuthoredDefault()
{
ImportedLayout layout = FixtureLoader.LoadSocialPanelHost();
SocialPanelController? controller = SocialPanelController.Bind(layout, MakeCallbacks());
Assert.NotNull(controller);
controller!.ActivateTabs();
Assert.Empty(controller.TabPanel.UnresolvedEntries);
Assert.True(controller.IsShowingAllegiance);
Assert.False(controller.IsShowingFellowship);
}
[Fact]
public void ShowFellowship_SwitchesTheActiveTab()
{
ImportedLayout layout = FixtureLoader.LoadSocialPanelHost();
SocialPanelController? controller = SocialPanelController.Bind(layout, MakeCallbacks());
Assert.NotNull(controller);
controller!.ActivateTabs();
controller.ShowFellowship();
Assert.True(controller.IsShowingFellowship);
Assert.False(controller.IsShowingAllegiance);
}
[Fact]
public void CloseButton_InvokesToggleCallback()
{
ImportedLayout layout = FixtureLoader.LoadSocialPanelHost();
var calls = new List();
SocialPanelController? controller = SocialPanelController.Bind(layout, MakeCallbacks(calls));
Assert.NotNull(controller);
var close = Assert.IsType(layout.FindElement(0x10000290u));
close.OnEvent(new UiEvent(0, close, UiEventType.Click));
Assert.Contains("toggle", calls);
}
// ── Fellowship empty state (item 4) ─────────────────────────────────────
[Fact]
public void Fellowship_NoFellowship_ShowsNotInFellowshipFrame()
{
ImportedLayout layout = FixtureLoader.LoadSocialPanelHost();
SocialPanelController? controller = SocialPanelController.Bind(
layout,
MakeCallbacks(fellowship: new RuntimeFellowshipSnapshot { IsInFellowship = false }));
Assert.NotNull(controller);
UiElement notIn = UiElement.FindDescendant(controller!.TabPanel, 0x1000026Bu)!;
UiElement inFellowship = UiElement.FindDescendant(controller.TabPanel, 0x10000275u)!;
Assert.True(notIn.Visible);
Assert.False(inFellowship.Visible);
}
[Fact]
public void Fellowship_HasFellowship_ShowsInFellowshipFrame()
{
ImportedLayout layout = FixtureLoader.LoadSocialPanelHost();
SocialPanelController? controller = SocialPanelController.Bind(
layout,
MakeCallbacks(fellowship: new RuntimeFellowshipSnapshot { IsInFellowship = true }));
Assert.NotNull(controller);
UiElement notIn = UiElement.FindDescendant(controller!.TabPanel, 0x1000026Bu)!;
UiElement inFellowship = UiElement.FindDescendant(controller.TabPanel, 0x10000275u)!;
Assert.False(notIn.Visible);
Assert.True(inFellowship.Visible);
}
// ── Allegiance empty state (item 4) ─────────────────────────────────────
[Fact]
public void Allegiance_NoProfile_HidesBlocksAndBlanksNames()
{
ImportedLayout layout = FixtureLoader.LoadSocialPanelHost();
SocialPanelController? controller = SocialPanelController.Bind(
layout,
MakeCallbacks(allegiance: new RuntimeAllegianceSnapshot { HasProfile = false }));
Assert.NotNull(controller);
UiElement monarchField = UiElement.FindDescendant(controller!.TabPanel, 0x10000255u)!;
UiElement patronField = UiElement.FindDescendant(controller.TabPanel, 0x1000025Au)!;
Assert.False(monarchField.Visible);
Assert.False(patronField.Visible);
var monarchName = Assert.IsType(
UiElement.FindDescendant(monarchField, 0x10000257u));
var patronName = Assert.IsType(
UiElement.FindDescendant(patronField, 0x1000025Cu));
Assert.Equal(" ", Assert.Single(monarchName.LinesProvider()).Text);
Assert.Equal(" ", Assert.Single(patronName.LinesProvider()).Text);
}
[Fact]
public void Allegiance_HasProfile_ShowsBothBlocks()
{
ImportedLayout layout = FixtureLoader.LoadSocialPanelHost();
SocialPanelController? controller = SocialPanelController.Bind(
layout,
MakeCallbacks(allegiance: new RuntimeAllegianceSnapshot { HasProfile = true }));
Assert.NotNull(controller);
UiElement monarchField = UiElement.FindDescendant(controller!.TabPanel, 0x10000255u)!;
UiElement patronField = UiElement.FindDescendant(controller.TabPanel, 0x1000025Au)!;
Assert.True(monarchField.Visible);
Assert.True(patronField.Visible);
}
// ── Friends/Squelch read-only lists (item 5) ────────────────────────────
[Fact]
public void Friends_PopulatesOneRowPerEntry()
{
var friends = new FriendsState();
friends.Apply(new FriendsUpdate(
FriendsUpdateType.Full,
new List
{
new(1u, "Alice", true, false, System.Array.Empty(), System.Array.Empty()),
new(2u, "Bob", false, false, System.Array.Empty(), System.Array.Empty()),
}));
ImportedLayout layout = FixtureLoader.LoadSocialPanelHost();
SocialPanelController? controller = SocialPanelController.Bind(
layout, MakeCallbacks(friends: friends));
Assert.NotNull(controller);
var listBox = Assert.IsType(
UiElement.FindDescendant(controller!.TabPanel, 0x10000513u) is { } friendsPage
? UiElement.FindDescendant(friendsPage, 0x10000517u)
: null);
Assert.Equal(2, listBox.ViewportForTest!.Children.Count);
}
[Fact]
public void Friends_ReactsToRevisionChange_OnTick()
{
var friends = new FriendsState();
ImportedLayout layout = FixtureLoader.LoadSocialPanelHost();
SocialPanelController? controller = SocialPanelController.Bind(
layout, MakeCallbacks(friends: friends));
Assert.NotNull(controller);
UiElement friendsPage = UiElement.FindDescendant(controller!.TabPanel, 0x10000513u)!;
var listBox = Assert.IsType(
UiElement.FindDescendant(friendsPage, 0x10000517u));
Assert.Equal(0, listBox.ViewportForTest?.Children.Count ?? 0);
friends.Apply(new FriendsUpdate(
FriendsUpdateType.Add,
new List
{
new(1u, "Alice", true, false, System.Array.Empty(), System.Array.Empty()),
}));
controller.Tick();
Assert.Single(listBox.ViewportForTest!.Children);
}
[Fact]
public void Squelch_PopulatesCharacterAndAccountRows()
{
var squelch = new SquelchState();
squelch.Replace(new SquelchDatabase(
new Dictionary(System.StringComparer.OrdinalIgnoreCase) { ["BadAccount"] = 1u },
new Dictionary
{
[7u] = new SquelchInfo("Grief", false, new HashSet()),
},
new SquelchInfo(string.Empty, false, new HashSet())));
ImportedLayout layout = FixtureLoader.LoadSocialPanelHost();
SocialPanelController? controller = SocialPanelController.Bind(
layout, MakeCallbacks(squelch: squelch));
Assert.NotNull(controller);
UiElement squelchPage = UiElement.FindDescendant(controller!.TabPanel, 0x1000054Au)!;
var listBox = Assert.IsType(
UiElement.FindDescendant(squelchPage, 0x1000053Eu));
Assert.Equal(2, listBox.ViewportForTest!.Children.Count);
}
// ── D1: Friends/Squelch action buttons are honest INERT ────────────────
[Fact]
public void FriendsAndSquelchActionButtons_AreClickable_ButHaveNoHandler()
{
ImportedLayout layout = FixtureLoader.LoadSocialPanelHost();
SocialPanelController? controller = SocialPanelController.Bind(layout, MakeCallbacks());
Assert.NotNull(controller);
foreach (uint buttonId in new[] { 0x10000514u, 0x10000515u, 0x10000516u })
{
var button = Assert.IsType(
UiElement.FindDescendant(controller!.TabPanel, buttonId));
Assert.Null(button.OnClick);
}
foreach (uint buttonId in new[] { 0x10000547u, 0x1000054Bu, 0x1000054Cu })
{
var button = Assert.IsType(
UiElement.FindDescendant(controller!.TabPanel, buttonId));
Assert.Null(button.OnClick);
}
}
}