test(D.2b): vitals importer conformance — golden fixture + tree/slice/chrome checks
Job 1: extract LayoutImporter.ImportInfos() (public dat-shell half that returns the resolved ElementInfo tree without building widgets) so fixture generation and conformance tests can call it directly. Import() now delegates to ImportInfos() + Build() — existing 32 Layout tests stay green. Job 2: generate tests/AcDream.App.Tests/UI/Layout/fixtures/vitals_2100006C.json from the real portal.dat via a throwaway [Fact] generator (deleted, not committed). System.Text.Json with IncludeFields=true — ValueTuple serializes as Item1/Item2. Pre-write validation confirmed health meter BackLeft=0x0600747E FrontRight=0x06007483 rect (5,5,150,16). Round-trip deserialization re-validated before writing. Job 3: FixtureLoader.LoadVitals() deserializes the fixture from the test output directory (CopyToOutputDirectory item in csproj) and returns ImportedLayout via LayoutImporter.Build(root, _ => (0,0,0), null) — no dats, no GL. Job 4: LayoutConformanceTests — 3 golden tests (35 asserts total): - VitalsTree_HasThreeMetersAtExpectedRects: 3 meters at x=5, w=150, h=16, y=5/21/37 - VitalsTree_MetersHaveExpectedSliceIds: all 18 back+front slice ids health/stamina/mana - VitalsTree_ChromeCornerHasExpectedSprite: TL corner 0x10000633 → sprite 0x060074C3 Full App suite: 326 pass / 1 skip (pre-existing) / 0 fail. Build: 0 errors, 0 warnings. Throwaway generator not committed (confirmed via git status). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
25be30b1a7
commit
3567135a04
5 changed files with 1238 additions and 14 deletions
38
tests/AcDream.App.Tests/UI/Layout/FixtureLoader.cs
Normal file
38
tests/AcDream.App.Tests/UI/Layout/FixtureLoader.cs
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using AcDream.App.UI.Layout;
|
||||
|
||||
namespace AcDream.App.Tests.UI.Layout;
|
||||
|
||||
/// <summary>
|
||||
/// Loads the committed vitals ElementInfo fixture and builds the widget tree —
|
||||
/// no dats required. The fixture was generated from layout <c>0x2100006C</c>
|
||||
/// via the real portal.dat and serialized with <see cref="System.Text.Json"/>.
|
||||
/// </summary>
|
||||
public static class FixtureLoader
|
||||
{
|
||||
private static readonly JsonSerializerOptions _opts = new()
|
||||
{
|
||||
IncludeFields = true,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Deserializes the committed <c>vitals_2100006C.json</c> fixture (copied to
|
||||
/// the test output directory via the csproj <c>CopyToOutputDirectory</c> item)
|
||||
/// into an <see cref="ElementInfo"/> tree, then builds and returns the
|
||||
/// <see cref="ImportedLayout"/> using a null-returning sprite resolver and no
|
||||
/// dat font — sufficient for conformance checks on tree structure and slice ids.
|
||||
/// </summary>
|
||||
public static ImportedLayout LoadVitals()
|
||||
{
|
||||
var fixturePath = Path.Combine(AppContext.BaseDirectory, "UI", "Layout", "fixtures", "vitals_2100006C.json");
|
||||
if (!File.Exists(fixturePath))
|
||||
throw new FileNotFoundException($"Vitals fixture not found at: {fixturePath}");
|
||||
|
||||
var json = File.ReadAllText(fixturePath, System.Text.Encoding.UTF8);
|
||||
var root = JsonSerializer.Deserialize<ElementInfo>(json, _opts)
|
||||
?? throw new InvalidOperationException("Failed to deserialize vitals fixture.");
|
||||
|
||||
return LayoutImporter.Build(root, _ => (0u, 0, 0), null);
|
||||
}
|
||||
}
|
||||
115
tests/AcDream.App.Tests/UI/Layout/LayoutConformanceTests.cs
Normal file
115
tests/AcDream.App.Tests/UI/Layout/LayoutConformanceTests.cs
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
using AcDream.App.UI;
|
||||
using AcDream.App.UI.Layout;
|
||||
|
||||
namespace AcDream.App.Tests.UI.Layout;
|
||||
|
||||
/// <summary>
|
||||
/// Golden conformance tests for the vitals LayoutDesc importer.
|
||||
/// Uses the committed JSON fixture (<c>vitals_2100006C.json</c>) — no dats, no GL.
|
||||
///
|
||||
/// These tests lock the importer's tree-building (factory dispatch, meter slice
|
||||
/// extraction, rects) against the real portal.dat values captured when the
|
||||
/// fixture was generated. Any regression in <see cref="LayoutImporter"/>,
|
||||
/// <see cref="DatWidgetFactory"/>, or <see cref="ElementReader"/> will surface here.
|
||||
///
|
||||
/// Sprite ids sourced from <c>docs/research/2026-06-15-layoutdesc-format.md §11</c>.
|
||||
/// </summary>
|
||||
public class LayoutConformanceTests
|
||||
{
|
||||
// ── Test 1: Three meters at expected rects ────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// The three vital bars must be UiMeters positioned at x=5, width=150, height=16,
|
||||
/// at y=5 (health), y=21 (stamina), y=37 (mana).
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void VitalsTree_HasThreeMetersAtExpectedRects()
|
||||
{
|
||||
var layout = FixtureLoader.LoadVitals();
|
||||
|
||||
(uint Id, float Y)[] expected =
|
||||
[
|
||||
(0x100000E6u, 5f), // health
|
||||
(0x100000ECu, 21f), // stamina
|
||||
(0x100000EEu, 37f), // mana
|
||||
];
|
||||
|
||||
foreach (var (id, y) in expected)
|
||||
{
|
||||
var elem = layout.FindElement(id);
|
||||
Assert.NotNull(elem);
|
||||
var meter = Assert.IsType<UiMeter>(elem);
|
||||
Assert.Equal(5f, meter.Left);
|
||||
Assert.Equal(y, meter.Top);
|
||||
Assert.Equal(150f, meter.Width);
|
||||
Assert.Equal(16f, meter.Height);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Test 2: All 18 slice ids ──────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// The six back+front 3-slice sprite ids for each of the three meters must
|
||||
/// match the values confirmed from the dat dump (format doc §11).
|
||||
/// This proves the factory's grandchild slice extraction against committed data.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void VitalsTree_MetersHaveExpectedSliceIds()
|
||||
{
|
||||
var layout = FixtureLoader.LoadVitals();
|
||||
|
||||
// Health bar
|
||||
{
|
||||
var elem = layout.FindElement(0x100000E6u);
|
||||
var m = Assert.IsType<UiMeter>(elem);
|
||||
Assert.Equal(0x0600747Eu, m.BackLeft);
|
||||
Assert.Equal(0x0600747Fu, m.BackTile);
|
||||
Assert.Equal(0x06007480u, m.BackRight);
|
||||
Assert.Equal(0x06007481u, m.FrontLeft);
|
||||
Assert.Equal(0x06007482u, m.FrontTile);
|
||||
Assert.Equal(0x06007483u, m.FrontRight);
|
||||
}
|
||||
|
||||
// Stamina bar
|
||||
{
|
||||
var elem = layout.FindElement(0x100000ECu);
|
||||
var m = Assert.IsType<UiMeter>(elem);
|
||||
Assert.Equal(0x06007484u, m.BackLeft);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Test 3: Chrome TL corner sprite ───────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// The top-left chrome corner element (id <c>0x10000633</c>) must be a
|
||||
/// <see cref="UiDatElement"/> whose active media file id is <c>0x060074C3</c>.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void VitalsTree_ChromeCornerHasExpectedSprite()
|
||||
{
|
||||
var layout = FixtureLoader.LoadVitals();
|
||||
|
||||
var elem = layout.FindElement(0x10000633u);
|
||||
Assert.NotNull(elem);
|
||||
var datElem = Assert.IsType<UiDatElement>(elem);
|
||||
var (file, _) = datElem.ActiveMedia();
|
||||
Assert.Equal(0x060074C3u, file);
|
||||
}
|
||||
}
|
||||
1058
tests/AcDream.App.Tests/UI/Layout/fixtures/vitals_2100006C.json
Normal file
1058
tests/AcDream.App.Tests/UI/Layout/fixtures/vitals_2100006C.json
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue