acdream/tests/AcDream.Core.Tests/Chat/WeenieErrorMessagesTests.cs
Erik 3501194083 fix(chat): /help client-side handler + System dedup + ThatIsNotAValidCommand template
Phase J follow-up after a 2026-04-25 trace where typing /help
produced two identical "Unknown command: help" lines (ACE fires the
text via both GameMessageSystemChat 0xF7E0 and a paired
CommunicationTransientString 0x02EB), and the server's WeenieError
0x0026 trailer rendered cryptically as "WeenieError 0x0026".

Three small changes:

1. WeenieErrorMessages: add 0x0026 ThatIsNotAValidCommand ->
   "That is not a valid command." Plus 0x0414 / 0x050F that Phase J
   already added are now covered by tests too.

2. ChatLog.OnSystemMessage dedup. Track last system text + arrival
   time; if a second identical text shows up within 1 second,
   suppress. ACE's two-path send (gag warnings, command errors,
   etc.) collapses to a single chat line. Long bursts of repeated
   text still skip the duplicates without resetting the timer.

3. Client-side /help and /clear in ChatPanel. Intercepted BEFORE
   the parser passes to the server bus:
   - /help, /?, /h (case-insensitive) -> render local cheat-sheet
     listing acdream's slash prefixes via ChatLog.OnSystemMessage.
     Avoids the round-trip to ACE that produced the duplicate
     "Unknown command: help" lines AND gives users discoverability.
   - /clear, /cls -> drains the chat log so the panel starts empty.

   New ChatVM.ShowSystemMessage() + ChatVM.Clear() expose the
   minimum surface the panel needs to dispatch client-only feedback
   without coupling the panel to ChatLog directly.

12 new tests:
- 3 WeenieErrorMessages template adds (0x0026 / 0x0414 / 0x050F).
- 4 ChatLog dedup cases (immediate dup, different text, triplet,
  bookended-by-different-text).
- 5 ChatPanel client-command cases (/help, 3 alias variants,
  /clear).

Solution total: 1033 green (243 Core.Net + 130 UI + 660 Core),
0 warnings.

Acceptance: type /help in chat -> local help banner appears, no
server round-trip, no "Unknown command: help" duplicates. Type
/clear -> chat tail empty. Welcome banner + WeenieError-templated
"You are not in an allegiance!" / "You do not belong to a
Fellowship." continue rendering once each.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 21:22:07 +02:00

139 lines
4.7 KiB
C#

using AcDream.Core.Chat;
namespace AcDream.Core.Tests.Chat;
/// <summary>
/// Tests for <see cref="WeenieErrorMessages"/>. The retail client showed
/// these as plain-language strings; we mirror that via templated lookup.
/// Filed after the 2026-04-25 live launch where the user saw cryptic
/// "WeenieError 0x051B" in chat for what was actually a friendly login
/// notification.
/// </summary>
public sealed class WeenieErrorMessagesTests
{
// ── known codes — informational, parameterised ───────────────────
[Fact]
public void Format_YouHaveEnteredChannel_SubstitutesParam()
{
// 0x051B = WeenieErrorWithString.YouHaveEnteredThe_Channel.
// Template "You have entered the _ channel." with `_` placeholder.
Assert.Equal(
"You have entered the General channel.",
WeenieErrorMessages.Format(0x051B, "General"));
}
[Fact]
public void Format_YouHaveEnteredChannel_WorksForEachChannelName()
{
Assert.Equal("You have entered the Trade channel.", WeenieErrorMessages.Format(0x051B, "Trade"));
Assert.Equal("You have entered the LFG channel.", WeenieErrorMessages.Format(0x051B, "LFG"));
Assert.Equal("You have entered the Roleplay channel.",WeenieErrorMessages.Format(0x051B, "Roleplay"));
}
[Fact]
public void Format_YouHaveLeftChannel_SubstitutesParam()
{
Assert.Equal(
"You have left the General channel.",
WeenieErrorMessages.Format(0x051C, "General"));
}
// ── known codes — informational, no parameter ────────────────────
[Fact]
public void Format_TurbineChatIsEnabled_NoParamForm()
{
// 0x051D came in WeenieError (no param) form at login.
Assert.Equal(
"Turbine Chat is enabled.",
WeenieErrorMessages.Format(0x051D, param: null));
}
// ── known codes — error-level ────────────────────────────────────
[Fact]
public void Format_CharacterNotAvailable_NoParam()
{
// 0x052B fired by the server when a Tell target lookup fails
// (e.g. the user typed "/t je, hello" → server got "je," → no
// character). Should read like the retail message.
Assert.Equal(
"That person is not available now.",
WeenieErrorMessages.Format(0x052B, param: null));
}
[Fact]
public void Format_TradeComplete()
{
Assert.Equal("Trade Complete!", WeenieErrorMessages.Format(0x0529, null));
}
[Fact]
public void Format_ThatIsNotAValidCommand()
{
// 0x0026 fires on /-prefixed text that ACE's command parser
// can't resolve. Filed after a 2026-04-25 trace where /help
// produced cryptic "WeenieError 0x0026" lines.
Assert.Equal(
"That is not a valid command.",
WeenieErrorMessages.Format(0x0026, null));
}
[Fact]
public void Format_YouAreNotInAllegiance()
{
Assert.Equal(
"You are not in an allegiance!",
WeenieErrorMessages.Format(0x0414, null));
}
[Fact]
public void Format_YouDoNotBelongToAFellowship()
{
Assert.Equal(
"You do not belong to a Fellowship.",
WeenieErrorMessages.Format(0x050F, null));
}
// ── unknown codes — graceful fallback preserves debug info ───────
[Fact]
public void Format_UnknownCode_NoParam_FallsBackToHexForm()
{
Assert.Equal("WeenieError 0xABCD", WeenieErrorMessages.Format(0xABCD, null));
}
[Fact]
public void Format_UnknownCode_WithParam_FallsBackToColonForm()
{
Assert.Equal(
"WeenieError 0xDEAD: Mana Stone",
WeenieErrorMessages.Format(0xDEAD, "Mana Stone"));
}
[Fact]
public void Format_UnknownCode_EmptyParam_StaysAsHexOnly()
{
// Empty string param shouldn't add a stray colon.
Assert.Equal("WeenieError 0xCAFE", WeenieErrorMessages.Format(0xCAFE, ""));
}
// ── parameterised templates with non-trivial params ──────────────
[Fact]
public void Format_HearListAdded_SubstitutesParam()
{
Assert.Equal(
"Caith has been added to the list of people you can hear.",
WeenieErrorMessages.Format(0x0521, "Caith"));
}
[Fact]
public void Format_FailToAffectCannotBeHarmed_SubstitutesParam()
{
Assert.Equal(
"You fail to affect Drudge because they cannot be harmed!",
WeenieErrorMessages.Format(0x004F, "Drudge"));
}
}