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] ← skipped by factory /// track (Type-3) [0x10000012] /// inputBar (Type-3) [0x10000013] /// menu (Type-3) [0x10000014] /// input (Type-12, no media) [0x10000016] ← skipped by factory /// 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 = 3, 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. ctrl!.Menu.OnChannelChanged!.Invoke(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); } }