// 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 Microsoft.AspNet.Razor.Editor; using Microsoft.AspNet.Razor.Generator; using Microsoft.AspNet.Razor.Parser; using Microsoft.AspNet.Razor.Parser.SyntaxTree; using Microsoft.AspNet.Razor.Test.Framework; using Microsoft.AspNet.Razor.Text; using Microsoft.AspNet.Razor.Tokenizer.Symbols; using Xunit; namespace Microsoft.AspNet.Razor.Test.Parser.Html { public class HtmlBlockTest : CsHtmlMarkupParserTestBase { [Fact] public void ParseBlockMethodThrowsArgNullExceptionOnNullContext() { // Arrange HtmlMarkupParser parser = new HtmlMarkupParser(); // Act and Assert var exception = Assert.Throws(() => parser.ParseBlock()); Assert.Equal(RazorResources.Parser_Context_Not_Set, exception.Message); } [Fact] public void ParseBlockHandlesOpenAngleAtEof() { ParseDocumentTest("@{" + Environment.NewLine + "<", new MarkupBlock( Factory.EmptyHtml(), new StatementBlock( Factory.CodeTransition(), Factory.MetaCode("{").Accepts(AcceptedCharacters.None), Factory.Code("\r\n").AsStatement(), new MarkupBlock( new MarkupTagBlock( Factory.Markup("<"))))), new RazorError( RazorResources.FormatParseError_Expected_EndOfBlock_Before_EOF(RazorResources.BlockName_Code, "}", "{"), 1, 0, 1)); } [Fact] public void ParseBlockHandlesOpenAngleWithProperTagFollowingIt() { ParseDocumentTest("@{" + Environment.NewLine + "<" + Environment.NewLine + "", new MarkupBlock( Factory.EmptyHtml(), new StatementBlock( Factory.CodeTransition(), Factory.MetaCode("{").Accepts(AcceptedCharacters.None), Factory.Code("\r\n").AsStatement(), new MarkupBlock( new MarkupTagBlock( Factory.Markup("<\r\n")) ), new MarkupBlock( new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharacters.None)) ), Factory.EmptyCSharp().AsStatement() ) ), designTimeParser: true, expectedErrors: new[] { new RazorError(RazorResources.FormatParseError_UnexpectedEndTag("html"), 7, 2, 0), new RazorError(RazorResources.FormatParseError_Expected_EndOfBlock_Before_EOF("code", "}", "{"), 1, 0, 1) }); } [Fact] public void TagWithoutCloseAngleDoesNotTerminateBlock() { ParseBlockTest("< " + Environment.NewLine + " ", new MarkupBlock( new MarkupTagBlock( Factory.Markup("< \r\n "))), designTimeParser: true, expectedErrors: new RazorError(RazorResources.FormatParseError_UnfinishedTag(string.Empty), 0, 0, 0)); } [Fact] public void ParseBlockAllowsStartAndEndTagsToDifferInCase() { ParseBlockTest("
  • Foo

  • ", new MarkupBlock( new MarkupTagBlock( Factory.Markup("
  • ").Accepts(AcceptedCharacters.None)), new MarkupTagBlock( Factory.Markup("

    ").Accepts(AcceptedCharacters.None)), Factory.Markup("Foo"), new MarkupTagBlock( Factory.Markup("

    ").Accepts(AcceptedCharacters.None)), new MarkupTagBlock( Factory.Markup("
  • ").Accepts(AcceptedCharacters.None)) )); } [Fact] public void ParseBlockReadsToEndOfLineIfFirstCharacterAfterTransitionIsColon() { ParseBlockTest("@:
  • Foo Bar Baz" + Environment.NewLine + "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() { ParseBlockTest("", new MarkupBlock( new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharacters.None)), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharacters.None)), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharacters.None)), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharacters.None)) )); } [Fact] public void ParseBlockParsesUntilMatchingEndTagIfFirstNonWhitespaceCharacterIsStartTag() { ParseBlockTest("", new MarkupBlock( new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharacters.None)), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharacters.None)), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharacters.None)), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharacters.None)), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharacters.None)), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharacters.None)) )); } [Fact] public void ParseBlockAllowsUnclosedTagsAsLongAsItCanRecoverToAnExpectedEndTag() { ParseBlockTest("", new MarkupBlock( new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharacters.None)), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharacters.None)), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharacters.None)), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharacters.None)) )); } [Fact] public void ParseBlockWithSelfClosingTagJustEmitsTag() { ParseBlockTest("", new MarkupBlock( new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharacters.None)) )); } [Fact] public void ParseBlockCanHandleSelfClosingTagsWithinBlock() { ParseBlockTest("", new MarkupBlock( new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharacters.None)), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharacters.None)), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharacters.None)) )); } [Fact] public void ParseBlockSupportsTagsWithAttributes() { ParseBlockTest("", new MarkupBlock( new MarkupTagBlock( 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(">").Accepts(AcceptedCharacters.None)), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharacters.None)), new MarkupTagBlock( 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)), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharacters.None)), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharacters.None)))); } [Fact] public void ParseBlockAllowsCloseAngleBracketInAttributeValueIfDoubleQuoted() { ParseBlockTest("\" />", new MarkupBlock( new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharacters.None)), new MarkupTagBlock( 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)), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharacters.None)))); } [Fact] public void ParseBlockAllowsCloseAngleBracketInAttributeValueIfSingleQuoted() { ParseBlockTest("\' />", new MarkupBlock( new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharacters.None)), new MarkupTagBlock( 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)), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharacters.None)))); } [Fact] public void ParseBlockAllowsSlashInAttributeValueIfDoubleQuoted() { ParseBlockTest("", new MarkupBlock( new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharacters.None)), new MarkupTagBlock( 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)), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharacters.None)), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharacters.None)))); } [Fact] public void ParseBlockAllowsSlashInAttributeValueIfSingleQuoted() { ParseBlockTest("", new MarkupBlock( new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharacters.None)), new MarkupTagBlock( 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)), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharacters.None)), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharacters.None)))); } [Fact] public void ParseBlockTerminatesAtEOF() { ParseBlockTest("", new MarkupBlock( new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharacters.None))), new RazorError(RazorResources.FormatParseError_MissingEndTag("foo"), new SourceLocation(0, 0, 0))); } [Fact] public void ParseBlockSupportsCommentAsBlock() { SingleSpanBlockTest("", BlockType.Markup, SpanKind.Markup, acceptedCharacters: AcceptedCharacters.None); } [Fact] public void ParseBlockSupportsCommentWithinBlock() { ParseBlockTest("barbaz", new MarkupBlock( new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharacters.None)), Factory.Markup("bar"), Factory.Markup("").Accepts(AcceptedCharacters.None), Factory.Markup("baz"), new MarkupTagBlock( Factory.Markup("").Accepts(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() { ParseBlockTest("-->", new MarkupBlock( new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharacters.None)), Factory.Markup("").Accepts(AcceptedCharacters.None), Factory.Markup("-->"), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharacters.None)))); } [Fact] public void ParseBlockTreatsMalformedTagsAsContent() { ParseBlockTest("", new MarkupBlock( new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharacters.None)), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharacters.None))), new RazorError(RazorResources.FormatParseError_MissingEndTag("foo"), 0, 0, 0)); } [Fact] public void ParseBlockParsesSGMLDeclarationAsEmptyTag() { ParseBlockTest("", new MarkupBlock( new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharacters.None)), Factory.Markup("").Accepts(AcceptedCharacters.None), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharacters.None)))); } [Fact] public void ParseBlockTerminatesSGMLDeclarationAtFirstCloseAngle() { ParseBlockTest(" baz>", new MarkupBlock( new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharacters.None)), Factory.Markup("").Accepts(AcceptedCharacters.None), Factory.Markup(" baz>"), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharacters.None)))); } [Fact] public void ParseBlockParsesXMLProcessingInstructionAsEmptyTag() { ParseBlockTest("", new MarkupBlock( new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharacters.None)), Factory.Markup("").Accepts(AcceptedCharacters.None), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharacters.None)))); } [Fact] public void ParseBlockTerminatesXMLProcessingInstructionAtQuestionMarkCloseAnglePair() { ParseBlockTest(" baz", new MarkupBlock( new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharacters.None)), Factory.Markup("").Accepts(AcceptedCharacters.None), Factory.Markup(" baz"), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharacters.None)))); } [Fact] public void ParseBlockDoesNotTerminateXMLProcessingInstructionAtCloseAngleUnlessPreceededByQuestionMark() { ParseBlockTest(" baz?>", new MarkupBlock( new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharacters.None)), Factory.Markup(" baz?>").Accepts(AcceptedCharacters.None), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharacters.None)))); } [Fact] public void ParseBlockSupportsScriptTagsWithLessThanSignsInThem() { ParseBlockTest(@"", new MarkupBlock( new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharacters.None)))); } [Fact] public void ParseBlockSupportsScriptTagsWithSpacedLessThanSignsInThem() { ParseBlockTest(@"", new MarkupBlock( new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharacters.None)))); } [Fact] public void ParseBlockAcceptsEmptyTextTag() { ParseBlockTest("", new MarkupBlock( new MarkupTagBlock( Factory.MarkupTransition("")) )); } [Fact] public void ParseBlockAcceptsTextTagAsOuterTagButDoesNotRender() { ParseBlockTest("Foo Bar Baz zoop", new MarkupBlock( new MarkupTagBlock( Factory.MarkupTransition("")), Factory.Markup("Foo Bar ").Accepts(AcceptedCharacters.None), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharacters.None)), Factory.Markup(" Baz"), new MarkupTagBlock( Factory.MarkupTransition("")), Factory.Markup(" ").Accepts(AcceptedCharacters.None) )); } [Fact] public void ParseBlockRendersLiteralTextTagIfDoubled() { ParseBlockTest("Foo Bar Baz zoop", new MarkupBlock( new MarkupTagBlock( Factory.MarkupTransition("")), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharacters.None)), Factory.Markup("Foo Bar "), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharacters.None)), Factory.Markup(" Baz"), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharacters.None)), new MarkupTagBlock( Factory.MarkupTransition("")), Factory.Markup(" ").Accepts(AcceptedCharacters.None) )); } [Fact] public void ParseBlockDoesNotConsiderPsuedoTagWithinMarkupBlock() { ParseBlockTest("", new MarkupBlock( new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharacters.None)), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharacters.None)), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharacters.None)), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharacters.None)), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharacters.None)) )); } [Fact] public void ParseBlockStopsParsingMidEmptyTagIfEOFReached() { ParseBlockTest("
    Foo @if(true) {} Bar", new MarkupBlock( new MarkupTagBlock( Factory.Markup("
    ").Accepts(AcceptedCharacters.None)), Factory.Markup("Foo "), new StatementBlock( Factory.CodeTransition(), Factory.Code("if(true) {}").AsStatement()), Factory.Markup(" Bar"), new MarkupTagBlock( Factory.Markup("
    ").Accepts(AcceptedCharacters.None)))); } [Fact] public void ParseBlockIgnoresTagsInContentsOfScriptTag() { ParseBlockTest(@"", new MarkupBlock( new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharacters.None)))); } } }