Unit tests for Renderer
This commit is contained in:
parent
a729a8d5c5
commit
34d3eb5b72
|
|
@ -0,0 +1,56 @@
|
|||
// 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.Blazor.Components;
|
||||
using Microsoft.Blazor.RenderTree;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.Blazor.Test
|
||||
{
|
||||
public static class AssertNode
|
||||
{
|
||||
public static void Text(RenderTreeNode node, string textContent)
|
||||
{
|
||||
Assert.Equal(RenderTreeNodeType.Text, node.NodeType);
|
||||
Assert.Equal(textContent, node.TextContent);
|
||||
Assert.Equal(0, node.ElementDescendantsEndIndex);
|
||||
}
|
||||
|
||||
public static void Element(RenderTreeNode node, string elementName, int descendantsEndIndex)
|
||||
{
|
||||
Assert.Equal(RenderTreeNodeType.Element, node.NodeType);
|
||||
Assert.Equal(elementName, node.ElementName);
|
||||
Assert.Equal(descendantsEndIndex, node.ElementDescendantsEndIndex);
|
||||
}
|
||||
|
||||
public static void Attribute(RenderTreeNode node, string attributeName)
|
||||
{
|
||||
Assert.Equal(RenderTreeNodeType.Attribute, node.NodeType);
|
||||
Assert.Equal(attributeName, node.AttributeName);
|
||||
}
|
||||
|
||||
public static void Attribute(RenderTreeNode node, string attributeName, string attributeValue)
|
||||
{
|
||||
AssertNode.Attribute(node, attributeName);
|
||||
Assert.Equal(attributeValue, node.AttributeValue);
|
||||
}
|
||||
|
||||
public static void Attribute(RenderTreeNode node, string attributeName, UIEventHandler attributeEventHandlerValue)
|
||||
{
|
||||
AssertNode.Attribute(node, attributeName);
|
||||
Assert.Equal(attributeEventHandlerValue, node.AttributeEventHandlerValue);
|
||||
}
|
||||
|
||||
public static void Component<T>(RenderTreeNode node) where T : IComponent
|
||||
{
|
||||
Assert.Equal(RenderTreeNodeType.Component, node.NodeType);
|
||||
|
||||
// Currently, we instantiate child components during the tree building phase.
|
||||
// Later this will change so it happens during the tree diffing phase, so this
|
||||
// logic will need to change. It will need to verify that we're tracking the
|
||||
// information needed to instantiate the component.
|
||||
Assert.NotNull(node.Component);
|
||||
Assert.IsType<T>(node.Component);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -48,8 +48,8 @@ namespace Microsoft.Blazor.Test
|
|||
var nodes = builder.GetNodes();
|
||||
Assert.Equal(0, nodes.Offset);
|
||||
Assert.Collection(nodes,
|
||||
node => AssertText(node, "First item"),
|
||||
node => AssertText(node, "Second item"));
|
||||
node => AssertNode.Text(node, "First item"),
|
||||
node => AssertNode.Text(node, "Second item"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -63,7 +63,7 @@ namespace Microsoft.Blazor.Test
|
|||
|
||||
// Assert
|
||||
var node = builder.GetNodes().Single();
|
||||
AssertElement(node, "my element", 0);
|
||||
AssertNode.Element(node, "my element", 0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -80,7 +80,7 @@ namespace Microsoft.Blazor.Test
|
|||
// Assert
|
||||
var nodes = builder.GetNodes();
|
||||
Assert.Equal(2, nodes.Count);
|
||||
AssertElement(nodes[1], "my element", 1);
|
||||
AssertNode.Element(nodes[1], "my element", 1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -99,7 +99,7 @@ namespace Microsoft.Blazor.Test
|
|||
// Assert
|
||||
var nodes = builder.GetNodes();
|
||||
Assert.Equal(4, nodes.Count);
|
||||
AssertElement(nodes[0], "my element", 2);
|
||||
AssertNode.Element(nodes[0], "my element", 2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -128,18 +128,18 @@ namespace Microsoft.Blazor.Test
|
|||
|
||||
// Assert
|
||||
Assert.Collection(builder.GetNodes(),
|
||||
node => AssertText(node, "standalone text 1"),
|
||||
node => AssertElement(node, "root", 10),
|
||||
node => AssertText(node, "root text 1"),
|
||||
node => AssertText(node, "root text 2"),
|
||||
node => AssertElement(node, "child", 8),
|
||||
node => AssertText(node, "child text"),
|
||||
node => AssertElement(node, "grandchild", 8),
|
||||
node => AssertText(node, "grandchild text 1"),
|
||||
node => AssertText(node, "grandchild text 2"),
|
||||
node => AssertText(node, "root text 3"),
|
||||
node => AssertElement(node, "child 2", 10),
|
||||
node => AssertText(node, "standalone text 2"));
|
||||
node => AssertNode.Text(node, "standalone text 1"),
|
||||
node => AssertNode.Element(node, "root", 10),
|
||||
node => AssertNode.Text(node, "root text 1"),
|
||||
node => AssertNode.Text(node, "root text 2"),
|
||||
node => AssertNode.Element(node, "child", 8),
|
||||
node => AssertNode.Text(node, "child text"),
|
||||
node => AssertNode.Element(node, "grandchild", 8),
|
||||
node => AssertNode.Text(node, "grandchild text 1"),
|
||||
node => AssertNode.Text(node, "grandchild text 2"),
|
||||
node => AssertNode.Text(node, "root text 3"),
|
||||
node => AssertNode.Element(node, "child 2", 10),
|
||||
node => AssertNode.Text(node, "standalone text 2"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -161,12 +161,12 @@ namespace Microsoft.Blazor.Test
|
|||
|
||||
// Assert
|
||||
Assert.Collection(builder.GetNodes(),
|
||||
node => AssertElement(node, "myelement", 5),
|
||||
node => AssertAttribute(node, "attribute1", "value 1"),
|
||||
node => AssertAttribute(node, "attribute2", "value 2"),
|
||||
node => AssertElement(node, "child", 5),
|
||||
node => AssertAttribute(node, "childevent", eventHandler),
|
||||
node => AssertText(node, "some text"));
|
||||
node => AssertNode.Element(node, "myelement", 5),
|
||||
node => AssertNode.Attribute(node, "attribute1", "value 1"),
|
||||
node => AssertNode.Attribute(node, "attribute2", "value 2"),
|
||||
node => AssertNode.Element(node, "child", 5),
|
||||
node => AssertNode.Attribute(node, "childevent", eventHandler),
|
||||
node => AssertNode.Text(node, "some text"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -242,12 +242,12 @@ namespace Microsoft.Blazor.Test
|
|||
|
||||
// Assert
|
||||
Assert.Collection(builder.GetNodes(),
|
||||
node => AssertElement(node, "parent", 5),
|
||||
node => AssertComponent<TestComponent>(node),
|
||||
node => AssertAttribute(node, "child1attribute1", "A"),
|
||||
node => AssertAttribute(node, "child1attribute2", "B"),
|
||||
node => AssertComponent<TestComponent>(node),
|
||||
node => AssertAttribute(node, "child2attribute", "C"));
|
||||
node => AssertNode.Element(node, "parent", 5),
|
||||
node => AssertNode.Component<TestComponent>(node),
|
||||
node => AssertNode.Attribute(node, "child1attribute1", "A"),
|
||||
node => AssertNode.Attribute(node, "child1attribute2", "B"),
|
||||
node => AssertNode.Component<TestComponent>(node),
|
||||
node => AssertNode.Attribute(node, "child2attribute", "C"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -267,50 +267,6 @@ namespace Microsoft.Blazor.Test
|
|||
Assert.Empty(builder.GetNodes());
|
||||
}
|
||||
|
||||
void AssertText(RenderTreeNode node, string textContent)
|
||||
{
|
||||
Assert.Equal(RenderTreeNodeType.Text, node.NodeType);
|
||||
Assert.Equal(textContent, node.TextContent);
|
||||
Assert.Equal(0, node.ElementDescendantsEndIndex);
|
||||
}
|
||||
|
||||
void AssertElement(RenderTreeNode node, string elementName, int descendantsEndIndex)
|
||||
{
|
||||
Assert.Equal(RenderTreeNodeType.Element, node.NodeType);
|
||||
Assert.Equal(elementName, node.ElementName);
|
||||
Assert.Equal(descendantsEndIndex, node.ElementDescendantsEndIndex);
|
||||
}
|
||||
|
||||
void AssertAttribute(RenderTreeNode node, string attributeName)
|
||||
{
|
||||
Assert.Equal(RenderTreeNodeType.Attribute, node.NodeType);
|
||||
Assert.Equal(attributeName, node.AttributeName);
|
||||
}
|
||||
|
||||
void AssertAttribute(RenderTreeNode node, string attributeName, string attributeValue)
|
||||
{
|
||||
AssertAttribute(node, attributeName);
|
||||
Assert.Equal(attributeValue, node.AttributeValue);
|
||||
}
|
||||
|
||||
void AssertAttribute(RenderTreeNode node, string attributeName, UIEventHandler attributeEventHandlerValue)
|
||||
{
|
||||
AssertAttribute(node, attributeName);
|
||||
Assert.Equal(attributeEventHandlerValue, node.AttributeEventHandlerValue);
|
||||
}
|
||||
|
||||
private void AssertComponent<T>(RenderTreeNode node) where T: IComponent
|
||||
{
|
||||
Assert.Equal(RenderTreeNodeType.Component, node.NodeType);
|
||||
|
||||
// Currently, we instantiate child components during the tree building phase.
|
||||
// Later this will change so it happens during the tree diffing phase, so this
|
||||
// logic will need to change. It will need to verify that we're tracking the
|
||||
// information needed to instantiate the component.
|
||||
Assert.NotNull(node.Component);
|
||||
Assert.IsType<T>(node.Component);
|
||||
}
|
||||
|
||||
private class TestComponent : IComponent
|
||||
{
|
||||
public void BuildRenderTree(RenderTreeBuilder builder)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
// 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.Linq;
|
||||
using Microsoft.Blazor.Components;
|
||||
using Microsoft.Blazor.Rendering;
|
||||
using Microsoft.Blazor.RenderTree;
|
||||
|
|
@ -11,6 +13,218 @@ namespace Microsoft.Blazor.Test
|
|||
{
|
||||
public class RendererTest
|
||||
{
|
||||
[Fact]
|
||||
public void CanRenderTopLevelComponents()
|
||||
{
|
||||
// Arrange
|
||||
var renderer = new TestRenderer();
|
||||
var component = new TestComponent(builder =>
|
||||
{
|
||||
builder.OpenElement("my element");
|
||||
builder.AddText("some text");
|
||||
builder.CloseElement();
|
||||
});
|
||||
|
||||
// Act
|
||||
var componentId = renderer.AssignComponentId(component);
|
||||
renderer.RenderComponent(componentId);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(renderer.RenderTreesByComponentId[componentId],
|
||||
node => AssertNode.Element(node, "my element", 1),
|
||||
node => AssertNode.Text(node, "some text"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanRenderNestedComponents()
|
||||
{
|
||||
// Arrange
|
||||
var renderer = new TestRenderer();
|
||||
var component = new TestComponent(builder =>
|
||||
{
|
||||
builder.AddText("Hello");
|
||||
builder.AddComponent<MessageComponent>();
|
||||
});
|
||||
|
||||
// Act/Assert
|
||||
var componentId = renderer.AssignComponentId(component);
|
||||
renderer.RenderComponent(componentId);
|
||||
var componentNode = renderer.RenderTreesByComponentId[componentId]
|
||||
.Single(node => node.NodeType == RenderTreeNodeType.Component);
|
||||
var nestedComponentId = componentNode.ComponentId;
|
||||
|
||||
// The nested component exists
|
||||
Assert.IsType<MessageComponent>(componentNode.Component);
|
||||
((MessageComponent)(componentNode.Component)).Message = "Nested component output";
|
||||
|
||||
// It isn't rendered until the consumer asks for it to be
|
||||
Assert.False(renderer.RenderTreesByComponentId.ContainsKey(nestedComponentId));
|
||||
|
||||
// It can be rendered
|
||||
renderer.RenderComponent(nestedComponentId);
|
||||
Assert.Collection(renderer.RenderTreesByComponentId[nestedComponentId],
|
||||
node => AssertNode.Text(node, "Nested component output"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanReRenderTopLevelComponents()
|
||||
{
|
||||
// Arrange
|
||||
var renderer = new TestRenderer();
|
||||
var component = new MessageComponent { Message = "Initial message" };
|
||||
var componentId = renderer.AssignComponentId(component);
|
||||
|
||||
// Act/Assert: first render
|
||||
renderer.RenderComponent(componentId);
|
||||
Assert.Collection(renderer.RenderTreesByComponentId[componentId],
|
||||
node => AssertNode.Text(node, "Initial message"));
|
||||
|
||||
// Act/Assert: second render
|
||||
component.Message = "Modified message";
|
||||
renderer.RenderComponent(componentId);
|
||||
Assert.Collection(renderer.RenderTreesByComponentId[componentId],
|
||||
node => AssertNode.Text(node, "Modified message"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanReRenderNestedComponents()
|
||||
{
|
||||
// Arrange: parent component already rendered
|
||||
var renderer = new TestRenderer();
|
||||
var parentComponent = new TestComponent(builder =>
|
||||
{
|
||||
builder.AddComponent<MessageComponent>();
|
||||
});
|
||||
var parentComponentId = renderer.AssignComponentId(parentComponent);
|
||||
renderer.RenderComponent(parentComponentId);
|
||||
var nestedComponentNode = renderer.RenderTreesByComponentId[parentComponentId]
|
||||
.Single(node => node.NodeType == RenderTreeNodeType.Component);
|
||||
var nestedComponent = (MessageComponent)nestedComponentNode.Component;
|
||||
var nestedComponentId = nestedComponentNode.ComponentId;
|
||||
|
||||
// Act/Assert: inital render
|
||||
nestedComponent.Message = "Render 1";
|
||||
renderer.RenderComponent(nestedComponentId);
|
||||
Assert.Collection(renderer.RenderTreesByComponentId[nestedComponentId],
|
||||
node => AssertNode.Text(node, "Render 1"));
|
||||
|
||||
// Act/Assert: re-render
|
||||
nestedComponent.Message = "Render 2";
|
||||
renderer.RenderComponent(nestedComponentId);
|
||||
Assert.Collection(renderer.RenderTreesByComponentId[nestedComponentId],
|
||||
node => AssertNode.Text(node, "Render 2"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanDispatchEventsToTopLevelComponents()
|
||||
{
|
||||
// Arrange: Render a component with an event handler
|
||||
var renderer = new TestRenderer();
|
||||
UIEventArgs receivedArgs = null;
|
||||
|
||||
var component = new EventComponent
|
||||
{
|
||||
Handler = args => { receivedArgs = args; }
|
||||
};
|
||||
var componentId = renderer.AssignComponentId(component);
|
||||
renderer.RenderComponent(componentId);
|
||||
|
||||
var (eventHandlerNodeIndex, _) = FirstWithIndex(
|
||||
renderer.RenderTreesByComponentId[componentId],
|
||||
node => node.AttributeEventHandlerValue != null);
|
||||
|
||||
// Assert: Event not yet fired
|
||||
Assert.Null(receivedArgs);
|
||||
|
||||
// Act/Assert: Event can be fired
|
||||
var eventArgs = new UIEventArgs();
|
||||
renderer.DispatchEvent(componentId, eventHandlerNodeIndex, eventArgs);
|
||||
Assert.Same(eventArgs, receivedArgs);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanDispatchEventsToNestedComponents()
|
||||
{
|
||||
UIEventArgs receivedArgs = null;
|
||||
|
||||
// Arrange: Render parent component
|
||||
var renderer = new TestRenderer();
|
||||
var parentComponent = new TestComponent(builder =>
|
||||
{
|
||||
builder.AddComponent<EventComponent>();
|
||||
});
|
||||
var parentComponentId = renderer.AssignComponentId(parentComponent);
|
||||
renderer.RenderComponent(parentComponentId);
|
||||
|
||||
// Arrange: Render nested component
|
||||
var nestedComponentNode = renderer.RenderTreesByComponentId[parentComponentId]
|
||||
.Single(node => node.NodeType == RenderTreeNodeType.Component);
|
||||
var nestedComponent = (EventComponent)nestedComponentNode.Component;
|
||||
nestedComponent.Handler = args => { receivedArgs = args; };
|
||||
var nestedComponentId = nestedComponentNode.ComponentId;
|
||||
renderer.RenderComponent(nestedComponentId);
|
||||
|
||||
// Find nested component's event handler ndoe
|
||||
var (eventHandlerNodeIndex, _) = FirstWithIndex(
|
||||
renderer.RenderTreesByComponentId[nestedComponentId],
|
||||
node => node.AttributeEventHandlerValue != null);
|
||||
|
||||
// Assert: Event not yet fired
|
||||
Assert.Null(receivedArgs);
|
||||
|
||||
// Act/Assert: Event can be fired
|
||||
var eventArgs = new UIEventArgs();
|
||||
renderer.DispatchEvent(nestedComponentId, eventHandlerNodeIndex, eventArgs);
|
||||
Assert.Same(eventArgs, receivedArgs);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CannotRenderUnknownComponents()
|
||||
{
|
||||
// Arrange
|
||||
var renderer = new TestRenderer();
|
||||
|
||||
// Act/Assert
|
||||
Assert.Throws<ArgumentException>(() =>
|
||||
{
|
||||
renderer.RenderComponent(123);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CannotDispatchEventsToUnknownComponents()
|
||||
{
|
||||
// Arrange
|
||||
var renderer = new TestRenderer();
|
||||
|
||||
// Act/Assert
|
||||
Assert.Throws<ArgumentException>(() =>
|
||||
{
|
||||
renderer.DispatchEvent(123, 0, new UIEventArgs());
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ComponentsCanBeAssociatedWithMultipleRenderers()
|
||||
{
|
||||
// Arrange
|
||||
var renderer1 = new TestRenderer();
|
||||
var renderer2 = new TestRenderer();
|
||||
var component = new MessageComponent { Message = "Hello, world!" };
|
||||
var renderer1ComponentId = renderer1.AssignComponentId(component);
|
||||
renderer2.AssignComponentId(new TestComponent(null)); // Just so they don't get the same IDs
|
||||
var renderer2ComponentId = renderer2.AssignComponentId(component);
|
||||
|
||||
// Act/Assert: Render component in renderer1
|
||||
renderer1.RenderComponent(renderer1ComponentId);
|
||||
Assert.True(renderer1.RenderTreesByComponentId.ContainsKey(renderer1ComponentId));
|
||||
Assert.False(renderer2.RenderTreesByComponentId.ContainsKey(renderer2ComponentId));
|
||||
|
||||
// Act/Assert: Render same component in renderer2
|
||||
renderer2.RenderComponent(renderer2ComponentId);
|
||||
Assert.True(renderer2.RenderTreesByComponentId.ContainsKey(renderer2ComponentId));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ComponentsAreNotPinnedInMemory()
|
||||
{
|
||||
|
|
@ -96,6 +310,26 @@ namespace Microsoft.Blazor.Test
|
|||
}
|
||||
}
|
||||
|
||||
private class TestRenderer : Renderer
|
||||
{
|
||||
public IDictionary<int, ArraySegment<RenderTreeNode>> RenderTreesByComponentId { get; }
|
||||
= new Dictionary<int, ArraySegment<RenderTreeNode>>();
|
||||
|
||||
public new int AssignComponentId(IComponent component)
|
||||
=> base.AssignComponentId(component);
|
||||
|
||||
public new void RenderComponent(int componentId)
|
||||
=> base.RenderComponent(componentId);
|
||||
|
||||
public new void DispatchEvent(int componentId, int renderTreeIndex, UIEventArgs args)
|
||||
=> base.DispatchEvent(componentId, renderTreeIndex, args);
|
||||
|
||||
protected override void UpdateDisplay(int componentId, ArraySegment<RenderTreeNode> renderTree)
|
||||
{
|
||||
RenderTreesByComponentId[componentId] = renderTree;
|
||||
}
|
||||
}
|
||||
|
||||
private class TestComponent : IComponent
|
||||
{
|
||||
private Action<RenderTreeBuilder> _renderAction;
|
||||
|
|
@ -109,6 +343,28 @@ namespace Microsoft.Blazor.Test
|
|||
=> _renderAction(builder);
|
||||
}
|
||||
|
||||
private class MessageComponent : IComponent
|
||||
{
|
||||
public string Message { get; set; }
|
||||
|
||||
public void BuildRenderTree(RenderTreeBuilder builder)
|
||||
{
|
||||
builder.AddText(Message);
|
||||
}
|
||||
}
|
||||
|
||||
private class EventComponent : IComponent
|
||||
{
|
||||
public UIEventHandler Handler { get; set; }
|
||||
|
||||
public void BuildRenderTree(RenderTreeBuilder builder)
|
||||
{
|
||||
builder.OpenElement("some element");
|
||||
builder.AddAttribute("some event", Handler);
|
||||
builder.CloseElement();
|
||||
}
|
||||
}
|
||||
|
||||
void AssertCanBeCollected(Func<object> targetFactory)
|
||||
{
|
||||
// We have to construct the WeakReference in a separate scope
|
||||
|
|
@ -119,5 +375,21 @@ namespace Microsoft.Blazor.Test
|
|||
GC.WaitForPendingFinalizers();
|
||||
Assert.Null(weakRef.Target);
|
||||
}
|
||||
|
||||
(int, T) FirstWithIndex<T>(IEnumerable<T> items, Predicate<T> predicate)
|
||||
{
|
||||
var index = 0;
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (predicate(item))
|
||||
{
|
||||
return (index, item);
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("No matching element was found.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue