diff --git a/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Rendering/BrowserRenderer.ts b/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Rendering/BrowserRenderer.ts
index b542fbda43..0d51b2b1d7 100644
--- a/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Rendering/BrowserRenderer.ts
+++ b/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Rendering/BrowserRenderer.ts
@@ -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) {
diff --git a/src/Microsoft.AspNetCore.Blazor.Browser/Rendering/BrowserRenderer.cs b/src/Microsoft.AspNetCore.Blazor.Browser/Rendering/BrowserRenderer.cs
index 954f987886..96b188f653 100644
--- a/src/Microsoft.AspNetCore.Blazor.Browser/Rendering/BrowserRenderer.cs
+++ b/src/Microsoft.AspNetCore.Blazor.Browser/Rendering/BrowserRenderer.cs
@@ -66,20 +66,23 @@ namespace Microsoft.AspNetCore.Blazor.Browser.Rendering
}
///
- protected override void UpdateDisplay(
- int componentId,
- RenderTreeDiff renderTreeDiff)
+ protected override void UpdateDisplay(RenderBatch batch)
{
- RegisteredFunction.InvokeUnmarshalled(
- "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(
+ "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
diff --git a/src/Microsoft.AspNetCore.Blazor.Browser/Rendering/BrowserRendererEventDispatcher.cs b/src/Microsoft.AspNetCore.Blazor.Browser/Rendering/BrowserRendererEventDispatcher.cs
index 94f7c07009..20e93cd35c 100644
--- a/src/Microsoft.AspNetCore.Blazor.Browser/Rendering/BrowserRendererEventDispatcher.cs
+++ b/src/Microsoft.AspNetCore.Blazor.Browser/Rendering/BrowserRendererEventDispatcher.cs
@@ -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)
diff --git a/src/Microsoft.AspNetCore.Blazor/RenderTree/ArrayBuilder.cs b/src/Microsoft.AspNetCore.Blazor/RenderTree/ArrayBuilder.cs
index 238334f56a..4a54e5965c 100644
--- a/src/Microsoft.AspNetCore.Blazor/RenderTree/ArrayBuilder.cs
+++ b/src/Microsoft.AspNetCore.Blazor/RenderTree/ArrayBuilder.cs
@@ -56,6 +56,16 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
_items[_itemsInUse++] = item;
}
+ ///
+ /// Sets the supplied value at the specified index. The index must be within
+ /// range for the array.
+ ///
+ /// The index.
+ /// The value.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Overwrite(int index, in T value)
+ => _items[index] = value;
+
///
/// Removes the last item.
///
diff --git a/src/Microsoft.AspNetCore.Blazor/RenderTree/RenderTreeDiff.cs b/src/Microsoft.AspNetCore.Blazor/RenderTree/RenderTreeDiff.cs
index 71c7fe957f..0c117937c5 100644
--- a/src/Microsoft.AspNetCore.Blazor/RenderTree/RenderTreeDiff.cs
+++ b/src/Microsoft.AspNetCore.Blazor/RenderTree/RenderTreeDiff.cs
@@ -11,6 +11,11 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
///
public readonly struct RenderTreeDiff
{
+ ///
+ /// Gets the ID of the component.
+ ///
+ public int ComponentId { get; }
+
///
/// Gets the changes to the render tree since a previous state.
///
@@ -23,9 +28,11 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
public ArrayRange CurrentState { get; }
internal RenderTreeDiff(
+ int componentId,
ArrayRange entries,
ArrayRange referenceTree)
{
+ ComponentId = componentId;
Edits = entries;
CurrentState = referenceTree;
}
diff --git a/src/Microsoft.AspNetCore.Blazor/RenderTree/RenderTreeDiffComputer.cs b/src/Microsoft.AspNetCore.Blazor/RenderTree/RenderTreeDiffComputer.cs
index 1f075cb050..f91b52d2e0 100644
--- a/src/Microsoft.AspNetCore.Blazor/RenderTree/RenderTreeDiffComputer.cs
+++ b/src/Microsoft.AspNetCore.Blazor/RenderTree/RenderTreeDiffComputer.cs
@@ -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.
///
- public RenderTreeDiff ApplyNewRenderTreeVersion(
+ public void ApplyNewRenderTreeVersion(
+ RenderBatchBuilder batchBuilder,
+ int componentId,
ArrayRange oldTree,
ArrayRange 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);
}
}
}
diff --git a/src/Microsoft.AspNetCore.Blazor/Rendering/ComponentState.cs b/src/Microsoft.AspNetCore.Blazor/Rendering/ComponentState.cs
index d565f4aebc..020df06189 100644
--- a/src/Microsoft.AspNetCore.Blazor/Rendering/ComponentState.cs
+++ b/src/Microsoft.AspNetCore.Blazor/Rendering/ComponentState.cs
@@ -38,25 +38,21 @@ namespace Microsoft.AspNetCore.Blazor.Rendering
}
///
- /// Regenerates the and notifies the
- /// to update the visible UI state.
+ /// Regenerates the and adds the changes to the
+ /// .
///
- 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);
}
///
@@ -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);
}
}
}
diff --git a/src/Microsoft.AspNetCore.Blazor/Rendering/RenderBatch.cs b/src/Microsoft.AspNetCore.Blazor/Rendering/RenderBatch.cs
new file mode 100644
index 0000000000..389a38a7e6
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Blazor/Rendering/RenderBatch.cs
@@ -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
+{
+ ///
+ /// Describes a set of UI changes.
+ ///
+ public readonly struct RenderBatch
+ {
+ ///
+ /// Gets the changes to components that were added or updated.
+ ///
+ public ArrayRange UpdatedComponents { get; }
+
+ internal RenderBatch(ArrayRange updatedComponents)
+ {
+ UpdatedComponents = updatedComponents;
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Blazor/Rendering/RenderBatchBuilder.cs b/src/Microsoft.AspNetCore.Blazor/Rendering/RenderBatchBuilder.cs
new file mode 100644
index 0000000000..259a0c2074
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Blazor/Rendering/RenderBatchBuilder.cs
@@ -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 _updatedComponentDiffs = new ArrayBuilder();
+
+ 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());
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Blazor/Rendering/Renderer.cs b/src/Microsoft.AspNetCore.Blazor/Rendering/Renderer.cs
index 8c5321e96b..af34be7513 100644
--- a/src/Microsoft.AspNetCore.Blazor/Rendering/Renderer.cs
+++ b/src/Microsoft.AspNetCore.Blazor/Rendering/Renderer.cs
@@ -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();
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;
+
///
/// Associates the with the , assigning
/// an identifier that is unique within the scope of the .
@@ -43,13 +49,10 @@ namespace Microsoft.AspNetCore.Blazor.Rendering
}
///
- /// Updates the visible UI to display the supplied
- /// at the location corresponding to the .
+ /// Updates the visible UI.
///
- /// The identifier for the updated .
- /// The changes to the render tree since the component was last rendered.
- internal protected abstract void UpdateDisplay(
- int componentId, RenderTreeDiff renderTreeDiff);
+ /// The changes to the UI since the previous call.
+ internal protected abstract void UpdateDisplay(RenderBatch renderBatch);
///
/// Updates the rendered state of the specified .
@@ -57,12 +60,40 @@ namespace Microsoft.AspNetCore.Blazor.Rendering
/// The identifier of the to render.
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);
}
///
diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/RazorCompilerTest.cs b/test/Microsoft.AspNetCore.Blazor.Build.Test/RazorCompilerTest.cs
index 1c4400d9a9..823997fb94 100644
--- a/test/Microsoft.AspNetCore.Blazor.Build.Test/RazorCompilerTest.cs
+++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/RazorCompilerTest.cs
@@ -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();
}
diff --git a/test/Microsoft.AspNetCore.Blazor.Test/RenderTreeBuilderTest.cs b/test/Microsoft.AspNetCore.Blazor.Test/RenderTreeBuilderTest.cs
index 170d2e5a92..80f12bbdbc 100644
--- a/test/Microsoft.AspNetCore.Blazor.Test/RenderTreeBuilderTest.cs
+++ b/test/Microsoft.AspNetCore.Blazor.Test/RenderTreeBuilderTest.cs
@@ -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();
}
}
diff --git a/test/Microsoft.AspNetCore.Blazor.Test/RenderTreeDiffComputerTest.cs b/test/Microsoft.AspNetCore.Blazor.Test/RenderTreeDiffComputerTest.cs
index 9c44beb9ff..e8afb908b5 100644
--- a/test/Microsoft.AspNetCore.Blazor.Test/RenderTreeDiffComputerTest.cs
+++ b/test/Microsoft.AspNetCore.Blazor.Test/RenderTreeDiffComputerTest.cs
@@ -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 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(123);
newTree.OpenComponentElement(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:
oldTree.CloseElement(); //
@@ -648,10 +574,14 @@ namespace Microsoft.AspNetCore.Blazor.Test
newTree.CloseElement(); //
// 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(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(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(0);
newTree.AddAttribute(1, "SomeUnknownProperty", 123);
@@ -717,7 +656,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
// Act/Assert
var ex = Assert.Throws(() =>
{
- 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(0);
newTree.AddAttribute(1, nameof(FakeComponent.ReadonlyProperty), 123);
@@ -738,7 +673,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
// Act/Assert
var ex = Assert.Throws(() =>
{
- 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:
oldTree.OpenComponentElement(12); // 2:
@@ -767,12 +698,12 @@ namespace Microsoft.AspNetCore.Blazor.Test
newTree.CloseElement(); //
newTree.CloseElement(); // (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(
diff --git a/test/Microsoft.AspNetCore.Blazor.Test/RendererTest.cs b/test/Microsoft.AspNetCore.Blazor.Test/RendererTest.cs
index 00159e44dd..e8c5b956e6 100644
--- a/test/Microsoft.AspNetCore.Blazor.Test/RendererTest.cs
+++ b/test/Microsoft.AspNetCore.Blazor.Test/RendererTest.cs
@@ -45,6 +45,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
{
builder.AddText(0, "Hello");
builder.OpenComponentElement(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(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;
+ }
}
}