test(D.2b): conformance polish — table-driven slice asserts + BOM-safe loader
Fix 1: replace 3 copy-paste meter blocks in VitalsTree_MetersHaveExpectedSliceIds
with a single table-driven loop — a 4th meter is now a one-liner and failures
name the failing meter id directly.
Fix 2: FixtureLoader now reads the fixture as bytes and strips the UTF-8 BOM
(EF BB BF) before passing the span to JsonSerializer, so a BOM-bearing fixture
file never causes a spurious JsonReaderException.
Fix 3: add [Trait("Category", "Conformance")] at the class level so conformance
tests are selectable by category filter.
Fix 4: add missing <param name="layoutId"> doc tag to LayoutImporter.ImportInfos.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
3567135a04
commit
2b653b8fc0
3 changed files with 23 additions and 35 deletions
|
|
@ -129,6 +129,8 @@ public static class LayoutImporter
|
||||||
/// ElementInfo tree (no widgets). Exposed for fixture generation + conformance tests.
|
/// ElementInfo tree (no widgets). Exposed for fixture generation + conformance tests.
|
||||||
/// Returns null if the layout is missing.
|
/// Returns null if the layout is missing.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="dats">The dat collection to read the LayoutDesc from.</param>
|
||||||
|
/// <param name="layoutId">The LayoutDesc dat id to read.</param>
|
||||||
public static ElementInfo? ImportInfos(DatCollection dats, uint layoutId)
|
public static ElementInfo? ImportInfos(DatCollection dats, uint layoutId)
|
||||||
{
|
{
|
||||||
var ld = dats.Get<LayoutDesc>(layoutId);
|
var ld = dats.Get<LayoutDesc>(layoutId);
|
||||||
|
|
|
||||||
|
|
@ -29,9 +29,14 @@ public static class FixtureLoader
|
||||||
if (!File.Exists(fixturePath))
|
if (!File.Exists(fixturePath))
|
||||||
throw new FileNotFoundException($"Vitals fixture not found at: {fixturePath}");
|
throw new FileNotFoundException($"Vitals fixture not found at: {fixturePath}");
|
||||||
|
|
||||||
var json = File.ReadAllText(fixturePath, System.Text.Encoding.UTF8);
|
var bytes = File.ReadAllBytes(fixturePath);
|
||||||
var root = JsonSerializer.Deserialize<ElementInfo>(json, _opts)
|
// Strip UTF-8 BOM (EF BB BF) if present so JsonSerializer.Deserialize<T>(ReadOnlySpan<byte>)
|
||||||
?? throw new InvalidOperationException("Failed to deserialize vitals fixture.");
|
// does not reject the first byte.
|
||||||
|
ReadOnlySpan<byte> span = bytes;
|
||||||
|
if (span.Length >= 3 && span[0] == 0xEF && span[1] == 0xBB && span[2] == 0xBF)
|
||||||
|
span = span[3..];
|
||||||
|
var root = JsonSerializer.Deserialize<AcDream.App.UI.Layout.ElementInfo>(span, _opts)
|
||||||
|
?? throw new InvalidOperationException($"fixture deserialized to null: {fixturePath}");
|
||||||
|
|
||||||
return LayoutImporter.Build(root, _ => (0u, 0, 0), null);
|
return LayoutImporter.Build(root, _ => (0u, 0, 0), null);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ namespace AcDream.App.Tests.UI.Layout;
|
||||||
///
|
///
|
||||||
/// Sprite ids sourced from <c>docs/research/2026-06-15-layoutdesc-format.md §11</c>.
|
/// Sprite ids sourced from <c>docs/research/2026-06-15-layoutdesc-format.md §11</c>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Trait("Category", "Conformance")]
|
||||||
public class LayoutConformanceTests
|
public class LayoutConformanceTests
|
||||||
{
|
{
|
||||||
// ── Test 1: Three meters at expected rects ────────────────────────────────
|
// ── Test 1: Three meters at expected rects ────────────────────────────────
|
||||||
|
|
@ -58,40 +59,20 @@ public class LayoutConformanceTests
|
||||||
{
|
{
|
||||||
var layout = FixtureLoader.LoadVitals();
|
var layout = FixtureLoader.LoadVitals();
|
||||||
|
|
||||||
// Health bar
|
// Columns: MeterId, then 6 slice ids in order:
|
||||||
{
|
// BackLeft, BackTile, BackRight, FrontLeft, FrontTile, FrontRight
|
||||||
var elem = layout.FindElement(0x100000E6u);
|
(uint MeterId, uint[] Slices)[] cases =
|
||||||
var m = Assert.IsType<UiMeter>(elem);
|
[
|
||||||
Assert.Equal(0x0600747Eu, m.BackLeft);
|
(0x100000E6u, [0x0600747Eu, 0x0600747Fu, 0x06007480u, 0x06007481u, 0x06007482u, 0x06007483u]), // health
|
||||||
Assert.Equal(0x0600747Fu, m.BackTile);
|
(0x100000ECu, [0x06007484u, 0x06007485u, 0x06007486u, 0x06007487u, 0x06007488u, 0x06007489u]), // stamina
|
||||||
Assert.Equal(0x06007480u, m.BackRight);
|
(0x100000EEu, [0x0600748Au, 0x0600748Bu, 0x0600748Cu, 0x0600748Du, 0x0600748Eu, 0x0600748Fu]), // mana
|
||||||
Assert.Equal(0x06007481u, m.FrontLeft);
|
];
|
||||||
Assert.Equal(0x06007482u, m.FrontTile);
|
|
||||||
Assert.Equal(0x06007483u, m.FrontRight);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stamina bar
|
foreach (var (meterId, s) in cases)
|
||||||
{
|
{
|
||||||
var elem = layout.FindElement(0x100000ECu);
|
var m = Assert.IsType<UiMeter>(layout.FindElement(meterId));
|
||||||
var m = Assert.IsType<UiMeter>(elem);
|
Assert.Equal(s[0], m.BackLeft); Assert.Equal(s[1], m.BackTile); Assert.Equal(s[2], m.BackRight);
|
||||||
Assert.Equal(0x06007484u, m.BackLeft);
|
Assert.Equal(s[3], m.FrontLeft); Assert.Equal(s[4], m.FrontTile); Assert.Equal(s[5], m.FrontRight);
|
||||||
Assert.Equal(0x06007485u, m.BackTile);
|
|
||||||
Assert.Equal(0x06007486u, m.BackRight);
|
|
||||||
Assert.Equal(0x06007487u, m.FrontLeft);
|
|
||||||
Assert.Equal(0x06007488u, m.FrontTile);
|
|
||||||
Assert.Equal(0x06007489u, m.FrontRight);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mana bar
|
|
||||||
{
|
|
||||||
var elem = layout.FindElement(0x100000EEu);
|
|
||||||
var m = Assert.IsType<UiMeter>(elem);
|
|
||||||
Assert.Equal(0x0600748Au, m.BackLeft);
|
|
||||||
Assert.Equal(0x0600748Bu, m.BackTile);
|
|
||||||
Assert.Equal(0x0600748Cu, m.BackRight);
|
|
||||||
Assert.Equal(0x0600748Du, m.FrontLeft);
|
|
||||||
Assert.Equal(0x0600748Eu, m.FrontTile);
|
|
||||||
Assert.Equal(0x0600748Fu, m.FrontRight);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue