Move to batched rendering
This commit is contained in:
parent
27d0ce4da9
commit
080e6395cb
|
|
@ -147,20 +147,10 @@ export class BrowserRenderer {
|
|||
const containerElement = document.createElement('blazor-component');
|
||||
insertNodeIntoDOM(containerElement, parent, childIndex);
|
||||
|
||||
// All we have to do is associate the child component ID with its location. We don't actually
|
||||
// do any rendering here, because the diff for the child will appear later in the render batch.
|
||||
const childComponentId = renderTreeNode.componentId(node);
|
||||
this.attachComponentToElement(childComponentId, containerElement);
|
||||
|
||||
if (!renderComponentMethod) {
|
||||
renderComponentMethod = platform.findMethod(
|
||||
'Microsoft.AspNetCore.Blazor.Browser', 'Microsoft.AspNetCore.Blazor.Browser.Rendering', 'BrowserRendererEventDispatcher', 'RenderChildComponent'
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: Consider caching the .NET string instance for this.browserRendererId
|
||||
platform.callMethod(renderComponentMethod, null, [
|
||||
platform.toDotNetString(this.browserRendererId.toString()),
|
||||
platform.toDotNetString(childComponentId.toString())
|
||||
]);
|
||||
}
|
||||
|
||||
insertText(parent: Element, childIndex: number, textNode: RenderTreeNodePointer) {
|
||||
|
|
|
|||
|
|
@ -66,20 +66,23 @@ namespace Microsoft.AspNetCore.Blazor.Browser.Rendering
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void UpdateDisplay(
|
||||
int componentId,
|
||||
RenderTreeDiff renderTreeDiff)
|
||||
protected override void UpdateDisplay(RenderBatch batch)
|
||||
{
|
||||
RegisteredFunction.InvokeUnmarshalled<RenderComponentArgs, object>(
|
||||
"renderRenderTree",
|
||||
new RenderComponentArgs
|
||||
{
|
||||
BrowserRendererId = _browserRendererId,
|
||||
ComponentId = componentId,
|
||||
RenderTreeEdits = renderTreeDiff.Edits.Array,
|
||||
RenderTreeEditsLength = renderTreeDiff.Edits.Count,
|
||||
RenderTree = renderTreeDiff.CurrentState.Array
|
||||
});
|
||||
// TODO: Pass to JS in a single call
|
||||
for (var i = 0; i < batch.UpdatedComponents.Count; i++)
|
||||
{
|
||||
ref var diff = ref batch.UpdatedComponents.Array[i];
|
||||
RegisteredFunction.InvokeUnmarshalled<RenderComponentArgs, object>(
|
||||
"renderRenderTree",
|
||||
new RenderComponentArgs
|
||||
{
|
||||
BrowserRendererId = _browserRendererId,
|
||||
ComponentId = diff.ComponentId,
|
||||
RenderTreeEdits = diff.Edits.Array,
|
||||
RenderTreeEditsLength = diff.Edits.Count,
|
||||
RenderTree = diff.CurrentState.Array
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Encapsulates the data we pass to the JS rendering function
|
||||
|
|
|
|||
|
|
@ -28,17 +28,6 @@ namespace Microsoft.AspNetCore.Blazor.Browser.Rendering
|
|||
eventArgs);
|
||||
}
|
||||
|
||||
// Again, the params are received as strings for the same reason as above and
|
||||
// can be simplified once runtime support improves.
|
||||
public static void RenderChildComponent(
|
||||
string browserRendererId,
|
||||
string componentId)
|
||||
{
|
||||
var browserRenderer = BrowserRendererRegistry.Find(int.Parse(browserRendererId));
|
||||
browserRenderer.RenderNewBatchInternal(
|
||||
int.Parse(componentId));
|
||||
}
|
||||
|
||||
private static UIEventArgs ParseEventArgsJson(string eventArgsType, string eventArgsJson)
|
||||
{
|
||||
switch (eventArgsType)
|
||||
|
|
|
|||
|
|
@ -56,6 +56,16 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
|||
_items[_itemsInUse++] = item;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the supplied value at the specified index. The index must be within
|
||||
/// range for the array.
|
||||
/// </summary>
|
||||
/// <param name="index">The index.</param>
|
||||
/// <param name="value">The value.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Overwrite(int index, in T value)
|
||||
=> _items[index] = value;
|
||||
|
||||
/// <summary>
|
||||
/// Removes the last item.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -11,6 +11,11 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
|||
/// </summary>
|
||||
public readonly struct RenderTreeDiff
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the ID of the component.
|
||||
/// </summary>
|
||||
public int ComponentId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the changes to the render tree since a previous state.
|
||||
/// </summary>
|
||||
|
|
@ -23,9 +28,11 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
|||
public ArrayRange<RenderTreeNode> CurrentState { get; }
|
||||
|
||||
internal RenderTreeDiff(
|
||||
int componentId,
|
||||
ArrayRange<RenderTreeEdit> entries,
|
||||
ArrayRange<RenderTreeNode> referenceTree)
|
||||
{
|
||||
ComponentId = componentId;
|
||||
Edits = entries;
|
||||
CurrentState = referenceTree;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,18 +24,24 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
|||
/// component instances onto retained Component nodes. It's particularly convenient to do that
|
||||
/// here because we have the right information and are already walking the trees to do the diff.
|
||||
/// </summary>
|
||||
public RenderTreeDiff ApplyNewRenderTreeVersion(
|
||||
public void ApplyNewRenderTreeVersion(
|
||||
RenderBatchBuilder batchBuilder,
|
||||
int componentId,
|
||||
ArrayRange<RenderTreeNode> oldTree,
|
||||
ArrayRange<RenderTreeNode> newTree)
|
||||
{
|
||||
_entries.Clear();
|
||||
var siblingIndex = 0;
|
||||
AppendDiffEntriesForRange(oldTree.Array, 0, oldTree.Count, newTree.Array, 0, newTree.Count, ref siblingIndex);
|
||||
|
||||
return new RenderTreeDiff(_entries.ToRange(), newTree);
|
||||
var slotId = batchBuilder.ReserveUpdatedComponentSlotId();
|
||||
AppendDiffEntriesForRange(batchBuilder, oldTree.Array, 0, oldTree.Count, newTree.Array, 0, newTree.Count, ref siblingIndex);
|
||||
batchBuilder.SetUpdatedComponent(
|
||||
slotId,
|
||||
new RenderTreeDiff(componentId, _entries.ToRange(), newTree));
|
||||
}
|
||||
|
||||
private void AppendDiffEntriesForRange(
|
||||
RenderBatchBuilder batchBuilder,
|
||||
RenderTreeNode[] oldTree, int oldStartIndex, int oldEndIndexExcl,
|
||||
RenderTreeNode[] newTree, int newStartIndex, int newEndIndexExcl,
|
||||
ref int siblingIndex)
|
||||
|
|
@ -51,7 +57,7 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
|||
|
||||
if (oldSeq == newSeq)
|
||||
{
|
||||
AppendDiffEntriesForNodesWithSameSequence(oldTree, oldStartIndex, newTree, newStartIndex, ref siblingIndex);
|
||||
AppendDiffEntriesForNodesWithSameSequence(batchBuilder, oldTree, oldStartIndex, newTree, newStartIndex, ref siblingIndex);
|
||||
oldStartIndex = NextSiblingIndex(oldTree[oldStartIndex], oldStartIndex);
|
||||
newStartIndex = NextSiblingIndex(newTree[newStartIndex], newStartIndex);
|
||||
hasMoreOld = oldEndIndexExcl > oldStartIndex;
|
||||
|
|
@ -137,7 +143,7 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
|||
{
|
||||
if (newNodeType == RenderTreeNodeType.Element || newNodeType == RenderTreeNodeType.Component)
|
||||
{
|
||||
InstantiateChildComponents(newTree, newStartIndex);
|
||||
InstantiateChildComponents(batchBuilder, newTree, newStartIndex);
|
||||
}
|
||||
|
||||
Append(RenderTreeEdit.PrependNode(siblingIndex, newStartIndex));
|
||||
|
|
@ -169,6 +175,7 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
|||
}
|
||||
|
||||
private void UpdateRetainedChildComponent(
|
||||
RenderBatchBuilder batchBuilder,
|
||||
RenderTreeNode[] oldTree, int oldComponentIndex,
|
||||
RenderTreeNode[] newTree, int newComponentIndex)
|
||||
{
|
||||
|
|
@ -260,7 +267,7 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
|||
{
|
||||
// TODO: Instead, call some OnPropertiesUpdated method on IComponent,
|
||||
// whose default implementation causes itself to be rerendered
|
||||
_renderer.RenderInExistingBatch(componentId);
|
||||
_renderer.RenderInExistingBatch(batchBuilder, componentId);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -308,6 +315,7 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
|||
}
|
||||
|
||||
private void AppendDiffEntriesForNodesWithSameSequence(
|
||||
RenderBatchBuilder batchBuilder,
|
||||
RenderTreeNode[] oldTree, int oldNodeIndex,
|
||||
RenderTreeNode[] newTree, int newNodeIndex,
|
||||
ref int siblingIndex)
|
||||
|
|
@ -342,6 +350,7 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
|||
|
||||
// Diff the attributes
|
||||
AppendDiffEntriesForRange(
|
||||
batchBuilder,
|
||||
oldTree, oldNodeIndex + 1, oldNodeAttributesEndIndexExcl,
|
||||
newTree, newNodeIndex + 1, newNodeAttributesEndIndexExcl,
|
||||
ref siblingIndex);
|
||||
|
|
@ -357,6 +366,7 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
|||
Append(RenderTreeEdit.StepIn(siblingIndex));
|
||||
var childSiblingIndex = 0;
|
||||
AppendDiffEntriesForRange(
|
||||
batchBuilder,
|
||||
oldTree, oldNodeAttributesEndIndexExcl, oldNodeChildrenEndIndexExcl,
|
||||
newTree, newNodeAttributesEndIndexExcl, newNodeChildrenEndIndexExcl,
|
||||
ref childSiblingIndex);
|
||||
|
|
@ -385,6 +395,7 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
|||
if (oldComponentType == newComponentType)
|
||||
{
|
||||
UpdateRetainedChildComponent(
|
||||
batchBuilder,
|
||||
oldTree, oldNodeIndex,
|
||||
newTree, newNodeIndex);
|
||||
|
||||
|
|
@ -460,30 +471,34 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
|||
_entries.Append(entry);
|
||||
}
|
||||
|
||||
private void InstantiateChildComponents(RenderTreeNode[] nodes, int elementOrComponentIndex)
|
||||
private void InstantiateChildComponents(RenderBatchBuilder batchBuilder, RenderTreeNode[] nodes, int elementOrComponentIndex)
|
||||
{
|
||||
var endIndex = nodes[elementOrComponentIndex].ElementDescendantsEndIndex;
|
||||
for (var i = elementOrComponentIndex; i <= endIndex; i++)
|
||||
{
|
||||
if (nodes[i].NodeType == RenderTreeNodeType.Component)
|
||||
ref var node = ref nodes[i];
|
||||
if (node.NodeType == RenderTreeNodeType.Component)
|
||||
{
|
||||
if (nodes[i].Component != null)
|
||||
if (node.Component != null)
|
||||
{
|
||||
throw new InvalidOperationException($"Child component already exists during {nameof(InstantiateChildComponents)}");
|
||||
}
|
||||
|
||||
_renderer.InstantiateChildComponent(nodes, i);
|
||||
var childComponentInstance = nodes[i].Component;
|
||||
var childComponentInstance = node.Component;
|
||||
|
||||
// All descendants of a component are its properties
|
||||
var componentDescendantsEndIndex = nodes[i].ElementDescendantsEndIndex;
|
||||
var componentDescendantsEndIndex = node.ElementDescendantsEndIndex;
|
||||
for (var attributeNodeIndex = i + 1; attributeNodeIndex <= componentDescendantsEndIndex; attributeNodeIndex++)
|
||||
{
|
||||
ref var attributeNode = ref nodes[attributeNodeIndex];
|
||||
SetChildComponentProperty(
|
||||
childComponentInstance,
|
||||
nodes[attributeNodeIndex].AttributeName,
|
||||
nodes[attributeNodeIndex].AttributeValue);
|
||||
attributeNode.AttributeName,
|
||||
attributeNode.AttributeValue);
|
||||
}
|
||||
|
||||
_renderer.RenderInExistingBatch(batchBuilder, node.ComponentId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,25 +38,21 @@ namespace Microsoft.AspNetCore.Blazor.Rendering
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Regenerates the <see cref="RenderTree"/> and notifies the <see cref="Renderer"/>
|
||||
/// to update the visible UI state.
|
||||
/// Regenerates the <see cref="RenderTree"/> and adds the changes to the
|
||||
/// <paramref name="batchBuilder"/>.
|
||||
/// </summary>
|
||||
public void Render()
|
||||
public void Render(RenderBatchBuilder batchBuilder)
|
||||
{
|
||||
// Swap the old and new tree builders
|
||||
(_renderTreeBuilderCurrent, _renderTreeBuilderPrevious) = (_renderTreeBuilderPrevious, _renderTreeBuilderCurrent);
|
||||
|
||||
_renderTreeBuilderCurrent.Clear();
|
||||
_component.BuildRenderTree(_renderTreeBuilderCurrent);
|
||||
var diff = _diffComputer.ApplyNewRenderTreeVersion(
|
||||
_diffComputer.ApplyNewRenderTreeVersion(
|
||||
batchBuilder,
|
||||
_componentId,
|
||||
_renderTreeBuilderPrevious.GetNodes(),
|
||||
_renderTreeBuilderCurrent.GetNodes());
|
||||
|
||||
// TODO: Instead of triggering the UpdateDisplay here, collect all the (componentId, diff)
|
||||
// pairs from the whole render cycle, including descendant components, then send them
|
||||
// in bulk to a single UpdateDisplay. Need to ensure that if the same component gets
|
||||
// triggered multiple times that the diff reflects the entire chain.
|
||||
_renderer.UpdateDisplay(_componentId, diff);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -82,7 +78,7 @@ namespace Microsoft.AspNetCore.Blazor.Rendering
|
|||
|
||||
// After any event, we synchronously re-render. Most of the time this means that
|
||||
// developers don't need to call Render() on their components explicitly.
|
||||
Render();
|
||||
_renderer.RenderNewBatch(_componentId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
// 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.RenderTree;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Rendering
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes a set of UI changes.
|
||||
/// </summary>
|
||||
public readonly struct RenderBatch
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the changes to components that were added or updated.
|
||||
/// </summary>
|
||||
public ArrayRange<RenderTreeDiff> UpdatedComponents { get; }
|
||||
|
||||
internal RenderBatch(ArrayRange<RenderTreeDiff> updatedComponents)
|
||||
{
|
||||
UpdatedComponents = updatedComponents;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
// 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.RenderTree;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Rendering
|
||||
{
|
||||
internal class RenderBatchBuilder
|
||||
{
|
||||
private ArrayBuilder<RenderTreeDiff> _updatedComponentDiffs = new ArrayBuilder<RenderTreeDiff>();
|
||||
|
||||
public int ReserveUpdatedComponentSlotId()
|
||||
{
|
||||
int id = _updatedComponentDiffs.Count;
|
||||
_updatedComponentDiffs.Append(default);
|
||||
return id;
|
||||
}
|
||||
|
||||
public void SetUpdatedComponent(int updatedComponentSlotId, RenderTreeDiff diff)
|
||||
=> _updatedComponentDiffs.Overwrite(updatedComponentSlotId, diff);
|
||||
|
||||
public void Clear()
|
||||
=> _updatedComponentDiffs.Clear();
|
||||
|
||||
public RenderBatch ToBatch()
|
||||
=> new RenderBatch(
|
||||
_updatedComponentDiffs.ToRange());
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Blazor.Components;
|
|||
using Microsoft.AspNetCore.Blazor.RenderTree;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Rendering
|
||||
{
|
||||
|
|
@ -24,6 +25,11 @@ namespace Microsoft.AspNetCore.Blazor.Rendering
|
|||
= new ConditionalWeakTable<IComponent, ComponentState>();
|
||||
private int _nextComponentId = 0; // TODO: change to 'long' when Mono .NET->JS interop supports it
|
||||
|
||||
// Because rendering is currently synchronous and single-threaded, we can keep re-using the
|
||||
// same RenderBatchBuilder instance to avoid reallocating
|
||||
private readonly RenderBatchBuilder _sharedRenderBatchBuilder = new RenderBatchBuilder();
|
||||
private int _renderBatchLock = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Associates the <see cref="IComponent"/> with the <see cref="Renderer"/>, assigning
|
||||
/// an identifier that is unique within the scope of the <see cref="Renderer"/>.
|
||||
|
|
@ -43,13 +49,10 @@ namespace Microsoft.AspNetCore.Blazor.Rendering
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the visible UI to display the supplied <paramref name="renderTree"/>
|
||||
/// at the location corresponding to the <paramref name="componentId"/>.
|
||||
/// Updates the visible UI.
|
||||
/// </summary>
|
||||
/// <param name="componentId">The identifier for the updated <see cref="IComponent"/>.</param>
|
||||
/// <param name="renderTreeDiff">The changes to the render tree since the component was last rendered.</param>
|
||||
internal protected abstract void UpdateDisplay(
|
||||
int componentId, RenderTreeDiff renderTreeDiff);
|
||||
/// <param name="renderBatch">The changes to the UI since the previous call.</param>
|
||||
internal protected abstract void UpdateDisplay(RenderBatch renderBatch);
|
||||
|
||||
/// <summary>
|
||||
/// Updates the rendered state of the specified <see cref="IComponent"/>.
|
||||
|
|
@ -57,12 +60,40 @@ namespace Microsoft.AspNetCore.Blazor.Rendering
|
|||
/// <param name="componentId">The identifier of the <see cref="IComponent"/> to render.</param>
|
||||
protected internal void RenderNewBatch(int componentId)
|
||||
{
|
||||
RenderInExistingBatch(componentId);
|
||||
// It's very important that components' rendering logic has no side-effects, and in particular
|
||||
// components must *not* trigger Render from inside their render logic, otherwise you could
|
||||
// easily get hard-to-debug infinite loops.
|
||||
// Since rendering is currently synchronous and single-threaded, we can enforce the above by
|
||||
// checking here that no other rendering process is already underway. This also means we only
|
||||
// need a single _renderBatchBuilder instance that can be reused throughout the lifetime of
|
||||
// the Renderer instance, which also means we're not allocating on a typical render cycle.
|
||||
// In the future, if rendering becomes async, we'll need a more sophisticated system of
|
||||
// capturing successive diffs from each component and probably serializing them for the
|
||||
// interop calls instead of using shared memory.
|
||||
|
||||
// Note that Monitor.TryEnter is not yet supported in Mono WASM, so using the following instead
|
||||
var renderAlreadyRunning = Interlocked.CompareExchange(ref _renderBatchLock, 1, 0) == 1;
|
||||
if (renderAlreadyRunning)
|
||||
{
|
||||
throw new InvalidOperationException("Cannot render while a render is already in progress. " +
|
||||
"Render logic must not have side-effects such as manually triggering other rendering.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
RenderInExistingBatch(_sharedRenderBatchBuilder, componentId);
|
||||
UpdateDisplay(_sharedRenderBatchBuilder.ToBatch());
|
||||
}
|
||||
finally
|
||||
{
|
||||
_sharedRenderBatchBuilder.Clear();
|
||||
Interlocked.Exchange(ref _renderBatchLock, 0);
|
||||
}
|
||||
}
|
||||
|
||||
internal void RenderInExistingBatch(int componentId)
|
||||
internal void RenderInExistingBatch(RenderBatchBuilder batchBuilder, int componentId)
|
||||
{
|
||||
GetRequiredComponentState(componentId).Render();
|
||||
GetRequiredComponentState(componentId).Render(batchBuilder);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -459,7 +459,7 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
|
|||
|
||||
private class TestRenderer : Renderer
|
||||
{
|
||||
protected override void UpdateDisplay(int componentId, RenderTreeDiff renderTreeDiff)
|
||||
protected override void UpdateDisplay(RenderBatch renderBatch)
|
||||
=> throw new NotImplementedException();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -297,7 +297,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
|
||||
private class TestRenderer : Renderer
|
||||
{
|
||||
protected internal override void UpdateDisplay(int componentId, RenderTreeDiff renderTreeDiff)
|
||||
protected internal override void UpdateDisplay(RenderBatch renderBatch)
|
||||
=> throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
using Microsoft.AspNetCore.Blazor.Components;
|
||||
using Microsoft.AspNetCore.Blazor.Rendering;
|
||||
using Microsoft.AspNetCore.Blazor.RenderTree;
|
||||
using Microsoft.AspNetCore.Blazor.Test.Shared;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
|
@ -13,20 +14,29 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
{
|
||||
public class RenderTreeDiffComputerTest
|
||||
{
|
||||
private readonly Renderer renderer;
|
||||
private readonly RenderTreeBuilder oldTree;
|
||||
private readonly RenderTreeBuilder newTree;
|
||||
private RenderTreeDiffComputer diff;
|
||||
|
||||
public RenderTreeDiffComputerTest()
|
||||
{
|
||||
renderer = new FakeRenderer();
|
||||
oldTree = new RenderTreeBuilder(renderer);
|
||||
newTree = new RenderTreeBuilder(renderer);
|
||||
diff = new RenderTreeDiffComputer(renderer);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecognizesEquivalentNodesAsSameCases))]
|
||||
public void RecognizesEquivalentNodesAsSame(Action<RenderTreeBuilder> appendAction)
|
||||
{
|
||||
// Arrange
|
||||
var renderer = new FakeRenderer();
|
||||
var oldTree = new RenderTreeBuilder(renderer);
|
||||
var newTree = new RenderTreeBuilder(renderer);
|
||||
var diff = new RenderTreeDiffComputer(renderer);
|
||||
appendAction(oldTree);
|
||||
appendAction(newTree);
|
||||
|
||||
// Act
|
||||
var result = diff.ApplyNewRenderTreeVersion(oldTree.GetNodes(), newTree.GetNodes());
|
||||
var result = GetSingleUpdatedComponent();
|
||||
|
||||
// Assert
|
||||
Assert.Empty(result.Edits);
|
||||
|
|
@ -50,10 +60,6 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
public void RecognizesNewItemsBeingInserted()
|
||||
{
|
||||
// Arrange
|
||||
var renderer = new FakeRenderer();
|
||||
var oldTree = new RenderTreeBuilder(renderer);
|
||||
var newTree = new RenderTreeBuilder(renderer);
|
||||
var diff = new RenderTreeDiffComputer(renderer);
|
||||
oldTree.AddText(0, "text0");
|
||||
oldTree.AddText(2, "text2");
|
||||
newTree.AddText(0, "text0");
|
||||
|
|
@ -61,7 +67,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
newTree.AddText(2, "text2");
|
||||
|
||||
// Act
|
||||
var result = diff.ApplyNewRenderTreeVersion(oldTree.GetNodes(), newTree.GetNodes());
|
||||
var result = GetSingleUpdatedComponent();
|
||||
|
||||
// Assert
|
||||
Assert.Collection(result.Edits,
|
||||
|
|
@ -76,10 +82,6 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
public void RecognizesOldItemsBeingRemoved()
|
||||
{
|
||||
// Arrange
|
||||
var renderer = new FakeRenderer();
|
||||
var oldTree = new RenderTreeBuilder(renderer);
|
||||
var newTree = new RenderTreeBuilder(renderer);
|
||||
var diff = new RenderTreeDiffComputer(renderer);
|
||||
oldTree.AddText(0, "text0");
|
||||
oldTree.AddText(1, "text1");
|
||||
oldTree.AddText(2, "text2");
|
||||
|
|
@ -87,7 +89,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
newTree.AddText(2, "text2");
|
||||
|
||||
// Act
|
||||
var result = diff.ApplyNewRenderTreeVersion(oldTree.GetNodes(), newTree.GetNodes());
|
||||
var result = GetSingleUpdatedComponent();
|
||||
|
||||
// Assert
|
||||
Assert.Collection(result.Edits,
|
||||
|
|
@ -98,10 +100,6 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
public void RecognizesTrailingSequenceWithinLoopBlockBeingRemoved()
|
||||
{
|
||||
// Arrange
|
||||
var renderer = new FakeRenderer();
|
||||
var oldTree = new RenderTreeBuilder(renderer);
|
||||
var newTree = new RenderTreeBuilder(renderer);
|
||||
var diff = new RenderTreeDiffComputer(renderer);
|
||||
oldTree.AddText(0, "x"); // Loop start
|
||||
oldTree.AddText(1, "x"); // Will be removed
|
||||
oldTree.AddText(2, "x"); // Will be removed
|
||||
|
|
@ -110,7 +108,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
newTree.AddText(0, "x"); // Loop start
|
||||
|
||||
// Act
|
||||
var result = diff.ApplyNewRenderTreeVersion(oldTree.GetNodes(), newTree.GetNodes());
|
||||
var result = GetSingleUpdatedComponent();
|
||||
|
||||
// Assert
|
||||
Assert.Collection(result.Edits,
|
||||
|
|
@ -122,10 +120,6 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
public void RecognizesTrailingSequenceWithinLoopBlockBeingAppended()
|
||||
{
|
||||
// Arrange
|
||||
var renderer = new FakeRenderer();
|
||||
var oldTree = new RenderTreeBuilder(renderer);
|
||||
var newTree = new RenderTreeBuilder(renderer);
|
||||
var diff = new RenderTreeDiffComputer(renderer);
|
||||
oldTree.AddText(0, "x"); // Loop start
|
||||
oldTree.AddText(0, "x"); // Loop start
|
||||
newTree.AddText(0, "x"); // Loop start
|
||||
|
|
@ -134,7 +128,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
newTree.AddText(0, "x"); // Loop start
|
||||
|
||||
// Act
|
||||
var result = diff.ApplyNewRenderTreeVersion(oldTree.GetNodes(), newTree.GetNodes());
|
||||
var result = GetSingleUpdatedComponent();
|
||||
|
||||
// Assert
|
||||
Assert.Collection(result.Edits,
|
||||
|
|
@ -154,10 +148,6 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
public void RecognizesTrailingLoopBlockBeingRemoved()
|
||||
{
|
||||
// Arrange
|
||||
var renderer = new FakeRenderer();
|
||||
var oldTree = new RenderTreeBuilder(renderer);
|
||||
var newTree = new RenderTreeBuilder(renderer);
|
||||
var diff = new RenderTreeDiffComputer(renderer);
|
||||
oldTree.AddText(0, "x");
|
||||
oldTree.AddText(1, "x");
|
||||
oldTree.AddText(0, "x"); // Will be removed
|
||||
|
|
@ -166,7 +156,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
newTree.AddText(1, "x");
|
||||
|
||||
// Act
|
||||
var result = diff.ApplyNewRenderTreeVersion(oldTree.GetNodes(), newTree.GetNodes());
|
||||
var result = GetSingleUpdatedComponent();
|
||||
|
||||
// Assert
|
||||
Assert.Collection(result.Edits,
|
||||
|
|
@ -178,10 +168,6 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
public void RecognizesTrailingLoopBlockBeingAdded()
|
||||
{
|
||||
// Arrange
|
||||
var renderer = new FakeRenderer();
|
||||
var oldTree = new RenderTreeBuilder(renderer);
|
||||
var newTree = new RenderTreeBuilder(renderer);
|
||||
var diff = new RenderTreeDiffComputer(renderer);
|
||||
oldTree.AddText(0, "x");
|
||||
oldTree.AddText(1, "x");
|
||||
newTree.AddText(0, "x");
|
||||
|
|
@ -190,7 +176,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
newTree.AddText(1, "x"); // Will be added
|
||||
|
||||
// Act
|
||||
var result = diff.ApplyNewRenderTreeVersion(oldTree.GetNodes(), newTree.GetNodes());
|
||||
var result = GetSingleUpdatedComponent();
|
||||
|
||||
// Assert
|
||||
Assert.Collection(result.Edits,
|
||||
|
|
@ -210,10 +196,6 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
public void RecognizesLeadingLoopBlockItemsBeingAdded()
|
||||
{
|
||||
// Arrange
|
||||
var renderer = new FakeRenderer();
|
||||
var oldTree = new RenderTreeBuilder(renderer);
|
||||
var newTree = new RenderTreeBuilder(renderer);
|
||||
var diff = new RenderTreeDiffComputer(renderer);
|
||||
oldTree.AddText(2, "x");
|
||||
oldTree.AddText(2, "x"); // Note that the '0' and '1' items are not present on this iteration
|
||||
newTree.AddText(2, "x");
|
||||
|
|
@ -222,7 +204,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
newTree.AddText(2, "x");
|
||||
|
||||
// Act
|
||||
var result = diff.ApplyNewRenderTreeVersion(oldTree.GetNodes(), newTree.GetNodes());
|
||||
var result = GetSingleUpdatedComponent();
|
||||
|
||||
// Assert
|
||||
Assert.Collection(result.Edits,
|
||||
|
|
@ -242,10 +224,6 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
public void RecognizesLeadingLoopBlockItemsBeingRemoved()
|
||||
{
|
||||
// Arrange
|
||||
var renderer = new FakeRenderer();
|
||||
var oldTree = new RenderTreeBuilder(renderer);
|
||||
var newTree = new RenderTreeBuilder(renderer);
|
||||
var diff = new RenderTreeDiffComputer(renderer);
|
||||
oldTree.AddText(2, "x");
|
||||
oldTree.AddText(0, "x");
|
||||
oldTree.AddText(1, "x");
|
||||
|
|
@ -254,7 +232,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
newTree.AddText(2, "x"); // Note that the '0' and '1' items are not present on this iteration
|
||||
|
||||
// Act
|
||||
var result = diff.ApplyNewRenderTreeVersion(oldTree.GetNodes(), newTree.GetNodes());
|
||||
var result = GetSingleUpdatedComponent();
|
||||
|
||||
// Assert
|
||||
Assert.Collection(result.Edits,
|
||||
|
|
@ -266,15 +244,11 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
public void HandlesAdjacentItemsBeingRemovedAndInsertedAtOnce()
|
||||
{
|
||||
// Arrange
|
||||
var renderer = new FakeRenderer();
|
||||
var oldTree = new RenderTreeBuilder(renderer);
|
||||
var newTree = new RenderTreeBuilder(renderer);
|
||||
var diff = new RenderTreeDiffComputer(renderer);
|
||||
oldTree.AddText(0, "text");
|
||||
newTree.AddText(1, "text");
|
||||
|
||||
// Act
|
||||
var result = diff.ApplyNewRenderTreeVersion(oldTree.GetNodes(), newTree.GetNodes());
|
||||
var result = GetSingleUpdatedComponent();
|
||||
|
||||
// Assert
|
||||
Assert.Collection(result.Edits,
|
||||
|
|
@ -286,17 +260,13 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
public void RecognizesTextUpdates()
|
||||
{
|
||||
// Arrange
|
||||
var renderer = new FakeRenderer();
|
||||
var oldTree = new RenderTreeBuilder(renderer);
|
||||
var newTree = new RenderTreeBuilder(renderer);
|
||||
var diff = new RenderTreeDiffComputer(renderer);
|
||||
oldTree.AddText(123, "old text 1");
|
||||
oldTree.AddText(182, "old text 2");
|
||||
newTree.AddText(123, "new text 1");
|
||||
newTree.AddText(182, "new text 2");
|
||||
|
||||
// Act
|
||||
var result = diff.ApplyNewRenderTreeVersion(oldTree.GetNodes(), newTree.GetNodes());
|
||||
var result = GetSingleUpdatedComponent();
|
||||
|
||||
// Assert
|
||||
Assert.Collection(result.Edits,
|
||||
|
|
@ -320,17 +290,13 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
// decide just to throw in this scenario, since it's unnecessary to support it.
|
||||
|
||||
// Arrange
|
||||
var renderer = new FakeRenderer();
|
||||
var oldTree = new RenderTreeBuilder(renderer);
|
||||
var newTree = new RenderTreeBuilder(renderer);
|
||||
var diff = new RenderTreeDiffComputer(renderer);
|
||||
oldTree.OpenElement(123, "old element");
|
||||
oldTree.CloseElement();
|
||||
newTree.OpenElement(123, "new element");
|
||||
newTree.CloseElement();
|
||||
|
||||
// Act
|
||||
var result = diff.ApplyNewRenderTreeVersion(oldTree.GetNodes(), newTree.GetNodes());
|
||||
var result = GetSingleUpdatedComponent();
|
||||
|
||||
// Assert
|
||||
Assert.Collection(result.Edits,
|
||||
|
|
@ -346,15 +312,11 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
public void RecognizesComponentTypeChangesAtSameSequenceNumber()
|
||||
{
|
||||
// Arrange
|
||||
var renderer = new FakeRenderer();
|
||||
var oldTree = new RenderTreeBuilder(renderer);
|
||||
var newTree = new RenderTreeBuilder(renderer);
|
||||
var diff = new RenderTreeDiffComputer(renderer);
|
||||
oldTree.OpenComponentElement<FakeComponent>(123);
|
||||
newTree.OpenComponentElement<FakeComponent2>(123);
|
||||
|
||||
// Act
|
||||
var result = diff.ApplyNewRenderTreeVersion(oldTree.GetNodes(), newTree.GetNodes());
|
||||
var result = GetSingleUpdatedComponent();
|
||||
|
||||
// Assert
|
||||
Assert.Collection(result.Edits,
|
||||
|
|
@ -370,10 +332,6 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
public void RecognizesAttributesAdded()
|
||||
{
|
||||
// Arrange
|
||||
var renderer = new FakeRenderer();
|
||||
var oldTree = new RenderTreeBuilder(renderer);
|
||||
var newTree = new RenderTreeBuilder(renderer);
|
||||
var diff = new RenderTreeDiffComputer(renderer);
|
||||
oldTree.OpenElement(0, "My element");
|
||||
oldTree.AddAttribute(1, "existing", "existing value");
|
||||
oldTree.CloseElement();
|
||||
|
|
@ -383,7 +341,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
newTree.CloseElement();
|
||||
|
||||
// Act
|
||||
var result = diff.ApplyNewRenderTreeVersion(oldTree.GetNodes(), newTree.GetNodes());
|
||||
var result = GetSingleUpdatedComponent();
|
||||
|
||||
// Assert
|
||||
Assert.Collection(result.Edits,
|
||||
|
|
@ -398,10 +356,6 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
public void RecognizesAttributesRemoved()
|
||||
{
|
||||
// Arrange
|
||||
var renderer = new FakeRenderer();
|
||||
var oldTree = new RenderTreeBuilder(renderer);
|
||||
var newTree = new RenderTreeBuilder(renderer);
|
||||
var diff = new RenderTreeDiffComputer(renderer);
|
||||
oldTree.OpenElement(0, "My element");
|
||||
oldTree.AddAttribute(1, "will be removed", "will be removed value");
|
||||
oldTree.AddAttribute(2, "will survive", "surviving value");
|
||||
|
|
@ -411,7 +365,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
newTree.CloseElement();
|
||||
|
||||
// Act
|
||||
var result = diff.ApplyNewRenderTreeVersion(oldTree.GetNodes(), newTree.GetNodes());
|
||||
var result = GetSingleUpdatedComponent();
|
||||
|
||||
// Assert
|
||||
Assert.Collection(result.Edits,
|
||||
|
|
@ -426,10 +380,6 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
public void RecognizesAttributeStringValuesChanged()
|
||||
{
|
||||
// Arrange
|
||||
var renderer = new FakeRenderer();
|
||||
var oldTree = new RenderTreeBuilder(renderer);
|
||||
var newTree = new RenderTreeBuilder(renderer);
|
||||
var diff = new RenderTreeDiffComputer(renderer);
|
||||
oldTree.OpenElement(0, "My element");
|
||||
oldTree.AddAttribute(1, "will remain", "will remain value");
|
||||
oldTree.AddAttribute(2, "will change", "will change value");
|
||||
|
|
@ -440,7 +390,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
newTree.CloseElement();
|
||||
|
||||
// Act
|
||||
var result = diff.ApplyNewRenderTreeVersion(oldTree.GetNodes(), newTree.GetNodes());
|
||||
var result = GetSingleUpdatedComponent();
|
||||
|
||||
// Assert
|
||||
Assert.Collection(result.Edits,
|
||||
|
|
@ -455,10 +405,6 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
public void RecognizesAttributeEventHandlerValuesChanged()
|
||||
{
|
||||
// Arrange
|
||||
var renderer = new FakeRenderer();
|
||||
var oldTree = new RenderTreeBuilder(renderer);
|
||||
var newTree = new RenderTreeBuilder(renderer);
|
||||
var diff = new RenderTreeDiffComputer(renderer);
|
||||
UIEventHandler retainedHandler = _ => { };
|
||||
UIEventHandler removedHandler = _ => { };
|
||||
UIEventHandler addedHandler = _ => { };
|
||||
|
|
@ -472,7 +418,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
newTree.CloseElement();
|
||||
|
||||
// Act
|
||||
var result = diff.ApplyNewRenderTreeVersion(oldTree.GetNodes(), newTree.GetNodes());
|
||||
var result = GetSingleUpdatedComponent();
|
||||
|
||||
// Assert
|
||||
Assert.Collection(result.Edits,
|
||||
|
|
@ -487,10 +433,6 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
public void RecognizesAttributeNamesChangedAtSameSourceSequence()
|
||||
{
|
||||
// Arrange
|
||||
var renderer = new FakeRenderer();
|
||||
var oldTree = new RenderTreeBuilder(renderer);
|
||||
var newTree = new RenderTreeBuilder(renderer);
|
||||
var diff = new RenderTreeDiffComputer(renderer);
|
||||
oldTree.OpenElement(0, "My element");
|
||||
oldTree.AddAttribute(1, "oldname", "same value");
|
||||
oldTree.CloseElement();
|
||||
|
|
@ -499,7 +441,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
newTree.CloseElement();
|
||||
|
||||
// Act
|
||||
var result = diff.ApplyNewRenderTreeVersion(oldTree.GetNodes(), newTree.GetNodes());
|
||||
var result = GetSingleUpdatedComponent();
|
||||
|
||||
// Assert
|
||||
Assert.Collection(result.Edits,
|
||||
|
|
@ -519,10 +461,6 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
public void DiffsElementsHierarchically()
|
||||
{
|
||||
// Arrange
|
||||
var renderer = new FakeRenderer();
|
||||
var oldTree = new RenderTreeBuilder(renderer);
|
||||
var newTree = new RenderTreeBuilder(renderer);
|
||||
var diff = new RenderTreeDiffComputer(renderer);
|
||||
oldTree.AddText(09, "unrelated");
|
||||
oldTree.OpenElement(10, "root");
|
||||
oldTree.OpenElement(11, "child");
|
||||
|
|
@ -542,7 +480,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
newTree.CloseElement();
|
||||
|
||||
// Act
|
||||
var result = diff.ApplyNewRenderTreeVersion(oldTree.GetNodes(), newTree.GetNodes());
|
||||
var result = GetSingleUpdatedComponent();
|
||||
|
||||
// Assert
|
||||
Assert.Collection(result.Edits,
|
||||
|
|
@ -563,10 +501,6 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
public void SkipsUnmodifiedSubtrees()
|
||||
{
|
||||
// Arrange
|
||||
var renderer = new FakeRenderer();
|
||||
var oldTree = new RenderTreeBuilder(renderer);
|
||||
var newTree = new RenderTreeBuilder(renderer);
|
||||
var diff = new RenderTreeDiffComputer(renderer);
|
||||
oldTree.OpenElement(10, "root");
|
||||
oldTree.AddText(11, "Text that will change");
|
||||
oldTree.OpenElement(12, "Subtree that will not change");
|
||||
|
|
@ -586,7 +520,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
newTree.CloseElement();
|
||||
|
||||
// Act
|
||||
var result = diff.ApplyNewRenderTreeVersion(oldTree.GetNodes(), newTree.GetNodes());
|
||||
var result = GetSingleUpdatedComponent();
|
||||
|
||||
// Assert
|
||||
Assert.Collection(result.Edits,
|
||||
|
|
@ -603,10 +537,6 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
public void SkipsUnmodifiedTrailingSiblings()
|
||||
{
|
||||
// Arrange
|
||||
var renderer = new FakeRenderer();
|
||||
var oldTree = new RenderTreeBuilder(renderer);
|
||||
var newTree = new RenderTreeBuilder(renderer);
|
||||
var diff = new RenderTreeDiffComputer(renderer);
|
||||
oldTree.AddText(10, "text1");
|
||||
oldTree.AddText(11, "text2");
|
||||
oldTree.AddText(12, "text3");
|
||||
|
|
@ -617,7 +547,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
newTree.AddText(13, "text4");
|
||||
|
||||
// Act
|
||||
var result = diff.ApplyNewRenderTreeVersion(oldTree.GetNodes(), newTree.GetNodes());
|
||||
var result = GetSingleUpdatedComponent();
|
||||
|
||||
// Assert
|
||||
Assert.Collection(result.Edits,
|
||||
|
|
@ -632,10 +562,6 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
public void InstantiatesChildComponentsForInsertedNodes()
|
||||
{
|
||||
// Arrange
|
||||
var renderer = new FakeRenderer();
|
||||
var oldTree = new RenderTreeBuilder(renderer);
|
||||
var newTree = new RenderTreeBuilder(renderer);
|
||||
var diff = new RenderTreeDiffComputer(renderer);
|
||||
oldTree.AddText(10, "text1"); // 0: text1
|
||||
oldTree.OpenElement(11, "container"); // 1: <container>
|
||||
oldTree.CloseElement(); // </container>
|
||||
|
|
@ -648,10 +574,14 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
newTree.CloseElement(); // </container>
|
||||
|
||||
// Act
|
||||
var result = diff.ApplyNewRenderTreeVersion(oldTree.GetNodes(), newTree.GetNodes());
|
||||
var renderBatch = GetRenderedBatch();
|
||||
|
||||
// Assert
|
||||
Assert.Collection(result.Edits,
|
||||
Assert.Equal(3, renderBatch.UpdatedComponents.Count);
|
||||
|
||||
// First component is the root one
|
||||
var firstComponentDiff = renderBatch.UpdatedComponents.Array[0];
|
||||
Assert.Collection(firstComponentDiff.Edits,
|
||||
entry => AssertEdit(entry, RenderTreeEditType.StepIn, 1),
|
||||
entry =>
|
||||
{
|
||||
|
|
@ -672,16 +602,26 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
Assert.IsType<FakeComponent2>(newTreeNode.Component);
|
||||
},
|
||||
entry => AssertEdit(entry, RenderTreeEditType.StepOut, 0));
|
||||
|
||||
// Second in batch is the first child component
|
||||
var secondComponentDiff = renderBatch.UpdatedComponents.Array[1];
|
||||
Assert.Equal(0, secondComponentDiff.ComponentId);
|
||||
Assert.Empty(secondComponentDiff.Edits); // Because FakeComponent produces no nodes
|
||||
Assert.Empty(secondComponentDiff.CurrentState); // Because FakeComponent produces no nodes
|
||||
|
||||
// Third in batch is the second child component
|
||||
var thirdComponentDiff = renderBatch.UpdatedComponents.Array[2];
|
||||
Assert.Equal(1, thirdComponentDiff.ComponentId);
|
||||
Assert.Collection(thirdComponentDiff.Edits,
|
||||
entry => AssertEdit(entry, RenderTreeEditType.PrependNode, 0));
|
||||
Assert.Collection(thirdComponentDiff.CurrentState,
|
||||
node => AssertNode.Text(node, $"Hello from {nameof(FakeComponent2)}"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetsKnownPropertiesOnChildComponents()
|
||||
{
|
||||
// Arrange
|
||||
var renderer = new FakeRenderer();
|
||||
var oldTree = new RenderTreeBuilder(renderer);
|
||||
var newTree = new RenderTreeBuilder(renderer);
|
||||
var diff = new RenderTreeDiffComputer(renderer);
|
||||
var testObject = new object();
|
||||
newTree.OpenComponentElement<FakeComponent>(0);
|
||||
newTree.AddAttribute(1, nameof(FakeComponent.IntProperty), 123);
|
||||
|
|
@ -690,11 +630,14 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
newTree.CloseElement();
|
||||
|
||||
// Act
|
||||
var result = diff.ApplyNewRenderTreeVersion(oldTree.GetNodes(), newTree.GetNodes());
|
||||
var renderBatch = GetRenderedBatch();
|
||||
var componentInstance = newTree.GetNodes().First().Component as FakeComponent;
|
||||
|
||||
// Assert
|
||||
AssertEdit(result.Edits.Single(), RenderTreeEditType.PrependNode, 0);
|
||||
Assert.Equal(2, renderBatch.UpdatedComponents.Count);
|
||||
|
||||
var rootComponentDiff = renderBatch.UpdatedComponents.Array[0];
|
||||
AssertEdit(rootComponentDiff.Edits.Single(), RenderTreeEditType.PrependNode, 0);
|
||||
Assert.NotNull(componentInstance);
|
||||
Assert.Equal(123, componentInstance.IntProperty);
|
||||
Assert.Equal("some string", componentInstance.StringProperty);
|
||||
|
|
@ -705,10 +648,6 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
public void ThrowsIfAssigningUnknownPropertiesToChildComponents()
|
||||
{
|
||||
// Arrange
|
||||
var renderer = new FakeRenderer();
|
||||
var oldTree = new RenderTreeBuilder(renderer);
|
||||
var newTree = new RenderTreeBuilder(renderer);
|
||||
var diff = new RenderTreeDiffComputer(renderer);
|
||||
var testObject = new object();
|
||||
newTree.OpenComponentElement<FakeComponent>(0);
|
||||
newTree.AddAttribute(1, "SomeUnknownProperty", 123);
|
||||
|
|
@ -717,7 +656,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
// Act/Assert
|
||||
var ex = Assert.Throws<InvalidOperationException>(() =>
|
||||
{
|
||||
diff.ApplyNewRenderTreeVersion(oldTree.GetNodes(), newTree.GetNodes());
|
||||
diff.ApplyNewRenderTreeVersion(new RenderBatchBuilder(), 0, oldTree.GetNodes(), newTree.GetNodes());
|
||||
});
|
||||
Assert.Equal($"Component of type '{typeof(FakeComponent).FullName}' does not have a property matching the name 'SomeUnknownProperty'.", ex.Message);
|
||||
}
|
||||
|
|
@ -726,10 +665,6 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
public void ThrowsIfAssigningReadOnlyPropertiesToChildComponents()
|
||||
{
|
||||
// Arrange
|
||||
var renderer = new FakeRenderer();
|
||||
var oldTree = new RenderTreeBuilder(renderer);
|
||||
var newTree = new RenderTreeBuilder(renderer);
|
||||
var diff = new RenderTreeDiffComputer(renderer);
|
||||
var testObject = new object();
|
||||
newTree.OpenComponentElement<FakeComponent>(0);
|
||||
newTree.AddAttribute(1, nameof(FakeComponent.ReadonlyProperty), 123);
|
||||
|
|
@ -738,7 +673,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
// Act/Assert
|
||||
var ex = Assert.Throws<InvalidOperationException>(() =>
|
||||
{
|
||||
diff.ApplyNewRenderTreeVersion(oldTree.GetNodes(), newTree.GetNodes());
|
||||
diff.ApplyNewRenderTreeVersion(new RenderBatchBuilder(), 0, oldTree.GetNodes(), newTree.GetNodes());
|
||||
});
|
||||
Assert.StartsWith($"Unable to set property '{nameof(FakeComponent.ReadonlyProperty)}' on " +
|
||||
$"component of type '{typeof(FakeComponent).FullName}'.", ex.Message);
|
||||
|
|
@ -748,10 +683,6 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
public void RetainsChildComponentsForExistingNodes()
|
||||
{
|
||||
// Arrange
|
||||
var renderer = new FakeRenderer();
|
||||
var oldTree = new RenderTreeBuilder(renderer);
|
||||
var newTree = new RenderTreeBuilder(renderer);
|
||||
var diff = new RenderTreeDiffComputer(renderer);
|
||||
oldTree.AddText(10, "text1"); // 0: text1
|
||||
oldTree.OpenElement(11, "container"); // 1: <container>
|
||||
oldTree.OpenComponentElement<FakeComponent>(12); // 2: <FakeComponent>
|
||||
|
|
@ -767,12 +698,12 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
newTree.CloseElement(); // </FakeComponent2>
|
||||
newTree.CloseElement(); // </container
|
||||
|
||||
diff.ApplyNewRenderTreeVersion(new RenderTreeBuilder(renderer).GetNodes(), oldTree.GetNodes());
|
||||
diff.ApplyNewRenderTreeVersion(new RenderBatchBuilder(), 0, new RenderTreeBuilder(renderer).GetNodes(), oldTree.GetNodes());
|
||||
var originalFakeComponentInstance = oldTree.GetNodes().Array[2].Component;
|
||||
var originalFakeComponent2Instance = oldTree.GetNodes().Array[3].Component;
|
||||
|
||||
// Act
|
||||
var result = diff.ApplyNewRenderTreeVersion(oldTree.GetNodes(), newTree.GetNodes());
|
||||
var result = GetSingleUpdatedComponent();
|
||||
var newNode1 = newTree.GetNodes().Array[2];
|
||||
var newNode2 = newTree.GetNodes().Array[3];
|
||||
|
||||
|
|
@ -788,10 +719,6 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
public void UpdatesChangedPropertiesOnRetainedChildComponents()
|
||||
{
|
||||
// Arrange
|
||||
var renderer = new FakeRenderer();
|
||||
var oldTree = new RenderTreeBuilder(renderer);
|
||||
var newTree = new RenderTreeBuilder(renderer);
|
||||
var diff = new RenderTreeDiffComputer(renderer);
|
||||
var objectWillNotChange = new object();
|
||||
oldTree.OpenComponentElement<FakeComponent>(12);
|
||||
oldTree.AddAttribute(13, nameof(FakeComponent.StringProperty), "String will change");
|
||||
|
|
@ -802,23 +729,38 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
newTree.AddAttribute(14, nameof(FakeComponent.ObjectProperty), objectWillNotChange);
|
||||
newTree.CloseElement();
|
||||
|
||||
diff.ApplyNewRenderTreeVersion(new RenderTreeBuilder(renderer).GetNodes(), oldTree.GetNodes());
|
||||
diff.ApplyNewRenderTreeVersion(new RenderBatchBuilder(), 0, new RenderTreeBuilder(renderer).GetNodes(), oldTree.GetNodes());
|
||||
var originalComponentInstance = (FakeComponent)oldTree.GetNodes().Array[0].Component;
|
||||
originalComponentInstance.ObjectProperty = null; // So we can see it doesn't get reassigned
|
||||
|
||||
// Act
|
||||
var result = diff.ApplyNewRenderTreeVersion(oldTree.GetNodes(), newTree.GetNodes());
|
||||
var renderBatch = GetRenderedBatch();
|
||||
var newComponentInstance = (FakeComponent)oldTree.GetNodes().Array[0].Component;
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, renderBatch.UpdatedComponents.Count);
|
||||
Assert.Same(originalComponentInstance, newComponentInstance);
|
||||
Assert.Equal("String did change", newComponentInstance.StringProperty);
|
||||
Assert.Null(newComponentInstance.ObjectProperty); // To observe that the property wasn't even written, we nulled it out on the original
|
||||
}
|
||||
|
||||
private RenderTreeDiff GetSingleUpdatedComponent()
|
||||
{
|
||||
var diffsInBatch = GetRenderedBatch().UpdatedComponents;
|
||||
Assert.Equal(1, diffsInBatch.Count);
|
||||
return diffsInBatch.Array[0];
|
||||
}
|
||||
|
||||
private RenderBatch GetRenderedBatch()
|
||||
{
|
||||
var batchBuilder = new RenderBatchBuilder();
|
||||
diff.ApplyNewRenderTreeVersion(batchBuilder, 0, oldTree.GetNodes(), newTree.GetNodes());
|
||||
return batchBuilder.ToBatch();
|
||||
}
|
||||
|
||||
private class FakeRenderer : Renderer
|
||||
{
|
||||
internal protected override void UpdateDisplay(int componentId, RenderTreeDiff renderTreeDiff)
|
||||
internal protected override void UpdateDisplay(RenderBatch renderBatch)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -839,7 +781,9 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
private class FakeComponent2 : IComponent
|
||||
{
|
||||
public void BuildRenderTree(RenderTreeBuilder builder)
|
||||
=> throw new NotImplementedException();
|
||||
{
|
||||
builder.AddText(100, $"Hello from {nameof(FakeComponent2)}");
|
||||
}
|
||||
}
|
||||
|
||||
private static void AssertEdit(
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
{
|
||||
builder.AddText(0, "Hello");
|
||||
builder.OpenComponentElement<MessageComponent>(1);
|
||||
builder.AddAttribute(2, nameof(MessageComponent.Message), "Nested component output");
|
||||
builder.CloseElement();
|
||||
});
|
||||
|
||||
|
|
@ -57,13 +58,8 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
|
||||
// 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.RenderNewBatch(nestedComponentId);
|
||||
// The nested component was rendered as part of the batch
|
||||
Assert.Collection(renderer.RenderTreesByComponentId[nestedComponentId],
|
||||
node => AssertNode.Text(node, "Nested component output"));
|
||||
}
|
||||
|
|
@ -418,7 +414,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
public new void RenderNewBatch(int componentId)
|
||||
=> base.RenderNewBatch(componentId);
|
||||
|
||||
protected internal override void UpdateDisplay(int componentId, RenderTreeDiff renderTreeDiff)
|
||||
protected internal override void UpdateDisplay(RenderBatch renderBatch)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -437,9 +433,14 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
public new void DispatchEvent(int componentId, int renderTreeIndex, UIEventArgs args)
|
||||
=> base.DispatchEvent(componentId, renderTreeIndex, args);
|
||||
|
||||
protected internal override void UpdateDisplay(int componentId, RenderTreeDiff renderTreeDiff)
|
||||
protected internal override void UpdateDisplay(RenderBatch renderBatch)
|
||||
{
|
||||
RenderTreesByComponentId[componentId] = renderTreeDiff.CurrentState;
|
||||
// TODO: Capture the batches and rephrase assertions in terms of those
|
||||
for (var i = 0; i < renderBatch.UpdatedComponents.Count; i++)
|
||||
{
|
||||
ref var renderTreeDiff = ref renderBatch.UpdatedComponents.Array[i];
|
||||
RenderTreesByComponentId[renderTreeDiff.ComponentId] = renderTreeDiff.CurrentState;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue