// 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 System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Language.Legacy; using Microsoft.AspNetCore.Razor.Language.Intermediate; using Xunit; using static Microsoft.AspNetCore.Razor.Language.Intermediate.RazorIRAssert; namespace Microsoft.AspNetCore.Razor.Language { public class DefaultRazorIRLoweringPhaseIntegrationTest { [Fact] public void Lower_EmptyDocument_AddsGlobalUsingsAndNamespace() { // Arrange var codeDocument = TestRazorCodeDocument.CreateEmpty(); // Act var irDocument = Lower(codeDocument); // Assert Children(irDocument, n => Checksum(n), n => Using("System", n), n => Using("System.Threading.Tasks", n)); } [Fact] public void Lower_SetsOptions() { // Arrange var codeDocument = TestRazorCodeDocument.CreateEmpty(); // Act var irDocument = Lower(codeDocument); // Assert Assert.NotNull(irDocument.Options); Assert.Same(codeDocument.GetSyntaxTree().Options, irDocument.Options); } [Fact] public void Lower_HelloWorld() { // Arrange var codeDocument = TestRazorCodeDocument.Create("Hello, World!"); // Act var irDocument = Lower(codeDocument); // Assert Children(irDocument, n => Checksum(n), n => Assert.IsType(n), n => Assert.IsType(n), n => Html("Hello, World!", n)); } [Fact] public void Lower_HtmlWithDataDashAttributes() { // Arrange var codeDocument = TestRazorCodeDocument.Create(@" "); // Act var irDocument = Lower(codeDocument); // Assert Children(irDocument, n => Checksum(n), n => Assert.IsType(n), n => Assert.IsType(n), n => Html( @" CSharpExpression("Hello", n), n => Html(@""" /> ", n)); } [Fact] public void Lower_HtmlWithConditionalAttributes() { // Arrange var codeDocument = TestRazorCodeDocument.Create(@" "); // Act var irDocument = Lower(codeDocument); // Assert Children(irDocument, n => Checksum(n), n => Assert.IsType(n), n => Assert.IsType(n), n => Html( @" ConditionalAttribute( prefix: " val=\"", name: "val", suffix: "\"", node: n, valueValidators: new Action[] { value => CSharpAttributeValue(string.Empty, "Hello", value), value => LiteralAttributeValue(" ", "World", value) }), n => Html(@" /> ", n)); } [Fact] public void Lower_WithFunctions() { // Arrange var codeDocument = TestRazorCodeDocument.Create(@"@functions { public int Foo { get; set; }}"); // Act var irDocument = Lower(codeDocument); // Assert Children(irDocument, n => Checksum(n), n => Assert.IsType(n), n => Assert.IsType(n), n => Directive( "functions", n, c => Assert.IsType(c))); } [Fact] public void Lower_WithUsing() { // Arrange var codeDocument = TestRazorCodeDocument.Create(@"@using System"); var expectedSourceLocation = new SourceSpan(codeDocument.Source.FileName, 1, 0, 1, 12); // Act var irDocument = Lower(codeDocument); // Assert Children(irDocument, n => Checksum(n), n => { Using("System", n); Assert.Equal(expectedSourceLocation, n.Source); }, n => Using(typeof(Task).Namespace, n)); } [Fact] public void Lower_TagHelpers() { // Arrange var codeDocument = TestRazorCodeDocument.Create(@"@addTagHelper *, TestAssembly "); var tagHelpers = new[] { CreateTagHelperDescriptor( tagName: "span", typeName: "SpanTagHelper", assemblyName: "TestAssembly") }; // Act var irDocument = Lower(codeDocument, tagHelpers: tagHelpers); // Assert Children(irDocument, n => Checksum(n), n => Using("System", n), n => Using(typeof(Task).Namespace, n), n => Directive( SyntaxConstants.CSharp.AddTagHelperKeyword, n, v => DirectiveToken(DirectiveTokenKind.String, "*, TestAssembly", v)), n => TagHelperFieldDeclaration(n, "SpanTagHelper"), n => { var tagHelperNode = Assert.IsType(n); Children( tagHelperNode, c => TagHelperStructure("span", TagMode.StartTagAndEndTag, c), c => Assert.IsType(c), c => TagHelperHtmlAttribute( "val", HtmlAttributeValueStyle.DoubleQuotes, c, v => CSharpAttributeValue(string.Empty, "Hello", v), v => LiteralAttributeValue(" ", "World", v)), c => Assert.IsType(c)); }); } [Fact] public void Lower_TagHelpers_WithPrefix() { // Arrange var codeDocument = TestRazorCodeDocument.Create(@"@addTagHelper *, TestAssembly @tagHelperPrefix cool: "); var tagHelpers = new[] { CreateTagHelperDescriptor( tagName: "span", typeName: "SpanTagHelper", assemblyName: "TestAssembly") }; // Act var irDocument = Lower(codeDocument, tagHelpers: tagHelpers); // Assert Children(irDocument, n => Checksum(n), n => Using("System", n), n => Using(typeof(Task).Namespace, n), 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 => TagHelperFieldDeclaration(n, "SpanTagHelper"), n => { var tagHelperNode = Assert.IsType(n); Children( tagHelperNode, c => TagHelperStructure("span", TagMode.StartTagAndEndTag, c), // Note: this is span not cool:span c => Assert.IsType(c), c => TagHelperHtmlAttribute( "val", HtmlAttributeValueStyle.DoubleQuotes, c, v => CSharpAttributeValue(string.Empty, "Hello", v), v => LiteralAttributeValue(" ", "World", v)), c => Assert.IsType(c)); }); } [Fact] public void Lower_TagHelper_InSection() { // Arrange var codeDocument = TestRazorCodeDocument.Create(@"@addTagHelper *, TestAssembly @section test { }"); var tagHelpers = new[] { CreateTagHelperDescriptor( tagName: "span", typeName: "SpanTagHelper", assemblyName: "TestAssembly") }; // Act var irDocument = Lower(codeDocument, tagHelpers: tagHelpers); // Assert Children( irDocument, n => Checksum(n), n => Using("System", n), n => Using(typeof(Task).Namespace, n), 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 => { var tagHelperNode = Assert.IsType(c1); Children( tagHelperNode, c2 => TagHelperStructure("span", TagMode.StartTagAndEndTag, c2), c2 => Assert.IsType(c2), c2 => TagHelperHtmlAttribute( "val", HtmlAttributeValueStyle.DoubleQuotes, c2, v => CSharpAttributeValue(string.Empty, "Hello", v), v => LiteralAttributeValue(" ", "World", v)), c2 => Assert.IsType(c2)); }, c1 => Html(Environment.NewLine, c1)), n => TagHelperFieldDeclaration(n, "SpanTagHelper")); } [Fact] public void Lower_TagHelpersWithBoundAttribute() { // Arrange var codeDocument = TestRazorCodeDocument.Create(@"@addTagHelper *, TestAssembly "); var tagHelpers = new[] { CreateTagHelperDescriptor( tagName: "input", typeName: "InputTagHelper", assemblyName: "TestAssembly", attributes: new Action[] { builder => builder .Name("bound") .PropertyName("FooProp") .TypeName("System.String"), }) }; // Act var irDocument = Lower(codeDocument, tagHelpers: tagHelpers); // Assert Children( irDocument, n => Checksum(n), n => Using("System", n), n => Using(typeof(Task).Namespace, n), n => Directive( SyntaxConstants.CSharp.AddTagHelperKeyword, n, v => DirectiveToken(DirectiveTokenKind.String, "*, TestAssembly", v)), n => TagHelperFieldDeclaration(n, "InputTagHelper"), n => { var tagHelperNode = Assert.IsType(n); Children(tagHelperNode, c => TagHelperStructure("input", TagMode.SelfClosing, c), c => Assert.IsType(c), c => SetTagHelperProperty( "bound", "FooProp", HtmlAttributeValueStyle.SingleQuotes, c, v => Html("foo", v)), c => Assert.IsType(c)); }); } [Fact] public void Lower_WithImports_Using() { // Arrange var source = TestRazorSourceDocument.Create("

Hi!

"); var imports = new[] { TestRazorSourceDocument.Create("@using System.Globalization"), TestRazorSourceDocument.Create("@using System.Text"), }; var codeDocument = TestRazorCodeDocument.Create(source, imports); // Act var irDocument = Lower(codeDocument); // Assert Children( irDocument, n => Checksum(n), n => Using("System", n), n => Using(typeof(Task).Namespace, n), n => Using("System.Globalization", n), n => Using("System.Text", n), n => Html("

Hi!

", n)); } [Fact] public void Lower_WithImports_Directive() { // Arrange var source = TestRazorSourceDocument.Create("

Hi!

"); var imports = new[] { TestRazorSourceDocument.Create("@test value1"), TestRazorSourceDocument.Create("@test value2"), }; var codeDocument = TestRazorCodeDocument.Create(source, imports); // Act var irDocument = Lower(codeDocument, b => { b.AddDirective(DirectiveDescriptorBuilder.Create("test").AddMember().Build()); }); // Assert Children( irDocument, n => Checksum(n), n => Using("System", n), n => Using(typeof(Task).Namespace, n), n => Directive("test", n, c => DirectiveToken(DirectiveTokenKind.Member, "value1", c)), n => Directive("test", n, c => DirectiveToken(DirectiveTokenKind.Member, "value2", c)), n => Html("

Hi!

", n)); } [Fact] public void Lower_WithImports_IgnoresBlockDirective() { // Arrange var source = TestRazorSourceDocument.Create("

Hi!

"); var imports = new[] { TestRazorSourceDocument.Create("@block token { }"), }; var codeDocument = TestRazorCodeDocument.Create(source, imports); // Act var irDocument = Lower(codeDocument, b => { b.AddDirective(DirectiveDescriptorBuilder.CreateRazorBlock("block").AddMember().Build()); }); // Assert Children( irDocument, n => Checksum(n), n => Using("System", n), n => Using(typeof(Task).Namespace, n), n => Html("

Hi!

", n)); } private DocumentIRNode Lower( RazorCodeDocument codeDocument, Action builder = null, IEnumerable tagHelpers = null) { tagHelpers = tagHelpers ?? new TagHelperDescriptor[0]; var engine = RazorEngine.Create(b => { builder?.Invoke(b); b.AddTagHelpers(tagHelpers); }); for (var i = 0; i < engine.Phases.Count; i++) { var phase = engine.Phases[i]; phase.Execute(codeDocument); if (phase is IRazorIRLoweringPhase) { break; } } var irDocument = codeDocument.GetIRDocument(); Assert.NotNull(irDocument); return irDocument; } private static TagHelperDescriptor CreateTagHelperDescriptor( string tagName, string typeName, string assemblyName, IEnumerable> attributes = null) { var builder = TagHelperDescriptorBuilder.Create(typeName, assemblyName); if (attributes != null) { foreach (var attributeBuilder in attributes) { builder.BindAttribute(attributeBuilder); } } builder.TagMatchingRule(ruleBuilder => ruleBuilder.RequireTagName(tagName)); var descriptor = builder.Build(); return descriptor; } } }