using MrGameEng.Core; using Xunit; namespace MrGameEng.Core.Tests; public class SceneManagerTests { private sealed class TrackingScene : Scene { public int LoadCount; public int UnloadCount; public int UpdateCount; protected override void OnLoad() => LoadCount++; protected override void OnUnload() => UnloadCount++; public override void Update(GameClock clock) { UpdateCount++; base.Update(clock); } } [Fact] public void Switch_IsDeferred_UntilNextUpdate() { var context = new EngineContext(); var scene = new TrackingScene(); context.Scenes.Switch(scene); Assert.Null(context.Scenes.Current); Assert.Equal(0, scene.LoadCount); context.Scenes.Update(context.Clock); Assert.Same(scene, context.Scenes.Current); Assert.Equal(1, scene.LoadCount); Assert.Equal(1, scene.UpdateCount); Assert.True(scene.IsLoaded); } [Fact] public void Switch_UnloadsPreviousScene_AndLoadsNext() { var context = new EngineContext(); var first = new TrackingScene(); var second = new TrackingScene(); context.Scenes.Switch(first); context.Scenes.Update(context.Clock); context.Scenes.Switch(second); context.Scenes.Update(context.Clock); Assert.Equal(1, first.UnloadCount); Assert.False(first.IsLoaded); Assert.Same(second, context.Scenes.Current); Assert.Equal(1, second.LoadCount); } [Fact] public void Switch_ToNull_UnloadsCurrentScene() { var context = new EngineContext(); var scene = new TrackingScene(); context.Scenes.Switch(scene); context.Scenes.Update(context.Clock); context.Scenes.Switch(null); context.Scenes.Update(context.Clock); Assert.Null(context.Scenes.Current); Assert.Equal(1, scene.UnloadCount); } [Fact] public void Update_WithoutScene_DoesNothing() { var context = new EngineContext(); context.Scenes.Update(context.Clock); context.Scenes.Draw(context.Clock); Assert.Null(context.Scenes.Current); } private sealed class CallbackScene(Action onLoad) : Scene { protected override void OnLoad() => onLoad(this); } [Fact] public void Switch_BackToLoadedSceneInstance_Throws() { var context = new EngineContext(); var first = new TrackingScene(); var second = new TrackingScene(); context.Scenes.Switch(first); context.Scenes.Update(context.Clock); context.Scenes.Switch(second); context.Scenes.Update(context.Clock); // Повторная загрузка молча задвоила бы системы и сущности — должен быть отказ. context.Scenes.Switch(first); Assert.Throws(() => context.Scenes.Update(context.Clock)); } [Fact] public void RegisterUnload_RunsOnUnload_InReverseOrder() { var context = new EngineContext(); var order = new List(); var scene = new CallbackScene(s => { s.RegisterUnload(() => order.Add(1)); s.RegisterUnload(() => order.Add(2)); }); context.Scenes.Switch(scene); context.Scenes.Update(context.Clock); Assert.Empty(order); context.Scenes.Switch(null); context.Scenes.Update(context.Clock); Assert.Equal([2, 1], order); } private sealed class DisposableService : IDisposable { public int DisposeCount; public void Dispose() => DisposeCount++; } [Fact] public void DisposeServices_DisposesEachServiceOnce_AndSkipsExcept() { var registry = new ServiceRegistry(); var service = new DisposableService(); var host = new DisposableService(); registry.Add(service); registry.Add(host); // host зарегистрирован, но его освобождает вызывающий registry.DisposeServices(except: host); Assert.Equal(1, service.DisposeCount); Assert.Equal(0, host.DisposeCount); Assert.Null(registry.GetOrDefault()); } }