diff --git a/Promete.Test/ScenelessExampleTests.cs b/Promete.Test/ScenelessExampleTests.cs new file mode 100644 index 0000000..c51ac96 --- /dev/null +++ b/Promete.Test/ScenelessExampleTests.cs @@ -0,0 +1,53 @@ +using Promete.Headless; +using Promete.Input; +using Promete.Windowing; +using System.Reflection; + +namespace Promete.Test; + +/// +/// Issue で示されたコード例と同様のテスト。 +/// シーンを使わずに PrometeApp を実行できることを確認する。 +/// +public class ScenelessExampleTests +{ + [Fact] + public void Example_ScenelessHelloWorld_ShouldHaveRequiredMethods() + { + // Arrange - Issue で示されたコード例と同様のパターン + var app = PrometeApp.Create() + .Use() + .Use() + .BuildWithHeadless(); + + var keyboard = app.GetPlugin(); + var console = app.GetPlugin(); + + // Window イベントにハンドラを登録できることを確認 + app.Window.Start += () => + { + console.Print("Hello, world!"); + }; + + app.Window.Update += () => + { + // Update logic would go here + }; + + // Run() メソッドが存在することを確認 + var runMethods = typeof(PrometeApp).GetMethods(BindingFlags.Public | BindingFlags.Instance) + .Where(m => m.Name == "Run" && m.GetParameters().Length == 0 && !m.IsGenericMethod); + Assert.Single(runMethods); + + var runMethodsWithOpts = typeof(PrometeApp).GetMethods(BindingFlags.Public | BindingFlags.Instance) + .Where(m => m.Name == "Run" && + m.GetParameters().Length == 1 && + m.GetParameters()[0].ParameterType == typeof(WindowOptions) && + !m.IsGenericMethod); + Assert.Single(runMethodsWithOpts); + + // プラグインが正しく取得できることを確認 + Assert.NotNull(keyboard); + Assert.NotNull(console); + } +} diff --git a/Promete.Test/ScenelessRunTests.cs b/Promete.Test/ScenelessRunTests.cs new file mode 100644 index 0000000..1e0adab --- /dev/null +++ b/Promete.Test/ScenelessRunTests.cs @@ -0,0 +1,62 @@ +using FluentAssertions; +using Promete.Headless; +using System.Reflection; + +namespace Promete.Test; + +public class ScenelessRunTests +{ + /// + /// DefaultScene の型を取得するヘルパーメソッド + /// + private static Type GetDefaultSceneType() + { + return typeof(PrometeApp).GetNestedType("DefaultScene", BindingFlags.NonPublic)!; + } + + [Fact] + public void Run_WithoutScene_ShouldHaveValidRoot() + { + // Arrange + var app = PrometeApp.Create() + .BuildWithHeadless(); + + // OnStartを手動で呼び出してDefaultSceneをロード + var onStartMethod = typeof(PrometeApp).GetMethod("OnStart", BindingFlags.NonPublic | BindingFlags.Instance)!; + var genericOnStartMethod = onStartMethod.MakeGenericMethod(GetDefaultSceneType()); + genericOnStartMethod.Invoke(app, null); + + // Assert + app.Root.Should().NotBeNull("Root container should be initialized even without explicit scene"); + } + + [Fact] + public void GetPlugin_ShouldWork_WhenRunningWithoutScene() + { + // Arrange + var app = PrometeApp.Create() + .Use() + .BuildWithHeadless(); + + // Act + var console = app.GetPlugin(); + + // Assert + console.Should().NotBeNull("Plugins should be accessible even when running without explicit scene"); + } + + [Fact] + public void DefaultScene_ShouldBeRegistered() + { + // Arrange + var app = PrometeApp.Create() + .BuildWithHeadless(); + + // Act - DefaultSceneのロードを試みる + var loadSceneMethod = typeof(PrometeApp).GetMethod("LoadScene", BindingFlags.Public | BindingFlags.Instance, [typeof(Type)])!; + + // Assert - 例外がスローされないことを確認 + var act = () => loadSceneMethod.Invoke(app, [GetDefaultSceneType()]); + act.Should().NotThrow("DefaultScene should be registered and loadable"); + } +} diff --git a/Promete/PrometeApp.cs b/Promete/PrometeApp.cs index d6a45a9..8559a0b 100644 --- a/Promete/PrometeApp.cs +++ b/Promete/PrometeApp.cs @@ -136,6 +136,25 @@ public int Run(WindowOptions opts) where TScene : Scene return _statusCode; } + /// + /// Promete アプリケーションをシーンなしで実行します。 + /// + /// 終了ステータスコード。 + public int Run() + { + return Run(WindowOptions.Default); + } + + /// + /// Promete アプリケーションをシーンなしで実行します。 + /// + /// ウィンドウのオプション。 + /// 終了ステータスコード。 + public int Run(WindowOptions opts) + { + return Run(opts); + } + /// /// 指定したステータスコードで Promete アプリケーションを終了します。 /// @@ -379,6 +398,9 @@ private void ProcessNextFrameQueue() private void RegisterAllScenes() { + // DefaultScene を明示的に登録 + _services.AddTransient(); + var asm = Assembly.GetEntryAssembly() ?? throw new InvalidOperationException("There is no entry assembly."); // Scene 派生クラスを全て取得する var types = asm.GetTypes(); @@ -403,6 +425,14 @@ private Scene GetScene(Type scene) /// public event Action? SceneWillChange; + /// + /// シーンを使用せずにアプリケーションを実行する際に使用されるデフォルトの空のシーン。 + /// + [IgnoredScene] + private sealed class DefaultScene : Scene + { + } + /// /// Promete アプリケーションを構築するためのビルダークラスです。 ///