From 5caa2662876da2136b4b15d722da6f76c0aad314 Mon Sep 17 00:00:00 2001 From: Jon Kazama Date: Mon, 17 Aug 2026 23:54:13 +0200 Subject: [PATCH] test(selftest): pin the sidebar active-surface invariant Drives the real Sidebar.Draw and reads the render counter, so a regression in the draw path fails instead of a parallel calculation passing. Three cases: one of two tabs active draws exactly one surface, a null active tab draws zero, and icon-only mode still marks the active row. Zero is a legitimate state, not a failure -- PickMainActiveTab returns null when every tab is popped out, so the invariant is "at most one", not "exactly one". --- HellionChat/Plugin.cs | 1 + .../SelfTests/SidebarActiveSurfaceStep.cs | 91 +++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 HellionChat/SelfTests/SidebarActiveSurfaceStep.cs diff --git a/HellionChat/Plugin.cs b/HellionChat/Plugin.cs index c117d0c..542a5d7 100755 --- a/HellionChat/Plugin.cs +++ b/HellionChat/Plugin.cs @@ -454,6 +454,7 @@ public sealed class Plugin : IAsyncDalamudPlugin new SelfTests.TellResetOnActivateStep(), new SelfTests.CurrentTabCouplingStep(this), new SelfTests.SidebarUnreadDotStep(this), + new SelfTests.SidebarActiveSurfaceStep(this), new SelfTests.UnreadDecisionStep(), new SelfTests.CurrentTabGuidedStep(this), new SelfTests.CardClipPlanStep(this), diff --git a/HellionChat/SelfTests/SidebarActiveSurfaceStep.cs b/HellionChat/SelfTests/SidebarActiveSurfaceStep.cs new file mode 100644 index 0000000..52fd174 --- /dev/null +++ b/HellionChat/SelfTests/SidebarActiveSurfaceStep.cs @@ -0,0 +1,91 @@ +using Dalamud.Bindings.ImGui; +using Dalamud.Plugin.SelfTest; +using HellionChat.Code; + +namespace HellionChat.SelfTests; + +// v1.10.0/C3: the active row gets a surface and an accent bar, so exactly the +// row the user is on must be marked -- and only that one. Drives the real +// Sidebar.Draw and reads the render-observability counter, so a regression in +// the draw path fails rather than a parallel calculation passing. +// +// "At most one", not "exactly one": PickMainActiveTab returns null when every +// tab is popped out, which is a legitimate state with zero marked rows. +internal sealed class SidebarActiveSurfaceStep : ISelfTestStep +{ + private readonly Plugin _plugin; + + public SidebarActiveSurfaceStep(Plugin plugin) => _plugin = plugin; + + public string Name => "Hellion Chat - Sidebar active surface"; + + public SelfTestStepResult RunStep() + { + var sidebar = _plugin.MainWindow.GetSidebarForSelfTest(); + if (sidebar is null) + { + ImGui.Text("Sidebar null"); + SelfTestReport.Append(Name, "FAIL", new[] { "Sidebar null" }); + return SelfTestStepResult.Fail; + } + + var a = NewProbe("Surface Probe A@SelfTest"); + var b = NewProbe("Surface Probe B@SelfTest"); + var list = new List { a, b }; + var width = (float)Plugin.Config.SidebarAutoSwitchThresholdPx + 100f; + var savedWidth = Plugin.Config.SidebarWidth; + + try + { + Plugin.Config.SidebarWidth = 220; + + // (a) one of two tabs is active -> exactly one surface + Tab? active = a; + sidebar.Draw(width, list, ref active); + if (sidebar.LastRenderedActiveSurfaceCount != 1) + return Fail( + $"active tab drew {sidebar.LastRenderedActiveSurfaceCount}, expected 1" + ); + + // (b) no active tab (every tab popped out) -> zero, not a crash + active = null; + sidebar.Draw(width, list, ref active); + if (sidebar.LastRenderedActiveSurfaceCount != 0) + return Fail( + $"null active drew {sidebar.LastRenderedActiveSurfaceCount}, expected 0" + ); + + // (c) icon-only mode still marks the active row + active = b; + sidebar.Draw((float)Plugin.Config.SidebarAutoSwitchThresholdPx - 1f, list, ref active); + if (sidebar.LastRenderedActiveSurfaceCount != 1) + return Fail($"icon-only drew {sidebar.LastRenderedActiveSurfaceCount}, expected 1"); + + SelfTestReport.Append(Name, "PASS", new[] { "1 / 0 / 1 across the three cases" }); + return SelfTestStepResult.Pass; + } + finally + { + Plugin.Config.SidebarWidth = savedWidth; + } + } + + private static Tab NewProbe(string name) => + new() + { + Name = name, + SelectedChannels = new Dictionary + { + [ChatType.Say] = (ChatSourceExt.All, ChatSourceExt.All), + }, + }; + + private SelfTestStepResult Fail(string message) + { + ImGui.Text(message); + SelfTestReport.Append(Name, "FAIL", new[] { message }); + return SelfTestStepResult.Fail; + } + + public void CleanUp() { } +}