aspnetcore/test/Microsoft.AspNetCore.Blazor.../GenericComponentRazorIntegr...

360 lines
12 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 Microsoft.AspNetCore.Blazor.Components;
using Microsoft.AspNetCore.Blazor.Razor;
using Microsoft.AspNetCore.Blazor.RenderTree;
using Microsoft.AspNetCore.Blazor.Test.Helpers;
using Microsoft.CodeAnalysis.CSharp;
using System;
using System.Collections.Generic;
using System.Linq;
using Xunit;
namespace Microsoft.AspNetCore.Blazor.Build.Test
{
public class GenericComponentRazorIntegrationTest : RazorIntegrationTestBase
{
private readonly CSharpSyntaxTree GenericContextComponent = Parse(@"
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Blazor;
using Microsoft.AspNetCore.Blazor.Components;
using Microsoft.AspNetCore.Blazor.RenderTree;
namespace Test
{
public class GenericContext<TItem> : BlazorComponent
{
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
var items = (IReadOnlyList<TItem>)Items ?? Array.Empty<TItem>();
for (var i = 0; i < items.Count; i++)
{
if (ChildContent == null)
{
builder.AddContent(i, Items[i]);
}
else
{
builder.AddContent(i, ChildContent, new Context() { Index = i, Item = items[i], });
}
}
}
[Parameter]
List<TItem> Items { get; set; }
[Parameter]
RenderFragment<Context> ChildContent { get; set; }
public class Context
{
public int Index { get; set; }
public TItem Item { get; set; }
}
}
}
");
private readonly CSharpSyntaxTree MultipleGenericParameterComponent = Parse(@"
using Microsoft.AspNetCore.Blazor;
using Microsoft.AspNetCore.Blazor.Components;
using Microsoft.AspNetCore.Blazor.RenderTree;
namespace Test
{
public class MultipleGenericParameter<TItem1, TItem2, TItem3> : BlazorComponent
{
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
builder.AddContent(0, Item1);
builder.AddContent(1, Item2);
builder.AddContent(2, Item3);
}
[Parameter]
TItem1 Item1 { get; set; }
[Parameter]
TItem2 Item2 { get; set; }
[Parameter]
TItem3 Item3 { get; set; }
}
}
");
internal override bool UseTwoPhaseCompilation => true;
[Fact]
public void Render_GenericComponent_WithoutChildContent()
{
// Arrange
AdditionalSyntaxTrees.Add(GenericContextComponent);
var component = CompileToComponent(@"
@addTagHelper *, TestAssembly
<GenericContext TItem=int Items=""@(new List<int>() { 1, 2, })"" />");
// Act
var frames = GetRenderTree(component);
// Assert
var genericComponentType = component.GetType().Assembly.DefinedTypes
.Where(t => t.Name == "GenericContext`1")
.Single()
.MakeGenericType(typeof(int));
Assert.Collection(
frames,
frame => AssertFrame.Component(frame, genericComponentType.FullName, 2, 0),
frame => AssertFrame.Attribute(frame, "Items", typeof(List<int>), 1),
frame => AssertFrame.Text(frame, "1", 0),
frame => AssertFrame.Text(frame, "2", 1));
}
[Fact]
public void Render_GenericComponent_WithRef()
{
// Arrange
AdditionalSyntaxTrees.Add(GenericContextComponent);
var component = CompileToComponent(@"
@addTagHelper *, TestAssembly
<GenericContext TItem=int Items=""@(new List<int>() { 1, 2, })"" ref=""_my"" />
@functions {
GenericContext<int> _my;
void Foo() { GC.KeepAlive(_my); }
}");
// Act
var frames = GetRenderTree(component);
// Assert
var genericComponentType = component.GetType().Assembly.DefinedTypes
.Where(t => t.Name == "GenericContext`1")
.Single()
.MakeGenericType(typeof(int));
Assert.Collection(
frames,
frame => AssertFrame.Component(frame, genericComponentType.FullName, 3, 0),
frame => AssertFrame.Attribute(frame, "Items", typeof(List<int>), 1),
frame => AssertFrame.ComponentReferenceCapture(frame, 2),
frame => AssertFrame.Text(frame, "1", 0),
frame => AssertFrame.Text(frame, "2", 1));
}
[Fact]
public void Render_GenericComponent_WithChildContent()
{
// Arrange
AdditionalSyntaxTrees.Add(GenericContextComponent);
var component = CompileToComponent(@"
@addTagHelper *, TestAssembly
<GenericContext TItem=int Items=""@(new List<int>() { 1, 2, })"">
<div>@(context.Item * context.Index)</div>
</GenericContext>");
// Act
var frames = GetRenderTree(component);
// Assert
var genericComponentType = component.GetType().Assembly.DefinedTypes
.Where(t => t.Name == "GenericContext`1")
.Single()
.MakeGenericType(typeof(int));
Assert.Collection(
frames,
frame => AssertFrame.Component(frame, genericComponentType.FullName, 3, 0),
frame => AssertFrame.Attribute(frame, "Items", typeof(List<int>), 1),
frame => AssertFrame.Attribute(frame, RenderTreeBuilder.ChildContent, 2),
frame => AssertFrame.Whitespace(frame, 3),
frame => AssertFrame.Element(frame, "div", 2, 4),
frame => AssertFrame.Text(frame, "0", 5),
frame => AssertFrame.Whitespace(frame, 6),
frame => AssertFrame.Whitespace(frame, 3),
frame => AssertFrame.Element(frame, "div", 2, 4),
frame => AssertFrame.Text(frame, "2", 5),
frame => AssertFrame.Whitespace(frame, 6));
}
[Fact]
public void Render_GenericComponent_TypeInference_WithRef()
{
// Arrange
AdditionalSyntaxTrees.Add(GenericContextComponent);
var component = CompileToComponent(@"
@addTagHelper *, TestAssembly
<GenericContext Items=""@(new List<int>() { 1, 2, })"" ref=""_my"" />
@functions {
GenericContext<int> _my;
void Foo() { GC.KeepAlive(_my); }
}");
// Act
var frames = GetRenderTree(component);
// Assert
var genericComponentType = component.GetType().Assembly.DefinedTypes
.Where(t => t.Name == "GenericContext`1")
.Single()
.MakeGenericType(typeof(int));
Assert.Collection(
frames,
frame => AssertFrame.Component(frame, genericComponentType.FullName, 3, 0),
frame => AssertFrame.Attribute(frame, "Items", typeof(List<int>), 1),
frame => AssertFrame.ComponentReferenceCapture(frame, 2),
frame => AssertFrame.Text(frame, "1", 0),
frame => AssertFrame.Text(frame, "2", 1));
}
[Fact]
public void Render_GenericComponent_TypeInference_WithRef_Recursive()
{
// Arrange
AdditionalSyntaxTrees.Add(GenericContextComponent);
var assembly = CompileToAssembly("Test.cshtml", @"
@addTagHelper *, TestAssembly
@typeparam TItem
<GenericContext Items=""@MyItems"" ref=""_my"" />
@functions {
[Parameter] List<TItem> MyItems { get; set; }
GenericContext<TItem> _my;
void Foo() { GC.KeepAlive(_my); }
}");
var componentType = assembly.Assembly.DefinedTypes
.Where(t => t.Name == "Test`1")
.Single()
.MakeGenericType(typeof(int));
var component = (IComponent)Activator.CreateInstance(componentType);
// Act
var frames = GetRenderTree(component);
// Assert
var genericComponentType = assembly.Assembly.DefinedTypes
.Where(t => t.Name == "GenericContext`1")
.Single()
.MakeGenericType(typeof(int));
Assert.Collection(
frames,
frame => AssertFrame.Component(frame, genericComponentType.FullName, 3, 0),
frame => AssertFrame.Attribute(frame, "Items", 1),
frame => AssertFrame.ComponentReferenceCapture(frame, 2));
}
[Fact]
public void Render_GenericComponent_TypeInference_WithoutChildContent()
{
// Arrange
AdditionalSyntaxTrees.Add(GenericContextComponent);
var component = CompileToComponent(@"
@addTagHelper *, TestAssembly
<GenericContext Items=""@(new List<int>() { 1, 2, })"" />");
// Act
var frames = GetRenderTree(component);
// Assert
var genericComponentType = component.GetType().Assembly.DefinedTypes
.Where(t => t.Name == "GenericContext`1")
.Single()
.MakeGenericType(typeof(int));
Assert.Collection(
frames,
frame => AssertFrame.Component(frame, genericComponentType.FullName, 2, 0),
frame => AssertFrame.Attribute(frame, "Items", typeof(List<int>), 1),
frame => AssertFrame.Text(frame, "1", 0),
frame => AssertFrame.Text(frame, "2", 1));
}
[Fact]
public void Render_GenericComponent_MultipleParameters_WithChildContent()
{
// Arrange
AdditionalSyntaxTrees.Add(MultipleGenericParameterComponent);
var component = CompileToComponent(@"
@addTagHelper *, TestAssembly
<MultipleGenericParameter
TItem1=""int""
TItem2=""string""
TItem3=long
Item1=3
Item2=""@(""FOO"")""
Item3=39L/>");
// Act
var frames = GetRenderTree(component);
// Assert
var genericComponentType = component.GetType().Assembly.DefinedTypes
.Where(t => t.Name == "MultipleGenericParameter`3")
.Single()
.MakeGenericType(typeof(int), typeof(string), typeof(long));
Assert.Collection(
frames,
frame => AssertFrame.Component(frame, genericComponentType.FullName, 4, 0),
frame => AssertFrame.Attribute(frame, "Item1", 3, 1),
frame => AssertFrame.Attribute(frame, "Item2", "FOO", 2),
frame => AssertFrame.Attribute(frame, "Item3", 39L, 3),
frame => AssertFrame.Text(frame, "3", 0),
frame => AssertFrame.Text(frame, "FOO", 1),
frame => AssertFrame.Text(frame, "39", 2));
}
[Fact]
public void GenericComponent_WithoutAnyTypeParameters_TriggersDiagnostic()
{
// Arrange
AdditionalSyntaxTrees.Add(GenericContextComponent);
// Act
var generated = CompileToCSharp(@"
@addTagHelper *, TestAssembly
<GenericContext />");
// Assert
var diagnostic = Assert.Single(generated.Diagnostics);
Assert.Same(BlazorDiagnosticFactory.GenericComponentTypeInferenceUnderspecified.Id, diagnostic.Id);
Assert.Equal(
"The type of component 'GenericContext' cannot be inferred based on the values provided. Consider " +
"specifying the type arguments directly using the following attributes: 'TItem'.",
diagnostic.GetMessage());
}
[Fact]
public void GenericComponent_WithMissingTypeParameters_TriggersDiagnostic()
{
// Arrange
AdditionalSyntaxTrees.Add(MultipleGenericParameterComponent);
// Act
var generated = CompileToCSharp(@"
@addTagHelper *, TestAssembly
<MultipleGenericParameter TItem1=int />");
// Assert
var diagnostic = Assert.Single(generated.Diagnostics);
Assert.Same(BlazorDiagnosticFactory.GenericComponentMissingTypeArgument.Id, diagnostic.Id);
Assert.Equal(
"The component 'MultipleGenericParameter' is missing required type arguments. " +
"Specify the missing types using the attributes: 'TItem2', 'TItem3'.",
diagnostic.GetMessage());
}
}
}