// 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.Legacy; using Microsoft.AspNetCore.Razor.Language.Intermediate; using Xunit; using static Microsoft.AspNetCore.Razor.Language.Intermediate.IntermediateNodeAssert; using Moq; using Microsoft.AspNetCore.Razor.Language.Extensions; 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(); callback .Setup(c => c.Configure(It.IsAny())) .Callback(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(@" "); // Act var documentNode = Lower(codeDocument); // Assert Children(documentNode, n => Html( @" CSharpExpression("Hello", n), n => Html(@""" /> ", n)); } [Fact] public void Lower_HtmlWithConditionalAttributes() { // Arrange var codeDocument = TestRazorCodeDocument.Create(@" "); // Act var documentNode = Lower(codeDocument); // Assert Children(documentNode, n => Html( @" ConditionalAttribute( prefix: " val=\"", name: "val", suffix: "\"", node: n, valueValidators: new Action[] { value => CSharpExpressionAttributeValue(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 documentNode = Lower(codeDocument); // Assert Children(documentNode, 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.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 "); 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(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: "); 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(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 { }"); 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(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 "); var tagHelpers = new[] { CreateTagHelperDescriptor( tagName: "input", typeName: "InputTagHelper", assemblyName: "TestAssembly", attributes: new Action[] { 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(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

Hi!

"); 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("

Hi!

", n)); } [Fact] public void Lower_WithMultipleImports_SingleLineFileScopedSinglyOccurring() { // 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 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("

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 documentNode = Lower(codeDocument, b => { b.AddDirective(DirectiveDescriptor.CreateDirective("block", DirectiveKind.RazorBlock, d => d.AddMemberToken())); }); // Assert Children( documentNode, n => Html("

Hi!

", n)); } private DocumentIntermediateNode Lower( RazorCodeDocument codeDocument, Action builder = null, IEnumerable tagHelpers = null, bool designTime = false) { tagHelpers = tagHelpers ?? new TagHelperDescriptor[0]; Action configureEngine = b => { builder?.Invoke(b); FunctionsDirective.Register(b); SectionDirective.Register(b); b.AddTagHelpers(tagHelpers); }; var engine = designTime ? RazorEngine.CreateDesignTime(configureEngine) : RazorEngine.Create(configureEngine); for (var i = 0; i < engine.Phases.Count; i++) { var phase = engine.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> 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; } } }