aspnetcore/test/Microsoft.AspNetCore.Razor..../DefaultRazorIntermediateNod...

543 lines
18 KiB
C#

// 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 System.Collections.Generic;
using Microsoft.AspNetCore.Razor.Language.Extensions;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
using Microsoft.AspNetCore.Razor.Language.Legacy;
using Moq;
using Xunit;
using static Microsoft.AspNetCore.Razor.Language.Intermediate.IntermediateNodeAssert;
namespace Microsoft.AspNetCore.Razor.Language
{
public class DefaultRazorIntermediateNodeLoweringPhaseIntegrationTest
{
[Fact]
public void Lower_SetsOptions_Defaults()
{
// Arrange
var codeDocument = TestRazorCodeDocument.CreateEmpty();
// Act
var documentNode = Lower(codeDocument);
// Assert
Assert.NotNull(documentNode.Options);
Assert.False(documentNode.Options.DesignTime);
Assert.Equal(4, documentNode.Options.IndentSize);
Assert.False(documentNode.Options.IndentWithTabs);
}
[Fact]
public void Lower_SetsOptions_RunsConfigureCallbacks()
{
// Arrange
var codeDocument = TestRazorCodeDocument.CreateEmpty();
var callback = new Mock<IConfigureRazorCodeGenerationOptionsFeature>();
callback
.Setup(c => c.Configure(It.IsAny<RazorCodeGenerationOptionsBuilder>()))
.Callback<RazorCodeGenerationOptionsBuilder>(o =>
{
o.IndentSize = 17;
o.IndentWithTabs = true;
o.SuppressChecksum = true;
});
// Act
var documentNode = Lower(
codeDocument,
builder: b =>
{
b.Features.Add(callback.Object);
},
designTime: true);
// Assert
Assert.NotNull(documentNode.Options);
Assert.True(documentNode.Options.DesignTime);
Assert.Equal(17, documentNode.Options.IndentSize);
Assert.True(documentNode.Options.IndentWithTabs);
Assert.True(documentNode.Options.SuppressChecksum);
}
[Fact]
public void Lower_HelloWorld()
{
// Arrange
var codeDocument = TestRazorCodeDocument.Create("Hello, World!");
// Act
var documentNode = Lower(codeDocument);
// Assert
Children(documentNode,
n => Html("Hello, World!", n));
}
[Fact]
public void Lower_HtmlWithDataDashAttributes()
{
// Arrange
var codeDocument = TestRazorCodeDocument.Create(@"
<html>
<body>
<span data-val=""@Hello"" />
</body>
</html>");
// Act
var documentNode = Lower(codeDocument);
// Assert
Children(documentNode,
n => Html(
@"
<html>
<body>
<span data-val=""", n),
n => CSharpExpression("Hello", n),
n => Html(@""" />
</body>
</html>", n));
}
[Fact]
public void Lower_HtmlWithConditionalAttributes()
{
// Arrange
var codeDocument = TestRazorCodeDocument.Create(@"
<html>
<body>
<span val=""@Hello World"" />
</body>
</html>");
// Act
var documentNode = Lower(codeDocument);
// Assert
Children(documentNode,
n => Html(
@"
<html>
<body>
<span", n),
n => ConditionalAttribute(
prefix: " val=\"",
name: "val",
suffix: "\"",
node: n,
valueValidators: new Action<IntermediateNode>[]
{
value => CSharpExpressionAttributeValue(string.Empty, "Hello", value),
value => LiteralAttributeValue(" ", "World", value)
}),
n => Html(@" />
</body>
</html>", n));
}
[Fact]
public void Lower_WithFunctions()
{
// Arrange
var codeDocument = TestRazorCodeDocument.Create(@"@functions { public int Foo { get; set; }}");
// Act
var documentNode = Lower(codeDocument);
// Assert
Children(documentNode,
n => Directive(
"functions",
n,
c => Assert.IsType<CSharpCodeIntermediateNode>(c)));
}
[Fact]
public void Lower_WithUsing()
{
// Arrange
var codeDocument = TestRazorCodeDocument.Create(@"@using System");
var expectedSourceLocation = new SourceSpan(codeDocument.Source.FilePath, 1, 0, 1, 12);
// Act
var documentNode = Lower(codeDocument);
// Assert
Children(documentNode,
n =>
{
Using("System", n);
Assert.Equal(expectedSourceLocation, n.Source);
});
}
[Fact]
public void Lower_TagHelpers()
{
// Arrange
var codeDocument = TestRazorCodeDocument.Create(@"@addTagHelper *, TestAssembly
<span val=""@Hello World""></span>");
var tagHelpers = new[]
{
CreateTagHelperDescriptor(
tagName: "span",
typeName: "SpanTagHelper",
assemblyName: "TestAssembly")
};
// Act
var documentNode = Lower(codeDocument, tagHelpers: tagHelpers);
// Assert
Children(documentNode,
n => Directive(
SyntaxConstants.CSharp.AddTagHelperKeyword,
n,
v => DirectiveToken(DirectiveTokenKind.String, "*, TestAssembly", v)),
n => TagHelper(
"span",
TagMode.StartTagAndEndTag,
tagHelpers,
n,
c => Assert.IsType<TagHelperBodyIntermediateNode>(c),
c => TagHelperHtmlAttribute(
"val",
AttributeStructure.DoubleQuotes,
c,
v => CSharpExpressionAttributeValue(string.Empty, "Hello", v),
v => LiteralAttributeValue(" ", "World", v))));
}
[Fact]
public void Lower_TagHelpers_WithPrefix()
{
// Arrange
var codeDocument = TestRazorCodeDocument.Create(@"@addTagHelper *, TestAssembly
@tagHelperPrefix cool:
<cool:span val=""@Hello World""></cool:span>");
var tagHelpers = new[]
{
CreateTagHelperDescriptor(
tagName: "span",
typeName: "SpanTagHelper",
assemblyName: "TestAssembly")
};
// Act
var documentNode = Lower(codeDocument, tagHelpers: tagHelpers);
// Assert
Children(documentNode,
n => Directive(
SyntaxConstants.CSharp.AddTagHelperKeyword,
n,
v => DirectiveToken(DirectiveTokenKind.String, "*, TestAssembly", v)),
n => Directive(
SyntaxConstants.CSharp.TagHelperPrefixKeyword,
n,
v => DirectiveToken(DirectiveTokenKind.String, "cool:", v)),
n => TagHelper(
"span", // Note: this is span not cool:span
TagMode.StartTagAndEndTag,
tagHelpers,
n,
c => Assert.IsType<TagHelperBodyIntermediateNode>(c),
c => TagHelperHtmlAttribute(
"val",
AttributeStructure.DoubleQuotes,
c,
v => CSharpExpressionAttributeValue(string.Empty, "Hello", v),
v => LiteralAttributeValue(" ", "World", v))));
}
[Fact]
public void Lower_TagHelper_InSection()
{
// Arrange
var codeDocument = TestRazorCodeDocument.Create(@"@addTagHelper *, TestAssembly
@section test {
<span val=""@Hello World""></span>
}");
var tagHelpers = new[]
{
CreateTagHelperDescriptor(
tagName: "span",
typeName: "SpanTagHelper",
assemblyName: "TestAssembly")
};
// Act
var documentNode = Lower(codeDocument, tagHelpers: tagHelpers);
// Assert
Children(
documentNode,
n => Directive(
SyntaxConstants.CSharp.AddTagHelperKeyword,
n,
v => DirectiveToken(DirectiveTokenKind.String, "*, TestAssembly", v)),
n => Directive(
"section",
n,
c1 => DirectiveToken(DirectiveTokenKind.Member, "test", c1),
c1 => Html(Environment.NewLine, c1),
c1 => TagHelper(
"span",
TagMode.StartTagAndEndTag,
tagHelpers,
c1,
c2 => Assert.IsType<TagHelperBodyIntermediateNode>(c2),
c2 => TagHelperHtmlAttribute(
"val",
AttributeStructure.DoubleQuotes,
c2,
v => CSharpExpressionAttributeValue(string.Empty, "Hello", v),
v => LiteralAttributeValue(" ", "World", v))),
c1 => Html(Environment.NewLine, c1)));
}
[Fact]
public void Lower_TagHelpersWithBoundAttribute()
{
// Arrange
var codeDocument = TestRazorCodeDocument.Create(@"@addTagHelper *, TestAssembly
<input bound='foo' />");
var tagHelpers = new[]
{
CreateTagHelperDescriptor(
tagName: "input",
typeName: "InputTagHelper",
assemblyName: "TestAssembly",
attributes: new Action<BoundAttributeDescriptorBuilder>[]
{
builder => builder
.Name("bound")
.PropertyName("FooProp")
.TypeName("System.String"),
})
};
// Act
var documentNode = Lower(codeDocument, tagHelpers: tagHelpers);
// Assert
Children(
documentNode,
n => Directive(
SyntaxConstants.CSharp.AddTagHelperKeyword,
n,
v => DirectiveToken(DirectiveTokenKind.String, "*, TestAssembly", v)),
n => TagHelper(
"input",
TagMode.SelfClosing,
tagHelpers,
n,
c => Assert.IsType<TagHelperBodyIntermediateNode>(c),
c => SetTagHelperProperty(
"bound",
"FooProp",
AttributeStructure.SingleQuotes,
c,
v => Html("foo", v))));
}
[Fact]
public void Lower_WithImports_Using()
{
// Arrange
var source = TestRazorSourceDocument.Create(@"@using System.Threading.Tasks
<p>Hi!</p>");
var imports = new[]
{
TestRazorSourceDocument.Create("@using System.Globalization"),
TestRazorSourceDocument.Create("@using System.Text"),
};
var codeDocument = TestRazorCodeDocument.Create(source, imports);
// Act
var documentNode = Lower(codeDocument);
// Assert
Children(
documentNode,
n => Using("System.Globalization", n),
n => Using("System.Text", n),
n => Using("System.Threading.Tasks", n),
n => Html("<p>Hi!</p>", n));
}
[Fact]
public void Lower_WithImports_AllowsIdenticalNamespacesInPrimaryDocument()
{
// Arrange
var source = TestRazorSourceDocument.Create(@"@using System.Threading.Tasks
@using System.Threading.Tasks");
var imports = new[]
{
TestRazorSourceDocument.Create("@using System.Threading.Tasks"),
};
var codeDocument = TestRazorCodeDocument.Create(source, imports);
// Act
var documentNode = Lower(codeDocument);
// Assert
Children(
documentNode,
n => Using("System.Threading.Tasks", n),
n => Using("System.Threading.Tasks", n));
}
[Fact]
public void Lower_WithMultipleImports_SingleLineFileScopedSinglyOccurring()
{
// Arrange
var source = TestRazorSourceDocument.Create("<p>Hi!</p>");
var imports = new[]
{
TestRazorSourceDocument.Create("@test value1"),
TestRazorSourceDocument.Create("@test value2"),
};
var codeDocument = TestRazorCodeDocument.Create(source, imports);
// Act
var documentNode = Lower(codeDocument, b =>
{
b.AddDirective(DirectiveDescriptor.CreateDirective(
"test",
DirectiveKind.SingleLine,
builder =>
{
builder.AddMemberToken();
builder.Usage = DirectiveUsage.FileScopedSinglyOccurring;
}));
});
// Assert
Children(
documentNode,
n => Directive("test", n, c => DirectiveToken(DirectiveTokenKind.Member, "value2", c)),
n => Html("<p>Hi!</p>", n));
}
[Fact]
public void Lower_WithImports_IgnoresBlockDirective()
{
// Arrange
var source = TestRazorSourceDocument.Create("<p>Hi!</p>");
var imports = new[]
{
TestRazorSourceDocument.Create("@block token { }"),
};
var codeDocument = TestRazorCodeDocument.Create(source, imports);
// Act
var documentNode = Lower(codeDocument, b =>
{
b.AddDirective(DirectiveDescriptor.CreateDirective("block", DirectiveKind.RazorBlock, d => d.AddMemberToken()));
});
// Assert
Children(
documentNode,
n => Html("<p>Hi!</p>", n));
}
private DocumentIntermediateNode Lower(
RazorCodeDocument codeDocument,
Action<RazorProjectEngineBuilder> builder = null,
IEnumerable<TagHelperDescriptor> tagHelpers = null,
bool designTime = false)
{
tagHelpers = tagHelpers ?? new TagHelperDescriptor[0];
Action<RazorProjectEngineBuilder> configureEngine = b =>
{
builder?.Invoke(b);
FunctionsDirective.Register(b);
SectionDirective.Register(b);
b.AddTagHelpers(tagHelpers);
b.Features.Add(new DesignTimeOptionsFeature(designTime));
};
var projectEngine = RazorProjectEngine.Create(configureEngine);
for (var i = 0; i < projectEngine.Phases.Count; i++)
{
var phase = projectEngine.Phases[i];
phase.Execute(codeDocument);
if (phase is IRazorIntermediateNodeLoweringPhase)
{
break;
}
}
var documentNode = codeDocument.GetDocumentIntermediateNode();
Assert.NotNull(documentNode);
return documentNode;
}
private static TagHelperDescriptor CreateTagHelperDescriptor(
string tagName,
string typeName,
string assemblyName,
IEnumerable<Action<BoundAttributeDescriptorBuilder>> attributes = null)
{
var builder = TagHelperDescriptorBuilder.Create(typeName, assemblyName);
builder.TypeName(typeName);
if (attributes != null)
{
foreach (var attributeBuilder in attributes)
{
builder.BoundAttributeDescriptor(attributeBuilder);
}
}
builder.TagMatchingRuleDescriptor(ruleBuilder => ruleBuilder.RequireTagName(tagName));
var descriptor = builder.Build();
return descriptor;
}
private class DesignTimeOptionsFeature : IConfigureRazorParserOptionsFeature, IConfigureRazorCodeGenerationOptionsFeature
{
private bool _designTime;
public DesignTimeOptionsFeature(bool designTime)
{
_designTime = designTime;
}
public int Order { get; }
public RazorEngine Engine { get; set; }
public void Configure(RazorParserOptionsBuilder options)
{
options.SetDesignTime(_designTime);
}
public void Configure(RazorCodeGenerationOptionsBuilder options)
{
options.SetDesignTime(_designTime);
}
}
}
}