// 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.Test.Framework;
using System.Web.Razor.Text;
using System.Web.Razor.Tokenizer.Symbols;
using Xunit;
namespace System.Web.Razor.Test.Parser.Html
{
public class HtmlToCodeSwitchTest : CsHtmlMarkupParserTestBase
{
[Fact]
public void ParseBlockSwitchesWhenCharacterBeforeSwapIsNonAlphanumeric()
{
ParseBlockTest("
foo#@i
",
new MarkupBlock(
Factory.Markup("foo#"),
new ExpressionBlock(
Factory.CodeTransition(),
Factory.Code("i").AsImplicitExpression(CSharpCodeParser.DefaultKeywords).Accepts(AcceptedCharacters.NonWhiteSpace)),
Factory.Markup("
").Accepts(AcceptedCharacters.None)));
}
[Fact]
public void ParseBlockSwitchesToCodeWhenSwapCharacterEncounteredMidTag()
{
ParseBlockTest("",
new MarkupBlock(
Factory.Markup("").Accepts(AcceptedCharacters.None)));
}
[Fact]
public void ParseBlockSwitchesToCodeWhenSwapCharacterEncounteredInAttributeValue()
{
ParseBlockTest("",
new MarkupBlock(
Factory.Markup("(" bar=\"", 4, 0, 4), new LocationTagged("\"", 14, 0, 14)),
Factory.Markup(" bar=\"").With(SpanCodeGenerator.Null),
new MarkupBlock(new DynamicAttributeBlockCodeGenerator(new LocationTagged(String.Empty, 10, 0, 10), 10, 0, 10),
new ExpressionBlock(
Factory.CodeTransition(),
Factory.Code("baz")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharacters.NonWhiteSpace))),
Factory.Markup("\"").With(SpanCodeGenerator.Null)),
Factory.Markup(" />").Accepts(AcceptedCharacters.None)));
}
[Fact]
public void ParseBlockSwitchesToCodeWhenSwapCharacterEncounteredInTagContent()
{
ParseBlockTest("@bar@boz",
new MarkupBlock(
Factory.Markup(""),
new ExpressionBlock(
Factory.CodeTransition(),
Factory.Code("bar")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharacters.NonWhiteSpace)),
Factory.Markup(""),
new ExpressionBlock(
Factory.CodeTransition(),
Factory.Code("boz")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharacters.NonWhiteSpace)),
Factory.Markup("").Accepts(AcceptedCharacters.None)));
}
[Fact]
public void ParseBlockParsesCodeWithinSingleLineMarkup()
{
ParseBlockTest(@"@:Foo @Bar Baz
bork",
new MarkupBlock(
Factory.MarkupTransition(),
Factory.MetaMarkup(":", HtmlSymbolType.Colon),
Factory.Markup("Foo ").With(new SingleLineMarkupEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString)),
new ExpressionBlock(
Factory.CodeTransition(),
Factory.Code("Bar")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharacters.NonWhiteSpace)),
Factory.Markup(" Baz\r\n")
.With(new SingleLineMarkupEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString, AcceptedCharacters.None))));
}
[Fact]
public void ParseBlockSupportsCodeWithinComment()
{
ParseBlockTest("",
new MarkupBlock(
Factory.Markup("").Accepts(AcceptedCharacters.None)));
}
[Fact]
public void ParseBlockSupportsCodeWithinSGMLDeclaration()
{
ParseBlockTest("",
new MarkupBlock(
Factory.Markup("").Accepts(AcceptedCharacters.None)));
}
[Fact]
public void ParseBlockSupportsCodeWithinCDataDeclaration()
{
ParseBlockTest("",
new MarkupBlock(
Factory.Markup("").Accepts(AcceptedCharacters.None)));
}
[Fact]
public void ParseBlockSupportsCodeWithinXMLProcessingInstruction()
{
ParseBlockTest("",
new MarkupBlock(
Factory.Markup("").Accepts(AcceptedCharacters.None)));
}
[Fact]
public void ParseBlockDoesNotSwitchToCodeOnEmailAddressInText()
{
SingleSpanBlockTest("anurse@microsoft.com", BlockType.Markup, SpanKind.Markup, acceptedCharacters: AcceptedCharacters.None);
}
[Fact]
public void ParseBlockDoesNotSwitchToCodeOnEmailAddressInAttribute()
{
ParseBlockTest("Email me",
new MarkupBlock(
Factory.Markup("(" href=\"", 2, 0, 2), new LocationTagged("\"", 36, 0, 36)),
Factory.Markup(" href=\"").With(SpanCodeGenerator.Null),
Factory.Markup("mailto:anurse@microsoft.com")
.With(new LiteralAttributeCodeGenerator(new LocationTagged(String.Empty, 9, 0, 9), new LocationTagged("mailto:anurse@microsoft.com", 9, 0, 9))),
Factory.Markup("\"").With(SpanCodeGenerator.Null)),
Factory.Markup(">Email me").Accepts(AcceptedCharacters.None)));
}
[Fact]
public void ParseBlockGivesWhitespacePreceedingAtToCodeIfThereIsNoMarkupOnThatLine()
{
ParseBlockTest(@"
@foreach(var p in Products) {
- Product: @p.Name
}
",
new MarkupBlock(
Factory.Markup(" \r\n"),
new StatementBlock(
Factory.Code(" ").AsStatement(),
Factory.CodeTransition(),
Factory.Code("foreach(var p in Products) {\r\n").AsStatement(),
new MarkupBlock(
Factory.Markup(" - Product: "),
new ExpressionBlock(
Factory.CodeTransition(),
Factory.Code("p.Name")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharacters.NonWhiteSpace)),
Factory.Markup("
\r\n").Accepts(AcceptedCharacters.None)),
Factory.Code(" }\r\n").AsStatement().Accepts(AcceptedCharacters.None)),
Factory.Markup("
").Accepts(AcceptedCharacters.None)));
}
[Fact]
public void ParseDocumentGivesWhitespacePreceedingAtToCodeIfThereIsNoMarkupOnThatLine()
{
ParseDocumentTest(@"
@foreach(var p in Products) {
- Product: @p.Name
}
",
new MarkupBlock(
Factory.Markup(" \r\n"),
new StatementBlock(
Factory.Code(" ").AsStatement(),
Factory.CodeTransition(),
Factory.Code("foreach(var p in Products) {\r\n").AsStatement(),
new MarkupBlock(
Factory.Markup(" - Product: "),
new ExpressionBlock(
Factory.CodeTransition(),
Factory.Code("p.Name")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharacters.NonWhiteSpace)),
Factory.Markup("
\r\n").Accepts(AcceptedCharacters.None)),
Factory.Code(" }\r\n").AsStatement().Accepts(AcceptedCharacters.None)),
Factory.Markup("
")));
}
[Fact]
public void SectionContextGivesWhitespacePreceedingAtToCodeIfThereIsNoMarkupOnThatLine()
{
ParseDocumentTest(@"@section foo {
@foreach(var p in Products) {
- Product: @p.Name
}
}",
new MarkupBlock(
Factory.EmptyHtml(),
new SectionBlock(new SectionCodeGenerator("foo"),
Factory.CodeTransition(),
Factory.MetaCode("section foo {").AutoCompleteWith(null, atEndOfSpan: true),
new MarkupBlock(
Factory.Markup("\r\n \r\n"),
new StatementBlock(
Factory.Code(" ").AsStatement(),
Factory.CodeTransition(),
Factory.Code("foreach(var p in Products) {\r\n").AsStatement(),
new MarkupBlock(
Factory.Markup(" - Product: "),
new ExpressionBlock(
Factory.CodeTransition(),
Factory.Code("p.Name")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharacters.NonWhiteSpace)),
Factory.Markup("
\r\n").Accepts(AcceptedCharacters.None)),
Factory.Code(" }\r\n").AsStatement().Accepts(AcceptedCharacters.None)),
Factory.Markup("
\r\n")),
Factory.MetaCode("}").Accepts(AcceptedCharacters.None)),
Factory.EmptyHtml()));
}
[Fact]
public void CSharpCodeParserDoesNotAcceptLeadingOrTrailingWhitespaceInDesignMode()
{
ParseBlockTest(@"
@foreach(var p in Products) {
- Product: @p.Name
}
",
new MarkupBlock(
Factory.Markup(" \r\n "),
new StatementBlock(
Factory.CodeTransition(),
Factory.Code("foreach(var p in Products) {\r\n ").AsStatement(),
new MarkupBlock(
Factory.Markup("- Product: "),
new ExpressionBlock(
Factory.CodeTransition(),
Factory.Code("p.Name").AsImplicitExpression(CSharpCodeParser.DefaultKeywords).Accepts(AcceptedCharacters.NonWhiteSpace)),
Factory.Markup("
").Accepts(AcceptedCharacters.None)),
Factory.Code("\r\n }").AsStatement().Accepts(AcceptedCharacters.None)),
Factory.Markup("\r\n
").Accepts(AcceptedCharacters.None)),
designTimeParser: true);
}
// Tests for "@@" escape sequence:
[Fact]
public void ParseBlockTreatsTwoAtSignsAsEscapeSequence()
{
HtmlParserTestUtils.RunSingleAtEscapeTest(ParseBlockTest);
}
[Fact]
public void ParseBlockTreatsPairsOfAtSignsAsEscapeSequence()
{
HtmlParserTestUtils.RunMultiAtEscapeTest(ParseBlockTest);
}
[Fact]
public void ParseDocumentTreatsTwoAtSignsAsEscapeSequence()
{
HtmlParserTestUtils.RunSingleAtEscapeTest(ParseDocumentTest, lastSpanAcceptedCharacters: AcceptedCharacters.Any);
}
[Fact]
public void ParseDocumentTreatsPairsOfAtSignsAsEscapeSequence()
{
HtmlParserTestUtils.RunMultiAtEscapeTest(ParseDocumentTest, lastSpanAcceptedCharacters: AcceptedCharacters.Any);
}
[Fact]
public void SectionBodyTreatsTwoAtSignsAsEscapeSequence()
{
ParseDocumentTest("@section Foo { @@bar }",
new MarkupBlock(
Factory.EmptyHtml(),
new SectionBlock(new SectionCodeGenerator("Foo"),
Factory.CodeTransition(),
Factory.MetaCode("section Foo {").AutoCompleteWith(null, atEndOfSpan: true),
new MarkupBlock(
Factory.Markup(" "),
Factory.Markup("@").Hidden(),
Factory.Markup("@bar ")),
Factory.MetaCode("}").Accepts(AcceptedCharacters.None)),
Factory.EmptyHtml()));
}
[Fact]
public void SectionBodyTreatsPairsOfAtSignsAsEscapeSequence()
{
ParseDocumentTest("@section Foo { @@@@@bar }",
new MarkupBlock(
Factory.EmptyHtml(),
new SectionBlock(new SectionCodeGenerator("Foo"),
Factory.CodeTransition(),
Factory.MetaCode("section Foo {").AutoCompleteWith(null, atEndOfSpan: true),
new MarkupBlock(
Factory.Markup(" "),
Factory.Markup("@").Hidden(),
Factory.Markup("@"),
Factory.Markup("@").Hidden(),
Factory.Markup("@"),
new ExpressionBlock(
Factory.CodeTransition(),
Factory.Code("bar")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharacters.NonWhiteSpace)),
Factory.Markup(" ")),
Factory.MetaCode("}").Accepts(AcceptedCharacters.None)),
Factory.EmptyHtml()));
}
}
}