[Components] Create a renderer to render components into HTML (#4463)
* Adds an HtmlRenderer to Microsoft.AspNetCore.Components * It renders the component into a list of strings. * It only handles synchronous rendering.
This commit is contained in:
parent
a7f6154520
commit
436b5461ad
|
|
@ -1,7 +1,7 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.27130.2010
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.28315.86
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{F5FDD4E5-6A52-4A86-BE5E-5E42CB1DC8DA}"
|
||||
EndProject
|
||||
|
|
|
|||
|
|
@ -9,7 +9,9 @@
|
|||
<InternalAspNetCoreSdkPackageVersion>3.0.0-alpha1-20181011.3</InternalAspNetCoreSdkPackageVersion>
|
||||
<MicrosoftAspNetCoreBenchmarkRunnerSourcesPackageVersion>2.2.0-preview1-34576</MicrosoftAspNetCoreBenchmarkRunnerSourcesPackageVersion>
|
||||
<MicrosoftAspNetCoreAppPackageVersion>3.0.0-alpha1-10605</MicrosoftAspNetCoreAppPackageVersion>
|
||||
<MicrosoftAspNetCoreHtmlAbstractionsPackageVersion>3.0.0-alpha1-10605</MicrosoftAspNetCoreHtmlAbstractionsPackageVersion>
|
||||
<MicrosoftAspNetCoreRazorDesignPackageVersion>3.0.0-alpha1-10605</MicrosoftAspNetCoreRazorDesignPackageVersion>
|
||||
<MicrosoftExtensionsDependencyInjectionPackageVersion>3.0.0-alpha1-10605</MicrosoftExtensionsDependencyInjectionPackageVersion>
|
||||
<MicrosoftNETCoreAppPackageVersion>3.0.0-preview1-26907-05</MicrosoftNETCoreAppPackageVersion>
|
||||
<SignalRPackageVersion>3.0.0-alpha1-10605</SignalRPackageVersion>
|
||||
<TemplateBlazorPackageVersion>0.8.0-preview1-20181122.3</TemplateBlazorPackageVersion>
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
public int ComponentId => _componentId;
|
||||
public IComponent Component => _component;
|
||||
public ComponentState ParentComponentState => _parentComponentState;
|
||||
public RenderTreeBuilder CurrrentRenderTree => _renderTreeBuilderCurrent;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs an instance of <see cref="ComponentState"/>.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,234 @@
|
|||
// 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.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Components.RenderTree;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Rendering
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="Renderer"/> that produces HTML.
|
||||
/// </summary>
|
||||
public class HtmlRenderer : Renderer
|
||||
{
|
||||
private static readonly HashSet<string> SelfClosingElements = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source", "track", "wbr"
|
||||
};
|
||||
|
||||
private readonly Func<string, string> _htmlEncoder;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="HtmlRenderer"/>.
|
||||
/// </summary>
|
||||
/// <param name="serviceProvider">The <see cref="IServiceProvider"/> to use to instantiate components.</param>
|
||||
/// <param name="htmlEncoder">A <see cref="Func{T, TResult}"/> that will HTML encode the given string.</param>
|
||||
public HtmlRenderer(IServiceProvider serviceProvider, Func<string, string> htmlEncoder) : base(serviceProvider)
|
||||
{
|
||||
_htmlEncoder = htmlEncoder;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Task UpdateDisplayAsync(in RenderBatch renderBatch)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Renders a component into a sequence of <see cref="string"/> fragments that represent the textual representation
|
||||
/// of the HTML produced by the component.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the <see cref="IComponent"/>.</typeparam>
|
||||
/// <param name="initialParameters">A <see cref="ParameterCollection"/> with the initial parameters to render the component.</param>
|
||||
/// <returns>A sequence of <see cref="string"/> fragments that represent the HTML text of the component.</returns>
|
||||
public IEnumerable<string> RenderComponent<T>(ParameterCollection initialParameters) where T : IComponent
|
||||
{
|
||||
return RenderComponent(typeof(T), initialParameters);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Renders a component into a sequence of <see cref="string"/> fragments that represent the textual representation
|
||||
/// of the HTML produced by the component.
|
||||
/// </summary>
|
||||
/// <param name="componentType">The type of the <see cref="IComponent"/>.</param>
|
||||
/// <param name="initialParameters">A <see cref="ParameterCollection"/> with the initial parameters to render the component.</param>
|
||||
/// <returns>A sequence of <see cref="string"/> fragments that represent the HTML text of the component.</returns>
|
||||
private IEnumerable<string> RenderComponent(Type componentType, ParameterCollection initialParameters)
|
||||
{
|
||||
var frames = CreateInitialRender(componentType, initialParameters);
|
||||
|
||||
if (frames.Count == 0)
|
||||
{
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
else
|
||||
{
|
||||
var result = new List<string>();
|
||||
var newPosition = RenderFrames(result, frames, 0, frames.Count);
|
||||
Debug.Assert(newPosition == frames.Count);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private int RenderFrames(List<string> result, ArrayRange<RenderTreeFrame> frames, int position, int maxElements)
|
||||
{
|
||||
var nextPosition = position;
|
||||
var endPosition = position + maxElements;
|
||||
while (position < endPosition)
|
||||
{
|
||||
nextPosition = RenderCore(result, frames, position, maxElements);
|
||||
if (position == nextPosition)
|
||||
{
|
||||
throw new InvalidOperationException("We didn't consume any input.");
|
||||
}
|
||||
position = nextPosition;
|
||||
}
|
||||
|
||||
return nextPosition;
|
||||
}
|
||||
|
||||
private int RenderCore(
|
||||
List<string> result,
|
||||
ArrayRange<RenderTreeFrame> frames,
|
||||
int position,
|
||||
int length)
|
||||
{
|
||||
ref var frame = ref frames.Array[position];
|
||||
switch (frame.FrameType)
|
||||
{
|
||||
case RenderTreeFrameType.Element:
|
||||
return RenderElement(result, frames, position);
|
||||
case RenderTreeFrameType.Attribute:
|
||||
return RenderAttributes(result, frames, position, 1);
|
||||
case RenderTreeFrameType.Text:
|
||||
result.Add(_htmlEncoder(frame.TextContent));
|
||||
return ++position;
|
||||
case RenderTreeFrameType.Markup:
|
||||
result.Add(frame.MarkupContent);
|
||||
return ++position;
|
||||
case RenderTreeFrameType.Component:
|
||||
return RenderChildComponent(result, frames, position);
|
||||
case RenderTreeFrameType.Region:
|
||||
return RenderFrames(result, frames, position + 1, frame.RegionSubtreeLength - 1);
|
||||
case RenderTreeFrameType.ElementReferenceCapture:
|
||||
case RenderTreeFrameType.ComponentReferenceCapture:
|
||||
return ++position;
|
||||
default:
|
||||
throw new InvalidOperationException($"Invalid element frame type '{frame.FrameType}'.");
|
||||
}
|
||||
}
|
||||
|
||||
private int RenderChildComponent(
|
||||
List<string> result,
|
||||
ArrayRange<RenderTreeFrame> frames,
|
||||
int position)
|
||||
{
|
||||
ref var frame = ref frames.Array[position];
|
||||
var childFrames = GetCurrentRenderTreeFrames(frame.ComponentId);
|
||||
RenderFrames(result, childFrames, 0, childFrames.Count);
|
||||
return position + frame.ComponentSubtreeLength;
|
||||
}
|
||||
|
||||
private int RenderElement(
|
||||
List<string> result,
|
||||
ArrayRange<RenderTreeFrame> frames,
|
||||
int position)
|
||||
{
|
||||
ref var frame = ref frames.Array[position];
|
||||
result.Add("<");
|
||||
result.Add(frame.ElementName);
|
||||
var afterAttributes = RenderAttributes(result, frames, position + 1, frame.ElementSubtreeLength - 1);
|
||||
var remainingElements = frame.ElementSubtreeLength + position - afterAttributes;
|
||||
if (remainingElements > 0)
|
||||
{
|
||||
result.Add(">");
|
||||
var afterElement = RenderChildren(result, frames, afterAttributes, remainingElements);
|
||||
result.Add("</");
|
||||
result.Add(frame.ElementName);
|
||||
result.Add(">");
|
||||
Debug.Assert(afterElement == position + frame.ElementSubtreeLength);
|
||||
return afterElement;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (SelfClosingElements.Contains(frame.ElementName))
|
||||
{
|
||||
result.Add(" />");
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Add(">");
|
||||
result.Add("</");
|
||||
result.Add(frame.ElementName);
|
||||
result.Add(">");
|
||||
}
|
||||
Debug.Assert(afterAttributes == position + frame.ElementSubtreeLength);
|
||||
return afterAttributes;
|
||||
}
|
||||
}
|
||||
|
||||
private int RenderChildren(List<string> result, ArrayRange<RenderTreeFrame> frames, int position, int maxElements)
|
||||
{
|
||||
if (maxElements == 0)
|
||||
{
|
||||
return position;
|
||||
}
|
||||
|
||||
return RenderFrames(result, frames, position, maxElements);
|
||||
}
|
||||
|
||||
private int RenderAttributes(
|
||||
List<string> result,
|
||||
ArrayRange<RenderTreeFrame> frames, int position, int maxElements)
|
||||
{
|
||||
if (maxElements == 0)
|
||||
{
|
||||
return position;
|
||||
}
|
||||
|
||||
for (var i = 0; i < maxElements; i++)
|
||||
{
|
||||
var candidateIndex = position + i;
|
||||
ref var frame = ref frames.Array[candidateIndex];
|
||||
if (frame.FrameType != RenderTreeFrameType.Attribute)
|
||||
{
|
||||
return candidateIndex;
|
||||
}
|
||||
|
||||
switch (frame.AttributeValue)
|
||||
{
|
||||
case bool flag when flag:
|
||||
result.Add(" ");
|
||||
result.Add(frame.AttributeName);
|
||||
break;
|
||||
case string value:
|
||||
result.Add(" ");
|
||||
result.Add(frame.AttributeName);
|
||||
result.Add("=");
|
||||
result.Add("\"");
|
||||
result.Add(_htmlEncoder(value));
|
||||
result.Add("\"");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return position + maxElements;
|
||||
}
|
||||
|
||||
private ArrayRange<RenderTreeFrame> CreateInitialRender(Type componentType, ParameterCollection initialParameters)
|
||||
{
|
||||
var component = InstantiateComponent(componentType);
|
||||
var componentId = AssignRootComponentId(component);
|
||||
|
||||
RenderRootComponent(componentId, initialParameters);
|
||||
|
||||
return GetCurrentRenderTreeFrames(componentId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -53,6 +53,13 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
protected int AssignRootComponentId(IComponent component)
|
||||
=> AttachAndInitComponent(component, -1).ComponentId;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current render tree for a given component.
|
||||
/// </summary>
|
||||
/// <param name="componentId">The id for the component.</param>
|
||||
/// <returns>The <see cref="RenderTreeBuilder"/> representing the current render tree.</returns>
|
||||
private protected ArrayRange<RenderTreeFrame> GetCurrentRenderTreeFrames(int componentId) => GetRequiredComponentState(componentId).CurrrentRenderTree.GetFrames();
|
||||
|
||||
/// <summary>
|
||||
/// Performs the first render for a root component. After this, the root component
|
||||
/// makes its own decisions about when to re-render, so there is no need to call
|
||||
|
|
@ -65,6 +72,19 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
.SetDirectParameters(ParameterCollection.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs the first render for a root component. After this, the root component
|
||||
/// makes its own decisions about when to re-render, so there is no need to call
|
||||
/// this more than once.
|
||||
/// </summary>
|
||||
/// <param name="componentId">The ID returned by <see cref="AssignRootComponentId(IComponent)"/>.</param>
|
||||
/// <param name="initialParameters">The <see cref="ParameterCollection"/>with the initial parameters to use for rendering.</param>
|
||||
protected void RenderRootComponent(int componentId, ParameterCollection initialParameters)
|
||||
{
|
||||
GetRequiredComponentState(componentId)
|
||||
.SetDirectParameters(initialParameters);
|
||||
}
|
||||
|
||||
private ComponentState AttachAndInitComponent(IComponent component, int parentComponentId)
|
||||
{
|
||||
var componentId = _nextComponentId++;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||
|
|
@ -11,6 +11,8 @@
|
|||
<PackageReference Include="xunit" Version="2.3.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
|
||||
<DotNetCliToolReference Include="dotnet-xunit" Version="2.3.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="$(MicrosoftExtensionsDependencyInjectionPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Html.Abstractions" Version="$(MicrosoftAspNetCoreHtmlAbstractionsPackageVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,447 @@
|
|||
// 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.Text.Encodings.Web;
|
||||
using Microsoft.AspNetCore.Components.RenderTree;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Rendering
|
||||
{
|
||||
public class HtmlRendererTests
|
||||
{
|
||||
private static readonly Func<string, string> _encoder = (string t) => HtmlEncoder.Default.Encode(t);
|
||||
|
||||
[Fact]
|
||||
public void RenderComponent_CanRenderEmptyElement()
|
||||
{
|
||||
// Arrange
|
||||
var expectedHtml = new[] { "<", "p", ">", "</", "p", ">" };
|
||||
var serviceProvider = new ServiceCollection().AddSingleton(new RenderFragment(rtb =>
|
||||
{
|
||||
rtb.OpenElement(0, "p");
|
||||
rtb.CloseElement();
|
||||
})).BuildServiceProvider();
|
||||
var htmlRenderer = new HtmlRenderer(serviceProvider, _encoder);
|
||||
|
||||
// Act
|
||||
var result = htmlRenderer.RenderComponent<TestComponent>(ParameterCollection.Empty);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedHtml, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RenderComponent_CanRenderSimpleComponent()
|
||||
{
|
||||
// Arrange
|
||||
var expectedHtml = new[] { "<", "p", ">", "Hello world!", "</", "p", ">" };
|
||||
var serviceProvider = new ServiceCollection().AddSingleton(new RenderFragment(rtb =>
|
||||
{
|
||||
rtb.OpenElement(0, "p");
|
||||
rtb.AddContent(1, "Hello world!");
|
||||
rtb.CloseElement();
|
||||
})).BuildServiceProvider();
|
||||
var htmlRenderer = new HtmlRenderer(serviceProvider, _encoder);
|
||||
|
||||
// Act
|
||||
var result = htmlRenderer.RenderComponent<TestComponent>(ParameterCollection.Empty);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedHtml, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RenderComponent_HtmlEncodesContent()
|
||||
{
|
||||
// Arrange
|
||||
var expectedHtml = new[] { "<", "p", ">", "<Hello world!>", "</", "p", ">" };
|
||||
var serviceProvider = new ServiceCollection().AddSingleton(new RenderFragment(rtb =>
|
||||
{
|
||||
rtb.OpenElement(0, "p");
|
||||
rtb.AddContent(1, "<Hello world!>");
|
||||
rtb.CloseElement();
|
||||
})).BuildServiceProvider();
|
||||
var htmlRenderer = new HtmlRenderer(serviceProvider, _encoder);
|
||||
|
||||
// Act
|
||||
var result = htmlRenderer.RenderComponent<TestComponent>(ParameterCollection.Empty);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedHtml, result);
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void RenderComponent_DoesNotEncodeMarkup()
|
||||
{
|
||||
// Arrange
|
||||
var expectedHtml = new[] { "<", "p", ">", "<span>Hello world!</span>", "</", "p", ">" };
|
||||
var serviceProvider = new ServiceCollection().AddSingleton(new RenderFragment(rtb =>
|
||||
{
|
||||
rtb.OpenElement(0, "p");
|
||||
rtb.AddMarkupContent(1, "<span>Hello world!</span>");
|
||||
rtb.CloseElement();
|
||||
})).BuildServiceProvider();
|
||||
var htmlRenderer = new HtmlRenderer(serviceProvider, _encoder);
|
||||
|
||||
// Act
|
||||
var result = htmlRenderer.RenderComponent<TestComponent>(ParameterCollection.Empty);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedHtml, result);
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void RenderComponent_CanRenderWithAttributes()
|
||||
{
|
||||
// Arrange
|
||||
var expectedHtml = new[] { "<", "p", " ", "class", "=", "\"", "lead", "\"", ">", "Hello world!", "</", "p", ">" };
|
||||
var serviceProvider = new ServiceCollection().AddSingleton(new RenderFragment(rtb =>
|
||||
{
|
||||
rtb.OpenElement(0, "p");
|
||||
rtb.AddAttribute(1, "class", "lead");
|
||||
rtb.AddContent(2, "Hello world!");
|
||||
rtb.CloseElement();
|
||||
})).BuildServiceProvider();
|
||||
|
||||
var htmlRenderer = new HtmlRenderer(serviceProvider, _encoder);
|
||||
|
||||
// Act
|
||||
var result = htmlRenderer.RenderComponent<TestComponent>(ParameterCollection.Empty);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedHtml, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RenderComponent_HtmlEncodesAttributeValues()
|
||||
{
|
||||
// Arrange
|
||||
var expectedHtml = new[] { "<", "p", " ", "class", "=", "\"", "<lead", "\"", ">", "Hello world!", "</", "p", ">" };
|
||||
var serviceProvider = new ServiceCollection().AddSingleton(new RenderFragment(rtb =>
|
||||
{
|
||||
rtb.OpenElement(0, "p");
|
||||
rtb.AddAttribute(1, "class", "<lead");
|
||||
rtb.AddContent(2, "Hello world!");
|
||||
rtb.CloseElement();
|
||||
})).BuildServiceProvider();
|
||||
|
||||
var htmlRenderer = new HtmlRenderer(serviceProvider, _encoder);
|
||||
|
||||
// Act
|
||||
var result = htmlRenderer.RenderComponent<TestComponent>(ParameterCollection.Empty);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedHtml, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RenderComponent_CanRenderBooleanAttributes()
|
||||
{
|
||||
// Arrange
|
||||
var expectedHtml = new[] { "<", "input", " ", "disabled", " />" };
|
||||
var serviceProvider = new ServiceCollection().AddSingleton(new RenderFragment(rtb =>
|
||||
{
|
||||
rtb.OpenElement(0, "input");
|
||||
rtb.AddAttribute(1, "disabled", true);
|
||||
rtb.CloseElement();
|
||||
})).BuildServiceProvider();
|
||||
|
||||
var htmlRenderer = new HtmlRenderer(serviceProvider, _encoder);
|
||||
|
||||
// Act
|
||||
var result = htmlRenderer.RenderComponent<TestComponent>(ParameterCollection.Empty);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedHtml, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RenderComponent_DoesNotRenderBooleanAttributesWhenValueIsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var expectedHtml = new[] { "<", "input", " />" };
|
||||
var serviceProvider = new ServiceCollection().AddSingleton(new RenderFragment(rtb =>
|
||||
{
|
||||
rtb.OpenElement(0, "input");
|
||||
rtb.AddAttribute(1, "disabled", false);
|
||||
rtb.CloseElement();
|
||||
})).BuildServiceProvider();
|
||||
|
||||
var htmlRenderer = new HtmlRenderer(serviceProvider, _encoder);
|
||||
|
||||
// Act
|
||||
var result = htmlRenderer.RenderComponent<TestComponent>(ParameterCollection.Empty);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedHtml, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RenderComponent_CanRenderWithChildren()
|
||||
{
|
||||
// Arrange
|
||||
var expectedHtml = new[] { "<", "p", ">", "<", "span", ">", "Hello world!", "</", "span", ">", "</", "p", ">" };
|
||||
var serviceProvider = new ServiceCollection().AddSingleton(new RenderFragment(rtb =>
|
||||
{
|
||||
rtb.OpenElement(0, "p");
|
||||
rtb.OpenElement(1, "span");
|
||||
rtb.AddContent(2, "Hello world!");
|
||||
rtb.CloseElement();
|
||||
rtb.CloseElement();
|
||||
})).BuildServiceProvider();
|
||||
|
||||
var htmlRenderer = new HtmlRenderer(serviceProvider, _encoder);
|
||||
|
||||
// Act
|
||||
var result = htmlRenderer.RenderComponent<TestComponent>(ParameterCollection.Empty);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedHtml, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RenderComponent_CanRenderWithMultipleChildren()
|
||||
{
|
||||
// Arrange
|
||||
var expectedHtml = new[] { "<", "p", ">",
|
||||
"<", "span", ">", "Hello world!", "</", "span", ">",
|
||||
"<", "span", ">", "Bye Bye world!", "</", "span", ">",
|
||||
"</", "p", ">"
|
||||
};
|
||||
var serviceProvider = new ServiceCollection().AddSingleton(new RenderFragment(rtb =>
|
||||
{
|
||||
rtb.OpenElement(0, "p");
|
||||
rtb.OpenElement(1, "span");
|
||||
rtb.AddContent(2, "Hello world!");
|
||||
rtb.CloseElement();
|
||||
rtb.OpenElement(3, "span");
|
||||
rtb.AddContent(4, "Bye Bye world!");
|
||||
rtb.CloseElement();
|
||||
rtb.CloseElement();
|
||||
})).BuildServiceProvider();
|
||||
|
||||
var htmlRenderer = new HtmlRenderer(serviceProvider, _encoder);
|
||||
|
||||
// Act
|
||||
var result = htmlRenderer.RenderComponent<TestComponent>(ParameterCollection.Empty);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedHtml, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RenderComponent_CanRenderComponentWithChildrenComponents()
|
||||
{
|
||||
// Arrange
|
||||
var expectedHtml = new[] {
|
||||
"<", "p", ">", "<", "span", ">", "Hello world!", "</", "span", ">", "</", "p", ">",
|
||||
"<", "span", ">", "Child content!", "</", "span", ">"
|
||||
};
|
||||
var serviceProvider = new ServiceCollection().AddSingleton(new RenderFragment(rtb =>
|
||||
{
|
||||
rtb.OpenElement(0, "p");
|
||||
rtb.OpenElement(1, "span");
|
||||
rtb.AddContent(2, "Hello world!");
|
||||
rtb.CloseElement();
|
||||
rtb.CloseElement();
|
||||
rtb.OpenComponent(3, typeof(ChildComponent));
|
||||
rtb.AddAttribute(4, "Value", "Child content!");
|
||||
rtb.CloseComponent();
|
||||
})).BuildServiceProvider();
|
||||
|
||||
var htmlRenderer = new HtmlRenderer(serviceProvider, _encoder);
|
||||
|
||||
// Act
|
||||
var result = htmlRenderer.RenderComponent<TestComponent>(ParameterCollection.Empty);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedHtml, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RenderComponent_ComponentReferenceNoops()
|
||||
{
|
||||
// Arrange
|
||||
var expectedHtml = new[] {
|
||||
"<", "p", ">", "<", "span", ">", "Hello world!", "</", "span", ">", "</", "p", ">",
|
||||
"<", "span", ">", "Child content!", "</", "span", ">"
|
||||
};
|
||||
var serviceProvider = new ServiceCollection().AddSingleton(new RenderFragment(rtb =>
|
||||
{
|
||||
rtb.OpenElement(0, "p");
|
||||
rtb.OpenElement(1, "span");
|
||||
rtb.AddContent(2, "Hello world!");
|
||||
rtb.CloseElement();
|
||||
rtb.CloseElement();
|
||||
rtb.OpenComponent(3, typeof(ChildComponent));
|
||||
rtb.AddAttribute(4, "Value", "Child content!");
|
||||
rtb.AddComponentReferenceCapture(5, cr => { });
|
||||
rtb.CloseComponent();
|
||||
})).BuildServiceProvider();
|
||||
|
||||
var htmlRenderer = new HtmlRenderer(serviceProvider, _encoder);
|
||||
|
||||
// Act
|
||||
var result = htmlRenderer.RenderComponent<TestComponent>(ParameterCollection.Empty);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedHtml, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RenderComponent_CanPassParameters()
|
||||
{
|
||||
// Arrange
|
||||
var expectedHtml = new[] {
|
||||
"<", "p", ">", "<", "input", " ", "value", "=", "\"", "5", "\"", " />", "</", "p", ">" };
|
||||
|
||||
RenderFragment Content(ParameterCollection pc) => new RenderFragment((RenderTreeBuilder rtb) =>
|
||||
{
|
||||
rtb.OpenElement(0, "p");
|
||||
rtb.OpenElement(1, "input");
|
||||
rtb.AddAttribute(2, "change", pc.GetValueOrDefault<Action<UIChangeEventArgs>>("update"));
|
||||
rtb.AddAttribute(3, "value", pc.GetValueOrDefault<int>("value"));
|
||||
rtb.CloseElement();
|
||||
rtb.CloseElement();
|
||||
});
|
||||
|
||||
var serviceProvider = new ServiceCollection()
|
||||
.AddSingleton(new Func<ParameterCollection, RenderFragment>(Content))
|
||||
.BuildServiceProvider();
|
||||
|
||||
var htmlRenderer = new HtmlRenderer(serviceProvider, _encoder);
|
||||
Action<UIChangeEventArgs> change = (UIChangeEventArgs changeArgs) => throw new InvalidOperationException();
|
||||
|
||||
// Act
|
||||
var result = htmlRenderer.RenderComponent<ComponentWithParameters>(
|
||||
new ParameterCollection(new[] {
|
||||
RenderTreeFrame.Element(0,string.Empty),
|
||||
RenderTreeFrame.Attribute(1,"update",change),
|
||||
RenderTreeFrame.Attribute(2,"value",5)
|
||||
}, 0));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedHtml, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RenderComponent_CanRenderComponentWithRenderFragmentContent()
|
||||
{
|
||||
// Arrange
|
||||
var expectedHtml = new[] {
|
||||
"<", "p", ">", "<", "span", ">", "Hello world!", "</", "span", ">", "</", "p", ">" };
|
||||
var serviceProvider = new ServiceCollection().AddSingleton(new RenderFragment(rtb =>
|
||||
{
|
||||
rtb.OpenElement(0, "p");
|
||||
rtb.OpenElement(1, "span");
|
||||
rtb.AddContent(2,
|
||||
// This internally creates a region frame.
|
||||
rf => rf.AddContent(0, "Hello world!"));
|
||||
rtb.CloseElement();
|
||||
rtb.CloseElement();
|
||||
})).BuildServiceProvider();
|
||||
|
||||
var htmlRenderer = new HtmlRenderer(serviceProvider, _encoder);
|
||||
|
||||
// Act
|
||||
var result = htmlRenderer.RenderComponent<TestComponent>(ParameterCollection.Empty);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedHtml, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RenderComponent_ElementRefsNoops()
|
||||
{
|
||||
// Arrange
|
||||
var expectedHtml = new[] {
|
||||
"<", "p", ">", "<", "span", ">", "Hello world!", "</", "span", ">", "</", "p", ">" };
|
||||
var serviceProvider = new ServiceCollection().AddSingleton(new RenderFragment(rtb =>
|
||||
{
|
||||
rtb.OpenElement(0, "p");
|
||||
rtb.AddElementReferenceCapture(1, er => { });
|
||||
rtb.OpenElement(2, "span");
|
||||
rtb.AddContent(3,
|
||||
// This internally creates a region frame.
|
||||
rf => rf.AddContent(0, "Hello world!"));
|
||||
rtb.CloseElement();
|
||||
rtb.CloseElement();
|
||||
})).BuildServiceProvider();
|
||||
|
||||
var htmlRenderer = new HtmlRenderer(serviceProvider, _encoder);
|
||||
|
||||
// Act
|
||||
var result = htmlRenderer.RenderComponent<TestComponent>(ParameterCollection.Empty);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedHtml, result);
|
||||
}
|
||||
|
||||
private class ComponentWithParameters : IComponent
|
||||
{
|
||||
public RenderHandle RenderHandle { get; private set; }
|
||||
|
||||
public void Init(RenderHandle renderHandle)
|
||||
{
|
||||
RenderHandle = renderHandle;
|
||||
}
|
||||
|
||||
[Inject]
|
||||
Func<ParameterCollection, RenderFragment> CreateRenderFragment { get; set; }
|
||||
|
||||
public void SetParameters(ParameterCollection parameters)
|
||||
{
|
||||
RenderHandle.Render(CreateRenderFragment(parameters));
|
||||
}
|
||||
}
|
||||
|
||||
private class ChildComponent : IComponent
|
||||
{
|
||||
private RenderHandle _renderHandle;
|
||||
|
||||
public void Init(RenderHandle renderHandle)
|
||||
{
|
||||
_renderHandle = renderHandle;
|
||||
}
|
||||
|
||||
public void SetParameters(ParameterCollection parameters)
|
||||
{
|
||||
_renderHandle.Render(CreateRenderFragment(parameters));
|
||||
}
|
||||
|
||||
private RenderFragment CreateRenderFragment(ParameterCollection parameters)
|
||||
{
|
||||
return RenderFragment;
|
||||
|
||||
void RenderFragment(RenderTreeBuilder rtb)
|
||||
{
|
||||
rtb.OpenElement(1, "span");
|
||||
rtb.AddContent(2, parameters.GetValueOrDefault<string>("Value"));
|
||||
rtb.CloseElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class TestComponent : IComponent
|
||||
{
|
||||
private RenderHandle _renderHandle;
|
||||
|
||||
[Inject]
|
||||
public RenderFragment Fragment { get; set; }
|
||||
|
||||
public void Init(RenderHandle renderHandle)
|
||||
{
|
||||
_renderHandle = renderHandle;
|
||||
}
|
||||
|
||||
public void SetParameters(ParameterCollection parameters)
|
||||
{
|
||||
_renderHandle.Render(Fragment);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue