File: ViewComponentTagHelperDescriptorFactoryTest.cs
Web Access
Project: src\src\Razor\src\Compiler\Microsoft.AspNetCore.Mvc.Razor.Extensions\test\Microsoft.AspNetCore.Mvc.Razor.Extensions.UnitTests.csproj (Microsoft.AspNetCore.Mvc.Razor.Extensions.UnitTests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Xml.Linq;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Xunit;
 
namespace Microsoft.AspNetCore.Mvc.Razor.Extensions;
 
public class ViewComponentTagHelperDescriptorFactoryTest
{
    private static readonly Compilation _compilation = TestCompilation.Create(syntaxTrees: [CSharpSyntaxTree.ParseText(Microsoft.CodeAnalysis.Text.SourceText.From(AdditionalCode, System.Text.Encoding.UTF8))], references: []);
 
    [Fact]
    public void CreateDescriptor_UnderstandsStringParameters()
    {
        // Arrange
        var testCompilation = _compilation;
        var viewComponent = testCompilation.GetTypeByMetadataName("TestNamespace.StringParameterViewComponent");
        var factory = new ViewComponentTagHelperDescriptorFactory(testCompilation);
 
        var expectedDescriptor = TagHelperDescriptorBuilder.CreateViewComponent("__Generated__StringParameterViewComponentTagHelper", TestCompilation.AssemblyName)
            .TypeName("__Generated__StringParameterViewComponentTagHelper")
            .Metadata(new ViewComponentMetadata("StringParameter", TypeNameObject.From("StringParameter")))
            .DisplayName("StringParameterViewComponentTagHelper")
            .TagMatchingRuleDescriptor(rule => rule
                .RequireTagName("vc:string-parameter")
                .RequireAttributeDescriptor(attribute => attribute.Name("foo"))
                .RequireAttributeDescriptor(attribute => attribute.Name("bar")))
            .BoundAttributeDescriptor(attribute => attribute
                .Name("foo")
                .PropertyName("foo")
                .TypeName(typeof(string).FullName)
                .DisplayName("string StringParameterViewComponentTagHelper.foo"))
            .BoundAttributeDescriptor(attribute => attribute
                .Name("bar")
                .PropertyName("bar")
                .TypeName(typeof(string).FullName)
                .DisplayName("string StringParameterViewComponentTagHelper.bar"))
            .Build();
 
        // Act
        var descriptor = factory.CreateDescriptor(viewComponent);
 
        // Assert
        Assert.Equal(expectedDescriptor, descriptor);
    }
 
    [Fact]
    public void CreateDescriptor_UnderstandsVariousParameterTypes()
    {
        // Arrange
        var testCompilation = _compilation;
        var viewComponent = testCompilation.GetTypeByMetadataName("TestNamespace.VariousParameterViewComponent");
        var factory = new ViewComponentTagHelperDescriptorFactory(testCompilation);
 
        var expectedDescriptor = TagHelperDescriptorBuilder.CreateViewComponent("__Generated__VariousParameterViewComponentTagHelper", TestCompilation.AssemblyName)
            .TypeName("__Generated__VariousParameterViewComponentTagHelper")
            .Metadata(new ViewComponentMetadata("VariousParameter", TypeNameObject.From("VariousParameter")))
            .DisplayName("VariousParameterViewComponentTagHelper")
            .TagMatchingRuleDescriptor(rule =>
                rule
                .RequireTagName("vc:various-parameter")
                .RequireAttributeDescriptor(attribute => attribute.Name("test-enum"))
                .RequireAttributeDescriptor(attribute => attribute.Name("test-string")))
            .BoundAttributeDescriptor(attribute =>
                attribute
                .Name("test-enum")
                .PropertyName("testEnum")
                .TypeName("TestNamespace.VariousParameterViewComponent.TestEnum")
                .AsEnum()
                .DisplayName("TestNamespace.VariousParameterViewComponent.TestEnum VariousParameterViewComponentTagHelper.testEnum"))
            .BoundAttributeDescriptor(attribute =>
                attribute
                .Name("test-string")
                .PropertyName("testString")
                .TypeName(typeof(string).FullName)
                .DisplayName("string VariousParameterViewComponentTagHelper.testString"))
            .BoundAttributeDescriptor(attribute =>
                attribute
                .Name("baz")
                .PropertyName("baz")
                .TypeName(typeof(int).FullName)
                .DisplayName("int VariousParameterViewComponentTagHelper.baz"))
            .Build();
 
        // Act
        var descriptor = factory.CreateDescriptor(viewComponent);
 
        // Assert
        Assert.Equal(expectedDescriptor, descriptor);
    }
 
    [Fact]
    public void CreateDescriptor_UnderstandsGenericParameters()
    {
        // Arrange
        var testCompilation = _compilation;
        var viewComponent = testCompilation.GetTypeByMetadataName("TestNamespace.GenericParameterViewComponent");
        var factory = new ViewComponentTagHelperDescriptorFactory(testCompilation);
 
        var expectedDescriptor = TagHelperDescriptorBuilder.CreateViewComponent("__Generated__GenericParameterViewComponentTagHelper", TestCompilation.AssemblyName)
            .TypeName("__Generated__GenericParameterViewComponentTagHelper")
            .Metadata(new ViewComponentMetadata("GenericParameter", TypeNameObject.From("GenericParameter")))
            .DisplayName("GenericParameterViewComponentTagHelper")
            .TagMatchingRuleDescriptor(rule =>
                rule
                .RequireTagName("vc:generic-parameter")
                .RequireAttributeDescriptor(attribute => attribute.Name("foo")))
            .BoundAttributeDescriptor(attribute =>
                attribute
                .Name("foo")
                .PropertyName("Foo")
                .TypeName("System.Collections.Generic.List<System.String>")
                .DisplayName("System.Collections.Generic.List<System.String> GenericParameterViewComponentTagHelper.Foo"))
            .BoundAttributeDescriptor(attribute =>
                attribute
                .Name("bar")
                .PropertyName("Bar")
                .TypeName("System.Collections.Generic.Dictionary<System.String, System.Int32>")
                .AsDictionaryAttribute("bar-", typeof(int).FullName)
                .DisplayName("System.Collections.Generic.Dictionary<System.String, System.Int32> GenericParameterViewComponentTagHelper.Bar"))
            .Build();
 
        // Act
        var descriptor = factory.CreateDescriptor(viewComponent);
 
        // Assert
        Assert.Equal(expectedDescriptor, descriptor);
    }
 
    [Fact]
    public void CreateDescriptor_ForSyncViewComponentWithInvokeInBaseType_Works()
    {
        // Arrange
        var testCompilation = _compilation;
        var factory = new ViewComponentTagHelperDescriptorFactory(testCompilation);
 
        var expectedDescriptor = TagHelperDescriptorBuilder.CreateViewComponent("__Generated__SyncDerivedViewComponentTagHelper", TestCompilation.AssemblyName)
            .TypeName("__Generated__SyncDerivedViewComponentTagHelper")
            .Metadata(new ViewComponentMetadata("SyncDerived", TypeNameObject.From("SyncDerived")))
            .DisplayName("SyncDerivedViewComponentTagHelper")
            .TagMatchingRuleDescriptor(rule =>
                rule
                .RequireTagName("vc:sync-derived")
                .RequireAttributeDescriptor(attribute => attribute.Name("foo"))
                .RequireAttributeDescriptor(attribute => attribute.Name("bar")))
            .BoundAttributeDescriptor(attribute =>
                attribute
                .Name("foo")
                .PropertyName("foo")
                .TypeName(typeof(string).FullName)
                .DisplayName("string SyncDerivedViewComponentTagHelper.foo"))
            .BoundAttributeDescriptor(attribute =>
                attribute
                .Name("bar")
                .PropertyName("bar")
                .TypeName(typeof(string).FullName)
                .DisplayName("string SyncDerivedViewComponentTagHelper.bar"))
            .Build();
 
        var viewComponent = testCompilation.GetTypeByMetadataName("TestNamespace.SyncDerivedViewComponent");
 
        // Act
        var descriptor = factory.CreateDescriptor(viewComponent);
 
        // Assert
        Assert.Equal(expectedDescriptor, descriptor);
    }
 
    [Fact]
    public void CreateDescriptor_ForAsyncViewComponentWithInvokeInBaseType_Works()
    {
        // Arrange
        var testCompilation = _compilation;
        var factory = new ViewComponentTagHelperDescriptorFactory(testCompilation);
 
        var expectedDescriptor = TagHelperDescriptorBuilder.CreateViewComponent("__Generated__AsyncDerivedViewComponentTagHelper", TestCompilation.AssemblyName)
            .TypeName("__Generated__AsyncDerivedViewComponentTagHelper")
            .Metadata(new ViewComponentMetadata("AsyncDerived", TypeNameObject.From("AsyncDerived")))
            .DisplayName("AsyncDerivedViewComponentTagHelper")
            .TagMatchingRuleDescriptor(rule => rule.RequireTagName("vc:async-derived"))
            .Build();
 
        var viewComponent = testCompilation.GetTypeByMetadataName("TestNamespace.AsyncDerivedViewComponent");
 
        // Act
        var descriptor = factory.CreateDescriptor(viewComponent);
 
        // Assert
        Assert.Equal(expectedDescriptor, descriptor);
    }
 
    [Fact]
    public void CreateDescriptor_AddsDiagnostic_ForViewComponentWithNoInvokeMethod()
    {
        // Arrange
        var testCompilation = _compilation;
        var factory = new ViewComponentTagHelperDescriptorFactory(testCompilation);
 
        var viewComponent = testCompilation.GetTypeByMetadataName("TestNamespace.ViewComponentWithoutInvokeMethod");
 
        // Act
        var descriptor = factory.CreateDescriptor(viewComponent);
 
        // Assert
        var diagnostic = Assert.Single(descriptor.GetAllDiagnostics());
        Assert.Equal(RazorExtensionsDiagnosticFactory.ViewComponent_CannotFindMethod.Id, diagnostic.Id);
    }
 
    [Fact]
    public void CreateDescriptor_AddsDiagnostic_ForViewComponentWithNoInstanceInvokeMethod()
    {
        // Arrange
        var testCompilation = _compilation;
        var factory = new ViewComponentTagHelperDescriptorFactory(testCompilation);
 
        var viewComponent = testCompilation.GetTypeByMetadataName("TestNamespace.StaticInvokeAsyncViewComponent");
 
        // Act
        var descriptor = factory.CreateDescriptor(viewComponent);
 
        // Assert
        var diagnostic = Assert.Single(descriptor.GetAllDiagnostics());
        Assert.Equal(RazorExtensionsDiagnosticFactory.ViewComponent_CannotFindMethod.Id, diagnostic.Id);
    }
 
    [Fact]
    public void CreateDescriptor_AddsDiagnostic_ForViewComponentWithNoPublicInvokeMethod()
    {
        // Arrange
        var testCompilation = _compilation;
        var factory = new ViewComponentTagHelperDescriptorFactory(testCompilation);
 
        var viewComponent = testCompilation.GetTypeByMetadataName("TestNamespace.NonPublicInvokeAsyncViewComponent");
 
        // Act
        var descriptor = factory.CreateDescriptor(viewComponent);
 
        // Assert
        var diagnostic = Assert.Single(descriptor.GetAllDiagnostics());
        Assert.Equal(RazorExtensionsDiagnosticFactory.ViewComponent_CannotFindMethod.Id, diagnostic.Id);
    }
 
    [Fact]
    public void CreateDescriptor_ForViewComponentWithInvokeAsync_UnderstandsGenericTask()
    {
        // Arrange
        var testCompilation = _compilation;
        var factory = new ViewComponentTagHelperDescriptorFactory(testCompilation);
 
        var viewComponent = testCompilation.GetTypeByMetadataName("TestNamespace.AsyncViewComponentWithGenericTask");
 
        // Act
        var descriptor = factory.CreateDescriptor(viewComponent);
 
        // Assert
        Assert.Empty(descriptor.GetAllDiagnostics());
    }
 
    [Fact]
    public void CreateDescriptor_ForViewComponentWithInvokeAsync_UnderstandsNonGenericTask()
    {
        // Arrange
        var testCompilation = _compilation;
        var factory = new ViewComponentTagHelperDescriptorFactory(testCompilation);
 
        var viewComponent = testCompilation.GetTypeByMetadataName("TestNamespace.AsyncViewComponentWithNonGenericTask");
 
        // Act
        var descriptor = factory.CreateDescriptor(viewComponent);
 
        // Assert
        Assert.Empty(descriptor.GetAllDiagnostics());
    }
 
    [Fact]
    public void CreateDescriptor_ForViewComponentWithInvokeAsync_DoesNotUnderstandVoid()
    {
        // Arrange
        var testCompilation = _compilation;
        var factory = new ViewComponentTagHelperDescriptorFactory(testCompilation);
 
        var viewComponent = testCompilation.GetTypeByMetadataName("TestNamespace.AsyncViewComponentWithString");
 
        // Act
        var descriptor = factory.CreateDescriptor(viewComponent);
 
        // Assert
        var diagnostic = Assert.Single(descriptor.GetAllDiagnostics());
        Assert.Equal(RazorExtensionsDiagnosticFactory.ViewComponent_AsyncMethod_ShouldReturnTask.Id, diagnostic.Id);
    }
 
    [Fact]
    public void CreateDescriptor_ForViewComponentWithInvokeAsync_DoesNotUnderstandString()
    {
        // Arrange
        var testCompilation = _compilation;
        var factory = new ViewComponentTagHelperDescriptorFactory(testCompilation);
 
        var viewComponent = testCompilation.GetTypeByMetadataName("TestNamespace.AsyncViewComponentWithString");
 
        // Act
        var descriptor = factory.CreateDescriptor(viewComponent);
 
        // Assert
        var diagnostic = Assert.Single(descriptor.GetAllDiagnostics());
        Assert.Equal(RazorExtensionsDiagnosticFactory.ViewComponent_AsyncMethod_ShouldReturnTask.Id, diagnostic.Id);
    }
 
    [Fact]
    public void CreateDescriptor_ForViewComponentWithInvoke_DoesNotUnderstandVoid()
    {
        // Arrange
        var testCompilation = _compilation;
        var factory = new ViewComponentTagHelperDescriptorFactory(testCompilation);
 
        var viewComponent = testCompilation.GetTypeByMetadataName("TestNamespace.SyncViewComponentWithVoid");
 
        // Act
        var descriptor = factory.CreateDescriptor(viewComponent);
 
        // Assert
        var diagnostic = Assert.Single(descriptor.GetAllDiagnostics());
        Assert.Equal(RazorExtensionsDiagnosticFactory.ViewComponent_SyncMethod_ShouldReturnValue.Id, diagnostic.Id);
    }
 
    [Fact]
    public void CreateDescriptor_ForViewComponentWithInvoke_DoesNotUnderstandNonGenericTask()
    {
        // Arrange
        var testCompilation = _compilation;
        var factory = new ViewComponentTagHelperDescriptorFactory(testCompilation);
 
        var viewComponent = testCompilation.GetTypeByMetadataName("TestNamespace.SyncViewComponentWithNonGenericTask");
 
        // Act
        var descriptor = factory.CreateDescriptor(viewComponent);
 
        // Assert
        var diagnostic = Assert.Single(descriptor.GetAllDiagnostics());
        Assert.Equal(RazorExtensionsDiagnosticFactory.ViewComponent_SyncMethod_CannotReturnTask.Id, diagnostic.Id);
    }
 
    [Fact]
    public void CreateDescriptor_ForViewComponentWithInvoke_DoesNotUnderstandGenericTask()
    {
        // Arrange
        var testCompilation = _compilation;
        var factory = new ViewComponentTagHelperDescriptorFactory(testCompilation);
 
        var viewComponent = testCompilation.GetTypeByMetadataName("TestNamespace.SyncViewComponentWithGenericTask");
 
        // Act
        var descriptor = factory.CreateDescriptor(viewComponent);
 
        // Assert
        var diagnostic = Assert.Single(descriptor.GetAllDiagnostics());
        Assert.Equal(RazorExtensionsDiagnosticFactory.ViewComponent_SyncMethod_CannotReturnTask.Id, diagnostic.Id);
    }
 
    [Fact]
    public void CreateDescriptor_ForViewComponent_WithAmbiguousMethods()
    {
        // Arrange
        var testCompilation = _compilation;
        var factory = new ViewComponentTagHelperDescriptorFactory(testCompilation);
 
        var viewComponent = testCompilation.GetTypeByMetadataName("TestNamespace.DerivedViewComponentWithAmbiguity");
 
        // Act
        var descriptor = factory.CreateDescriptor(viewComponent);
 
        // Assert
        var diagnostic = Assert.Single(descriptor.GetAllDiagnostics());
        Assert.Equal(RazorExtensionsDiagnosticFactory.ViewComponent_AmbiguousMethods.Id, diagnostic.Id);
    }
 
    public const string AdditionalCode = """
        using System.Collections.Generic;
        using System.Threading.Tasks;
 
        namespace TestNamespace
        {
            public class StringParameterViewComponent
            {
                public string Invoke(string foo, string bar) => null;
            }
 
            public class VariousParameterViewComponent
            {
                public string Invoke(TestEnum testEnum, string testString, int baz = 5) => null;
 
                public enum TestEnum
                {
                    A = 1,
                    B = 2,
                    C = 3
                }
            }
 
            public class GenericParameterViewComponent
            {
                public string Invoke(List<string> Foo, Dictionary<string, int> Bar) => null;
            }
 
            public class ViewComponentWithoutInvokeMethod
            {
            }
 
            public class AsyncViewComponentWithGenericTask
            {
                public Task<string> InvokeAsync() => null;
            }
 
            public class AsyncViewComponentWithNonGenericTask
            {
                public Task InvokeAsync() => null;
            }
 
            public class AsyncViewComponentWithVoid
            {
                public void InvokeAsync() { }
            }
 
            public class AsyncViewComponentWithString
            {
                public string InvokeAsync() => null;
            }
 
            public class SyncViewComponentWithVoid
            {
                public void Invoke() { }
            }
 
            public class SyncViewComponentWithNonGenericTask
            {
                public Task Invoke() => null;
            }
 
            public class SyncViewComponentWithGenericTask
            {
                public Task<string> Invoke() => null;
            }
 
            public class SyncDerivedViewComponent : StringParameterViewComponent
            {
            }
 
            public class AsyncDerivedViewComponent : AsyncViewComponentWithNonGenericTask
            {
            }
 
            public class DerivedViewComponentWithAmbiguity : AsyncViewComponentWithNonGenericTask
            {
                public string Invoke() => null;
            }
 
            public class StaticInvokeAsyncViewComponent
            {
                public static Task<string> InvokeAsync() => null;
            }
 
            public class NonPublicInvokeAsyncViewComponent
            {
                protected Task<string> InvokeAsync() => null;
            }
        }
        """;
}