diff --git a/src/Microsoft.Blazor/RenderTree/RenderTreeDiffComputer.cs b/src/Microsoft.Blazor/RenderTree/RenderTreeDiffComputer.cs index 2a3694d1ea..8c0bcb5b36 100644 --- a/src/Microsoft.Blazor/RenderTree/RenderTreeDiffComputer.cs +++ b/src/Microsoft.Blazor/RenderTree/RenderTreeDiffComputer.cs @@ -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 _entries = new ArrayBuilder(10); - public RenderTreeDiff ComputeDifference( + public RenderTreeDiffComputer(Renderer renderer) + { + _renderer = renderer ?? throw new ArgumentNullException(nameof(renderer)); + } + + /// + /// 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. + /// + public RenderTreeDiff ApplyNewRenderTreeVersion( ArrayRange oldTree, ArrayRange 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]); + } + } + } } } diff --git a/src/Microsoft.Blazor/Rendering/ComponentState.cs b/src/Microsoft.Blazor/Rendering/ComponentState.cs index e0cbdd4959..84b27efce5 100644 --- a/src/Microsoft.Blazor/Rendering/ComponentState.cs +++ b/src/Microsoft.Blazor/Rendering/ComponentState.cs @@ -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 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); - } - } - } - /// /// Invokes the handler corresponding to an event. /// diff --git a/src/Microsoft.Blazor/Rendering/Renderer.cs b/src/Microsoft.Blazor/Rendering/Renderer.cs index 8c05afc083..155f0aca12 100644 --- a/src/Microsoft.Blazor/Rendering/Renderer.cs +++ b/src/Microsoft.Blazor/Rendering/Renderer.cs @@ -30,7 +30,7 @@ namespace Microsoft.Blazor.Rendering /// /// The . /// The assigned identifier for the . - 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); + } + /// /// Updates the visible UI to display the supplied /// at the location corresponding to the . diff --git a/test/Microsoft.Blazor.Test/RenderTreeDiffComputerTest.cs b/test/Microsoft.Blazor.Test/RenderTreeDiffComputerTest.cs index e7fdb922f9..19c7014896 100644 --- a/test/Microsoft.Blazor.Test/RenderTreeDiffComputerTest.cs +++ b/test/Microsoft.Blazor.Test/RenderTreeDiffComputerTest.cs @@ -18,14 +18,15 @@ namespace Microsoft.Blazor.Test public void RecognizesEquivalentNodesAsSame(Action 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(123); newTree.AddComponentElement(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: + oldTree.CloseElement(); // + newTree.AddText(10, "text1"); // 0: text1 + newTree.OpenElement(11, "container"); // 1: + newTree.AddComponentElement(12); // 2: + newTree.CloseElement(); // + newTree.AddComponentElement(13); // 3: + newTree.CloseElement(); // + newTree.CloseElement(); // + + // 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(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(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: + oldTree.AddComponentElement(12); // 2: + oldTree.CloseElement(); // + oldTree.AddComponentElement(13); // 3: + oldTree.CloseElement(); // + oldTree.CloseElement(); // + newTree.AddComponentElement(12); // 2: + newTree.CloseElement(); // + newTree.AddComponentElement(13); // 3: + newTree.CloseElement(); // + newTree.CloseElement(); // (1); + builder.CloseElement(); }); // Act/Assert @@ -95,6 +96,7 @@ namespace Microsoft.Blazor.Test var parentComponent = new TestComponent(builder => { builder.AddComponentElement(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(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(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)