Add tests to validate @removetaghelper functionality.

- Added utility methods to construct valid SyntaxTreeNodes that represent the @removetaghelper directive.
- Added parse level unit tests to validate the @removetaghelper generates an accurate SyntaxTreeNode.
- Added parse level unit tests to validate the @removetaghelper throws with bad formats.
- Added TagHelperRegistration unit tests to validate the AddOrRemoveTagHelperCodeGenerators are understood and affect the descriptors found.
- Added Designtime mapping tests to validate correct source mappings are made to ensure proper coloring and lack-of C# intellisense.
- Added end-to-end tests to validate @removetaghelper can essentially disable TagHelpers on a page.

#112
This commit is contained in:
N. Taylor Mullen 2014-10-21 12:14:31 -07:00
parent 30221f7ce0
commit 7ab25918e0
10 changed files with 427 additions and 1 deletions

View File

@ -20,6 +20,7 @@ namespace Microsoft.AspNet.Razor.Parser
internal static ISet<string> DefaultKeywords = new HashSet<string>()
{
SyntaxConstants.CSharp.AddTagHelperKeyword,
SyntaxConstants.CSharp.RemoveTagHelperKeyword,
"if",
"do",
"try",

View File

@ -302,7 +302,14 @@ namespace Microsoft.AspNet.Razor.Test.Framework
public SpanConstructor AsAddTagHelper(string lookupText)
{
return _self.With(new AddTagHelperCodeGenerator(lookupText));
return _self.With(
new AddOrRemoveTagHelperCodeGenerator(removeTagHelperDescriptors: false, lookupText: lookupText));
}
public SpanConstructor AsRemoveTagHelper(string lookupText)
{
return _self.With(
new AddOrRemoveTagHelperCodeGenerator(removeTagHelperDescriptors: true, lookupText: lookupText));
}
public SpanConstructor As(ISpanCodeGenerator codeGenerator)

View File

@ -11,6 +11,23 @@ namespace Microsoft.AspNet.Razor.Test.Generator
{
public class CSharpTagHelperRenderingTest : TagHelperTestBase
{
[Fact]
public void CSharpCodeGenerator_CorrectlyGeneratesMappings_ForRemoveTagHelperDirective()
{
// Act & Assert
RunTagHelperTest("RemoveTagHelperDirective",
designTimeMode: true,
expectedDesignTimePragmas: new List<LineMapping>()
{
BuildLineMapping(documentAbsoluteIndex: 17,
documentLineIndex: 0,
generatedAbsoluteIndex: 442,
generatedLineIndex: 14,
characterOffsetIndex: 17,
contentLength: 11)
});
}
[Fact]
public void CSharpCodeGenerator_CorrectlyGeneratesMappings_ForAddTagHelperDirective()
{
@ -65,6 +82,7 @@ namespace Microsoft.AspNet.Razor.Test.Generator
[Theory]
[InlineData("SingleTagHelper")]
[InlineData("BasicTagHelpers")]
[InlineData("BasicTagHelpers.RemoveTagHelper")]
[InlineData("ComplexTagHelpers")]
public void TagHelpers_GenerateExpectedOutput(string testType)
{

View File

@ -36,6 +36,30 @@ namespace Microsoft.AspNet.Razor
Assert.Equal(addTagHelperChunk.LookupText, "some text");
}
[Fact]
public void AddRemoveTagHelperChunk_AddsChunkToTopLevelCodeTree()
{
// Arrange
var spanFactory = SpanFactory.CreateCsHtml();
var builder = new CodeTreeBuilder();
var block = new ExpressionBlock();
var removeTagHelperDirective = spanFactory.MetaCode(SyntaxConstants.CSharp.RemoveTagHelperKeyword + " ");
// Act
builder.StartChunkBlock<ExpressionBlockChunk>(block);
builder.AddRemoveTagHelperChunk("some text", removeTagHelperDirective);
builder.EndChunkBlock();
// Assert
Assert.Equal(2, builder.CodeTree.Chunks.Count);
var chunkBlock = Assert.IsType<ExpressionBlockChunk>(builder.CodeTree.Chunks.First());
Assert.Empty(chunkBlock.Children);
var removeTagHelperChunk = Assert.IsType<RemoveTagHelperChunk>(builder.CodeTree.Chunks.Last());
Assert.Equal(removeTagHelperChunk.LookupText, "some text");
}
[Fact]
public void AddLiteralChunk_AddsChunkToCodeTree()
{

View File

@ -12,6 +12,94 @@ namespace Microsoft.AspNet.Razor.Test.Parser.CSharp
{
public class CSharpDirectivesTest : CsHtmlCodeParserTestBase
{
[Fact]
public void RemoveTagHelperDirective_Succeeds()
{
ParseBlockTest("@removetaghelper \"Foo\"",
new DirectiveBlock(
Factory.CodeTransition(),
Factory.MetaCode(SyntaxConstants.CSharp.RemoveTagHelperKeyword + " ")
.Accepts(AcceptedCharacters.None),
Factory.Code("\"Foo\"").AsRemoveTagHelper("Foo")));
}
[Fact]
public void RemoveTagHelperDirective_SupportsSpaces()
{
ParseBlockTest("@removetaghelper \" Foo, Bar \" ",
new DirectiveBlock(
Factory.CodeTransition(),
Factory.MetaCode(SyntaxConstants.CSharp.RemoveTagHelperKeyword + " ")
.Accepts(AcceptedCharacters.None),
Factory.Code("\" Foo, Bar \" ").AsRemoveTagHelper(" Foo, Bar ")));
}
[Fact]
public void RemoveTagHelperDirective_RequiresValue()
{
ParseBlockTest("@removetaghelper ",
new DirectiveBlock(
Factory.CodeTransition(),
Factory.MetaCode(SyntaxConstants.CSharp.RemoveTagHelperKeyword + " ")
.Accepts(AcceptedCharacters.None),
Factory.EmptyCSharp().AsRemoveTagHelper(string.Empty)),
new RazorError(
RazorResources.FormatParseError_DirectiveMustHaveValue(
SyntaxConstants.CSharp.RemoveTagHelperKeyword),
absoluteIndex: 17, lineIndex: 0, columnIndex: 17));
}
[Fact]
public void RemoveTagHelperDirective_StartQuoteRequiresDoubleQuotesAroundValue()
{
ParseBlockTest("@removetaghelper \"Foo",
new DirectiveBlock(
Factory.CodeTransition(),
Factory.MetaCode(SyntaxConstants.CSharp.RemoveTagHelperKeyword + " ")
.Accepts(AcceptedCharacters.None),
Factory.Code("\"Foo").AsRemoveTagHelper("Foo")),
new RazorError(
RazorResources.ParseError_Unterminated_String_Literal,
absoluteIndex: 17, lineIndex: 0, columnIndex: 17),
new RazorError(
RazorResources.FormatParseError_DirectiveMustBeSurroundedByQuotes(
SyntaxConstants.CSharp.RemoveTagHelperKeyword),
absoluteIndex: 17, lineIndex: 0, columnIndex: 17));
}
[Fact]
public void RemoveTagHelperDirective_EndQuoteRequiresDoubleQuotesAroundValue()
{
ParseBlockTest("@removetaghelper Foo\"",
new DirectiveBlock(
Factory.CodeTransition(),
Factory.MetaCode(SyntaxConstants.CSharp.RemoveTagHelperKeyword + " ")
.Accepts(AcceptedCharacters.None),
Factory.Code("Foo\"").AsRemoveTagHelper("Foo")),
new RazorError(
RazorResources.ParseError_Unterminated_String_Literal,
absoluteIndex: 20, lineIndex: 0, columnIndex: 20),
new RazorError(
RazorResources.FormatParseError_DirectiveMustBeSurroundedByQuotes(
SyntaxConstants.CSharp.RemoveTagHelperKeyword),
absoluteIndex: 17, lineIndex: 0, columnIndex: 17));
}
[Fact]
public void RemoveTagHelperDirective_RequiresDoubleQuotesAroundValue()
{
ParseBlockTest("@removetaghelper Foo",
new DirectiveBlock(
Factory.CodeTransition(),
Factory.MetaCode(SyntaxConstants.CSharp.RemoveTagHelperKeyword + " ")
.Accepts(AcceptedCharacters.None),
Factory.Code("Foo").AsRemoveTagHelper("Foo")),
new RazorError(
RazorResources.FormatParseError_DirectiveMustBeSurroundedByQuotes(
SyntaxConstants.CSharp.RemoveTagHelperKeyword),
absoluteIndex: 17, lineIndex: 0, columnIndex: 17));
}
[Fact]
public void AddTagHelperDirective_Succeeds()
{

View File

@ -0,0 +1,219 @@
// 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 Microsoft.AspNet.Razor.Parser;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
using Microsoft.AspNet.Razor.Parser.TagHelpers;
using Microsoft.AspNet.Razor.Test.Framework;
#if !ASPNETCORE50
using Moq;
#endif
using Xunit;
namespace Microsoft.AspNet.Razor.TagHelpers
{
public class AddOrRemoveTagHelperSpanVisitorTest
{
private static readonly SpanFactory Factory = SpanFactory.CreateCsHtml();
private static TagHelperDescriptor PTagHelperDescriptor
{
get
{
return new TagHelperDescriptor("p", "PTagHelper", ContentBehavior.None);
}
}
private static TagHelperDescriptor DivTagHelperDescriptor
{
get
{
return new TagHelperDescriptor("div", "DivTagHelper", ContentBehavior.None);
}
}
#if !ASPNETCORE50
[Fact]
public void GetDescriptors_InvokesResolveForEachLookup()
{
// Arrange
var resolver = new Mock<ITagHelperDescriptorResolver>();
resolver.Setup(mock => mock.Resolve(It.IsAny<string>())).Returns(Enumerable.Empty<TagHelperDescriptor>());
var addOrRemoveTagHelperSpanVisitor = new AddOrRemoveTagHelperSpanVisitor(resolver.Object);
var document = new MarkupBlock(
Factory.Code("\"one\"").AsAddTagHelper("one"),
Factory.Code("\"two\"").AsRemoveTagHelper("two"),
Factory.Code("\"three\"").AsRemoveTagHelper("three"));
// Act
var descriptors = addOrRemoveTagHelperSpanVisitor.GetDescriptors(document);
// Assert
Assert.Empty(descriptors);
resolver.Verify(mock => mock.Resolve(It.IsAny<string>()), Times.Exactly(3));
}
#endif
[Fact]
public void GetDescriptors_LocatesNestedRemoveTagHelperCodeGenerator()
{
// Arrange
var resolver = new TestTagHelperDescriptorResolver(
new Dictionary<string, IEnumerable<TagHelperDescriptor>>
{
{ "something", new[] { PTagHelperDescriptor } }
});
var addOrRemoveTagHelperSpanVisitor = new AddOrRemoveTagHelperSpanVisitor(resolver);
var document = new MarkupBlock(
new DirectiveBlock(
Factory.CodeTransition(),
Factory.MetaCode(SyntaxConstants.CSharp.RemoveTagHelperKeyword + " ")
.Accepts(AcceptedCharacters.None),
Factory.Code("\"something\"").AsRemoveTagHelper("something"))
);
// Act
var descriptors = addOrRemoveTagHelperSpanVisitor.GetDescriptors(document);
// Assert
Assert.Empty(descriptors);
var lookup = Assert.Single(resolver.Lookups);
Assert.Equal("something", lookup);
}
[Fact]
public void GetDescriptors_RemovesSpecifiedTagHelper()
{
// Arrange
var resolver = new TestTagHelperDescriptorResolver(
new Dictionary<string, IEnumerable<TagHelperDescriptor>>
{
{ "twoTagHelpers", new[] { PTagHelperDescriptor, DivTagHelperDescriptor } },
{ "singleTagHelper", new [] { PTagHelperDescriptor } }
});
var addOrRemoveTagHelperSpanVisitor = new AddOrRemoveTagHelperSpanVisitor(resolver);
var document = new MarkupBlock(
Factory.Code("\"twoTagHelpers\"").AsAddTagHelper("twoTagHelpers"),
Factory.Code("\"singleTagHelper\"").AsRemoveTagHelper("singleTagHelper"));
// Act
var descriptors = addOrRemoveTagHelperSpanVisitor.GetDescriptors(document);
// Assert
var descriptor = Assert.Single(descriptors);
Assert.Equal(DivTagHelperDescriptor, descriptor, TagHelperDescriptorComparer.Default);
Assert.Equal(2, resolver.Lookups.Count);
Assert.Contains("twoTagHelpers", resolver.Lookups);
Assert.Contains("singleTagHelper", resolver.Lookups);
}
[Fact]
public void GetDescriptors_RemovesAddedTagHelpers()
{
// Arrange
var resolver = new TestTagHelperDescriptorResolver(
new Dictionary<string, IEnumerable<TagHelperDescriptor>>
{
{ "twoTagHelpers", new[] { PTagHelperDescriptor, DivTagHelperDescriptor } }
});
var addOrRemoveTagHelperSpanVisitor = new AddOrRemoveTagHelperSpanVisitor(resolver);
var document = new MarkupBlock(
Factory.Code("\"twoTagHelpers\"").AsAddTagHelper("twoTagHelpers"),
Factory.Code("\"twoTagHelpers\"").AsRemoveTagHelper("twoTagHelpers"));
// Act
var descriptors = addOrRemoveTagHelperSpanVisitor.GetDescriptors(document);
// Assert
Assert.Empty(descriptors);
Assert.Equal(Enumerable.Repeat("twoTagHelpers", 2), resolver.Lookups);
}
[Fact]
public void GetDescriptors_RemoveTagHelper_OrderMatters()
{
// Arrange
var expectedDescriptors = new[] { PTagHelperDescriptor, DivTagHelperDescriptor };
var resolver = new TestTagHelperDescriptorResolver(
new Dictionary<string, IEnumerable<TagHelperDescriptor>>
{
{ "twoTagHelpers", expectedDescriptors }
});
var addOrRemoveTagHelperSpanVisitor = new AddOrRemoveTagHelperSpanVisitor(resolver);
var document = new MarkupBlock(
Factory.Code("\"twoTagHelpers\"").AsRemoveTagHelper("twoTagHelpers"),
Factory.Code("\"twoTagHelpers\"").AsAddTagHelper("twoTagHelpers"));
// Act
var descriptors = addOrRemoveTagHelperSpanVisitor.GetDescriptors(document);
// Assert
Assert.Equal(expectedDescriptors, descriptors, TagHelperDescriptorComparer.Default);
Assert.Equal(Enumerable.Repeat("twoTagHelpers", 2), resolver.Lookups);
}
[Fact]
public void GetDescriptors_RemoveTagHelperInDocument_ThrowsIfNullResolver()
{
// Arrange
var addOrRemoveTagHelperSpanVisitor = new AddOrRemoveTagHelperSpanVisitor(descriptorResolver: null);
var document = new MarkupBlock(
Factory.Code("\"something\"").AsRemoveTagHelper("something"));
var expectedMessage = "Cannot use directive 'removetaghelper' when a Microsoft.AspNet.Razor.TagHelpers." +
"ITagHelperDescriptorResolver has not been provided to the Microsoft.AspNet.Razor." +
"Parser.RazorParser.";
// Act & Assert
var ex = Assert.Throws<InvalidOperationException>(() =>
{
addOrRemoveTagHelperSpanVisitor.GetDescriptors(document);
});
Assert.Equal(expectedMessage, ex.Message);
}
[Fact]
public void GetDescriptors_RemoveTagHelperNotInDocument_DoesNotThrow()
{
// Arrange
var addOrRemoveTagHelperSpanVisitor = new AddOrRemoveTagHelperSpanVisitor(descriptorResolver: null);
var document = new MarkupBlock(Factory.Markup("Hello World"));
// Act & Assert
Assert.DoesNotThrow(() => addOrRemoveTagHelperSpanVisitor.GetDescriptors(document));
}
// TODO: Add @addtaghelper directive unit tests. Tracked by https://github.com/aspnet/Razor/issues/202.
private class TestTagHelperDescriptorResolver : ITagHelperDescriptorResolver
{
private readonly IReadOnlyDictionary<string, IEnumerable<TagHelperDescriptor>> _lookupTable;
public TestTagHelperDescriptorResolver(
IReadOnlyDictionary<string, IEnumerable<TagHelperDescriptor>> lookupTable)
{
_lookupTable = lookupTable;
Lookups = new List<string>();
}
public List<string> Lookups { get; private set; }
public IEnumerable<TagHelperDescriptor> Resolve(string lookupText)
{
Lookups.Add(lookupText);
IEnumerable<TagHelperDescriptor> descriptors;
if (_lookupTable.TryGetValue(lookupText, out descriptors))
{
return descriptors;
}
return Enumerable.Empty<TagHelperDescriptor>();
}
}
}
}

View File

@ -0,0 +1,25 @@
#pragma checksum "BasicTagHelpers.RemoveTagHelper.cshtml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "631e4af360bd37f540b637596d5f059c7abc0ac2"
namespace TestOutput
{
using System;
using System.Threading.Tasks;
public class BasicTagHelpers.RemoveTagHelper
{
#line hidden
public BasicTagHelpers.RemoveTagHelper()
{
}
#pragma warning disable 1998
public override async Task ExecuteAsync()
{
Instrumentation.BeginContext(60, 187, true);
WriteLiteral("\r\n<div class=\"randomNonTagHelperAttribute\">\r\n <p class=\"Hello World\">\r\n " +
" <p></p>\r\n <input type=\"text\" />\r\n <input type=\"checkbox\" checked=" +
"\"true\"/>\r\n </p>\r\n</div>");
Instrumentation.EndContext();
}
#pragma warning restore 1998
}
}

View File

@ -0,0 +1,33 @@
namespace TestOutput
{
using System;
using System.Threading.Tasks;
public class RemoveTagHelperDirective
{
private static object @__o;
private void @__RazorDesignTimeHelpers__()
{
#pragma warning disable 219
string __tagHelperDirectiveSyntaxHelper = null;
__tagHelperDirectiveSyntaxHelper =
#line 1 "RemoveTagHelperDirective.cshtml"
"something"
#line default
#line hidden
;
#pragma warning restore 219
}
#line hidden
public RemoveTagHelperDirective()
{
}
#pragma warning disable 1998
public override async Task ExecuteAsync()
{
}
#pragma warning restore 1998
}
}

View File

@ -0,0 +1,10 @@
@addtaghelper "something"
@removetaghelper "doesntmatter"
<div class="randomNonTagHelperAttribute">
<p class="Hello World">
<p></p>
<input type="text" />
<input type="checkbox" checked="true"/>
</p>
</div>

View File

@ -0,0 +1 @@
@removetaghelper "something"