Make RenderTreeEdit specify which sibling node it acts on, so we can eliminate the Continue concept and no longer track indices on JS side

This commit is contained in:
Steve Sanderson 2018-01-22 20:17:32 -08:00
parent 0ea4c930e9
commit df321b9b8d
6 changed files with 168 additions and 181 deletions

View File

@ -26,56 +26,58 @@ export class BrowserRenderer {
throw new Error(`No element is currently associated with component ${componentId}`);
}
this.applyEdits(componentId, { parent: element, childIndex: 0 }, edits, editsLength, referenceTree);
this.applyEdits(componentId, element, 0, edits, editsLength, referenceTree);
}
applyEdits(componentId: number, location: DOMLocation, edits: System_Array, editsLength: number, referenceTree: System_Array) {
applyEdits(componentId: number, parent: Element, childIndex: number, edits: System_Array, editsLength: number, referenceTree: System_Array) {
const childIndexStack: number[] = []; // TODO: This can be removed. We only (potentially) have nonzero childIndex values at the root, so we only need to track the current depth to determine whether we are at the root
for (let editIndex = 0; editIndex < editsLength; editIndex++) {
const edit = getRenderTreeEditPtr(edits, editIndex);
const editType = renderTreeEdit.type(edit);
switch (editType) {
case EditType.continue: {
location.childIndex++;
break;
}
case EditType.prependNode: {
const nodeIndex = renderTreeEdit.newTreeIndex(edit);
const node = getTreeNodePtr(referenceTree, nodeIndex);
this.insertNode(componentId, location, referenceTree, node, nodeIndex);
const siblingIndex = renderTreeEdit.siblingIndex(edit);
this.insertNode(componentId, parent, childIndex + siblingIndex, referenceTree, node, nodeIndex);
break;
}
case EditType.removeNode: {
removeNodeFromDOM(location);
const siblingIndex = renderTreeEdit.siblingIndex(edit);
removeNodeFromDOM(parent, childIndex + siblingIndex);
break;
}
case EditType.setAttribute: {
const nodeIndex = renderTreeEdit.newTreeIndex(edit);
const node = getTreeNodePtr(referenceTree, nodeIndex);
const element = location.parent.childNodes[location.childIndex] as HTMLElement;
const siblingIndex = renderTreeEdit.siblingIndex(edit);
const element = parent.childNodes[childIndex + siblingIndex] as HTMLElement;
this.applyAttribute(componentId, element, node, nodeIndex);
break;
}
case EditType.removeAttribute: {
removeAttributeFromDOM(location, renderTreeEdit.removedAttributeName(edit)!);
const siblingIndex = renderTreeEdit.siblingIndex(edit);
removeAttributeFromDOM(parent, childIndex + siblingIndex, renderTreeEdit.removedAttributeName(edit)!);
break;
}
case EditType.updateText: {
const nodeIndex = renderTreeEdit.newTreeIndex(edit);
const node = getTreeNodePtr(referenceTree, nodeIndex);
const domTextNode = location.parent.childNodes[location.childIndex] as Text;
const siblingIndex = renderTreeEdit.siblingIndex(edit);
const domTextNode = parent.childNodes[childIndex + siblingIndex] as Text;
domTextNode.textContent = renderTreeNode.textContent(node);
break;
}
case EditType.stepIn: {
location.parent = location.parent.childNodes[location.childIndex] as HTMLElement;
location.childIndex = 0;
childIndexStack.push(childIndex);
const siblingIndex = renderTreeEdit.siblingIndex(edit);
parent = parent.childNodes[childIndex + siblingIndex] as HTMLElement;
childIndex = 0;
break;
}
case EditType.stepOut: {
// To avoid the indexOf, consider maintaining a stack of locations
const targetElement = location.parent;
location.parent = location.parent.parentElement!;
location.childIndex = Array.prototype.indexOf.call(location.parent.childNodes, targetElement) + 1;
parent = parent.parentElement!;
childIndex = childIndexStack.pop()!;
break;
}
default: {
@ -86,19 +88,19 @@ export class BrowserRenderer {
}
}
insertNode(componentId: number, location: DOMLocation, nodes: System_Array, node: RenderTreeNodePointer, nodeIndex: number) {
insertNode(componentId: number, parent: Element, childIndex: number, nodes: System_Array, node: RenderTreeNodePointer, nodeIndex: number) {
const nodeType = renderTreeNode.nodeType(node);
switch (nodeType) {
case NodeType.element:
this.insertElement(componentId, location, nodes, node, nodeIndex);
this.insertElement(componentId, parent, childIndex, nodes, node, nodeIndex);
break;
case NodeType.text:
this.insertText(location, node);
this.insertText(parent, childIndex, node);
break;
case NodeType.attribute:
throw new Error('Attribute nodes should only be present as leading children of element nodes.');
case NodeType.component:
this.insertComponent(location, node);
this.insertComponent(parent, childIndex, node);
break;
default:
const unknownType: never = nodeType; // Compile-time verification that the switch was exhaustive
@ -106,10 +108,10 @@ export class BrowserRenderer {
}
}
insertElement(componentId: number, location: DOMLocation, nodes: System_Array, node: RenderTreeNodePointer, nodeIndex: number) {
insertElement(componentId: number, parent: Element, childIndex: number, nodes: System_Array, node: RenderTreeNodePointer, nodeIndex: number) {
const tagName = renderTreeNode.elementName(node)!;
const newDomElement = document.createElement(tagName);
insertNodeIntoDOM(newDomElement, location);
insertNodeIntoDOM(newDomElement, parent, childIndex);
// Apply attributes
const descendantsEndIndex = renderTreeNode.descendantsEndIndex(node);
@ -120,15 +122,15 @@ export class BrowserRenderer {
} else {
// As soon as we see a non-attribute child, all the subsequent child nodes are
// not attributes, so bail out and insert the remnants recursively
this.insertNodeRange(componentId, { parent: newDomElement, childIndex: 0 }, nodes, descendantIndex, descendantsEndIndex);
this.insertNodeRange(componentId, newDomElement, 0, nodes, descendantIndex, descendantsEndIndex);
break;
}
}
}
insertComponent(location: DOMLocation, node: RenderTreeNodePointer) {
insertComponent(parent: Element, childIndex: number, node: RenderTreeNodePointer) {
const containerElement = document.createElement('blazor-component');
insertNodeIntoDOM(containerElement, location);
insertNodeIntoDOM(containerElement, parent, childIndex);
const childComponentId = renderTreeNode.componentId(node);
this.attachComponentToElement(childComponentId, containerElement);
@ -146,10 +148,10 @@ export class BrowserRenderer {
]);
}
insertText(location: DOMLocation, textNode: RenderTreeNodePointer) {
insertText(parent: Element, childIndex: number, textNode: RenderTreeNodePointer) {
const textContent = renderTreeNode.textContent(textNode)!;
const newDomTextNode = document.createTextNode(textContent);
insertNodeIntoDOM(newDomTextNode, location);
insertNodeIntoDOM(newDomTextNode, parent, childIndex);
}
applyAttribute(componentId: number, toDomElement: Element, attributeNode: RenderTreeNodePointer, attributeNodeIndex: number) {
@ -189,10 +191,11 @@ export class BrowserRenderer {
}
}
insertNodeRange(componentId: number, location: DOMLocation, nodes: System_Array, startIndex: number, endIndex: number) {
insertNodeRange(componentId: number, parent: Element, childIndex: number, nodes: System_Array, startIndex: number, endIndex: number) {
for (let index = startIndex; index <= endIndex; index++) {
const node = getTreeNodePtr(nodes, index);
this.insertNode(componentId, location, nodes, node, index);
this.insertNode(componentId, parent, childIndex, nodes, node, index);
childIndex++;
// Skip over any descendants, since they are already dealt with recursively
const descendantsEndIndex = renderTreeNode.descendantsEndIndex(node);
@ -203,28 +206,20 @@ export class BrowserRenderer {
}
}
export interface DOMLocation {
parent: Element;
childIndex: number;
}
function insertNodeIntoDOM(node: Node, location: DOMLocation) {
const parent = location.parent;
if (location.childIndex >= parent.childNodes.length) {
function insertNodeIntoDOM(node: Node, parent: Element, childIndex: number) {
if (childIndex >= parent.childNodes.length) {
parent.appendChild(node);
} else {
parent.insertBefore(node, parent.childNodes[location.childIndex]);
parent.insertBefore(node, parent.childNodes[childIndex]);
}
location.childIndex++;
}
function removeNodeFromDOM(location: DOMLocation) {
const parent = location.parent;
parent.removeChild(parent.childNodes[location.childIndex]);
function removeNodeFromDOM(parent: Element, childIndex: number) {
parent.removeChild(parent.childNodes[childIndex]);
}
function removeAttributeFromDOM(location: DOMLocation, attributeName: string) {
const element = location.parent.childNodes[location.childIndex] as HTMLElement;
function removeAttributeFromDOM(parent: Element, childIndex: number, attributeName: string) {
const element = parent.childNodes[childIndex] as Element;
element.removeAttribute(attributeName);
}

View File

@ -1,6 +1,6 @@
import { System_Array, Pointer } from '../Platform/Platform';
import { platform } from '../Environment';
const renderTreeEditStructLength = 12;
const renderTreeEditStructLength = 16;
export function getRenderTreeEditPtr(renderTreeEdits: System_Array, index: number): RenderTreeEditPointer {
return platform.getArrayEntryPtr(renderTreeEdits, index, renderTreeEditStructLength) as RenderTreeEditPointer;
@ -9,19 +9,19 @@ export function getRenderTreeEditPtr(renderTreeEdits: System_Array, index: numbe
export const renderTreeEdit = {
// The properties and memory layout must be kept in sync with the .NET equivalent in RenderTreeEdit.cs
type: (edit: RenderTreeEditPointer) => platform.readInt32Field(edit, 0) as EditType,
newTreeIndex: (edit: RenderTreeEditPointer) => platform.readInt32Field(edit, 4),
removedAttributeName: (edit: RenderTreeEditPointer) => platform.readStringField(edit, 8),
siblingIndex: (edit: RenderTreeEditPointer) => platform.readInt32Field(edit, 4),
newTreeIndex: (edit: RenderTreeEditPointer) => platform.readInt32Field(edit, 8),
removedAttributeName: (edit: RenderTreeEditPointer) => platform.readStringField(edit, 12),
};
export enum EditType {
continue = 1,
prependNode = 2,
removeNode = 3,
setAttribute = 4,
removeAttribute = 5,
updateText = 6,
stepIn = 7,
stepOut = 8,
prependNode = 1,
removeNode = 2,
setAttribute = 3,
removeAttribute = 4,
updateText = 5,
stepIn = 6,
stepOut = 7,
}
// Nominal type to ensure only valid pointers are passed to the renderTreeEdit functions.

View File

@ -16,8 +16,8 @@ namespace Microsoft.Blazor.RenderTree
ArraySegment<RenderTreeNode> newTree)
{
_entriesInUse = 0;
AppendDiffEntriesForRange(oldTree.Array, 0, oldTree.Count, newTree.Array, 0, newTree.Count);
TrimTrailingContinueNodes();
var siblingIndex = 0;
AppendDiffEntriesForRange(oldTree.Array, 0, oldTree.Count, newTree.Array, 0, newTree.Count, ref siblingIndex);
// If the previous usage of the buffer showed that we have allocated
// much more space than needed, free up the excess memory
@ -34,7 +34,8 @@ namespace Microsoft.Blazor.RenderTree
private void AppendDiffEntriesForRange(
RenderTreeNode[] oldTree, int oldStartIndex, int oldEndIndexExcl,
RenderTreeNode[] newTree, int newStartIndex, int newEndIndexExcl)
RenderTreeNode[] newTree, int newStartIndex, int newEndIndexExcl,
ref int siblingIndex)
{
var hasMoreOld = oldEndIndexExcl > oldStartIndex;
var hasMoreNew = newEndIndexExcl > newStartIndex;
@ -47,7 +48,7 @@ namespace Microsoft.Blazor.RenderTree
if (oldSeq == newSeq)
{
AppendDiffEntriesForNodesWithSameSequence(oldTree, oldStartIndex, newTree, newStartIndex);
AppendDiffEntriesForNodesWithSameSequence(oldTree, oldStartIndex, newTree, newStartIndex, ref siblingIndex);
oldStartIndex = NextSiblingIndex(oldTree, oldStartIndex);
newStartIndex = NextSiblingIndex(newTree, newStartIndex);
hasMoreOld = oldEndIndexExcl > oldStartIndex;
@ -124,13 +125,14 @@ namespace Microsoft.Blazor.RenderTree
{
if (newTree[newStartIndex].NodeType == RenderTreeNodeType.Attribute)
{
Append(RenderTreeEdit.SetAttribute(newStartIndex));
Append(RenderTreeEdit.SetAttribute(siblingIndex, newStartIndex));
newStartIndex++;
}
else
{
Append(RenderTreeEdit.PrependNode(newStartIndex));
Append(RenderTreeEdit.PrependNode(siblingIndex, newStartIndex));
newStartIndex = NextSiblingIndex(newTree, newStartIndex);
siblingIndex++;
}
hasMoreNew = newEndIndexExcl > newStartIndex;
@ -140,12 +142,12 @@ namespace Microsoft.Blazor.RenderTree
{
if (oldTree[oldStartIndex].NodeType == RenderTreeNodeType.Attribute)
{
Append(RenderTreeEdit.RemoveAttribute(oldTree[oldStartIndex].AttributeName));
Append(RenderTreeEdit.RemoveAttribute(siblingIndex, oldTree[oldStartIndex].AttributeName));
oldStartIndex++;
}
else
{
Append(RenderTreeEdit.RemoveNode());
Append(RenderTreeEdit.RemoveNode(siblingIndex));
oldStartIndex = NextSiblingIndex(oldTree, oldStartIndex);
}
hasMoreOld = oldEndIndexExcl > oldStartIndex;
@ -163,7 +165,8 @@ namespace Microsoft.Blazor.RenderTree
private void AppendDiffEntriesForNodesWithSameSequence(
RenderTreeNode[] oldTree, int oldNodeIndex,
RenderTreeNode[] newTree, int newNodeIndex)
RenderTreeNode[] newTree, int newNodeIndex,
ref int siblingIndex)
{
// 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).
@ -175,11 +178,11 @@ namespace Microsoft.Blazor.RenderTree
var newText = newTree[newNodeIndex].TextContent;
if (string.Equals(oldText, newText, StringComparison.Ordinal))
{
Append(RenderTreeEdit.Continue());
siblingIndex++;
}
else
{
Append(RenderTreeEdit.UpdateText(newNodeIndex));
Append(RenderTreeEdit.UpdateText(siblingIndex, newNodeIndex));
}
break;
}
@ -196,7 +199,8 @@ namespace Microsoft.Blazor.RenderTree
// Diff the attributes
AppendDiffEntriesForRange(
oldTree, oldNodeIndex + 1, oldNodeAttributesEndIndexExcl,
newTree, newNodeIndex + 1, newNodeAttributesEndIndexExcl);
newTree, newNodeIndex + 1, newNodeAttributesEndIndexExcl,
ref siblingIndex);
// Diff the children
var oldNodeChildrenEndIndexExcl = oldTree[oldNodeIndex].ElementDescendantsEndIndex + 1;
@ -206,22 +210,26 @@ namespace Microsoft.Blazor.RenderTree
newNodeChildrenEndIndexExcl > newNodeAttributesEndIndexExcl;
if (hasChildrenToProcess)
{
Append(RenderTreeEdit.StepIn());
Append(RenderTreeEdit.StepIn(siblingIndex));
var childSiblingIndex = 0;
AppendDiffEntriesForRange(
oldTree, oldNodeAttributesEndIndexExcl, oldNodeChildrenEndIndexExcl,
newTree, newNodeAttributesEndIndexExcl, newNodeChildrenEndIndexExcl);
newTree, newNodeAttributesEndIndexExcl, newNodeChildrenEndIndexExcl,
ref childSiblingIndex);
Append(RenderTreeEdit.StepOut());
siblingIndex++;
}
else
{
Append(RenderTreeEdit.Continue());
siblingIndex++;
}
}
else
{
// Elements with different names are treated as completely unrelated
Append(RenderTreeEdit.PrependNode(newNodeIndex));
Append(RenderTreeEdit.RemoveNode());
Append(RenderTreeEdit.PrependNode(siblingIndex, newNodeIndex));
siblingIndex++;
Append(RenderTreeEdit.RemoveNode(siblingIndex));
}
break;
}
@ -236,14 +244,14 @@ namespace Microsoft.Blazor.RenderTree
// 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(RenderTreeEdit.Continue());
siblingIndex++;
}
else
{
// Child components of different types are treated as completely unrelated
Append(RenderTreeEdit.PrependNode(newNodeIndex));
Append(RenderTreeEdit.RemoveNode());
Append(RenderTreeEdit.PrependNode(siblingIndex, newNodeIndex));
siblingIndex++;
Append(RenderTreeEdit.RemoveNode(siblingIndex));
}
break;
}
@ -259,7 +267,7 @@ namespace Microsoft.Blazor.RenderTree
|| oldTree[oldNodeIndex].AttributeEventHandlerValue != newTree[newNodeIndex].AttributeEventHandlerValue;
if (changed)
{
Append(RenderTreeEdit.SetAttribute(newNodeIndex));
Append(RenderTreeEdit.SetAttribute(siblingIndex, newNodeIndex));
}
}
else
@ -267,8 +275,8 @@ namespace Microsoft.Blazor.RenderTree
// Since this code path is never reachable for Razor components (because you
// can't have two different attribute names from the same source sequence), we
// could consider removing the 'name equality' check entirely for perf
Append(RenderTreeEdit.SetAttribute(newNodeIndex));
Append(RenderTreeEdit.RemoveAttribute(oldName));
Append(RenderTreeEdit.SetAttribute(siblingIndex, newNodeIndex));
Append(RenderTreeEdit.RemoveAttribute(siblingIndex, oldName));
}
break;
}
@ -297,14 +305,11 @@ namespace Microsoft.Blazor.RenderTree
{
if (entry.Type == RenderTreeEditType.StepOut)
{
TrimTrailingContinueNodes();
// If the preceding node is now a StepIn, then we can coalesce the StepIn+StepOut
// down to a single Continue
// If the preceding node is a StepIn, then the StepOut cancels it out
if (_entriesInUse > 0 && _entries[_entriesInUse - 1].Type == RenderTreeEditType.StepIn)
{
_entriesInUse--;
entry = RenderTreeEdit.Continue();
return;
}
}
@ -315,13 +320,5 @@ namespace Microsoft.Blazor.RenderTree
_entries[_entriesInUse++] = entry;
}
private void TrimTrailingContinueNodes()
{
while (_entriesInUse > 0 && _entries[_entriesInUse - 1].Type == RenderTreeEditType.Continue)
{
_entriesInUse--;
}
}
}
}

View File

@ -13,6 +13,11 @@ namespace Microsoft.Blazor.RenderTree
/// </summary>
public RenderTreeEditType Type { get; private set; }
/// <summary>
/// Gets the index of the sibling node that the edit relates to.
/// </summary>
public int SiblingIndex { get; private set; }
/// <summary>
/// Gets the index of related data in an associated render tree. For example, if the
/// <see cref="Type"/> value is <see cref="RenderTreeEditType.PrependNode"/>, gets the
@ -26,43 +31,44 @@ namespace Microsoft.Blazor.RenderTree
/// </summary>
public string RemovedAttributeName { get; private set; }
internal static RenderTreeEdit Continue() => new RenderTreeEdit
internal static RenderTreeEdit RemoveNode(int siblingIndex) => new RenderTreeEdit
{
Type = RenderTreeEditType.Continue
Type = RenderTreeEditType.RemoveNode,
SiblingIndex = siblingIndex
};
internal static RenderTreeEdit RemoveNode() => new RenderTreeEdit
{
Type = RenderTreeEditType.RemoveNode
};
internal static RenderTreeEdit PrependNode(int newTreeIndex) => new RenderTreeEdit
internal static RenderTreeEdit PrependNode(int siblingIndex, int newTreeIndex) => new RenderTreeEdit
{
Type = RenderTreeEditType.PrependNode,
SiblingIndex = siblingIndex,
NewTreeIndex = newTreeIndex
};
internal static RenderTreeEdit UpdateText(int newTreeIndex) => new RenderTreeEdit
internal static RenderTreeEdit UpdateText(int siblingIndex, int newTreeIndex) => new RenderTreeEdit
{
Type = RenderTreeEditType.UpdateText,
SiblingIndex = siblingIndex,
NewTreeIndex = newTreeIndex
};
internal static RenderTreeEdit SetAttribute(int newNodeIndex) => new RenderTreeEdit
internal static RenderTreeEdit SetAttribute(int siblingIndex, int newNodeIndex) => new RenderTreeEdit
{
Type = RenderTreeEditType.SetAttribute,
SiblingIndex = siblingIndex,
NewTreeIndex = newNodeIndex
};
internal static RenderTreeEdit RemoveAttribute(string name) => new RenderTreeEdit
internal static RenderTreeEdit RemoveAttribute(int siblingIndex, string name) => new RenderTreeEdit
{
Type = RenderTreeEditType.RemoveAttribute,
SiblingIndex = siblingIndex,
RemovedAttributeName = name
};
internal static RenderTreeEdit StepIn() => new RenderTreeEdit
internal static RenderTreeEdit StepIn(int siblingIndex) => new RenderTreeEdit
{
Type = RenderTreeEditType.StepIn
Type = RenderTreeEditType.StepIn,
SiblingIndex = siblingIndex
};
internal static RenderTreeEdit StepOut() => new RenderTreeEdit

View File

@ -9,52 +9,41 @@ namespace Microsoft.Blazor.RenderTree
public enum RenderTreeEditType: int
{
/// <summary>
/// Indicates that there are no further operations on the current tree node, and
/// so the edit position should move to the next sibling (or if there is no next
/// sibling, then to the position where one would be).
/// Indicates that a new node should be inserted before the specified tree node.
/// </summary>
Continue = 1,
PrependNode = 1,
/// <summary>
/// Indicates that a new node should be inserted before the current tree node.
/// Indicates that the specified tree node should be removed.
/// </summary>
PrependNode = 2,
RemoveNode = 2,
/// <summary>
/// Indicates that the current tree node should be removed, and that the edit position
/// should then move to the next sibling (or if there is no next sibling, then to the
/// position where one would be).
/// </summary>
RemoveNode = 3,
/// <summary>
/// Indicates that an attribute value should be applied to the current node.
/// Indicates that an attribute value should be applied to the specified node.
/// This may be a change to an existing attribute, or the addition of a new attribute.
/// </summary>
SetAttribute = 4,
SetAttribute = 3,
/// <summary>
/// Indicates that a named attribute should be removed from the current node.
/// Indicates that a named attribute should be removed from the specified node.
/// </summary>
RemoveAttribute = 5,
RemoveAttribute = 4,
/// <summary>
/// Indicates that the text content of the current node (which must be a text node)
/// Indicates that the text content of the specified node (which must be a text node)
/// should be updated.
/// </summary>
UpdateText = 6,
UpdateText = 5,
/// <summary>
/// Indicates that the edit position should move inside the current node to its first
/// child (or, if there are no children, to a point where a first child would be).
/// Indicates that the edit position should move inside the specified node.
/// </summary>
StepIn = 7,
StepIn = 6,
/// <summary>
/// Indicates that there are no further edit operations on the current node, and the
/// edit position should move to the next sibling of the parent node (or if it does not
/// have a next sibling, then to the position where one would be).
/// edit position should move back to the parent node.
/// </summary>
StepOut = 8,
StepOut = 7,
}
}

View File

@ -63,10 +63,9 @@ namespace Microsoft.Blazor.Test
// Assert
Assert.Collection(result.Edits,
entry => Assert.Equal(RenderTreeEditType.Continue, entry.Type),
entry =>
{
Assert.Equal(RenderTreeEditType.PrependNode, entry.Type);
AssertEdit(entry, RenderTreeEditType.PrependNode, 1);
Assert.Equal(1, entry.NewTreeIndex);
});
}
@ -89,8 +88,7 @@ namespace Microsoft.Blazor.Test
// Assert
Assert.Collection(result.Edits,
entry => Assert.Equal(RenderTreeEditType.Continue, entry.Type),
entry => Assert.Equal(RenderTreeEditType.RemoveNode, entry.Type));
entry => AssertEdit(entry, RenderTreeEditType.RemoveNode, 1));
}
[Fact]
@ -112,9 +110,8 @@ namespace Microsoft.Blazor.Test
// Assert
Assert.Collection(result.Edits,
entry => Assert.Equal(RenderTreeEditType.Continue, entry.Type),
entry => Assert.Equal(RenderTreeEditType.RemoveNode, entry.Type),
entry => Assert.Equal(RenderTreeEditType.RemoveNode, entry.Type));
entry => AssertEdit(entry, RenderTreeEditType.RemoveNode, 1),
entry => AssertEdit(entry, RenderTreeEditType.RemoveNode, 1));
}
[Fact]
@ -136,15 +133,14 @@ namespace Microsoft.Blazor.Test
// Assert
Assert.Collection(result.Edits,
entry => Assert.Equal(RenderTreeEditType.Continue, entry.Type),
entry =>
{
Assert.Equal(RenderTreeEditType.PrependNode, entry.Type);
AssertEdit(entry, RenderTreeEditType.PrependNode, 1);
Assert.Equal(1, entry.NewTreeIndex);
},
entry =>
{
Assert.Equal(RenderTreeEditType.PrependNode, entry.Type);
AssertEdit(entry, RenderTreeEditType.PrependNode, 2);
Assert.Equal(2, entry.NewTreeIndex);
});
}
@ -168,10 +164,8 @@ namespace Microsoft.Blazor.Test
// Assert
Assert.Collection(result.Edits,
entry => Assert.Equal(RenderTreeEditType.Continue, entry.Type),
entry => Assert.Equal(RenderTreeEditType.Continue, entry.Type),
entry => Assert.Equal(RenderTreeEditType.RemoveNode, entry.Type),
entry => Assert.Equal(RenderTreeEditType.RemoveNode, entry.Type));
entry => AssertEdit(entry, RenderTreeEditType.RemoveNode, 2),
entry => AssertEdit(entry, RenderTreeEditType.RemoveNode, 2));
}
[Fact]
@ -193,16 +187,14 @@ namespace Microsoft.Blazor.Test
// Assert
Assert.Collection(result.Edits,
entry => Assert.Equal(RenderTreeEditType.Continue, entry.Type),
entry => Assert.Equal(RenderTreeEditType.Continue, entry.Type),
entry =>
{
Assert.Equal(RenderTreeEditType.PrependNode, entry.Type);
AssertEdit(entry, RenderTreeEditType.PrependNode, 2);
Assert.Equal(2, entry.NewTreeIndex);
},
entry =>
{
Assert.Equal(RenderTreeEditType.PrependNode, entry.Type);
AssertEdit(entry, RenderTreeEditType.PrependNode, 3);
Assert.Equal(3, entry.NewTreeIndex);
});
}
@ -226,15 +218,14 @@ namespace Microsoft.Blazor.Test
// Assert
Assert.Collection(result.Edits,
entry => Assert.Equal(RenderTreeEditType.Continue, entry.Type),
entry =>
{
Assert.Equal(RenderTreeEditType.PrependNode, entry.Type);
AssertEdit(entry, RenderTreeEditType.PrependNode, 1);
Assert.Equal(1, entry.NewTreeIndex);
},
entry =>
{
Assert.Equal(RenderTreeEditType.PrependNode, entry.Type);
AssertEdit(entry, RenderTreeEditType.PrependNode, 2);
Assert.Equal(2, entry.NewTreeIndex);
});
}
@ -258,9 +249,8 @@ namespace Microsoft.Blazor.Test
// Assert
Assert.Collection(result.Edits,
entry => Assert.Equal(RenderTreeEditType.Continue, entry.Type),
entry => Assert.Equal(RenderTreeEditType.RemoveNode, entry.Type),
entry => Assert.Equal(RenderTreeEditType.RemoveNode, entry.Type));
entry => AssertEdit(entry, RenderTreeEditType.RemoveNode, 1),
entry => AssertEdit(entry, RenderTreeEditType.RemoveNode, 1));
}
[Fact]
@ -278,8 +268,8 @@ namespace Microsoft.Blazor.Test
// Assert
Assert.Collection(result.Edits,
entry => Assert.Equal(RenderTreeEditType.RemoveNode, entry.Type),
entry => Assert.Equal(RenderTreeEditType.PrependNode, entry.Type));
entry => AssertEdit(entry, RenderTreeEditType.RemoveNode, 0),
entry => AssertEdit(entry, RenderTreeEditType.PrependNode, 0));
}
[Fact]
@ -299,7 +289,7 @@ namespace Microsoft.Blazor.Test
Assert.Collection(result.Edits,
entry =>
{
Assert.Equal(RenderTreeEditType.UpdateText, entry.Type);
AssertEdit(entry, RenderTreeEditType.UpdateText, 0);
Assert.Equal(0, entry.NewTreeIndex);
});
}
@ -327,10 +317,10 @@ namespace Microsoft.Blazor.Test
Assert.Collection(result.Edits,
entry =>
{
Assert.Equal(RenderTreeEditType.PrependNode, entry.Type);
AssertEdit(entry, RenderTreeEditType.PrependNode, 0);
Assert.Equal(0, entry.NewTreeIndex);
},
entry => Assert.Equal(RenderTreeEditType.RemoveNode, entry.Type));
entry => AssertEdit(entry, RenderTreeEditType.RemoveNode, 1));
}
[Fact]
@ -350,10 +340,10 @@ namespace Microsoft.Blazor.Test
Assert.Collection(result.Edits,
entry =>
{
Assert.Equal(RenderTreeEditType.PrependNode, entry.Type);
AssertEdit(entry, RenderTreeEditType.PrependNode, 0);
Assert.Equal(0, entry.NewTreeIndex);
},
entry => Assert.Equal(RenderTreeEditType.RemoveNode, entry.Type));
entry => AssertEdit(entry, RenderTreeEditType.RemoveNode, 1));
}
[Fact]
@ -378,7 +368,7 @@ namespace Microsoft.Blazor.Test
Assert.Collection(result.Edits,
entry =>
{
Assert.Equal(RenderTreeEditType.SetAttribute, entry.Type);
AssertEdit(entry, RenderTreeEditType.SetAttribute, 0);
Assert.Equal(2, entry.NewTreeIndex);
});
}
@ -405,7 +395,7 @@ namespace Microsoft.Blazor.Test
Assert.Collection(result.Edits,
entry =>
{
Assert.Equal(RenderTreeEditType.RemoveAttribute, entry.Type);
AssertEdit(entry, RenderTreeEditType.RemoveAttribute, 0);
Assert.Equal("will be removed", entry.RemovedAttributeName);
});
}
@ -433,7 +423,7 @@ namespace Microsoft.Blazor.Test
Assert.Collection(result.Edits,
entry =>
{
Assert.Equal(RenderTreeEditType.SetAttribute, entry.Type);
AssertEdit(entry, RenderTreeEditType.SetAttribute, 0);
Assert.Equal(2, entry.NewTreeIndex);
});
}
@ -464,7 +454,7 @@ namespace Microsoft.Blazor.Test
Assert.Collection(result.Edits,
entry =>
{
Assert.Equal(RenderTreeEditType.SetAttribute, entry.Type);
AssertEdit(entry, RenderTreeEditType.SetAttribute, 0);
Assert.Equal(2, entry.NewTreeIndex);
});
}
@ -490,12 +480,12 @@ namespace Microsoft.Blazor.Test
Assert.Collection(result.Edits,
entry =>
{
Assert.Equal(RenderTreeEditType.SetAttribute, entry.Type);
AssertEdit(entry, RenderTreeEditType.SetAttribute, 0);
Assert.Equal(1, entry.NewTreeIndex);
},
entry =>
{
Assert.Equal(RenderTreeEditType.RemoveAttribute, entry.Type);
AssertEdit(entry, RenderTreeEditType.RemoveAttribute, 0);
Assert.Equal("oldname", entry.RemovedAttributeName);
});
}
@ -507,6 +497,7 @@ namespace Microsoft.Blazor.Test
var oldTree = new RenderTreeBuilder(new FakeRenderer());
var newTree = new RenderTreeBuilder(new FakeRenderer());
var diff = new RenderTreeDiffComputer();
oldTree.AddText(09, "unrelated");
oldTree.OpenElement(10, "root");
oldTree.OpenElement(11, "child");
oldTree.OpenElement(12, "grandchild");
@ -515,6 +506,7 @@ namespace Microsoft.Blazor.Test
oldTree.CloseElement();
oldTree.CloseElement();
newTree.AddText(09, "unrelated");
newTree.OpenElement(10, "root");
newTree.OpenElement(11, "child");
newTree.OpenElement(12, "grandchild");
@ -528,17 +520,17 @@ namespace Microsoft.Blazor.Test
// Assert
Assert.Collection(result.Edits,
entry => Assert.Equal(RenderTreeEditType.StepIn, entry.Type),
entry => Assert.Equal(RenderTreeEditType.StepIn, entry.Type),
entry => Assert.Equal(RenderTreeEditType.StepIn, entry.Type),
entry => AssertEdit(entry, RenderTreeEditType.StepIn, 1),
entry => AssertEdit(entry, RenderTreeEditType.StepIn, 0),
entry => AssertEdit(entry, RenderTreeEditType.StepIn, 0),
entry =>
{
Assert.Equal(RenderTreeEditType.UpdateText, entry.Type);
Assert.Equal(3, entry.NewTreeIndex);
AssertEdit(entry, RenderTreeEditType.UpdateText, 0);
Assert.Equal(4, entry.NewTreeIndex);
},
entry => Assert.Equal(RenderTreeEditType.StepOut, entry.Type),
entry => Assert.Equal(RenderTreeEditType.StepOut, entry.Type),
entry => Assert.Equal(RenderTreeEditType.StepOut, entry.Type));
entry => AssertEdit(entry, RenderTreeEditType.StepOut, 0),
entry => AssertEdit(entry, RenderTreeEditType.StepOut, 0),
entry => AssertEdit(entry, RenderTreeEditType.StepOut, 0));
}
[Fact]
@ -571,13 +563,13 @@ namespace Microsoft.Blazor.Test
// Assert
Assert.Collection(result.Edits,
entry => Assert.Equal(RenderTreeEditType.StepIn, entry.Type),
entry => AssertEdit(entry, RenderTreeEditType.StepIn, 0),
entry =>
{
Assert.Equal(RenderTreeEditType.UpdateText, entry.Type);
AssertEdit(entry, RenderTreeEditType.UpdateText, 0);
Assert.Equal(1, entry.NewTreeIndex);
},
entry => Assert.Equal(RenderTreeEditType.StepOut, entry.Type));
entry => AssertEdit(entry, RenderTreeEditType.StepOut, 0));
}
[Fact]
@ -601,10 +593,9 @@ namespace Microsoft.Blazor.Test
// Assert
Assert.Collection(result.Edits,
entry => Assert.Equal(RenderTreeEditType.Continue, entry.Type),
entry =>
{
Assert.Equal(RenderTreeEditType.UpdateText, entry.Type);
AssertEdit(entry, RenderTreeEditType.UpdateText, 1);
Assert.Equal(1, entry.NewTreeIndex);
});
}
@ -626,5 +617,14 @@ namespace Microsoft.Blazor.Test
public void BuildRenderTree(RenderTreeBuilder builder)
=> throw new NotImplementedException();
}
private static void AssertEdit(
RenderTreeEdit edit,
RenderTreeEditType type,
int siblingIndex)
{
Assert.Equal(type, edit.Type);
Assert.Equal(siblingIndex, edit.SiblingIndex);
}
}
}