// 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.Evolution.Legacy { public class HtmlBlockTest : CsHtmlMarkupParserTestBase { [Fact] public void ParseBlockHandlesOpenAngleAtEof() { ParseDocumentTest("@{" + Environment.NewLine + "<", new MarkupBlock( Factory.EmptyHtml(), new StatementBlock( Factory.CodeTransition(), Factory.MetaCode("{").Accepts(AcceptedCharacters.None), Factory.Code(Environment.NewLine) .AsStatement() .AutoCompleteWith("}"), new MarkupBlock( new MarkupTagBlock( Factory.Markup("<"))))), new RazorError( LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF( LegacyResources.BlockName_Code, "}", "{"), new SourceLocation(1, 0, 1), length: 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(Environment.NewLine) .AsStatement() .AutoCompleteWith("}"), new MarkupBlock( new MarkupTagBlock( Factory.Markup("<" + Environment.NewLine)) ), new MarkupBlock( new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharacters.None)) ), Factory.EmptyCSharp().AsStatement() ) ), designTime: true, expectedErrors: new[] { new RazorError( LegacyResources.FormatParseError_UnexpectedEndTag("html"), new SourceLocation(5 + Environment.NewLine.Length * 2, 2, 2), length: 4), new RazorError( LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF("code", "}", "{"), new SourceLocation(1, 0, 1), length: 1) }); } [Fact] public void TagWithoutCloseAngleDoesNotTerminateBlock() { ParseBlockTest("< " + Environment.NewLine + " ", new MarkupBlock( new MarkupTagBlock( Factory.Markup($"< {Environment.NewLine} "))), designTime: true, expectedErrors: new RazorError( LegacyResources.FormatParseError_UnfinishedTag(string.Empty), new SourceLocation(1, 0, 1), length: 1)); } [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" + Environment.NewLine) .With(new SpanEditHandler(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 SpanEditHandler(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(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(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(SpanChunkGenerator.Null), Factory.Markup("zork").With(new LiteralAttributeChunkGenerator(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(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(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(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(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(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(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(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(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( LegacyResources.FormatParseError_MissingEndTag("foo"), new SourceLocation(1, 0, 1), length: 3)); } [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)))); } public static TheoryData HtmlCommentSupportsMultipleDashesData { get { var factory = new SpanFactory(); return new TheoryData { { "
    ", new MarkupBlock( new MarkupTagBlock( factory.Markup("
    ").Accepts(AcceptedCharacters.None)), factory.Markup("").Accepts(AcceptedCharacters.None), new MarkupTagBlock( factory.Markup("
    ").Accepts(AcceptedCharacters.None))) }, { "
    ", new MarkupBlock( new MarkupTagBlock( factory.Markup("
    ").Accepts(AcceptedCharacters.None)), factory.Markup("").Accepts(AcceptedCharacters.None), new MarkupTagBlock( factory.Markup("
    ").Accepts(AcceptedCharacters.None))) }, { "
    ", new MarkupBlock( new MarkupTagBlock( factory.Markup("
    ").Accepts(AcceptedCharacters.None)), factory.Markup("").Accepts(AcceptedCharacters.None), new MarkupTagBlock( factory.Markup("
    ").Accepts(AcceptedCharacters.None))) }, { "
    ", new MarkupBlock( new MarkupTagBlock( factory.Markup("
    ").Accepts(AcceptedCharacters.None)), factory.Markup("").Accepts(AcceptedCharacters.None), new MarkupTagBlock( factory.Markup("
    ").Accepts(AcceptedCharacters.None))) }, }; } } [Theory] [MemberData(nameof(HtmlCommentSupportsMultipleDashesData))] public void HtmlCommentSupportsMultipleDashes(string documentContent, object expectedOutput) { FixupSpans = true; ParseBlockTest(documentContent, (MarkupBlock)expectedOutput); } [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( LegacyResources.FormatParseError_MissingEndTag("foo"), new SourceLocation(1, 0, 1), length: 3)); } [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("")))); } [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("")))); } [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)))); } } }