feat(ui): #14 IPanelRenderer widget extension - TextColored, Checkbox, Combo, InputTextSubmit, BeginTable, etc.
Adds 14 widget signatures to IPanelRenderer + ImGuiPanelRenderer impl: TextColored, CollapsingHeader, TreeNode/TreePop, Checkbox, Button, Combo, SliderFloat, PlotLines, BeginTable/TableNextColumn/EndTable, InputTextSubmit (Enter-key submit), Spacing, Dummy, TextWrapped. InputTextSubmit uses ImGuiInputTextFlags.EnterReturnsTrue and clears the buffer + emits via `out submitted` on the frame Enter is pressed. PlotLines passes `ref values[0]` with empty-array guard. CollapsingHeader defaultOpen=true uses ImGuiTreeNodeFlags.DefaultOpen (= 0x20). FakePanelRenderer test double records (Method, Args) tuples and exposes knobs to drive ref/out values. 17 new tests dispatch through IPanelRenderer (not the concrete fake) so tests fail to compile when the interface itself lacks a method - real RED -> GREEN signal. Tests: 26 -> 43 in UI.Abstractions.Tests. Total solution 881 green. Foundation for Phase I.2 (DebugPanel) and I.4 (ChatPanel input field). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
196f883c10
commit
b131514d51
4 changed files with 683 additions and 0 deletions
308
tests/AcDream.UI.Abstractions.Tests/IPanelRendererWidgetTests.cs
Normal file
308
tests/AcDream.UI.Abstractions.Tests/IPanelRendererWidgetTests.cs
Normal file
|
|
@ -0,0 +1,308 @@
|
|||
using System.Numerics;
|
||||
|
||||
namespace AcDream.UI.Abstractions.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Tests the shape of the new Phase I.1 widget extensions on
|
||||
/// <see cref="IPanelRenderer"/>. Every test dispatches through the
|
||||
/// interface (not the concrete fake) so the test only compiles when
|
||||
/// the interface actually has the method — that's the RED signal for
|
||||
/// TDD when adding new widgets in the future.
|
||||
/// </summary>
|
||||
public sealed class IPanelRendererWidgetTests
|
||||
{
|
||||
private static (IPanelRenderer Iface, FakePanelRenderer Fake) New()
|
||||
{
|
||||
var f = new FakePanelRenderer();
|
||||
return (f, f);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TextColored_RecordsCallWithColorAndText()
|
||||
{
|
||||
var (r, fake) = New();
|
||||
var color = new Vector4(0.9f, 0.4f, 0.2f, 1f);
|
||||
|
||||
r.TextColored(color, "ouch");
|
||||
|
||||
var call = Assert.Single(fake.Calls);
|
||||
Assert.Equal("TextColored", call.Method);
|
||||
Assert.Equal(color, call.Args[0]);
|
||||
Assert.Equal("ouch", call.Args[1]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CollapsingHeader_RecordsLabelAndDefaultOpen_ReturnsBackingValue()
|
||||
{
|
||||
var (r, fake) = New();
|
||||
fake.CollapsingHeaderNextReturn = false;
|
||||
|
||||
bool open = r.CollapsingHeader("Player Info");
|
||||
Assert.False(open);
|
||||
|
||||
var call = Assert.Single(fake.Calls);
|
||||
Assert.Equal("CollapsingHeader", call.Method);
|
||||
Assert.Equal("Player Info", call.Args[0]);
|
||||
Assert.Equal(true, call.Args[1]); // default
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CollapsingHeader_PassesDefaultOpenFalse()
|
||||
{
|
||||
var (r, fake) = New();
|
||||
|
||||
r.CollapsingHeader("Diagnostics", defaultOpen: false);
|
||||
|
||||
var call = Assert.Single(fake.Calls);
|
||||
Assert.Equal(false, call.Args[1]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TreeNode_AndTreePop_FormPair()
|
||||
{
|
||||
var (r, fake) = New();
|
||||
fake.TreeNodeNextReturn = true;
|
||||
|
||||
if (r.TreeNode("Inventory"))
|
||||
{
|
||||
r.Text("sword");
|
||||
r.TreePop();
|
||||
}
|
||||
|
||||
Assert.Equal(3, fake.Calls.Count);
|
||||
Assert.Equal("TreeNode", fake.Calls[0].Method);
|
||||
Assert.Equal("Inventory", fake.Calls[0].Args[0]);
|
||||
Assert.Equal("Text", fake.Calls[1].Method);
|
||||
Assert.Equal("TreePop", fake.Calls[2].Method);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Checkbox_AppliesNextValueToRefAndRecordsBefore()
|
||||
{
|
||||
var (r, fake) = New();
|
||||
fake.CheckboxNextReturn = true;
|
||||
fake.CheckboxNextValue = true;
|
||||
|
||||
bool flag = false;
|
||||
bool changed = r.Checkbox("DumpMotion", ref flag);
|
||||
|
||||
Assert.True(changed);
|
||||
Assert.True(flag); // fake mutated the ref
|
||||
var call = Assert.Single(fake.Calls);
|
||||
Assert.Equal("Checkbox", call.Method);
|
||||
Assert.Equal("DumpMotion", call.Args[0]);
|
||||
Assert.Equal(false, call.Args[1]); // value-before-the-call captured in trace
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Button_ReturnsBackingValueAndRecordsLabel()
|
||||
{
|
||||
var (r, fake) = New();
|
||||
fake.ButtonNextReturn = true;
|
||||
|
||||
bool clicked = r.Button("Cycle Weather");
|
||||
|
||||
Assert.True(clicked);
|
||||
var call = Assert.Single(fake.Calls);
|
||||
Assert.Equal("Button", call.Method);
|
||||
Assert.Equal("Cycle Weather", call.Args[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Combo_AppliesNextSelectedIndex_AndReturnsChanged()
|
||||
{
|
||||
var (r, fake) = New();
|
||||
fake.ComboNextReturn = true;
|
||||
fake.ComboNextSelectedIndex = 2;
|
||||
|
||||
int idx = 0;
|
||||
var items = new[] { "Dawn", "Noon", "Dusk", "Night" };
|
||||
bool changed = r.Combo("Time", ref idx, items);
|
||||
|
||||
Assert.True(changed);
|
||||
Assert.Equal(2, idx);
|
||||
var call = Assert.Single(fake.Calls);
|
||||
Assert.Equal("Combo", call.Method);
|
||||
Assert.Equal("Time", call.Args[0]);
|
||||
Assert.Equal(0, call.Args[1]);
|
||||
Assert.Same(items, call.Args[2]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SliderFloat_AppliesNextValueAndReturnsChanged()
|
||||
{
|
||||
var (r, fake) = New();
|
||||
fake.SliderFloatNextReturn = true;
|
||||
fake.SliderFloatNextValue = 0.7f;
|
||||
|
||||
float v = 0.1f;
|
||||
bool changed = r.SliderFloat("Sensitivity", ref v, 0f, 1f);
|
||||
|
||||
Assert.True(changed);
|
||||
Assert.Equal(0.7f, v);
|
||||
var call = Assert.Single(fake.Calls);
|
||||
Assert.Equal("SliderFloat", call.Method);
|
||||
Assert.Equal("Sensitivity", call.Args[0]);
|
||||
Assert.Equal(0.1f, call.Args[1]);
|
||||
Assert.Equal(0f, call.Args[2]);
|
||||
Assert.Equal(1f, call.Args[3]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PlotLines_RecordsAllOptionalArgs()
|
||||
{
|
||||
var (r, fake) = New();
|
||||
var values = new[] { 16f, 17f, 16.5f, 16.2f };
|
||||
|
||||
r.PlotLines(
|
||||
"FrameMs",
|
||||
values,
|
||||
count: values.Length,
|
||||
offset: 1,
|
||||
overlay: "60 fps",
|
||||
min: 0f,
|
||||
max: 33f,
|
||||
size: new Vector2(120, 40));
|
||||
|
||||
var call = Assert.Single(fake.Calls);
|
||||
Assert.Equal("PlotLines", call.Method);
|
||||
Assert.Equal("FrameMs", call.Args[0]);
|
||||
Assert.Same(values, call.Args[1]);
|
||||
Assert.Equal(values.Length, call.Args[2]);
|
||||
Assert.Equal(1, call.Args[3]);
|
||||
Assert.Equal("60 fps", call.Args[4]);
|
||||
Assert.Equal(0f, call.Args[5]);
|
||||
Assert.Equal(33f, call.Args[6]);
|
||||
Assert.Equal(new Vector2(120, 40), call.Args[7]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PlotLines_DefaultsApplied_WhenOptionalsOmitted()
|
||||
{
|
||||
var (r, fake) = New();
|
||||
var values = new[] { 1f, 2f, 3f };
|
||||
|
||||
r.PlotLines("FPS", values, count: values.Length);
|
||||
|
||||
var call = Assert.Single(fake.Calls);
|
||||
Assert.Equal(0, call.Args[3]); // offset default
|
||||
Assert.Null(call.Args[4]); // overlay default
|
||||
Assert.Null(call.Args[5]); // min default
|
||||
Assert.Null(call.Args[6]); // max default
|
||||
Assert.Null(call.Args[7]); // size default
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Table_BeginNextColumnEnd_FormSequence()
|
||||
{
|
||||
var (r, fake) = New();
|
||||
|
||||
r.BeginTable("Keybinds", 2);
|
||||
r.TableNextColumn();
|
||||
r.Text("F1");
|
||||
r.TableNextColumn();
|
||||
r.Text("Toggle Info");
|
||||
r.EndTable();
|
||||
|
||||
Assert.Equal(6, fake.Calls.Count);
|
||||
Assert.Equal("BeginTable", fake.Calls[0].Method);
|
||||
Assert.Equal("Keybinds", fake.Calls[0].Args[0]);
|
||||
Assert.Equal(2, fake.Calls[0].Args[1]);
|
||||
Assert.Equal("TableNextColumn", fake.Calls[1].Method);
|
||||
Assert.Equal("Text", fake.Calls[2].Method);
|
||||
Assert.Equal("TableNextColumn", fake.Calls[3].Method);
|
||||
Assert.Equal("Text", fake.Calls[4].Method);
|
||||
Assert.Equal("EndTable", fake.Calls[5].Method);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InputTextSubmit_NoSubmit_ReturnsFalseAndSubmittedNull()
|
||||
{
|
||||
var (r, fake) = New();
|
||||
// InputTextSubmitNextSubmitted left null → the buffer should be untouched
|
||||
// and the out arg should be null.
|
||||
|
||||
string buf = "hel";
|
||||
bool submitted = r.InputTextSubmit("##chat", ref buf, 256, out var output);
|
||||
|
||||
Assert.False(submitted);
|
||||
Assert.Null(output);
|
||||
Assert.Equal("hel", buf);
|
||||
|
||||
var call = Assert.Single(fake.Calls);
|
||||
Assert.Equal("InputTextSubmit", call.Method);
|
||||
Assert.Equal("##chat", call.Args[0]);
|
||||
Assert.Equal("hel", call.Args[1]);
|
||||
Assert.Equal(256, call.Args[2]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InputTextSubmit_OnSubmit_OutputsTextAndClearsBuffer()
|
||||
{
|
||||
var (r, fake) = New();
|
||||
fake.InputTextSubmitNextSubmitted = "hello world";
|
||||
fake.InputTextSubmitNextBufferAfter = string.Empty;
|
||||
|
||||
string buf = "hello world";
|
||||
bool submitted = r.InputTextSubmit("##chat", ref buf, 256, out var output);
|
||||
|
||||
Assert.True(submitted);
|
||||
Assert.Equal("hello world", output);
|
||||
// Contract: on submit the impl clears the buffer for the next frame.
|
||||
Assert.Equal(string.Empty, buf);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Spacing_RecordsCall()
|
||||
{
|
||||
var (r, fake) = New();
|
||||
r.Spacing();
|
||||
var call = Assert.Single(fake.Calls);
|
||||
Assert.Equal("Spacing", call.Method);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Dummy_RecordsSize()
|
||||
{
|
||||
var (r, fake) = New();
|
||||
r.Dummy(new Vector2(8, 12));
|
||||
var call = Assert.Single(fake.Calls);
|
||||
Assert.Equal("Dummy", call.Method);
|
||||
Assert.Equal(new Vector2(8, 12), call.Args[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TextWrapped_RecordsText()
|
||||
{
|
||||
var (r, fake) = New();
|
||||
r.TextWrapped("a long description that wraps onto multiple lines");
|
||||
|
||||
var call = Assert.Single(fake.Calls);
|
||||
Assert.Equal("TextWrapped", call.Method);
|
||||
Assert.Equal("a long description that wraps onto multiple lines", call.Args[0]);
|
||||
}
|
||||
|
||||
// -- Sanity: existing widgets still recorded correctly via the fake -------
|
||||
|
||||
[Fact]
|
||||
public void BeginEndTextSeparatorProgressBar_StillRecorded()
|
||||
{
|
||||
var (r, fake) = New();
|
||||
fake.BeginReturns = true;
|
||||
|
||||
Assert.True(r.Begin("Window"));
|
||||
r.Text("hi");
|
||||
r.SameLine();
|
||||
r.Separator();
|
||||
r.ProgressBar(0.5f, 100f, "50%");
|
||||
r.End();
|
||||
|
||||
Assert.Equal(6, fake.Calls.Count);
|
||||
Assert.Equal("Begin", fake.Calls[0].Method);
|
||||
Assert.Equal("Text", fake.Calls[1].Method);
|
||||
Assert.Equal("SameLine", fake.Calls[2].Method);
|
||||
Assert.Equal("Separator", fake.Calls[3].Method);
|
||||
Assert.Equal("ProgressBar", fake.Calls[4].Method);
|
||||
Assert.Equal("End", fake.Calls[5].Method);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue