Implement basic Component discovery
This is the basics of component discovery along with some tests. The next set of changes will integrate it into the compilation process.
This commit is contained in:
parent
2a2a045863
commit
daf6a404f9
|
|
@ -87,6 +87,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorHosted.CSharp.Server"
|
|||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlazorHosted.CSharp.Shared", "src\Microsoft.AspNetCore.Blazor.Templates\content\BlazorHosted.CSharp\BlazorHosted.CSharp.Shared\BlazorHosted.CSharp.Shared.csproj", "{F3E02B21-1127-431A-B832-0E53CB72097B}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Blazor.Razor.Extensions.Test", "test\Microsoft.AspNetCore.Blazor.Razor.Extensions.Test\Microsoft.AspNetCore.Blazor.Razor.Extensions.Test.csproj", "{FF25111E-5A3E-48A3-96D8-08A2C5A2A91C}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
|
@ -209,6 +211,10 @@ Global
|
|||
{F3E02B21-1127-431A-B832-0E53CB72097B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F3E02B21-1127-431A-B832-0E53CB72097B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{F3E02B21-1127-431A-B832-0E53CB72097B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{FF25111E-5A3E-48A3-96D8-08A2C5A2A91C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{FF25111E-5A3E-48A3-96D8-08A2C5A2A91C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{FF25111E-5A3E-48A3-96D8-08A2C5A2A91C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{FF25111E-5A3E-48A3-96D8-08A2C5A2A91C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
@ -249,6 +255,7 @@ Global
|
|||
{7549444A-9C81-44DE-AD0D-2C55501EAAC7} = {73DA1DFD-79F0-4BA2-B0B6-4F3A21D2C3F8}
|
||||
{78ED9932-0912-4F36-8F82-33DE850E7A33} = {73DA1DFD-79F0-4BA2-B0B6-4F3A21D2C3F8}
|
||||
{F3E02B21-1127-431A-B832-0E53CB72097B} = {73DA1DFD-79F0-4BA2-B0B6-4F3A21D2C3F8}
|
||||
{FF25111E-5A3E-48A3-96D8-08A2C5A2A91C} = {ADA3AE29-F6DE-49F6-8C7C-B321508CAE8E}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {504DA352-6788-4DC0-8705-82167E72A4D3}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,18 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
|||
public static readonly string FullTypeName = "Microsoft.AspNetCore.Blazor.Layouts.LayoutAttribute";
|
||||
}
|
||||
|
||||
public static class IComponent
|
||||
{
|
||||
public static readonly string FullTypeName = "Microsoft.AspNetCore.Blazor.Components.IComponent";
|
||||
|
||||
public static readonly string MetadataName = FullTypeName;
|
||||
}
|
||||
|
||||
public static class IDictionary
|
||||
{
|
||||
public static readonly string MetadataName = "System.Collection.IDictionary`2";
|
||||
}
|
||||
|
||||
public static class RenderFragment
|
||||
{
|
||||
public static readonly string FullTypeName = "Microsoft.AspNetCore.Blazor.RenderFragment";
|
||||
|
|
|
|||
|
|
@ -12,7 +12,9 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
|||
{
|
||||
internal class ComponentDocumentClassifierPass : DocumentClassifierPassBase, IRazorDocumentClassifierPass
|
||||
{
|
||||
protected override string DocumentKind => "Blazor.Component-0.1";
|
||||
public static readonly string ComponentDocumentKind = "Blazor.Component-0.1";
|
||||
|
||||
protected override string DocumentKind => ComponentDocumentKind;
|
||||
|
||||
protected override bool IsMatch(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,247 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Razor
|
||||
{
|
||||
internal class ComponentTagHelperDescriptorProvider : RazorEngineFeatureBase, ITagHelperDescriptorProvider
|
||||
{
|
||||
public readonly static string ComponentTagHelperKind = ComponentDocumentClassifierPass.ComponentDocumentKind;
|
||||
|
||||
private static readonly SymbolDisplayFormat FullNameTypeDisplayFormat =
|
||||
SymbolDisplayFormat.FullyQualifiedFormat
|
||||
.WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.Omitted)
|
||||
.WithMiscellaneousOptions(SymbolDisplayFormat.FullyQualifiedFormat.MiscellaneousOptions & (~SymbolDisplayMiscellaneousOptions.UseSpecialTypes));
|
||||
|
||||
public bool IncludeDocumentation { get; set; }
|
||||
|
||||
public int Order { get; set; }
|
||||
|
||||
public void Execute(TagHelperDescriptorProviderContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
var compilation = context.GetCompilation();
|
||||
if (compilation == null)
|
||||
{
|
||||
// No compilation, nothing to do.
|
||||
return;
|
||||
}
|
||||
|
||||
var componentSymbol = compilation.GetTypeByMetadataName(BlazorApi.IComponent.MetadataName);
|
||||
if (componentSymbol == null)
|
||||
{
|
||||
// No definition for IComponent, nothing to do.
|
||||
return;
|
||||
}
|
||||
|
||||
var types = new List<INamedTypeSymbol>();
|
||||
var visitor = new ComponentTypeVisitor(componentSymbol, types);
|
||||
|
||||
// Visit the primary output of this compilation, as well as all references.
|
||||
visitor.Visit(compilation.Assembly);
|
||||
foreach (var reference in compilation.References)
|
||||
{
|
||||
// We ignore .netmodules here - there really isn't a case where they are used by user code
|
||||
// even though the Roslyn APIs all support them.
|
||||
if (compilation.GetAssemblyOrModuleSymbol(reference) is IAssemblySymbol assembly)
|
||||
{
|
||||
visitor.Visit(assembly);
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < types.Count; i++)
|
||||
{
|
||||
var type = types[i];
|
||||
context.Results.Add(CreateDescriptor(type));
|
||||
}
|
||||
}
|
||||
|
||||
private TagHelperDescriptor CreateDescriptor(INamedTypeSymbol type)
|
||||
{
|
||||
if (type == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(type));
|
||||
}
|
||||
|
||||
var typeName = type.ToDisplayString(FullNameTypeDisplayFormat);
|
||||
var assemblyName = type.ContainingAssembly.Identity.Name;
|
||||
|
||||
var builder = TagHelperDescriptorBuilder.Create(ComponentTagHelperKind, typeName, assemblyName);
|
||||
builder.SetTypeName(typeName);
|
||||
|
||||
// This opts out this 'component' tag helper for any processing that's specific to the default
|
||||
// Razor ITagHelper runtime.
|
||||
builder.Metadata[TagHelperMetadata.Runtime.Name] = "Blazor.IComponent";
|
||||
|
||||
var xml = type.GetDocumentationCommentXml();
|
||||
if (!string.IsNullOrEmpty(xml))
|
||||
{
|
||||
builder.Documentation = xml;
|
||||
}
|
||||
|
||||
// Components have very simple matching rules. The type name (short) matches the tag name.
|
||||
builder.TagMatchingRule(r => r.TagName = type.Name);
|
||||
|
||||
foreach (var property in GetVisibleProperties(type))
|
||||
{
|
||||
if (property.kind == PropertyKind.Ignored)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
builder.BindAttribute(pb =>
|
||||
{
|
||||
pb.Name = property.property.Name;
|
||||
pb.TypeName = property.property.Type.ToDisplayString(FullNameTypeDisplayFormat);
|
||||
pb.SetPropertyName(property.property.Name);
|
||||
|
||||
if (property.kind == PropertyKind.Enum)
|
||||
{
|
||||
pb.IsEnum = true;
|
||||
}
|
||||
|
||||
xml = property.property.GetDocumentationCommentXml();
|
||||
if (!string.IsNullOrEmpty(xml))
|
||||
{
|
||||
pb.Documentation = xml;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var descriptor = builder.Build();
|
||||
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
// Does a walk up the inheritance chain to determine the set of 'visible' properties by using
|
||||
// a dictionary keyed on property name.
|
||||
//
|
||||
// Note that we're only interested in a property if all of the above are true:
|
||||
// - visible (not shadowed)
|
||||
// - has public getter
|
||||
// - has public setter
|
||||
// - is not an indexer
|
||||
private IEnumerable<(IPropertySymbol property, PropertyKind kind)> GetVisibleProperties(INamedTypeSymbol type)
|
||||
{
|
||||
var properties = new Dictionary<string, (IPropertySymbol, PropertyKind)>(StringComparer.Ordinal);
|
||||
do
|
||||
{
|
||||
var members = type.GetMembers();
|
||||
for (var i = 0; i < members.Length; i++)
|
||||
{
|
||||
var property = members[i] as IPropertySymbol;
|
||||
if (property == null)
|
||||
{
|
||||
// Not a property
|
||||
continue;
|
||||
}
|
||||
|
||||
var kind = PropertyKind.Default;
|
||||
if (properties.ContainsKey(property.Name))
|
||||
{
|
||||
// Not visible
|
||||
kind = PropertyKind.Ignored;
|
||||
}
|
||||
|
||||
if (property.Parameters.Length != 0)
|
||||
{
|
||||
// Indexer
|
||||
kind = PropertyKind.Ignored;
|
||||
}
|
||||
|
||||
if (property.GetMethod?.DeclaredAccessibility != Accessibility.Public)
|
||||
{
|
||||
// Non-public getter or no getter
|
||||
kind = PropertyKind.Ignored;
|
||||
}
|
||||
|
||||
if (property.SetMethod?.DeclaredAccessibility != Accessibility.Public)
|
||||
{
|
||||
// Non-public setter or no setter
|
||||
kind = PropertyKind.Ignored;
|
||||
}
|
||||
|
||||
if (kind == PropertyKind.Default && property.Type.TypeKind == TypeKind.Enum)
|
||||
{
|
||||
kind = PropertyKind.Enum;
|
||||
}
|
||||
|
||||
properties.Add(property.Name, (property, kind));
|
||||
}
|
||||
|
||||
type = type.BaseType;
|
||||
}
|
||||
while (type != null);
|
||||
|
||||
return properties.Values;
|
||||
}
|
||||
|
||||
private enum PropertyKind
|
||||
{
|
||||
Ignored,
|
||||
Default,
|
||||
Enum,
|
||||
}
|
||||
|
||||
private class ComponentTypeVisitor : SymbolVisitor
|
||||
{
|
||||
private INamedTypeSymbol _interface;
|
||||
private List<INamedTypeSymbol> _results;
|
||||
|
||||
public ComponentTypeVisitor(INamedTypeSymbol @interface, List<INamedTypeSymbol> results)
|
||||
{
|
||||
_interface = @interface;
|
||||
_results = results;
|
||||
}
|
||||
|
||||
public override void VisitNamedType(INamedTypeSymbol symbol)
|
||||
{
|
||||
if (IsComponent(symbol))
|
||||
{
|
||||
_results.Add(symbol);
|
||||
}
|
||||
}
|
||||
|
||||
public override void VisitNamespace(INamespaceSymbol symbol)
|
||||
{
|
||||
foreach (var member in symbol.GetMembers())
|
||||
{
|
||||
Visit(member);
|
||||
}
|
||||
}
|
||||
|
||||
public override void VisitAssembly(IAssemblySymbol symbol)
|
||||
{
|
||||
// This as a simple yet high-value optimization that excludes the vast majority of
|
||||
// assemblies that (by definition) can't contain a component.
|
||||
if (symbol.Name != null && !symbol.Name.StartsWith("System.", StringComparison.Ordinal))
|
||||
{
|
||||
Visit(symbol.GlobalNamespace);
|
||||
}
|
||||
}
|
||||
|
||||
internal bool IsComponent(INamedTypeSymbol symbol)
|
||||
{
|
||||
if (_interface == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return
|
||||
symbol.DeclaredAccessibility == Accessibility.Public &&
|
||||
!symbol.IsAbstract &&
|
||||
!symbol.IsGenericType &&
|
||||
symbol.AllInterfaces.Contains(_interface);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,10 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.AspNetCore.Blazor.Razor;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
|
||||
[assembly: ProvideRazorExtensionInitializer("Blazor-0.1", typeof(BlazorExtensionInitializer))]
|
||||
[assembly: ProvideRazorExtensionInitializer("Blazor-0.1", typeof(BlazorExtensionInitializer))]
|
||||
|
||||
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Blazor.Razor.Extensions.Test")]
|
||||
|
|
@ -0,0 +1,295 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
using Microsoft.Extensions.DependencyModel;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Razor.Extensions
|
||||
{
|
||||
public class ComponentTagHelperDescriptorProviderTest
|
||||
{
|
||||
static ComponentTagHelperDescriptorProviderTest()
|
||||
{
|
||||
var dependencyContext = DependencyContext.Load(typeof(ComponentTagHelperDescriptorProviderTest).Assembly);
|
||||
|
||||
var metadataReferences = dependencyContext.CompileLibraries
|
||||
.SelectMany(l => l.ResolveReferencePaths())
|
||||
.Select(assemblyPath => MetadataReference.CreateFromFile(assemblyPath))
|
||||
.ToArray();
|
||||
|
||||
BaseCompilation = CSharpCompilation.Create(
|
||||
"TestAssembly",
|
||||
Array.Empty<SyntaxTree>(),
|
||||
metadataReferences,
|
||||
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
|
||||
}
|
||||
|
||||
private static Compilation BaseCompilation { get; }
|
||||
|
||||
[Fact]
|
||||
public void Excecute_FindsIComponentType_CreatesDescriptor()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
var compilation = BaseCompilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(@"
|
||||
using Microsoft.AspNetCore.Blazor.Components;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
public class MyComponent : IComponent
|
||||
{
|
||||
public void Init(RenderHandle renderHandle) { }
|
||||
|
||||
public void SetParameters(ParameterCollection parameters) { }
|
||||
|
||||
public string MyProperty { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
"));
|
||||
|
||||
Assert.Empty(compilation.GetDiagnostics());
|
||||
|
||||
var context = TagHelperDescriptorProviderContext.Create();
|
||||
context.SetCompilation(compilation);
|
||||
|
||||
var provider = new ComponentTagHelperDescriptorProvider();
|
||||
|
||||
// Act
|
||||
provider.Execute(context);
|
||||
|
||||
// Assert
|
||||
var components = ExcludeBuiltInComponents(context);
|
||||
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(ComponentTagHelperDescriptorProvider.ComponentTagHelperKind, component.Kind);
|
||||
Assert.Equal("Blazor.Component-0.1", component.Kind);
|
||||
Assert.False(component.IsDefaultKind());
|
||||
Assert.False(component.KindUsesDefaultTagHelperRuntime());
|
||||
|
||||
// No documentation in this test
|
||||
Assert.Null(component.Documentation);
|
||||
|
||||
// These are all trivally 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.GetTypeName());
|
||||
|
||||
// Our use of matching rules is also very simple, and derived from the name. Veriying
|
||||
// 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 metadata is also (for now) an invariant for all Components - other than the type name
|
||||
// which is trivial. Verifying it once in detail and then ignoring it.
|
||||
Assert.Collection(
|
||||
component.Metadata.OrderBy(kvp => kvp.Key),
|
||||
kvp => { Assert.Equal(TagHelperMetadata.Common.TypeName, kvp.Key); Assert.Equal("Test.MyComponent", kvp.Value); },
|
||||
kvp => { Assert.Equal(TagHelperMetadata.Runtime.Name, kvp.Key); Assert.Equal("Blazor.IComponent", kvp.Value); });
|
||||
|
||||
// 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("Blazor.Component-0.1", attribute.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);
|
||||
|
||||
// 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.GetPropertyName());
|
||||
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);
|
||||
|
||||
// Our use of metadata is also (for now) an invariant for all Component properties - other than the type name
|
||||
// which is trivial. Verifying it once in detail and then ignoring it.
|
||||
Assert.Collection(
|
||||
attribute.Metadata.OrderBy(kvp => kvp.Key),
|
||||
kvp => { Assert.Equal(TagHelperMetadata.Common.PropertyName, kvp.Key); Assert.Equal("MyProperty", kvp.Value); });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Excecute_FindsBlazorComponentType_CreatesDescriptor()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
var compilation = BaseCompilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(@"
|
||||
using Microsoft.AspNetCore.Blazor.Components;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
public class MyComponent : BlazorComponent
|
||||
{
|
||||
public string MyProperty { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
"));
|
||||
|
||||
Assert.Empty(compilation.GetDiagnostics());
|
||||
|
||||
var context = TagHelperDescriptorProviderContext.Create();
|
||||
context.SetCompilation(compilation);
|
||||
|
||||
var provider = new ComponentTagHelperDescriptorProvider();
|
||||
|
||||
// Act
|
||||
provider.Execute(context);
|
||||
|
||||
// Assert
|
||||
var components = ExcludeBuiltInComponents(context);
|
||||
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] // bool properties support minimized attributes
|
||||
public void Excecute_BoolProperty_CreatesDescriptor()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
var compilation = BaseCompilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(@"
|
||||
using Microsoft.AspNetCore.Blazor.Components;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
public class MyComponent : BlazorComponent
|
||||
{
|
||||
public bool MyProperty { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
"));
|
||||
|
||||
Assert.Empty(compilation.GetDiagnostics());
|
||||
|
||||
var context = TagHelperDescriptorProviderContext.Create();
|
||||
context.SetCompilation(compilation);
|
||||
|
||||
var provider = new ComponentTagHelperDescriptorProvider();
|
||||
|
||||
// Act
|
||||
provider.Execute(context);
|
||||
|
||||
// Assert
|
||||
var components = ExcludeBuiltInComponents(context);
|
||||
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 Excecute_EnumProperty_CreatesDescriptor()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
var compilation = BaseCompilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(@"
|
||||
using Microsoft.AspNetCore.Blazor.Components;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
public enum MyEnum
|
||||
{
|
||||
One,
|
||||
Two
|
||||
}
|
||||
|
||||
public class MyComponent : BlazorComponent
|
||||
{
|
||||
public MyEnum MyProperty { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
"));
|
||||
|
||||
Assert.Empty(compilation.GetDiagnostics());
|
||||
|
||||
var context = TagHelperDescriptorProviderContext.Create();
|
||||
context.SetCompilation(compilation);
|
||||
|
||||
var provider = new ComponentTagHelperDescriptorProvider();
|
||||
|
||||
// Act
|
||||
provider.Execute(context);
|
||||
|
||||
// Assert
|
||||
var components = ExcludeBuiltInComponents(context);
|
||||
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);
|
||||
}
|
||||
|
||||
// For simplicity in testing, exlude the built-in components. We'll add more and we
|
||||
// don't want to update the tests when that happens.
|
||||
private TagHelperDescriptor[] ExcludeBuiltInComponents(TagHelperDescriptorProviderContext context)
|
||||
{
|
||||
return context.Results
|
||||
.Where(c => c.AssemblyName == "TestAssembly")
|
||||
.OrderBy(c => c.Name)
|
||||
.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
|
||||
<!--
|
||||
Retains compilation settings so we can create a compilation during tests with the same data
|
||||
used to compile this assembly.
|
||||
-->
|
||||
<PreserveCompilationContext>true</PreserveCompilationContext>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="xunit.runner.json" CopyToOutputDirectory="PreserveNewest" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="2.6.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="2.0.4" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.5.0" />
|
||||
<PackageReference Include="xunit" Version="2.3.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Blazor\Microsoft.AspNetCore.Blazor.csproj" />
|
||||
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Blazor.Razor.Extensions\Microsoft.AspNetCore.Blazor.Razor.Extensions.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"shadowCopy": false
|
||||
}
|
||||
Loading…
Reference in New Issue