// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. using System.Web.Razor.Editor; using System.Web.Razor.Generator; using System.Web.Razor.Parser; using System.Web.Razor.Parser.SyntaxTree; using System.Web.Razor.Resources; using System.Web.Razor.Test.Framework; using System.Web.Razor.Text; using System.Web.Razor.Tokenizer.Symbols; using Xunit; using Assert = Microsoft.TestCommon.AssertEx; namespace System.Web.Razor.Test.Parser.Html { public class HtmlBlockTest : CsHtmlMarkupParserTestBase { [Fact] public void ParseBlockMethodThrowsArgNullExceptionOnNullContext() { // Arrange HtmlMarkupParser parser = new HtmlMarkupParser(); // Act and Assert Assert.Throws(() => parser.ParseBlock(), RazorResources.Parser_Context_Not_Set); } [Fact] public void ParseBlockHandlesOpenAngleAtEof() { ParseDocumentTest(@"@{ <", new MarkupBlock( Factory.EmptyHtml(), new StatementBlock( Factory.CodeTransition(), Factory.MetaCode("{").Accepts(AcceptedCharacters.None), Factory.Code("\r\n").AsStatement(), new MarkupBlock( Factory.Markup("<")))), new RazorError( String.Format(RazorResources.ParseError_Expected_EndOfBlock_Before_EOF, RazorResources.BlockName_Code, "}", "{"), 1, 0, 1)); } [Fact] public void ParseBlockHandlesOpenAngleWithProperTagFollowingIt() { ParseDocumentTest(@"@{ < ", new MarkupBlock( Factory.EmptyHtml(), new StatementBlock( Factory.CodeTransition(), Factory.MetaCode("{").Accepts(AcceptedCharacters.None), Factory.Code("\r\n").AsStatement(), new MarkupBlock( Factory.Markup("<\r\n") ), new MarkupBlock( Factory.Markup(@"").Accepts(AcceptedCharacters.None) ), Factory.EmptyCSharp().AsStatement() ) ), designTimeParser: true, expectedErrors: new[] { new RazorError(String.Format(RazorResources.ParseError_UnexpectedEndTag, "html"), 7, 2, 0), new RazorError(String.Format(RazorResources.ParseError_Expected_EndOfBlock_Before_EOF, "code", "}", "{"), 1, 0, 1) }); } [Fact] public void TagWithoutCloseAngleDoesNotTerminateBlock() { ParseBlockTest(@"< ", new MarkupBlock( Factory.Markup("< \r\n ")), designTimeParser: true, expectedErrors: new RazorError(String.Format(RazorResources.ParseError_UnfinishedTag, String.Empty), 0, 0, 0)); } [Fact] public void ParseBlockAllowsStartAndEndTagsToDifferInCase() { SingleSpanBlockTest("
  • Foo

  • ", BlockType.Markup, SpanKind.Markup, acceptedCharacters: AcceptedCharacters.None); } [Fact] public void ParseBlockReadsToEndOfLineIfFirstCharacterAfterTransitionIsColon() { ParseBlockTest(@"@:
  • Foo Bar Baz bork", new MarkupBlock( Factory.MarkupTransition(), Factory.MetaMarkup(":", HtmlSymbolType.Colon), Factory.Markup("
  • Foo Bar Baz\r\n") .With(new SingleLineMarkupEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString, AcceptedCharacters.None)) )); } [Fact] public void ParseBlockStopsParsingSingleLineBlockAtEOFIfNoEOLReached() { ParseBlockTest("@:foo bar", new MarkupBlock( Factory.MarkupTransition(), Factory.MetaMarkup(":", HtmlSymbolType.Colon), Factory.Markup(@"foo bar") .With(new SingleLineMarkupEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString)) )); } [Fact] public void ParseBlockStopsAtMatchingCloseTagToStartTag() { SingleSpanBlockTest("", "", BlockType.Markup, SpanKind.Markup, acceptedCharacters: AcceptedCharacters.None); } [Fact] public void ParseBlockParsesUntilMatchingEndTagIfFirstNonWhitespaceCharacterIsStartTag() { SingleSpanBlockTest("", BlockType.Markup, SpanKind.Markup, acceptedCharacters: AcceptedCharacters.None); } [Fact] public void ParseBlockAllowsUnclosedTagsAsLongAsItCanRecoverToAnExpectedEndTag() { SingleSpanBlockTest("", BlockType.Markup, SpanKind.Markup, acceptedCharacters: AcceptedCharacters.None); } [Fact] public void ParseBlockWithSelfClosingTagJustEmitsTag() { SingleSpanBlockTest("", BlockType.Markup, SpanKind.Markup, acceptedCharacters: AcceptedCharacters.None); } [Fact] public void ParseBlockCanHandleSelfClosingTagsWithinBlock() { SingleSpanBlockTest("", BlockType.Markup, SpanKind.Markup, acceptedCharacters: AcceptedCharacters.None); } [Fact] public void ParseBlockSupportsTagsWithAttributes() { ParseBlockTest("", new MarkupBlock( Factory.Markup("(" bar=\"", 4, 0, 4), new LocationTagged("\"", 13, 0, 13)), Factory.Markup(" bar=\"").With(SpanCodeGenerator.Null), Factory.Markup("baz").With(new LiteralAttributeCodeGenerator(new LocationTagged(String.Empty, 10, 0, 10), new LocationTagged("baz", 10, 0, 10))), Factory.Markup("\"").With(SpanCodeGenerator.Null)), Factory.Markup(">(" zoop=", 24, 0, 24), new LocationTagged(String.Empty, 34, 0, 34)), Factory.Markup(" zoop=").With(SpanCodeGenerator.Null), Factory.Markup("zork").With(new LiteralAttributeCodeGenerator(new LocationTagged(String.Empty, 30, 0, 30), new LocationTagged("zork", 30, 0, 30)))), Factory.Markup("/>").Accepts(AcceptedCharacters.None))); } [Fact] public void ParseBlockAllowsCloseAngleBracketInAttributeValueIfDoubleQuoted() { ParseBlockTest("\" />", new MarkupBlock( Factory.Markup("(" baz=\"", 9, 0, 9), new LocationTagged("\"", 16, 0, 16)), Factory.Markup(" baz=\"").With(SpanCodeGenerator.Null), Factory.Markup(">").With(new LiteralAttributeCodeGenerator(new LocationTagged(String.Empty, 15, 0, 15), new LocationTagged(">", 15, 0, 15))), Factory.Markup("\"").With(SpanCodeGenerator.Null)), Factory.Markup(" />").Accepts(AcceptedCharacters.None))); } [Fact] public void ParseBlockAllowsCloseAngleBracketInAttributeValueIfSingleQuoted() { ParseBlockTest("\' />", new MarkupBlock( Factory.Markup("(" baz='", 9, 0, 9), new LocationTagged("'", 16, 0, 16)), Factory.Markup(" baz='").With(SpanCodeGenerator.Null), Factory.Markup(">").With(new LiteralAttributeCodeGenerator(new LocationTagged(String.Empty, 15, 0, 15), new LocationTagged(">", 15, 0, 15))), Factory.Markup("'").With(SpanCodeGenerator.Null)), Factory.Markup(" />").Accepts(AcceptedCharacters.None))); } [Fact] public void ParseBlockAllowsSlashInAttributeValueIfDoubleQuoted() { ParseBlockTest("", new MarkupBlock( Factory.Markup("(" baz=\"", 9, 0, 9), new LocationTagged("\"", 16, 0, 16)), Factory.Markup(" baz=\"").With(SpanCodeGenerator.Null), Factory.Markup("/").With(new LiteralAttributeCodeGenerator(new LocationTagged(String.Empty, 15, 0, 15), new LocationTagged("/", 15, 0, 15))), Factory.Markup("\"").With(SpanCodeGenerator.Null)), Factory.Markup(">").Accepts(AcceptedCharacters.None))); } [Fact] public void ParseBlockAllowsSlashInAttributeValueIfSingleQuoted() { ParseBlockTest("", new MarkupBlock( Factory.Markup("(" baz='", 9, 0, 9), new LocationTagged("'", 16, 0, 16)), Factory.Markup(" baz='").With(SpanCodeGenerator.Null), Factory.Markup("/").With(new LiteralAttributeCodeGenerator(new LocationTagged(String.Empty, 15, 0, 15), new LocationTagged("/", 15, 0, 15))), Factory.Markup("'").With(SpanCodeGenerator.Null)), Factory.Markup(">").Accepts(AcceptedCharacters.None))); } [Fact] public void ParseBlockTerminatesAtEOF() { SingleSpanBlockTest("", "", BlockType.Markup, SpanKind.Markup, new RazorError(String.Format(RazorResources.ParseError_MissingEndTag, "foo"), new SourceLocation(0, 0, 0))); } [Fact] public void ParseBlockSupportsCommentAsBlock() { SingleSpanBlockTest("", BlockType.Markup, SpanKind.Markup, acceptedCharacters: AcceptedCharacters.None); } [Fact] public void ParseBlockSupportsCommentWithinBlock() { SingleSpanBlockTest("barbaz", BlockType.Markup, SpanKind.Markup, acceptedCharacters: AcceptedCharacters.None); } [Fact] public void ParseBlockProperlyBalancesCommentStartAndEndTags() { SingleSpanBlockTest("", BlockType.Markup, SpanKind.Markup, acceptedCharacters: AcceptedCharacters.None); } [Fact] public void ParseBlockTerminatesAtEOFWhenParsingComment() { SingleSpanBlockTest("", BlockType.Markup, SpanKind.Markup, acceptedCharacters: AcceptedCharacters.None); } [Fact] public void ParseBlockTerminatesCommentAtFirstOccurrenceOfEndSequence() { SingleSpanBlockTest("-->", BlockType.Markup, SpanKind.Markup, acceptedCharacters: AcceptedCharacters.None); } [Fact] public void ParseBlockTreatsMalformedTagsAsContent() { SingleSpanBlockTest( "", "", BlockType.Markup, SpanKind.Markup, AcceptedCharacters.None, new RazorError(String.Format(RazorResources.ParseError_MissingEndTag, "foo"), 0, 0, 0)); } [Fact] public void ParseBlockParsesSGMLDeclarationAsEmptyTag() { SingleSpanBlockTest("", BlockType.Markup, SpanKind.Markup, acceptedCharacters: AcceptedCharacters.None); } [Fact] public void ParseBlockTerminatesSGMLDeclarationAtFirstCloseAngle() { SingleSpanBlockTest(" baz>", BlockType.Markup, SpanKind.Markup, acceptedCharacters: AcceptedCharacters.None); } [Fact] public void ParseBlockParsesXMLProcessingInstructionAsEmptyTag() { SingleSpanBlockTest("", BlockType.Markup, SpanKind.Markup, acceptedCharacters: AcceptedCharacters.None); } [Fact] public void ParseBlockTerminatesXMLProcessingInstructionAtQuestionMarkCloseAnglePair() { SingleSpanBlockTest(" baz", BlockType.Markup, SpanKind.Markup, acceptedCharacters: AcceptedCharacters.None); } [Fact] public void ParseBlockDoesNotTerminateXMLProcessingInstructionAtCloseAngleUnlessPreceededByQuestionMark() { SingleSpanBlockTest(" baz?>", BlockType.Markup, SpanKind.Markup, acceptedCharacters: AcceptedCharacters.None); } [Fact] public void ParseBlockSupportsScriptTagsWithLessThanSignsInThem() { SingleSpanBlockTest(@"", BlockType.Markup, SpanKind.Markup, acceptedCharacters: AcceptedCharacters.None); } [Fact] public void ParseBlockSupportsScriptTagsWithSpacedLessThanSignsInThem() { SingleSpanBlockTest(@"", BlockType.Markup, SpanKind.Markup, acceptedCharacters: AcceptedCharacters.None); } [Fact] public void ParseBlockAcceptsEmptyTextTag() { ParseBlockTest("", new MarkupBlock( Factory.MarkupTransition("") )); } [Fact] public void ParseBlockAcceptsTextTagAsOuterTagButDoesNotRender() { ParseBlockTest("Foo Bar Baz zoop", new MarkupBlock( Factory.MarkupTransition(""), Factory.Markup("Foo Bar Baz"), Factory.MarkupTransition(""), Factory.Markup(" ").Accepts(AcceptedCharacters.None) )); } [Fact] public void ParseBlockRendersLiteralTextTagIfDoubled() { ParseBlockTest("Foo Bar Baz zoop", new MarkupBlock( Factory.MarkupTransition(""), Factory.Markup("Foo Bar Baz"), Factory.MarkupTransition(""), Factory.Markup(" ").Accepts(AcceptedCharacters.None) )); } [Fact] public void ParseBlockDoesNotConsiderPsuedoTagWithinMarkupBlock() { ParseBlockTest("", new MarkupBlock( Factory.Markup("").Accepts(AcceptedCharacters.None) )); } [Fact] public void ParseBlockStopsParsingMidEmptyTagIfEOFReached() { ParseBlockTest("
    Foo @if(true) {} Bar", new MarkupBlock( Factory.Markup("
    Foo "), new StatementBlock( Factory.CodeTransition(), Factory.Code("if(true) {}").AsStatement()), Factory.Markup(" Bar
    ").Accepts(AcceptedCharacters.None))); } [Fact] public void ParseBlockIgnoresTagsInContentsOfScriptTag() { ParseBlockTest(@"", new MarkupBlock( Factory.Markup("") .Accepts(AcceptedCharacters.None))); } } }