Files
riojoy/tests/RioJoy.Core.Tests/Plasma/PlasmaDisplayTests.cs
T
CydandClaude Fable 5 ad7ac19ab2 feedback: game-to-cockpit endpoint (pipe/UDP lamps+plasma), rumble lamp flash
Phase 9: FeedbackPipeServer (\\.\pipe\riojoy-feedback) + loopback UDP share a
forgiving text line protocol into FeedbackRouter; CoalescingLampScheduler rate-
governs the 9600-baud link; plasma finally wired into activation (greeting,
teardown blank, PlasmaDisplay write lock); ViGEm FeedbackReceived drives
RumbleLampAdapter. Per-profile Feedback config, docs/FEEDBACK.md, 425 tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-31 19:31:03 -05:00

80 lines
2.5 KiB
C#

using RioJoy.Core.Plasma;
using RioJoy.Core.Tests.Serial;
using Xunit;
namespace RioJoy.Core.Tests.Plasma;
public class PlasmaDisplayTests
{
private static byte[][] PosTextChunks(string text, byte x = 0, byte y = 0, byte attr = 0, byte font = 0)
{
(byte rx, byte ry, byte rfont, int len) = PlasmaCommands.ResolvePosText(text, x, y, font);
return new[]
{
PlasmaCommands.CursorX(rx),
PlasmaCommands.CursorY(ry),
PlasmaCommands.FontAttr(attr),
PlasmaCommands.Font(rfont),
PlasmaCommands.Text(text[..len]),
};
}
[Fact]
public async Task PosTextAsync_EmitsThePosTextSequenceInOrder()
{
var transport = new FakeTransport();
var display = new PlasmaDisplay(transport);
await display.PosTextAsync("VIPER 1-1").WithTimeout();
foreach (byte[] expected in PosTextChunks("VIPER 1-1"))
Assert.Equal(expected, await transport.NextWriteAsync());
}
[Fact]
public async Task PosTextAsync_EmptyText_WritesNothing()
{
var transport = new FakeTransport();
var display = new PlasmaDisplay(transport);
await display.PosTextAsync("").WithTimeout();
Assert.False(transport.Writes.TryRead(out _));
}
[Fact]
public async Task ClearAsync_WritesTheClearCommand()
{
var transport = new FakeTransport();
var display = new PlasmaDisplay(transport);
await display.ClearAsync().WithTimeout();
Assert.Equal(PlasmaCommands.Clear(), await transport.NextWriteAsync());
}
[Fact]
public async Task PosTextAsync_ConcurrentCalls_DoNotInterleave()
{
// Without the write lock, B's cursor/font fragments land between A's five
// writes and corrupt the ESC stream. Gate A's first write so B has every
// chance to sneak in, then assert the ten writes arrive as A's five
// followed by B's five.
var transport = new GatedTransport();
var display = new PlasmaDisplay(transport);
Task a = display.PosTextAsync("AAAA");
Task b = display.PosTextAsync("BBBB");
transport.Open();
await Task.WhenAll(a, b).WithTimeout();
var writes = new List<byte[]>();
for (int i = 0; i < 10; i++)
writes.Add(await transport.NextWriteAsync());
byte[][] expected = PosTextChunks("AAAA").Concat(PosTextChunks("BBBB")).ToArray();
for (int i = 0; i < expected.Length; i++)
Assert.Equal(expected[i], writes[i]);
}
}