From 4bd02badda18e7097dbc38bcd67b48b7965f022f Mon Sep 17 00:00:00 2001 From: "N. Taylor Mullen" Date: Tue, 24 Feb 2015 19:59:19 -0800 Subject: [PATCH] Add tests to verify tagHelperPrefix directive. - Added parsing, sub-rewriting, rewriting and code generation tests for runtime/designtime. - Fixed existing tests to utilize new class structures. #309 --- .../TagHelperDescriptorResolverTest.cs | 537 +++++++++++++++++- .../Framework/TestSpanBuilder.cs | 5 + .../Generator/CSharpTagHelperRenderingTest.cs | 159 ++++-- .../Parser/CSharp/CSharpDirectivesTest.cs | 94 +++ .../TagHelperDescriptorProviderTest.cs | 104 +++- ...s => TagHelperDirectiveSpanVisitorTest.cs} | 92 ++- .../TagHelperParseTreeRewriterTest.cs | 276 +++++++++ .../BasicTagHelpers.Prefixed.DesignTime.cs | 55 ++ .../CS/Output/BasicTagHelpers.Prefixed.cs | 97 ++++ .../CS/Source/BasicTagHelpers.Prefixed.cshtml | 10 + 10 files changed, 1337 insertions(+), 92 deletions(-) rename test/Microsoft.AspNet.Razor.Test/TagHelpers/{AddOrRemoveTagHelperSpanVisitorTest.cs => TagHelperDirectiveSpanVisitorTest.cs} (71%) create mode 100644 test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/CS/Output/BasicTagHelpers.Prefixed.DesignTime.cs create mode 100644 test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/CS/Output/BasicTagHelpers.Prefixed.cs create mode 100644 test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/CS/Source/BasicTagHelpers.Prefixed.cshtml diff --git a/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/TagHelperDescriptorResolverTest.cs b/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/TagHelperDescriptorResolverTest.cs index 343ff89ddb..f8a3ab8f81 100644 --- a/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/TagHelperDescriptorResolverTest.cs +++ b/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/TagHelperDescriptorResolverTest.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; using Microsoft.AspNet.Razor.Parser; +using Microsoft.AspNet.Razor.Parser.SyntaxTree; using Microsoft.AspNet.Razor.TagHelpers; using Microsoft.AspNet.Razor.Text; using Xunit; @@ -41,6 +42,491 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers } } + + public static TheoryData ResolveDirectiveDescriptorsInvalidTagHelperPrefixData + { + get + { + var assemblyA = AssemblyName; + var stringType = typeof(string); + var assemblyB = stringType.GetTypeInfo().Assembly.GetName().Name; + var defaultAssemblyLookups = new Dictionary> + { + { assemblyA, new[] { Valid_PlainTagHelperType, Valid_InheritedTagHelperType } }, + { assemblyB, new[] { stringType } } + }; + var directiveLocation1 = new SourceLocation(1, 2, 3); + var directiveLocation2 = new SourceLocation(4, 5, 6); + var multipleDirectiveError = + "Invalid tag helper directive '{0}'. Cannot have multiple '{0}' directives on a page."; + var invalidTagHelperPrefixValueError = + "Invalid tag helper directive '{0}' value. '{1} is not allowed in prefix '{2}'."; + + return new TheoryData>, // descriptorAssemblyLookups + IEnumerable, // directiveDescriptors + IEnumerable, // expectedDescriptors + IEnumerable> // expectedErrors + { + { + defaultAssemblyLookups, + new[] + { + new TagHelperDirectiveDescriptor( + "th:", + directiveLocation1, + TagHelperDirectiveType.TagHelperPrefix), + new TagHelperDirectiveDescriptor( + "different", + directiveLocation2, + TagHelperDirectiveType.TagHelperPrefix) + }, + new TagHelperDescriptor[0], + new[] + { + new RazorError( + string.Format(multipleDirectiveError, SyntaxConstants.CSharp.TagHelperPrefixKeyword), + directiveLocation2) + } + }, + { + defaultAssemblyLookups, + new[] + { + new TagHelperDirectiveDescriptor( + "th:", + directiveLocation1, + TagHelperDirectiveType.TagHelperPrefix), + new TagHelperDirectiveDescriptor( + "different", + directiveLocation2, + TagHelperDirectiveType.TagHelperPrefix), + new TagHelperDirectiveDescriptor( + "*Plain*, " + assemblyA, + directiveLocation1, + TagHelperDirectiveType.AddTagHelper), + }, + new[] { CreatePrefixedValidPlainDescriptor("th:") }, + new[] + { + new RazorError( + string.Format(multipleDirectiveError, SyntaxConstants.CSharp.TagHelperPrefixKeyword), + directiveLocation2) + } + }, + { + defaultAssemblyLookups, + new[] + { + new TagHelperDirectiveDescriptor( + "th:", + directiveLocation1, + TagHelperDirectiveType.TagHelperPrefix), + new TagHelperDirectiveDescriptor( + "different", + directiveLocation2, + TagHelperDirectiveType.TagHelperPrefix), + new TagHelperDirectiveDescriptor( + "*Plain*, " + assemblyA, + directiveLocation1, + TagHelperDirectiveType.AddTagHelper), + new TagHelperDirectiveDescriptor( + "*String*, " + assemblyB, + directiveLocation1, + TagHelperDirectiveType.AddTagHelper), + }, + new[] { CreatePrefixedValidPlainDescriptor("th:"), CreatePrefixedStringDescriptor("th:") }, + new[] + { + new RazorError( + string.Format(multipleDirectiveError, SyntaxConstants.CSharp.TagHelperPrefixKeyword), + directiveLocation2) + } + }, + { + defaultAssemblyLookups, + new[] + { + new TagHelperDirectiveDescriptor( + "th ", + directiveLocation1, + TagHelperDirectiveType.TagHelperPrefix), + }, + new TagHelperDescriptor[0], + new[] + { + new RazorError( + string.Format( + invalidTagHelperPrefixValueError, + SyntaxConstants.CSharp.TagHelperPrefixKeyword, + ' ', + "th "), + directiveLocation1) + } + }, + { + defaultAssemblyLookups, + new[] + { + new TagHelperDirectiveDescriptor( + "th\t", + directiveLocation1, + TagHelperDirectiveType.TagHelperPrefix), + }, + new TagHelperDescriptor[0], + new[] + { + new RazorError( + string.Format( + invalidTagHelperPrefixValueError, + SyntaxConstants.CSharp.TagHelperPrefixKeyword, + '\t', + "th\t"), + directiveLocation1) + } + }, + { + defaultAssemblyLookups, + new[] + { + new TagHelperDirectiveDescriptor( + "th" + Environment.NewLine, + directiveLocation1, + TagHelperDirectiveType.TagHelperPrefix), + }, + new TagHelperDescriptor[0], + new[] + { + new RazorError( + string.Format( + invalidTagHelperPrefixValueError, + SyntaxConstants.CSharp.TagHelperPrefixKeyword, + Environment.NewLine[0], + "th" + Environment.NewLine), + directiveLocation1) + } + }, + { + defaultAssemblyLookups, + new[] + { + new TagHelperDirectiveDescriptor( + " th ", + directiveLocation1, + TagHelperDirectiveType.TagHelperPrefix), + }, + new TagHelperDescriptor[0], + new[] + { + new RazorError( + string.Format( + invalidTagHelperPrefixValueError, + SyntaxConstants.CSharp.TagHelperPrefixKeyword, + ' ', + " th "), + directiveLocation1) + } + }, + { + defaultAssemblyLookups, + new[] + { + new TagHelperDirectiveDescriptor( + "@", + directiveLocation1, + TagHelperDirectiveType.TagHelperPrefix), + }, + new TagHelperDescriptor[0], + new[] + { + new RazorError( + string.Format( + invalidTagHelperPrefixValueError, + SyntaxConstants.CSharp.TagHelperPrefixKeyword, + '@', + "@"), + directiveLocation1) + } + }, + { + defaultAssemblyLookups, + new[] + { + new TagHelperDirectiveDescriptor( + "t@h", + directiveLocation1, + TagHelperDirectiveType.TagHelperPrefix), + }, + new TagHelperDescriptor[0], + new[] + { + new RazorError( + string.Format( + invalidTagHelperPrefixValueError, + SyntaxConstants.CSharp.TagHelperPrefixKeyword, + '@', + "t@h"), + directiveLocation1) + } + }, + { + defaultAssemblyLookups, + new[] + { + new TagHelperDirectiveDescriptor( + "!", + directiveLocation1, + TagHelperDirectiveType.TagHelperPrefix), + }, + new TagHelperDescriptor[0], + new[] + { + new RazorError( + string.Format( + invalidTagHelperPrefixValueError, + SyntaxConstants.CSharp.TagHelperPrefixKeyword, + '!', + "!"), + directiveLocation1) + } + }, + { + defaultAssemblyLookups, + new[] + { + new TagHelperDirectiveDescriptor( + "!th", + directiveLocation1, + TagHelperDirectiveType.TagHelperPrefix), + }, + new TagHelperDescriptor[0], + new[] + { + new RazorError( + string.Format( + invalidTagHelperPrefixValueError, + SyntaxConstants.CSharp.TagHelperPrefixKeyword, + '!', + "!th"), + directiveLocation1) + } + }, + }; + } + } + + [Theory] + [MemberData(nameof(ResolveDirectiveDescriptorsInvalidTagHelperPrefixData))] + public void Resolve_CreatesExpectedErrorsForTagHelperPrefixDirectives( + Dictionary> descriptorAssemblyLookups, + IEnumerable directiveDescriptors, + IEnumerable expectedDescriptors, + IEnumerable expectedErrors) + { + // Arrange + var tagHelperDescriptorResolver = + new TestTagHelperDescriptorResolver( + new LookupBasedTagHelperTypeResolver(descriptorAssemblyLookups)); + var errorSink = new ParserErrorSink(); + var resolutionContext = new TagHelperDescriptorResolutionContext( + directiveDescriptors, + errorSink); + + // Act + var descriptors = tagHelperDescriptorResolver.Resolve(resolutionContext); + + // Assert + Assert.Equal(expectedErrors, errorSink.Errors); + Assert.Equal(expectedDescriptors.Count(), descriptors.Count()); + + foreach (var expectedDescriptor in expectedDescriptors) + { + Assert.Contains(expectedDescriptor, descriptors, TagHelperDescriptorComparer.Default); + } + } + + public static TheoryData ResolveDirectiveDescriptorsTagHelperPrefixData + { + get + { + var assemblyA = AssemblyName; + var stringType = typeof(string); + var assemblyB = stringType.GetTypeInfo().Assembly.GetName().Name; + var defaultAssemblyLookups = new Dictionary> + { + { assemblyA, new[] { Valid_PlainTagHelperType, Valid_InheritedTagHelperType } }, + { assemblyB, new[] { stringType } } + }; + + return new TheoryData< + Dictionary>, // descriptorAssemblyLookups + IEnumerable, // directiveDescriptors + IEnumerable> // expectedDescriptors + { + { + defaultAssemblyLookups, + new [] + { + new TagHelperDirectiveDescriptor("", TagHelperDirectiveType.TagHelperPrefix), + new TagHelperDirectiveDescriptor( + "*Plain*, " + assemblyA, + TagHelperDirectiveType.AddTagHelper), + }, + new [] { Valid_PlainTagHelperDescriptor } + }, + { + defaultAssemblyLookups, + new [] + { + new TagHelperDirectiveDescriptor("th:", TagHelperDirectiveType.TagHelperPrefix), + new TagHelperDirectiveDescriptor( + "*Plain*, " + assemblyA, + TagHelperDirectiveType.AddTagHelper), + }, + new [] { CreatePrefixedValidPlainDescriptor("th:") } + }, + { + defaultAssemblyLookups, + new [] + { + new TagHelperDirectiveDescriptor( + "*Plain*, " + assemblyA, + TagHelperDirectiveType.AddTagHelper), + new TagHelperDirectiveDescriptor("th:", TagHelperDirectiveType.TagHelperPrefix) + }, + new [] { CreatePrefixedValidPlainDescriptor("th:") } + }, + { + defaultAssemblyLookups, + new [] + { + new TagHelperDirectiveDescriptor("*, " + assemblyA, TagHelperDirectiveType.AddTagHelper), + new TagHelperDirectiveDescriptor("th:", TagHelperDirectiveType.TagHelperPrefix) + }, + new [] + { + CreatePrefixedValidPlainDescriptor("th:"), + CreatePrefixedValidInheritedDescriptor("th:") + } + }, + { + defaultAssemblyLookups, + new [] + { + new TagHelperDirectiveDescriptor("th-", TagHelperDirectiveType.TagHelperPrefix), + new TagHelperDirectiveDescriptor( + "*Plain*, " + assemblyA, + TagHelperDirectiveType.AddTagHelper), + new TagHelperDirectiveDescriptor( + "*Inherited*, " + assemblyA, + TagHelperDirectiveType.AddTagHelper) + }, + new [] + { + CreatePrefixedValidPlainDescriptor("th-"), + CreatePrefixedValidInheritedDescriptor("th-") + } + }, + { + defaultAssemblyLookups, + new [] + { + new TagHelperDirectiveDescriptor("", TagHelperDirectiveType.TagHelperPrefix), + new TagHelperDirectiveDescriptor( + "*Plain*, " + assemblyA, + TagHelperDirectiveType.AddTagHelper), + new TagHelperDirectiveDescriptor( + "*Inherited*, " + assemblyA, + TagHelperDirectiveType.AddTagHelper) + }, + new [] { Valid_PlainTagHelperDescriptor, Valid_InheritedTagHelperDescriptor } + }, + { + defaultAssemblyLookups, + new [] + { + new TagHelperDirectiveDescriptor( + "*Plain*, " + assemblyA, + TagHelperDirectiveType.AddTagHelper), + new TagHelperDirectiveDescriptor( + "*Inherited*, " + assemblyA, + TagHelperDirectiveType.AddTagHelper), + new TagHelperDirectiveDescriptor("th:", TagHelperDirectiveType.TagHelperPrefix) + }, + new [] + { + CreatePrefixedValidPlainDescriptor("th:"), + CreatePrefixedValidInheritedDescriptor("th:") + } + }, + { + defaultAssemblyLookups, + new [] + { + new TagHelperDirectiveDescriptor("th", TagHelperDirectiveType.TagHelperPrefix), + new TagHelperDirectiveDescriptor( + "*, " + assemblyA, + TagHelperDirectiveType.AddTagHelper), + new TagHelperDirectiveDescriptor( + "*, " + assemblyB, + TagHelperDirectiveType.AddTagHelper), + }, + new [] + { + CreatePrefixedValidPlainDescriptor("th"), + CreatePrefixedValidInheritedDescriptor("th"), + CreatePrefixedStringDescriptor("th") + } + }, + { + defaultAssemblyLookups, + new [] + { + new TagHelperDirectiveDescriptor( + "*, " + assemblyA, + TagHelperDirectiveType.AddTagHelper), + new TagHelperDirectiveDescriptor("th:-", TagHelperDirectiveType.TagHelperPrefix), + new TagHelperDirectiveDescriptor( + "*, " + assemblyB, + TagHelperDirectiveType.AddTagHelper), + }, + new [] + { + CreatePrefixedValidPlainDescriptor("th:-"), + CreatePrefixedValidInheritedDescriptor("th:-"), + CreatePrefixedStringDescriptor("th:-") + } + }, + }; + } + } + + [Theory] + [MemberData(nameof(ResolveDirectiveDescriptorsTagHelperPrefixData))] + public void Resolve_ReturnsPrefixedDescriptorsBasedOnDirectiveDescriptors( + Dictionary> descriptorAssemblyLookups, + IEnumerable directiveDescriptors, + IEnumerable expectedDescriptors) + { + // Arrange + var tagHelperDescriptorResolver = + new TestTagHelperDescriptorResolver( + new LookupBasedTagHelperTypeResolver(descriptorAssemblyLookups)); + var resolutionContext = new TagHelperDescriptorResolutionContext( + directiveDescriptors, + new ParserErrorSink()); + + // Act + var descriptors = tagHelperDescriptorResolver.Resolve(resolutionContext); + + // Assert + Assert.Equal(expectedDescriptors.Count(), descriptors.Count()); + + foreach (var expectedDescriptor in expectedDescriptors) + { + Assert.Contains(expectedDescriptor, descriptors, TagHelperDescriptorComparer.Default); + } + } + [Theory] [InlineData("MyType, MyAssembly", "MyAssembly")] [InlineData("*, MyAssembly2", "MyAssembly2")] @@ -477,7 +963,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers new TestTagHelperDescriptorResolver( new LookupBasedTagHelperTypeResolver(descriptorAssemblyLookups)); var resolutionContext = new TagHelperDescriptorResolutionContext( - directiveDescriptors, + directiveDescriptors, new ParserErrorSink()); // Act @@ -832,7 +1318,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers "format is: \"typeName, assemblyName\".", lookupText); var resolutionContext = new TagHelperDescriptorResolutionContext( - new [] { new TagHelperDirectiveDescriptor(lookupText, documentLocation, directiveType)}, + new[] { new TagHelperDirectiveDescriptor(lookupText, documentLocation, directiveType) }, errorSink); // Act @@ -872,6 +1358,49 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers Assert.Equal(expectedErrorMessage, error.Message); } + private static TagHelperDescriptor CreateDescriptor( + string prefix, + string tagName, + string typeName, + string assemblyName) + { + return new TagHelperDescriptor( + prefix, + tagName, + typeName, + assemblyName, + attributes: Enumerable.Empty()); + } + + private static TagHelperDescriptor CreatePrefixedValidPlainDescriptor(string prefix) + { + return CreateDescriptor( + prefix, + tagName: "valid_plain", + typeName: Valid_PlainTagHelperType.FullName, + assemblyName: AssemblyName); + } + + private static TagHelperDescriptor CreatePrefixedValidInheritedDescriptor(string prefix) + { + return CreateDescriptor( + prefix, + tagName: "valid_inherited", + typeName: Valid_InheritedTagHelperType.FullName, + assemblyName: AssemblyName); + } + + private static TagHelperDescriptor CreatePrefixedStringDescriptor(string prefix) + { + var stringType = typeof(string); + + return CreateDescriptor( + prefix, + tagName: "string", + typeName: stringType.FullName, + assemblyName: stringType.GetTypeInfo().Assembly.GetName().Name); + } + private class TestTagHelperDescriptorResolver : TagHelperDescriptorResolver { public TestTagHelperDescriptorResolver(TagHelperTypeResolver typeResolver) @@ -939,8 +1468,8 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers } protected override IEnumerable ResolveDescriptorsInAssembly( - string assemblyName, - SourceLocation documentLocation, + string assemblyName, + SourceLocation documentLocation, ParserErrorSink errorSink) { throw _error; diff --git a/test/Microsoft.AspNet.Razor.Test/Framework/TestSpanBuilder.cs b/test/Microsoft.AspNet.Razor.Test/Framework/TestSpanBuilder.cs index 033b9f6e2b..9a23533196 100644 --- a/test/Microsoft.AspNet.Razor.Test/Framework/TestSpanBuilder.cs +++ b/test/Microsoft.AspNet.Razor.Test/Framework/TestSpanBuilder.cs @@ -320,6 +320,11 @@ namespace Microsoft.AspNet.Razor.Test.Framework new AddOrRemoveTagHelperCodeGenerator(removeTagHelperDescriptors: true, lookupText: lookupText)); } + public SpanConstructor AsTagHelperPrefixDirective(string prefix) + { + return _self.With(new TagHelperPrefixDirectiveCodeGenerator(prefix)); + } + public SpanConstructor As(ISpanCodeGenerator codeGenerator) { return _self.With(codeGenerator); diff --git a/test/Microsoft.AspNet.Razor.Test/Generator/CSharpTagHelperRenderingTest.cs b/test/Microsoft.AspNet.Razor.Test/Generator/CSharpTagHelperRenderingTest.cs index 33d241a318..a43b0cabd9 100644 --- a/test/Microsoft.AspNet.Razor.Test/Generator/CSharpTagHelperRenderingTest.cs +++ b/test/Microsoft.AspNet.Razor.Test/Generator/CSharpTagHelperRenderingTest.cs @@ -14,37 +14,10 @@ namespace Microsoft.AspNet.Razor.Test.Generator { public class CSharpTagHelperRenderingTest : TagHelperTestBase { - private static IEnumerable PAndInputTagHelperDescriptors - { - get - { - var pAgePropertyInfo = typeof(TestType).GetProperty("Age"); - var inputTypePropertyInfo = typeof(TestType).GetProperty("Type"); - var checkedPropertyInfo = typeof(TestType).GetProperty("Checked"); - return new[] - { - new TagHelperDescriptor("p", - "PTagHelper", - "SomeAssembly", - new [] { - new TagHelperAttributeDescriptor("age", pAgePropertyInfo) - }), - new TagHelperDescriptor("input", - "InputTagHelper", - "SomeAssembly", - new TagHelperAttributeDescriptor[] { - new TagHelperAttributeDescriptor("type", inputTypePropertyInfo) - }), - new TagHelperDescriptor("input", - "InputTagHelper2", - "SomeAssembly", - new TagHelperAttributeDescriptor[] { - new TagHelperAttributeDescriptor("type", inputTypePropertyInfo), - new TagHelperAttributeDescriptor("checked", checkedPropertyInfo) - }) - }; - } - } + private static IEnumerable DefaultPAndInputTagHelperDescriptors + => BuildPAndInputTagHelperDescriptors(prefix: string.Empty); + private static IEnumerable PrefixedPAndInputTagHelperDescriptors + => BuildPAndInputTagHelperDescriptors("THS"); public static TheoryData TagHelperDescriptorFlowTestData { @@ -59,50 +32,64 @@ namespace Microsoft.AspNet.Razor.Test.Generator { "SingleTagHelper", "SingleTagHelper", - PAndInputTagHelperDescriptors, - PAndInputTagHelperDescriptors, + DefaultPAndInputTagHelperDescriptors, + DefaultPAndInputTagHelperDescriptors, false }, { "SingleTagHelper", "SingleTagHelper.DesignTime", - PAndInputTagHelperDescriptors, - PAndInputTagHelperDescriptors, + DefaultPAndInputTagHelperDescriptors, + DefaultPAndInputTagHelperDescriptors, true }, { "BasicTagHelpers", "BasicTagHelpers", - PAndInputTagHelperDescriptors, - PAndInputTagHelperDescriptors, + DefaultPAndInputTagHelperDescriptors, + DefaultPAndInputTagHelperDescriptors, false }, { "BasicTagHelpers", "BasicTagHelpers.DesignTime", - PAndInputTagHelperDescriptors, - PAndInputTagHelperDescriptors, + DefaultPAndInputTagHelperDescriptors, + DefaultPAndInputTagHelperDescriptors, true }, { "BasicTagHelpers.RemoveTagHelper", "BasicTagHelpers.RemoveTagHelper", - PAndInputTagHelperDescriptors, + DefaultPAndInputTagHelperDescriptors, Enumerable.Empty(), false }, + { + "BasicTagHelpers.Prefixed", + "BasicTagHelpers.Prefixed", + PrefixedPAndInputTagHelperDescriptors, + PrefixedPAndInputTagHelperDescriptors, + false + }, + { + "BasicTagHelpers.Prefixed", + "BasicTagHelpers.Prefixed.DesignTime", + PrefixedPAndInputTagHelperDescriptors, + PrefixedPAndInputTagHelperDescriptors, + true + }, { "ComplexTagHelpers", "ComplexTagHelpers", - PAndInputTagHelperDescriptors, - PAndInputTagHelperDescriptors, + DefaultPAndInputTagHelperDescriptors, + DefaultPAndInputTagHelperDescriptors, false }, { "ComplexTagHelpers", "ComplexTagHelpers.DesignTime", - PAndInputTagHelperDescriptors, - PAndInputTagHelperDescriptors, + DefaultPAndInputTagHelperDescriptors, + DefaultPAndInputTagHelperDescriptors, true } }; @@ -141,7 +128,7 @@ namespace Microsoft.AspNet.Razor.Test.Generator { "SingleTagHelper", "SingleTagHelper.DesignTime", - PAndInputTagHelperDescriptors, + DefaultPAndInputTagHelperDescriptors, new List { BuildLineMapping(documentAbsoluteIndex: 14, @@ -161,7 +148,7 @@ namespace Microsoft.AspNet.Razor.Test.Generator { "BasicTagHelpers", "BasicTagHelpers.DesignTime", - PAndInputTagHelperDescriptors, + DefaultPAndInputTagHelperDescriptors, new List { BuildLineMapping(documentAbsoluteIndex: 14, @@ -178,10 +165,36 @@ namespace Microsoft.AspNet.Razor.Test.Generator contentLength: 4) } }, + { + "BasicTagHelpers.Prefixed", + "BasicTagHelpers.Prefixed.DesignTime", + PrefixedPAndInputTagHelperDescriptors, + new List + { + BuildLineMapping(documentAbsoluteIndex: 17, + documentLineIndex: 0, + generatedAbsoluteIndex: 496, + generatedLineIndex: 15, + characterOffsetIndex: 17, + contentLength: 5), + BuildLineMapping(documentAbsoluteIndex: 38, + documentLineIndex: 1, + generatedAbsoluteIndex: 655, + generatedLineIndex: 22, + characterOffsetIndex: 14, + contentLength: 17), + BuildLineMapping(documentAbsoluteIndex: 228, + documentLineIndex: 7, + generatedAbsoluteIndex: 1480, + generatedLineIndex: 46, + characterOffsetIndex: 43, + contentLength: 4) + } + }, { "ComplexTagHelpers", "ComplexTagHelpers.DesignTime", - PAndInputTagHelperDescriptors, + DefaultPAndInputTagHelperDescriptors, new List { BuildLineMapping(14, 0, 479, 15, 14, 17), @@ -217,7 +230,7 @@ namespace Microsoft.AspNet.Razor.Test.Generator { "EmptyAttributeTagHelpers", "EmptyAttributeTagHelpers.DesignTime", - PAndInputTagHelperDescriptors, + DefaultPAndInputTagHelperDescriptors, new List { BuildLineMapping(documentAbsoluteIndex: 14, @@ -251,7 +264,7 @@ namespace Microsoft.AspNet.Razor.Test.Generator { "EscapedTagHelpers", "EscapedTagHelpers.DesignTime", - PAndInputTagHelperDescriptors, + DefaultPAndInputTagHelperDescriptors, new List { BuildLineMapping(documentAbsoluteIndex: 14, @@ -308,12 +321,13 @@ namespace Microsoft.AspNet.Razor.Test.Generator // Note: The baseline resource name is equivalent to the test resource name. return new TheoryData> { - { "SingleTagHelper", PAndInputTagHelperDescriptors }, - { "BasicTagHelpers", PAndInputTagHelperDescriptors }, - { "BasicTagHelpers.RemoveTagHelper", PAndInputTagHelperDescriptors }, - { "ComplexTagHelpers", PAndInputTagHelperDescriptors }, - { "EmptyAttributeTagHelpers", PAndInputTagHelperDescriptors }, - { "EscapedTagHelpers", PAndInputTagHelperDescriptors }, + { "SingleTagHelper", DefaultPAndInputTagHelperDescriptors }, + { "BasicTagHelpers", DefaultPAndInputTagHelperDescriptors }, + { "BasicTagHelpers.RemoveTagHelper", DefaultPAndInputTagHelperDescriptors }, + { "BasicTagHelpers.Prefixed", PrefixedPAndInputTagHelperDescriptors }, + { "ComplexTagHelpers", DefaultPAndInputTagHelperDescriptors }, + { "EmptyAttributeTagHelpers", DefaultPAndInputTagHelperDescriptors }, + { "EscapedTagHelpers", DefaultPAndInputTagHelperDescriptors }, }; } } @@ -395,6 +409,41 @@ namespace Microsoft.AspNet.Razor.Test.Generator RunTagHelperTest(testType, tagHelperDescriptors: tagHelperDescriptors); } + private static IEnumerable BuildPAndInputTagHelperDescriptors(string prefix) + { + var pAgePropertyInfo = typeof(TestType).GetProperty("Age"); + var inputTypePropertyInfo = typeof(TestType).GetProperty("Type"); + var checkedPropertyInfo = typeof(TestType).GetProperty("Checked"); + return new[] + { + new TagHelperDescriptor( + prefix, + tagName: "p", + typeName: "PTagHelper", + assemblyName: "SomeAssembly", + attributes: new [] { + new TagHelperAttributeDescriptor("age", pAgePropertyInfo) + }), + new TagHelperDescriptor( + prefix, + tagName: "input", + typeName: "InputTagHelper", + assemblyName: "SomeAssembly", + attributes: new TagHelperAttributeDescriptor[] { + new TagHelperAttributeDescriptor("type", inputTypePropertyInfo) + }), + new TagHelperDescriptor( + prefix, + tagName: "input", + typeName: "InputTagHelper2", + assemblyName: "SomeAssembly", + attributes: new TagHelperAttributeDescriptor[] { + new TagHelperAttributeDescriptor("type", inputTypePropertyInfo), + new TagHelperAttributeDescriptor("checked", checkedPropertyInfo) + }) + }; + } + private class TestType { public int Age { get; set; } diff --git a/test/Microsoft.AspNet.Razor.Test/Parser/CSharp/CSharpDirectivesTest.cs b/test/Microsoft.AspNet.Razor.Test/Parser/CSharp/CSharpDirectivesTest.cs index 7ba38e31a2..0ca2be065d 100644 --- a/test/Microsoft.AspNet.Razor.Test/Parser/CSharp/CSharpDirectivesTest.cs +++ b/test/Microsoft.AspNet.Razor.Test/Parser/CSharp/CSharpDirectivesTest.cs @@ -12,6 +12,100 @@ namespace Microsoft.AspNet.Razor.Test.Parser.CSharp { public class CSharpDirectivesTest : CsHtmlCodeParserTestBase { + [Fact] + public void TagHelperPrefixDirective_NoValueSucceeds() + { + ParseBlockTest("@tagHelperPrefix \"\"", + new DirectiveBlock( + Factory.CodeTransition(), + Factory + .MetaCode(SyntaxConstants.CSharp.TagHelperPrefixKeyword + " ") + .Accepts(AcceptedCharacters.None), + Factory.Code("\"\"").AsTagHelperPrefixDirective(""))); + } + + [Fact] + public void TagHelperPrefixDirective_Succeeds() + { + ParseBlockTest("@tagHelperPrefix \"Foo\"", + new DirectiveBlock( + Factory.CodeTransition(), + Factory + .MetaCode(SyntaxConstants.CSharp.TagHelperPrefixKeyword + " ") + .Accepts(AcceptedCharacters.None), + Factory.Code("\"Foo\"").AsTagHelperPrefixDirective("Foo"))); + } + + [Fact] + public void TagHelperPrefixDirective_RequiresValue() + { + ParseBlockTest("@tagHelperPrefix ", + new DirectiveBlock( + Factory.CodeTransition(), + Factory + .MetaCode(SyntaxConstants.CSharp.TagHelperPrefixKeyword + " ") + .Accepts(AcceptedCharacters.None), + Factory.EmptyCSharp().AsTagHelperPrefixDirective(string.Empty)), + new RazorError( + RazorResources.FormatParseError_DirectiveMustHaveValue( + SyntaxConstants.CSharp.TagHelperPrefixKeyword), + absoluteIndex: 17, lineIndex: 0, columnIndex: 17)); + } + + [Fact] + public void TagHelperPrefixDirective_StartQuoteRequiresDoubleQuotesAroundValue() + { + ParseBlockTest("@tagHelperPrefix \"Foo", + new DirectiveBlock( + Factory.CodeTransition(), + Factory + .MetaCode(SyntaxConstants.CSharp.TagHelperPrefixKeyword + " ") + .Accepts(AcceptedCharacters.None), + Factory.Code("\"Foo").AsTagHelperPrefixDirective("Foo")), + new RazorError( + RazorResources.ParseError_Unterminated_String_Literal, + absoluteIndex: 17, lineIndex: 0, columnIndex: 17), + new RazorError( + RazorResources.FormatParseError_DirectiveMustBeSurroundedByQuotes( + SyntaxConstants.CSharp.TagHelperPrefixKeyword), + absoluteIndex: 17, lineIndex: 0, columnIndex: 17)); + } + + [Fact] + public void TagHelperPrefixDirective_EndQuoteRequiresDoubleQuotesAroundValue() + { + ParseBlockTest("@tagHelperPrefix Foo\"", + new DirectiveBlock( + Factory.CodeTransition(), + Factory + .MetaCode(SyntaxConstants.CSharp.TagHelperPrefixKeyword + " ") + .Accepts(AcceptedCharacters.None), + Factory.Code("Foo\"").AsTagHelperPrefixDirective("Foo")), + new RazorError( + RazorResources.ParseError_Unterminated_String_Literal, + absoluteIndex: 20, lineIndex: 0, columnIndex: 20), + new RazorError( + RazorResources.FormatParseError_DirectiveMustBeSurroundedByQuotes( + SyntaxConstants.CSharp.TagHelperPrefixKeyword), + absoluteIndex: 17, lineIndex: 0, columnIndex: 17)); + } + + [Fact] + public void TagHelperPrefixDirective_RequiresDoubleQuotesAroundValue() + { + ParseBlockTest("@tagHelperPrefix Foo", + new DirectiveBlock( + Factory.CodeTransition(), + Factory + .MetaCode(SyntaxConstants.CSharp.TagHelperPrefixKeyword + " ") + .Accepts(AcceptedCharacters.None), + Factory.Code("Foo").AsTagHelperPrefixDirective("Foo")), + new RazorError( + RazorResources.FormatParseError_DirectiveMustBeSurroundedByQuotes( + SyntaxConstants.CSharp.TagHelperPrefixKeyword), + absoluteIndex: 17, lineIndex: 0, columnIndex: 17)); + } + [Fact] public void RemoveTagHelperDirective_Succeeds() { diff --git a/test/Microsoft.AspNet.Razor.Test/TagHelpers/TagHelperDescriptorProviderTest.cs b/test/Microsoft.AspNet.Razor.Test/TagHelpers/TagHelperDescriptorProviderTest.cs index 017ae4a9ed..978653e099 100644 --- a/test/Microsoft.AspNet.Razor.Test/TagHelpers/TagHelperDescriptorProviderTest.cs +++ b/test/Microsoft.AspNet.Razor.Test/TagHelpers/TagHelperDescriptorProviderTest.cs @@ -10,7 +10,93 @@ namespace Microsoft.AspNet.Razor.Test.TagHelpers public class TagHelperDescriptorProviderTest { [Fact] - public void TagHelperDescriptorProvider_GetTagHelpersReturnsNothingForUnregisteredTags() + public void GetTagHelpers_ReturnsEmptyDescriptorsWithPrefixAsTagName() + { + // Arrange + var catchAllDescriptor = CreatePrefixedDescriptor("th", "*", "foo1"); + var descriptors = new[] { catchAllDescriptor }; + var provider = new TagHelperDescriptorProvider(descriptors); + + // Act + var resolvedDescriptors = provider.GetTagHelpers("th"); + + // Assert + Assert.Empty(resolvedDescriptors); + } + + [Fact] + public void GetTagHelpers_OnlyUnderstandsSinglePrefix() + { + // Arrange + var divDescriptor = CreatePrefixedDescriptor("th:", "div", "foo1"); + var spanDescriptor = CreatePrefixedDescriptor("th2:", "span", "foo2"); + var descriptors = new[] { divDescriptor, spanDescriptor }; + var provider = new TagHelperDescriptorProvider(descriptors); + + // Act + var retrievedDescriptorsDiv = provider.GetTagHelpers("th:div"); + var retrievedDescriptorsSpan = provider.GetTagHelpers("th2:span"); + + // Assert + var descriptor = Assert.Single(retrievedDescriptorsDiv); + Assert.Same(divDescriptor, descriptor); + Assert.Empty(retrievedDescriptorsSpan); + } + + [Fact] + public void GetTagHelpers_ReturnsCatchAllDescriptorsForPrefixedTags() + { + // Arrange + var catchAllDescriptor = CreatePrefixedDescriptor("th:", "*", "foo1"); + var descriptors = new[] { catchAllDescriptor }; + var provider = new TagHelperDescriptorProvider(descriptors); + + // Act + var retrievedDescriptorsDiv = provider.GetTagHelpers("th:div"); + var retrievedDescriptorsSpan = provider.GetTagHelpers("th:span"); + + // Assert + var descriptor = Assert.Single(retrievedDescriptorsDiv); + Assert.Same(catchAllDescriptor, descriptor); + descriptor = Assert.Single(retrievedDescriptorsSpan); + Assert.Same(catchAllDescriptor, descriptor); + } + + [Fact] + public void GetTagHelpers_ReturnsDescriptorsForPrefixedTags() + { + // Arrange + var divDescriptor = CreatePrefixedDescriptor("th:", "div", "foo1"); + var descriptors = new[] { divDescriptor }; + var provider = new TagHelperDescriptorProvider(descriptors); + + // Act + var retrievedDescriptors = provider.GetTagHelpers("th:div"); + + // Assert + var descriptor = Assert.Single(retrievedDescriptors); + Assert.Same(divDescriptor, descriptor); + } + + [Theory] + [InlineData("*")] + [InlineData("div")] + public void GetTagHelpers_ReturnsNothingForUnprefixedTags(string tagName) + { + // Arrange + var divDescriptor = CreatePrefixedDescriptor("th:", tagName, "foo1"); + var descriptors = new[] { divDescriptor }; + var provider = new TagHelperDescriptorProvider(descriptors); + + // Act + var retrievedDescriptorsDiv = provider.GetTagHelpers("div"); + + // Assert + Assert.Empty(retrievedDescriptorsDiv); + } + + [Fact] + public void GetTagHelpers_ReturnsNothingForUnregisteredTags() { // Arrange var divDescriptor = new TagHelperDescriptor("div", "foo1", "SomeAssembly"); @@ -26,7 +112,7 @@ namespace Microsoft.AspNet.Razor.Test.TagHelpers } [Fact] - public void TagHelperDescriptorProvider_GetTagHelpersDoesNotReturnNonCatchAllTagsForCatchAll() + public void GetTagHelpers_DoesNotReturnNonCatchAllTagsForCatchAll() { // Arrange var divDescriptor = new TagHelperDescriptor("div", "foo1", "SomeAssembly"); @@ -44,7 +130,7 @@ namespace Microsoft.AspNet.Razor.Test.TagHelpers } [Fact] - public void TagHelperDescriptorProvider_GetTagHelpersReturnsCatchAllsWithEveryTagName() + public void GetTagHelpers_ReturnsCatchAllsWithEveryTagName() { // Arrange var divDescriptor = new TagHelperDescriptor("div", "foo1", "SomeAssembly"); @@ -70,7 +156,7 @@ namespace Microsoft.AspNet.Razor.Test.TagHelpers } [Fact] - public void TagHelperDescriptorProvider_DuplicateDescriptorsAreNotPartOfTagHelperDescriptorPool() + public void GetTagHelpers_DuplicateDescriptorsAreNotPartOfTagHelperDescriptorPool() { // Arrange var divDescriptor = new TagHelperDescriptor("div", "foo1", "SomeAssembly"); @@ -84,5 +170,15 @@ namespace Microsoft.AspNet.Razor.Test.TagHelpers var descriptor = Assert.Single(retrievedDescriptors); Assert.Same(divDescriptor, descriptor); } + + private static TagHelperDescriptor CreatePrefixedDescriptor(string prefix, string tagName, string typeName) + { + return new TagHelperDescriptor( + prefix, + tagName, + typeName, + assemblyName: "SomeAssembly", + attributes: Enumerable.Empty()); + } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Razor.Test/TagHelpers/AddOrRemoveTagHelperSpanVisitorTest.cs b/test/Microsoft.AspNet.Razor.Test/TagHelpers/TagHelperDirectiveSpanVisitorTest.cs similarity index 71% rename from test/Microsoft.AspNet.Razor.Test/TagHelpers/AddOrRemoveTagHelperSpanVisitorTest.cs rename to test/Microsoft.AspNet.Razor.Test/TagHelpers/TagHelperDirectiveSpanVisitorTest.cs index 53fa28bd77..48af054303 100644 --- a/test/Microsoft.AspNet.Razor.Test/TagHelpers/AddOrRemoveTagHelperSpanVisitorTest.cs +++ b/test/Microsoft.AspNet.Razor.Test/TagHelpers/TagHelperDirectiveSpanVisitorTest.cs @@ -16,7 +16,7 @@ using Xunit; namespace Microsoft.AspNet.Razor.TagHelpers { - public class AddOrRemoveTagHelperSpanVisitorTest + public class TagHelperDirectiveSpanVisitorTest { private static readonly SpanFactory Factory = SpanFactory.CreateCsHtml(); @@ -28,16 +28,17 @@ namespace Microsoft.AspNet.Razor.TagHelpers var resolver = new Mock(); resolver.Setup(mock => mock.Resolve(It.IsAny())) .Returns(Enumerable.Empty()); - var addOrRemoveTagHelperSpanVisitor = new AddOrRemoveTagHelperSpanVisitor( + var tagHelperDirectiveSpanVisitor = new TagHelperDirectiveSpanVisitor( resolver.Object, new ParserErrorSink()); var document = new MarkupBlock( Factory.Code("\"one\"").AsAddTagHelper("one"), Factory.Code("\"two\"").AsRemoveTagHelper("two"), - Factory.Code("\"three\"").AsRemoveTagHelper("three")); + Factory.Code("\"three\"").AsRemoveTagHelper("three"), + Factory.Code("\"four\"").AsTagHelperPrefixDirective("four")); // Act - addOrRemoveTagHelperSpanVisitor.GetDescriptors(document); + tagHelperDirectiveSpanVisitor.GetDescriptors(document); // Assert resolver.Verify(mock => mock.Resolve(It.IsAny()), Times.Once); @@ -49,25 +50,28 @@ namespace Microsoft.AspNet.Razor.TagHelpers { // Arrange var resolver = new TestTagHelperDescriptorResolver(); - var addOrRemoveTagHelperSpanVisitor = new AddOrRemoveTagHelperSpanVisitor(resolver, new ParserErrorSink()); + var tagHelperDirectiveSpanVisitor = new TagHelperDirectiveSpanVisitor(resolver, new ParserErrorSink()); var document = new MarkupBlock( Factory.Code("\"one\"").AsAddTagHelper("one"), Factory.Code("\"two\"").AsRemoveTagHelper("two"), - Factory.Code("\"three\"").AsRemoveTagHelper("three")); - var expectedRegistrations = new TagHelperDirectiveDescriptor[] + Factory.Code("\"three\"").AsRemoveTagHelper("three"), + Factory.Code("\"four\"").AsTagHelperPrefixDirective("four")); + var expectedDescriptors = new TagHelperDirectiveDescriptor[] { new TagHelperDirectiveDescriptor("one", TagHelperDirectiveType.AddTagHelper), new TagHelperDirectiveDescriptor("two", TagHelperDirectiveType.RemoveTagHelper), new TagHelperDirectiveDescriptor("three", TagHelperDirectiveType.RemoveTagHelper), + new TagHelperDirectiveDescriptor("four", TagHelperDirectiveType.TagHelperPrefix), }; // Act - addOrRemoveTagHelperSpanVisitor.GetDescriptors(document); + tagHelperDirectiveSpanVisitor.GetDescriptors(document); // Assert - Assert.Equal(expectedRegistrations, - resolver.DirectiveDescriptors, - TagHelperDirectiveDescriptorComparer.Default); + Assert.Equal( + expectedDescriptors, + resolver.DirectiveDescriptors, + TagHelperDirectiveDescriptorComparer.Default); } [Fact] @@ -80,29 +84,32 @@ namespace Microsoft.AspNet.Razor.TagHelpers new TagHelperDirectiveDescriptor("one", TagHelperDirectiveType.AddTagHelper), new TagHelperDirectiveDescriptor("two", TagHelperDirectiveType.RemoveTagHelper), new TagHelperDirectiveDescriptor("three", TagHelperDirectiveType.RemoveTagHelper), + new TagHelperDirectiveDescriptor("four", TagHelperDirectiveType.TagHelperPrefix), }; var expectedEndDirectiveDescriptors = new TagHelperDirectiveDescriptor[] { new TagHelperDirectiveDescriptor("custom", TagHelperDirectiveType.AddTagHelper) }; - var addOrRemoveTagHelperSpanVisitor = new CustomAddOrRemoveTagHelperSpanVisitor( + var tagHelperDirectiveSpanVisitor = new CustomTagHelperDirectiveSpanVisitor( resolver, (descriptors, errorSink) => { - Assert.Equal(expectedInitialDirectiveDescriptors, - descriptors, - TagHelperDirectiveDescriptorComparer.Default); + Assert.Equal( + expectedInitialDirectiveDescriptors, + descriptors, + TagHelperDirectiveDescriptorComparer.Default); return new TagHelperDescriptorResolutionContext(expectedEndDirectiveDescriptors, errorSink); }); var document = new MarkupBlock( Factory.Code("\"one\"").AsAddTagHelper("one"), Factory.Code("\"two\"").AsRemoveTagHelper("two"), - Factory.Code("\"three\"").AsRemoveTagHelper("three")); + Factory.Code("\"three\"").AsRemoveTagHelper("three"), + Factory.Code("\"four\"").AsTagHelperPrefixDirective("four")); // Act - addOrRemoveTagHelperSpanVisitor.GetDescriptors(document); + tagHelperDirectiveSpanVisitor.GetDescriptors(document); // Assert Assert.Equal(expectedEndDirectiveDescriptors, @@ -110,12 +117,39 @@ namespace Microsoft.AspNet.Razor.TagHelpers TagHelperDirectiveDescriptorComparer.Default); } + [Fact] + public void GetDescriptors_LocatesTagHelperPrefixDirectiveCodeGenerator() + { + // Arrange + var resolver = new TestTagHelperDescriptorResolver(); + var tagHelperDirectiveSpanVisitor = new TagHelperDirectiveSpanVisitor(resolver, new ParserErrorSink()); + var document = new MarkupBlock( + new DirectiveBlock( + Factory.CodeTransition(), + Factory + .MetaCode(SyntaxConstants.CSharp.TagHelperPrefixKeyword + " ") + .Accepts(AcceptedCharacters.None), + Factory.Code("\"something\"").AsTagHelperPrefixDirective("something"))); + var expectedDirectiveDescriptor = + new TagHelperDirectiveDescriptor("something", TagHelperDirectiveType.TagHelperPrefix); + + // Act + tagHelperDirectiveSpanVisitor.GetDescriptors(document); + + // Assert + var directiveDescriptor = Assert.Single(resolver.DirectiveDescriptors); + Assert.Equal( + expectedDirectiveDescriptor, + directiveDescriptor, + TagHelperDirectiveDescriptorComparer.Default); + } + [Fact] public void GetDescriptors_LocatesAddTagHelperCodeGenerator() { // Arrange var resolver = new TestTagHelperDescriptorResolver(); - var addOrRemoveTagHelperSpanVisitor = new AddOrRemoveTagHelperSpanVisitor(resolver, new ParserErrorSink()); + var tagHelperDirectiveSpanVisitor = new TagHelperDirectiveSpanVisitor(resolver, new ParserErrorSink()); var document = new MarkupBlock( new DirectiveBlock( Factory.CodeTransition(), @@ -127,7 +161,7 @@ namespace Microsoft.AspNet.Razor.TagHelpers new TagHelperDirectiveDescriptor("something", TagHelperDirectiveType.AddTagHelper); // Act - addOrRemoveTagHelperSpanVisitor.GetDescriptors(document); + tagHelperDirectiveSpanVisitor.GetDescriptors(document); // Assert var directiveDescriptor = Assert.Single(resolver.DirectiveDescriptors); @@ -139,7 +173,7 @@ namespace Microsoft.AspNet.Razor.TagHelpers { // Arrange var resolver = new TestTagHelperDescriptorResolver(); - var addOrRemoveTagHelperSpanVisitor = new AddOrRemoveTagHelperSpanVisitor(resolver, new ParserErrorSink()); + var tagHelperDirectiveSpanVisitor = new TagHelperDirectiveSpanVisitor(resolver, new ParserErrorSink()); var document = new MarkupBlock( new DirectiveBlock( Factory.CodeTransition(), @@ -151,7 +185,7 @@ namespace Microsoft.AspNet.Razor.TagHelpers new TagHelperDirectiveDescriptor("something", TagHelperDirectiveType.RemoveTagHelper); // Act - addOrRemoveTagHelperSpanVisitor.GetDescriptors(document); + tagHelperDirectiveSpanVisitor.GetDescriptors(document); // Assert var directiveDescriptor = Assert.Single(resolver.DirectiveDescriptors); @@ -162,14 +196,14 @@ namespace Microsoft.AspNet.Razor.TagHelpers public void GetDescriptors_RemoveTagHelperNotInDocument_DoesNotThrow() { // Arrange - var addOrRemoveTagHelperSpanVisitor = - new AddOrRemoveTagHelperSpanVisitor( + var tagHelperDirectiveSpanVisitor = + new TagHelperDirectiveSpanVisitor( new TestTagHelperDescriptorResolver(), new ParserErrorSink()); var document = new MarkupBlock(Factory.Markup("Hello World")); // Act - var descriptors = addOrRemoveTagHelperSpanVisitor.GetDescriptors(document); + var descriptors = tagHelperDirectiveSpanVisitor.GetDescriptors(document); Assert.Empty(descriptors); } @@ -203,8 +237,8 @@ namespace Microsoft.AspNet.Razor.TagHelpers public bool Equals(TagHelperDirectiveDescriptor directiveDescriptorX, TagHelperDirectiveDescriptor directiveDescriptorY) { - return string.Equals(directiveDescriptorX.LookupText, - directiveDescriptorY.LookupText, + return string.Equals(directiveDescriptorX.DirectiveText, + directiveDescriptorY.DirectiveText, StringComparison.Ordinal) && directiveDescriptorX.DirectiveType == directiveDescriptorY.DirectiveType; } @@ -213,19 +247,19 @@ namespace Microsoft.AspNet.Razor.TagHelpers { return HashCodeCombiner.Start() .Add(base.GetHashCode()) - .Add(directiveDescriptor.LookupText) + .Add(directiveDescriptor.DirectiveText) .Add(directiveDescriptor.DirectiveType) .CombinedHash; } } - private class CustomAddOrRemoveTagHelperSpanVisitor : AddOrRemoveTagHelperSpanVisitor + private class CustomTagHelperDirectiveSpanVisitor : TagHelperDirectiveSpanVisitor { private Func, ParserErrorSink, TagHelperDescriptorResolutionContext> _replacer; - public CustomAddOrRemoveTagHelperSpanVisitor( + public CustomTagHelperDirectiveSpanVisitor( ITagHelperDescriptorResolver descriptorResolver, Func, ParserErrorSink, diff --git a/test/Microsoft.AspNet.Razor.Test/TagHelpers/TagHelperParseTreeRewriterTest.cs b/test/Microsoft.AspNet.Razor.Test/TagHelpers/TagHelperParseTreeRewriterTest.cs index 58a460b966..4bd3966446 100644 --- a/test/Microsoft.AspNet.Razor.Test/TagHelpers/TagHelperParseTreeRewriterTest.cs +++ b/test/Microsoft.AspNet.Razor.Test/TagHelpers/TagHelperParseTreeRewriterTest.cs @@ -19,6 +19,282 @@ namespace Microsoft.AspNet.Razor.Test.TagHelpers { public class TagHelperParseTreeRewriterTest : CsHtmlMarkupParserTestBase { + public static TheoryData PrefixedTagHelperBoundData + { + get + { + var factory = CreateDefaultSpanFactory(); + var blockFactory = new BlockFactory(factory); + var availableDescriptorsColon = new TagHelperDescriptor[] + { + new TagHelperDescriptor( + prefix: "th:", + tagName: "myth", + typeName: "mythTagHelper", + assemblyName: "SomeAssembly", + attributes: Enumerable.Empty()), + new TagHelperDescriptor( + prefix: "th:", + tagName: "myth2", + typeName: "mythTagHelper2", + assemblyName: "SomeAssembly", + attributes: new [] + { + new TagHelperAttributeDescriptor( + name: "bound", + propertyName: "Bound", + typeName: typeof(bool).FullName), + }) + }; + var availableDescriptorsText = new TagHelperDescriptor[] + { + new TagHelperDescriptor( + prefix: "PREFIX", + tagName: "myth", + typeName: "mythTagHelper", + assemblyName: "SomeAssembly", + attributes: Enumerable.Empty()), + new TagHelperDescriptor( + prefix: "PREFIX", + tagName: "myth2", + typeName: "mythTagHelper2", + assemblyName: "SomeAssembly", + attributes: new [] + { + new TagHelperAttributeDescriptor( + name: "bound", + propertyName: "Bound", + typeName: typeof(bool).FullName), + }) + }; + var availableDescriptorsCatchAll = new TagHelperDescriptor[] + { + new TagHelperDescriptor( + prefix: "myth", + tagName: "*", + typeName: "mythTagHelper", + assemblyName: "SomeAssembly", + attributes: Enumerable.Empty()), + }; + + // documentContent, expectedOutput, availableDescriptors + return new TheoryData> + { + { + "", + new MarkupBlock(blockFactory.MarkupTagBlock("")), + availableDescriptorsCatchAll + }, + { + "words and spaces", + new MarkupBlock( + blockFactory.MarkupTagBlock(""), + factory.Markup("words and spaces"), + blockFactory.MarkupTagBlock("")), + availableDescriptorsCatchAll + }, + { + "", + new MarkupBlock( + new MarkupTagHelperBlock("th:myth", selfClosing: true)), + availableDescriptorsColon + }, + { + "", + new MarkupBlock( + new MarkupTagHelperBlock("PREFIXmyth", selfClosing: true)), + availableDescriptorsText + }, + { + "", + new MarkupBlock( + new MarkupTagHelperBlock("th:myth")), + availableDescriptorsColon + }, + { + "", + new MarkupBlock( + new MarkupTagHelperBlock("PREFIXmyth")), + availableDescriptorsText + }, + { + "", + new MarkupBlock( + new MarkupTagHelperBlock( + "th:myth", + blockFactory.MarkupTagBlock(""), + blockFactory.MarkupTagBlock(""))), + availableDescriptorsColon + }, + { + "", + new MarkupBlock( + new MarkupTagHelperBlock( + "PREFIXmyth", + blockFactory.MarkupTagBlock(""), + blockFactory.MarkupTagBlock(""))), + availableDescriptorsText + }, + { + "", + new MarkupBlock( + blockFactory.EscapedMarkupTagBlock("<", "th:myth />")), + availableDescriptorsColon + }, + { + "", + new MarkupBlock( + blockFactory.EscapedMarkupTagBlock("<", "PREFIXmyth />")), + availableDescriptorsText + }, + { + "", + new MarkupBlock( + blockFactory.EscapedMarkupTagBlock("<", "th:myth>"), + blockFactory.EscapedMarkupTagBlock("")), + availableDescriptorsColon + }, + { + "", + new MarkupBlock( + blockFactory.EscapedMarkupTagBlock("<", "PREFIXmyth>"), + blockFactory.EscapedMarkupTagBlock("")), + availableDescriptorsText + }, + { + "", + new MarkupBlock( + new MarkupTagHelperBlock( + "th:myth", + selfClosing: true, + attributes: new Dictionary + { + { "class", factory.Markup("btn") } + })), + availableDescriptorsColon + }, + { + "", + new MarkupBlock( + new MarkupTagHelperBlock( + "PREFIXmyth", + selfClosing: true, + attributes: new Dictionary + { + { "class", factory.Markup("btn") } + })), + availableDescriptorsText + }, + { + "", + new MarkupBlock( + new MarkupTagHelperBlock( + "th:myth2", + selfClosing: true, + attributes: new Dictionary + { + { "class", factory.Markup("btn") } + })), + availableDescriptorsColon + }, + { + "", + new MarkupBlock( + new MarkupTagHelperBlock( + "PREFIXmyth2", + selfClosing: true, + attributes: new Dictionary + { + { "class", factory.Markup("btn") } + })), + availableDescriptorsText + }, + { + "words and spaces", + new MarkupBlock( + new MarkupTagHelperBlock( + "th:myth", + attributes: new Dictionary + { + { "class", factory.Markup("btn") } + }, + children: factory.Markup("words and spaces"))), + availableDescriptorsColon + }, + { + "words and spaces", + new MarkupBlock( + new MarkupTagHelperBlock( + "PREFIXmyth", + attributes: new Dictionary + { + { "class", factory.Markup("btn") } + }, + children: factory.Markup("words and spaces"))), + availableDescriptorsText + }, + { + "", + new MarkupBlock( + new MarkupTagHelperBlock( + "th:myth2", + selfClosing: true, + attributes: new Dictionary + { + { + "bound", + new MarkupBlock( + new MarkupBlock( + new ExpressionBlock( + factory.CodeTransition(), + factory.Code("DateTime.Now") + .AsImplicitExpression(CSharpCodeParser.DefaultKeywords) + .Accepts(AcceptedCharacters.NonWhiteSpace)))) } + })), + availableDescriptorsColon + }, + { + "", + new MarkupBlock( + new MarkupTagHelperBlock( + "PREFIXmyth2", + selfClosing: true, + attributes: new Dictionary + { + { + "bound", + new MarkupBlock( + new MarkupBlock( + new ExpressionBlock( + factory.CodeTransition(), + factory.Code("DateTime.Now") + .AsImplicitExpression(CSharpCodeParser.DefaultKeywords) + .Accepts(AcceptedCharacters.NonWhiteSpace)))) } + })), + availableDescriptorsText + }, + }; + } + } + + [Theory] + [MemberData(nameof(PrefixedTagHelperBoundData))] + public void Rewrite_AllowsPrefixedTagHelpers( + string documentContent, + MarkupBlock expectedOutput, + IEnumerable availableDescriptors) + { + // Arrange + var descriptorProvider = new TagHelperDescriptorProvider(availableDescriptors); + + // Act & Assert + EvaluateData( + descriptorProvider, + documentContent, + expectedOutput, + expectedErrors: Enumerable.Empty()); + } + public static TheoryData EmptyTagHelperBoundAttributeData { get diff --git a/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/CS/Output/BasicTagHelpers.Prefixed.DesignTime.cs b/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/CS/Output/BasicTagHelpers.Prefixed.DesignTime.cs new file mode 100644 index 0000000000..64ff193777 --- /dev/null +++ b/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/CS/Output/BasicTagHelpers.Prefixed.DesignTime.cs @@ -0,0 +1,55 @@ +namespace TestOutput +{ + using Microsoft.AspNet.Razor.Runtime.TagHelpers; + using System; + using System.Threading.Tasks; + + public class BasicTagHelpers.Prefixed + { + private static object @__o; + private void @__RazorDesignTimeHelpers__() + { + #pragma warning disable 219 + string __tagHelperDirectiveSyntaxHelper = null; + __tagHelperDirectiveSyntaxHelper = +#line 1 "BasicTagHelpers.Prefixed.cshtml" + "THS" + +#line default +#line hidden + ; + __tagHelperDirectiveSyntaxHelper = +#line 2 "BasicTagHelpers.Prefixed.cshtml" + "something, nice" + +#line default +#line hidden + ; + #pragma warning restore 219 + } + #line hidden + private PTagHelper __PTagHelper = null; + private InputTagHelper __InputTagHelper = null; + private InputTagHelper2 __InputTagHelper2 = null; + #line hidden + public BasicTagHelpers.Prefixed() + { + } + + #pragma warning disable 1998 + public override async Task ExecuteAsync() + { + __InputTagHelper = CreateTagHelper(); + __InputTagHelper.Type = "checkbox"; + __InputTagHelper2 = CreateTagHelper(); + __InputTagHelper2.Type = __InputTagHelper.Type; +#line 8 "BasicTagHelpers.Prefixed.cshtml" + __InputTagHelper2.Checked = true; + +#line default +#line hidden + __PTagHelper = CreateTagHelper(); + } + #pragma warning restore 1998 + } +} diff --git a/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/CS/Output/BasicTagHelpers.Prefixed.cs b/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/CS/Output/BasicTagHelpers.Prefixed.cs new file mode 100644 index 0000000000..f9bf2d196f --- /dev/null +++ b/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/CS/Output/BasicTagHelpers.Prefixed.cs @@ -0,0 +1,97 @@ +#pragma checksum "BasicTagHelpers.Prefixed.cshtml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "27630097585fd58e68cb0ac5b772154eff02a52a" +namespace TestOutput +{ + using Microsoft.AspNet.Razor.Runtime.TagHelpers; + using System; + using System.Threading.Tasks; + + public class BasicTagHelpers.Prefixed + { + #line hidden + #pragma warning disable 0414 + private System.IO.TextWriter __tagHelperStringValueBuffer = null; + #pragma warning restore 0414 + private TagHelperExecutionContext __tagHelperExecutionContext = null; + private TagHelperRunner __tagHelperRunner = new TagHelperRunner(); + private TagHelperScopeManager __tagHelperScopeManager = new TagHelperScopeManager(); + private PTagHelper __PTagHelper = null; + private InputTagHelper __InputTagHelper = null; + private InputTagHelper2 __InputTagHelper2 = null; + #line hidden + public BasicTagHelpers.Prefixed() + { + } + + #pragma warning disable 1998 + public override async Task ExecuteAsync() + { + Instrumentation.BeginContext(57, 52, true); + WriteLiteral("\r\n\r\n "); + Instrumentation.EndContext(); + __tagHelperExecutionContext = __tagHelperScopeManager.Begin("p", false, "test", async() => { + WriteLiteral("\r\n

\r\n \r\n "); + __tagHelperExecutionContext = __tagHelperScopeManager.Begin("input", true, "test", async() => { + } + , StartWritingScope, EndWritingScope); + __InputTagHelper = CreateTagHelper(); + __tagHelperExecutionContext.Add(__InputTagHelper); + __InputTagHelper.Type = "checkbox"; + __tagHelperExecutionContext.AddTagHelperAttribute("type", __InputTagHelper.Type); + __InputTagHelper2 = CreateTagHelper(); + __tagHelperExecutionContext.Add(__InputTagHelper2); + __InputTagHelper2.Type = __InputTagHelper.Type; +#line 8 "BasicTagHelpers.Prefixed.cshtml" + __InputTagHelper2.Checked = true; + +#line default +#line hidden + __tagHelperExecutionContext.AddTagHelperAttribute("checked", __InputTagHelper2.Checked); + __tagHelperExecutionContext.Output = __tagHelperRunner.RunAsync(__tagHelperExecutionContext).Result; + WriteLiteral(__tagHelperExecutionContext.Output.GenerateStartTag()); + WriteLiteral(__tagHelperExecutionContext.Output.GeneratePreContent()); + if (__tagHelperExecutionContext.Output.ContentSet) + { + WriteLiteral(__tagHelperExecutionContext.Output.GenerateContent()); + } + else if (__tagHelperExecutionContext.ChildContentRetrieved) + { + WriteLiteral(__tagHelperExecutionContext.GetChildContentAsync().Result); + } + else + { + __tagHelperExecutionContext.ExecuteChildContentAsync().Wait(); + } + WriteLiteral(__tagHelperExecutionContext.Output.GeneratePostContent()); + WriteLiteral(__tagHelperExecutionContext.Output.GenerateEndTag()); + __tagHelperExecutionContext = __tagHelperScopeManager.End(); + WriteLiteral("\r\n "); + } + , StartWritingScope, EndWritingScope); + __PTagHelper = CreateTagHelper(); + __tagHelperExecutionContext.Add(__PTagHelper); + __tagHelperExecutionContext.AddHtmlAttribute("class", "Hello World"); + __tagHelperExecutionContext.Output = __tagHelperRunner.RunAsync(__tagHelperExecutionContext).Result; + WriteLiteral(__tagHelperExecutionContext.Output.GenerateStartTag()); + WriteLiteral(__tagHelperExecutionContext.Output.GeneratePreContent()); + if (__tagHelperExecutionContext.Output.ContentSet) + { + WriteLiteral(__tagHelperExecutionContext.Output.GenerateContent()); + } + else if (__tagHelperExecutionContext.ChildContentRetrieved) + { + WriteLiteral(__tagHelperExecutionContext.GetChildContentAsync().Result); + } + else + { + __tagHelperExecutionContext.ExecuteChildContentAsync().Wait(); + } + WriteLiteral(__tagHelperExecutionContext.Output.GeneratePostContent()); + WriteLiteral(__tagHelperExecutionContext.Output.GenerateEndTag()); + __tagHelperExecutionContext = __tagHelperScopeManager.End(); + Instrumentation.BeginContext(249, 11, true); + WriteLiteral("\r\n
"); + Instrumentation.EndContext(); + } + #pragma warning restore 1998 + } +} diff --git a/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/CS/Source/BasicTagHelpers.Prefixed.cshtml b/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/CS/Source/BasicTagHelpers.Prefixed.cshtml new file mode 100644 index 0000000000..77fd0756c2 --- /dev/null +++ b/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/CS/Source/BasicTagHelpers.Prefixed.cshtml @@ -0,0 +1,10 @@ +@tagHelperPrefix "THS" +@addTagHelper "something, nice" + + + +

+ + +
+
\ No newline at end of file