using GFramework.Godot.SourceGenerators.Tests.Core; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Testing; using Microsoft.CodeAnalysis.Testing; using NUnit.Framework; namespace GFramework.Godot.SourceGenerators.Tests.GetNode; [TestFixture] public class GetNodeGeneratorTests { [Test] public async Task Generates_InferredUniqueNameBindings_And_ReadyHook_WhenReadyIsMissing() { const string source = """ using System; using GFramework.Godot.SourceGenerators.Abstractions; using Godot; namespace GFramework.Godot.SourceGenerators.Abstractions { [AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)] public sealed class GetNodeAttribute : Attribute { public GetNodeAttribute() {} public GetNodeAttribute(string path) { Path = path; } public string? Path { get; set; } public bool Required { get; set; } = true; public NodeLookupMode Lookup { get; set; } = NodeLookupMode.Auto; } public enum NodeLookupMode { Auto = 0, UniqueName = 1, RelativePath = 2, AbsolutePath = 3 } } namespace Godot { public class Node { public virtual void _Ready() {} public T GetNode(string path) where T : Node => throw new InvalidOperationException(path); public T? GetNodeOrNull(string path) where T : Node => default; } public class HBoxContainer : Node { } } namespace TestApp { public partial class TopBar : HBoxContainer { [GetNode] private HBoxContainer _leftContainer = null!; [GetNode] private HBoxContainer m_rightContainer = null!; } } """; const string expected = """ // #nullable enable namespace TestApp; partial class TopBar { private void __InjectGetNodes_Generated() { _leftContainer = GetNode("%LeftContainer"); m_rightContainer = GetNode("%RightContainer"); } partial void OnGetNodeReadyGenerated(); public override void _Ready() { __InjectGetNodes_Generated(); OnGetNodeReadyGenerated(); } } """; await GeneratorTest.RunAsync( source, ("TestApp_TopBar.GetNode.g.cs", expected)); } [Test] public async Task Generates_ManualInjectionOnly_WhenReadyAlreadyExists() { const string source = """ using System; using GFramework.Godot.SourceGenerators.Abstractions; using Godot; namespace GFramework.Godot.SourceGenerators.Abstractions { [AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)] public sealed class GetNodeAttribute : Attribute { public GetNodeAttribute() {} public GetNodeAttribute(string path) { Path = path; } public string? Path { get; set; } public bool Required { get; set; } = true; public NodeLookupMode Lookup { get; set; } = NodeLookupMode.Auto; } public enum NodeLookupMode { Auto = 0, UniqueName = 1, RelativePath = 2, AbsolutePath = 3 } } namespace Godot { public class Node { public virtual void _Ready() {} public T GetNode(string path) where T : Node => throw new InvalidOperationException(path); public T? GetNodeOrNull(string path) where T : Node => default; } public class HBoxContainer : Node { } } namespace TestApp { public partial class TopBar : HBoxContainer { [GetNode("%LeftContainer")] private HBoxContainer _leftContainer = null!; [GetNode(Required = false, Lookup = NodeLookupMode.RelativePath)] private HBoxContainer? _rightContainer; public override void _Ready() { __InjectGetNodes_Generated(); } } } """; const string expected = """ // #nullable enable namespace TestApp; partial class TopBar { private void __InjectGetNodes_Generated() { _leftContainer = GetNode("%LeftContainer"); _rightContainer = GetNodeOrNull("RightContainer"); } } """; await GeneratorTest.RunAsync( source, ("TestApp_TopBar.GetNode.g.cs", expected)); } [Test] public async Task Reports_Diagnostic_When_FieldType_IsNotGodotNode() { const string source = """ using System; using GFramework.Godot.SourceGenerators.Abstractions; using Godot; namespace GFramework.Godot.SourceGenerators.Abstractions { [AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)] public sealed class GetNodeAttribute : Attribute { public string? Path { get; set; } public bool Required { get; set; } = true; public NodeLookupMode Lookup { get; set; } = NodeLookupMode.Auto; } public enum NodeLookupMode { Auto = 0, UniqueName = 1, RelativePath = 2, AbsolutePath = 3 } } namespace Godot { public class Node { public virtual void _Ready() {} public T GetNode(string path) where T : Node => throw new InvalidOperationException(path); public T? GetNodeOrNull(string path) where T : Node => default; } } namespace TestApp { public partial class TopBar : Node { [GetNode] private string _leftContainer = string.Empty; } } """; var test = new CSharpSourceGeneratorTest { TestState = { Sources = { source } }, DisabledDiagnostics = { "GF_Common_Trace_001" } }; test.ExpectedDiagnostics.Add(new DiagnosticResult("GF_Godot_GetNode_004", DiagnosticSeverity.Error) .WithSpan(39, 24, 39, 38) .WithArguments("_leftContainer")); await test.RunAsync(); } }