File: ComponentTagHelperProducerTest.cs
Web Access
Project: src\src\Razor\src\Compiler\Microsoft.CodeAnalysis.Razor\test\Microsoft.CodeAnalysis.Razor.UnitTests.csproj (Microsoft.CodeAnalysis.Razor.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.Linq;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.Language.Components;
using Microsoft.AspNetCore.Razor.Language.TagHelpers.Producers;
using Xunit;
 
namespace Microsoft.CodeAnalysis.Razor;
 
public class ComponentTagHelperProducerTest : TagHelperDescriptorProviderTestBase
{
    protected override void ConfigureEngine(RazorProjectEngineBuilder builder)
    {
        builder.Features.Add(new BindTagHelperProducer.Factory());
        builder.Features.Add(new ComponentTagHelperProducer.Factory());
    }
 
    [Fact]
    public void Execute_FindsIComponentType_CreatesDescriptor()
    {
        // Arrange
 
        var compilation = BaseCompilation.AddSyntaxTrees(Parse(@"
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
 
namespace Test
{
    public class MyComponent : IComponent
    {
        public void Attach(RenderHandle renderHandle) { }
 
        public Task SetParametersAsync(ParameterView parameters)
        {
            return Task.CompletedTask;
        }
 
        [Parameter]
        public string MyProperty { get; set; }
    }
}
 
"));
 
        Assert.Empty(compilation.GetDiagnostics());
 
        // Act
        var result = GetTagHelpers(compilation);
 
        // Assert
        var components = result.Where(c => !IsBuiltInComponent(c));
        components = AssertAndExcludeFullyQualifiedNameMatchComponents(components, expectedCount: 1);
        var component = Assert.Single(components);
 
        // These are features Components don't use. Verifying them once here and
        // then ignoring them.
        Assert.Empty(component.AllowedChildTags);
        Assert.Null(component.TagOutputHint);
 
        // These are features that are invariants of all Components. Verifying them once
        // here and then ignoring them.
        Assert.Empty(component.Diagnostics);
        Assert.False(component.HasErrors);
        Assert.Equal(TagHelperKind.Component, component.Kind);
        Assert.Equal(RuntimeKind.IComponent, component.RuntimeKind);
        Assert.False(component.IsDefaultKind());
        Assert.False(component.KindUsesDefaultTagHelperRuntime());
        Assert.True(component.IsComponentOrChildContentTagHelper());
        Assert.True(component.CaseSensitive);
 
        // No documentation in this test
        Assert.Null(component.Documentation);
 
        // These are all trivially derived from the assembly/namespace/type name
        Assert.Equal("TestAssembly", component.AssemblyName);
        Assert.Equal("Test.MyComponent", component.Name);
        Assert.Equal("Test.MyComponent", component.DisplayName);
        Assert.Equal("Test.MyComponent", component.TypeName);
        Assert.Equal("Test", component.TypeNamespace);
        Assert.Equal("MyComponent", component.TypeNameIdentifier);
 
        // Our use of matching rules is also very simple, and derived from the name. Verifying
        // it once in detail here and then ignoring it.
        var rule = Assert.Single(component.TagMatchingRules);
        Assert.Empty(rule.Attributes);
        Assert.Empty(rule.Diagnostics);
        Assert.False(rule.HasErrors);
        Assert.Null(rule.ParentTag);
        Assert.Equal("MyComponent", rule.TagName);
        Assert.Equal(TagStructure.Unspecified, rule.TagStructure);
 
        // Our use of bound attributes is what tests will focus on. As you might expect right now, this test
        // is going to cover a lot of trivial stuff that will be true for all components/component-properties.
        var attribute = Assert.Single(component.BoundAttributes);
 
        // Invariants
        Assert.Empty(attribute.Diagnostics);
        Assert.False(attribute.HasErrors);
        Assert.Equal(TagHelperKind.Component, attribute.Parent.Kind);
        Assert.False(attribute.IsDefaultKind());
 
        // Related to dictionaries/indexers, not supported currently, not sure if we ever will
        Assert.False(attribute.HasIndexer);
        Assert.Null(attribute.IndexerNamePrefix);
        Assert.Null(attribute.IndexerTypeName);
        Assert.False(attribute.IsIndexerBooleanProperty);
        Assert.False(attribute.IsIndexerStringProperty);
        Assert.True(component.CaseSensitive);
 
        // No documentation in this test
        Assert.Null(attribute.Documentation);
 
        // Names are trivially derived from the property name
        Assert.Equal("MyProperty", attribute.Name);
        Assert.Equal("MyProperty", attribute.PropertyName);
        Assert.Equal("string Test.MyComponent.MyProperty", attribute.DisplayName);
 
        // Defined from the property type
        Assert.Equal("System.String", attribute.TypeName);
        Assert.True(attribute.IsStringProperty);
        Assert.False(attribute.IsBooleanProperty);
        Assert.False(attribute.IsEnum);
        Assert.Equal("global::System.String", attribute.GetGloballyQualifiedTypeName());
    }
 
    [Fact]
    public void Execute_FindsIComponentType_CreatesDescriptor_Generic()
    {
        // Arrange
 
        var compilation = BaseCompilation.AddSyntaxTrees(Parse(@"
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
 
namespace Test
{
    public class MyComponent<T> : IComponent
    {
        public void Attach(RenderHandle renderHandle) { }
 
        public Task SetParametersAsync(ParameterView parameters)
        {
            return Task.CompletedTask;
        }
 
        [Parameter]
        public string MyProperty { get; set; }
    }
}
 
"));
 
        Assert.Empty(compilation.GetDiagnostics());
 
        // Act
        var result = GetTagHelpers(compilation);
 
        // Assert
        var components = result.Where(c => !IsBuiltInComponent(c));
        components = AssertAndExcludeFullyQualifiedNameMatchComponents(components, expectedCount: 1);
        var component = Assert.Single(components);
 
        Assert.Equal("TestAssembly", component.AssemblyName);
        Assert.Equal("Test.MyComponent<T>", component.Name);
        Assert.Equal("Test.MyComponent<T>", component.DisplayName);
        Assert.Equal("Test.MyComponent<T>", component.TypeName);
 
        Assert.True(component.IsGenericTypedComponent());
 
        var rule = Assert.Single(component.TagMatchingRules);
        Assert.Equal("MyComponent", rule.TagName);
 
        Assert.Collection(
            component.BoundAttributes.OrderBy(a => a.Name),
            a =>
            {
                Assert.Equal("MyProperty", a.Name);
                Assert.Equal("MyProperty", a.PropertyName);
                Assert.Equal("string Test.MyComponent<T>.MyProperty", a.DisplayName);
                Assert.Equal("System.String", a.TypeName);
 
            },
            a =>
            {
                Assert.Equal("T", a.Name);
                Assert.Equal("T", a.PropertyName);
                Assert.Equal("T", a.DisplayName);
                Assert.Equal("System.Type", a.TypeName);
                Assert.True(a.IsTypeParameterProperty());
            });
    }
 
    [Fact]
    public void Execute_FindsBlazorComponentType_CreatesDescriptor()
    {
        // Arrange
 
        var compilation = BaseCompilation.AddSyntaxTrees(Parse(@"
using Microsoft.AspNetCore.Components;
 
namespace Test
{
    public class MyComponent : ComponentBase
    {
        [Parameter]
        public string MyProperty { get; set; }
    }
}
 
"));
 
        Assert.Empty(compilation.GetDiagnostics());
 
        // Act
        var result = GetTagHelpers(compilation);
 
        // Assert
        var components = result.Where(c => !IsBuiltInComponent(c));
        components = AssertAndExcludeFullyQualifiedNameMatchComponents(components, expectedCount: 1);
        var component = Assert.Single(components);
 
        Assert.Equal("TestAssembly", component.AssemblyName);
        Assert.Equal("Test.MyComponent", component.Name);
 
        var attribute = Assert.Single(component.BoundAttributes);
        Assert.Equal("MyProperty", attribute.Name);
        Assert.Equal("System.String", attribute.TypeName);
    }
 
    [Fact]
    public void Execute_IgnoresPrivateParameters_CreatesDescriptor()
    {
        // Arrange
 
        var compilation = BaseCompilation.AddSyntaxTrees(Parse(@"
using Microsoft.AspNetCore.Components;
 
namespace Test
{
    public class MyComponent : ComponentBase
    {
        [Parameter]
        private string MyProperty { get; set; }
    }
}
 
"));
 
        Assert.Empty(compilation.GetDiagnostics());
 
        // Act
        var result = GetTagHelpers(compilation);
 
        // Assert
        var components = result.Where(c => !IsBuiltInComponent(c));
        components = AssertAndExcludeFullyQualifiedNameMatchComponents(components, expectedCount: 1);
        var component = Assert.Single(components);
 
        Assert.Equal("TestAssembly", component.AssemblyName);
        Assert.Equal("Test.MyComponent", component.Name);
 
        Assert.Empty(component.BoundAttributes);
    }
 
    [Fact]
    public void Execute_IgnoresParametersWithPrivateSetters_CreatesDescriptor()
    {
        // Arrange
 
        var compilation = BaseCompilation.AddSyntaxTrees(Parse(@"
using Microsoft.AspNetCore.Components;
 
namespace Test
{
    public class MyComponent : ComponentBase
    {
        [Parameter]
        public string MyProperty { get; private set; }
    }
}
 
"));
 
        Assert.Empty(compilation.GetDiagnostics());
 
        // Act
        var result = GetTagHelpers(compilation);
 
        // Assert
        var components = result.Where(c => !IsBuiltInComponent(c));
        components = AssertAndExcludeFullyQualifiedNameMatchComponents(components, expectedCount: 1);
        var component = Assert.Single(components);
 
        Assert.Equal("TestAssembly", component.AssemblyName);
        Assert.Equal("Test.MyComponent", component.Name);
 
        Assert.Empty(component.BoundAttributes);
    }
 
    [Fact] // bool properties support minimized attributes
    public void Execute_BoolProperty_CreatesDescriptor()
    {
        // Arrange
 
        var compilation = BaseCompilation.AddSyntaxTrees(Parse(@"
using Microsoft.AspNetCore.Components;
 
namespace Test
{
    public class MyComponent : ComponentBase
    {
        [Parameter]
        public bool MyProperty { get; set; }
    }
}
 
"));
 
        Assert.Empty(compilation.GetDiagnostics());
 
        // Act
        var result = GetTagHelpers(compilation);
 
        // Assert
        var components = result.Where(c => !IsBuiltInComponent(c));
        components = AssertAndExcludeFullyQualifiedNameMatchComponents(components, expectedCount: 1);
        var component = Assert.Single(components);
 
        Assert.Equal("TestAssembly", component.AssemblyName);
        Assert.Equal("Test.MyComponent", component.Name);
 
        var attribute = Assert.Single(component.BoundAttributes);
        Assert.Equal("MyProperty", attribute.Name);
        Assert.Equal("System.Boolean", attribute.TypeName);
 
        Assert.False(attribute.HasIndexer);
        Assert.True(attribute.IsBooleanProperty);
        Assert.False(attribute.IsEnum);
        Assert.False(attribute.IsStringProperty);
    }
 
    [Fact] // enum properties have some special intellisense behavior
    public void Execute_EnumProperty_CreatesDescriptor()
    {
        // Arrange
 
        var compilation = BaseCompilation.AddSyntaxTrees(Parse(@"
using Microsoft.AspNetCore.Components;
 
namespace Test
{
    public enum MyEnum
    {
        One,
        Two
    }
 
    public class MyComponent : ComponentBase
    {
        [Parameter]
        public MyEnum MyProperty { get; set; }
    }
}
 
"));
 
        Assert.Empty(compilation.GetDiagnostics());
 
        // Act
        var result = GetTagHelpers(compilation);
 
        // Assert
        var components = result.Where(c => !IsBuiltInComponent(c));
        components = AssertAndExcludeFullyQualifiedNameMatchComponents(components, expectedCount: 1);
        var component = Assert.Single(components);
 
        Assert.Equal("TestAssembly", component.AssemblyName);
        Assert.Equal("Test.MyComponent", component.Name);
 
        var attribute = Assert.Single(component.BoundAttributes);
        Assert.Equal("MyProperty", attribute.Name);
        Assert.Equal("Test.MyEnum", attribute.TypeName);
 
        Assert.False(attribute.HasIndexer);
        Assert.False(attribute.IsBooleanProperty);
        Assert.True(attribute.IsEnum);
        Assert.False(attribute.IsStringProperty);
    }
 
    [Fact] // editor-required parameters
    public void Execute_EditorRequiredProperty_CreatesDescriptor()
    {
        // Arrange
 
        var compilation = BaseCompilation.AddSyntaxTrees(Parse(@"
using Microsoft.AspNetCore.Components;
 
namespace Test
{
    public class MyComponent : ComponentBase
    {
        [Parameter]
        [EditorRequired]
        public string MyProperty { get; set; }
    }
}
 
"));
 
        Assert.Empty(compilation.GetDiagnostics());
 
        // Act
        var result = GetTagHelpers(compilation);
 
        // Assert
        var components = result.Where(c => !IsBuiltInComponent(c));
        components = AssertAndExcludeFullyQualifiedNameMatchComponents(components, expectedCount: 1);
        var component = Assert.Single(components);
 
        Assert.Equal("TestAssembly", component.AssemblyName);
        Assert.Equal("Test.MyComponent", component.Name);
 
        var attribute = Assert.Single(component.BoundAttributes);
        Assert.Equal("MyProperty", attribute.Name);
        Assert.Equal("System.String", attribute.TypeName);
 
        Assert.False(attribute.HasIndexer);
        Assert.False(attribute.IsBooleanProperty);
        Assert.False(attribute.IsEnum);
        Assert.True(attribute.IsStringProperty);
        Assert.True(attribute.IsEditorRequired);
    }
 
    [Fact]
    public void Execute_GenericProperty_CreatesDescriptor()
    {
        // Arrange
 
        var compilation = BaseCompilation.AddSyntaxTrees(Parse(@"
using Microsoft.AspNetCore.Components;
 
namespace Test
{
    public class MyComponent<T> : ComponentBase
    {
        [Parameter]
        public T MyProperty { get; set; }
    }
}
 
"));
 
        Assert.Empty(compilation.GetDiagnostics());
 
        // Act
        var result = GetTagHelpers(compilation);
 
        // Assert
        var components = result.Where(c => !IsBuiltInComponent(c));
        components = AssertAndExcludeFullyQualifiedNameMatchComponents(components, expectedCount: 1);
        var component = Assert.Single(components);
 
        Assert.Equal("TestAssembly", component.AssemblyName);
        Assert.Equal("Test.MyComponent<T>", component.Name);
 
        Assert.Collection(
            component.BoundAttributes.OrderBy(a => a.Name),
            a =>
            {
                Assert.Equal("MyProperty", a.Name);
                Assert.Equal("MyProperty", a.PropertyName);
                Assert.Equal("T Test.MyComponent<T>.MyProperty", a.DisplayName);
                Assert.Equal("T", a.TypeName);
                Assert.True(a.IsGenericTypedProperty());
 
            },
            a =>
            {
                Assert.Equal("T", a.Name);
                Assert.Equal("T", a.PropertyName);
                Assert.Equal("T", a.DisplayName);
                Assert.Equal("System.Type", a.TypeName);
                Assert.True(a.IsTypeParameterProperty());
            });
    }
 
    [Fact]
    public void Execute_MultipleGenerics_CreatesDescriptor()
    {
        // Arrange
 
        var compilation = BaseCompilation.AddSyntaxTrees(Parse(@"
using Microsoft.AspNetCore.Components;
 
namespace Test
{
    public class MyComponent<T, U, V> : ComponentBase
    {
        [Parameter]
        public T MyProperty1 { get; set; }
 
        [Parameter]
        public U MyProperty2 { get; set; }
 
        [Parameter]
        public V MyProperty3 { get; set; }
    }
}
 
"));
 
        Assert.Empty(compilation.GetDiagnostics());
 
        // Act
        var result = GetTagHelpers(compilation);
 
        // Assert
        var components = result.Where(c => !IsBuiltInComponent(c));
        components = AssertAndExcludeFullyQualifiedNameMatchComponents(components, expectedCount: 1);
        var component = Assert.Single(components);
 
        Assert.Equal("TestAssembly", component.AssemblyName);
        Assert.Equal("Test.MyComponent<T, U, V>", component.Name);
 
        Assert.Collection(
            component.BoundAttributes.OrderBy(a => a.Name),
            a =>
            {
                Assert.Equal("MyProperty1", a.Name);
                Assert.Equal("T", a.TypeName);
                Assert.True(a.IsGenericTypedProperty());
            },
            a =>
            {
                Assert.Equal("MyProperty2", a.Name);
                Assert.Equal("U", a.TypeName);
                Assert.True(a.IsGenericTypedProperty());
            },
            a =>
            {
                Assert.Equal("MyProperty3", a.Name);
                Assert.Equal("V", a.TypeName);
                Assert.True(a.IsGenericTypedProperty());
            },
            a =>
            {
                Assert.Equal("T", a.Name);
                Assert.True(a.IsTypeParameterProperty());
            },
            a =>
            {
                Assert.Equal("U", a.Name);
                Assert.True(a.IsTypeParameterProperty());
            },
            a =>
            {
                Assert.Equal("V", a.Name);
                Assert.True(a.IsTypeParameterProperty());
            });
    }
 
    [Fact]
    public void Execute_DelegateProperty_CreatesDescriptor()
    {
        // Arrange
 
        var compilation = BaseCompilation.AddSyntaxTrees(Parse(@"
using System;
using Microsoft.AspNetCore.Components;
 
namespace Test
{
    public class MyComponent : ComponentBase
    {
        [Parameter]
        public Action<EventArgs> OnClick { get; set; }
    }
}
 
"));
 
        Assert.Empty(compilation.GetDiagnostics());
 
        // Act
        var result = GetTagHelpers(compilation);
 
        // Assert
        var components = result.Where(c => !IsBuiltInComponent(c));
        components = AssertAndExcludeFullyQualifiedNameMatchComponents(components, expectedCount: 1);
        var component = Assert.Single(components);
 
        Assert.Equal("TestAssembly", component.AssemblyName);
        Assert.Equal("Test.MyComponent", component.Name);
 
        var attribute = Assert.Single(component.BoundAttributes);
        Assert.Equal("OnClick", attribute.Name);
        Assert.Equal("System.Action<System.EventArgs>", attribute.TypeName);
 
        Assert.False(attribute.HasIndexer);
        Assert.False(attribute.IsBooleanProperty);
        Assert.False(attribute.IsEnum);
        Assert.False(attribute.IsStringProperty);
        Assert.True(attribute.IsDelegateProperty());
        Assert.False(attribute.IsChildContentProperty());
    }
 
    [Fact]
    public void Execute_DelegateProperty_CreatesDescriptor_Generic()
    {
        // Arrange
 
        var compilation = BaseCompilation.AddSyntaxTrees(Parse(@"
using System;
using Microsoft.AspNetCore.Components;
 
namespace Test
{
    public class MyComponent<T> : ComponentBase
    {
        [Parameter]
        public Action<T> OnClick { get; set; }
    }
}
 
"));
 
        Assert.Empty(compilation.GetDiagnostics());
 
        // Act
        var result = GetTagHelpers(compilation);
 
        // Assert
        var components = result.Where(c => !IsBuiltInComponent(c));
        components = AssertAndExcludeFullyQualifiedNameMatchComponents(components, expectedCount: 1);
        var component = Assert.Single(components);
 
        Assert.Equal("TestAssembly", component.AssemblyName);
        Assert.Equal("Test.MyComponent<T>", component.Name);
 
        Assert.Collection(
            component.BoundAttributes.OrderBy(a => a.Name),
            a =>
            {
                Assert.Equal("OnClick", a.Name);
                Assert.Equal("System.Action<T>", a.TypeName);
                Assert.False(a.HasIndexer);
                Assert.False(a.IsBooleanProperty);
                Assert.False(a.IsEnum);
                Assert.False(a.IsStringProperty);
                Assert.True(a.IsDelegateProperty());
                Assert.False(a.IsChildContentProperty());
                Assert.True(a.IsGenericTypedProperty());
 
            },
            a =>
            {
                Assert.Equal("T", a.Name);
                Assert.Equal("T", a.PropertyName);
                Assert.Equal("T", a.DisplayName);
                Assert.Equal("System.Type", a.TypeName);
                Assert.True(a.IsTypeParameterProperty());
            });
    }
 
    [Fact]
    public void Execute_EventCallbackProperty_CreatesDescriptor()
    {
        // Arrange
 
        var compilation = BaseCompilation.AddSyntaxTrees(Parse(@"
using Microsoft.AspNetCore.Components;
 
namespace Test
{
    public class MyComponent : ComponentBase
    {
        [Parameter]
        public EventCallback OnClick { get; set; }
    }
}
 
"));
 
        Assert.Empty(compilation.GetDiagnostics());
 
        // Act
        var result = GetTagHelpers(compilation);
 
        // Assert
        var components = result.Where(c => !IsBuiltInComponent(c));
        components = AssertAndExcludeFullyQualifiedNameMatchComponents(components, expectedCount: 1);
        var component = Assert.Single(components);
 
        Assert.Equal("TestAssembly", component.AssemblyName);
        Assert.Equal("Test.MyComponent", component.Name);
 
        var attribute = Assert.Single(component.BoundAttributes);
        Assert.Equal("OnClick", attribute.Name);
        Assert.Equal("Microsoft.AspNetCore.Components.EventCallback", attribute.TypeName);
 
        Assert.False(attribute.HasIndexer);
        Assert.False(attribute.IsBooleanProperty);
        Assert.False(attribute.IsEnum);
        Assert.False(attribute.IsStringProperty);
        Assert.True(attribute.IsEventCallbackProperty());
        Assert.False(attribute.IsDelegateProperty());
        Assert.False(attribute.IsChildContentProperty());
    }
 
    [Fact]
    public void Execute_EventCallbackProperty_CreatesDescriptor_ClosedGeneric()
    {
        // Arrange
 
        var compilation = BaseCompilation.AddSyntaxTrees(Parse(@"
using System;
using Microsoft.AspNetCore.Components;
 
namespace Test
{
    public class MyComponent : ComponentBase
    {
        [Parameter]
        public EventCallback<EventArgs> OnClick { get; set; }
    }
}
 
"));
 
        Assert.Empty(compilation.GetDiagnostics());
 
        // Act
        var result = GetTagHelpers(compilation);
 
        // Assert
        var components = result.Where(c => !IsBuiltInComponent(c));
        components = AssertAndExcludeFullyQualifiedNameMatchComponents(components, expectedCount: 1);
        var component = Assert.Single(components);
 
        Assert.Equal("TestAssembly", component.AssemblyName);
        Assert.Equal("Test.MyComponent", component.Name);
 
        Assert.Collection(
            component.BoundAttributes.OrderBy(a => a.Name),
            a =>
            {
                Assert.Equal("OnClick", a.Name);
                Assert.Equal("Microsoft.AspNetCore.Components.EventCallback<System.EventArgs>", a.TypeName);
                Assert.False(a.HasIndexer);
                Assert.False(a.IsBooleanProperty);
                Assert.False(a.IsEnum);
                Assert.False(a.IsStringProperty);
                Assert.True(a.IsEventCallbackProperty());
                Assert.False(a.IsDelegateProperty());
                Assert.False(a.IsChildContentProperty());
                Assert.False(a.IsGenericTypedProperty());
 
            });
    }
 
    [Fact]
    public void Execute_EventCallbackProperty_CreatesDescriptor_OpenGeneric()
    {
        // Arrange
 
        var compilation = BaseCompilation.AddSyntaxTrees(Parse(@"
using Microsoft.AspNetCore.Components;
 
namespace Test
{
    public class MyComponent<T> : ComponentBase
    {
        [Parameter]
        public EventCallback<T> OnClick { get; set; }
    }
}
 
"));
 
        Assert.Empty(compilation.GetDiagnostics());
 
        // Act
        var result = GetTagHelpers(compilation);
 
        // Assert
        var components = result.Where(c => !IsBuiltInComponent(c));
        components = AssertAndExcludeFullyQualifiedNameMatchComponents(components, expectedCount: 1);
        var component = Assert.Single(components);
 
        Assert.Equal("TestAssembly", component.AssemblyName);
        Assert.Equal("Test.MyComponent<T>", component.Name);
 
        Assert.Collection(
            component.BoundAttributes.OrderBy(a => a.Name),
            a =>
            {
                Assert.Equal("OnClick", a.Name);
                Assert.Equal("Microsoft.AspNetCore.Components.EventCallback<T>", a.TypeName);
                Assert.False(a.HasIndexer);
                Assert.False(a.IsBooleanProperty);
                Assert.False(a.IsEnum);
                Assert.False(a.IsStringProperty);
                Assert.True(a.IsEventCallbackProperty());
                Assert.False(a.IsDelegateProperty());
                Assert.False(a.IsChildContentProperty());
                Assert.True(a.IsGenericTypedProperty());
 
            },
            a =>
            {
                Assert.Equal("T", a.Name);
                Assert.Equal("T", a.PropertyName);
                Assert.Equal("T", a.DisplayName);
                Assert.Equal("System.Type", a.TypeName);
                Assert.True(a.IsTypeParameterProperty());
            });
    }
 
    [Fact]
    public void Execute_RenderFragmentProperty_CreatesDescriptors()
    {
        // Arrange
 
        var compilation = BaseCompilation.AddSyntaxTrees(Parse(@"
using Microsoft.AspNetCore.Components;
 
namespace Test
{
    public class MyComponent : ComponentBase
    {
        [Parameter]
        public RenderFragment ChildContent2 { get; set; }
    }
}
 
"));
 
        Assert.Empty(compilation.GetDiagnostics());
 
        // Act
        var result = GetTagHelpers(compilation);
 
        // Assert
        var components = result.Where(c => !IsBuiltInComponent(c));
        components = AssertAndExcludeFullyQualifiedNameMatchComponents(components, expectedCount: 2);
        var component = Assert.Single(components, c => c.Kind == TagHelperKind.Component);
 
        Assert.Equal("TestAssembly", component.AssemblyName);
        Assert.Equal("Test.MyComponent", component.Name);
 
        var attribute = Assert.Single(component.BoundAttributes);
        Assert.Equal("ChildContent2", attribute.Name);
        Assert.Equal("Microsoft.AspNetCore.Components.RenderFragment", attribute.TypeName);
 
        Assert.False(attribute.HasIndexer);
        Assert.False(attribute.IsBooleanProperty);
        Assert.False(attribute.IsEnum);
        Assert.False(attribute.IsStringProperty);
        Assert.False(attribute.IsDelegateProperty()); // We treat RenderFragment as separate from generalized delegates
        Assert.True(attribute.IsChildContentProperty());
        Assert.False(attribute.IsParameterizedChildContentProperty());
 
        var childContent = Assert.Single(components, c => c.Kind == TagHelperKind.ChildContent);
 
        Assert.Equal("TestAssembly", childContent.AssemblyName);
        Assert.Equal("Test.MyComponent.ChildContent2", childContent.Name);
 
        Assert.Empty(childContent.BoundAttributes);
    }
 
    [Fact]
    public void Execute_RenderFragmentOfTProperty_CreatesDescriptor()
    {
        // Arrange
 
        var compilation = BaseCompilation.AddSyntaxTrees(Parse(@"
using Microsoft.AspNetCore.Components;
 
namespace Test
{
    public class MyComponent : ComponentBase
    {
        [Parameter]
        public RenderFragment<string> ChildContent2 { get; set; }
    }
}
 
"));
 
        Assert.Empty(compilation.GetDiagnostics());
 
        // Act
        var result = GetTagHelpers(compilation);
 
        // Assert
        var components = result.Where(c => !IsBuiltInComponent(c));
        components = AssertAndExcludeFullyQualifiedNameMatchComponents(components, expectedCount: 2);
        var component = Assert.Single(components, c => c.Kind == TagHelperKind.Component);
 
        Assert.Equal("TestAssembly", component.AssemblyName);
        Assert.Equal("Test.MyComponent", component.Name);
 
        Assert.Collection(
            component.BoundAttributes,
            a =>
            {
                Assert.Equal("ChildContent2", a.Name);
                Assert.Equal("Microsoft.AspNetCore.Components.RenderFragment<System.String>", a.TypeName);
 
                Assert.False(a.HasIndexer);
                Assert.False(a.IsBooleanProperty);
                Assert.False(a.IsEnum);
                Assert.False(a.IsStringProperty);
                Assert.False(a.IsDelegateProperty()); // We treat RenderFragment as separate from generalized delegates
                Assert.True(a.IsChildContentProperty());
                Assert.True(a.IsParameterizedChildContentProperty());
                Assert.False(a.IsGenericTypedProperty());
            },
            a =>
            {
                Assert.Equal(ComponentHelpers.ChildContent.ParameterAttributeName, a.Name);
                Assert.True(a.IsChildContentParameterNameProperty());
            });
 
        var childContent = Assert.Single(components, c => c.Kind == TagHelperKind.ChildContent);
 
        Assert.Equal("TestAssembly", childContent.AssemblyName);
        Assert.Equal("Test.MyComponent.ChildContent2", childContent.Name);
 
        // A RenderFragment<T> tag helper has a parameter to allow you to set the lambda parameter name.
        var contextAttribute = Assert.Single(childContent.BoundAttributes);
        Assert.Equal(ComponentHelpers.ChildContent.ParameterAttributeName, contextAttribute.Name);
        Assert.Equal("System.String", contextAttribute.TypeName);
        Assert.Equal("Specifies the parameter name for the 'ChildContent2' child content expression.", contextAttribute.Documentation);
        Assert.True(contextAttribute.IsChildContentParameterNameProperty());
    }
 
    [Fact]
    public void Execute_RenderFragmentOfTProperty_ComponentDefinesContextParameter()
    {
        // Arrange
 
        var compilation = BaseCompilation.AddSyntaxTrees(Parse(@"
using Microsoft.AspNetCore.Components;
 
namespace Test
{
    public class MyComponent : ComponentBase
    {
        [Parameter]
        public RenderFragment<string> ChildContent2 { get; set; }
 
        [Parameter]
        public string Context { get; set; }
    }
}
 
"));
 
        Assert.Empty(compilation.GetDiagnostics());
 
        // Act
        var result = GetTagHelpers(compilation);
 
        // Assert
        var components = result.Where(c => !IsBuiltInComponent(c));
        components = AssertAndExcludeFullyQualifiedNameMatchComponents(components, expectedCount: 2);
        var component = Assert.Single(components, c => c.Kind == TagHelperKind.Component);
 
        Assert.Equal("TestAssembly", component.AssemblyName);
        Assert.Equal("Test.MyComponent", component.Name);
 
        Assert.Collection(
            component.BoundAttributes,
            a =>
            {
                Assert.Equal("ChildContent2", a.Name);
                Assert.Equal("Microsoft.AspNetCore.Components.RenderFragment<System.String>", a.TypeName);
 
                Assert.False(a.HasIndexer);
                Assert.False(a.IsBooleanProperty);
                Assert.False(a.IsEnum);
                Assert.False(a.IsStringProperty);
                Assert.False(a.IsDelegateProperty()); // We treat RenderFragment as separate from generalized delegates
                Assert.True(a.IsChildContentProperty());
                Assert.True(a.IsParameterizedChildContentProperty());
                Assert.False(a.IsGenericTypedProperty());
            },
            a =>
            {
                Assert.Equal(ComponentHelpers.ChildContent.ParameterAttributeName, a.Name);
                Assert.False(a.IsChildContentParameterNameProperty());
            });
 
        var childContent = Assert.Single(components, c => c.Kind == TagHelperKind.ChildContent);
 
        Assert.Equal("TestAssembly", childContent.AssemblyName);
        Assert.Equal("Test.MyComponent.ChildContent2", childContent.Name);
 
        // A RenderFragment<T> tag helper has a parameter to allow you to set the lambda parameter name.
        var contextAttribute = Assert.Single(childContent.BoundAttributes);
        Assert.Equal(ComponentHelpers.ChildContent.ParameterAttributeName, contextAttribute.Name);
        Assert.Equal("System.String", contextAttribute.TypeName);
        Assert.Equal("Specifies the parameter name for the 'ChildContent2' child content expression.", contextAttribute.Documentation);
        Assert.True(contextAttribute.IsChildContentParameterNameProperty());
    }
 
    [Fact]
    public void Execute_RenderFragmentGenericProperty_CreatesDescriptor()
    {
        // Arrange
 
        var compilation = BaseCompilation.AddSyntaxTrees(Parse(@"
using Microsoft.AspNetCore.Components;
 
namespace Test
{
    public class MyComponent<T> : ComponentBase
    {
        [Parameter]
        public RenderFragment<T> ChildContent2 { get; set; }
    }
}
 
"));
 
        Assert.Empty(compilation.GetDiagnostics());
 
        // Act
        var result = GetTagHelpers(compilation);
 
        // Assert
        var components = result.Where(c => !IsBuiltInComponent(c));
        components = AssertAndExcludeFullyQualifiedNameMatchComponents(components, expectedCount: 2);
        var component = Assert.Single(components, c => c.Kind == TagHelperKind.Component);
 
        Assert.Equal("TestAssembly", component.AssemblyName);
        Assert.Equal("Test.MyComponent<T>", component.Name);
 
        Assert.Collection(
            component.BoundAttributes.OrderBy(a => a.Name),
            a =>
            {
                Assert.Equal("ChildContent2", a.Name);
                Assert.Equal("Microsoft.AspNetCore.Components.RenderFragment<T>", a.TypeName);
 
                Assert.False(a.HasIndexer);
                Assert.False(a.IsBooleanProperty);
                Assert.False(a.IsEnum);
                Assert.False(a.IsStringProperty);
                Assert.False(a.IsDelegateProperty()); // We treat RenderFragment as separate from generalized delegates
                Assert.True(a.IsChildContentProperty());
                Assert.True(a.IsParameterizedChildContentProperty());
                Assert.True(a.IsGenericTypedProperty());
 
            },
            a =>
            {
                Assert.Equal(ComponentHelpers.ChildContent.ParameterAttributeName, a.Name);
                Assert.True(a.IsChildContentParameterNameProperty());
            },
            a =>
            {
                Assert.Equal("T", a.Name);
                Assert.Equal("T", a.PropertyName);
                Assert.Equal("T", a.DisplayName);
                Assert.Equal("System.Type", a.TypeName);
                Assert.True(a.IsTypeParameterProperty());
            });
 
        var childContent = Assert.Single(components, c => c.Kind == TagHelperKind.ChildContent);
 
        Assert.Equal("TestAssembly", childContent.AssemblyName);
        Assert.Equal("Test.MyComponent<T>.ChildContent2", childContent.Name);
 
        // A RenderFragment<T> tag helper has a parameter to allow you to set the lambda parameter name.
        var contextAttribute = Assert.Single(childContent.BoundAttributes);
        Assert.Equal(ComponentHelpers.ChildContent.ParameterAttributeName, contextAttribute.Name);
        Assert.Equal("System.String", contextAttribute.TypeName);
        Assert.Equal("Specifies the parameter name for the 'ChildContent2' child content expression.", contextAttribute.Documentation);
        Assert.True(contextAttribute.IsChildContentParameterNameProperty());
    }
 
    [Fact]
    public void Execute_RenderFragmentClosedGenericListProperty_CreatesDescriptor()
    {
        // Arrange
 
        var compilation = BaseCompilation.AddSyntaxTrees(Parse(@"
using System.Collections.Generic;
using Microsoft.AspNetCore.Components;
 
namespace Test
{
    public class MyComponent<T> : ComponentBase
    {
        [Parameter]
        public RenderFragment<List<string>> ChildContent2 { get; set; }
    }
}
 
"));
 
        Assert.Empty(compilation.GetDiagnostics());
 
        // Act
        var result = GetTagHelpers(compilation);
 
        // Assert
        var components = result.Where(c => !IsBuiltInComponent(c));
        components = AssertAndExcludeFullyQualifiedNameMatchComponents(components, expectedCount: 2);
        var component = Assert.Single(components, c => c.Kind == TagHelperKind.Component);
 
        Assert.Equal("TestAssembly", component.AssemblyName);
        Assert.Equal("Test.MyComponent<T>", component.Name);
 
        Assert.Collection(
            component.BoundAttributes.OrderBy(a => a.Name),
            a =>
            {
                Assert.Equal("ChildContent2", a.Name);
                Assert.Equal("Microsoft.AspNetCore.Components.RenderFragment<System.Collections.Generic.List<System.String>>", a.TypeName);
 
                Assert.False(a.HasIndexer);
                Assert.False(a.IsBooleanProperty);
                Assert.False(a.IsEnum);
                Assert.False(a.IsStringProperty);
                Assert.False(a.IsDelegateProperty()); // We treat RenderFragment as separate from generalized delegates
                Assert.True(a.IsChildContentProperty());
                Assert.True(a.IsParameterizedChildContentProperty());
                Assert.False(a.IsGenericTypedProperty());
 
            },
            a =>
            {
                Assert.Equal(ComponentHelpers.ChildContent.ParameterAttributeName, a.Name);
                Assert.True(a.IsChildContentParameterNameProperty());
            },
            a =>
            {
                Assert.Equal("T", a.Name);
                Assert.Equal("T", a.PropertyName);
                Assert.Equal("T", a.DisplayName);
                Assert.Equal("System.Type", a.TypeName);
                Assert.True(a.IsTypeParameterProperty());
            });
 
        var childContent = Assert.Single(components, c => c.Kind == TagHelperKind.ChildContent);
 
        Assert.Equal("TestAssembly", childContent.AssemblyName);
        Assert.Equal("Test.MyComponent<T>.ChildContent2", childContent.Name);
 
        // A RenderFragment<T> tag helper has a parameter to allow you to set the lambda parameter name.
        var contextAttribute = Assert.Single(childContent.BoundAttributes);
        Assert.Equal(ComponentHelpers.ChildContent.ParameterAttributeName, contextAttribute.Name);
        Assert.Equal("System.String", contextAttribute.TypeName);
        Assert.Equal("Specifies the parameter name for the 'ChildContent2' child content expression.", contextAttribute.Documentation);
        Assert.True(contextAttribute.IsChildContentParameterNameProperty());
    }
 
    [Fact]
    public void Execute_RenderFragmentGenericListProperty_CreatesDescriptor()
    {
        // Arrange
 
        var compilation = BaseCompilation.AddSyntaxTrees(Parse(@"
using System.Collections.Generic;
using Microsoft.AspNetCore.Components;
 
namespace Test
{
    public class MyComponent<T> : ComponentBase
    {
        [Parameter]
        public RenderFragment<List<T>> ChildContent2 { get; set; }
    }
}
 
"));
 
        Assert.Empty(compilation.GetDiagnostics());
 
        // Act
        var result = GetTagHelpers(compilation);
 
        // Assert
        var components = result.Where(c => !IsBuiltInComponent(c));
        components = AssertAndExcludeFullyQualifiedNameMatchComponents(components, expectedCount: 2);
        var component = Assert.Single(components, c => c.Kind == TagHelperKind.Component);
 
        Assert.Equal("TestAssembly", component.AssemblyName);
        Assert.Equal("Test.MyComponent<T>", component.Name);
 
        Assert.Collection(
            component.BoundAttributes.OrderBy(a => a.Name),
            a =>
            {
                Assert.Equal("ChildContent2", a.Name);
                Assert.Equal("Microsoft.AspNetCore.Components.RenderFragment<System.Collections.Generic.List<T>>", a.TypeName);
 
                Assert.False(a.HasIndexer);
                Assert.False(a.IsBooleanProperty);
                Assert.False(a.IsEnum);
                Assert.False(a.IsStringProperty);
                Assert.False(a.IsDelegateProperty()); // We treat RenderFragment as separate from generalized delegates
                Assert.True(a.IsChildContentProperty());
                Assert.True(a.IsParameterizedChildContentProperty());
                Assert.True(a.IsGenericTypedProperty());
 
            },
            a =>
            {
                Assert.Equal(ComponentHelpers.ChildContent.ParameterAttributeName, a.Name);
                Assert.True(a.IsChildContentParameterNameProperty());
            },
            a =>
            {
                Assert.Equal("T", a.Name);
                Assert.Equal("T", a.PropertyName);
                Assert.Equal("T", a.DisplayName);
                Assert.Equal("System.Type", a.TypeName);
                Assert.True(a.IsTypeParameterProperty());
            });
 
        var childContent = Assert.Single(components, c => c.Kind == TagHelperKind.ChildContent);
 
        Assert.Equal("TestAssembly", childContent.AssemblyName);
        Assert.Equal("Test.MyComponent<T>.ChildContent2", childContent.Name);
 
        // A RenderFragment<T> tag helper has a parameter to allow you to set the lambda parameter name.
        var contextAttribute = Assert.Single(childContent.BoundAttributes);
        Assert.Equal(ComponentHelpers.ChildContent.ParameterAttributeName, contextAttribute.Name);
        Assert.Equal("System.String", contextAttribute.TypeName);
        Assert.Equal("Specifies the parameter name for the 'ChildContent2' child content expression.", contextAttribute.Documentation);
        Assert.True(contextAttribute.IsChildContentParameterNameProperty());
    }
 
    [Fact]
    public void Execute_RenderFragmentGenericContextProperty_CreatesDescriptor()
    {
        // Arrange
 
        var compilation = BaseCompilation.AddSyntaxTrees(Parse(@"
using Microsoft.AspNetCore.Components;
 
namespace Test
{
    public class MyComponent<T> : ComponentBase
    {
        [Parameter]
        public RenderFragment<Context> ChildContent2 { get; set; }
 
        public class Context
        {
            public T Item { get; set; }
        }
    }
}
 
"));
 
        Assert.Empty(compilation.GetDiagnostics());
 
        // Act
        var result = GetTagHelpers(compilation);
 
        // Assert
        var components = result.Where(c => !IsBuiltInComponent(c));
        components = AssertAndExcludeFullyQualifiedNameMatchComponents(components, expectedCount: 2);
        var component = Assert.Single(components, c => c.Kind == TagHelperKind.Component);
 
        Assert.Equal("TestAssembly", component.AssemblyName);
        Assert.Equal("Test.MyComponent<T>", component.Name);
 
        Assert.Collection(
            component.BoundAttributes.OrderBy(a => a.Name),
            a =>
            {
                Assert.Equal("ChildContent2", a.Name);
                Assert.Equal("Microsoft.AspNetCore.Components.RenderFragment<Test.MyComponent<T>.Context>", a.TypeName);
 
                Assert.False(a.HasIndexer);
                Assert.False(a.IsBooleanProperty);
                Assert.False(a.IsEnum);
                Assert.False(a.IsStringProperty);
                Assert.False(a.IsDelegateProperty()); // We treat RenderFragment as separate from generalized delegates
                Assert.True(a.IsChildContentProperty());
                Assert.True(a.IsParameterizedChildContentProperty());
                Assert.True(a.IsGenericTypedProperty());
 
            },
            a =>
            {
                Assert.Equal(ComponentHelpers.ChildContent.ParameterAttributeName, a.Name);
                Assert.True(a.IsChildContentParameterNameProperty());
            },
            a =>
            {
                Assert.Equal("T", a.Name);
                Assert.Equal("T", a.PropertyName);
                Assert.Equal("T", a.DisplayName);
                Assert.Equal("System.Type", a.TypeName);
                Assert.True(a.IsTypeParameterProperty());
            });
 
        var childContent = Assert.Single(components, c => c.Kind == TagHelperKind.ChildContent);
 
        Assert.Equal("TestAssembly", childContent.AssemblyName);
        Assert.Equal("Test.MyComponent<T>.ChildContent2", childContent.Name);
 
        // A RenderFragment<T> tag helper has a parameter to allow you to set the lambda parameter name.
        var contextAttribute = Assert.Single(childContent.BoundAttributes);
        Assert.Equal(ComponentHelpers.ChildContent.ParameterAttributeName, contextAttribute.Name);
        Assert.Equal("System.String", contextAttribute.TypeName);
        Assert.Equal("Specifies the parameter name for the 'ChildContent2' child content expression.", contextAttribute.Documentation);
    }
 
    [Fact]
    public void Execute_MultipleRenderFragmentProperties_CreatesDescriptor()
    {
        // Arrange
 
        var compilation = BaseCompilation.AddSyntaxTrees(Parse(@"
using Microsoft.AspNetCore.Components;
 
namespace Test
{
    public class MyComponent : ComponentBase
    {
        [Parameter]
        public RenderFragment ChildContent { get; set; }
 
        [Parameter]
        public RenderFragment<string> Header { get; set; }
 
        [Parameter]
        public RenderFragment<string> Footer { get; set; }
    }
}
 
"));
 
        Assert.Empty(compilation.GetDiagnostics());
 
        // Act
        var result = GetTagHelpers(compilation);
 
        // Assert
        var components = result.Where(c => !IsBuiltInComponent(c));
        components = AssertAndExcludeFullyQualifiedNameMatchComponents(components, expectedCount: 4);
        var component = Assert.Single(components, c => c.Kind == TagHelperKind.Component);
 
        Assert.Equal("TestAssembly", component.AssemblyName);
        Assert.Equal("Test.MyComponent", component.Name);
 
        Assert.Collection(
            component.BoundAttributes.OrderBy(a => a.Name),
            a =>
            {
                Assert.Equal("ChildContent", a.Name);
                Assert.Equal("Microsoft.AspNetCore.Components.RenderFragment", a.TypeName);
                Assert.True(a.IsChildContentProperty());
            },
            a =>
            {
                Assert.Equal(ComponentHelpers.ChildContent.ParameterAttributeName, a.Name);
                Assert.True(a.IsChildContentParameterNameProperty());
            },
            a =>
            {
                Assert.Equal("Footer", a.Name);
                Assert.Equal("Microsoft.AspNetCore.Components.RenderFragment<System.String>", a.TypeName);
                Assert.True(a.IsChildContentProperty());
            },
            a =>
            {
                Assert.Equal("Header", a.Name);
                Assert.Equal("Microsoft.AspNetCore.Components.RenderFragment<System.String>", a.TypeName);
                Assert.True(a.IsChildContentProperty());
            });
 
 
        var childContents = components.Where(c => c.Kind == TagHelperKind.ChildContent).OrderBy(c => c.Name);
        Assert.Collection(
            childContents,
            c => Assert.Equal("Test.MyComponent.ChildContent", c.Name),
            c => Assert.Equal("Test.MyComponent.Footer", c.Name),
            c => Assert.Equal("Test.MyComponent.Header", c.Name));
    }
 
    [Fact] // This component has lots of properties that don't become components.
    public void Execute_IgnoredProperties_CreatesDescriptor()
    {
        // Arrange
 
        var compilation = BaseCompilation.AddSyntaxTrees(Parse(@"
using Microsoft.AspNetCore.Components;
 
namespace Test
{
    public abstract class MyBase : ComponentBase
    {
        [Parameter]
        public string Hidden { get; set; }
    }
 
    public class MyComponent : MyBase
    {
        [Parameter]
        public string NoSetter { get; }
 
        [Parameter]
        public static string StaticProperty { get; set; }
 
        public string NoParameterAttribute { get; set; }
 
        // No attribute here, hides base-class property of the same name.
        public new int Hidden { get; set; }
 
        public string this[int i]
        {
            get { throw null; }
            set { throw null; }
        }
    }
}
 
"));
 
        Assert.Empty(compilation.GetDiagnostics());
 
        // Act
        var result = GetTagHelpers(compilation);
 
        // Assert
        var components = result.Where(c => !IsBuiltInComponent(c));
        components = AssertAndExcludeFullyQualifiedNameMatchComponents(components, expectedCount: 1);
        var component = Assert.Single(components);
 
        Assert.Equal("TestAssembly", component.AssemblyName);
        Assert.Equal("Test.MyComponent", component.Name);
 
        Assert.Empty(component.BoundAttributes);
    }
 
    [Fact] // Testing multilevel overrides with the [Parameter] attribute on different levels.
    public void Execute_MultiLevelOverriddenProperties_CreatesDescriptorCorrectly()
    {
        // Arrange
 
        var compilation = BaseCompilation.AddSyntaxTrees(Parse(@"
using Microsoft.AspNetCore.Components;
 
namespace Test
{
    public abstract class MyBaseComponent : ComponentBase
    {
        [Parameter]
        public virtual string Header { get; set; }
 
        public virtual string Footer { get; set; }
    }
 
    public abstract class MyDerivedComponent1 : MyBaseComponent
    {
        public override string Header { get; set; }
 
        [Parameter]
        public override string Footer { get; set; }
    }
 
    public class MyDerivedComponent2 : MyDerivedComponent1
    {
        public override string Header { get; set; }
 
        public override string Footer { get; set; }
    }
}
"));
 
        Assert.Empty(compilation.GetDiagnostics());
 
        // Act
        var result = GetTagHelpers(compilation);
 
        // Assert
        var components = result.Where(c => !IsBuiltInComponent(c));
        components = AssertAndExcludeFullyQualifiedNameMatchComponents(components, expectedCount: 1);
        var component = Assert.Single(components, c => c.Kind == TagHelperKind.Component);
 
        Assert.Equal("TestAssembly", component.AssemblyName);
        Assert.Equal("Test.MyDerivedComponent2", component.Name);
 
        Assert.Collection(
            component.BoundAttributes.OrderBy(a => a.Name),
            a =>
            {
                Assert.Equal("Footer", a.Name);
                Assert.Equal("System.String", a.TypeName);
            },
            a =>
            {
                Assert.Equal("Header", a.Name);
                Assert.Equal("System.String", a.TypeName);
            });
    }
 
    [Fact]
    public void Execute_WithTargetAssembly_Works()
    {
        // Arrange
        var testComponent = "Test.MyComponent";
        var routerComponent = "Microsoft.AspNetCore.Components.Routing.Router";
        var compilation = BaseCompilation.AddSyntaxTrees(Parse(@"
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
 
namespace Test
{
    public class MyComponent : IComponent
    {
        public void Attach(RenderHandle renderHandle) { }
 
        public Task SetParametersAsync(ParameterView parameters)
        {
            return Task.CompletedTask;
        }
 
        [Parameter]
        public string MyProperty { get; set; }
    }
}
 
"));
 
        Assert.Empty(compilation.GetDiagnostics());
 
        var targetAssembly = (IAssemblySymbol?)compilation.GetAssemblyOrModuleSymbol(
            compilation.References.First(static r => r.Display?.Contains("Microsoft.CodeAnalysis.Razor.Test") == true));
        Assert.NotNull(targetAssembly);
 
        // Act
        Assert.True(TryGetDiscoverer(compilation, out var discoverer));
        var result = discoverer.GetTagHelpers(targetAssembly);
 
        // Assert
        Assert.NotNull(compilation.GetTypeByMetadataName(testComponent));
        Assert.Empty(result); // Target assembly contains no components
        Assert.DoesNotContain(result, f => f.TypeName == testComponent);
        Assert.DoesNotContain(result, f => f.TypeName == routerComponent);
    }
 
    [Fact]
    public void Execute_WithDefaultDiscoversTagHelpersFromAssemblyAndReference()
    {
        // Arrange
        var testComponent = "Test.MyComponent";
        var routerComponent
            = "Microsoft.AspNetCore.Components.Routing.Router";
        var compilation = BaseCompilation.AddSyntaxTrees(Parse(@"
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
 
namespace Test
{
    public class MyComponent : IComponent
    {
        public void Attach(RenderHandle renderHandle) { }
 
        public Task SetParametersAsync(ParameterView parameters)
        {
            return Task.CompletedTask;
        }
 
        [Parameter]
        public string MyProperty { get; set; }
    }
}
 
"));
 
        Assert.Empty(compilation.GetDiagnostics());
 
        // Act
        var result = GetTagHelpers(compilation);
 
        // Assert
        Assert.NotNull(compilation.GetTypeByMetadataName(testComponent));
        Assert.NotEmpty(result);
        Assert.NotEmpty(result.Where(f => f.TypeName == testComponent));
        Assert.NotEmpty(result.Where(f => f.TypeName == routerComponent));
    }
}