Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions Promete.Test/ScenelessExampleTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using Promete.Headless;
using Promete.Input;
using Promete.Windowing;
using System.Reflection;

namespace Promete.Test;

/// <summary>
/// Issue で示されたコード例と同様のテスト。
/// シーンを使わずに PrometeApp を実行できることを確認する。
/// </summary>
public class ScenelessExampleTests
{
[Fact]
public void Example_ScenelessHelloWorld_ShouldHaveRequiredMethods()
{
// Arrange - Issue で示されたコード例と同様のパターン
var app = PrometeApp.Create()
.Use<Keyboard>()
.Use<ConsoleLayer>()
.BuildWithHeadless();

var keyboard = app.GetPlugin<Keyboard>();
var console = app.GetPlugin<ConsoleLayer>();

// 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);
}
}
62 changes: 62 additions & 0 deletions Promete.Test/ScenelessRunTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
using FluentAssertions;
using Promete.Headless;
using System.Reflection;

namespace Promete.Test;

public class ScenelessRunTests
{
/// <summary>
/// DefaultScene の型を取得するヘルパーメソッド
/// </summary>
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<ConsoleLayer>()
.BuildWithHeadless();

// Act
var console = app.GetPlugin<ConsoleLayer>();

// 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");
}
}
30 changes: 30 additions & 0 deletions Promete/PrometeApp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,25 @@ public int Run<TScene>(WindowOptions opts) where TScene : Scene
return _statusCode;
}

/// <summary>
/// Promete アプリケーションをシーンなしで実行します。
/// </summary>
/// <returns>終了ステータスコード。</returns>
public int Run()
{
return Run(WindowOptions.Default);
}

/// <summary>
/// Promete アプリケーションをシーンなしで実行します。
/// </summary>
/// <param name="opts">ウィンドウのオプション。</param>
/// <returns>終了ステータスコード。</returns>
public int Run(WindowOptions opts)
{
return Run<DefaultScene>(opts);
}

/// <summary>
/// 指定したステータスコードで Promete アプリケーションを終了します。
/// </summary>
Expand Down Expand Up @@ -379,6 +398,9 @@ private void ProcessNextFrameQueue()

private void RegisterAllScenes()
{
// DefaultScene を明示的に登録
_services.AddTransient<DefaultScene>();

var asm = Assembly.GetEntryAssembly() ?? throw new InvalidOperationException("There is no entry assembly.");
// Scene 派生クラスを全て取得する
var types = asm.GetTypes();
Expand All @@ -403,6 +425,14 @@ private Scene GetScene(Type scene)
/// </summary>
public event Action? SceneWillChange;

/// <summary>
/// シーンを使用せずにアプリケーションを実行する際に使用されるデフォルトの空のシーン。
/// </summary>
[IgnoredScene]
private sealed class DefaultScene : Scene
{
}

/// <summary>
/// Promete アプリケーションを構築するためのビルダークラスです。
/// </summary>
Expand Down
Loading