using GFramework.Godot.SourceGenerators.Behavior; using GFramework.Godot.SourceGenerators.Tests.Core; namespace GFramework.Godot.SourceGenerators.Tests.Behavior; [TestFixture] public class AutoSceneGeneratorTests { [Test] public async Task Generates_Scene_Behavior_Boilerplate() { const string source = """ using System; using GFramework.Godot.SourceGenerators.Abstractions; using Godot; namespace GFramework.Godot.SourceGenerators.Abstractions { [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] public sealed class AutoSceneAttribute : Attribute { public AutoSceneAttribute(string key) { } } } namespace Godot { public class Node { } public class Node2D : Node { } } namespace GFramework.Game.Abstractions.Scene { public interface ISceneBehavior { } } namespace GFramework.Godot.Scene { using GFramework.Game.Abstractions.Scene; using Godot; public static class SceneBehaviorFactory { public static ISceneBehavior Create(T owner, string key) where T : Node { return null!; } } } namespace TestApp { [AutoScene("Gameplay")] public partial class GameplayRoot : Node2D { } } """; const string expected = """ // #nullable enable namespace TestApp; partial class GameplayRoot { private global::GFramework.Game.Abstractions.Scene.ISceneBehavior? __autoSceneBehavior_Generated; public static string SceneKeyStr => "Gameplay"; public global::GFramework.Game.Abstractions.Scene.ISceneBehavior GetScene() { return __autoSceneBehavior_Generated ??= global::GFramework.Godot.Scene.SceneBehaviorFactory.Create(this, SceneKeyStr); } } """; await GeneratorTest.RunAsync( source, ("TestApp_GameplayRoot.AutoScene.g.cs", expected)); } [Test] public async Task Reports_Diagnostic_When_AutoScene_Arguments_Are_Invalid() { const string source = """ using System; using GFramework.Godot.SourceGenerators.Abstractions; using Godot; namespace GFramework.Godot.SourceGenerators.Abstractions { [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] public sealed class AutoSceneAttribute : Attribute { public AutoSceneAttribute() { } } } namespace Godot { public class Node { } public class Node2D : Node { } } namespace TestApp { [{|#0:AutoScene|}] public partial class GameplayRoot : Node2D { } } """; var test = new CSharpSourceGeneratorTest { TestState = { Sources = { source } }, DisabledDiagnostics = { "GF_Common_Trace_001" } }; test.ExpectedDiagnostics.Add(new DiagnosticResult("GF_AutoBehavior_004", DiagnosticSeverity.Error) .WithLocation(0) .WithArguments("AutoSceneAttribute", "GameplayRoot", "a single string scene key argument")); await test.RunAsync(); } [Test] public async Task Generates_Type_Constraints_For_Nullable_Reference_NotNull_And_Unmanaged_Parameters() { const string source = """ #nullable enable using System; using GFramework.Godot.SourceGenerators.Abstractions; using Godot; namespace GFramework.Godot.SourceGenerators.Abstractions { [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] public sealed class AutoSceneAttribute : Attribute { public AutoSceneAttribute(string key) { } } } namespace Godot { public class Node { } public class Node2D : Node { } } namespace GFramework.Game.Abstractions.Scene { public interface ISceneBehavior { } } namespace GFramework.Godot.Scene { using GFramework.Game.Abstractions.Scene; using Godot; public static class SceneBehaviorFactory { public static ISceneBehavior Create(T owner, string key) where T : Node { return null!; } } } namespace TestApp { [AutoScene("Gameplay")] public partial class GameplayRoot : Node2D where TReference : class? where TNotNull : notnull where TValue : struct where TUnmanaged : unmanaged { } } """; const string expected = """ // #nullable enable namespace TestApp; partial class GameplayRoot where TReference : class? where TNotNull : notnull where TValue : struct where TUnmanaged : unmanaged { private global::GFramework.Game.Abstractions.Scene.ISceneBehavior? __autoSceneBehavior_Generated; public static string SceneKeyStr => "Gameplay"; public global::GFramework.Game.Abstractions.Scene.ISceneBehavior GetScene() { return __autoSceneBehavior_Generated ??= global::GFramework.Godot.Scene.SceneBehaviorFactory.Create(this, SceneKeyStr); } } """; await GeneratorTest.RunAsync( source, ("TestApp_GameplayRoot.AutoScene.g.cs", expected)); } /// /// 验证宿主类型声明同名 SceneKeyStr 属性时,生成器会报告保留成员冲突并停止生成。 /// [Test] public async Task Reports_Diagnostic_When_SceneKeyStr_Property_Name_Conflicts() { const string source = """ using System; using GFramework.Godot.SourceGenerators.Abstractions; using Godot; namespace GFramework.Godot.SourceGenerators.Abstractions { [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] public sealed class AutoSceneAttribute : Attribute { public AutoSceneAttribute(string key) { } } } namespace Godot { public class Node { } public class Node2D : Node { } } namespace TestApp { [AutoScene("Gameplay")] public partial class GameplayRoot : Node2D { public static string {|#0:SceneKeyStr|} => "Conflict"; } } """; var test = new CSharpSourceGeneratorTest { TestState = { Sources = { source } }, DisabledDiagnostics = { "GF_Common_Trace_001" }, TestBehaviors = TestBehaviors.SkipGeneratedSourcesCheck }; test.ExpectedDiagnostics.Add(new DiagnosticResult("GF_Common_Class_002", DiagnosticSeverity.Error) .WithLocation(0) .WithArguments("GameplayRoot", "SceneKeyStr")); await test.RunAsync(); } /// /// 验证宿主类型声明同名缓存字段时,生成器会报告保留成员冲突并停止生成。 /// [Test] public async Task Reports_Diagnostic_When_Generated_Behavior_Field_Name_Conflicts() { const string source = """ using System; using GFramework.Game.Abstractions.Scene; using GFramework.Godot.SourceGenerators.Abstractions; using Godot; namespace GFramework.Godot.SourceGenerators.Abstractions { [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] public sealed class AutoSceneAttribute : Attribute { public AutoSceneAttribute(string key) { } } } namespace Godot { public class Node { } public class Node2D : Node { } } namespace GFramework.Game.Abstractions.Scene { public interface ISceneBehavior { } } namespace TestApp { [AutoScene("Gameplay")] public partial class GameplayRoot : Node2D { private ISceneBehavior? {|#0:__autoSceneBehavior_Generated|}; } } """; var test = new CSharpSourceGeneratorTest { TestState = { Sources = { source } }, DisabledDiagnostics = { "GF_Common_Trace_001" }, TestBehaviors = TestBehaviors.SkipGeneratedSourcesCheck }; test.ExpectedDiagnostics.Add(new DiagnosticResult("GF_Common_Class_002", DiagnosticSeverity.Error) .WithLocation(0) .WithArguments("GameplayRoot", "__autoSceneBehavior_Generated")); await test.RunAsync(); } }