using System.Collections.Generic;
using AcDream.App.UI;
using AcDream.App.UI.Layout;
using AcDream.Core.Chat;
using AcDream.UI.Abstractions;
using AcDream.UI.Abstractions.Panels.Chat;
namespace AcDream.App.Tests.UI.Layout;
///
/// Smoke tests for — no dats, no GL.
///
/// Building the Type-12 "skipped" elements via the pure
/// path is the correct approach: we build a synthetic info tree that reflects the
/// real chat layout hierarchy (root → transcript panel + input bar as Type-3
/// containers, with Type-12 children for transcript + input, plus a Type-3 track
/// and menu), call to get the widget tree
/// (Type-12 children skipped, Type-3 parents created), then call
/// which reads rects from the info tree
/// and places behavioral widgets under the parent containers.
///
public class ChatWindowControllerTests
{
// ── Null-resolve helper (no GL needed) ─────────────────────────────────
private static (uint, int, int) NoTex(uint _) => (0u, 0, 0);
// ── Capture bus — records every Publish call ────────────────────────────
private sealed class CaptureBus : ICommandBus
{
public readonly List Published = new();
public void Publish(T cmd) where T : notnull => Published.Add(cmd!);
}
// ── Synthetic element tree matching the real chat layout topology ────────
///
/// Build a minimal synthetic ElementInfo tree that mirrors the real chat
/// layout (0x21000006) with enough fidelity for Bind to succeed:
/// root (Type-3)
/// transcriptPanel (Type-3) [0x10000010]
/// transcript (Type-12, no media) [0x10000011] ← built as UiText by factory; Bind binds in place
/// track (Type-3) [0x10000012] ← Type-3 in test (not Type-11); Bind skips scrollbar bind
/// inputBar (Type-3) [0x10000013]
/// menu (Type-6) [0x10000014]
/// input (Type-12, no media) [0x10000016] ← built as UiText by factory; Bind removes + replaces with UiField
/// send (Type-3) [0x10000019]
/// maxmin (Type-3) [0x1000046F]
///
private static (ElementInfo rootInfo, ImportedLayout layout, ChatVM vm) BuildTestTree()
{
var transcriptNode = new ElementInfo
{
Id = 0x10000011u, Type = 12, // Type-12, no media → skipped by factory
X = 16, Y = 0, Width = 458, Height = 74,
};
var trackNode = new ElementInfo
{
Id = 0x10000012u, Type = 3,
X = 474, Y = 6, Width = 16, Height = 68,
};
var transcriptPanel = new ElementInfo
{
Id = 0x10000010u, Type = 3, X = 0, Y = 9, Width = 490, Height = 74,
};
transcriptPanel.Children.Add(transcriptNode);
transcriptPanel.Children.Add(trackNode);
var menuNode = new ElementInfo
{
Id = 0x10000014u, Type = 6, X = 0, Y = 0, Width = 46, Height = 17,
};
var inputNode = new ElementInfo
{
Id = 0x10000016u, Type = 12, // Type-12, no media → skipped by factory
X = 46, Y = 0, Width = 398, Height = 17,
};
var sendNode = new ElementInfo
{
Id = 0x10000019u, Type = 3, X = 444, Y = 0, Width = 46, Height = 17,
};
var inputBar = new ElementInfo
{
Id = 0x10000013u, Type = 3, X = 0, Y = 83, Width = 490, Height = 17,
};
inputBar.Children.Add(menuNode);
inputBar.Children.Add(inputNode);
inputBar.Children.Add(sendNode);
var maxMinNode = new ElementInfo
{
Id = 0x1000046Fu, Type = 3, X = 474, Y = 0, Width = 16, Height = 16,
};
var root = new ElementInfo
{
Id = 0x1000000Eu, Type = 3, Width = 490, Height = 100,
};
root.Children.Add(transcriptPanel);
root.Children.Add(inputBar);
root.Children.Add(maxMinNode);
var layout = LayoutImporter.Build(root, NoTex, null);
var vm = new ChatVM(new ChatLog());
return (root, layout, vm);
}
// ── Test 1: Bind returns non-null with the minimal tree ──────────────────
[Fact]
public void Bind_Returns_NonNull_OnValidTree()
{
var (rootInfo, layout, vm) = BuildTestTree();
var bus = new CaptureBus();
var ctrl = ChatWindowController.Bind(rootInfo, layout, vm, () => bus, null, null, NoTex);
Assert.NotNull(ctrl);
}
// ── Test 2: Transcript is placed as a child of the transcript panel ──────
[Fact]
public void Bind_Transcript_IsChildOfTranscriptPanel()
{
var (rootInfo, layout, vm) = BuildTestTree();
var bus = new CaptureBus();
var ctrl = ChatWindowController.Bind(rootInfo, layout, vm, () => bus, null, null, NoTex);
Assert.NotNull(ctrl);
var panel = layout.FindElement(0x10000010u);
Assert.NotNull(panel);
// The transcript widget must be a child of the transcript panel.
Assert.Contains(ctrl!.Transcript, panel!.Children);
}
// ── Test 3: Input is placed as a child of the input bar ─────────────────
[Fact]
public void Bind_Input_IsChildOfInputBar()
{
var (rootInfo, layout, vm) = BuildTestTree();
var bus = new CaptureBus();
var ctrl = ChatWindowController.Bind(rootInfo, layout, vm, () => bus, null, null, NoTex);
Assert.NotNull(ctrl);
var bar = layout.FindElement(0x10000013u);
Assert.NotNull(bar);
Assert.Contains(ctrl!.Input, bar!.Children);
}
// ── Test 4: Input.OnSubmit publishes SendChatCmd via the capture bus ─────
[Fact]
public void Bind_InputSubmit_PublishesSendChatCmd()
{
var (rootInfo, layout, vm) = BuildTestTree();
var bus = new CaptureBus();
var ctrl = ChatWindowController.Bind(rootInfo, layout, vm, () => bus, null, null, NoTex);
Assert.NotNull(ctrl);
ctrl!.Input.OnSubmit!.Invoke("hello world");
// ChatCommandRouter.Submit should have published a SendChatCmd.
Assert.Single(bus.Published);
var cmd = Assert.IsType(bus.Published[0]);
Assert.Equal("hello world", cmd.Text);
}
// ── Test 5: Channel change updates the channel used by subsequent submits ─
[Fact]
public void Bind_ChannelChange_UpdatesSubmitChannel()
{
var (rootInfo, layout, vm) = BuildTestTree();
var bus = new CaptureBus();
var ctrl = ChatWindowController.Bind(rootInfo, layout, vm, () => bus, null, null, NoTex);
Assert.NotNull(ctrl);
// Switch channel to General via the generic OnSelect (payload is ChatChannelKind).
ctrl!.Menu.OnSelect!.Invoke((object?)ChatChannelKind.General);
ctrl.Input.OnSubmit!.Invoke("hey all");
Assert.Single(bus.Published);
var cmd = Assert.IsType(bus.Published[0]);
Assert.Equal(ChatChannelKind.General, cmd.Channel);
}
// ── Test 6: Bind returns null when required elements are absent ──────────
[Fact]
public void Bind_Returns_Null_WhenTranscriptPanelMissing()
{
// Build a layout that is missing the transcript panel entirely.
var root = new ElementInfo { Id = 0x1000000Eu, Type = 3, Width = 490, Height = 100 };
// No children → TranscriptPanelId and InputBarId are absent from the widget tree.
var layout = LayoutImporter.Build(root, NoTex, null);
var vm = new ChatVM(new ChatLog());
var bus = new CaptureBus();
var ctrl = ChatWindowController.Bind(root, layout, vm, () => bus, null, null, NoTex);
Assert.Null(ctrl);
}
}