|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.Extensions;
public class DefaultTagHelperTargetExtensionTest : RazorProjectEngineTestBase
{
protected override RazorLanguageVersion Version => RazorLanguageVersion.Latest;
private static readonly TagHelperDescriptor StringPropertyTagHelper = CreateTagHelperDescriptor(
tagName: "input",
typeName: "InputTagHelper",
assemblyName: "TestAssembly",
attributes: new Action<BoundAttributeDescriptorBuilder>[]
{
builder => builder
.Name("bound")
.PropertyName("StringProp")
.TypeName("System.String"),
});
private static readonly TagHelperDescriptor IntPropertyTagHelper = CreateTagHelperDescriptor(
tagName: "input",
typeName: "InputTagHelper",
assemblyName: "TestAssembly",
attributes: new Action<BoundAttributeDescriptorBuilder>[]
{
builder => builder
.Name("bound")
.PropertyName("IntProp")
.TypeName("System.Int32"),
});
private static readonly TagHelperDescriptor IntIndexerTagHelper = CreateTagHelperDescriptor(
tagName: "input",
typeName: "InputTagHelper",
assemblyName: "TestAssembly",
attributes: new Action<BoundAttributeDescriptorBuilder>[]
{
builder => builder
.Name("bound")
.PropertyName("IntIndexer")
.TypeName("System.Collections.Generic.Dictionary<System.String, System.Int32>")
.AsDictionary("foo-", "System.Int32"),
});
private static readonly SourceSpan Span = new SourceSpan("test.cshtml", 15, 2, 5, 2);
[Fact]
public void WriteTagHelperBody_DesignTime_WritesChildren()
{
// Arrange
var extension = new DefaultTagHelperTargetExtension();
using var context = TestCodeRenderingContext.CreateDesignTime();
var tagHelperNode = new TagHelperIntermediateNode()
{
TagMode = 0,
TagName = "Test"
};
var node = new DefaultTagHelperBodyIntermediateNode()
{
Children =
{
new CSharpExpressionIntermediateNode(),
}
};
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act
extension.WriteTagHelperBody(context, node);
// Assert
var csharp = context.CodeWriter.GetText().ToString();
Assert.Equal(
@"Render Children
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteTagHelperBody_Runtime_RendersCorrectly_UsesTagNameAndModeFromContext()
{
// Arrange
var extension = new DefaultTagHelperTargetExtension();
using var context = TestCodeRenderingContext.CreateRuntime();
var tagHelperNode = new TagHelperIntermediateNode()
{
TagMode = 0,
TagName = "Test"
};
var node = new DefaultTagHelperBodyIntermediateNode()
{
Children =
{
new CSharpExpressionIntermediateNode(),
},
TagMode = TagMode.SelfClosing,
TagName = "p",
};
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act
extension.WriteTagHelperBody(context, node);
// Assert
var csharp = context.CodeWriter.GetText().ToString();
Assert.Equal(
@"__tagHelperExecutionContext = __tagHelperScopeManager.Begin(""p"", global::Microsoft.AspNetCore.Razor.TagHelpers.TagMode.SelfClosing, ""test"", async() => {
Render Children
}
);
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteTagHelperCreate_DesignTime_RendersCorrectly_UsesSpecifiedTagHelperType()
{
// Arrange
var extension = new DefaultTagHelperTargetExtension();
using var context = TestCodeRenderingContext.CreateDesignTime();
var tagHelperNode = new TagHelperIntermediateNode()
{
TagMode = 0,
TagName = "Test"
};
var node = new DefaultTagHelperCreateIntermediateNode()
{
FieldName = "__TestNamespace_MyTagHelper",
TypeName = "TestNamespace.MyTagHelper",
};
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act
extension.WriteTagHelperCreate(context, node);
// Assert
var csharp = context.CodeWriter.GetText().ToString();
Assert.Equal(
@"__TestNamespace_MyTagHelper = CreateTagHelper<global::TestNamespace.MyTagHelper>();
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteTagHelperCreate_Runtime_RendersCorrectly_UsesSpecifiedTagHelperType()
{
// Arrange
var extension = new DefaultTagHelperTargetExtension();
using var context = TestCodeRenderingContext.CreateRuntime();
var tagHelperNode = new TagHelperIntermediateNode()
{
TagMode = 0,
TagName = "Test"
};
var node = new DefaultTagHelperCreateIntermediateNode()
{
FieldName = "__TestNamespace_MyTagHelper",
TypeName = "TestNamespace.MyTagHelper",
};
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act
extension.WriteTagHelperCreate(context, node);
// Assert
var csharp = context.CodeWriter.GetText().ToString();
Assert.Equal(
@"__TestNamespace_MyTagHelper = CreateTagHelper<global::TestNamespace.MyTagHelper>();
__tagHelperExecutionContext.Add(__TestNamespace_MyTagHelper);
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteTagHelperExecute_DesignTime_RendersAsyncCode()
{
// Arrange
var extension = new DefaultTagHelperTargetExtension();
using var context = TestCodeRenderingContext.CreateDesignTime();
var tagHelperNode = new TagHelperIntermediateNode()
{
TagMode = 0,
TagName = "Test"
};
var node = new DefaultTagHelperExecuteIntermediateNode();
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act
extension.WriteTagHelperExecute(context, node);
// Assert
var csharp = context.CodeWriter.GetText().ToString();
Assert.Equal(
@"await __tagHelperRunner.RunAsync(__tagHelperExecutionContext);
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteTagHelperExecute_Runtime_RendersCorrectly()
{
// Arrange
var extension = new DefaultTagHelperTargetExtension();
using var context = TestCodeRenderingContext.CreateRuntime();
var tagHelperNode = new TagHelperIntermediateNode()
{
TagMode = 0,
TagName = "Test"
};
var node = new DefaultTagHelperExecuteIntermediateNode();
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act
extension.WriteTagHelperExecute(context, node);
// Assert
var csharp = context.CodeWriter.GetText().ToString();
Assert.Equal(
@"await __tagHelperRunner.RunAsync(__tagHelperExecutionContext);
if (!__tagHelperExecutionContext.Output.IsContentModified)
{
await __tagHelperExecutionContext.SetOutputContentAsync();
}
Write(__tagHelperExecutionContext.Output);
__tagHelperExecutionContext = __tagHelperScopeManager.End();
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteTagHelperHtmlAttribute_DesignTime_WritesNothing()
{
// Arrange
var extension = new DefaultTagHelperTargetExtension();
using var context = TestCodeRenderingContext.CreateDesignTime();
var tagHelperNode = new TagHelperIntermediateNode()
{
TagMode = 0,
TagName = "Test"
};
var node = new DefaultTagHelperHtmlAttributeIntermediateNode()
{
AttributeName = "name",
AttributeStructure = AttributeStructure.DoubleQuotes,
Children =
{
new HtmlAttributeValueIntermediateNode()
{
Children = { IntermediateNodeFactory.HtmlToken("Blah-") }
},
new CSharpCodeAttributeValueIntermediateNode()
{
Children = { IntermediateNodeFactory.CSharpToken("\"Foo\"") }
}
}
};
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act
extension.WriteTagHelperHtmlAttribute(context, node);
// Assert
var csharp = context.CodeWriter.GetText().ToString();
Assert.Equal(
@"Render Children
Render Children
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteTagHelperHtmlAttribute_Runtime_SimpleAttribute_RendersCorrectly()
{
// Arrange
var extension = new DefaultTagHelperTargetExtension();
using var context = TestCodeRenderingContext.CreateRuntime();
var tagHelperNode = new TagHelperIntermediateNode()
{
TagMode = 0,
TagName = "Test"
};
var node = new DefaultTagHelperHtmlAttributeIntermediateNode()
{
AttributeName = "name",
AttributeStructure = AttributeStructure.DoubleQuotes,
Children =
{
new HtmlAttributeIntermediateNode()
{
Children = { IntermediateNodeFactory.HtmlToken("\"value\"") }
}
}
};
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act
extension.WriteTagHelperHtmlAttribute(context, node);
// Assert
var csharp = context.CodeWriter.GetText().ToString();
Assert.Equal(
@"BeginWriteTagHelperAttribute();
Render Children
__tagHelperStringValueBuffer = EndWriteTagHelperAttribute();
__tagHelperExecutionContext.AddHtmlAttribute(""name"", Html.Raw(__tagHelperStringValueBuffer), global::Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeValueStyle.DoubleQuotes);
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteTagHelperHtmlAttribute_Runtime_DynamicAttribute_RendersCorrectly()
{
// Arrange
var extension = new DefaultTagHelperTargetExtension();
using var context = TestCodeRenderingContext.CreateRuntime();
var tagHelperNode = new TagHelperIntermediateNode()
{
TagMode = 0,
TagName = "Test"
};
var node = new DefaultTagHelperHtmlAttributeIntermediateNode()
{
AttributeName = "name",
AttributeStructure = AttributeStructure.DoubleQuotes,
Children =
{
new HtmlAttributeValueIntermediateNode()
{
Children = { IntermediateNodeFactory.HtmlToken("Blah-") }
},
new CSharpCodeAttributeValueIntermediateNode()
{
Children = { IntermediateNodeFactory.CSharpToken("\"Foo\"") }
}
}
};
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act
extension.WriteTagHelperHtmlAttribute(context, node);
// Assert
var csharp = context.CodeWriter.GetText().ToString();
Assert.Equal(
@"BeginAddHtmlAttributeValues(__tagHelperExecutionContext, ""name"", 2, global::Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeValueStyle.DoubleQuotes);
Render Children
Render Children
EndAddHtmlAttributeValues(__tagHelperExecutionContext);
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void RenderTagHelperAttributeInline_NonString_StatementInAttribute_Errors()
{
// Arrange
var extension = new DefaultTagHelperTargetExtension();
using var context = TestCodeRenderingContext.CreateRuntime();
var node = new DefaultTagHelperPropertyIntermediateNode()
{
BoundAttribute = IntPropertyTagHelper.BoundAttributes.Single(),
IsIndexerNameMatch = false,
};
var expectedLocation = new SourceSpan(100, 10);
var expectedDiagnostic = RazorDiagnosticFactory.CreateTagHelper_CodeBlocksNotSupportedInAttributes(expectedLocation);
// Act
extension.RenderTagHelperAttributeInline(context, node, new CSharpCodeIntermediateNode(), expectedLocation);
// Assert
var diagnostic = Assert.Single(context.GetDiagnostics());
Assert.Equal(expectedDiagnostic, diagnostic);
}
[Fact]
public void RenderTagHelperAttributeInline_NonStringIndexerMatch_TemplateInAttribute_Errors()
{
// Arrange
var extension = new DefaultTagHelperTargetExtension();
using var context = TestCodeRenderingContext.CreateRuntime();
var node = new DefaultTagHelperPropertyIntermediateNode()
{
BoundAttribute = IntIndexerTagHelper.BoundAttributes.Single(),
IsIndexerNameMatch = true,
};
var expectedLocation = new SourceSpan(100, 10);
var expectedDiagnostic = RazorDiagnosticFactory.CreateTagHelper_InlineMarkupBlocksNotSupportedInAttributes(expectedLocation, "System.Int32");
// Act
extension.RenderTagHelperAttributeInline(context, node, new TemplateIntermediateNode(), expectedLocation);
// Assert
var diagnostic = Assert.Single(context.GetDiagnostics());
Assert.Equal(expectedDiagnostic, diagnostic);
}
[Fact]
public void RenderTagHelperAttributeInline_NonString_TemplateInAttribute_Errors()
{
// Arrange
var extension = new DefaultTagHelperTargetExtension();
using var context = TestCodeRenderingContext.CreateRuntime();
var node = new DefaultTagHelperPropertyIntermediateNode()
{
BoundAttribute = IntIndexerTagHelper.BoundAttributes.Single(),
IsIndexerNameMatch = false,
};
var expectedLocation = new SourceSpan(100, 10);
var expectedDiagnostic = RazorDiagnosticFactory.CreateTagHelper_InlineMarkupBlocksNotSupportedInAttributes(
expectedLocation,
"System.Collections.Generic.Dictionary<System.String, System.Int32>");
// Act
extension.RenderTagHelperAttributeInline(context, node, new TemplateIntermediateNode(), expectedLocation);
// Assert
var diagnostic = Assert.Single(context.GetDiagnostics());
Assert.Equal(expectedDiagnostic, diagnostic);
}
[Fact]
public void WriteTagHelperProperty_DesignTime_StringProperty_HtmlContent_RendersCorrectly()
{
// Arrange
var extension = new DefaultTagHelperTargetExtension();
using var context = TestCodeRenderingContext.CreateDesignTime();
var tagHelperNode = new TagHelperIntermediateNode()
{
TagMode = 0,
TagName = "Test"
};
var node = new DefaultTagHelperPropertyIntermediateNode()
{
AttributeName = "bound",
AttributeStructure = AttributeStructure.DoubleQuotes,
BoundAttribute = StringPropertyTagHelper.BoundAttributes.Single(),
FieldName = "__InputTagHelper",
IsIndexerNameMatch = false,
PropertyName = "StringProp",
Children =
{
new HtmlContentIntermediateNode()
{
Children = { IntermediateNodeFactory.HtmlToken("value") }
}
}
};
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act
extension.WriteTagHelperProperty(context, node);
// Assert
var csharp = context.CodeWriter.GetText().ToString();
Assert.Equal(
@"Render Children
__InputTagHelper.StringProp = ""value"";
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact] // We don't actually assign the expression result at design time, we just use string.Empty as a placeholder.
public void WriteTagHelperProperty_DesignTime_StringProperty_NonHtmlContent_RendersCorrectly()
{
// Arrange
var extension = new DefaultTagHelperTargetExtension();
using var context = TestCodeRenderingContext.CreateDesignTime();
var tagHelperNode = new TagHelperIntermediateNode()
{
TagMode = 0,
TagName = "Test"
};
var node = new DefaultTagHelperPropertyIntermediateNode()
{
AttributeName = "bound",
AttributeStructure = AttributeStructure.DoubleQuotes,
BoundAttribute = StringPropertyTagHelper.BoundAttributes.Single(),
FieldName = "__InputTagHelper",
IsIndexerNameMatch = false,
PropertyName = "StringProp",
Children =
{
new CSharpExpressionIntermediateNode()
{
Children = { IntermediateNodeFactory.CSharpToken("\"3+5\"") }
}
}
};
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act
extension.WriteTagHelperProperty(context, node);
// Assert
var csharp = context.CodeWriter.GetText().ToString();
Assert.Equal(
@"Render Children
__InputTagHelper.StringProp = string.Empty;
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteTagHelperProperty_DesignTime_NonStringProperty_RendersCorrectly()
{
// Arrange
var extension = new DefaultTagHelperTargetExtension();
using var context = TestCodeRenderingContext.CreateDesignTime();
var tagHelperNode = new TagHelperIntermediateNode()
{
TagMode = 0,
TagName = "Test"
};
var node = new DefaultTagHelperPropertyIntermediateNode()
{
AttributeName = "bound",
AttributeStructure = AttributeStructure.DoubleQuotes,
BoundAttribute = IntPropertyTagHelper.BoundAttributes.Single(),
FieldName = "__InputTagHelper",
IsIndexerNameMatch = false,
PropertyName = "IntProp",
Source = Span,
Children =
{
new CSharpExpressionIntermediateNode()
{
Children = { IntermediateNodeFactory.CSharpToken("32") }
}
}
};
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act
extension.WriteTagHelperProperty(context, node);
// Assert
var csharp = context.CodeWriter.GetText().ToString();
Assert.Equal(
@"
#nullable restore
#line 3 ""test.cshtml""
__InputTagHelper.IntProp = 32;
#line default
#line hidden
#nullable disable
",
csharp,
ignoreLineEndingDifferences: true);
}
// If a value is bound to multiple tag helpers, we want to make sure to only render the first
// occurrence of the expression due to side-effects.
[Fact]
public void WriteTagHelperProperty_DesignTime_NonStringProperty_SecondUseOfAttribute()
{
// Arrange
var extension = new DefaultTagHelperTargetExtension();
using var context = TestCodeRenderingContext.CreateDesignTime();
var tagHelperNode = new TagHelperIntermediateNode()
{
TagMode = 0,
TagName = "Test"
};
var node1 = new DefaultTagHelperPropertyIntermediateNode()
{
// We only look at the attribute name here.
AttributeName = "bound",
FieldName = "__OtherTagHelper",
PropertyName = "IntProp",
};
var node2 = new DefaultTagHelperPropertyIntermediateNode()
{
AttributeName = "bound",
AttributeStructure = AttributeStructure.DoubleQuotes,
BoundAttribute = IntPropertyTagHelper.BoundAttributes.Single(),
FieldName = "__InputTagHelper",
IsIndexerNameMatch = false,
PropertyName = "IntProp",
Source = Span,
};
tagHelperNode.Children.Add(node1);
tagHelperNode.Children.Add(node2);
Push(context, tagHelperNode);
// Act
extension.WriteTagHelperProperty(context, node2);
// Assert
var csharp = context.CodeWriter.GetText().ToString();
Assert.Equal(
@"__InputTagHelper.IntProp = __OtherTagHelper.IntProp;
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteTagHelperProperty_DesignTime_NonStringProperty_RendersCorrectly_WithoutLocation()
{
// Arrange
var extension = new DefaultTagHelperTargetExtension();
using var context = TestCodeRenderingContext.CreateDesignTime();
var tagHelperNode = new TagHelperIntermediateNode()
{
TagMode = 0,
TagName = "Test"
};
var node = new DefaultTagHelperPropertyIntermediateNode()
{
AttributeName = "bound",
AttributeStructure = AttributeStructure.DoubleQuotes,
BoundAttribute = IntPropertyTagHelper.BoundAttributes.Single(),
FieldName = "__InputTagHelper",
IsIndexerNameMatch = false,
PropertyName = "IntProp",
Children =
{
new CSharpExpressionIntermediateNode()
{
Children = { IntermediateNodeFactory.CSharpToken("32") }
}
}
};
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act
extension.WriteTagHelperProperty(context, node);
// Assert
var csharp = context.CodeWriter.GetText().ToString();
Assert.Equal(
@"__InputTagHelper.IntProp = 32;
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteTagHelperProperty_DesignTime_NonStringIndexer_RendersCorrectly()
{
// Arrange
var extension = new DefaultTagHelperTargetExtension();
using var context = TestCodeRenderingContext.CreateDesignTime();
var tagHelperNode = new TagHelperIntermediateNode()
{
TagMode = 0,
TagName = "Test"
};
var node = new DefaultTagHelperPropertyIntermediateNode()
{
AttributeName = "foo-bound",
AttributeStructure = AttributeStructure.DoubleQuotes,
BoundAttribute = IntIndexerTagHelper.BoundAttributes.Single(),
FieldName = "__InputTagHelper",
IsIndexerNameMatch = true,
PropertyName = "IntIndexer",
Source = Span,
Children =
{
new CSharpExpressionIntermediateNode()
{
Children = { IntermediateNodeFactory.CSharpToken("32") }
}
}
};
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act
extension.WriteTagHelperProperty(context, node);
// Assert
var csharp = context.CodeWriter.GetText().ToString();
Assert.Equal(
@"
#nullable restore
#line 3 ""test.cshtml""
__InputTagHelper.IntIndexer[""bound""] = 32;
#line default
#line hidden
#nullable disable
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteTagHelperProperty_DesignTime_NonStringIndexer_RendersCorrectly_WithoutLocation()
{
// Arrange
var extension = new DefaultTagHelperTargetExtension();
using var context = TestCodeRenderingContext.CreateDesignTime();
var tagHelperNode = new TagHelperIntermediateNode()
{
TagMode = 0,
TagName = "Test"
};
var node = new DefaultTagHelperPropertyIntermediateNode()
{
AttributeName = "foo-bound",
AttributeStructure = AttributeStructure.DoubleQuotes,
BoundAttribute = IntIndexerTagHelper.BoundAttributes.Single(),
FieldName = "__InputTagHelper",
IsIndexerNameMatch = true,
PropertyName = "IntIndexer",
Children =
{
new CSharpExpressionIntermediateNode()
{
Children = { IntermediateNodeFactory.CSharpToken("32") }
}
}
};
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act
extension.WriteTagHelperProperty(context, node);
// Assert
var csharp = context.CodeWriter.GetText().ToString();
Assert.Equal(
@"__InputTagHelper.IntIndexer[""bound""] = 32;
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteTagHelperProperty_Runtime_StringProperty_HtmlContent_RendersCorrectly()
{
// Arrange
var extension = new DefaultTagHelperTargetExtension();
using var context = TestCodeRenderingContext.CreateRuntime();
var tagHelperNode = new TagHelperIntermediateNode()
{
TagMode = 0,
TagName = "Test"
};
var node = new DefaultTagHelperPropertyIntermediateNode()
{
AttributeName = "bound",
AttributeStructure = AttributeStructure.DoubleQuotes,
BoundAttribute = StringPropertyTagHelper.BoundAttributes.Single(),
FieldName = "__InputTagHelper",
IsIndexerNameMatch = false,
PropertyName = "StringProp",
Children =
{
new HtmlContentIntermediateNode()
{
Children = { IntermediateNodeFactory.HtmlToken("\"value\"") }
}
}
};
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act
extension.WriteTagHelperProperty(context, node);
// Assert
var csharp = context.CodeWriter.GetText().ToString();
// The attribute value is not rendered inline because we are not using the preallocated writer.
Assert.Equal(
@"BeginWriteTagHelperAttribute();
Render Children
__tagHelperStringValueBuffer = EndWriteTagHelperAttribute();
__InputTagHelper.StringProp = __tagHelperStringValueBuffer;
__tagHelperExecutionContext.AddTagHelperAttribute(""bound"", __InputTagHelper.StringProp, global::Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeValueStyle.DoubleQuotes);
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteTagHelperProperty_Runtime_NonStringProperty_RendersCorrectly()
{
// Arrange
var extension = new DefaultTagHelperTargetExtension();
using var context = TestCodeRenderingContext.CreateRuntime();
var tagHelperNode = new TagHelperIntermediateNode()
{
TagMode = 0,
TagName = "Test"
};
var node = new DefaultTagHelperPropertyIntermediateNode()
{
AttributeName = "bound",
AttributeStructure = AttributeStructure.DoubleQuotes,
BoundAttribute = IntPropertyTagHelper.BoundAttributes.Single(),
FieldName = "__InputTagHelper",
IsIndexerNameMatch = false,
PropertyName = "IntProp",
Children =
{
new CSharpExpressionIntermediateNode()
{
Children = { IntermediateNodeFactory.CSharpToken("32", Span) }
}
},
};
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act
extension.WriteTagHelperProperty(context, node);
// Assert
var csharp = context.CodeWriter.GetText().ToString();
Assert.Equal(
@"__InputTagHelper.IntProp =
#nullable restore
#line (3,6)-(3,1) ""test.cshtml""
32
#line default
#line hidden
#nullable disable
;
__tagHelperExecutionContext.AddTagHelperAttribute(""bound"", __InputTagHelper.IntProp, global::Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeValueStyle.DoubleQuotes);
",
csharp,
ignoreLineEndingDifferences: true);
}
// If a value is bound to multiple tag helpers, we want to make sure to only render the first
// occurrence of the expression due to side-effects.
[Fact]
public void WriteTagHelperProperty_Runtime_NonStringProperty_SecondUseOfAttribute()
{
// Arrange
var extension = new DefaultTagHelperTargetExtension();
using var context = TestCodeRenderingContext.CreateRuntime();
var tagHelperNode = new TagHelperIntermediateNode()
{
TagMode = 0,
TagName = "Test"
};
var node1 = new DefaultTagHelperPropertyIntermediateNode()
{
// We only look at the attribute name here.
AttributeName = "bound",
FieldName = "__OtherTagHelper",
PropertyName = "IntProp",
};
var node2 = new DefaultTagHelperPropertyIntermediateNode()
{
AttributeName = "bound",
AttributeStructure = AttributeStructure.DoubleQuotes,
BoundAttribute = IntPropertyTagHelper.BoundAttributes.Single(),
FieldName = "__InputTagHelper",
IsIndexerNameMatch = false,
PropertyName = "IntProp",
Source = Span,
};
tagHelperNode.Children.Add(node1);
tagHelperNode.Children.Add(node2);
Push(context, tagHelperNode);
// Act
extension.WriteTagHelperProperty(context, node2);
// Assert
var csharp = context.CodeWriter.GetText().ToString();
Assert.Equal(
@"__InputTagHelper.IntProp = __OtherTagHelper.IntProp;
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteTagHelperProperty_Runtime_NonStringProperty_RendersCorrectly_WithoutLocation()
{
// Arrange
var extension = new DefaultTagHelperTargetExtension();
using var context = TestCodeRenderingContext.CreateRuntime();
var tagHelperNode = new TagHelperIntermediateNode()
{
TagMode = 0,
TagName = "Test"
};
var node = new DefaultTagHelperPropertyIntermediateNode()
{
AttributeName = "bound",
AttributeStructure = AttributeStructure.DoubleQuotes,
BoundAttribute = IntPropertyTagHelper.BoundAttributes.Single(),
FieldName = "__InputTagHelper",
IsIndexerNameMatch = false,
PropertyName = "IntProp",
Children =
{
new CSharpExpressionIntermediateNode()
{
Children = { IntermediateNodeFactory.CSharpToken("32") }
}
}
};
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act
extension.WriteTagHelperProperty(context, node);
// Assert
var csharp = context.CodeWriter.GetText().ToString();
Assert.Equal(
@"__InputTagHelper.IntProp = 32;
__tagHelperExecutionContext.AddTagHelperAttribute(""bound"", __InputTagHelper.IntProp, global::Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeValueStyle.DoubleQuotes);
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteTagHelperProperty_Runtime_NonStringIndexer_RendersCorrectly()
{
// Arrange
var extension = new DefaultTagHelperTargetExtension();
using var context = TestCodeRenderingContext.CreateRuntime();
var tagHelperNode = new TagHelperIntermediateNode()
{
TagMode = 0,
TagName = "Test"
};
var node = new DefaultTagHelperPropertyIntermediateNode()
{
AttributeName = "foo-bound",
AttributeStructure = AttributeStructure.DoubleQuotes,
BoundAttribute = IntIndexerTagHelper.BoundAttributes.Single(),
FieldName = "__InputTagHelper",
IsIndexerNameMatch = true,
PropertyName = "IntIndexer",
Children =
{
new CSharpExpressionIntermediateNode()
{
Children = { IntermediateNodeFactory.CSharpToken("32", Span) }
}
}
};
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act
extension.WriteTagHelperProperty(context, node);
// Assert
var csharp = context.CodeWriter.GetText().ToString();
Assert.Equal(
@"if (__InputTagHelper.IntIndexer == null)
{
throw new InvalidOperationException(InvalidTagHelperIndexerAssignment(""foo-bound"", ""InputTagHelper"", ""IntIndexer""));
}
__InputTagHelper.IntIndexer[""bound""] =
#nullable restore
#line (3,6)-(3,1) ""test.cshtml""
32
#line default
#line hidden
#nullable disable
;
__tagHelperExecutionContext.AddTagHelperAttribute(""foo-bound"", __InputTagHelper.IntIndexer[""bound""], global::Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeValueStyle.DoubleQuotes);
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact] // We should only emit the validation code for the first use of an indexer property.
public void WriteTagHelperProperty_Runtime_NonStringIndexer_MultipleValues()
{
// Arrange
var extension = new DefaultTagHelperTargetExtension();
using var context = TestCodeRenderingContext.CreateRuntime();
var tagHelperNode = new TagHelperIntermediateNode()
{
TagMode = 0,
TagName = "Test"
};
var node1 = new DefaultTagHelperPropertyIntermediateNode()
{
AttributeName = "foo-first",
AttributeStructure = AttributeStructure.DoubleQuotes,
BoundAttribute = IntIndexerTagHelper.BoundAttributes.Single(),
FieldName = "__InputTagHelper",
IsIndexerNameMatch = true,
PropertyName = "IntIndexer",
Children =
{
new CSharpExpressionIntermediateNode()
{
Children = { IntermediateNodeFactory.CSharpToken("17", Span) }
}
}
};
var node2 = new DefaultTagHelperPropertyIntermediateNode()
{
AttributeName = "foo-bound",
AttributeStructure = AttributeStructure.DoubleQuotes,
BoundAttribute = IntIndexerTagHelper.BoundAttributes.Single(),
FieldName = "__InputTagHelper",
IsIndexerNameMatch = true,
PropertyName = "IntIndexer",
Children =
{
new CSharpExpressionIntermediateNode()
{
Children = { IntermediateNodeFactory.CSharpToken("32", Span) }
}
}
};
tagHelperNode.Children.Add(node1);
tagHelperNode.Children.Add(node2);
Push(context, tagHelperNode);
// Act
extension.WriteTagHelperProperty(context, node2);
// Assert
var csharp = context.CodeWriter.GetText().ToString();
Assert.Equal(
@"__InputTagHelper.IntIndexer[""bound""] =
#nullable restore
#line (3,6)-(3,1) ""test.cshtml""
32
#line default
#line hidden
#nullable disable
;
__tagHelperExecutionContext.AddTagHelperAttribute(""foo-bound"", __InputTagHelper.IntIndexer[""bound""], global::Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeValueStyle.DoubleQuotes);
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteTagHelperProperty_Runtime_NonStringIndexer_RendersCorrectly_WithoutLocation()
{
// Arrange
var extension = new DefaultTagHelperTargetExtension();
using var context = TestCodeRenderingContext.CreateRuntime();
var tagHelperNode = new TagHelperIntermediateNode()
{
TagMode = 0,
TagName = "Test"
};
var node = new DefaultTagHelperPropertyIntermediateNode()
{
AttributeName = "foo-bound",
AttributeStructure = AttributeStructure.DoubleQuotes,
BoundAttribute = IntIndexerTagHelper.BoundAttributes.Single(),
FieldName = "__InputTagHelper",
IsIndexerNameMatch = true,
PropertyName = "IntIndexer",
Children =
{
new CSharpExpressionIntermediateNode()
{
Children = { IntermediateNodeFactory.CSharpToken("32") }
}
}
};
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act
extension.WriteTagHelperProperty(context, node);
// Assert
var csharp = context.CodeWriter.GetText().ToString();
Assert.Equal(
@"if (__InputTagHelper.IntIndexer == null)
{
throw new InvalidOperationException(InvalidTagHelperIndexerAssignment(""foo-bound"", ""InputTagHelper"", ""IntIndexer""));
}
__InputTagHelper.IntIndexer[""bound""] = 32;
__tagHelperExecutionContext.AddTagHelperAttribute(""foo-bound"", __InputTagHelper.IntIndexer[""bound""], global::Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeValueStyle.DoubleQuotes);
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteTagHelperRuntime_DesignTime_RendersPreRequisites()
{
// Arrange
var extension = new DefaultTagHelperTargetExtension();
using var context = TestCodeRenderingContext.CreateDesignTime();
var node = new DefaultTagHelperRuntimeIntermediateNode();
// Act
extension.WriteTagHelperRuntime(context, node);
// Assert
var csharp = context.CodeWriter.GetText().ToString();
Assert.Equal(
@"#line hidden
#pragma warning disable 0649
private global::Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperExecutionContext __tagHelperExecutionContext;
#pragma warning restore 0649
private global::Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperRunner __tagHelperRunner = new global::Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperRunner();
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteTagHelperRuntime_Runtime_DeclaresRequiredFields()
{
// Arrange
var extension = new DefaultTagHelperTargetExtension();
using var context = TestCodeRenderingContext.CreateRuntime();
var node = new DefaultTagHelperRuntimeIntermediateNode();
// Act
extension.WriteTagHelperRuntime(context, node);
// Assert
var csharp = context.CodeWriter.GetText().ToString();
Assert.Equal(
@"#line hidden
#pragma warning disable 0649
private global::Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperExecutionContext __tagHelperExecutionContext;
#pragma warning restore 0649
private global::Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperRunner __tagHelperRunner = new global::Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperRunner();
#pragma warning disable 0169
private string __tagHelperStringValueBuffer;
#pragma warning restore 0169
private global::Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperScopeManager __backed__tagHelperScopeManager = null;
private global::Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperScopeManager __tagHelperScopeManager
{
get
{
if (__backed__tagHelperScopeManager == null)
{
__backed__tagHelperScopeManager = new global::Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperScopeManager(StartTagHelperWritingScope, EndTagHelperWritingScope);
}
return __backed__tagHelperScopeManager;
}
}
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void GetDeterministicId_IsDeterministic()
{
// Arrange
using var context = TestCodeRenderingContext.CreateRuntime(suppressUniqueIds: null);
// Act
var firstId = DefaultTagHelperTargetExtension.GetDeterministicId(context);
var secondId = DefaultTagHelperTargetExtension.GetDeterministicId(context);
// Assert
Assert.Equal(firstId, secondId);
}
private static void Push(CodeRenderingContext context, TagHelperIntermediateNode node)
{
context.PushAncestor(node);
}
private static TagHelperDescriptor CreateTagHelperDescriptor(
string tagName,
string typeName,
string assemblyName,
IEnumerable<Action<BoundAttributeDescriptorBuilder>>? attributes = null)
{
var builder = TagHelperDescriptorBuilder.CreateTagHelper(typeName, assemblyName);
builder.SetTypeName(typeName, typeNamespace: null, typeNameIdentifier: null);
if (attributes != null)
{
foreach (var attributeBuilder in attributes)
{
builder.BindAttribute(attributeBuilder);
}
}
builder.TagMatchingRule(ruleBuilder => ruleBuilder.RequireTagName(tagName));
var descriptor = builder.Build();
return descriptor;
}
}
|