In diffing, support elements with descendants

This commit is contained in:
Steve Sanderson 2018-01-21 21:30:01 +00:00
parent 0a5e27fdcf
commit b021e19598
4 changed files with 93 additions and 6 deletions

View File

@ -187,13 +187,32 @@ namespace Microsoft.Blazor.RenderTree
var newElementName = newTree[newNodeIndex].ElementName;
if (string.Equals(oldElementName, newElementName, StringComparison.Ordinal))
{
// Recurse into the element. This covers diffing the attributes as well as
// the descendants.
AppendDiffEntriesForRange(
oldTree, oldNodeIndex + 1, oldTree[oldNodeIndex].ElementDescendantsEndIndex + 1,
newTree, newNodeIndex + 1, newTree[newNodeIndex].ElementDescendantsEndIndex + 1);
var oldNodeAttributesEndIndexExcl = GetAttributesEndIndexExclusive(oldTree, oldNodeIndex);
var newNodeAttributesEndIndexExcl = GetAttributesEndIndexExclusive(newTree, newNodeIndex);
Append(RenderTreeDiffEntry.Continue());
// Diff the attributes
AppendDiffEntriesForRange(
oldTree, oldNodeIndex + 1, oldNodeAttributesEndIndexExcl,
newTree, newNodeIndex + 1, newNodeAttributesEndIndexExcl);
// Diff the children
var oldNodeChildrenEndIndexExcl = oldTree[oldNodeIndex].ElementDescendantsEndIndex + 1;
var newNodeChildrenEndIndexExcl = newTree[newNodeIndex].ElementDescendantsEndIndex + 1;
var hasChildrenToProcess =
oldNodeChildrenEndIndexExcl > oldNodeAttributesEndIndexExcl ||
newNodeChildrenEndIndexExcl > newNodeAttributesEndIndexExcl;
if (hasChildrenToProcess)
{
Append(RenderTreeDiffEntry.StepIn());
AppendDiffEntriesForRange(
oldTree, oldNodeAttributesEndIndexExcl, oldNodeChildrenEndIndexExcl,
newTree, newNodeAttributesEndIndexExcl, newNodeChildrenEndIndexExcl);
Append(RenderTreeDiffEntry.StepOut());
}
else
{
Append(RenderTreeDiffEntry.Continue());
}
}
else
{
@ -256,6 +275,21 @@ namespace Microsoft.Blazor.RenderTree
}
}
private int GetAttributesEndIndexExclusive(RenderTreeNode[] tree, int rootIndex)
{
var descendantsEndIndex = tree[rootIndex].ElementDescendantsEndIndex;
var index = rootIndex + 1;
for (; index <= descendantsEndIndex; index++)
{
if (tree[index].NodeType != RenderTreeNodeType.Attribute)
{
break;
}
}
return index;
}
private void Append(RenderTreeDiffEntry entry)
{
if (_entriesInUse == _entries.Length)

View File

@ -45,5 +45,15 @@ namespace Microsoft.Blazor.RenderTree
Type = RenderTreeDiffEntryType.RemoveAttribute,
RemovedAttributeName = name
};
internal static RenderTreeDiffEntry StepIn() => new RenderTreeDiffEntry
{
Type = RenderTreeDiffEntryType.StepIn
};
internal static RenderTreeDiffEntry StepOut() => new RenderTreeDiffEntry
{
Type = RenderTreeDiffEntryType.StepOut
};
}
}

View File

@ -11,5 +11,7 @@ namespace Microsoft.Blazor.RenderTree
SetAttribute = 4,
RemoveAttribute = 5,
UpdateText = 6,
StepIn = 7,
StepOut = 8,
}
}

View File

@ -512,6 +512,47 @@ namespace Microsoft.Blazor.Test
entry => Assert.Equal(RenderTreeDiffEntryType.Continue, entry.Type));
}
[Fact]
public void DiffsElementsHierarchically()
{
// Arrange
var oldTree = new RenderTreeBuilder(new FakeRenderer());
var newTree = new RenderTreeBuilder(new FakeRenderer());
var diff = new RenderTreeDiff();
oldTree.OpenElement(10, "root");
oldTree.OpenElement(11, "child");
oldTree.OpenElement(12, "grandchild");
oldTree.AddText(13, "grandchild old text");
oldTree.CloseElement();
oldTree.CloseElement();
oldTree.CloseElement();
newTree.OpenElement(10, "root");
newTree.OpenElement(11, "child");
newTree.OpenElement(12, "grandchild");
newTree.AddText(13, "grandchild new text");
newTree.CloseElement();
newTree.CloseElement();
newTree.CloseElement();
// Act
var result = diff.ComputeDifference(oldTree.GetNodes(), newTree.GetNodes());
// Assert
Assert.Collection(result,
entry => Assert.Equal(RenderTreeDiffEntryType.StepIn, entry.Type),
entry => Assert.Equal(RenderTreeDiffEntryType.StepIn, entry.Type),
entry => Assert.Equal(RenderTreeDiffEntryType.StepIn, entry.Type),
entry =>
{
Assert.Equal(RenderTreeDiffEntryType.UpdateText, entry.Type);
Assert.Equal(3, entry.NewTreeIndex);
},
entry => Assert.Equal(RenderTreeDiffEntryType.StepOut, entry.Type),
entry => Assert.Equal(RenderTreeDiffEntryType.StepOut, entry.Type),
entry => Assert.Equal(RenderTreeDiffEntryType.StepOut, entry.Type));
}
private class FakeRenderer : Renderer
{
internal protected override void UpdateDisplay(int componentId, ArraySegment<RenderTreeNode> renderTree)