diff --git a/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/CommonTagHelpers.cs b/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/CommonTagHelpers.cs new file mode 100644 index 0000000000..5ef221dd4b --- /dev/null +++ b/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/CommonTagHelpers.cs @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.Razor.Runtime.TagHelpers +{ + public class Valid_PlainTagHelper : ITagHelper + { + } + + public class Valid_InheritedTagHelper : Valid_PlainTagHelper + { + } + + public class SingleAttributeTagHelper : ITagHelper + { + public int IntAttribute { get; set; } + } + + public class MissingAccessorTagHelper : ITagHelper + { + public string ValidAttribute { get; set; } + public string InvalidNoGetAttribute { set { } } + public string InvalidNoSetAttribute { get { return string.Empty; } } + } + + public class PrivateAccessorTagHelper : ITagHelper + { + public string ValidAttribute { get; set; } + public string InvalidPrivateSetAttribute { get; private set; } + public string InvalidPrivateGetAttribute { private get; set; } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/CompleteTagHelperDescriptorComparer.cs b/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/CompleteTagHelperDescriptorComparer.cs new file mode 100644 index 0000000000..fbbc3cf4f6 --- /dev/null +++ b/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/CompleteTagHelperDescriptorComparer.cs @@ -0,0 +1,59 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNet.Razor.TagHelpers; +using Microsoft.Internal.Web.Utils; + +namespace Microsoft.AspNet.Razor.Runtime.TagHelpers +{ + public class CompleteTagHelperDescriptorComparer : TagHelperDescriptorComparer + { + public new static readonly CompleteTagHelperDescriptorComparer Default = + new CompleteTagHelperDescriptorComparer(); + + private CompleteTagHelperDescriptorComparer() + { + } + + public new bool Equals(TagHelperDescriptor descriptorX, TagHelperDescriptor descriptorY) + { + return base.Equals(descriptorX, descriptorY) && + descriptorX.Attributes.SequenceEqual(descriptorY.Attributes, + CompleteTagHelperAttributeDescriptorComparer.Default); + } + + public new int GetHashCode(TagHelperDescriptor descriptor) + { + return HashCodeCombiner.Start() + .Add(base.GetHashCode()) + .Add(descriptor.Attributes) + .CombinedHash; + } + + private class CompleteTagHelperAttributeDescriptorComparer : IEqualityComparer + { + public static readonly CompleteTagHelperAttributeDescriptorComparer Default = + new CompleteTagHelperAttributeDescriptorComparer(); + + private CompleteTagHelperAttributeDescriptorComparer() + { + } + + public bool Equals(TagHelperAttributeDescriptor descriptorX, TagHelperAttributeDescriptor descriptorY) + { + return descriptorX.AttributeName == descriptorY.AttributeName && + descriptorX.AttributePropertyName == descriptorY.AttributePropertyName; + } + + public int GetHashCode(TagHelperAttributeDescriptor descriptor) + { + return HashCodeCombiner.Start() + .Add(descriptor.AttributeName) + .Add(descriptor.AttributePropertyName) + .CombinedHash; + } + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/TagHelperDescriptorFactoryTest.cs b/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/TagHelperDescriptorFactoryTest.cs new file mode 100644 index 0000000000..1dc078faa7 --- /dev/null +++ b/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/TagHelperDescriptorFactoryTest.cs @@ -0,0 +1,87 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNet.Razor.TagHelpers; +using Xunit; + +namespace Microsoft.AspNet.Razor.Runtime.TagHelpers +{ + public class TagHelperDescriptorFactoryTest + { + [Fact] + public void DescriptorFactory_BuildsDescriptorsFromSimpleTypes() + { + // Arrange + var expectedDescriptor = new TagHelperDescriptor("Object", "System.Object", ContentBehavior.None); + + // Act + var descriptor = TagHelperDescriptorFactory.CreateDescriptor(typeof(object)); + + // Assert + Assert.Equal(descriptor, expectedDescriptor, CompleteTagHelperDescriptorComparer.Default); + } + + [Fact] + public void DescriptorFactory_BuildsDescriptorsWithConventionNames() + { + // Arrange + var intProperty = typeof(SingleAttributeTagHelper).GetProperty(nameof(SingleAttributeTagHelper.IntAttribute)); + var expectedDescriptor = new TagHelperDescriptor( + "SingleAttribute", + typeof(SingleAttributeTagHelper).FullName, + ContentBehavior.None, + new[] { + new TagHelperAttributeDescriptor(nameof(SingleAttributeTagHelper.IntAttribute), intProperty) + }); + + // Act + var descriptor = TagHelperDescriptorFactory.CreateDescriptor(typeof(SingleAttributeTagHelper)); + + // Assert + Assert.Equal(descriptor, expectedDescriptor, CompleteTagHelperDescriptorComparer.Default); + } + + [Fact] + public void DescriptorFactory_OnlyAcceptsPropertiesWithGetAndSet() + { + // Arrange + var validProperty = typeof(MissingAccessorTagHelper).GetProperty( + nameof(MissingAccessorTagHelper.ValidAttribute)); + var expectedDescriptor = new TagHelperDescriptor( + "MissingAccessor", + typeof(MissingAccessorTagHelper).FullName, + ContentBehavior.None, + new[] { + new TagHelperAttributeDescriptor(nameof(MissingAccessorTagHelper.ValidAttribute), validProperty) + }); + + // Act + var descriptor = TagHelperDescriptorFactory.CreateDescriptor(typeof(MissingAccessorTagHelper)); + + // Assert + Assert.Equal(descriptor, expectedDescriptor, CompleteTagHelperDescriptorComparer.Default); + } + + [Fact] + public void DescriptorFactory_OnlyAcceptsPropertiesWithPublicGetAndSet() + { + // Arrange + var validProperty = typeof(PrivateAccessorTagHelper).GetProperty( + nameof(PrivateAccessorTagHelper.ValidAttribute)); + var expectedDescriptor = new TagHelperDescriptor( + "PrivateAccessor", + typeof(PrivateAccessorTagHelper).FullName, + ContentBehavior.None, + new[] { + new TagHelperAttributeDescriptor( + nameof(PrivateAccessorTagHelper.ValidAttribute), validProperty) + }); + + // Act + var descriptor = TagHelperDescriptorFactory.CreateDescriptor(typeof(PrivateAccessorTagHelper)); + + // Assert + Assert.Equal(descriptor, expectedDescriptor, CompleteTagHelperDescriptorComparer.Default); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/TagHelperDescriptorResolverTest.cs b/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/TagHelperDescriptorResolverTest.cs new file mode 100644 index 0000000000..3930d14085 --- /dev/null +++ b/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/TagHelperDescriptorResolverTest.cs @@ -0,0 +1,190 @@ +// Copyright (c) Microsoft Open Technologies, Inc. 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.AspNet.Razor.TagHelpers; +using Xunit; + +namespace Microsoft.AspNet.Razor.Runtime.TagHelpers +{ + public class TagHelperDescriptorResolverTest : TagHelperTypeResolverTest + { + [Fact] + public void DescriptorResolver_DoesNotReturnInvalidTagHelpersWhenSpecified() + { + // Arrange + var tagHelperDescriptorResolver = + new TagHelperDescriptorResolver( + new TestTagHelperTypeResolver(TestableTagHelpers)); + + // Act + var descriptors = tagHelperDescriptorResolver.Resolve( + "Microsoft.AspNet.Razor.Runtime.Test.TagHelpers.Invalid_AbstractTagHelper, MyAssembly"); + descriptors = descriptors.Concat(tagHelperDescriptorResolver.Resolve( + "Microsoft.AspNet.Razor.Runtime.Test.TagHelpers.Invalid_GenericTagHelper`, MyAssembly")); + descriptors = descriptors.Concat(tagHelperDescriptorResolver.Resolve( + "Microsoft.AspNet.Razor.Runtime.Test.TagHelpers.Invalid_NestedPublicTagHelper, MyAssembly")); + descriptors = descriptors.Concat(tagHelperDescriptorResolver.Resolve( + "Microsoft.AspNet.Razor.Runtime.Test.TagHelpers.Invalid_NestedInternalTagHelper, MyAssembly")); + descriptors = descriptors.Concat(tagHelperDescriptorResolver.Resolve( + "Microsoft.AspNet.Razor.Runtime.Test.TagHelpers.Invalid_PrivateTagHelper, MyAssembly")); + descriptors = descriptors.Concat(tagHelperDescriptorResolver.Resolve( + "Microsoft.AspNet.Razor.Runtime.Test.TagHelpers.Invalid_ProtectedTagHelper, MyAssembly")); + descriptors = descriptors.Concat(tagHelperDescriptorResolver.Resolve( + "Microsoft.AspNet.Razor.Runtime.Test.TagHelpers.Invalid_InternalTagHelper, MyAssembly")); + + // Assert + Assert.Empty(descriptors); + } + + [Theory] + [InlineData("Microsoft.AspNet.Razor.Runtime.TagHelpers.Valid_PlainTagHelper,MyAssembly")] + [InlineData(" Microsoft.AspNet.Razor.Runtime.TagHelpers.Valid_PlainTagHelper,MyAssembly")] + [InlineData("Microsoft.AspNet.Razor.Runtime.TagHelpers.Valid_PlainTagHelper ,MyAssembly")] + [InlineData(" Microsoft.AspNet.Razor.Runtime.TagHelpers.Valid_PlainTagHelper ,MyAssembly")] + [InlineData("Microsoft.AspNet.Razor.Runtime.TagHelpers.Valid_PlainTagHelper, MyAssembly")] + [InlineData("Microsoft.AspNet.Razor.Runtime.TagHelpers.Valid_PlainTagHelper,MyAssembly ")] + [InlineData("Microsoft.AspNet.Razor.Runtime.TagHelpers.Valid_PlainTagHelper, MyAssembly ")] + [InlineData(" Microsoft.AspNet.Razor.Runtime.TagHelpers.Valid_PlainTagHelper, MyAssembly ")] + [InlineData(" Microsoft.AspNet.Razor.Runtime.TagHelpers.Valid_PlainTagHelper , MyAssembly ")] + public void DescriptorResolver_IgnoresSpaces(string lookupText) + { + // Arrange + var tagHelperTypeResolver = new TestTagHelperTypeResolver(TestableTagHelpers) + { + OnGetLibraryDefinedTypes = (assemblyName) => + { + Assert.Equal("MyAssembly", assemblyName.Name); + } + }; + var tagHelperDescriptorResolver = new TagHelperDescriptorResolver(tagHelperTypeResolver); + var expectedDescriptor = new TagHelperDescriptor("Valid_Plain", + typeof(Valid_PlainTagHelper).FullName, + ContentBehavior.None); + + // Act + var descriptors = tagHelperDescriptorResolver.Resolve(lookupText); + + // Assert + var descriptor = Assert.Single(descriptors); + Assert.Equal(expectedDescriptor, descriptor, CompleteTagHelperDescriptorComparer.Default); + } + + [Fact] + public void DescriptorResolver_ResolvesOnlyTypeResolverProvidedTypes() + { + // Arrange + var resolver = new TagHelperDescriptorResolver( + new LookupBasedTagHelperTypeResolver( + new Dictionary>(StringComparer.OrdinalIgnoreCase) + { + { "lookupText1", ValidTestableTagHelpers }, + { "lookupText2", new Type[]{ typeof(Valid_PlainTagHelper) } } + })); + var expectedDescriptor = new TagHelperDescriptor("Valid_Plain", + typeof(Valid_PlainTagHelper).FullName, + ContentBehavior.None); + + // Act + var descriptors = resolver.Resolve("lookupText2"); + + // Assert + var descriptor = Assert.Single(descriptors); + Assert.Equal(descriptor, expectedDescriptor, CompleteTagHelperDescriptorComparer.Default); + } + + [Fact] + public void DescriptorResolver_ResolvesMultipleTypes() + { + // Arrange + var resolver = new TagHelperDescriptorResolver( + new LookupBasedTagHelperTypeResolver( + new Dictionary>(StringComparer.OrdinalIgnoreCase) + { + { "lookupText", new Type[]{ typeof(Valid_PlainTagHelper), typeof(Valid_InheritedTagHelper) } }, + })); + var expectedDescriptors = new TagHelperDescriptor[] + { + new TagHelperDescriptor("Valid_Plain", + typeof(Valid_PlainTagHelper).FullName, + ContentBehavior.None), + new TagHelperDescriptor("Valid_Inherited", + typeof(Valid_InheritedTagHelper).FullName, + ContentBehavior.None) + }; + + // Act + var descriptors = resolver.Resolve("lookupText").ToArray(); + + // Assert + Assert.Equal(descriptors.Length, 2); + Assert.Equal(descriptors, expectedDescriptors, CompleteTagHelperDescriptorComparer.Default); + } + + [Fact] + public void DescriptorResolver_DoesNotResolveTypesForNoTypeResolvingLookupText() + { + // Arrange + var resolver = new TagHelperDescriptorResolver( + new LookupBasedTagHelperTypeResolver( + new Dictionary>(StringComparer.OrdinalIgnoreCase) + { + { "lookupText1", ValidTestableTagHelpers }, + { "lookupText2", new Type[]{ typeof(Valid_PlainTagHelper) } } + })); + + // Act + var descriptors = resolver.Resolve("lookupText").ToArray(); + + // Assert + Assert.Empty(descriptors); + } + + [Theory] + [InlineData("")] + [InlineData(null)] + public void DescriptorResolver_ResolveThrowsIfNullOrEmptyLookupText(string lookupText) + { + // Arrange + var tagHelperDescriptorResolver = + new TagHelperDescriptorResolver( + new TestTagHelperTypeResolver(InvalidTestableTagHelpers)); + + var expectedMessage = + Resources.FormatTagHelperDescriptorResolver_InvalidTagHelperLookupText(lookupText) + + Environment.NewLine + + "Parameter name: lookupText"; + + // Act & Assert + var ex = Assert.Throws(nameof(lookupText), + () => + { + tagHelperDescriptorResolver.Resolve(lookupText); + }); + + Assert.Equal(expectedMessage, ex.Message); + } + + private class LookupBasedTagHelperTypeResolver : TagHelperTypeResolver + { + private Dictionary> _lookupValues; + + public LookupBasedTagHelperTypeResolver(Dictionary> lookupValues) + { + _lookupValues = lookupValues; + } + + internal override IEnumerable GetLibraryDefinedTypes(AssemblyName assemblyName) + { + IEnumerable types; + + _lookupValues.TryGetValue(assemblyName.Name, out types); + + return types?.Select(type => type.GetTypeInfo()) ?? Enumerable.Empty(); + } + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/TagHelperTypeResolverTest.cs b/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/TagHelperTypeResolverTest.cs new file mode 100644 index 0000000000..425e587ff0 --- /dev/null +++ b/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/TagHelperTypeResolverTest.cs @@ -0,0 +1,153 @@ +// Copyright (c) Microsoft Open Technologies, Inc. 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.Globalization; +using System.Linq; +using System.Reflection; +using Xunit; + +namespace Microsoft.AspNet.Razor.Runtime.TagHelpers +{ + public class TagHelperTypeResolverTest + { + protected static readonly Type[] ValidTestableTagHelpers = new[] + { + typeof(Valid_PlainTagHelper), + typeof(Valid_InheritedTagHelper) + }; + + protected static readonly Type[] InvalidTestableTagHelpers = new[] + { + typeof(Invalid_AbstractTagHelper), + typeof(Invalid_GenericTagHelper<>), + typeof(Invalid_NestedPublicTagHelper), + typeof(Invalid_NestedInternalTagHelper), + typeof(Invalid_PrivateTagHelper), + typeof(Invalid_ProtectedTagHelper), + typeof(Invalid_InternalTagHelper) + }; + + protected static readonly Type[] TestableTagHelpers = + ValidTestableTagHelpers.Concat(InvalidTestableTagHelpers).ToArray(); + + [Fact] + public void TypeResolver_ThrowsWhenCannotResolveAssembly() + { + // Arrange + var tagHelperTypeResolver = new TagHelperTypeResolver(); + var expectedErrorMessage = string.Format( + CultureInfo.InvariantCulture, + "Cannot resolve TagHelper containing assembly '{0}'. Error: '{1}'.", + "abcd", + "Could not load file or assembly 'abcd' or one of its dependencies. " + + "The system cannot find the file specified."); + + // Act & Assert + var ex = Assert.Throws(() => + { + tagHelperTypeResolver.Resolve("abcd"); + }); + + Assert.Equal(expectedErrorMessage, ex.Message, StringComparer.OrdinalIgnoreCase); + } + + [Fact] + public void TypeResolver_OnlyReturnsValidTagHelpersForAssemblyLookup() + { + // Arrange + var tagHelperTypeResolver = new TestTagHelperTypeResolver(TestableTagHelpers); + + // Act + var types = tagHelperTypeResolver.Resolve("Foo"); + + // Assert + Assert.Equal(ValidTestableTagHelpers, types); + } + + [Fact] + public void TypeResolver_ReturnsEmptyEnumerableIfNoValidTagHelpersFound() + { + // Arrange + var tagHelperTypeResolver = new TestTagHelperTypeResolver(InvalidTestableTagHelpers); + + // Act + var types = tagHelperTypeResolver.Resolve("Foo"); + + // Assert + Assert.Empty(types); + } + + [Theory] + [InlineData("")] + [InlineData(null)] + public void TypeResolver_ResolveThrowsIfEmptyOrNullLookupText(string name) + { + // Arrange + var tagHelperTypeResolver = new TestTagHelperTypeResolver(InvalidTestableTagHelpers); + var expectedMessage = "Tag helper directive assembly name cannot be null or empty." + + Environment.NewLine + + "Parameter name: name"; + + // Act & Assert + var ex = Assert.Throws(nameof(name), + () => + { + tagHelperTypeResolver.Resolve(name); + }); + + Assert.Equal(expectedMessage, ex.Message); + } + + protected class TestTagHelperTypeResolver : TagHelperTypeResolver + { + private IEnumerable _assemblyTypeInfos; + + public TestTagHelperTypeResolver(IEnumerable assemblyTypes) + { + _assemblyTypeInfos = assemblyTypes.Select(type => type.GetTypeInfo()); + OnGetLibraryDefinedTypes = (_) => { }; + } + + public Action OnGetLibraryDefinedTypes { get; set; } + + internal override IEnumerable GetLibraryDefinedTypes(AssemblyName assemblyName) + { + OnGetLibraryDefinedTypes(assemblyName); + + return _assemblyTypeInfos; + } + } + + public class Invalid_NestedPublicTagHelper : ITagHelper + { + } + + internal class Invalid_NestedInternalTagHelper : ITagHelper + { + } + + private class Invalid_PrivateTagHelper : ITagHelper + { + } + + protected class Invalid_ProtectedTagHelper : ITagHelper + { + } + } + + // These tag helper types must be unnested and public to potentially be valid tag helpers. + // In this case they do not fulfill other TagHelper requirements. + public abstract class Invalid_AbstractTagHelper : ITagHelper + { + } + + public class Invalid_GenericTagHelper : ITagHelper + { + } + + internal class Invalid_InternalTagHelper : ITagHelper + { + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Razor.Runtime.Test/project.json b/test/Microsoft.AspNet.Razor.Runtime.Test/project.json index 409a7fe339..f9b7e5327c 100644 --- a/test/Microsoft.AspNet.Razor.Runtime.Test/project.json +++ b/test/Microsoft.AspNet.Razor.Runtime.Test/project.json @@ -1,5 +1,6 @@ { "dependencies": { + "Microsoft.AspNet.Razor.Runtime": "", "Microsoft.AspNet.Razor.Test": "" }, "commands": { diff --git a/test/Microsoft.AspNet.Razor.Test/Generator/CodeTree/CSharpPaddingBuilderTests.cs b/test/Microsoft.AspNet.Razor.Test/Generator/CodeTree/CSharpPaddingBuilderTests.cs index 23b5534154..1ad157af24 100644 --- a/test/Microsoft.AspNet.Razor.Test/Generator/CodeTree/CSharpPaddingBuilderTests.cs +++ b/test/Microsoft.AspNet.Razor.Test/Generator/CodeTree/CSharpPaddingBuilderTests.cs @@ -8,7 +8,6 @@ using System.Linq; using Microsoft.AspNet.Razor.Generator.Compiler.CSharp; using Microsoft.AspNet.Razor.Parser; using Microsoft.AspNet.Razor.Parser.SyntaxTree; -using Microsoft.AspNet.Razor.TagHelpers; using Xunit; namespace Microsoft.AspNet.Razor.Test.Generator diff --git a/test/Microsoft.AspNet.Razor.Test/Parser/RazorParserTest.cs b/test/Microsoft.AspNet.Razor.Test/Parser/RazorParserTest.cs index ac99c0450b..2c6df35c33 100644 --- a/test/Microsoft.AspNet.Razor.Test/Parser/RazorParserTest.cs +++ b/test/Microsoft.AspNet.Razor.Test/Parser/RazorParserTest.cs @@ -5,7 +5,6 @@ using System; using System.IO; using Microsoft.AspNet.Razor.Parser; using Microsoft.AspNet.Razor.Parser.SyntaxTree; -using Microsoft.AspNet.Razor.TagHelpers; using Microsoft.AspNet.Razor.Test.Framework; using Xunit;