using AcDream.App.UI;
using AcDream.App.UI.Layout;
namespace AcDream.App.Tests.UI.Layout;
///
/// Unit tests for Fix C: per-element dat FontDid resolver in .
///
///
/// cannot be constructed in pure unit tests (GL + dat required),
/// so these tests verify the plumbing using null/non-null identity and sentinel resolvers:
///
/// - Null resolver → DatFont unchanged (backward-compat: same as before Fix C).
/// - Resolver present, FontDid == 0 → resolver NOT called; DatFont == global datFont.
/// - Resolver returns null for an id (font missing) → DatFont falls back to global datFont.
/// - Controller sets DatFont after build → controller value wins.
/// - threads fontResolve to the factory.
///
///
///
public class DatWidgetFactoryFontResolveTests
{
private static (uint, int, int) NoTex(uint _) => (0, 0, 0);
// ── Test 1: null fontResolve → DatFont == datFont (backward-compat) ─────
///
/// When fontResolve is null (the live GameWindow path), BuildText must NOT
/// touch DatFont beyond setting it to the global datFont. Null in → null out.
///
[Fact]
public void NullFontResolve_DatFont_EqualsGlobalFont()
{
var info = new ElementInfo { Type = 12, Width = 100, Height = 20, FontDid = 0x40000001u };
// datFont=null, fontResolve=null → backward-compat: DatFont == null
var t = Assert.IsType(DatWidgetFactory.Create(info, NoTex, null, fontResolve: null));
Assert.Null(t.DatFont);
}
// ── Test 2: FontDid == 0 → resolver NOT called ────────────────────────────
///
/// When the element has FontDid == 0, the fontResolve delegate must NOT be
/// invoked (the element has no dat font; fall through to global datFont).
///
[Fact]
public void FontDidZero_ResolverNotCalled()
{
var info = new ElementInfo { Type = 12, Width = 100, Height = 20, FontDid = 0u };
bool called = false;
UiDatFont? Resolver(uint id) { called = true; return null; }
// FontDid=0 → resolver should never fire, even though one is provided.
Assert.IsType(DatWidgetFactory.Create(info, NoTex, null, fontResolve: Resolver));
Assert.False(called, "fontResolve must NOT be called when FontDid == 0");
}
// ── Test 3: resolver returns null (font missing) → fallback to datFont ───
///
/// When the element has a non-zero FontDid but the resolver returns null
/// (font not found in dats), DatFont must fall back to the shared global datFont.
/// Since datFont is null in unit tests, this verifies the fallback chain.
///
[Fact]
public void ResolverReturnsNull_FallsBackToGlobalFont()
{
const uint SomeFontDid = 0x40000005u;
var info = new ElementInfo { Type = 12, Width = 100, Height = 20, FontDid = SomeFontDid };
int callCount = 0;
UiDatFont? Resolver(uint id) { callCount++; return null; } // always returns null (font missing)
var t = Assert.IsType(DatWidgetFactory.Create(info, NoTex, datFont: null, fontResolve: Resolver));
// Resolver was called exactly once for this element.
Assert.Equal(1, callCount);
// Fallback: DatFont == global datFont (null in unit tests).
Assert.Null(t.DatFont);
}
// ── Test 4: resolver called with correct FontDid ──────────────────────────
///
/// When fontResolve is provided and FontDid is non-zero, the resolver is called
/// with the element's exact FontDid value.
///
[Fact]
public void ResolverCalledWithElementFontDid()
{
const uint ExpectedFontDid = 0x40000002u;
var info = new ElementInfo { Type = 12, Width = 100, Height = 20, FontDid = ExpectedFontDid };
uint? capturedId = null;
UiDatFont? Resolver(uint id) { capturedId = id; return null; }
DatWidgetFactory.Create(info, NoTex, datFont: null, fontResolve: Resolver);
Assert.Equal(ExpectedFontDid, capturedId);
}
// ── Test 5: controller DatFont override wins over build-time value ───────
///
/// A controller that calls FindElement and sets DatFont afterward MUST override
/// whatever the factory set at build time. This is the backward-compat guarantee.
/// The DatFont property is settable — a controller can override it after build.
///
[Fact]
public void ControllerDatFontOverride_WinsOverBuildTimeFont()
{
const uint SomeFontDid = 0x40000003u;
var info = new ElementInfo { Type = 12, Width = 100, Height = 20, FontDid = SomeFontDid };
UiDatFont? Resolver(uint _) => null; // returns null → build-time DatFont == null
var t = Assert.IsType(DatWidgetFactory.Create(info, NoTex, datFont: null, fontResolve: Resolver));
Assert.Null(t.DatFont); // build-time value set by factory
// Controller simulated override: set DatFont to null explicitly.
// The real guarantee is that DatFont is a settable property.
// (We can't construct a real UiDatFont here — it requires GL + dats.)
t.DatFont = null; // controller overrides after build
Assert.Null(t.DatFont);
// ✓ DatFont is settable — controller override mechanism is in place.
}
// ── Test 6: LayoutImporter.Build threads fontResolve to factory ──────────
///
/// When is given a fontResolve, it must pass
/// it to for EVERY element in the tree.
/// Verified by checking that a Type-12 child with FontDid triggers the resolver.
///
[Fact]
public void LayoutImporter_Build_ThreadsFontResolve_ToFactory()
{
const uint FontDid = 0x40000004u;
var root = new ElementInfo { Id = 1u, Type = 3, Width = 200, Height = 100 };
var child = new ElementInfo { Id = 2u, Type = 12, Width = 100, Height = 20, FontDid = FontDid };
root.Children.Add(child);
int resolveCallCount = 0;
UiDatFont? Resolver(uint id) { resolveCallCount++; return null; }
var layout = LayoutImporter.Build(root, NoTex, datFont: null, fontResolve: Resolver);
// The child element has a non-zero FontDid — resolver must have been called for it.
Assert.True(resolveCallCount > 0, "fontResolve was not called for the child element");
// The child widget was built (findable in the layout).
Assert.NotNull(layout.FindElement(2u));
}
// ── Test 7: Meter element also receives element font from resolver ────────
///
/// Type-7 (UiMeter) elements with a FontDid must also go through the resolver.
/// Verified by checking the resolver fires for a meter element with a non-zero FontDid.
///
[Fact]
public void FontResolve_CalledForMeter_WhenFontDidPresent()
{
const uint MeterFontDid = 0x40000006u;
var meter = new ElementInfo { Type = 7, Width = 150, Height = 16, FontDid = MeterFontDid };
int callCount = 0;
UiDatFont? Resolver(uint id) { callCount++; return null; }
var w = DatWidgetFactory.Create(meter, NoTex, datFont: null, fontResolve: Resolver);
var m = Assert.IsType(w);
// Resolver was called for the meter element's FontDid.
Assert.True(callCount > 0, "fontResolve was not called for meter element");
// Meter DatFont: resolver returned null, so DatFont falls back to global datFont (null).
Assert.Null(m.DatFont);
}
// ── Test 8: LayoutImporter.BuildFromInfos signature is backward-compat ────
///
/// without fontResolve (default null)
/// must still work for all callers that don't pass it — verifies the optional
/// parameter default is correct and no existing callers broke.
///
[Fact]
public void BuildFromInfos_WithoutFontResolve_WorksAsBeforeFix()
{
var root = new ElementInfo { Id = 10u, Type = 3, Width = 200, Height = 100 };
var child = new ElementInfo { Id = 11u, Type = 12, Width = 100, Height = 20, FontDid = 0x40000001u };
// Call without fontResolve (the old signature shape).
var layout = LayoutImporter.BuildFromInfos(root, new[] { child }, NoTex, datFont: null);
var t = Assert.IsType(layout.FindElement(11u));
// No resolver → DatFont == global datFont (null in unit tests).
Assert.Null(t.DatFont);
}
}