274 lines
9.0 KiB
C#
274 lines
9.0 KiB
C#
// 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 System.Linq;
|
|
using System.Reflection;
|
|
using Microsoft.AspNetCore.Mvc.ApplicationParts;
|
|
using Xunit;
|
|
|
|
namespace Microsoft.AspNetCore.Mvc.ViewComponents
|
|
{
|
|
public class DefaultViewComponentSelectorTest
|
|
{
|
|
private static readonly string Namespace = typeof(DefaultViewComponentSelectorTest).Namespace;
|
|
|
|
[Fact]
|
|
public void SelectComponent_ByShortNameWithSuffix()
|
|
{
|
|
// Arrange
|
|
var selector = CreateSelector();
|
|
|
|
// Act
|
|
var result = selector.SelectComponent("Suffix");
|
|
|
|
// Assert
|
|
Assert.Same(typeof(ViewComponentContainer.SuffixViewComponent).GetTypeInfo(), result.TypeInfo);
|
|
}
|
|
|
|
[Fact]
|
|
public void SelectComponent_ByLongNameWithSuffix()
|
|
{
|
|
// Arrange
|
|
var selector = CreateSelector();
|
|
|
|
// Act
|
|
var result = selector.SelectComponent($"{Namespace}.Suffix");
|
|
|
|
// Assert
|
|
Assert.Same(typeof(ViewComponentContainer.SuffixViewComponent).GetTypeInfo(), result.TypeInfo);
|
|
}
|
|
|
|
[Fact]
|
|
public void SelectComponent_ByShortNameWithoutSuffix()
|
|
{
|
|
// Arrange
|
|
var selector = CreateSelector();
|
|
|
|
// Act
|
|
var result = selector.SelectComponent("WithoutSuffix");
|
|
|
|
// Assert
|
|
Assert.Same(typeof(ViewComponentContainer.WithoutSuffix).GetTypeInfo(), result.TypeInfo);
|
|
}
|
|
|
|
[Fact]
|
|
public void SelectComponent_ByLongNameWithoutSuffix()
|
|
{
|
|
// Arrange
|
|
var selector = CreateSelector();
|
|
|
|
// Act
|
|
var result = selector.SelectComponent($"{Namespace}.WithoutSuffix");
|
|
|
|
// Assert
|
|
Assert.Same(typeof(ViewComponentContainer.WithoutSuffix).GetTypeInfo(), result.TypeInfo);
|
|
}
|
|
|
|
[Fact]
|
|
public void SelectComponent_ByAttribute()
|
|
{
|
|
// Arrange
|
|
var selector = CreateSelector();
|
|
|
|
// Act
|
|
var result = selector.SelectComponent("ByAttribute");
|
|
|
|
// Assert
|
|
Assert.Same(typeof(ViewComponentContainer.ByAttribute).GetTypeInfo(), result.TypeInfo);
|
|
}
|
|
|
|
[Fact]
|
|
public void SelectComponent_ByNamingConvention()
|
|
{
|
|
// Arrange
|
|
var selector = CreateSelector();
|
|
|
|
// Act
|
|
var result = selector.SelectComponent("ByNamingConvention");
|
|
|
|
// Assert
|
|
Assert.Same(typeof(ViewComponentContainer.ByNamingConventionViewComponent).GetTypeInfo(), result.TypeInfo);
|
|
}
|
|
|
|
[Fact]
|
|
public void SelectComponent_Ambiguity()
|
|
{
|
|
// Arrange
|
|
var selector = CreateSelector();
|
|
var expected =
|
|
"The view component name 'Ambiguous' matched multiple types:" + Environment.NewLine +
|
|
$"Type: '{typeof(ViewComponentContainer.Ambiguous1)}' - " +
|
|
"Name: 'Namespace1.Ambiguous'" + Environment.NewLine +
|
|
$"Type: '{typeof(ViewComponentContainer.Ambiguous2)}' - " +
|
|
"Name: 'Namespace2.Ambiguous'";
|
|
|
|
// Act
|
|
var ex = Assert.Throws<InvalidOperationException>(() => selector.SelectComponent("Ambiguous"));
|
|
|
|
// Assert
|
|
Assert.Equal(expected, ex.Message);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("Name")]
|
|
[InlineData("Ambiguous.Name")]
|
|
public void SelectComponent_AmbiguityDueToDerivation(string name)
|
|
{
|
|
// Arrange
|
|
var selector = CreateSelector();
|
|
var expected =
|
|
$"The view component name '{name}' matched multiple types:" + Environment.NewLine +
|
|
$"Type: '{typeof(ViewComponentContainer.AmbiguousBase)}' - " +
|
|
"Name: 'Ambiguous.Name'" + Environment.NewLine +
|
|
$"Type: '{typeof(ViewComponentContainer.DerivedAmbiguous)}' - " +
|
|
"Name: 'Ambiguous.Name'";
|
|
|
|
// Act
|
|
var ex = Assert.Throws<InvalidOperationException>(() => selector.SelectComponent(name));
|
|
|
|
// Assert
|
|
Assert.Equal(expected, ex.Message);
|
|
}
|
|
|
|
[Fact]
|
|
public void SelectComponent_FullNameToAvoidAmbiguity()
|
|
{
|
|
// Arrange
|
|
var selector = CreateSelector();
|
|
|
|
// Act
|
|
var result = selector.SelectComponent("Namespace1.Ambiguous");
|
|
|
|
// Assert
|
|
Assert.Same(typeof(ViewComponentContainer.Ambiguous1).GetTypeInfo(), result.TypeInfo);
|
|
}
|
|
|
|
[Fact]
|
|
public void SelectComponent_OverrideNameToAvoidAmbiguity()
|
|
{
|
|
// Arrange
|
|
var selector = CreateSelector();
|
|
|
|
// Act
|
|
var result = selector.SelectComponent("NonAmbiguousName");
|
|
|
|
// Assert
|
|
Assert.Same(typeof(ViewComponentContainer.DerivedAmbiguousWithOverriddenName).GetTypeInfo(), result.TypeInfo);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("FullNameInAttribute")]
|
|
[InlineData("CoolNameSpace.FullNameInAttribute")]
|
|
public void SelectComponent_FullNameInAttribute(string name)
|
|
{
|
|
// Arrange
|
|
var selector = CreateSelector();
|
|
|
|
// Act
|
|
var result = selector.SelectComponent(name);
|
|
|
|
// Assert
|
|
Assert.Same(typeof(ViewComponentContainer.FullNameInAttribute).GetTypeInfo(), result.TypeInfo);
|
|
}
|
|
|
|
private IViewComponentSelector CreateSelector()
|
|
{
|
|
var provider = new DefaultViewComponentDescriptorCollectionProvider(
|
|
new FilteredViewComponentDescriptorProvider());
|
|
|
|
return new DefaultViewComponentSelector(provider);
|
|
}
|
|
|
|
private class ViewComponentContainer
|
|
{
|
|
public class SuffixViewComponent : ViewComponent
|
|
{
|
|
public string Invoke() => "Hello";
|
|
}
|
|
|
|
public class WithoutSuffix : ViewComponent
|
|
{
|
|
public string Invoke() => "Hello";
|
|
}
|
|
|
|
public class ByNamingConventionViewComponent
|
|
{
|
|
public string Invoke() => "Hello";
|
|
}
|
|
|
|
[ViewComponent]
|
|
public class ByAttribute
|
|
{
|
|
public string Invoke() => "Hello";
|
|
}
|
|
|
|
[ViewComponent(Name = "Namespace1.Ambiguous")]
|
|
public class Ambiguous1
|
|
{
|
|
public string Invoke() => "Hello";
|
|
}
|
|
|
|
[ViewComponent(Name = "Namespace2.Ambiguous")]
|
|
public class Ambiguous2
|
|
{
|
|
public string Invoke() => "Hello";
|
|
}
|
|
|
|
[ViewComponent(Name = "CoolNameSpace.FullNameInAttribute")]
|
|
public class FullNameInAttribute
|
|
{
|
|
public string Invoke() => "Hello";
|
|
}
|
|
|
|
[ViewComponent(Name = "Ambiguous.Name")]
|
|
public class AmbiguousBase
|
|
{
|
|
public string Invoke() => "Hello";
|
|
}
|
|
|
|
public class DerivedAmbiguous : AmbiguousBase
|
|
{
|
|
}
|
|
|
|
[ViewComponent(Name = "NonAmbiguousName")]
|
|
public class DerivedAmbiguousWithOverriddenName : AmbiguousBase
|
|
{
|
|
}
|
|
}
|
|
// This will only consider types nested inside this class as ViewComponent classes
|
|
private class FilteredViewComponentDescriptorProvider : DefaultViewComponentDescriptorProvider
|
|
{
|
|
public FilteredViewComponentDescriptorProvider()
|
|
: this(typeof(ViewComponentContainer).GetNestedTypes(bindingAttr: BindingFlags.Public))
|
|
{
|
|
}
|
|
|
|
// For error messages in tests above, ensure the TestApplicationPart returns types in a consistent order.
|
|
public FilteredViewComponentDescriptorProvider(params Type[] allowedTypes)
|
|
: base(GetApplicationPartManager(allowedTypes.OrderBy(type => type.Name, StringComparer.Ordinal)))
|
|
{
|
|
}
|
|
|
|
private static ApplicationPartManager GetApplicationPartManager(IEnumerable<Type> types)
|
|
{
|
|
var manager = new ApplicationPartManager();
|
|
manager.ApplicationParts.Add(new TestApplicationPart(types));
|
|
manager.FeatureProviders.Add(new TestFeatureProvider());
|
|
return manager;
|
|
}
|
|
|
|
private class TestFeatureProvider : IApplicationFeatureProvider<ViewComponentFeature>
|
|
{
|
|
public void PopulateFeature(IEnumerable<ApplicationPart> parts, ViewComponentFeature feature)
|
|
{
|
|
foreach (var type in parts.OfType<IApplicationPartTypeProvider>().SelectMany(p => p.Types))
|
|
{
|
|
feature.ViewComponents.Add(type);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} |