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; /// /// Dat-free conformance tests for the committed chat_21000006.json golden fixture. /// Verifies that LayoutImporter.ImportInfos correctly resolves the BaseElement / /// BaseLayoutId inheritance chain for the chat window (LayoutDesc 0x21000006). /// public class ChatLayoutConformanceTests { private static (uint, int, int) NoTex(uint _) => (0, 0, 0); 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 ChatFixture_ResolvesKnownElements() { var root = FixtureLoader.LoadChatInfos(); Assert.NotNull(Find(root, 0x10000011u)); // transcript Assert.NotNull(Find(root, 0x10000016u)); // input Assert.NotNull(Find(root, 0x10000012u)); // scrollbar track Assert.NotNull(Find(root, 0x10000014u)); // channel menu Assert.NotNull(Find(root, 0x10000019u)); // send button Assert.NotNull(Find(root, 0x1000046Fu)); // max/min button } [Fact] public void ChatFixture_ResolvedTypes_MatchRetailRegistry() { var root = FixtureLoader.LoadChatInfos(); Assert.Equal(6u, Find(root, 0x10000014u)!.Type); // Menu Assert.Equal(11u, Find(root, 0x10000012u)!.Type); // Scrollbar Assert.Equal(1u, Find(root, 0x10000019u)!.Type); // Button (Send) Assert.Equal(1u, Find(root, 0x1000046Fu)!.Type); // Button (Max/Min) Assert.Equal(12u, Find(root, 0x10000011u)!.Type); // Text/style-prototype (transcript) Assert.Equal(12u, Find(root, 0x10000016u)!.Type); // Text/style-prototype (input) } [Fact] public void ChatFixture_BuildsSelectableTranscriptAndEditableInputInPlace() { var layout = FixtureLoader.LoadChat(); var transcript = Assert.IsType(layout.FindElement(0x10000011u)); var input = Assert.IsType(layout.FindElement(0x10000016u)); Assert.True(transcript.Selectable); Assert.True(input.Selectable); Assert.True(input.OneLine); Assert.Equal(0x10000016u, input.DatElementId); } [Fact] public void ChatFixture_ScrollbarImportsInheritedMediaRoles() { var layout = FixtureLoader.LoadChat(); var scrollbar = Assert.IsType(layout.FindElement(0x10000012u)); Assert.Equal(0x06004C5Fu, scrollbar.TrackSprite); Assert.Equal(0x06004C60u, scrollbar.ThumbTopSprite); Assert.Equal(0x06004C63u, scrollbar.ThumbSprite); Assert.Equal(0x06004C66u, scrollbar.ThumbBotSprite); Assert.Equal(0x06004C69u, scrollbar.UpSprite); Assert.Equal(0x06004C6Cu, scrollbar.DownSprite); } [Fact] public void ChatFixture_WindowRootCarriesRetailHeightConstraints() { var root = FixtureLoader.LoadChatInfos(); var window = Find(root, 0x1000000Eu)!; Assert.True(window.TryGetEffectiveInteger(0x3Eu, out int minHeight)); Assert.True(window.TryGetEffectiveInteger(0x3Cu, out int maxHeight)); Assert.Equal(100, minHeight); Assert.Equal(360, maxHeight); } [Fact] public void ChatFixture_CroppedDockedRoot_ReflowsContentAcrossMountedWidth() { var infos = FixtureLoader.LoadChatInfos(); var layout = LayoutImporter.Build(infos, NoTex, null); var controller = ChatWindowController.Bind( infos, layout, new ChatVM(new ChatLog()), () => NullCommandBus.Instance, null, null, NoTex); Assert.NotNull(controller); var root = controller!.Root; root.Width = 490; root.Height = 100; root.RebaseChildLayoutBaselines(); root.Width = 615; var transcriptPanel = layout.FindElement(0x10000010u)!; var inputBar = layout.FindElement(0x10000013u)!; transcriptPanel.ApplyAnchor(root.Width, root.Height); inputBar.ApplyAnchor(root.Width, root.Height); Assert.Equal(615f, transcriptPanel.Width); Assert.Equal(615f, inputBar.Width); } [Fact] public void ChatFixture_ChannelCaptionWidth_SurvivesImportedLayoutPass() { var infos = FixtureLoader.LoadChatInfos(); var layout = LayoutImporter.Build(infos, NoTex, null); var controller = ChatWindowController.Bind( infos, layout, new ChatVM(new ChatLog()), () => NullCommandBus.Instance, null, null, NoTex); Assert.NotNull(controller); controller!.Menu.OnSelect!.Invoke(ChatChannelKind.General); float fittedWidth = controller.Menu.Width; controller.Menu.ApplyAnchor(controller.Menu.Parent!.Width, controller.Menu.Parent.Height); Assert.True(fittedWidth > 46f); Assert.Equal(fittedWidth, controller.Menu.Width); Assert.Equal(controller.Menu.Left + fittedWidth, controller.Input.Left); } [Fact] public void ChatMaximize_ResizesOuterFrameByHalfParent_AndRestores() { var infos = FixtureLoader.LoadChatInfos(); var layout = LayoutImporter.Build(infos, NoTex, null); var controller = ChatWindowController.Bind( infos, layout, new ChatVM(new ChatLog()), () => NullCommandBus.Instance, null, null, NoTex)!; var root = new UiRoot { Width = 800, Height = 600 }; RetailWindowHandle handle = RetailWindowFrame.Mount( root, controller.Root, NoTex, new RetailWindowFrame.Options { WindowName = WindowNames.Chat, Chrome = RetailWindowChrome.NineSlice, Left = 12f, Top = 390f, ContentWidth = 490f, ContentHeight = controller.Root.Height, RebaseContentLayout = true, DatConstraintSource = controller.DatWindowInfo, MinWidth = 200f, }); controller.AttachWindow(handle); var maxMin = Assert.IsType(layout.FindElement(0x1000046Fu)); maxMin.OnClick!(); Assert.True(controller.IsMaximized); Assert.Equal(90f, handle.Top); Assert.Equal(370f, handle.Height); // DAT max 360 + 2*5px shared chrome Assert.Equal(RetailUiStateIds.Maximized, maxMin.ActiveRetailStateId); controller.Root.ApplyAnchor(handle.Width, handle.Height); Assert.Equal(360f, controller.Root.Height); maxMin.OnClick!(); Assert.False(controller.IsMaximized); Assert.Equal(390f, handle.Top); Assert.Equal(110f, handle.Height); Assert.Equal(RetailUiStateIds.Minimized, maxMin.ActiveRetailStateId); } [Fact] public void MountedChatScrollbar_ButtonsAndThumbDragDriveTranscriptModel() { var infos = FixtureLoader.LoadChatInfos(); var layout = LayoutImporter.Build(infos, NoTex, null); var controller = ChatWindowController.Bind( infos, layout, new ChatVM(new ChatLog()), () => NullCommandBus.Instance, null, null, NoTex)!; var root = new UiRoot { Width = 800, Height = 600 }; RetailWindowHandle handle = RetailWindowFrame.Mount( root, controller.Root, NoTex, new RetailWindowFrame.Options { WindowName = WindowNames.Chat, Chrome = RetailWindowChrome.NineSlice, Left = 10f, Top = 440f, ContentWidth = 490f, ContentHeight = controller.Root.Height, RebaseContentLayout = true, DatConstraintSource = controller.DatWindowInfo, MinWidth = 200f, }); controller.AttachWindow(handle); UiScrollbar bar = controller.Scrollbar; UiScrollable scroll = controller.Transcript.Scroll; scroll.LineHeight = 16; scroll.SetExtents(contentHeight: 400, viewHeight: 50, preserveEnd: true); Assert.Equal(350, scroll.ScrollY); var screen = bar.ScreenPosition; int centerX = (int)(screen.X + bar.Width / 2f); // Top/decrement button. int upY = (int)(screen.Y + 8f); root.OnMouseDown(UiMouseButton.Left, centerX, upY); root.OnMouseUp(UiMouseButton.Left, centerX, upY); Assert.Equal(334, scroll.ScrollY); // Re-layout must retain the position chosen through the scrollbar. scroll.SetExtents(contentHeight: 400, viewHeight: 50, preserveEnd: true); Assert.Equal(334, scroll.ScrollY); // Drag the thumb toward the top of the track. float trackTop = 16f; float trackLength = bar.Height - 32f; var (thumbY, thumbHeight) = UiScrollbar.ThumbRect(scroll, trackTop, trackLength); int dragStartY = (int)(screen.Y + thumbY + thumbHeight / 2f); int dragEndY = (int)(screen.Y + trackTop + thumbHeight / 2f); root.OnMouseDown(UiMouseButton.Left, centerX, dragStartY); root.OnMouseMove(centerX, dragEndY); root.OnMouseUp(UiMouseButton.Left, centerX, dragEndY); Assert.True(scroll.ScrollY < 334); int draggedPosition = scroll.ScrollY; scroll.SetExtents(contentHeight: 400, viewHeight: 50, preserveEnd: true); Assert.Equal(draggedPosition, scroll.ScrollY); } }