Preserve child component instances across renders. Move instantiation into the diffing phase.

This commit is contained in:
Steve Sanderson 2018-01-24 14:29:46 -08:00
parent 23c2816bcd
commit 2469558410
5 changed files with 290 additions and 109 deletions

View File

@ -2,14 +2,28 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.Blazor.Components;
using Microsoft.Blazor.Rendering;
namespace Microsoft.Blazor.RenderTree
{
internal class RenderTreeDiffComputer
{
private readonly Renderer _renderer;
private readonly ArrayBuilder<RenderTreeEdit> _entries = new ArrayBuilder<RenderTreeEdit>(10);
public RenderTreeDiff ComputeDifference(
public RenderTreeDiffComputer(Renderer renderer)
{
_renderer = renderer ?? throw new ArgumentNullException(nameof(renderer));
}
/// <summary>
/// As well as computing the diff between the two trees, this method also has the side-effect
/// of instantiating child components on newly-inserted Component nodes, and copying the existing
/// 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(
ArrayRange<RenderTreeNode> oldTree,
ArrayRange<RenderTreeNode> newTree)
{
@ -111,13 +125,19 @@ namespace Microsoft.Blazor.RenderTree
if (treatAsInsert)
{
if (newTree[newStartIndex].NodeType == RenderTreeNodeType.Attribute)
var newNodeType = newTree[newStartIndex].NodeType;
if (newNodeType == RenderTreeNodeType.Attribute)
{
Append(RenderTreeEdit.SetAttribute(siblingIndex, newStartIndex));
newStartIndex++;
}
else
{
if (newNodeType == RenderTreeNodeType.Element || newNodeType == RenderTreeNodeType.Component)
{
InstantiateChildComponents(newTree, newStartIndex);
}
Append(RenderTreeEdit.PrependNode(siblingIndex, newStartIndex));
newStartIndex = NextSiblingIndex(newTree, newStartIndex);
siblingIndex++;
@ -225,10 +245,15 @@ namespace Microsoft.Blazor.RenderTree
var newComponentType = newTree[newNodeIndex].ComponentType;
if (oldComponentType == newComponentType)
{
// Since it's the same child component type, we'll preserve the instance
// rather than instantiating a new one
newTree[newNodeIndex].SetChildComponentInstance(
oldTree[oldNodeIndex].ComponentId,
oldTree[oldNodeIndex].Component);
// TODO: Compare attributes and notify the existing child component
// instance of any changes
// TODO: Also copy the existing child component instance to the new
// tree so we can preserve it across multiple updates
siblingIndex++;
}
else
@ -301,5 +326,22 @@ namespace Microsoft.Blazor.RenderTree
_entries.Append(entry);
}
private void InstantiateChildComponents(RenderTreeNode[] nodes, int startIndex)
{
var endIndex = nodes[startIndex].ElementDescendantsEndIndex;
for (var i = startIndex; i <= endIndex; i++)
{
if (nodes[i].NodeType == RenderTreeNodeType.Component)
{
if (nodes[i].Component != null)
{
throw new InvalidOperationException($"Child component already exists during {nameof(InstantiateChildComponents)}");
}
_renderer.InstantiateChildComponent(ref nodes[i]);
}
}
}
}
}

View File

@ -32,7 +32,7 @@ namespace Microsoft.Blazor.Rendering
_componentId = componentId;
_component = component ?? throw new ArgumentNullException(nameof(component));
_renderer = renderer ?? throw new ArgumentNullException(nameof(renderer));
_diffComputer = new RenderTreeDiffComputer();
_diffComputer = new RenderTreeDiffComputer(renderer);
_renderTreeBuilderCurrent = new RenderTreeBuilder(renderer);
_renderTreeBuilderPrevious = new RenderTreeBuilder(renderer);
}
@ -48,30 +48,12 @@ namespace Microsoft.Blazor.Rendering
_renderTreeBuilderCurrent.Clear();
_component.BuildRenderTree(_renderTreeBuilderCurrent);
var diff = _diffComputer.ComputeDifference(
var diff = _diffComputer.ApplyNewRenderTreeVersion(
_renderTreeBuilderPrevious.GetNodes(),
_renderTreeBuilderCurrent.GetNodes());
EnsureChildComponentsInstantiated(diff.CurrentState); // TODO: Move this into the diff phase
_renderer.UpdateDisplay(_componentId, diff);
}
private void EnsureChildComponentsInstantiated(ArrayRange<RenderTreeNode> renderTree)
{
var array = renderTree.Array;
var count = renderTree.Count;
for (var i = 0; i < count; i++)
{
if (array[i].NodeType == RenderTreeNodeType.Component
&& array[i].Component == null)
{
var instance = (IComponent)Activator.CreateInstance(array[i].ComponentType);
array[i].SetChildComponentInstance(
_renderer.AssignComponentId(instance),
instance);
}
}
}
/// <summary>
/// Invokes the handler corresponding to an event.
/// </summary>

View File

@ -30,7 +30,7 @@ namespace Microsoft.Blazor.Rendering
/// </summary>
/// <param name="component">The <see cref="IComponent"/>.</param>
/// <returns>The assigned identifier for the <see cref="IComponent"/>.</returns>
protected internal int AssignComponentId(IComponent component)
protected int AssignComponentId(IComponent component)
{
lock (_componentStateById)
{
@ -42,6 +42,23 @@ namespace Microsoft.Blazor.Rendering
}
}
internal void InstantiateChildComponent(ref RenderTreeNode node)
{
if (node.NodeType != RenderTreeNodeType.Component)
{
throw new ArgumentException($"The node's {nameof(RenderTreeNode.NodeType)} property must equal {RenderTreeNodeType.Component}", nameof(node));
}
if (node.Component != null)
{
throw new ArgumentException($"The node already has a non-null component instance", nameof(node));
}
var newComponent = (IComponent)Activator.CreateInstance(node.ComponentType);
var newComponentId = AssignComponentId(newComponent);
node.SetChildComponentInstance(newComponentId, newComponent);
}
/// <summary>
/// Updates the visible UI to display the supplied <paramref name="renderTree"/>
/// at the location corresponding to the <paramref name="componentId"/>.

View File

@ -18,14 +18,15 @@ namespace Microsoft.Blazor.Test
public void RecognizesEquivalentNodesAsSame(Action<RenderTreeBuilder> appendAction)
{
// Arrange
var oldTree = new RenderTreeBuilder(new FakeRenderer());
var newTree = new RenderTreeBuilder(new FakeRenderer());
var diff = new RenderTreeDiffComputer();
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.ComputeDifference(oldTree.GetNodes(), newTree.GetNodes());
var result = diff.ApplyNewRenderTreeVersion(oldTree.GetNodes(), newTree.GetNodes());
// Assert
Assert.Empty(result.Edits);
@ -49,9 +50,10 @@ namespace Microsoft.Blazor.Test
public void RecognizesNewItemsBeingInserted()
{
// Arrange
var oldTree = new RenderTreeBuilder(new FakeRenderer());
var newTree = new RenderTreeBuilder(new FakeRenderer());
var diff = new RenderTreeDiffComputer();
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");
@ -59,7 +61,7 @@ namespace Microsoft.Blazor.Test
newTree.AddText(2, "text2");
// Act
var result = diff.ComputeDifference(oldTree.GetNodes(), newTree.GetNodes());
var result = diff.ApplyNewRenderTreeVersion(oldTree.GetNodes(), newTree.GetNodes());
// Assert
Assert.Collection(result.Edits,
@ -74,9 +76,10 @@ namespace Microsoft.Blazor.Test
public void RecognizesOldItemsBeingRemoved()
{
// Arrange
var oldTree = new RenderTreeBuilder(new FakeRenderer());
var newTree = new RenderTreeBuilder(new FakeRenderer());
var diff = new RenderTreeDiffComputer();
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");
@ -84,7 +87,7 @@ namespace Microsoft.Blazor.Test
newTree.AddText(2, "text2");
// Act
var result = diff.ComputeDifference(oldTree.GetNodes(), newTree.GetNodes());
var result = diff.ApplyNewRenderTreeVersion(oldTree.GetNodes(), newTree.GetNodes());
// Assert
Assert.Collection(result.Edits,
@ -95,9 +98,10 @@ namespace Microsoft.Blazor.Test
public void RecognizesTrailingSequenceWithinLoopBlockBeingRemoved()
{
// Arrange
var oldTree = new RenderTreeBuilder(new FakeRenderer());
var newTree = new RenderTreeBuilder(new FakeRenderer());
var diff = new RenderTreeDiffComputer();
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
@ -106,7 +110,7 @@ namespace Microsoft.Blazor.Test
newTree.AddText(0, "x"); // Loop start
// Act
var result = diff.ComputeDifference(oldTree.GetNodes(), newTree.GetNodes());
var result = diff.ApplyNewRenderTreeVersion(oldTree.GetNodes(), newTree.GetNodes());
// Assert
Assert.Collection(result.Edits,
@ -118,9 +122,10 @@ namespace Microsoft.Blazor.Test
public void RecognizesTrailingSequenceWithinLoopBlockBeingAppended()
{
// Arrange
var oldTree = new RenderTreeBuilder(new FakeRenderer());
var newTree = new RenderTreeBuilder(new FakeRenderer());
var diff = new RenderTreeDiffComputer();
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
@ -129,7 +134,7 @@ namespace Microsoft.Blazor.Test
newTree.AddText(0, "x"); // Loop start
// Act
var result = diff.ComputeDifference(oldTree.GetNodes(), newTree.GetNodes());
var result = diff.ApplyNewRenderTreeVersion(oldTree.GetNodes(), newTree.GetNodes());
// Assert
Assert.Collection(result.Edits,
@ -149,9 +154,10 @@ namespace Microsoft.Blazor.Test
public void RecognizesTrailingLoopBlockBeingRemoved()
{
// Arrange
var oldTree = new RenderTreeBuilder(new FakeRenderer());
var newTree = new RenderTreeBuilder(new FakeRenderer());
var diff = new RenderTreeDiffComputer();
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
@ -160,7 +166,7 @@ namespace Microsoft.Blazor.Test
newTree.AddText(1, "x");
// Act
var result = diff.ComputeDifference(oldTree.GetNodes(), newTree.GetNodes());
var result = diff.ApplyNewRenderTreeVersion(oldTree.GetNodes(), newTree.GetNodes());
// Assert
Assert.Collection(result.Edits,
@ -172,9 +178,10 @@ namespace Microsoft.Blazor.Test
public void RecognizesTrailingLoopBlockBeingAdded()
{
// Arrange
var oldTree = new RenderTreeBuilder(new FakeRenderer());
var newTree = new RenderTreeBuilder(new FakeRenderer());
var diff = new RenderTreeDiffComputer();
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");
@ -183,7 +190,7 @@ namespace Microsoft.Blazor.Test
newTree.AddText(1, "x"); // Will be added
// Act
var result = diff.ComputeDifference(oldTree.GetNodes(), newTree.GetNodes());
var result = diff.ApplyNewRenderTreeVersion(oldTree.GetNodes(), newTree.GetNodes());
// Assert
Assert.Collection(result.Edits,
@ -203,9 +210,10 @@ namespace Microsoft.Blazor.Test
public void RecognizesLeadingLoopBlockItemsBeingAdded()
{
// Arrange
var oldTree = new RenderTreeBuilder(new FakeRenderer());
var newTree = new RenderTreeBuilder(new FakeRenderer());
var diff = new RenderTreeDiffComputer();
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");
@ -214,7 +222,7 @@ namespace Microsoft.Blazor.Test
newTree.AddText(2, "x");
// Act
var result = diff.ComputeDifference(oldTree.GetNodes(), newTree.GetNodes());
var result = diff.ApplyNewRenderTreeVersion(oldTree.GetNodes(), newTree.GetNodes());
// Assert
Assert.Collection(result.Edits,
@ -234,9 +242,10 @@ namespace Microsoft.Blazor.Test
public void RecognizesLeadingLoopBlockItemsBeingRemoved()
{
// Arrange
var oldTree = new RenderTreeBuilder(new FakeRenderer());
var newTree = new RenderTreeBuilder(new FakeRenderer());
var diff = new RenderTreeDiffComputer();
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");
@ -245,7 +254,7 @@ namespace Microsoft.Blazor.Test
newTree.AddText(2, "x"); // Note that the '0' and '1' items are not present on this iteration
// Act
var result = diff.ComputeDifference(oldTree.GetNodes(), newTree.GetNodes());
var result = diff.ApplyNewRenderTreeVersion(oldTree.GetNodes(), newTree.GetNodes());
// Assert
Assert.Collection(result.Edits,
@ -257,14 +266,15 @@ namespace Microsoft.Blazor.Test
public void HandlesAdjacentItemsBeingRemovedAndInsertedAtOnce()
{
// Arrange
var oldTree = new RenderTreeBuilder(new FakeRenderer());
var newTree = new RenderTreeBuilder(new FakeRenderer());
var diff = new RenderTreeDiffComputer();
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.ComputeDifference(oldTree.GetNodes(), newTree.GetNodes());
var result = diff.ApplyNewRenderTreeVersion(oldTree.GetNodes(), newTree.GetNodes());
// Assert
Assert.Collection(result.Edits,
@ -276,16 +286,17 @@ namespace Microsoft.Blazor.Test
public void RecognizesTextUpdates()
{
// Arrange
var oldTree = new RenderTreeBuilder(new FakeRenderer());
var newTree = new RenderTreeBuilder(new FakeRenderer());
var diff = new RenderTreeDiffComputer();
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.ComputeDifference(oldTree.GetNodes(), newTree.GetNodes());
var result = diff.ApplyNewRenderTreeVersion(oldTree.GetNodes(), newTree.GetNodes());
// Assert
Assert.Collection(result.Edits,
@ -309,16 +320,17 @@ namespace Microsoft.Blazor.Test
// decide just to throw in this scenario, since it's unnecessary to support it.
// Arrange
var oldTree = new RenderTreeBuilder(new FakeRenderer());
var newTree = new RenderTreeBuilder(new FakeRenderer());
var diff = new RenderTreeDiffComputer();
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.ComputeDifference(oldTree.GetNodes(), newTree.GetNodes());
var result = diff.ApplyNewRenderTreeVersion(oldTree.GetNodes(), newTree.GetNodes());
// Assert
Assert.Collection(result.Edits,
@ -334,14 +346,15 @@ namespace Microsoft.Blazor.Test
public void RecognizesComponentTypeChangesAtSameSequenceNumber()
{
// Arrange
var oldTree = new RenderTreeBuilder(new FakeRenderer());
var newTree = new RenderTreeBuilder(new FakeRenderer());
var diff = new RenderTreeDiffComputer();
var renderer = new FakeRenderer();
var oldTree = new RenderTreeBuilder(renderer);
var newTree = new RenderTreeBuilder(renderer);
var diff = new RenderTreeDiffComputer(renderer);
oldTree.AddComponentElement<FakeComponent>(123);
newTree.AddComponentElement<FakeComponent2>(123);
// Act
var result = diff.ComputeDifference(oldTree.GetNodes(), newTree.GetNodes());
var result = diff.ApplyNewRenderTreeVersion(oldTree.GetNodes(), newTree.GetNodes());
// Assert
Assert.Collection(result.Edits,
@ -357,9 +370,10 @@ namespace Microsoft.Blazor.Test
public void RecognizesAttributesAdded()
{
// Arrange
var oldTree = new RenderTreeBuilder(new FakeRenderer());
var newTree = new RenderTreeBuilder(new FakeRenderer());
var diff = new RenderTreeDiffComputer();
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();
@ -369,7 +383,7 @@ namespace Microsoft.Blazor.Test
newTree.CloseElement();
// Act
var result = diff.ComputeDifference(oldTree.GetNodes(), newTree.GetNodes());
var result = diff.ApplyNewRenderTreeVersion(oldTree.GetNodes(), newTree.GetNodes());
// Assert
Assert.Collection(result.Edits,
@ -384,9 +398,10 @@ namespace Microsoft.Blazor.Test
public void RecognizesAttributesRemoved()
{
// Arrange
var oldTree = new RenderTreeBuilder(new FakeRenderer());
var newTree = new RenderTreeBuilder(new FakeRenderer());
var diff = new RenderTreeDiffComputer();
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");
@ -396,7 +411,7 @@ namespace Microsoft.Blazor.Test
newTree.CloseElement();
// Act
var result = diff.ComputeDifference(oldTree.GetNodes(), newTree.GetNodes());
var result = diff.ApplyNewRenderTreeVersion(oldTree.GetNodes(), newTree.GetNodes());
// Assert
Assert.Collection(result.Edits,
@ -411,9 +426,10 @@ namespace Microsoft.Blazor.Test
public void RecognizesAttributeStringValuesChanged()
{
// Arrange
var oldTree = new RenderTreeBuilder(new FakeRenderer());
var newTree = new RenderTreeBuilder(new FakeRenderer());
var diff = new RenderTreeDiffComputer();
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");
@ -424,7 +440,7 @@ namespace Microsoft.Blazor.Test
newTree.CloseElement();
// Act
var result = diff.ComputeDifference(oldTree.GetNodes(), newTree.GetNodes());
var result = diff.ApplyNewRenderTreeVersion(oldTree.GetNodes(), newTree.GetNodes());
// Assert
Assert.Collection(result.Edits,
@ -439,9 +455,10 @@ namespace Microsoft.Blazor.Test
public void RecognizesAttributeEventHandlerValuesChanged()
{
// Arrange
var oldTree = new RenderTreeBuilder(new FakeRenderer());
var newTree = new RenderTreeBuilder(new FakeRenderer());
var diff = new RenderTreeDiffComputer();
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 = _ => { };
@ -455,7 +472,7 @@ namespace Microsoft.Blazor.Test
newTree.CloseElement();
// Act
var result = diff.ComputeDifference(oldTree.GetNodes(), newTree.GetNodes());
var result = diff.ApplyNewRenderTreeVersion(oldTree.GetNodes(), newTree.GetNodes());
// Assert
Assert.Collection(result.Edits,
@ -470,9 +487,10 @@ namespace Microsoft.Blazor.Test
public void RecognizesAttributeNamesChangedAtSameSourceSequence()
{
// Arrange
var oldTree = new RenderTreeBuilder(new FakeRenderer());
var newTree = new RenderTreeBuilder(new FakeRenderer());
var diff = new RenderTreeDiffComputer();
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();
@ -481,7 +499,7 @@ namespace Microsoft.Blazor.Test
newTree.CloseElement();
// Act
var result = diff.ComputeDifference(oldTree.GetNodes(), newTree.GetNodes());
var result = diff.ApplyNewRenderTreeVersion(oldTree.GetNodes(), newTree.GetNodes());
// Assert
Assert.Collection(result.Edits,
@ -501,9 +519,10 @@ namespace Microsoft.Blazor.Test
public void DiffsElementsHierarchically()
{
// Arrange
var oldTree = new RenderTreeBuilder(new FakeRenderer());
var newTree = new RenderTreeBuilder(new FakeRenderer());
var diff = new RenderTreeDiffComputer();
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");
@ -523,7 +542,7 @@ namespace Microsoft.Blazor.Test
newTree.CloseElement();
// Act
var result = diff.ComputeDifference(oldTree.GetNodes(), newTree.GetNodes());
var result = diff.ApplyNewRenderTreeVersion(oldTree.GetNodes(), newTree.GetNodes());
// Assert
Assert.Collection(result.Edits,
@ -544,9 +563,10 @@ namespace Microsoft.Blazor.Test
public void SkipsUnmodifiedSubtrees()
{
// Arrange
var oldTree = new RenderTreeBuilder(new FakeRenderer());
var newTree = new RenderTreeBuilder(new FakeRenderer());
var diff = new RenderTreeDiffComputer();
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");
@ -566,7 +586,7 @@ namespace Microsoft.Blazor.Test
newTree.CloseElement();
// Act
var result = diff.ComputeDifference(oldTree.GetNodes(), newTree.GetNodes());
var result = diff.ApplyNewRenderTreeVersion(oldTree.GetNodes(), newTree.GetNodes());
// Assert
Assert.Collection(result.Edits,
@ -583,9 +603,10 @@ namespace Microsoft.Blazor.Test
public void SkipsUnmodifiedTrailingSiblings()
{
// Arrange
var oldTree = new RenderTreeBuilder(new FakeRenderer());
var newTree = new RenderTreeBuilder(new FakeRenderer());
var diff = new RenderTreeDiffComputer();
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");
@ -596,7 +617,7 @@ namespace Microsoft.Blazor.Test
newTree.AddText(13, "text4");
// Act
var result = diff.ComputeDifference(oldTree.GetNodes(), newTree.GetNodes());
var result = diff.ApplyNewRenderTreeVersion(oldTree.GetNodes(), newTree.GetNodes());
// Assert
Assert.Collection(result.Edits,
@ -607,6 +628,92 @@ namespace Microsoft.Blazor.Test
});
}
[Fact]
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>
newTree.AddText(10, "text1"); // 0: text1
newTree.OpenElement(11, "container"); // 1: <container>
newTree.AddComponentElement<FakeComponent>(12); // 2: <FakeComponent>
newTree.CloseElement(); // </FakeComponent>
newTree.AddComponentElement<FakeComponent2>(13); // 3: <FakeComponent2>
newTree.CloseElement(); // </FakeComponent2>
newTree.CloseElement(); // </container>
// Act
var result = diff.ApplyNewRenderTreeVersion(oldTree.GetNodes(), newTree.GetNodes());
// Assert
Assert.Collection(result.Edits,
entry => AssertEdit(entry, RenderTreeEditType.StepIn, 1),
entry =>
{
AssertEdit(entry, RenderTreeEditType.PrependNode, 0);
Assert.Equal(2, entry.NewTreeIndex);
var newTreeNode = newTree.GetNodes().Array[entry.NewTreeIndex];
Assert.Equal(0, newTreeNode.ComponentId);
Assert.IsType<FakeComponent>(newTreeNode.Component);
},
entry =>
{
AssertEdit(entry, RenderTreeEditType.PrependNode, 1);
Assert.Equal(3, entry.NewTreeIndex);
var newTreeNode = newTree.GetNodes().Array[entry.NewTreeIndex];
Assert.Equal(1, newTreeNode.ComponentId);
Assert.IsType<FakeComponent2>(newTreeNode.Component);
},
entry => AssertEdit(entry, RenderTreeEditType.StepOut, 0));
}
[Fact]
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.AddComponentElement<FakeComponent>(12); // 2: <FakeComponent>
oldTree.CloseElement(); // </FakeComponent>
oldTree.AddComponentElement<FakeComponent2>(13); // 3: <FakeComponent2>
oldTree.CloseElement(); // </FakeComponent2>
oldTree.CloseElement(); // </container
newTree.AddText(10, "text1"); // 0: text1
newTree.OpenElement(11, "container"); // 1: <container>
newTree.AddComponentElement<FakeComponent>(12); // 2: <FakeComponent>
newTree.CloseElement(); // </FakeComponent>
newTree.AddComponentElement<FakeComponent2>(13); // 3: <FakeComponent2>
newTree.CloseElement(); // </FakeComponent2>
newTree.CloseElement(); // </container
diff.ApplyNewRenderTreeVersion(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 newNode1 = newTree.GetNodes().Array[2];
var newNode2 = newTree.GetNodes().Array[3];
// Assert
Assert.Empty(result.Edits);
Assert.Equal(0, newNode1.ComponentId);
Assert.Equal(1, newNode2.ComponentId);
Assert.Same(originalFakeComponentInstance, newNode1.Component);
Assert.Same(originalFakeComponent2Instance, newNode2.Component);
}
private class FakeRenderer : Renderer
{
internal protected override void UpdateDisplay(int componentId, RenderTreeDiff renderTreeDiff)

View File

@ -45,6 +45,7 @@ namespace Microsoft.Blazor.Test
{
builder.AddText(0, "Hello");
builder.AddComponentElement<MessageComponent>(1);
builder.CloseElement();
});
// Act/Assert
@ -95,6 +96,7 @@ namespace Microsoft.Blazor.Test
var parentComponent = new TestComponent(builder =>
{
builder.AddComponentElement<MessageComponent>(0);
builder.CloseElement();
});
var parentComponentId = renderer.AssignComponentId(parentComponent);
renderer.RenderComponent(parentComponentId);
@ -153,6 +155,7 @@ namespace Microsoft.Blazor.Test
var parentComponent = new TestComponent(builder =>
{
builder.AddComponentElement<EventComponent>(0);
builder.CloseElement();
});
var parentComponentId = renderer.AssignComponentId(parentComponent);
renderer.RenderComponent(parentComponentId);
@ -298,6 +301,36 @@ namespace Microsoft.Blazor.Test
Assert.True(didRender);
}
[Fact]
public void PreservesChildComponentInstancesWithNoAttributes()
{
// Arrange: First render, capturing child component instance
var renderer = new TestRenderer();
var message = "Hello";
var component = new TestComponent(builder =>
{
builder.AddText(0, message);
builder.AddComponentElement<MessageComponent>(1);
builder.CloseElement();
});
var rootComponentId = renderer.AssignComponentId(component);
renderer.RenderComponent(rootComponentId);
var nestedComponentInstance = (MessageComponent)renderer.RenderTreesByComponentId[rootComponentId]
.Single(node => node.NodeType == RenderTreeNodeType.Component)
.Component;
// Act: Second render
message = "Modified message";
renderer.RenderComponent(rootComponentId);
// Assert
Assert.Collection(renderer.RenderTreesByComponentId[rootComponentId],
node => AssertNode.Text(node, "Modified message"),
node => Assert.Same(nestedComponentInstance, node.Component));
}
private class NoOpRenderer : Renderer
{
public new int AssignComponentId(IComponent component)