using AcDream.App.UI; using AcDream.UI.Abstractions.Panels.Settings; namespace AcDream.App.Tests.UI; public sealed class RetailWindowLayoutPersistenceTests : IDisposable { private readonly string _directory = Path.Combine( Path.GetTempPath(), "acdream-window-layout-tests-" + Guid.NewGuid().ToString("N")); private string PathName => Path.Combine(_directory, "settings.json"); [Fact] public void CompletedInteractions_SaveCompleteCharacterResolutionLayout() { var store = new SettingsStore(PathName); var root = new UiRoot { Width = 800, Height = 600 }; RetailWindowHandle handle = Mount(root, "chat"); using var persistence = new RetailWindowLayoutPersistence( root.WindowManager, store, () => "Alice", () => (800, 600)); handle.MoveTo(44f, 55f); handle.ResizeTo(320f, 180f); handle.Hide(); UiWindowLayout saved = Assert.IsType( store.LoadWindowLayout("Alice", "800x600", "chat", default)); Assert.Equal((44f, 55f, 320f, 180f), (saved.X, saved.Y, saved.Width, saved.Height)); Assert.False(saved.Visible); } [Fact] public void Restore_ClampsBoundsAndReappliesPanelStateAndVisibility() { var store = new SettingsStore(PathName); store.SaveWindowLayout( "Alice", "640x480", "chat", new UiWindowLayout(900f, 700f, 500f, 300f, false, false, true)); var root = new UiRoot { Width = 640, Height = 480 }; var state = new FakeWindowState(); RetailWindowHandle handle = Mount(root, "chat", state); using var persistence = new RetailWindowLayoutPersistence( root.WindowManager, store, () => "Alice", () => (640, 480)); persistence.RestoreAll(); Assert.Equal((500f, 300f), (handle.Width, handle.Height)); Assert.Equal((140f, 180f), (handle.Left, handle.Top)); Assert.False(handle.IsVisible); Assert.True(state.Restored.Maximized); } [Fact] public void DefaultPreLoginKey_NeverOverwritesCharacterLayouts() { var store = new SettingsStore(PathName); var root = new UiRoot { Width = 800, Height = 600 }; RetailWindowHandle handle = Mount(root, "radar"); using var persistence = new RetailWindowLayoutPersistence( root.WindowManager, store, () => "default", () => (800, 600)); handle.MoveTo(123f, 234f); Assert.Null(store.LoadWindowLayout("default", "800x600", "radar", default)); } [Fact] public void Restore_StateManagedWindow_PreservesAuthoritativeVisibility() { var store = new SettingsStore(PathName); store.SaveWindowLayout( "Alice", "800x600", "combat", new UiWindowLayout(44f, 55f, 200f, 100f, true, false, false)); var root = new UiRoot { Width = 800, Height = 600 }; RetailWindowHandle handle = Mount(root, "combat"); handle.Hide(); using var persistence = new RetailWindowLayoutPersistence( root.WindowManager, store, () => "Alice", () => (800, 600), stateManagedVisibilityWindows: ["combat"]); persistence.RestoreAll(); Assert.Equal((44f, 55f), (handle.Left, handle.Top)); Assert.False(handle.IsVisible); // Combat mode owns subsequent show/hide transitions as well; they must // not turn into saved user visibility preferences. handle.Show(); UiWindowLayout saved = Assert.IsType( store.LoadWindowLayout("Alice", "800x600", "combat", default)); Assert.False(saved.Visible); } [Fact] public void NamedProfile_SaveAndRestore_AppliesEveryMountedWindow() { var store = new SettingsStore(PathName); var root = new UiRoot { Width = 800, Height = 600 }; RetailWindowHandle chat = Mount(root, "chat"); RetailWindowHandle radar = Mount(root, "radar"); using var persistence = new RetailWindowLayoutPersistence( root.WindowManager, store, () => "Alice", () => (800, 600)); chat.MoveTo(44f, 55f); radar.MoveTo(300f, 120f); radar.Hide(); persistence.SaveNamed("hunting"); chat.MoveTo(1f, 2f); radar.MoveTo(3f, 4f); radar.Show(); persistence.RestoreNamed("hunting"); Assert.Equal((44f, 55f), (chat.Left, chat.Top)); Assert.Equal((300f, 120f), (radar.Left, radar.Top)); Assert.False(radar.IsVisible); } private static RetailWindowHandle Mount( UiRoot root, string name, IRetainedWindowStateController? state = null) { var frame = new UiPanel { Left = 10f, Top = 20f, Width = 200f, Height = 100f, MinWidth = 100f, MinHeight = 80f, MaxWidth = 600f, MaxHeight = 400f, Draggable = true, Resizable = true, }; root.AddChild(frame); return root.RegisterWindow(name, frame, stateController: state); } public void Dispose() { if (Directory.Exists(_directory)) Directory.Delete(_directory, recursive: true); } private sealed class FakeWindowState : IRetainedWindowStateController { public RetainedWindowState Restored { get; private set; } public RetainedWindowState CaptureWindowState() => Restored; public void RestoreWindowState(RetainedWindowState state) => Restored = state; } }