// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using Xunit; namespace Microsoft.AspNetCore.Razor.Language.Legacy { public class HtmlBlockTest : CsHtmlMarkupParserTestBase { [Fact] public void ParseBlockHandlesUnbalancedTripleDashHTMLComments() { ParseDocumentTest( @"@{ }", new MarkupBlock( Factory.EmptyHtml(), new StatementBlock( Factory.CodeTransition(), Factory.MetaCode("{").Accepts(AcceptedCharactersInternal.None), Factory.Code(Environment.NewLine).AsStatement().AutoCompleteWith(null), new MarkupBlock( Factory.Markup(" "), BlockFactory.HtmlCommentBlock(" Hello, I'm a comment that shouldn't break razor -"), Factory.Markup(Environment.NewLine).Accepts(AcceptedCharactersInternal.None)), Factory.EmptyCSharp().AsStatement(), Factory.MetaCode("}").Accepts(AcceptedCharactersInternal.None)), Factory.EmptyHtml()), new RazorDiagnostic[0]); } [Fact] public void ParseBlockHandlesOpenAngleAtEof() { ParseDocumentTest("@{" + Environment.NewLine + "<", new MarkupBlock( Factory.EmptyHtml(), new StatementBlock( Factory.CodeTransition(), Factory.MetaCode("{").Accepts(AcceptedCharactersInternal.None), Factory.Code(Environment.NewLine) .AsStatement() .AutoCompleteWith("}"), new MarkupBlock( new MarkupTagBlock( Factory.Markup("<"))))), RazorDiagnosticFactory.CreateParsing_ExpectedEndOfBlockBeforeEOF( new SourceSpan(new SourceLocation(1, 0, 1), contentLength: 1), Resources.BlockName_Code, "}", "{")); } [Fact] public void ParseBlockHandlesOpenAngleWithProperTagFollowingIt() { ParseDocumentTest("@{" + Environment.NewLine + "<" + Environment.NewLine + "", new MarkupBlock( Factory.EmptyHtml(), new StatementBlock( Factory.CodeTransition(), Factory.MetaCode("{").Accepts(AcceptedCharactersInternal.None), Factory.Code(Environment.NewLine) .AsStatement() .AutoCompleteWith("}"), new MarkupBlock( new MarkupTagBlock( Factory.Markup("<" + Environment.NewLine)) ), new MarkupBlock( new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharactersInternal.None)) ), Factory.EmptyCSharp().AsStatement() ) ), designTime: true, expectedErrors: new[] { RazorDiagnosticFactory.CreateParsing_UnexpectedEndTag( new SourceSpan(new SourceLocation(5 + Environment.NewLine.Length * 2, 2, 2), contentLength: 4), "html"), RazorDiagnosticFactory.CreateParsing_ExpectedEndOfBlockBeforeEOF( new SourceSpan(new SourceLocation(1, 0, 1), contentLength: 1), Resources.BlockName_Code, "}", "{"), }); } [Fact] public void TagWithoutCloseAngleDoesNotTerminateBlock() { ParseBlockTest("< " + Environment.NewLine + " ", new MarkupBlock( new MarkupTagBlock( Factory.Markup($"< {Environment.NewLine} "))), designTime: true, expectedErrors: RazorDiagnosticFactory.CreateParsing_UnfinishedTag( new SourceSpan(new SourceLocation(1, 0, 1), contentLength: 1), string.Empty)); } [Fact] public void ParseBlockAllowsStartAndEndTagsToDifferInCase() { ParseBlockTest("
  • Foo

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

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

    ").Accepts(AcceptedCharactersInternal.None)), new MarkupTagBlock( Factory.Markup("
  • ").Accepts(AcceptedCharactersInternal.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" + Environment.NewLine) .With(new SpanEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString, AcceptedCharactersInternal.None)) )); } [Fact] public void ParseBlockStopsParsingSingleLineBlockAtEOFIfNoEOLReached() { ParseBlockTest("@:foo bar", new MarkupBlock( Factory.MarkupTransition(), Factory.MetaMarkup(":", HtmlSymbolType.Colon), Factory.Markup(@"foo bar") .With(new SpanEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString)) )); } [Fact] public void ParseBlockStopsAtMatchingCloseTagToStartTag() { ParseBlockTest("", new MarkupBlock( new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharactersInternal.None)), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharactersInternal.None)), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharactersInternal.None)), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharactersInternal.None)) )); } [Fact] public void ParseBlockParsesUntilMatchingEndTagIfFirstNonWhitespaceCharacterIsStartTag() { ParseBlockTest("", new MarkupBlock( new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharactersInternal.None)), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharactersInternal.None)), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharactersInternal.None)), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharactersInternal.None)), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharactersInternal.None)), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharactersInternal.None)) )); } [Fact] public void ParseBlockAllowsUnclosedTagsAsLongAsItCanRecoverToAnExpectedEndTag() { ParseBlockTest("", new MarkupBlock( new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharactersInternal.None)), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharactersInternal.None)), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharactersInternal.None)), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharactersInternal.None)) )); } [Fact] public void ParseBlockWithSelfClosingTagJustEmitsTag() { ParseBlockTest("", new MarkupBlock( new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharactersInternal.None)) )); } [Fact] public void ParseBlockCanHandleSelfClosingTagsWithinBlock() { ParseBlockTest("", new MarkupBlock( new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharactersInternal.None)), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharactersInternal.None)), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharactersInternal.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(SpanChunkGenerator.Null), Factory.Markup("baz").With(new LiteralAttributeChunkGenerator(new LocationTagged(string.Empty, 10, 0, 10), new LocationTagged("baz", 10, 0, 10))), Factory.Markup("\"").With(SpanChunkGenerator.Null)), Factory.Markup(">").Accepts(AcceptedCharactersInternal.None)), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharactersInternal.None)), new MarkupTagBlock( Factory.Markup("(" zoop=", 24, 0, 24), new LocationTagged(string.Empty, 34, 0, 34)), Factory.Markup(" zoop=").With(SpanChunkGenerator.Null), Factory.Markup("zork").With(new LiteralAttributeChunkGenerator(new LocationTagged(string.Empty, 30, 0, 30), new LocationTagged("zork", 30, 0, 30)))), Factory.Markup("/>").Accepts(AcceptedCharactersInternal.None)), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharactersInternal.None)), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharactersInternal.None)))); } [Fact] public void ParseBlockAllowsCloseAngleBracketInAttributeValueIfDoubleQuoted() { ParseBlockTest("\" />", new MarkupBlock( new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharactersInternal.None)), new MarkupTagBlock( Factory.Markup("(" baz=\"", 9, 0, 9), new LocationTagged("\"", 16, 0, 16)), Factory.Markup(" baz=\"").With(SpanChunkGenerator.Null), Factory.Markup(">").With(new LiteralAttributeChunkGenerator(new LocationTagged(string.Empty, 15, 0, 15), new LocationTagged(">", 15, 0, 15))), Factory.Markup("\"").With(SpanChunkGenerator.Null)), Factory.Markup(" />").Accepts(AcceptedCharactersInternal.None)), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharactersInternal.None)))); } [Fact] public void ParseBlockAllowsCloseAngleBracketInAttributeValueIfSingleQuoted() { ParseBlockTest("\' />", new MarkupBlock( new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharactersInternal.None)), new MarkupTagBlock( Factory.Markup("(" baz='", 9, 0, 9), new LocationTagged("'", 16, 0, 16)), Factory.Markup(" baz='").With(SpanChunkGenerator.Null), Factory.Markup(">").With(new LiteralAttributeChunkGenerator(new LocationTagged(string.Empty, 15, 0, 15), new LocationTagged(">", 15, 0, 15))), Factory.Markup("'").With(SpanChunkGenerator.Null)), Factory.Markup(" />").Accepts(AcceptedCharactersInternal.None)), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharactersInternal.None)))); } [Fact] public void ParseBlockAllowsSlashInAttributeValueIfDoubleQuoted() { ParseBlockTest("", new MarkupBlock( new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharactersInternal.None)), new MarkupTagBlock( Factory.Markup("(" baz=\"", 9, 0, 9), new LocationTagged("\"", 16, 0, 16)), Factory.Markup(" baz=\"").With(SpanChunkGenerator.Null), Factory.Markup("/").With(new LiteralAttributeChunkGenerator(new LocationTagged(string.Empty, 15, 0, 15), new LocationTagged("/", 15, 0, 15))), Factory.Markup("\"").With(SpanChunkGenerator.Null)), Factory.Markup(">").Accepts(AcceptedCharactersInternal.None)), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharactersInternal.None)), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharactersInternal.None)))); } [Fact] public void ParseBlockAllowsSlashInAttributeValueIfSingleQuoted() { ParseBlockTest("", new MarkupBlock( new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharactersInternal.None)), new MarkupTagBlock( Factory.Markup("(" baz='", 9, 0, 9), new LocationTagged("'", 16, 0, 16)), Factory.Markup(" baz='").With(SpanChunkGenerator.Null), Factory.Markup("/").With(new LiteralAttributeChunkGenerator(new LocationTagged(string.Empty, 15, 0, 15), new LocationTagged("/", 15, 0, 15))), Factory.Markup("'").With(SpanChunkGenerator.Null)), Factory.Markup(">").Accepts(AcceptedCharactersInternal.None)), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharactersInternal.None)), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharactersInternal.None)))); } [Fact] public void ParseBlockTerminatesAtEOF() { ParseBlockTest("", new MarkupBlock( new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharactersInternal.None))), RazorDiagnosticFactory.CreateParsing_MissingEndTag( new SourceSpan(new SourceLocation(1, 0, 1), contentLength: 3), "foo")); } [Fact] public void ParseBlockSupportsCommentAsBlock() { ParseBlockTest("", new MarkupBlock(BlockFactory.HtmlCommentBlock(" foo "))); } [Fact] public void ParseBlockSupportsCommentWithExtraDashAsBlock() { ParseBlockTest("", new MarkupBlock(BlockFactory.HtmlCommentBlock(" foo -"))); } [Fact] public void ParseBlockSupportsCommentWithinBlock() { ParseBlockTest("barbaz", new MarkupBlock( new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharactersInternal.None)), Factory.Markup("bar"), BlockFactory.HtmlCommentBlock(" zoop "), Factory.Markup("baz").Accepts(AcceptedCharactersInternal.None), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharactersInternal.None)))); } public static TheoryData HtmlCommentSupportsMultipleDashesData { get { var factory = new SpanFactory(); var blockFactory = new BlockFactory(factory); return new TheoryData { { "
    ", new MarkupBlock( new MarkupTagBlock( factory.Markup("
    ").Accepts(AcceptedCharactersInternal.None)), blockFactory.HtmlCommentBlock("- Hello World -"), new MarkupTagBlock( factory.Markup("
    ").Accepts(AcceptedCharactersInternal.None))) }, { "
    ", new MarkupBlock( new MarkupTagBlock( factory.Markup("
    ").Accepts(AcceptedCharactersInternal.None)), blockFactory.HtmlCommentBlock("-- Hello World --"), new MarkupTagBlock( factory.Markup("
    ").Accepts(AcceptedCharactersInternal.None))) }, { "
    ", new MarkupBlock( new MarkupTagBlock( factory.Markup("
    ").Accepts(AcceptedCharactersInternal.None)), blockFactory.HtmlCommentBlock("--- Hello World ---"), new MarkupTagBlock( factory.Markup("
    ").Accepts(AcceptedCharactersInternal.None))) }, { "
    ", new MarkupBlock( new MarkupTagBlock( factory.Markup("
    ").Accepts(AcceptedCharactersInternal.None)), blockFactory.HtmlCommentBlock("--- Hello < --- > World
    ---"), new MarkupTagBlock( factory.Markup("").Accepts(AcceptedCharactersInternal.None))) }, }; } } [Theory] [MemberData(nameof(HtmlCommentSupportsMultipleDashesData))] public void HtmlCommentSupportsMultipleDashes(string documentContent, object expectedOutput) { FixupSpans = true; ParseBlockTest(documentContent, (MarkupBlock)expectedOutput); } [Fact] public void ParseBlockProperlyBalancesCommentStartAndEndTags() { ParseBlockTest("", new MarkupBlock(BlockFactory.HtmlCommentBlock(""))); } [Fact] public void ParseBlockTerminatesAtEOFWhenParsingComment() { ParseBlockTest( "", new MarkupBlock(BlockFactory.HtmlCommentBlock("--"))); } [Fact] public void ParseBlockTerminatesCommentAtFirstOccurrenceOfEndSequence() { ParseBlockTest("-->", new MarkupBlock( new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharactersInternal.None)), BlockFactory.HtmlCommentBlock("").Accepts(AcceptedCharactersInternal.None), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharactersInternal.None)))); } [Fact] public void ParseBlockTreatsMalformedTagsAsContent() { ParseBlockTest("", new MarkupBlock( new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharactersInternal.None)), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharactersInternal.None))), RazorDiagnosticFactory.CreateParsing_MissingEndTag( new SourceSpan(new SourceLocation(1, 0, 1), contentLength: 3), "foo")); } [Fact] public void ParseBlockParsesSGMLDeclarationAsEmptyTag() { ParseBlockTest("", new MarkupBlock( new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharactersInternal.None)), Factory.Markup("").Accepts(AcceptedCharactersInternal.None), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharactersInternal.None)))); } [Fact] public void ParseBlockTerminatesSGMLDeclarationAtFirstCloseAngle() { ParseBlockTest(" baz>", new MarkupBlock( new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharactersInternal.None)), Factory.Markup("").Accepts(AcceptedCharactersInternal.None), Factory.Markup(" baz>"), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharactersInternal.None)))); } [Fact] public void ParseBlockParsesXMLProcessingInstructionAsEmptyTag() { ParseBlockTest("", new MarkupBlock( new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharactersInternal.None)), Factory.Markup("").Accepts(AcceptedCharactersInternal.None), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharactersInternal.None)))); } [Fact] public void ParseBlockTerminatesXMLProcessingInstructionAtQuestionMarkCloseAnglePair() { ParseBlockTest(" baz", new MarkupBlock( new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharactersInternal.None)), Factory.Markup("").Accepts(AcceptedCharactersInternal.None), Factory.Markup(" baz"), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharactersInternal.None)))); } [Fact] public void ParseBlockDoesNotTerminateXMLProcessingInstructionAtCloseAngleUnlessPreceededByQuestionMark() { ParseBlockTest(" baz?>", new MarkupBlock( new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharactersInternal.None)), Factory.Markup(" baz?>").Accepts(AcceptedCharactersInternal.None), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharactersInternal.None)))); } [Fact] public void ParseBlockSupportsScriptTagsWithLessThanSignsInThem() { ParseBlockTest(@"", new MarkupBlock( new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharactersInternal.None)))); } [Fact] public void ParseBlockSupportsScriptTagsWithSpacedLessThanSignsInThem() { ParseBlockTest(@"", new MarkupBlock( new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharactersInternal.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(AcceptedCharactersInternal.None), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharactersInternal.None)), Factory.Markup(" Baz"), new MarkupTagBlock( Factory.MarkupTransition("")))); } [Fact] public void ParseBlockRendersLiteralTextTagIfDoubled() { ParseBlockTest("Foo Bar Baz zoop", new MarkupBlock( new MarkupTagBlock( Factory.MarkupTransition("")), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharactersInternal.None)), Factory.Markup("Foo Bar "), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharactersInternal.None)), Factory.Markup(" Baz"), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharactersInternal.None)), new MarkupTagBlock( Factory.MarkupTransition("")))); } [Fact] public void ParseBlockDoesNotConsiderPsuedoTagWithinMarkupBlock() { ParseBlockTest("", new MarkupBlock( new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharactersInternal.None)), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharactersInternal.None)), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharactersInternal.None)), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharactersInternal.None)), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharactersInternal.None)) )); } [Fact] public void ParseBlockStopsParsingMidEmptyTagIfEOFReached() { ParseBlockTest("
    Foo @if(true) {} Bar", new MarkupBlock( new MarkupTagBlock( Factory.Markup("
    ").Accepts(AcceptedCharactersInternal.None)), Factory.Markup("Foo "), new StatementBlock( Factory.CodeTransition(), Factory.Code("if(true) {}").AsStatement()), Factory.Markup(" Bar"), new MarkupTagBlock( Factory.Markup("
    ").Accepts(AcceptedCharactersInternal.None)))); } [Fact] public void ParseBlockIgnoresTagsInContentsOfScriptTag() { ParseBlockTest(@"", new MarkupBlock( new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharactersInternal.None)))); } } }