refactor(ui): own retained controller lifetimes
This commit is contained in:
parent
921c388e2c
commit
5d9e98c118
21 changed files with 373 additions and 35 deletions
|
|
@ -82,7 +82,15 @@ public class SelectedObjectControllerTests
|
|||
=> SelectedObjectController.Bind(
|
||||
layout,
|
||||
subscribeSelectionChanged: h => SelectionHandler = h,
|
||||
unsubscribeSelectionChanged: h =>
|
||||
{
|
||||
if (SelectionHandler == h) SelectionHandler = null;
|
||||
},
|
||||
subscribeHealthChanged: h => HealthHandler = h,
|
||||
unsubscribeHealthChanged: h =>
|
||||
{
|
||||
if (HealthHandler == h) HealthHandler = null;
|
||||
},
|
||||
isHealthTarget: g => HealthTargetMap.TryGetValue(g, out var v) && v,
|
||||
name: g => NameMap.TryGetValue(g, out var v) ? v : null,
|
||||
healthPercent: g => HealthMap.TryGetValue(g, out var v) ? v : 1f,
|
||||
|
|
@ -403,4 +411,23 @@ public class SelectedObjectControllerTests
|
|||
h.FireSelection(null);
|
||||
Assert.Equal(0f, healthMeterEl.Fill() ?? 0f);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Dispose_UnsubscribesBothHandlers_AndIsIdempotent()
|
||||
{
|
||||
var (layout, _, _, healthMeterEl) = FakeLayout();
|
||||
var h = new Harness();
|
||||
h.HealthTargetMap[0xAA09u] = true;
|
||||
var controller = h.Bind(layout);
|
||||
|
||||
controller.Dispose();
|
||||
controller.Dispose();
|
||||
h.FireSelection(0xAA09u);
|
||||
h.FireHealth(0xAA09u, 0.5f);
|
||||
|
||||
Assert.Null(h.SelectionHandler);
|
||||
Assert.Null(h.HealthHandler);
|
||||
Assert.False(healthMeterEl.Visible);
|
||||
Assert.Empty(h.QueryHealthCalls);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -98,6 +98,28 @@ public class ToolbarControllerTests
|
|||
Assert.Equal(0x5002u, slots[Row1[2]].Cell.ItemId); // rebound on ItemAdded
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Dispose_UnsubscribesDeferredRebind_AndIsIdempotent()
|
||||
{
|
||||
var (layout, slots, _) = FakeToolbar();
|
||||
var repo = new ClientObjectTable();
|
||||
var shortcuts = new List<PlayerDescriptionParser.ShortcutEntry>
|
||||
{ new(Index: 2, ObjectGuid: 0x5002u, SpellId: 0, Layer: 0) };
|
||||
var controller = ToolbarController.Bind(layout, repo, () => shortcuts,
|
||||
iconIds: (_,_,_,_,_) => 0x88u, useItem: _ => { });
|
||||
|
||||
controller.Dispose();
|
||||
controller.Dispose();
|
||||
repo.AddOrUpdate(new ClientObject
|
||||
{
|
||||
ObjectId = 0x5002u,
|
||||
WeenieClassId = 1u,
|
||||
IconId = 0x06005678u,
|
||||
});
|
||||
|
||||
Assert.Equal(0u, slots[Row1[2]].Cell.ItemId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Click_emitsUseForBoundItem()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -179,6 +179,28 @@ public sealed class RetailWindowManagerTests
|
|||
Assert.Equal(1, secondController.DisposeCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AttachController_AfterRegistration_DeliversVisibilityAndOwnsDisposal()
|
||||
{
|
||||
var root = new UiRoot { Width = 800, Height = 600 };
|
||||
var frame = new UiPanel { Width = 100, Height = 100 };
|
||||
root.AddChild(frame);
|
||||
RetailWindowHandle handle = root.RegisterWindow("inventory", frame);
|
||||
var controller = new RecordingController();
|
||||
|
||||
root.WindowManager.AttachController("inventory", controller);
|
||||
|
||||
Assert.Same(controller, handle.Controller);
|
||||
Assert.Equal(1, controller.ShownCount);
|
||||
Assert.Throws<InvalidOperationException>(() =>
|
||||
root.WindowManager.AttachController("inventory", new RecordingController()));
|
||||
|
||||
root.WindowManager.Dispose();
|
||||
root.WindowManager.Dispose();
|
||||
Assert.Equal(1, controller.HiddenCount);
|
||||
Assert.Equal(1, controller.DisposeCount);
|
||||
}
|
||||
|
||||
private sealed class RecordingController : IRetainedPanelController
|
||||
{
|
||||
public int ShownCount { get; private set; }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
using System.Collections.Generic;
|
||||
using AcDream.App.UI;
|
||||
|
||||
namespace AcDream.App.Tests.UI;
|
||||
|
||||
public sealed class RetainedPanelControllerGroupTests
|
||||
{
|
||||
[Fact]
|
||||
public void Lifecycle_ForwardsInOwnershipOrder_AndDisposesInReverseExactlyOnce()
|
||||
{
|
||||
var calls = new List<string>();
|
||||
var first = new RecordingController("first", calls);
|
||||
var second = new RecordingController("second", calls);
|
||||
var group = new RetainedPanelControllerGroup(first, second);
|
||||
var focus = new UiPanel();
|
||||
|
||||
group.OnShown();
|
||||
group.OnDescendantFocusChanged(focus);
|
||||
group.OnHidden();
|
||||
group.Dispose();
|
||||
group.Dispose();
|
||||
|
||||
Assert.Equal(
|
||||
[
|
||||
"first:shown", "second:shown",
|
||||
"first:focus", "second:focus",
|
||||
"second:hidden", "first:hidden",
|
||||
"second:dispose", "first:dispose",
|
||||
], calls);
|
||||
}
|
||||
|
||||
private sealed class RecordingController(
|
||||
string name,
|
||||
List<string> calls) : IRetainedPanelController
|
||||
{
|
||||
public void OnShown() => calls.Add($"{name}:shown");
|
||||
public void OnHidden() => calls.Add($"{name}:hidden");
|
||||
public void OnDescendantFocusChanged(UiElement? focusedDescendant)
|
||||
=> calls.Add($"{name}:focus");
|
||||
public void Dispose() => calls.Add($"{name}:dispose");
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue