The user tested Campaign CH's CODE-COMPLETE build live and reported ten defects (docs/plans/2026-08-09-chat-parity-campaign.md, "User gate — round 1"). Items A-G are fixed here; the remaining three (extra chat windows on 1/2/3/4, resize working in only one corner, transparency/ artifacts) are out of scope for a fix and filed as slice CH6. A. Jump-in-air refusal never fired live: the jump block only ever evaluated input.Jump inside the grounded-charge or already-charging branches. PlayerMovementController now detects the press RISING EDGE while airborne and reports WeenieError.NotGrounded once per press, leaving the grounded charge/fire path untouched. B. ChatVM's invented "[System] " prefix is dropped — retail prints system text bare. [Popup] is unchanged (AP-175). C. SpewBoxController's color is now the user-pinned exact value (1, 1, 0.247, 1), the same bright yellow as an incoming Tell. Register row AP-178 updated: color CLOSES, size/position/font stay open per the user's live report that they still differ. D. Closes #329: PortalTunnelPresentation now emits the portal wait cue unconditionally on every rotation-segment boundary, matching gmSmartBoxUI::UseTime's decompiled else-arm exactly instead of gating on a 5-second hold local transits never reached. PortalWaitNotice Controller now renders it in the same pinned yellow as item C. Register row AP-150 retired. E. Closes #362: new ClientCommandResponses.cs parses and renders the four previously-unhandled inbound GameEvents (ChannelIndex, ChannelList, AvailableHouses, AllegianceInfoResponse), each ported line-for-line from the named-retail decomp's inbound handlers. Register row TS-70 retired. F. ChatWindowController.WrapText now splits on embedded '\n'/'\r\n' first, then word-wraps each segment independently — server text like /help's reply no longer collapses onto one line. G. The chat input field's right edge no longer holds a fixed absolute pixel position across a window resize; Bind now upgrades it to retail edge-mode 1 (UiLayoutPolicy) or the AnchorEdges.Right stretch fallback so it tracks the window's client width instead of overflowing past a narrower resize. Full Release suite: 12,247 passed / 4 skipped / 0 failed (baseline 12,221/4/0 + 26 new tests across items A, E, F, G). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
164 lines
6.1 KiB
C#
164 lines
6.1 KiB
C#
using AcDream.Core.Chat;
|
|
using AcDream.UI.Abstractions.Panels.Chat;
|
|
|
|
namespace AcDream.UI.Abstractions.Tests;
|
|
|
|
public sealed class ChatVMTests
|
|
{
|
|
[Fact]
|
|
public void RecentLines_ReturnsEmpty_ForEmptyLog()
|
|
{
|
|
var log = new ChatLog();
|
|
var vm = new ChatVM(log);
|
|
|
|
Assert.Empty(vm.RecentLines());
|
|
}
|
|
|
|
[Fact]
|
|
public void RecentLines_ReturnsAllEntries_WhenBelowLimit()
|
|
{
|
|
var log = new ChatLog();
|
|
log.OnLocalSpeech(sender: "Caith", text: "hello", senderGuid: 0x5000_0001u, isRanged: false, logTextType: 0x02u);
|
|
log.OnLocalSpeech(sender: "Regal", text: "world", senderGuid: 0x5000_0002u, isRanged: false, logTextType: 0x02u);
|
|
|
|
var vm = new ChatVM(log, displayLimit: 20);
|
|
var lines = vm.RecentLines();
|
|
|
|
Assert.Equal(2, lines.Count);
|
|
Assert.Equal("Caith says, \"hello\"", lines[0]);
|
|
Assert.Equal("Regal says, \"world\"", lines[1]);
|
|
}
|
|
|
|
[Fact]
|
|
public void RecentLines_ReturnsTail_WhenAboveLimit_InOldestFirstOrder()
|
|
{
|
|
var log = new ChatLog();
|
|
for (int i = 0; i < 30; i++)
|
|
log.OnLocalSpeech(sender: "A", text: $"msg{i}", senderGuid: 0x5000_0001u, isRanged: false, logTextType: 0x02u);
|
|
|
|
var vm = new ChatVM(log, displayLimit: 5);
|
|
var lines = vm.RecentLines();
|
|
|
|
// Tail = msg25..msg29 (5 entries, oldest first).
|
|
Assert.Equal(5, lines.Count);
|
|
Assert.Equal("A says, \"msg25\"", lines[0]);
|
|
Assert.Equal("A says, \"msg29\"", lines[4]);
|
|
}
|
|
|
|
[Fact]
|
|
public void FormatEntry_LocalSpeech_RetailStyleSays()
|
|
{
|
|
// Retail format: "Name says, \"text\"" for someone else;
|
|
// "You say, \"text\"" for our own /say (sender == "" or "You").
|
|
var incoming = new ChatEntry(ChatKind.LocalSpeech, "Caith", "hello", 0x5000_0001u, 0);
|
|
Assert.Equal("Caith says, \"hello\"", ChatVM.FormatEntry(incoming));
|
|
|
|
var ownEcho = new ChatEntry(ChatKind.LocalSpeech, "", "hi there", 0, 0);
|
|
Assert.Equal("You say, \"hi there\"", ChatVM.FormatEntry(ownEcho));
|
|
|
|
var ownEchoSubst = new ChatEntry(ChatKind.LocalSpeech, "You", "shouted echo", 0, 0);
|
|
Assert.Equal("You say, \"shouted echo\"", ChatVM.FormatEntry(ownEchoSubst));
|
|
}
|
|
|
|
[Fact]
|
|
public void FormatEntry_RangedSpeech_RetailStyleShouts()
|
|
{
|
|
var incoming = new ChatEntry(ChatKind.RangedSpeech, "Caith", "hello", 0x5000_0001u, 0);
|
|
Assert.Equal("Caith shouts, \"hello\"", ChatVM.FormatEntry(incoming));
|
|
|
|
var ownEcho = new ChatEntry(ChatKind.RangedSpeech, "You", "loud", 0, 0);
|
|
Assert.Equal("You shout, \"loud\"", ChatVM.FormatEntry(ownEcho));
|
|
}
|
|
|
|
[Fact]
|
|
public void FormatEntry_Channel_UsesChannelNameWhenPresent()
|
|
{
|
|
// Friendly name takes precedence — "[Trade] Caith says, \"...\""
|
|
var named = new ChatEntry(ChatKind.Channel, "Caith", "g'day", 0x5000_0001u, 7u)
|
|
{
|
|
ChannelName = "Trade",
|
|
};
|
|
Assert.Equal("[Trade] Caith says, \"g'day\"", ChatVM.FormatEntry(named));
|
|
|
|
// Falls back to "ch {id}" when ChannelName isn't set (legacy
|
|
// path / older callers).
|
|
var unnamed = new ChatEntry(ChatKind.Channel, "Caith", "g'day", 0x5000_0001u, 7u);
|
|
Assert.Equal("[ch 7] Caith says, \"g'day\"", ChatVM.FormatEntry(unnamed));
|
|
}
|
|
|
|
[Fact]
|
|
public void FormatEntry_Tell_RetailStyleTells()
|
|
{
|
|
// SenderGuid != 0 -> incoming whisper -> "Regal tells you, ..."
|
|
var incoming = new ChatEntry(ChatKind.Tell, "Regal", "psst", 0x5000_0002u, 0);
|
|
Assert.Equal("Regal tells you, \"psst\"", ChatVM.FormatEntry(incoming));
|
|
|
|
// SenderGuid == 0 -> our own outbound echo -> Sender carries
|
|
// the target name -> "You tell Regal, ..."
|
|
var ownEcho = new ChatEntry(ChatKind.Tell, "Regal", "psst", 0, 0);
|
|
Assert.Equal("You tell Regal, \"psst\"", ChatVM.FormatEntry(ownEcho));
|
|
}
|
|
|
|
[Fact]
|
|
public void FormatEntry_System_NoSenderShown()
|
|
{
|
|
// Campaign CH user-gate round 1 (item B): retail prints system text
|
|
// bare, with no "[System]" prefix.
|
|
var entry = new ChatEntry(ChatKind.System, Sender: "", "Your spell fizzled!", 0, 0);
|
|
Assert.Equal("Your spell fizzled!", ChatVM.FormatEntry(entry));
|
|
}
|
|
|
|
[Fact]
|
|
public void FormatEntry_Popup_Prefixed()
|
|
{
|
|
var entry = new ChatEntry(ChatKind.Popup, Sender: "", "A door stands before you.", 0, 0);
|
|
Assert.Equal("[Popup] A door stands before you.", ChatVM.FormatEntry(entry));
|
|
}
|
|
|
|
[Fact]
|
|
public void Constructor_ThrowsOnNullLog()
|
|
{
|
|
Assert.Throws<ArgumentNullException>(() => new ChatVM(null!));
|
|
}
|
|
|
|
[Fact]
|
|
public void Constructor_ThrowsOnZeroOrNegativeLimit()
|
|
{
|
|
var log = new ChatLog();
|
|
Assert.Throws<ArgumentOutOfRangeException>(() => new ChatVM(log, displayLimit: 0));
|
|
Assert.Throws<ArgumentOutOfRangeException>(() => new ChatVM(log, displayLimit: -1));
|
|
}
|
|
|
|
[Fact]
|
|
public void RecentLines_ReturnsNewLineData_AfterSubsequentAppend()
|
|
{
|
|
// Confirm the VM isn't caching — each call re-snapshots the log.
|
|
var log = new ChatLog();
|
|
var vm = new ChatVM(log);
|
|
|
|
Assert.Empty(vm.RecentLines());
|
|
|
|
log.OnLocalSpeech("Caith", "hello", 0x5000_0001u, false, logTextType: 0x02u);
|
|
var after = vm.RecentLines();
|
|
Assert.Single(after);
|
|
Assert.Equal("Caith says, \"hello\"", after[0]);
|
|
}
|
|
|
|
[Fact]
|
|
public void ShowSystemMessage_UsesDefaultLogTextType()
|
|
{
|
|
// Corrected 2026-08-09, Opus review of 172c6f9a: ShowSystemMessage
|
|
// is ClientCommandController's general-purpose informational sink
|
|
// (/help, /clear, /framerate, /loc, @version, friends list, ...).
|
|
// Retail types the great majority of that command output 0x00
|
|
// Default (green); 0x1A (bright red) is reserved for genuine
|
|
// refusals, which land separately with CH2's SpewBox routing.
|
|
var log = new ChatLog();
|
|
var vm = new ChatVM(log);
|
|
|
|
vm.ShowSystemMessage("Unknown command: foo");
|
|
|
|
var entry = Assert.Single(log.Snapshot());
|
|
Assert.Equal(0x00u, entry.LogTextType);
|
|
}
|
|
}
|