fix(vtank): slice 7 fix round C item F3 — discover secondary popups instead of hardcoding

SecondaryPopupPanelsFitTheirOwnBoundsAndEveryBindingResolves was a
[Theory] over a hand-maintained [InlineData] file list — every popup
added or renamed since (buffpicker, metaeditor) already needed a manual
row here, and a forgotten one would leave a new popup completely
uncovered with no test failure to flag it.

Converted to a single [Fact] that discovers popup files with the exact
same Directory.GetFiles(AppContext.BaseDirectory, "mosstank*.xml") glob
AuthoredControlsInTheSameContainerNeverOverlapASibling and
NoButtonAnywhereUsesTheUnrenderableArrowGlyphs already use (excluding
mosstank.xml itself, the main panel covered by its own tests). Expected
width/height per file now lives in an ExpectedPopupBounds dictionary; a
discovered file with no entry fails loudly, naming the file, instead of
silently going unchecked.

Mutation named: temporarily commented out the mosstank-buffpicker.xml
entry and confirmed the test now fails with the new "has no expected
width/height entry" message (rather than the old behavior, where an
unlisted file was simply never discovered) before restoring the entry.

MossTank suite 681 -> 678 (the four [InlineData] cases collapse into one
[Fact] with the same coverage; net -3 test count, not a coverage loss).
Full solution build green; App markup/plugin filter 203/203.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-09-07 15:40:26 +02:00
parent 37055bddce
commit 0724761adb

View file

@ -453,48 +453,80 @@ public sealed class MossTankMarkupContractTests
/// <summary>
/// Fix round A (2026-09-07): the Advanced Options and Loot Editor popups
/// moved out of mosstank.xml into their own plugin panel files. This
/// pins that both files still parse, still declare VTank's own popup
/// footprint (392x300 / 268x300, docs/research/vtank-kb/08-ui-views.md
/// pins that every secondary popup file still parses, still declares
/// VTank's own popup footprint (docs/research/vtank-kb/08-ui-views.md
/// §1's secondary-view table), every child fits its own declared bounds,
/// and every markup binding still resolves against a real
/// <see cref="MossTankPanel"/> property — the same three guarantees
/// <see cref="VtankTabOrderAndEveryBindingResolveAgainstTheLivePanel"/>
/// and <see cref="AuthoredShellFitsTheMinimumCanvasAndEverySizedChildFitsItsParent"/>
/// give the main panel, now split across three files instead of one.
/// give the main panel, now split across multiple files instead of one.
/// Fix round C item F3: the file list used to be a hardcoded
/// <c>[InlineData]</c> set that drifted the moment a popup was added or
/// renamed without a matching row here. It is now discovered with the
/// exact same glob the two "covers a new popup for free" tests already
/// use (<see cref="AuthoredControlsInTheSameContainerNeverOverlapASibling"/>,
/// <see cref="NoButtonAnywhereUsesTheUnrenderableArrowGlyphs"/>),
/// excluding mosstank.xml itself (the main panel, covered by the two
/// tests cited above). A newly discovered popup with no entry in
/// <see cref="ExpectedPopupBounds"/> fails loudly, naming the file,
/// instead of being silently skipped.
/// </summary>
[Theory]
[InlineData("mosstank-advanced.xml", 392f, 476f)]
[InlineData("mosstank-loot-editor.xml", 268f, 300f)]
[InlineData("mosstank-buffpicker.xml", 268f, 236f)]
[InlineData("mosstank-metaeditor.xml", 630f, 160f)]
public void SecondaryPopupPanelsFitTheirOwnBoundsAndEveryBindingResolves(
string fileName, float expectedWidth, float expectedHeight)
private static readonly Dictionary<string, (float Width, float Height)> ExpectedPopupBounds =
new(StringComparer.OrdinalIgnoreCase)
{
["mosstank-advanced.xml"] = (392f, 476f),
["mosstank-loot-editor.xml"] = (268f, 300f),
["mosstank-buffpicker.xml"] = (268f, 236f),
["mosstank-metaeditor.xml"] = (630f, 160f),
};
[Fact]
public void SecondaryPopupPanelsFitTheirOwnBoundsAndEveryBindingResolves()
{
XDocument document = XDocument.Load(
Path.Combine(AppContext.BaseDirectory, fileName));
XElement root = Assert.IsType<XElement>(document.Root);
Assert.Equal(expectedWidth, Number(root, "w"));
Assert.Equal(expectedHeight, Number(root, "h"));
AssertWithinParent(root);
PropertyInfo[] properties = typeof(MossTankPanel).GetProperties(
BindingFlags.Instance | BindingFlags.Public);
var byName = properties.ToDictionary(
static property => property.Name,
StringComparer.Ordinal);
foreach (XAttribute attribute in root.DescendantsAndSelf().Attributes())
string[] popupFileNames = Directory.GetFiles(AppContext.BaseDirectory, "mosstank*.xml")
.Select(static path => Path.GetFileName(path)!)
.Where(static name => !string.Equals(
name, "mosstank.xml", StringComparison.OrdinalIgnoreCase))
.OrderBy(static name => name, StringComparer.Ordinal)
.ToArray();
Assert.NotEmpty(popupFileNames);
foreach (string fileName in popupFileNames)
{
string value = attribute.Value;
if (!value.Contains('{', StringComparison.Ordinal))
continue;
Assert.Matches("^\\{[^{}]+\\}$", value);
string name = value[1..^1];
Assert.True(
byName.ContainsKey(name),
$"Markup binding {value} on <{attribute.Parent?.Name}> in "
+ $"{fileName} has no public MossTankPanel property.");
ExpectedPopupBounds.TryGetValue(fileName, out (float Width, float Height) expected),
$"{fileName} has no expected width/height entry in "
+ $"{nameof(ExpectedPopupBounds)} — add one (VTank's own "
+ "secondary-view footprint from docs/research/vtank-kb/"
+ "08-ui-views.md §1) before this popup can be trusted.");
XDocument document = XDocument.Load(
Path.Combine(AppContext.BaseDirectory, fileName));
XElement root = Assert.IsType<XElement>(document.Root);
Assert.Equal(expected.Width, Number(root, "w"));
Assert.Equal(expected.Height, Number(root, "h"));
AssertWithinParent(root);
foreach (XAttribute attribute in root.DescendantsAndSelf().Attributes())
{
string value = attribute.Value;
if (!value.Contains('{', StringComparison.Ordinal))
continue;
Assert.Matches("^\\{[^{}]+\\}$", value);
string name = value[1..^1];
Assert.True(
byName.ContainsKey(name),
$"Markup binding {value} on <{attribute.Parent?.Name}> in "
+ $"{fileName} has no public MossTankPanel property.");
}
}
}