using AcDream.Core.Chat; using AcDream.UI.Abstractions.Panels.Chat; namespace AcDream.UI.Abstractions.Tests.Panels.Chat; /// /// Phase I.4: when the user submits text via the chat input field, the /// panel must publish a to the command bus. /// We exercise the full Render path with the /// pre-loading a "submitted" string and a recording bus capturing the /// resulting command. /// public sealed class ChatPanelInputTests { private sealed class RecordingBus : ICommandBus { public List Published { get; } = new(); public void Publish(T command) where T : notnull => Published.Add(command); } [Fact] public void Submit_HelpCommand_RendersLocalHelpAndDoesNotPublish() { // Phase J follow-up: client-side commands (/help, /?, /h) are // intercepted before the parser. They render a local cheat-sheet // via ChatLog.OnSystemMessage and do NOT round-trip the server // — that's what prevented the "Unknown command: help" duplicate // ACE was firing back. var log = new ChatLog(); var vm = new ChatVM(log); var panel = new ChatPanel(vm); var bus = new RecordingBus(); var renderer = new FakePanelRenderer { InputTextSubmitNextSubmitted = "/help", InputTextSubmitNextBufferAfter = "", }; panel.Render(new PanelContext(0.016f, bus), renderer); Assert.Empty(bus.Published); var entry = Assert.Single(log.Snapshot()); Assert.Equal(ChatKind.System, entry.Kind); // Help text mentions / and @ equivalence and points at @acehelp // for the server's full command list. Assert.Contains("/tell", entry.Text); Assert.Contains("@acehelp", entry.Text); } [Theory] [InlineData("/?")] [InlineData("/h")] [InlineData("/HELP")] public void Submit_HelpAliases_AlsoRenderLocalHelp(string raw) { var log = new ChatLog(); var vm = new ChatVM(log); var panel = new ChatPanel(vm); var bus = new RecordingBus(); var renderer = new FakePanelRenderer { InputTextSubmitNextSubmitted = raw, InputTextSubmitNextBufferAfter = "", }; panel.Render(new PanelContext(0.016f, bus), renderer); Assert.Empty(bus.Published); Assert.Single(log.Snapshot()); } [Fact] public void Submit_FramerateCommand_PrintsFpsAndDoesNotPublish() { var log = new ChatLog(); var vm = new ChatVM(log) { FpsProvider = () => 60f }; var panel = new ChatPanel(vm); var bus = new RecordingBus(); var renderer = new FakePanelRenderer { InputTextSubmitNextSubmitted = "/framerate", InputTextSubmitNextBufferAfter = "", }; panel.Render(new PanelContext(0.016f, bus), renderer); Assert.Empty(bus.Published); var entry = Assert.Single(log.Snapshot()); Assert.Contains("60.0 FPS", entry.Text); } [Fact] public void Submit_LocCommand_PrintsPositionAndDoesNotPublish() { var log = new ChatLog(); var vm = new ChatVM(log) { PositionProvider = () => new System.Numerics.Vector3(10f, 20f, 30f), }; var panel = new ChatPanel(vm); var bus = new RecordingBus(); var renderer = new FakePanelRenderer { InputTextSubmitNextSubmitted = "@loc", InputTextSubmitNextBufferAfter = "", }; panel.Render(new PanelContext(0.016f, bus), renderer); Assert.Empty(bus.Published); var entry = Assert.Single(log.Snapshot()); Assert.Contains("(10.0, 20.0, 30.0)", entry.Text); } [Fact] public void Submit_AtAcehelp_PassesThroughToSayWithAtIntact() { // Unknown @-verb falls through to the default channel with the // literal "@acehelp" text intact so ACE's CommandManager // intercepts it server-side. We DO publish a SendChatCmd here — // the publish is what carries the message to the server. var log = new ChatLog(); var vm = new ChatVM(log); var panel = new ChatPanel(vm); var bus = new RecordingBus(); var renderer = new FakePanelRenderer { InputTextSubmitNextSubmitted = "@acehelp", InputTextSubmitNextBufferAfter = "", }; panel.Render(new PanelContext(0.016f, bus), renderer); var sendCmd = Assert.IsType(Assert.Single(bus.Published)); Assert.Equal(ChatChannelKind.Say, sendCmd.Channel); Assert.Equal("@acehelp", sendCmd.Text); } [Fact] public void Submit_ClearCommand_DrainsLog_AndDoesNotPublish() { var log = new ChatLog(); log.OnSystemMessage("seed line", chatType: 0); var vm = new ChatVM(log); var panel = new ChatPanel(vm); var bus = new RecordingBus(); var renderer = new FakePanelRenderer { InputTextSubmitNextSubmitted = "/clear", InputTextSubmitNextBufferAfter = "", }; panel.Render(new PanelContext(0.016f, bus), renderer); Assert.Empty(bus.Published); Assert.Empty(log.Snapshot()); } [Fact] public void Submit_PlainText_PublishesSayCommand() { var log = new ChatLog(); var vm = new ChatVM(log); var panel = new ChatPanel(vm); var bus = new RecordingBus(); var renderer = new FakePanelRenderer { InputTextSubmitNextSubmitted = "hello world", InputTextSubmitNextBufferAfter = "", }; panel.Render(new PanelContext(0.016f, bus), renderer); var cmd = Assert.Single(bus.Published); var sendCmd = Assert.IsType(cmd); Assert.Equal(ChatChannelKind.Say, sendCmd.Channel); Assert.Null(sendCmd.TargetName); Assert.Equal("hello world", sendCmd.Text); } [Fact] public void Submit_TellSlashCommand_PublishesTellCommand() { var log = new ChatLog(); var vm = new ChatVM(log); var panel = new ChatPanel(vm); var bus = new RecordingBus(); var renderer = new FakePanelRenderer { InputTextSubmitNextSubmitted = "/t Bestie ping", InputTextSubmitNextBufferAfter = "", }; panel.Render(new PanelContext(0.016f, bus), renderer); var sendCmd = Assert.IsType(Assert.Single(bus.Published)); Assert.Equal(ChatChannelKind.Tell, sendCmd.Channel); Assert.Equal("Bestie", sendCmd.TargetName); Assert.Equal("ping", sendCmd.Text); } [Fact] public void Submit_ReplySlashCommand_UsesLastIncomingTellSender() { var log = new ChatLog(); var vm = new ChatVM(log); log.OnTellReceived("Bestie", "ping", senderGuid: 0x5000_00AAu); var panel = new ChatPanel(vm); var bus = new RecordingBus(); var renderer = new FakePanelRenderer { InputTextSubmitNextSubmitted = "/r back at you", InputTextSubmitNextBufferAfter = "", }; panel.Render(new PanelContext(0.016f, bus), renderer); var sendCmd = Assert.IsType(Assert.Single(bus.Published)); Assert.Equal(ChatChannelKind.Tell, sendCmd.Channel); Assert.Equal("Bestie", sendCmd.TargetName); Assert.Equal("back at you", sendCmd.Text); } [Fact] public void Submit_EmptyOrWhitespace_PublishesNothing() { var log = new ChatLog(); var vm = new ChatVM(log); var panel = new ChatPanel(vm); var bus = new RecordingBus(); var renderer = new FakePanelRenderer { InputTextSubmitNextSubmitted = " ", InputTextSubmitNextBufferAfter = "", }; panel.Render(new PanelContext(0.016f, bus), renderer); Assert.Empty(bus.Published); } [Fact] public void NoSubmit_PublishesNothing() { // Most frames: user is typing or idle; submitted == null. var log = new ChatLog(); var vm = new ChatVM(log); var panel = new ChatPanel(vm); var bus = new RecordingBus(); var renderer = new FakePanelRenderer { InputTextSubmitNextSubmitted = null, }; panel.Render(new PanelContext(0.016f, bus), renderer); Assert.Empty(bus.Published); } [Fact] public void Render_AlwaysCallsInputTextSubmit_ToShowTheField() { var log = new ChatLog(); var vm = new ChatVM(log); var panel = new ChatPanel(vm); var bus = new RecordingBus(); var renderer = new FakePanelRenderer { InputTextSubmitNextSubmitted = null, }; panel.Render(new PanelContext(0.016f, bus), renderer); Assert.Contains(renderer.Calls, c => c.Method == "InputTextSubmit"); } }