diff --git a/test/Microsoft.AspNet.Razor.Test/TagHelpers/TagHelperParseTreeRewriterTest.cs b/test/Microsoft.AspNet.Razor.Test/TagHelpers/TagHelperParseTreeRewriterTest.cs new file mode 100644 index 0000000000..4d60856852 --- /dev/null +++ b/test/Microsoft.AspNet.Razor.Test/TagHelpers/TagHelperParseTreeRewriterTest.cs @@ -0,0 +1,982 @@ +// 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 Microsoft.AspNet.Razor.Generator; +using Microsoft.AspNet.Razor.Parser; +using Microsoft.AspNet.Razor.Parser.SyntaxTree; +using Microsoft.AspNet.Razor.Parser.TagHelpers.Internal; +using Microsoft.AspNet.Razor.TagHelpers; +using Microsoft.AspNet.Razor.Test.Framework; +using Microsoft.AspNet.Razor.Text; +using Microsoft.AspNet.Razor.Tokenizer; +using Xunit; + +namespace Microsoft.AspNet.Razor.Test.TagHelpers +{ + public class TagHelperParseTreeRewriterTest : CsHtmlMarkupParserTestBase + { + public static IEnumerable TextTagsBlockData + { + get + { + var factory = CreateDefaultSpanFactory(); + + // Should re-write text tags that aren't in C# blocks + yield return new object[] { + "Hello World", + new MarkupBlock( + new MarkupTagHelperBlock("text", + factory.Markup("Hello World"))) + }; + yield return new object[] { + "@{Hello World}", + new MarkupBlock( + factory.EmptyHtml(), + new StatementBlock( + factory.CodeTransition(), + factory.MetaCode("{").Accepts(AcceptedCharacters.None), + new MarkupBlock( + new MarkupTagBlock( + factory.MarkupTransition("")), + factory.Markup("Hello World").Accepts(AcceptedCharacters.None), + new MarkupTagBlock( + factory.MarkupTransition(""))), + factory.EmptyCSharp().AsStatement(), + factory.MetaCode("}").Accepts(AcceptedCharacters.None)), + factory.EmptyHtml()) + }; + yield return new object[] { + "@{

Hello World

}", + new MarkupBlock( + factory.EmptyHtml(), + new StatementBlock( + factory.CodeTransition(), + factory.MetaCode("{").Accepts(AcceptedCharacters.None), + new MarkupBlock( + new MarkupTagBlock( + factory.MarkupTransition("")), + new MarkupTagHelperBlock("p", + factory.Markup("Hello World")), + new MarkupTagBlock( + factory.MarkupTransition(""))), + factory.EmptyCSharp().AsStatement(), + factory.MetaCode("}").Accepts(AcceptedCharacters.None)), + factory.EmptyHtml()) + }; + yield return new object[] { + "@{

Hello World

}", + new MarkupBlock( + factory.EmptyHtml(), + new StatementBlock( + factory.CodeTransition(), + factory.MetaCode("{").Accepts(AcceptedCharacters.None), + new MarkupBlock( + new MarkupTagHelperBlock("p", + new MarkupTagHelperBlock("text", + factory.Markup("Hello World")))), + factory.EmptyCSharp().AsStatement(), + factory.MetaCode("}").Accepts(AcceptedCharacters.None)), + factory.EmptyHtml()) + }; + } + } + + [Theory] + [MemberData(nameof(TextTagsBlockData))] + public void TagHelperParseTreeRewriter_DoesntRewriteTextTagTransitionTagHelpers( + string documentContent, + MarkupBlock expectedOutput) + { + RunParseTreeRewriterTest(documentContent, expectedOutput, "p", "text"); + } + + public static IEnumerable SpecialTagsBlockData + { + get + { + var factory = CreateDefaultSpanFactory(); + + yield return new object[] { + "", + new MarkupBlock( + new MarkupTagBlock( + factory.Markup("")), + factory.Markup(""), + new MarkupTagBlock( + factory.Markup(""))) + }; + yield return new object[] { + "", + new MarkupBlock( + new MarkupTagBlock( + factory.Markup("")), + factory.Markup(""), + new MarkupTagBlock( + factory.Markup(""))) + }; + yield return new object[] { + "", + new MarkupBlock( + new MarkupTagBlock( + factory.Markup("")), + factory.Markup(""), + new MarkupTagBlock( + factory.Markup(""))) + }; + yield return new object[] { + "", + new MarkupBlock( + new MarkupTagBlock( + factory.Markup("")), + factory.Markup(""), + new MarkupTagBlock( + factory.Markup(""))) + }; + yield return new object[] { + "", + new MarkupBlock( + new MarkupTagBlock( + factory.Markup("")), + factory.Markup(""), + new MarkupTagBlock( + factory.Markup(""))) + }; + yield return new object[] { + "", + new MarkupBlock( + new MarkupTagBlock( + factory.Markup("")), + factory.Markup(""), + new MarkupTagBlock( + factory.Markup(""))) + }; + yield return new object[] { + "", + new MarkupBlock( + new MarkupTagBlock( + factory.Markup("")), + factory.Markup(""), + new MarkupTagBlock( + factory.Markup(""))) + }; + yield return new object[] { + "", + new MarkupBlock( + new MarkupTagBlock( + factory.Markup("")), + factory.Markup(""), + new MarkupTagBlock( + factory.Markup(""))) + }; + } + } + + [Theory] + [MemberData(nameof(SpecialTagsBlockData))] + public void TagHelperParseTreeRewriter_DoesntRewriteSpecialTagTagHelpers( + string documentContent, + MarkupBlock expectedOutput) + { + RunParseTreeRewriterTest(documentContent, expectedOutput, "!--", "?xml", "![CDATA[", "!DOCTYPE"); + } + + public static IEnumerable OddlySpacedBlockData + { + get + { + var factory = CreateDefaultSpanFactory(); + + yield return new object[] { + "

", + new MarkupBlock( + new MarkupTagHelperBlock("p", + new Dictionary + { + { "class", new MarkupBlock(factory.Markup(" foo")) }, + { "style", + new MarkupBlock( + factory.Markup(" color"), + factory.Markup(" :"), + factory.Markup(" red"), + factory.Markup(" ;"), + factory.Markup(" ")) + } + })) + }; + yield return new object[] { + "

Hello World

", + new MarkupBlock( + new MarkupTagHelperBlock("p", + new Dictionary + { + { "class", new MarkupBlock(factory.Markup(" foo")) }, + { "style", + new MarkupBlock( + factory.Markup(" color"), + factory.Markup(" :"), + factory.Markup(" red"), + factory.Markup(" ;"), + factory.Markup(" ")) + } + }, + factory.Markup("Hello World"))) + }; + yield return new object[] { + "

Hello

World

", + new MarkupBlock( + new MarkupTagHelperBlock("p", + new Dictionary + { + { "class", new MarkupBlock(factory.Markup(" foo"), factory.Markup(" ")) } + }, + factory.Markup("Hello")), + factory.Markup(" "), + new MarkupTagHelperBlock("p", + new Dictionary + { + { "style", new MarkupBlock(factory.Markup(" color:red;"), factory.Markup(" ")) } + }, + factory.Markup("World"))) + }; + } + } + + [Theory] + [MemberData(nameof(OddlySpacedBlockData))] + public void TagHelperParseTreeRewriter_RewritesOddlySpacedTagHelperTagBlocks( + string documentContent, + MarkupBlock expectedOutput) + { + RunParseTreeRewriterTest(documentContent, expectedOutput, "p"); + } + + public static IEnumerable ComplexAttributeTagHelperBlockData + { + get + { + var factory = CreateDefaultSpanFactory(); + var blockFactory = new BlockFactory(factory); + var dateTimeNowString = "@DateTime.Now"; + var dateTimeNow = new MarkupBlock( + new ExpressionBlock( + factory.CodeTransition(), + factory.Code("DateTime.Now") + .AsImplicitExpression(CSharpCodeParser.DefaultKeywords) + .Accepts(AcceptedCharacters.NonWhiteSpace))); + var doWhileString = "@do { var foo = bar; Foo foo++; } while (foo);"; + var doWhile = new MarkupBlock( + new StatementBlock( + factory.CodeTransition(), + factory.Code("do { var foo = bar;").AsStatement(), + new MarkupBlock( + factory.Markup(" "), + new MarkupTagBlock( + factory.MarkupTransition("")), + factory.Markup("Foo").Accepts(AcceptedCharacters.None), + new MarkupTagBlock( + factory.MarkupTransition("")), + factory.Markup(" ").Accepts(AcceptedCharacters.None)), + factory.Code("foo++; } while (foo);").AsStatement().Accepts(AcceptedCharacters.None))); + + var currentFormattedString = "

"; + yield return new object[] { + string.Format(currentFormattedString, dateTimeNowString), + new MarkupBlock( + new MarkupTagHelperBlock("p", + new Dictionary + { + { "class", new MarkupBlock(dateTimeNow) }, + { "style", new MarkupBlock(dateTimeNow) } + })) + }; + yield return new object[] { + string.Format(currentFormattedString, doWhileString), + new MarkupBlock( + new MarkupTagHelperBlock("p", + new Dictionary + { + { "class", new MarkupBlock(doWhile) }, + { "style", new MarkupBlock(doWhile) } + })) + }; + + currentFormattedString = "

Hello World

"; + yield return new object[] { + string.Format(currentFormattedString, dateTimeNowString), + new MarkupBlock( + new MarkupTagHelperBlock("p", + new Dictionary + { + { "class", new MarkupBlock(dateTimeNow) }, + { "style", new MarkupBlock(dateTimeNow) } + }, + factory.Markup("Hello World"))) + }; + yield return new object[] { + string.Format(currentFormattedString, doWhileString), + new MarkupBlock( + new MarkupTagHelperBlock("p", + new Dictionary + { + { "class", new MarkupBlock(doWhile) }, + { "style", new MarkupBlock(doWhile) } + }, + factory.Markup("Hello World"))) + }; + + currentFormattedString = "

Hello

World

"; + yield return new object[] { + string.Format(currentFormattedString, dateTimeNowString), + new MarkupBlock( + new MarkupTagHelperBlock("p", + new Dictionary + { + { "class", new MarkupBlock(dateTimeNow) } + }, + factory.Markup("Hello")), + factory.Markup(" "), + new MarkupTagHelperBlock("p", + new Dictionary + { + { "style", new MarkupBlock(dateTimeNow) } + }, + factory.Markup("World"))) + }; + yield return new object[] { + string.Format(currentFormattedString, doWhileString), + new MarkupBlock( + new MarkupTagHelperBlock("p", + new Dictionary + { + { "class", new MarkupBlock(doWhile) } + }, + factory.Markup("Hello")), + factory.Markup(" "), + new MarkupTagHelperBlock("p", + new Dictionary + { + { "style", new MarkupBlock(doWhile) } + }, + factory.Markup("World"))) + }; + + currentFormattedString = + "

Hello World inside of strong tag

"; + yield return new object[] { + string.Format(currentFormattedString, dateTimeNowString), + new MarkupBlock( + new MarkupTagHelperBlock("p", + new Dictionary + { + { "class", new MarkupBlock(dateTimeNow) }, + { "style", new MarkupBlock(dateTimeNow) } + }, + factory.Markup("Hello World "), + new MarkupTagBlock( + factory.Markup("(" class=\"", 66, 0, 66), + suffix: new LocationTagged("\"", 87, 0, 87)), + factory.Markup(" class=\"").With(SpanCodeGenerator.Null), + new MarkupBlock(new DynamicAttributeBlockCodeGenerator(new LocationTagged(string.Empty, 74, 0, 74), 74, 0, 74), + new ExpressionBlock( + factory.CodeTransition(), + factory.Code("DateTime.Now") + .AsImplicitExpression(CSharpCodeParser.DefaultKeywords) + .Accepts(AcceptedCharacters.NonWhiteSpace))), + factory.Markup("\"").With(SpanCodeGenerator.Null)), + factory.Markup(">")), + factory.Markup("inside of strong tag"), + blockFactory.MarkupTagBlock(""))) + }; + } + } + + [Theory] + [MemberData(nameof(ComplexAttributeTagHelperBlockData))] + public void TagHelperParseTreeRewriter_RewritesComplexAttributeTagHelperTagBlocks( + string documentContent, + MarkupBlock expectedOutput) + { + RunParseTreeRewriterTest(documentContent, expectedOutput, "p"); + } + + public static IEnumerable ComplexTagHelperBlockData + { + get + { + var factory = CreateDefaultSpanFactory(); + var blockFactory = new BlockFactory(factory); + var dateTimeNowString = "@DateTime.Now"; + var dateTimeNow = new ExpressionBlock( + factory.CodeTransition(), + factory.Code("DateTime.Now") + .AsImplicitExpression(CSharpCodeParser.DefaultKeywords) + .Accepts(AcceptedCharacters.NonWhiteSpace)); + var doWhileString = "@do { var foo = bar;

Foo

foo++; } while (foo);"; + var doWhile = new StatementBlock( + factory.CodeTransition(), + factory.Code("do { var foo = bar;").AsStatement(), + new MarkupBlock( + factory.Markup(" "), + new MarkupTagHelperBlock("p", + factory.Markup("Foo")), + factory.Markup(" ").Accepts(AcceptedCharacters.None)), + factory.Code("foo++; } while (foo);").AsStatement().Accepts(AcceptedCharacters.None)); + + var currentFormattedString = "

{0}

"; + yield return new object[] { + string.Format(currentFormattedString, dateTimeNowString), + new MarkupBlock( + new MarkupTagHelperBlock("p", dateTimeNow)) + }; + yield return new object[] { + string.Format(currentFormattedString, doWhileString), + new MarkupBlock( + new MarkupTagHelperBlock("p", doWhile)) + }; + + currentFormattedString = "

Hello World {0}

"; + yield return new object[] { + string.Format(currentFormattedString, dateTimeNowString), + new MarkupBlock( + new MarkupTagHelperBlock("p", + factory.Markup("Hello World "), + dateTimeNow)) + }; + yield return new object[] { + string.Format(currentFormattedString, doWhileString), + new MarkupBlock( + new MarkupTagHelperBlock("p", + factory.Markup("Hello World "), + doWhile)) + }; + + currentFormattedString = "

{0}

{0}

"; + yield return new object[] { + string.Format(currentFormattedString, dateTimeNowString), + new MarkupBlock( + new MarkupTagHelperBlock("p", dateTimeNow), + factory.Markup(" "), + new MarkupTagHelperBlock("p", dateTimeNow)) + }; + yield return new object[] { + string.Format(currentFormattedString, doWhileString), + new MarkupBlock( + new MarkupTagHelperBlock("p", doWhile), + factory.Markup(" "), + new MarkupTagHelperBlock("p", doWhile)) + }; + + currentFormattedString = "

Hello {0}inside of {0} strong tag

"; + yield return new object[] { + string.Format(currentFormattedString, dateTimeNowString), + new MarkupBlock( + new MarkupTagHelperBlock("p", + factory.Markup("Hello "), + dateTimeNow, + blockFactory.MarkupTagBlock(""), + factory.Markup("inside of "), + dateTimeNow, + factory.Markup(" strong tag"), + blockFactory.MarkupTagBlock(""))) + }; + yield return new object[] { + string.Format(currentFormattedString, doWhileString), + new MarkupBlock( + new MarkupTagHelperBlock("p", + factory.Markup("Hello "), + doWhile, + blockFactory.MarkupTagBlock(""), + factory.Markup("inside of "), + doWhile, + factory.Markup(" strong tag"), + blockFactory.MarkupTagBlock(""))) + }; + } + } + + [Theory] + [MemberData(nameof(ComplexTagHelperBlockData))] + public void TagHelperParseTreeRewriter_RewritesComplexTagHelperTagBlocks( + string documentContent, + MarkupBlock expectedOutput) + { + RunParseTreeRewriterTest(documentContent, expectedOutput, "p"); + } + + public static IEnumerable ScriptBlockData + { + get + { + var factory = CreateDefaultSpanFactory(); + + yield return new object[] { + "", + new MarkupBlock( + new MarkupTagHelperBlock("script", + factory.Markup("", + new MarkupBlock( + new MarkupTagHelperBlock("script", + factory.Markup("Hello World
"))) + }; + yield return new object[] { + "

World

", + new MarkupBlock( + new MarkupTagHelperBlock("script", + factory.Markup("Hel

lo

")), + factory.Markup(" "), + new MarkupTagHelperBlock("p", + new MarkupTagHelperBlock("div", + factory.Markup("World")))) + }; + yield return new object[] { + " ", + new MarkupBlock( + new MarkupTagHelperBlock("script", + factory.Markup("Hello")), + factory.Markup(" "), + new MarkupTagHelperBlock("script", + factory.Markup("World"))) + }; + yield return new object[] { + " World

", + new MarkupBlock( + new MarkupTagHelperBlock("p", + factory.Markup("Hello "), + new MarkupTagHelperBlock("script", + new Dictionary + { + { "class", new MarkupBlock(factory.Markup("foo")) }, + { "style", new MarkupBlock(factory.Markup("color:red;")) } + }), + factory.Markup(" World"))) + }; + } + } + + [Theory] + [MemberData(nameof(ScriptBlockData))] + public void TagHelperParseTreeRewriter_RewritesScriptTagHelpers( + string documentContent, + MarkupBlock expectedOutput) + { + RunParseTreeRewriterTest(documentContent, expectedOutput, "p", "div", "script"); + } + + public static IEnumerable SelfClosingBlockData + { + get + { + var factory = CreateDefaultSpanFactory(); + + yield return new object[] { + "

", + new MarkupBlock( + new MarkupTagHelperBlock("p", + new Dictionary + { + { "class", new MarkupBlock(factory.Markup("foo")) }, + { "style", new MarkupBlock(factory.Markup("color:red;")) } + })) + }; + yield return new object[] { + "

Hello

World

", + new MarkupBlock( + new MarkupTagHelperBlock("p", + factory.Markup("Hello "), + new MarkupTagHelperBlock("p", + new Dictionary + { + { "class", new MarkupBlock(factory.Markup("foo")) }, + { "style", new MarkupBlock(factory.Markup("color:red;")) } + }), + factory.Markup(" World"))) + }; + yield return new object[] { + "Hello

World", + new MarkupBlock( + factory.Markup("Hello"), + new MarkupTagHelperBlock("p", + new Dictionary + { + { "class", new MarkupBlock(factory.Markup("foo")) } + }), + factory.Markup(" "), + new MarkupTagHelperBlock("p", + new Dictionary + { + { "style", new MarkupBlock(factory.Markup("color:red;")) } + }), + factory.Markup("World")) + }; + } + } + + [Theory] + [MemberData(nameof(SelfClosingBlockData))] + public void TagHelperParseTreeRewriter_RewritesSelfClosingTagHelpers( + string documentContent, + MarkupBlock expectedOutput) + { + RunParseTreeRewriterTest(documentContent, expectedOutput, "p"); + } + + public static IEnumerable QuotelessAttributeBlockData + { + get + { + var factory = CreateDefaultSpanFactory(); + var blockFactory = new BlockFactory(factory); + var dateTimeNow = new MarkupBlock( + new ExpressionBlock( + factory.CodeTransition(), + factory.Code("DateTime.Now") + .AsImplicitExpression(CSharpCodeParser.DefaultKeywords) + .Accepts(AcceptedCharacters.NonWhiteSpace))); + + yield return new object[] { + "

", + new MarkupBlock( + new MarkupTagHelperBlock("p", + new Dictionary + { + { "class", new MarkupBlock(factory.Markup("foo")) }, + { "dynamic", new MarkupBlock(dateTimeNow) }, + { "style", new MarkupBlock(factory.Markup("color:red;")) } + })) + }; + yield return new object[] { + "

Hello World

", + new MarkupBlock( + new MarkupTagHelperBlock("p", + new Dictionary + { + { "class", new MarkupBlock(factory.Markup("foo")) }, + { "dynamic", new MarkupBlock(dateTimeNow) }, + { "style", new MarkupBlock(factory.Markup("color:red;")) } + }, + factory.Markup("Hello World"))) + }; + yield return new object[] { + "

Hello

World

", + new MarkupBlock( + new MarkupTagHelperBlock("p", + new Dictionary + { + { "class", new MarkupBlock(factory.Markup("foo")) }, + { "dynamic", new MarkupBlock(dateTimeNow) } + }, + factory.Markup("Hello")), + factory.Markup(" "), + new MarkupTagHelperBlock("p", + new Dictionary + { + { "style", new MarkupBlock(factory.Markup("color:red;")) }, + { "dynamic", new MarkupBlock(dateTimeNow) } + }, + factory.Markup("World"))) + }; + yield return new object[] { + "

Hello World inside of strong tag

", + new MarkupBlock( + new MarkupTagHelperBlock("p", + new Dictionary + { + { "class", new MarkupBlock(factory.Markup("foo")) }, + { "dynamic", new MarkupBlock(dateTimeNow) }, + { "style", new MarkupBlock(factory.Markup("color:red;")) } + }, + factory.Markup("Hello World "), + new MarkupTagBlock( + factory.Markup("(" class=\"", 71, 0, 71), + suffix: new LocationTagged("\"", 82, 0, 82)), + factory.Markup(" class=\"").With(SpanCodeGenerator.Null), + factory.Markup("foo").With(new LiteralAttributeCodeGenerator(prefix: new LocationTagged(string.Empty, 79, 0, 79), + value: new LocationTagged("foo", 79, 0, 79))), + factory.Markup("\"").With(SpanCodeGenerator.Null)), + factory.Markup(">")), + factory.Markup("inside of strong tag"), + blockFactory.MarkupTagBlock(""))) + }; + } + } + + [Theory] + [MemberData(nameof(QuotelessAttributeBlockData))] + public void TagHelperParseTreeRewriter_RewritesTagHelpersWithQuotelessAttributes( + string documentContent, + MarkupBlock expectedOutput) + { + RunParseTreeRewriterTest(documentContent, expectedOutput, "p"); + } + + public static IEnumerable PlainAttributeBlockData + { + get + { + var factory = CreateDefaultSpanFactory(); + var blockFactory = new BlockFactory(factory); + + yield return new object[] { + "

", + new MarkupBlock( + new MarkupTagHelperBlock("p", + new Dictionary + { + { "class", new MarkupBlock(factory.Markup("foo")) }, + { "style", new MarkupBlock(factory.Markup("color:red;")) } + })) + }; + yield return new object[] { + "

Hello World

", + new MarkupBlock( + new MarkupTagHelperBlock("p", + new Dictionary + { + { "class", new MarkupBlock(factory.Markup("foo")) }, + { "style", new MarkupBlock(factory.Markup("color:red;")) } + }, + factory.Markup("Hello World"))) + }; + yield return new object[] { + "

Hello

World

", + new MarkupBlock( + new MarkupTagHelperBlock("p", + new Dictionary + { + { "class", new MarkupBlock(factory.Markup("foo")) } + }, + factory.Markup("Hello")), + factory.Markup(" "), + new MarkupTagHelperBlock("p", + new Dictionary + { + { "style", new MarkupBlock(factory.Markup("color:red;")) } + }, + factory.Markup("World"))) + }; + yield return new object[] { + "

Hello World inside of strong tag

", + new MarkupBlock( + new MarkupTagHelperBlock("p", + new Dictionary + { + { "class", new MarkupBlock(factory.Markup("foo")) }, + { "style", new MarkupBlock(factory.Markup("color:red;")) } + }, + factory.Markup("Hello World "), + new MarkupTagBlock( + factory.Markup("(" class=\"", 53, 0, 53), + suffix: new LocationTagged("\"", 64, 0, 64)), + factory.Markup(" class=\"").With(SpanCodeGenerator.Null), + factory.Markup("foo").With(new LiteralAttributeCodeGenerator(prefix: new LocationTagged(string.Empty, 61, 0, 61), + value: new LocationTagged("foo", 61, 0, 61))), + factory.Markup("\"").With(SpanCodeGenerator.Null)), + factory.Markup(">")), + factory.Markup("inside of strong tag"), + blockFactory.MarkupTagBlock(""))) + }; + } + } + + [Theory] + [MemberData(nameof(PlainAttributeBlockData))] + public void TagHelperParseTreeRewriter_RewritesTagHelpersWithPlainAttributes( + string documentContent, + MarkupBlock expectedOutput) + { + RunParseTreeRewriterTest(documentContent, expectedOutput, "p"); + } + + public static IEnumerable PlainBlockData + { + get + { + var factory = CreateDefaultSpanFactory(); + var blockFactory = new BlockFactory(factory); + + yield return new object[] { + "

", + new MarkupBlock( + new MarkupTagHelperBlock("p")) + }; + yield return new object[] { + "

Hello World

", + new MarkupBlock( + new MarkupTagHelperBlock("p", + factory.Markup("Hello World"))) + }; + yield return new object[] { + "

Hello

World

", + new MarkupBlock( + new MarkupTagHelperBlock("p", + factory.Markup("Hello")), + factory.Markup(" "), + new MarkupTagHelperBlock("p", + factory.Markup("World"))) + }; + yield return new object[] { + "

Hello World inside of strong tag

", + new MarkupBlock( + new MarkupTagHelperBlock("p", + factory.Markup("Hello World "), + blockFactory.MarkupTagBlock(""), + factory.Markup("inside of strong tag"), + blockFactory.MarkupTagBlock(""))) + }; + } + } + + [Theory] + [MemberData(nameof(PlainBlockData))] + public void TagHelperParseTreeRewriter_RewritesPlainTagHelperTagBlocks( + string documentContent, + MarkupBlock expectedOutput) + { + RunParseTreeRewriterTest(documentContent, expectedOutput, "p"); + } + + public static IEnumerable NestedBlockData + { + get + { + var factory = CreateDefaultSpanFactory(); + var blockFactory = new BlockFactory(factory); + + yield return new object[] { + "

", + new MarkupBlock( + new MarkupTagHelperBlock("p", + new MarkupTagHelperBlock("div"))) + }; + yield return new object[] { + "

Hello World

", + new MarkupBlock( + new MarkupTagHelperBlock("p", + factory.Markup("Hello World "), + new MarkupTagHelperBlock("div"))) + }; + yield return new object[] { + "

Hel

lo

World

", + new MarkupBlock( + new MarkupTagHelperBlock("p", + factory.Markup("Hel"), + new MarkupTagHelperBlock("p", + factory.Markup("lo"))), + factory.Markup(" "), + new MarkupTagHelperBlock("p", + new MarkupTagHelperBlock("div", + factory.Markup("World")))) + }; + yield return new object[] { + "

Hello

World

", + new MarkupBlock( + new MarkupTagHelperBlock("p", + factory.Markup("Hel"), + blockFactory.MarkupTagBlock(""), + factory.Markup("lo"), + blockFactory.MarkupTagBlock("")), + factory.Markup(" "), + new MarkupTagHelperBlock("p", + blockFactory.MarkupTagBlock(""), + factory.Markup("World"), + blockFactory.MarkupTagBlock(""))) + }; + } + } + + [Theory] + [MemberData(nameof(NestedBlockData))] + public void TagHelperParseTreeRewriter_RewritesNestedTagHelperTagBlocks( + string documentContent, + MarkupBlock expectedOutput) + { + RunParseTreeRewriterTest(documentContent, expectedOutput, "p", "div"); + } + + private void RunParseTreeRewriterTest(string documentContent, + MarkupBlock expectedOutput, + params string[] tagNames) + { + // Arrange + var providerContext = BuildProviderContext(tagNames); + + // Act & Assert + EvaluateData(providerContext, documentContent, expectedOutput); + } + + private TagHelperDescriptorProvider BuildProviderContext(params string[] tagNames) + { + var descriptors = new List(); + + foreach (var tagName in tagNames) + { + descriptors.Add( + new TagHelperDescriptor(tagName, tagName + "taghelper", ContentBehavior.None)); + } + + return new TagHelperDescriptorProvider(descriptors); + } + + private void EvaluateData(TagHelperDescriptorProvider provider, string documentContent, MarkupBlock expectedOutput) + { + var results = ParseDocument(documentContent); + var rewritten = new TagHelperParseTreeRewriter(provider).Rewrite(results.Document); + + Assert.Empty(results.ParserErrors); + EvaluateParseTree(rewritten, expectedOutput); + } + + private static SpanFactory CreateDefaultSpanFactory() + { + return new SpanFactory + { + MarkupTokenizerFactory = doc => new HtmlTokenizer(doc), + CodeTokenizerFactory = doc => new CSharpTokenizer(doc) + }; + } + } +} \ No newline at end of file