A comment that reads "MUST stay in lockstep with TryGetActiveCrossfade (K8)" helps nobody outside the plan that used to have a K8 in it, and the plans are not in this repo. Same for "Spec FR-4", "plan §B.2", "Sub-Task 4.4" and the F/R/M/A/S round codes scattered through the style engine and the self-tests. Personal names go too. "tester feedback from Jin (v1.4.7)" and "Flo decision 2026-06-15" carry the reason fine without naming anyone -- the version and the reason are the parts a reader can act on, and a public repo should not need a cast list to be read. The rule applied throughout: keep the why, drop the reference. Version numbers stay, since those resolve through the changelog. 77 files. ChunkUtil also carried 281 lines of commented-out code -- an older ToChunks variant and two helpers with no callers, inherited and never removed. Deleted; git remembers them.
126 lines
4.6 KiB
C#
126 lines
4.6 KiB
C#
using Dalamud.Bindings.ImGui;
|
|
using Dalamud.Plugin.SelfTest;
|
|
|
|
namespace HellionChat.SelfTests;
|
|
|
|
// CurrentTab is coupled to MainWindow.ActiveTab (no longer the fixed index-0
|
|
// Tabs lookup). Asserts ReferenceEquals between the two, with false-green
|
|
// defenses: (1) empty-config exercises the getter's fallback; (2) null ActiveTab
|
|
// opens the window so the Draw-seed sets it and retries via Waiting (bounded so a
|
|
// never-drawn window cannot hang a batch); (3) a victim tab at index 0 makes a
|
|
// regressed index-0 getter return the victim (!= ActiveTab) and fail. Also checks
|
|
// the ResetActiveTabIfRemoved reference no-op branch.
|
|
internal sealed class CurrentTabCouplingStep : ISelfTestStep
|
|
{
|
|
private readonly Plugin _plugin;
|
|
private bool _forcedOpen;
|
|
private int _waitFrames;
|
|
|
|
public CurrentTabCouplingStep(Plugin plugin)
|
|
{
|
|
_plugin = plugin;
|
|
}
|
|
|
|
public string Name => "Hellion Chat - CurrentTab couples to active tab";
|
|
|
|
public SelfTestStepResult RunStep()
|
|
{
|
|
// Empty-config edge: actually exercise the getter's empty-fallback (it must
|
|
// return a fresh Tab, not null/throw) rather than an unconditional pass.
|
|
if (Plugin.Config.Tabs.Count == 0)
|
|
{
|
|
if (_plugin.CurrentTab is null)
|
|
{
|
|
ImGui.Text("Empty-config getter returned null instead of a fallback Tab.");
|
|
return SelfTestStepResult.Fail;
|
|
}
|
|
|
|
ImGui.Text("No tabs configured; getter returns the empty-fallback Tab.");
|
|
return SelfTestStepResult.Pass;
|
|
}
|
|
|
|
// /xlperf usually runs without the window drawn, so ActiveTab can be null
|
|
// on the first pass. Open the window so the Draw-seed sets it, retry next
|
|
// frame, and assert unconditionally once it is non-null. Bounded so a
|
|
// never-drawn window cannot hang a batch run.
|
|
if (_plugin.MainWindow.ActiveTab is null)
|
|
{
|
|
if (!_plugin.MainWindow.IsOpen)
|
|
{
|
|
_plugin.MainWindow.Toggle();
|
|
_forcedOpen = true;
|
|
}
|
|
|
|
if (++_waitFrames > 300)
|
|
{
|
|
RestoreWindow();
|
|
ImGui.Text(
|
|
"MainWindow never drew a seed within 300 frames; coupling not asserted."
|
|
);
|
|
return SelfTestStepResult.Pass;
|
|
}
|
|
|
|
ImGui.Text("Opening window so the draw-seed can set ActiveTab; retrying...");
|
|
return SelfTestStepResult.Waiting;
|
|
}
|
|
|
|
try
|
|
{
|
|
// Insert a victim at index 0: a regressed index-0 getter would return
|
|
// THIS instead of ActiveTab, so ReferenceEquals would catch it.
|
|
var victim = new Tab { Name = "selftest-coupling-victim" };
|
|
// Insert shifts every index; the worker's DropOldestTempTab holds
|
|
// TabsListLock across its index lookup and removal.
|
|
lock (_plugin.TabsListLock)
|
|
Plugin.Config.Tabs.Insert(0, victim);
|
|
try
|
|
{
|
|
if (!ReferenceEquals(_plugin.CurrentTab, _plugin.MainWindow.ActiveTab))
|
|
{
|
|
ImGui.Text("CurrentTab is not the same reference as ActiveTab");
|
|
return SelfTestStepResult.Fail;
|
|
}
|
|
if (ReferenceEquals(_plugin.CurrentTab, victim))
|
|
{
|
|
ImGui.Text("CurrentTab returned the index-0 victim (getter still index-based)");
|
|
return SelfTestStepResult.Fail;
|
|
}
|
|
|
|
// Reference no-op: resetting against a tab that is NOT the active
|
|
// one must leave the active reference untouched.
|
|
var activeBefore = _plugin.MainWindow.ActiveTab;
|
|
_plugin.MainWindow.ResetActiveTabIfRemoved(victim);
|
|
if (!ReferenceEquals(_plugin.MainWindow.ActiveTab, activeBefore))
|
|
{
|
|
ImGui.Text("ResetActiveTabIfRemoved changed the active tab on a non-match");
|
|
return SelfTestStepResult.Fail;
|
|
}
|
|
|
|
return SelfTestStepResult.Pass;
|
|
}
|
|
finally
|
|
{
|
|
lock (_plugin.TabsListLock)
|
|
Plugin.Config.Tabs.Remove(victim);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
RestoreWindow();
|
|
}
|
|
}
|
|
|
|
private void RestoreWindow()
|
|
{
|
|
if (_forcedOpen && _plugin.MainWindow.IsOpen)
|
|
_plugin.MainWindow.Toggle();
|
|
_forcedOpen = false;
|
|
}
|
|
|
|
public void CleanUp()
|
|
{
|
|
RestoreWindow();
|
|
_waitFrames = 0;
|
|
}
|
|
}
|