ChatVM gains an OnInterfaceText hook + ShowInterfaceText(text), the
App-layer composition wires it to RuntimeCommunicationState.AddText,
and ChatCommandRouter routes every retail-0x1A command refusal through
it instead of the chat log's 0x00 sink. UI.Abstractions still never
references Runtime directly; unwired hosts (headless, tests) fall back
to the chat log tagged ClientLocal so no text is ever silently lost.
Reclassified per register row AP-183 (DoChannelList/On/Off, DoAllegiance,
DoHouseAvailableList — the last also corrected to retail's own bad-house-
type string instead of a synthesized "Usage:" line) and newly wired two
sites that previously showed nothing at all (DoStupidChannelHack's bare
legacy-channel-verb refusal, DoReply's message-but-no-last-teller
refusal). The generic bad-args fallback now resolves WeenieErrorMessages
0x026 ("That is not a valid command.", retail's HandleFailureEvent(0x26))
instead of synthesizing "Usage: {Usage}". DoSpeaker/DoEndurance/DoTitle
are untouched — already correct at 0x00.
Also closes #367 (DoHelp's "Unknown command" fallback and the degenerate-
prefix refusal now reach the SpewBox too) and retires register row
AP-186, whose own filing proposed exactly this seam shape.
Full Release suite: 12,542 passed / 4 skipped / 0 failed (baseline
12,466/4/0 at ff2784ea).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
170 lines
5.3 KiB
C#
170 lines
5.3 KiB
C#
using System.Numerics;
|
|
using AcDream.Core.Chat;
|
|
using AcDream.UI.Abstractions.Panels.Chat;
|
|
|
|
namespace AcDream.UI.Abstractions.Tests.Panels.Chat;
|
|
|
|
/// <summary>
|
|
/// Phase J Tier 2: <see cref="ChatVM"/> tracks the last OUTGOING tell
|
|
/// target (for <c>/retell</c>) and exposes optional FpsProvider /
|
|
/// PositionProvider callbacks the panel uses to render
|
|
/// <c>/framerate</c> and <c>/loc</c> client-side commands without
|
|
/// reaching into render-loop state.
|
|
/// </summary>
|
|
public sealed class ChatVMRetellAndProvidersTests
|
|
{
|
|
[Fact]
|
|
public void BorrowedCommandTargetsRemainLiveAfterOneViewModelDisposes()
|
|
{
|
|
var log = new ChatLog();
|
|
using var targets = new ChatCommandTargetState(log);
|
|
var first = new ChatVM(log, commandTargets: targets);
|
|
using var second = new ChatVM(log, commandTargets: targets);
|
|
|
|
first.Dispose();
|
|
log.OnTellReceived("Bestie", "incoming", 0x50000001u, logTextType: 0x03u);
|
|
|
|
Assert.Equal("Bestie", second.LastIncomingTellSender);
|
|
}
|
|
|
|
[Fact]
|
|
public void ResetSessionTargets_ClearsReplyAndRetellWithoutClearingTranscript()
|
|
{
|
|
var log = new ChatLog();
|
|
var vm = new ChatVM(log);
|
|
log.OnTellReceived("Bestie", "incoming", 0x50000001u, logTextType: 0x03u);
|
|
log.OnSelfSent(ChatKind.Tell, "outgoing", logTextType: 0x04u, targetOrChannel: "Caith");
|
|
Assert.NotNull(vm.LastIncomingTellSender);
|
|
Assert.NotNull(vm.LastOutgoingTellTarget);
|
|
|
|
vm.ResetSessionTargets();
|
|
|
|
Assert.Null(vm.LastIncomingTellSender);
|
|
Assert.Null(vm.LastOutgoingTellTarget);
|
|
Assert.Equal(2, log.Count);
|
|
}
|
|
|
|
[Fact]
|
|
public void LastOutgoingTellTarget_StartsNull()
|
|
{
|
|
var log = new ChatLog();
|
|
var vm = new ChatVM(log);
|
|
Assert.Null(vm.LastOutgoingTellTarget);
|
|
}
|
|
|
|
[Fact]
|
|
public void OnSelfSentTell_PopulatesLastOutgoingTellTarget()
|
|
{
|
|
var log = new ChatLog();
|
|
var vm = new ChatVM(log);
|
|
|
|
log.OnSelfSent(ChatKind.Tell, "hi there", logTextType: 0x04u, targetOrChannel: "Caith");
|
|
|
|
Assert.Equal("Caith", vm.LastOutgoingTellTarget);
|
|
// Inbound-tell tracker must NOT pick up an outgoing echo —
|
|
// the SenderGuid==0 discriminator separates the two paths.
|
|
Assert.Null(vm.LastIncomingTellSender);
|
|
}
|
|
|
|
[Fact]
|
|
public void IncomingTell_DoesNotTouchOutgoingTarget()
|
|
{
|
|
var log = new ChatLog();
|
|
var vm = new ChatVM(log);
|
|
|
|
log.OnTellReceived("Bestie", "psst", senderGuid: 0x5000_0042, logTextType: 0x03u);
|
|
|
|
Assert.Equal("Bestie", vm.LastIncomingTellSender);
|
|
Assert.Null(vm.LastOutgoingTellTarget);
|
|
}
|
|
|
|
[Fact]
|
|
public void OutgoingThenIncoming_TracksBothIndependently()
|
|
{
|
|
var log = new ChatLog();
|
|
var vm = new ChatVM(log);
|
|
|
|
log.OnSelfSent(ChatKind.Tell, "hi", logTextType: 0x04u, targetOrChannel: "Caith");
|
|
log.OnTellReceived("Bestie", "psst", senderGuid: 0x5000_0042, logTextType: 0x03u);
|
|
|
|
Assert.Equal("Caith", vm.LastOutgoingTellTarget);
|
|
Assert.Equal("Bestie", vm.LastIncomingTellSender);
|
|
}
|
|
|
|
[Fact]
|
|
public void ShowFps_WithProvider_AppendsFormattedLine()
|
|
{
|
|
var log = new ChatLog();
|
|
var vm = new ChatVM(log) { FpsProvider = () => 144.2f };
|
|
|
|
vm.ShowFps();
|
|
|
|
var entry = Assert.Single(log.Snapshot());
|
|
Assert.Equal(ChatKind.System, entry.Kind);
|
|
Assert.Equal("Framerate: 144.2 FPS", entry.Text);
|
|
}
|
|
|
|
[Fact]
|
|
public void ShowFps_NoProvider_StillProducesDiagnosticLine()
|
|
{
|
|
var log = new ChatLog();
|
|
var vm = new ChatVM(log);
|
|
|
|
vm.ShowFps();
|
|
|
|
var entry = Assert.Single(log.Snapshot());
|
|
Assert.Contains("provider unavailable", entry.Text);
|
|
}
|
|
|
|
[Fact]
|
|
public void ShowLocation_WithProvider_AppendsFormattedLine()
|
|
{
|
|
var log = new ChatLog();
|
|
var vm = new ChatVM(log) { PositionProvider = () => new Vector3(123.4f, 567.8f, 60f) };
|
|
|
|
vm.ShowLocation();
|
|
|
|
var entry = Assert.Single(log.Snapshot());
|
|
Assert.Equal("Location: (123.4, 567.8, 60.0)", entry.Text);
|
|
}
|
|
|
|
[Fact]
|
|
public void ShowLocation_NoProvider_StillProducesDiagnosticLine()
|
|
{
|
|
var log = new ChatLog();
|
|
var vm = new ChatVM(log);
|
|
|
|
vm.ShowLocation();
|
|
|
|
Assert.Contains("provider unavailable", log.Snapshot()[0].Text);
|
|
}
|
|
|
|
// ── Issue #363: ShowInterfaceText / OnInterfaceText seam ────────────
|
|
|
|
[Fact]
|
|
public void ShowInterfaceText_WithHook_InvokesHook_NeverTouchesTheChatLog()
|
|
{
|
|
var log = new ChatLog();
|
|
var received = new List<string>();
|
|
var vm = new ChatVM(log) { OnInterfaceText = received.Add };
|
|
|
|
vm.ShowInterfaceText("Someone must @tell you first!");
|
|
|
|
Assert.Equal("Someone must @tell you first!", Assert.Single(received));
|
|
Assert.Empty(log.Snapshot());
|
|
}
|
|
|
|
[Fact]
|
|
public void ShowInterfaceText_NoHook_FallsBackToChatLog_TaggedClientLocal()
|
|
{
|
|
var log = new ChatLog();
|
|
var vm = new ChatVM(log);
|
|
|
|
vm.ShowInterfaceText("Someone must @tell you first!");
|
|
|
|
var entry = Assert.Single(log.Snapshot());
|
|
Assert.Equal("Someone must @tell you first!", entry.Text);
|
|
Assert.Equal((uint)RetailLogTextType.ClientLocal, entry.LogTextType);
|
|
Assert.Equal(ChatKind.System, entry.Kind);
|
|
}
|
|
}
|