Begin on RenderTree diffing. Doesn't yet recurse into elements to handle attributes/children.
This commit is contained in:
parent
a0b354e0eb
commit
6b37494e7c
|
|
@ -0,0 +1,3 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Microsoft.Blazor.Test")]
|
||||
|
|
@ -0,0 +1,214 @@
|
|||
// 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 System;
|
||||
|
||||
namespace Microsoft.Blazor.RenderTree
|
||||
{
|
||||
internal class RenderTreeDiff
|
||||
{
|
||||
private const int MinBufferLength = 10;
|
||||
private RenderTreeDiffEntry[] _entries = new RenderTreeDiffEntry[10];
|
||||
private int _entriesInUse = 0;
|
||||
|
||||
public ArraySegment<RenderTreeDiffEntry> ComputeDifference(
|
||||
ArraySegment<RenderTreeNode> oldTree,
|
||||
ArraySegment<RenderTreeNode> newTree)
|
||||
{
|
||||
_entriesInUse = 0;
|
||||
AppendDiffEntriesForRange(oldTree.Array, 0, oldTree.Count, newTree.Array, 0, newTree.Count);
|
||||
|
||||
// If the previous usage of the buffer showed that we have allocated
|
||||
// much more space than needed, free up the excess memory
|
||||
var shrinkToLength = Math.Max(MinBufferLength, _entries.Length / 2);
|
||||
if (_entriesInUse < shrinkToLength)
|
||||
{
|
||||
Array.Resize(ref _entries, shrinkToLength);
|
||||
}
|
||||
|
||||
return new ArraySegment<RenderTreeDiffEntry>(_entries, 0, _entriesInUse);
|
||||
}
|
||||
|
||||
private void AppendDiffEntriesForRange(
|
||||
RenderTreeNode[] oldTree, int oldStartIndex, int oldEndIndexExcl,
|
||||
RenderTreeNode[] newTree, int newStartIndex, int newEndIndexExcl)
|
||||
{
|
||||
var hasMoreOld = oldEndIndexExcl > 0;
|
||||
var hasMoreNew = newEndIndexExcl > 0;
|
||||
var prevOldSeq = -1;
|
||||
var prevNewSeq = -1;
|
||||
while (hasMoreOld || hasMoreNew)
|
||||
{
|
||||
var oldSeq = hasMoreOld ? oldTree[oldStartIndex].Sequence : int.MaxValue;
|
||||
var newSeq = hasMoreNew ? newTree[newStartIndex].Sequence : int.MaxValue;
|
||||
|
||||
if (oldSeq == newSeq)
|
||||
{
|
||||
AppendDiffEntriesForNodesWithSameSequence(oldTree, oldStartIndex, newTree, newStartIndex);
|
||||
oldStartIndex = NextSiblingIndex(oldTree, oldStartIndex);
|
||||
newStartIndex = NextSiblingIndex(newTree, newStartIndex);
|
||||
hasMoreOld = oldEndIndexExcl > oldStartIndex;
|
||||
hasMoreNew = newEndIndexExcl > newStartIndex;
|
||||
prevOldSeq = oldSeq;
|
||||
prevNewSeq = newSeq;
|
||||
}
|
||||
else
|
||||
{
|
||||
bool treatAsInsert;
|
||||
var oldLoopedBack = oldSeq <= prevOldSeq;
|
||||
var newLoopedBack = newSeq <= prevNewSeq;
|
||||
if (oldLoopedBack == newLoopedBack)
|
||||
{
|
||||
// Both sequences are proceeding through the same loop block, so do a simple
|
||||
// preordered merge join (picking from whichever side brings us closer to being
|
||||
// back in sync)
|
||||
treatAsInsert = newSeq < oldSeq;
|
||||
}
|
||||
else if (oldLoopedBack)
|
||||
{
|
||||
// Old sequence looped back but new one didn't
|
||||
// The new sequence either has some extra trailing elements in the current loop block
|
||||
// which we should insert, or omits some old trailing loop blocks which we should delete
|
||||
var newLoopsBackLater = false;
|
||||
for (var testIndex = newStartIndex + 1; testIndex < newEndIndexExcl; testIndex++)
|
||||
{
|
||||
if (newTree[testIndex].Sequence < newSeq)
|
||||
{
|
||||
newLoopsBackLater = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If the new sequence loops back later to an earlier point than this,
|
||||
// then we know it's part of the existing loop block (so should be inserted).
|
||||
// If not, then it's unrelated to the previous loop block (so we should treat
|
||||
// the old items as trailing loop blocks to be removed).
|
||||
treatAsInsert = newLoopsBackLater;
|
||||
}
|
||||
else
|
||||
{
|
||||
// New sequence looped back but old one didn't
|
||||
// The old sequence either has some extra trailing elements in the current loop block
|
||||
// which we should delete, or the new sequence has extra trailing loop blocks which we
|
||||
// should insert
|
||||
var oldLoopsBackLater = false;
|
||||
for (var testIndex = oldStartIndex + 1; testIndex < oldEndIndexExcl; testIndex++)
|
||||
{
|
||||
if (oldTree[testIndex].Sequence < oldSeq)
|
||||
{
|
||||
oldLoopsBackLater = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If the old sequence loops back later to an earlier point than this,
|
||||
// then we know it's part of the existing loop block (so should be removed).
|
||||
// If not, then it's unrelated to the previous loop block (so we should treat
|
||||
// the new items as trailing loop blocks to be inserted).
|
||||
treatAsInsert = !oldLoopsBackLater;
|
||||
}
|
||||
|
||||
if (treatAsInsert)
|
||||
{
|
||||
Append(RenderTreeDiffEntry.PrependNode(newStartIndex));
|
||||
newStartIndex = NextSiblingIndex(newTree, newStartIndex);
|
||||
hasMoreNew = newEndIndexExcl > newStartIndex;
|
||||
prevNewSeq = newSeq;
|
||||
}
|
||||
else
|
||||
{
|
||||
Append(RenderTreeDiffEntry.RemoveNode());
|
||||
oldStartIndex = NextSiblingIndex(oldTree, oldStartIndex);
|
||||
hasMoreOld = oldEndIndexExcl > oldStartIndex;
|
||||
prevOldSeq = oldSeq;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int NextSiblingIndex(RenderTreeNode[] tree, int nodeIndex)
|
||||
{
|
||||
var descendantsEndIndex = tree[nodeIndex].ElementDescendantsEndIndex;
|
||||
return (descendantsEndIndex == 0 ? nodeIndex : descendantsEndIndex) + 1;
|
||||
}
|
||||
|
||||
private void AppendDiffEntriesForNodesWithSameSequence(
|
||||
RenderTreeNode[] oldTree, int oldNodeIndex,
|
||||
RenderTreeNode[] newTree, int newNodeIndex)
|
||||
{
|
||||
// We can assume that the old and new nodes are of the same type, because they correspond
|
||||
// to the same sequence number (and if not, the behaviour is undefined).
|
||||
switch (newTree[newNodeIndex].NodeType)
|
||||
{
|
||||
case RenderTreeNodeType.Text:
|
||||
{
|
||||
var oldText = oldTree[oldNodeIndex].TextContent;
|
||||
var newText = newTree[newNodeIndex].TextContent;
|
||||
if (string.Equals(oldText, newText, StringComparison.Ordinal))
|
||||
{
|
||||
Append(RenderTreeDiffEntry.Continue());
|
||||
}
|
||||
else
|
||||
{
|
||||
Append(RenderTreeDiffEntry.UpdateText(newNodeIndex));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case RenderTreeNodeType.Element:
|
||||
{
|
||||
var oldElementName = oldTree[oldNodeIndex].ElementName;
|
||||
var newElementName = newTree[newNodeIndex].ElementName;
|
||||
if (string.Equals(oldElementName, newElementName, StringComparison.Ordinal))
|
||||
{
|
||||
// TODO: Compare attributes
|
||||
// TODO: Then, recurse into children
|
||||
Append(RenderTreeDiffEntry.Continue());
|
||||
}
|
||||
else
|
||||
{
|
||||
// Elements with different names are treated as completely unrelated
|
||||
Append(RenderTreeDiffEntry.PrependNode(newNodeIndex));
|
||||
Append(RenderTreeDiffEntry.RemoveNode());
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case RenderTreeNodeType.Component:
|
||||
{
|
||||
var oldComponentType = oldTree[oldNodeIndex].ComponentType;
|
||||
var newComponentType = newTree[newNodeIndex].ComponentType;
|
||||
if (oldComponentType == newComponentType)
|
||||
{
|
||||
// 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
|
||||
|
||||
Append(RenderTreeDiffEntry.Continue());
|
||||
}
|
||||
else
|
||||
{
|
||||
// Child components of different types are treated as completely unrelated
|
||||
Append(RenderTreeDiffEntry.PrependNode(newNodeIndex));
|
||||
Append(RenderTreeDiffEntry.RemoveNode());
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
throw new NotImplementedException($"Not yet implemented: diffing for nodes of type {newTree[newNodeIndex].NodeType}");
|
||||
}
|
||||
}
|
||||
|
||||
private void Append(RenderTreeDiffEntry entry)
|
||||
{
|
||||
if (_entriesInUse == _entries.Length)
|
||||
{
|
||||
Array.Resize(ref _entries, _entries.Length * 2);
|
||||
}
|
||||
|
||||
_entries[_entriesInUse++] = entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
// 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.
|
||||
|
||||
namespace Microsoft.Blazor.RenderTree
|
||||
{
|
||||
internal struct RenderTreeDiffEntry
|
||||
{
|
||||
public RenderTreeDiffEntryType Type { get; private set; }
|
||||
public int NewTreeIndex { get; private set; }
|
||||
|
||||
public static RenderTreeDiffEntry Continue() => new RenderTreeDiffEntry
|
||||
{
|
||||
Type = RenderTreeDiffEntryType.Continue
|
||||
};
|
||||
|
||||
internal static RenderTreeDiffEntry RemoveNode() => new RenderTreeDiffEntry
|
||||
{
|
||||
Type = RenderTreeDiffEntryType.RemoveNode
|
||||
};
|
||||
|
||||
internal static RenderTreeDiffEntry PrependNode(int newTreeIndex) => new RenderTreeDiffEntry
|
||||
{
|
||||
Type = RenderTreeDiffEntryType.PrependNode,
|
||||
NewTreeIndex = newTreeIndex
|
||||
};
|
||||
|
||||
internal static RenderTreeDiffEntry UpdateText(int newTreeIndex) => new RenderTreeDiffEntry
|
||||
{
|
||||
Type = RenderTreeDiffEntryType.UpdateText,
|
||||
NewTreeIndex = newTreeIndex
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
// 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.
|
||||
|
||||
namespace Microsoft.Blazor.RenderTree
|
||||
{
|
||||
internal enum RenderTreeDiffEntryType: int
|
||||
{
|
||||
Continue = 1,
|
||||
PrependNode = 2,
|
||||
RemoveNode = 3,
|
||||
UpdateAttribute = 4,
|
||||
UpdateText = 5,
|
||||
}
|
||||
}
|
||||
|
|
@ -298,7 +298,7 @@ namespace Microsoft.Blazor.Test
|
|||
|
||||
private class TestRenderer : Renderer
|
||||
{
|
||||
protected override void UpdateDisplay(int componentId, ArraySegment<RenderTreeNode> renderTree)
|
||||
internal protected override void UpdateDisplay(int componentId, ArraySegment<RenderTreeNode> renderTree)
|
||||
=> throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,326 @@
|
|||
// 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.Blazor.Components;
|
||||
using Microsoft.Blazor.Rendering;
|
||||
using Microsoft.Blazor.RenderTree;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.Blazor.Test
|
||||
{
|
||||
public class RenderTreeDiffTest
|
||||
{
|
||||
[Theory]
|
||||
[MemberData(nameof(RecognizesEquivalentNodesAsSameCases))]
|
||||
public void RecognizesEquivalentNodesAsSame(Action<RenderTreeBuilder> appendAction)
|
||||
{
|
||||
// Arrange
|
||||
var oldTree = new RenderTreeBuilder(new FakeRenderer());
|
||||
var newTree = new RenderTreeBuilder(new FakeRenderer());
|
||||
var diff = new RenderTreeDiff();
|
||||
appendAction(oldTree);
|
||||
appendAction(newTree);
|
||||
|
||||
// Act
|
||||
var result = diff.ComputeDifference(oldTree.GetNodes(), newTree.GetNodes());
|
||||
|
||||
// Assert
|
||||
Assert.Collection(result,
|
||||
entry => Assert.Equal(RenderTreeDiffEntryType.Continue, entry.Type));
|
||||
}
|
||||
|
||||
public static IEnumerable<object[]> RecognizesEquivalentNodesAsSameCases()
|
||||
=> new Action<RenderTreeBuilder>[]
|
||||
{
|
||||
builder => builder.AddText(0, "Hello"),
|
||||
builder => builder.OpenElement(0, "Some Element"),
|
||||
builder =>
|
||||
{
|
||||
builder.OpenElement(0, "Some Element");
|
||||
builder.AddAttribute("My attribute", "My value");
|
||||
builder.CloseElement();
|
||||
},
|
||||
builder => builder.AddComponent<FakeComponent>(0)
|
||||
}.Select(x => new object[] { x });
|
||||
|
||||
[Fact]
|
||||
public void RecognizesNewItemsBeingInserted()
|
||||
{
|
||||
// Arrange
|
||||
var oldTree = new RenderTreeBuilder(new FakeRenderer());
|
||||
var newTree = new RenderTreeBuilder(new FakeRenderer());
|
||||
var diff = new RenderTreeDiff();
|
||||
oldTree.AddText(0, "text0");
|
||||
oldTree.AddText(2, "text2");
|
||||
newTree.AddText(0, "text0");
|
||||
newTree.AddText(1, "text1");
|
||||
newTree.AddText(2, "text2");
|
||||
|
||||
// Act
|
||||
var result = diff.ComputeDifference(oldTree.GetNodes(), newTree.GetNodes());
|
||||
|
||||
// Assert
|
||||
Assert.Collection(result,
|
||||
entry => Assert.Equal(RenderTreeDiffEntryType.Continue, entry.Type),
|
||||
entry =>
|
||||
{
|
||||
Assert.Equal(RenderTreeDiffEntryType.PrependNode, entry.Type);
|
||||
Assert.Equal(1, entry.NewTreeIndex);
|
||||
},
|
||||
entry => Assert.Equal(RenderTreeDiffEntryType.Continue, entry.Type));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RecognizesOldItemsBeingRemoved()
|
||||
{
|
||||
// Arrange
|
||||
var oldTree = new RenderTreeBuilder(new FakeRenderer());
|
||||
var newTree = new RenderTreeBuilder(new FakeRenderer());
|
||||
var diff = new RenderTreeDiff();
|
||||
oldTree.AddText(0, "text0");
|
||||
oldTree.AddText(1, "text1");
|
||||
oldTree.AddText(2, "text2");
|
||||
newTree.AddText(0, "text0");
|
||||
newTree.AddText(2, "text2");
|
||||
|
||||
// Act
|
||||
var result = diff.ComputeDifference(oldTree.GetNodes(), newTree.GetNodes());
|
||||
|
||||
// Assert
|
||||
Assert.Collection(result,
|
||||
entry => Assert.Equal(RenderTreeDiffEntryType.Continue, entry.Type),
|
||||
entry => Assert.Equal(RenderTreeDiffEntryType.RemoveNode, entry.Type),
|
||||
entry => Assert.Equal(RenderTreeDiffEntryType.Continue, entry.Type));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RecognizesTrailingSequenceWithinLoopBlockBeingRemoved()
|
||||
{
|
||||
// Arrange
|
||||
var oldTree = new RenderTreeBuilder(new FakeRenderer());
|
||||
var newTree = new RenderTreeBuilder(new FakeRenderer());
|
||||
var diff = new RenderTreeDiff();
|
||||
oldTree.AddText(0, "x"); // Loop start
|
||||
oldTree.AddText(1, "x"); // Will be removed
|
||||
oldTree.AddText(2, "x"); // Will be removed
|
||||
oldTree.AddText(0, "x"); // Loop start
|
||||
newTree.AddText(0, "x"); // Loop start
|
||||
newTree.AddText(0, "x"); // Loop start
|
||||
|
||||
// Act
|
||||
var result = diff.ComputeDifference(oldTree.GetNodes(), newTree.GetNodes());
|
||||
|
||||
// Assert
|
||||
Assert.Collection(result,
|
||||
entry => Assert.Equal(RenderTreeDiffEntryType.Continue, entry.Type),
|
||||
entry => Assert.Equal(RenderTreeDiffEntryType.RemoveNode, entry.Type),
|
||||
entry => Assert.Equal(RenderTreeDiffEntryType.RemoveNode, entry.Type),
|
||||
entry => Assert.Equal(RenderTreeDiffEntryType.Continue, entry.Type));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RecognizesTrailingSequenceWithinLoopBlockBeingAppended()
|
||||
{
|
||||
// Arrange
|
||||
var oldTree = new RenderTreeBuilder(new FakeRenderer());
|
||||
var newTree = new RenderTreeBuilder(new FakeRenderer());
|
||||
var diff = new RenderTreeDiff();
|
||||
oldTree.AddText(0, "x"); // Loop start
|
||||
oldTree.AddText(0, "x"); // Loop start
|
||||
newTree.AddText(0, "x"); // Loop start
|
||||
newTree.AddText(1, "x"); // Will be added
|
||||
newTree.AddText(2, "x"); // Will be added
|
||||
newTree.AddText(0, "x"); // Loop start
|
||||
|
||||
// Act
|
||||
var result = diff.ComputeDifference(oldTree.GetNodes(), newTree.GetNodes());
|
||||
|
||||
// Assert
|
||||
Assert.Collection(result,
|
||||
entry => Assert.Equal(RenderTreeDiffEntryType.Continue, entry.Type),
|
||||
entry =>
|
||||
{
|
||||
Assert.Equal(RenderTreeDiffEntryType.PrependNode, entry.Type);
|
||||
Assert.Equal(1, entry.NewTreeIndex);
|
||||
},
|
||||
entry =>
|
||||
{
|
||||
Assert.Equal(RenderTreeDiffEntryType.PrependNode, entry.Type);
|
||||
Assert.Equal(2, entry.NewTreeIndex);
|
||||
},
|
||||
entry => Assert.Equal(RenderTreeDiffEntryType.Continue, entry.Type));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RecognizesTrailingLoopBlockBeingRemoved()
|
||||
{
|
||||
// Arrange
|
||||
var oldTree = new RenderTreeBuilder(new FakeRenderer());
|
||||
var newTree = new RenderTreeBuilder(new FakeRenderer());
|
||||
var diff = new RenderTreeDiff();
|
||||
oldTree.AddText(0, "x");
|
||||
oldTree.AddText(1, "x");
|
||||
oldTree.AddText(0, "x"); // Will be removed
|
||||
oldTree.AddText(1, "x"); // Will be removed
|
||||
newTree.AddText(0, "x");
|
||||
newTree.AddText(1, "x");
|
||||
|
||||
// Act
|
||||
var result = diff.ComputeDifference(oldTree.GetNodes(), newTree.GetNodes());
|
||||
|
||||
// Assert
|
||||
Assert.Collection(result,
|
||||
entry => Assert.Equal(RenderTreeDiffEntryType.Continue, entry.Type),
|
||||
entry => Assert.Equal(RenderTreeDiffEntryType.Continue, entry.Type),
|
||||
entry => Assert.Equal(RenderTreeDiffEntryType.RemoveNode, entry.Type),
|
||||
entry => Assert.Equal(RenderTreeDiffEntryType.RemoveNode, entry.Type));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RecognizesTrailingLoopBlockBeingAdded()
|
||||
{
|
||||
// Arrange
|
||||
var oldTree = new RenderTreeBuilder(new FakeRenderer());
|
||||
var newTree = new RenderTreeBuilder(new FakeRenderer());
|
||||
var diff = new RenderTreeDiff();
|
||||
oldTree.AddText(0, "x");
|
||||
oldTree.AddText(1, "x");
|
||||
newTree.AddText(0, "x");
|
||||
newTree.AddText(1, "x");
|
||||
newTree.AddText(0, "x"); // Will be added
|
||||
newTree.AddText(1, "x"); // Will be added
|
||||
|
||||
// Act
|
||||
var result = diff.ComputeDifference(oldTree.GetNodes(), newTree.GetNodes());
|
||||
|
||||
// Assert
|
||||
Assert.Collection(result,
|
||||
entry => Assert.Equal(RenderTreeDiffEntryType.Continue, entry.Type),
|
||||
entry => Assert.Equal(RenderTreeDiffEntryType.Continue, entry.Type),
|
||||
entry =>
|
||||
{
|
||||
Assert.Equal(RenderTreeDiffEntryType.PrependNode, entry.Type);
|
||||
Assert.Equal(2, entry.NewTreeIndex);
|
||||
},
|
||||
entry =>
|
||||
{
|
||||
Assert.Equal(RenderTreeDiffEntryType.PrependNode, entry.Type);
|
||||
Assert.Equal(3, entry.NewTreeIndex);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HandlesAdjacentItemsBeingRemovedAndInsertedAtOnce()
|
||||
{
|
||||
// Arrange
|
||||
var oldTree = new RenderTreeBuilder(new FakeRenderer());
|
||||
var newTree = new RenderTreeBuilder(new FakeRenderer());
|
||||
var diff = new RenderTreeDiff();
|
||||
oldTree.AddText(0, "text");
|
||||
newTree.AddText(1, "text");
|
||||
|
||||
// Act
|
||||
var result = diff.ComputeDifference(oldTree.GetNodes(), newTree.GetNodes());
|
||||
|
||||
// Assert
|
||||
Assert.Collection(result,
|
||||
entry => Assert.Equal(RenderTreeDiffEntryType.RemoveNode, entry.Type),
|
||||
entry => Assert.Equal(RenderTreeDiffEntryType.PrependNode, entry.Type));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RecognizesTextUpdates()
|
||||
{
|
||||
// Arrange
|
||||
var oldTree = new RenderTreeBuilder(new FakeRenderer());
|
||||
var newTree = new RenderTreeBuilder(new FakeRenderer());
|
||||
var diff = new RenderTreeDiff();
|
||||
oldTree.AddText(123, "old text");
|
||||
newTree.AddText(123, "new text");
|
||||
|
||||
// Act
|
||||
var result = diff.ComputeDifference(oldTree.GetNodes(), newTree.GetNodes());
|
||||
|
||||
// Assert
|
||||
Assert.Collection(result,
|
||||
entry =>
|
||||
{
|
||||
Assert.Equal(RenderTreeDiffEntryType.UpdateText, entry.Type);
|
||||
Assert.Equal(0, entry.NewTreeIndex);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RecognizesElementNameChangesAtSameSequenceNumber()
|
||||
{
|
||||
// Note: It's not possible to trigger this scenario from a Razor component, because
|
||||
// a given source sequence can only have a single fixed element name. We might later
|
||||
// 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 RenderTreeDiff();
|
||||
oldTree.OpenElement(123, "old element");
|
||||
oldTree.CloseElement();
|
||||
newTree.OpenElement(123, "new element");
|
||||
newTree.CloseElement();
|
||||
|
||||
// Act
|
||||
var result = diff.ComputeDifference(oldTree.GetNodes(), newTree.GetNodes());
|
||||
|
||||
// Assert
|
||||
Assert.Collection(result,
|
||||
entry =>
|
||||
{
|
||||
Assert.Equal(RenderTreeDiffEntryType.PrependNode, entry.Type);
|
||||
Assert.Equal(0, entry.NewTreeIndex);
|
||||
},
|
||||
entry => Assert.Equal(RenderTreeDiffEntryType.RemoveNode, entry.Type));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RecognizesComponentTypeChangesAtSameSequenceNumber()
|
||||
{
|
||||
// Arrange
|
||||
var oldTree = new RenderTreeBuilder(new FakeRenderer());
|
||||
var newTree = new RenderTreeBuilder(new FakeRenderer());
|
||||
var diff = new RenderTreeDiff();
|
||||
oldTree.AddComponent<FakeComponent>(123);
|
||||
newTree.AddComponent<FakeComponent2>(123);
|
||||
|
||||
// Act
|
||||
var result = diff.ComputeDifference(oldTree.GetNodes(), newTree.GetNodes());
|
||||
|
||||
// Assert
|
||||
Assert.Collection(result,
|
||||
entry =>
|
||||
{
|
||||
Assert.Equal(RenderTreeDiffEntryType.PrependNode, entry.Type);
|
||||
Assert.Equal(0, entry.NewTreeIndex);
|
||||
},
|
||||
entry => Assert.Equal(RenderTreeDiffEntryType.RemoveNode, entry.Type));
|
||||
}
|
||||
|
||||
private class FakeRenderer : Renderer
|
||||
{
|
||||
internal protected override void UpdateDisplay(int componentId, ArraySegment<RenderTreeNode> renderTree)
|
||||
=> throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private class FakeComponent : IComponent
|
||||
{
|
||||
public void BuildRenderTree(RenderTreeBuilder builder)
|
||||
=> throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private class FakeComponent2 : IComponent
|
||||
{
|
||||
public void BuildRenderTree(RenderTreeBuilder builder)
|
||||
=> throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -306,7 +306,7 @@ namespace Microsoft.Blazor.Test
|
|||
public new void RenderComponent(int componentId)
|
||||
=> base.RenderComponent(componentId);
|
||||
|
||||
protected override void UpdateDisplay(int componentId, ArraySegment<RenderTreeNode> renderTree)
|
||||
internal protected override void UpdateDisplay(int componentId, ArraySegment<RenderTreeNode> renderTree)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -325,7 +325,7 @@ namespace Microsoft.Blazor.Test
|
|||
public new void DispatchEvent(int componentId, int renderTreeIndex, UIEventArgs args)
|
||||
=> base.DispatchEvent(componentId, renderTreeIndex, args);
|
||||
|
||||
protected override void UpdateDisplay(int componentId, ArraySegment<RenderTreeNode> renderTree)
|
||||
internal protected override void UpdateDisplay(int componentId, ArraySegment<RenderTreeNode> renderTree)
|
||||
{
|
||||
RenderTreesByComponentId[componentId] = renderTree;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue