612 lines
19 KiB
C#
612 lines
19 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.Linq;
|
|
using Microsoft.AspNetCore.Blazor.RenderTree;
|
|
using Microsoft.AspNetCore.Blazor.Test.Helpers;
|
|
using Microsoft.CodeAnalysis.CSharp;
|
|
using Xunit;
|
|
|
|
namespace Microsoft.AspNetCore.Blazor.Build.Test
|
|
{
|
|
public class ComponentRenderingRazorIntegrationTest : RazorIntegrationTestBase
|
|
{
|
|
internal override bool UseTwoPhaseCompilation => true;
|
|
|
|
[Fact]
|
|
public void Render_ChildComponent_Simple()
|
|
{
|
|
// Arrange
|
|
AdditionalSyntaxTrees.Add(Parse(@"
|
|
using Microsoft.AspNetCore.Blazor.Components;
|
|
|
|
namespace Test
|
|
{
|
|
public class MyComponent : BlazorComponent
|
|
{
|
|
}
|
|
}
|
|
"));
|
|
|
|
var component = CompileToComponent(@"
|
|
@addTagHelper *, TestAssembly
|
|
<MyComponent/>");
|
|
|
|
// Act
|
|
var frames = GetRenderTree(component);
|
|
|
|
// Assert
|
|
Assert.Collection(
|
|
frames,
|
|
frame => AssertFrame.Component(frame, "Test.MyComponent", 1, 0));
|
|
}
|
|
|
|
[Fact]
|
|
public void Render_ChildComponent_WithParameters()
|
|
{
|
|
// Arrange
|
|
AdditionalSyntaxTrees.Add(Parse(@"
|
|
using Microsoft.AspNetCore.Blazor.Components;
|
|
|
|
namespace Test
|
|
{
|
|
public class SomeType
|
|
{
|
|
}
|
|
|
|
public class MyComponent : BlazorComponent
|
|
{
|
|
[Parameter] int IntProperty { get; set; }
|
|
[Parameter] bool BoolProperty { get; set; }
|
|
[Parameter] string StringProperty { get; set; }
|
|
[Parameter] SomeType ObjectProperty { get; set; }
|
|
}
|
|
}
|
|
"));
|
|
|
|
var component = CompileToComponent(@"
|
|
@addTagHelper *, TestAssembly
|
|
<MyComponent
|
|
IntProperty=""123""
|
|
BoolProperty=""true""
|
|
StringProperty=""My string""
|
|
ObjectProperty=""new SomeType()"" />");
|
|
|
|
// Act
|
|
var frames = GetRenderTree(component);
|
|
|
|
// Assert
|
|
Assert.Collection(
|
|
frames,
|
|
frame => AssertFrame.Component(frame, "Test.MyComponent", 5, 0),
|
|
frame => AssertFrame.Attribute(frame, "IntProperty", 123, 1),
|
|
frame => AssertFrame.Attribute(frame, "BoolProperty", true, 2),
|
|
frame => AssertFrame.Attribute(frame, "StringProperty", "My string", 3),
|
|
frame =>
|
|
{
|
|
AssertFrame.Attribute(frame, "ObjectProperty", 4);
|
|
Assert.Equal("Test.SomeType", frame.AttributeValue.GetType().FullName);
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public void Render_ChildComponent_TriesToSetNonParamter()
|
|
{
|
|
// Arrange
|
|
AdditionalSyntaxTrees.Add(Parse(@"
|
|
using Microsoft.AspNetCore.Blazor.Components;
|
|
|
|
namespace Test
|
|
{
|
|
public class MyComponent : BlazorComponent
|
|
{
|
|
public int IntProperty { get; set; }
|
|
}
|
|
}
|
|
"));
|
|
|
|
var component = CompileToComponent(@"
|
|
@addTagHelper *, TestAssembly
|
|
<MyComponent IntProperty=""123"" />");
|
|
|
|
// Act
|
|
var ex = Assert.Throws<InvalidOperationException>(() => GetRenderTree(component));
|
|
|
|
// Assert
|
|
Assert.Equal(
|
|
"Object of type 'Test.MyComponent' has a property matching the name 'IntProperty', " +
|
|
"but it does not have [ParameterAttribute] or [CascadingParameterAttribute] applied.",
|
|
ex.Message);
|
|
}
|
|
|
|
[Fact]
|
|
public void Render_ChildComponent_WithExplicitStringParameter()
|
|
{
|
|
// Arrange
|
|
AdditionalSyntaxTrees.Add(Parse(@"
|
|
using Microsoft.AspNetCore.Blazor.Components;
|
|
|
|
namespace Test
|
|
{
|
|
public class MyComponent : BlazorComponent
|
|
{
|
|
[Parameter]
|
|
string StringProperty { get; set; }
|
|
}
|
|
}
|
|
"));
|
|
|
|
var component = CompileToComponent(@"
|
|
@addTagHelper *, TestAssembly
|
|
<MyComponent StringProperty=""@(42.ToString())"" />");
|
|
|
|
// Act
|
|
var frames = GetRenderTree(component);
|
|
|
|
// Assert
|
|
Assert.Collection(
|
|
frames,
|
|
frame => AssertFrame.Component(frame, "Test.MyComponent", 2, 0),
|
|
frame => AssertFrame.Attribute(frame, "StringProperty", "42", 1));
|
|
}
|
|
|
|
[Fact]
|
|
public void Render_ChildComponent_WithNonPropertyAttributes()
|
|
{
|
|
// Arrange
|
|
AdditionalSyntaxTrees.Add(Parse(@"
|
|
using Microsoft.AspNetCore.Blazor.Components;
|
|
|
|
namespace Test
|
|
{
|
|
public class MyComponent : BlazorComponent, IComponent
|
|
{
|
|
void IComponent.SetParameters(ParameterCollection parameters)
|
|
{
|
|
}
|
|
}
|
|
}
|
|
"));
|
|
|
|
var component = CompileToComponent(@"
|
|
@addTagHelper *, TestAssembly
|
|
<MyComponent some-attribute=""foo"" another-attribute=""@(42.ToString())"" />");
|
|
|
|
// Act
|
|
var frames = GetRenderTree(component);
|
|
|
|
// Assert
|
|
Assert.Collection(
|
|
frames,
|
|
frame => AssertFrame.Component(frame, "Test.MyComponent", 3, 0),
|
|
frame => AssertFrame.Attribute(frame, "some-attribute", "foo", 1),
|
|
frame => AssertFrame.Attribute(frame, "another-attribute", "42", 2));
|
|
}
|
|
|
|
|
|
[Theory]
|
|
[InlineData("e => Increment(e)")]
|
|
[InlineData("(e) => Increment(e)")]
|
|
[InlineData("@(e => Increment(e))")]
|
|
[InlineData("@(e => { Increment(e); })")]
|
|
[InlineData("Increment")]
|
|
[InlineData("@Increment")]
|
|
[InlineData("@(Increment)")]
|
|
public void Render_ChildComponent_WithEventHandler(string expression)
|
|
{
|
|
// Arrange
|
|
AdditionalSyntaxTrees.Add(Parse(@"
|
|
using System;
|
|
using Microsoft.AspNetCore.Blazor;
|
|
using Microsoft.AspNetCore.Blazor.Components;
|
|
|
|
namespace Test
|
|
{
|
|
public class MyComponent : BlazorComponent
|
|
{
|
|
[Parameter]
|
|
Action<UIMouseEventArgs> OnClick { get; set; }
|
|
}
|
|
}
|
|
"));
|
|
|
|
var component = CompileToComponent($@"
|
|
@addTagHelper *, TestAssembly
|
|
<MyComponent OnClick=""{expression}""/>
|
|
|
|
@functions {{
|
|
private int counter;
|
|
private void Increment(UIMouseEventArgs e) {{
|
|
counter++;
|
|
}}
|
|
}}");
|
|
|
|
// Act
|
|
var frames = GetRenderTree(component);
|
|
|
|
// Assert
|
|
Assert.Collection(
|
|
frames,
|
|
frame => AssertFrame.Component(frame, "Test.MyComponent", 2, 0),
|
|
frame =>
|
|
{
|
|
AssertFrame.Attribute(frame, "OnClick", 1);
|
|
|
|
// The handler will have been assigned to a lambda
|
|
var handler = Assert.IsType<Action<UIMouseEventArgs>>(frame.AttributeValue);
|
|
Assert.Equal("Test.TestComponent", handler.Target.GetType().FullName);
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public void Render_ChildComponent_WithExplicitEventHandler()
|
|
{
|
|
// Arrange
|
|
AdditionalSyntaxTrees.Add(Parse(@"
|
|
using System;
|
|
using Microsoft.AspNetCore.Blazor;
|
|
using Microsoft.AspNetCore.Blazor.Components;
|
|
|
|
namespace Test
|
|
{
|
|
public class MyComponent : BlazorComponent
|
|
{
|
|
[Parameter]
|
|
Action<UIEventArgs> OnClick { get; set; }
|
|
}
|
|
}
|
|
"));
|
|
|
|
var component = CompileToComponent(@"
|
|
@addTagHelper *, TestAssembly
|
|
<MyComponent OnClick=""@Increment""/>
|
|
|
|
@functions {
|
|
private int counter;
|
|
private void Increment(UIEventArgs e) {
|
|
counter++;
|
|
}
|
|
}");
|
|
|
|
// Act
|
|
var frames = GetRenderTree(component);
|
|
|
|
// Assert
|
|
Assert.Collection(
|
|
frames,
|
|
frame => AssertFrame.Component(frame, "Test.MyComponent", 2, 0),
|
|
frame =>
|
|
{
|
|
AssertFrame.Attribute(frame, "OnClick", 1);
|
|
|
|
// The handler will have been assigned to a lambda
|
|
var handler = Assert.IsType<Action<UIEventArgs>>(frame.AttributeValue);
|
|
Assert.Equal("Test.TestComponent", handler.Target.GetType().FullName);
|
|
Assert.Equal("Increment", handler.Method.Name);
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public void Render_ChildComponent_WithMinimizedBoolAttribute()
|
|
{
|
|
// Arrange
|
|
AdditionalSyntaxTrees.Add(Parse(@"
|
|
using Microsoft.AspNetCore.Blazor.Components;
|
|
|
|
namespace Test
|
|
{
|
|
public class MyComponent : BlazorComponent
|
|
{
|
|
[Parameter]
|
|
bool BoolProperty { get; set; }
|
|
}
|
|
}"));
|
|
|
|
var component = CompileToComponent(@"
|
|
@addTagHelper *, TestAssembly
|
|
<MyComponent BoolProperty />");
|
|
|
|
// Act
|
|
var frames = GetRenderTree(component);
|
|
|
|
// Assert
|
|
Assert.Collection(
|
|
frames,
|
|
frame => AssertFrame.Component(frame, "Test.MyComponent", 2, 0),
|
|
frame => AssertFrame.Attribute(frame, "BoolProperty", true, 1));
|
|
}
|
|
|
|
[Fact]
|
|
public void Render_ChildComponent_WithChildContent()
|
|
{
|
|
// Arrange
|
|
AdditionalSyntaxTrees.Add(Parse(@"
|
|
using Microsoft.AspNetCore.Blazor;
|
|
using Microsoft.AspNetCore.Blazor.Components;
|
|
namespace Test
|
|
{
|
|
public class MyComponent : BlazorComponent
|
|
{
|
|
[Parameter]
|
|
string MyAttr { get; set; }
|
|
|
|
[Parameter]
|
|
RenderFragment ChildContent { get; set; }
|
|
}
|
|
}
|
|
"));
|
|
|
|
var component = CompileToComponent(@"
|
|
@addTagHelper *, TestAssembly
|
|
<MyComponent MyAttr=""abc"">Some text<some-child a='1'>Nested text @(""Hello"")</some-child></MyComponent>");
|
|
|
|
// Act
|
|
var frames = GetRenderTree(component);
|
|
|
|
// Assert: component frames are correct
|
|
Assert.Collection(
|
|
frames,
|
|
frame => AssertFrame.Component(frame, "Test.MyComponent", 3, 0),
|
|
frame => AssertFrame.Attribute(frame, "MyAttr", "abc", 1),
|
|
frame => AssertFrame.Attribute(frame, RenderTreeBuilder.ChildContent, 2));
|
|
|
|
// Assert: Captured ChildContent frames are correct
|
|
var childFrames = GetFrames((RenderFragment)frames[2].AttributeValue);
|
|
Assert.Collection(
|
|
childFrames,
|
|
frame => AssertFrame.Text(frame, "Some text", 3),
|
|
frame => AssertFrame.Element(frame, "some-child", 4, 4),
|
|
frame => AssertFrame.Attribute(frame, "a", "1", 5),
|
|
frame => AssertFrame.Text(frame, "Nested text ", 6),
|
|
frame => AssertFrame.Text(frame, "Hello", 7));
|
|
}
|
|
|
|
[Fact]
|
|
public void Render_ChildComponent_Nested()
|
|
{
|
|
// Arrange
|
|
AdditionalSyntaxTrees.Add(Parse(@"
|
|
using Microsoft.AspNetCore.Blazor;
|
|
using Microsoft.AspNetCore.Blazor.Components;
|
|
|
|
namespace Test
|
|
{
|
|
public class MyComponent : BlazorComponent
|
|
{
|
|
[Parameter]
|
|
RenderFragment ChildContent { get; set; }
|
|
}
|
|
}
|
|
"));
|
|
|
|
var component = CompileToComponent(@"
|
|
@addTagHelper *, TestAssembly
|
|
<MyComponent><MyComponent>Some text</MyComponent></MyComponent>");
|
|
|
|
// Act
|
|
var frames = GetRenderTree(component);
|
|
|
|
// Assert: outer component frames are correct
|
|
Assert.Collection(
|
|
frames,
|
|
frame => AssertFrame.Component(frame, "Test.MyComponent", 2, 0),
|
|
frame => AssertFrame.Attribute(frame, RenderTreeBuilder.ChildContent, 1));
|
|
|
|
// Assert: first level of ChildContent is correct
|
|
// Note that we don't really need the sequence numbers to continue on from the
|
|
// sequence numbers at the parent level. All that really matters is that they are
|
|
// correct relative to each other (i.e., incrementing) within the nesting level.
|
|
// As an implementation detail, it happens that they do follow on from the parent
|
|
// level, but we could change that part of the implementation if we wanted.
|
|
var innerFrames = GetFrames((RenderFragment)frames[1].AttributeValue).ToArray();
|
|
Assert.Collection(
|
|
innerFrames,
|
|
frame => AssertFrame.Component(frame, "Test.MyComponent", 2, 2),
|
|
frame => AssertFrame.Attribute(frame, RenderTreeBuilder.ChildContent, 3));
|
|
|
|
// Assert: second level of ChildContent is correct
|
|
Assert.Collection(
|
|
GetFrames((RenderFragment)innerFrames[1].AttributeValue),
|
|
frame => AssertFrame.Text(frame, "Some text", 4));
|
|
}
|
|
|
|
[Fact] // https://github.com/aspnet/Blazor/issues/773
|
|
public void Regression_773()
|
|
{
|
|
// Arrange
|
|
AdditionalSyntaxTrees.Add(Parse(@"
|
|
using Microsoft.AspNetCore.Blazor.Components;
|
|
|
|
namespace Test
|
|
{
|
|
public class SurveyPrompt : BlazorComponent
|
|
{
|
|
[Parameter] private string Title { get; set; }
|
|
}
|
|
}
|
|
"));
|
|
|
|
var component = CompileToComponent(@"
|
|
@addTagHelper *, TestAssembly
|
|
@page ""/""
|
|
|
|
<SurveyPrompt Title=""<div>Test!</div>"" />
|
|
");
|
|
|
|
// Act
|
|
var frames = GetRenderTree(component);
|
|
|
|
// Assert
|
|
Assert.Collection(
|
|
frames,
|
|
frame => AssertFrame.Component(frame, "Test.SurveyPrompt", 2, 0),
|
|
frame => AssertFrame.Attribute(frame, "Title", "<div>Test!</div>", 1));
|
|
}
|
|
|
|
|
|
[Fact]
|
|
public void Regression_784()
|
|
{
|
|
// Arrange
|
|
|
|
// Act
|
|
var component = CompileToComponent(@"
|
|
<p onmouseover=""@OnComponentHover"" style=""background: @ParentBgColor;"" />
|
|
@functions {
|
|
public string ParentBgColor { get; set; } = ""#FFFFFF"";
|
|
|
|
public void OnComponentHover(UIMouseEventArgs e)
|
|
{
|
|
}
|
|
}
|
|
");
|
|
|
|
// Act
|
|
var frames = GetRenderTree(component);
|
|
|
|
// Assert
|
|
Assert.Collection(
|
|
frames,
|
|
frame => AssertFrame.Element(frame, "p", 3, 0),
|
|
frame => AssertFrame.Attribute(frame, "onmouseover", 1),
|
|
frame => AssertFrame.Attribute(frame, "style", "background: #FFFFFF;", 2));
|
|
}
|
|
|
|
// Text nodes decode HTML entities
|
|
[Fact]
|
|
public void Render_Component_HtmlEncoded()
|
|
{
|
|
// Arrange
|
|
var component = CompileToComponent(@"<span>Hi</span>");
|
|
|
|
// Act
|
|
var frames = GetRenderTree(component);
|
|
|
|
// Assert
|
|
Assert.Collection(
|
|
frames,
|
|
frame => AssertFrame.Text(frame, "<span>Hi</span>"));
|
|
}
|
|
|
|
// Integration test for HTML block rewriting
|
|
[Fact]
|
|
public void Render_HtmlBlock_Integration()
|
|
{
|
|
// Arrange
|
|
AdditionalSyntaxTrees.Add(Parse(@"
|
|
using Microsoft.AspNetCore.Blazor;
|
|
using Microsoft.AspNetCore.Blazor.Components;
|
|
namespace Test
|
|
{
|
|
public class MyComponent : BlazorComponent
|
|
{
|
|
[Parameter]
|
|
RenderFragment ChildContent { get; set; }
|
|
}
|
|
}
|
|
"));
|
|
|
|
var component = CompileToComponent(@"
|
|
@addTagHelper *, TestAssembly
|
|
|
|
<html>
|
|
<head><meta><meta></head>
|
|
<body>
|
|
<MyComponent>
|
|
<div><span></span><span></span></div>
|
|
<div>@(""hi"")</div>
|
|
<div><span></span><span></span></div>
|
|
<div></div>
|
|
<div>@(""hi"")</div>
|
|
<div></div>
|
|
</MyComponent>
|
|
</body>
|
|
</html>");
|
|
|
|
// Act
|
|
var frames = GetRenderTree(component);
|
|
|
|
// Assert: component frames are correct
|
|
Assert.Collection(
|
|
frames,
|
|
frame => AssertFrame.Element(frame, "html", 9, 0),
|
|
frame => AssertFrame.Whitespace(frame, 1),
|
|
frame => AssertFrame.Markup(frame, "<head><meta><meta></head>\n ", 2),
|
|
frame => AssertFrame.Element(frame, "body", 5, 3),
|
|
frame => AssertFrame.Whitespace(frame, 4),
|
|
frame => AssertFrame.Component(frame, "Test.MyComponent", 2, 5),
|
|
frame => AssertFrame.Attribute(frame, RenderTreeBuilder.ChildContent, 6),
|
|
frame => AssertFrame.Whitespace(frame, 16),
|
|
frame => AssertFrame.Whitespace(frame, 17));
|
|
|
|
// Assert: Captured ChildContent frames are correct
|
|
var childFrames = GetFrames((RenderFragment)frames[6].AttributeValue);
|
|
Assert.Collection(
|
|
childFrames,
|
|
frame => AssertFrame.Whitespace(frame, 7),
|
|
frame => AssertFrame.Markup(frame, "<div><span></span><span></span></div>\n ", 8),
|
|
frame => AssertFrame.Element(frame, "div", 2, 9),
|
|
frame => AssertFrame.Text(frame, "hi", 10),
|
|
frame => AssertFrame.Whitespace(frame, 11),
|
|
frame => AssertFrame.Markup(frame, "<div><span></span><span></span></div>\n <div></div>\n ", 12),
|
|
frame => AssertFrame.Element(frame, "div", 2, 13),
|
|
frame => AssertFrame.Text(frame, "hi", 14),
|
|
frame => AssertFrame.Markup(frame, "\n <div></div>\n ", 15));
|
|
}
|
|
|
|
[Fact]
|
|
public void RazorTemplate_CanBeUsedFromComponent()
|
|
{
|
|
// Arrange
|
|
AdditionalSyntaxTrees.Add(Parse(@"
|
|
using Microsoft.AspNetCore.Blazor;
|
|
using Microsoft.AspNetCore.Blazor.Components;
|
|
using Microsoft.AspNetCore.Blazor.RenderTree;
|
|
|
|
namespace Test
|
|
{
|
|
public class Repeater : BlazorComponent
|
|
{
|
|
[Parameter] int Count { get; set; }
|
|
[Parameter] RenderFragment<string> Template { get; set; }
|
|
[Parameter] string Value { get; set; }
|
|
|
|
protected override void BuildRenderTree(RenderTreeBuilder builder)
|
|
{
|
|
base.BuildRenderTree(builder);
|
|
for (var i = 0; i < Count; i++)
|
|
{
|
|
builder.AddContent(i, Template, Value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
"));
|
|
|
|
var component = CompileToComponent(@"
|
|
@addTagHelper ""*, TestAssembly""
|
|
@{ RenderFragment<string> template = (context) => @<div>@context.ToLower()</div>; }
|
|
<Repeater Count=3 Value=""Hello, World!"" Template=""template"" />
|
|
");
|
|
|
|
// Act
|
|
var frames = GetRenderTree(component);
|
|
|
|
// Assert
|
|
Assert.Collection(
|
|
frames,
|
|
frame => AssertFrame.Component(frame, "Test.Repeater", 4, 2),
|
|
frame => AssertFrame.Attribute(frame, "Count", typeof(int), 3),
|
|
frame => AssertFrame.Attribute(frame, "Value", typeof(string), 4),
|
|
frame => AssertFrame.Attribute(frame, "Template", typeof(RenderFragment<string>), 5),
|
|
frame => AssertFrame.Element(frame, "div", 2, 0),
|
|
frame => AssertFrame.Text(frame, "hello, world!", 1),
|
|
frame => AssertFrame.Element(frame, "div", 2, 0),
|
|
frame => AssertFrame.Text(frame, "hello, world!", 1),
|
|
frame => AssertFrame.Element(frame, "div", 2, 0),
|
|
frame => AssertFrame.Text(frame, "hello, world!", 1));
|
|
}
|
|
}
|
|
}
|