Support diffing attributes. This required tracking 'sequence' values on attributes too.
This commit is contained in:
parent
7a1abbaca3
commit
0a5e27fdcf
|
|
@ -30,10 +30,22 @@ namespace Microsoft.Blazor.Build.Core.RazorCompilation.Engine
|
||||||
|
|
||||||
private string _unconsumedHtml;
|
private string _unconsumedHtml;
|
||||||
private IList<object> _currentAttributeValues;
|
private IList<object> _currentAttributeValues;
|
||||||
private IDictionary<string, object> _currentElementAttributes = new Dictionary<string, object>();
|
private IDictionary<string, PendingAttribute> _currentElementAttributes = new Dictionary<string, PendingAttribute>();
|
||||||
private IList<IntermediateToken> _currentElementAttributeTokens = new List<IntermediateToken>();
|
private IList<PendingAttributeToken> _currentElementAttributeTokens = new List<PendingAttributeToken>();
|
||||||
private int _sourceSequence = 0;
|
private int _sourceSequence = 0;
|
||||||
|
|
||||||
|
private struct PendingAttribute
|
||||||
|
{
|
||||||
|
public int SourceSequence;
|
||||||
|
public object AttributeValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct PendingAttributeToken
|
||||||
|
{
|
||||||
|
public int SourceSequence;
|
||||||
|
public IntermediateToken AttributeValue;
|
||||||
|
}
|
||||||
|
|
||||||
public override void BeginWriterScope(CodeRenderingContext context, string writer)
|
public override void BeginWriterScope(CodeRenderingContext context, string writer)
|
||||||
{
|
{
|
||||||
throw new System.NotImplementedException(nameof(BeginWriterScope));
|
throw new System.NotImplementedException(nameof(BeginWriterScope));
|
||||||
|
|
@ -100,7 +112,11 @@ namespace Microsoft.Blazor.Build.Core.RazorCompilation.Engine
|
||||||
if (_unconsumedHtml != null)
|
if (_unconsumedHtml != null)
|
||||||
{
|
{
|
||||||
var token = (IntermediateToken)node.Children.Single();
|
var token = (IntermediateToken)node.Children.Single();
|
||||||
_currentElementAttributeTokens.Add(token);
|
_currentElementAttributeTokens.Add(new PendingAttributeToken
|
||||||
|
{
|
||||||
|
SourceSequence = _sourceSequence++,
|
||||||
|
AttributeValue = token
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -149,9 +165,14 @@ namespace Microsoft.Blazor.Build.Core.RazorCompilation.Engine
|
||||||
|
|
||||||
public override void WriteHtmlAttribute(CodeRenderingContext context, HtmlAttributeIntermediateNode node)
|
public override void WriteHtmlAttribute(CodeRenderingContext context, HtmlAttributeIntermediateNode node)
|
||||||
{
|
{
|
||||||
|
var attributeSourceSequence = _sourceSequence++;
|
||||||
_currentAttributeValues = new List<object>();
|
_currentAttributeValues = new List<object>();
|
||||||
context.RenderChildren(node);
|
context.RenderChildren(node);
|
||||||
_currentElementAttributes[node.AttributeName] = _currentAttributeValues;
|
_currentElementAttributes[node.AttributeName] = new PendingAttribute
|
||||||
|
{
|
||||||
|
SourceSequence = attributeSourceSequence,
|
||||||
|
AttributeValue = _currentAttributeValues
|
||||||
|
};
|
||||||
_currentAttributeValues = null;
|
_currentAttributeValues = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -213,14 +234,14 @@ namespace Microsoft.Blazor.Build.Core.RazorCompilation.Engine
|
||||||
|
|
||||||
foreach (var attribute in nextTag.Attributes)
|
foreach (var attribute in nextTag.Attributes)
|
||||||
{
|
{
|
||||||
WriteAttribute(codeWriter, attribute.Key, attribute.Value);
|
WriteAttribute(codeWriter, _sourceSequence++, attribute.Key, attribute.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_currentElementAttributes.Count > 0)
|
if (_currentElementAttributes.Count > 0)
|
||||||
{
|
{
|
||||||
foreach (var pair in _currentElementAttributes)
|
foreach (var pair in _currentElementAttributes)
|
||||||
{
|
{
|
||||||
WriteAttribute(codeWriter, pair.Key, pair.Value);
|
WriteAttribute(codeWriter, pair.Value.SourceSequence, pair.Key, pair.Value.AttributeValue);
|
||||||
}
|
}
|
||||||
_currentElementAttributes.Clear();
|
_currentElementAttributes.Clear();
|
||||||
}
|
}
|
||||||
|
|
@ -231,7 +252,9 @@ namespace Microsoft.Blazor.Build.Core.RazorCompilation.Engine
|
||||||
{
|
{
|
||||||
codeWriter
|
codeWriter
|
||||||
.WriteStartMethodInvocation($"{builderVarName}.{nameof(RenderTreeBuilder.AddAttribute)}")
|
.WriteStartMethodInvocation($"{builderVarName}.{nameof(RenderTreeBuilder.AddAttribute)}")
|
||||||
.Write(token.Content)
|
.Write(token.SourceSequence.ToString())
|
||||||
|
.WriteParameterSeparator()
|
||||||
|
.Write(token.AttributeValue.Content)
|
||||||
.WriteEndMethodInvocation();
|
.WriteEndMethodInvocation();
|
||||||
}
|
}
|
||||||
_currentElementAttributeTokens.Clear();
|
_currentElementAttributeTokens.Clear();
|
||||||
|
|
@ -263,10 +286,12 @@ namespace Microsoft.Blazor.Build.Core.RazorCompilation.Engine
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void WriteAttribute(CodeWriter codeWriter, string key, object value)
|
private static void WriteAttribute(CodeWriter codeWriter, int sourceSequence, string key, object value)
|
||||||
{
|
{
|
||||||
codeWriter
|
codeWriter
|
||||||
.WriteStartMethodInvocation($"{builderVarName}.{nameof(RenderTreeBuilder.AddAttribute)}")
|
.WriteStartMethodInvocation($"{builderVarName}.{nameof(RenderTreeBuilder.AddAttribute)}")
|
||||||
|
.Write(sourceSequence.ToString())
|
||||||
|
.WriteParameterSeparator()
|
||||||
.WriteStringLiteral(key)
|
.WriteStringLiteral(key)
|
||||||
.WriteParameterSeparator();
|
.WriteParameterSeparator();
|
||||||
WriteAttributeValue(codeWriter, value);
|
WriteAttributeValue(codeWriter, value);
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@ namespace Microsoft.Blazor.Components
|
||||||
/// <param name="handler">The handler to be invoked when the event occurs.</param>
|
/// <param name="handler">The handler to be invoked when the event occurs.</param>
|
||||||
/// <returns>A <see cref="RenderTreeNode"/> that represents the event handler.</returns>
|
/// <returns>A <see cref="RenderTreeNode"/> that represents the event handler.</returns>
|
||||||
protected RenderTreeNode onclick(Action handler)
|
protected RenderTreeNode onclick(Action handler)
|
||||||
=> RenderTreeNode.Attribute("onclick", _ => handler());
|
// Note that the 'sequence' value is updated later when inserted into the tree
|
||||||
|
=> RenderTreeNode.Attribute(0, "onclick", _ => handler());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -73,45 +73,49 @@ namespace Microsoft.Blazor.RenderTree
|
||||||
/// Appends a node representing a string-valued attribute.
|
/// Appends a node representing a string-valued attribute.
|
||||||
/// The attribute is associated with the most recently added element.
|
/// The attribute is associated with the most recently added element.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="sequence">An integer that represents the position of the instruction in the source code.</param>
|
||||||
/// <param name="name">The name of the attribute.</param>
|
/// <param name="name">The name of the attribute.</param>
|
||||||
/// <param name="value">The value of the attribute.</param>
|
/// <param name="value">The value of the attribute.</param>
|
||||||
public void AddAttribute(string name, string value)
|
public void AddAttribute(int sequence, string name, string value)
|
||||||
{
|
{
|
||||||
AssertCanAddAttribute();
|
AssertCanAddAttribute();
|
||||||
Append(RenderTreeNode.Attribute(name, value));
|
Append(RenderTreeNode.Attribute(sequence, name, value));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Appends a node representing an <see cref="UIEventArgs"/>-valued attribute.
|
/// Appends a node representing an <see cref="UIEventArgs"/>-valued attribute.
|
||||||
/// The attribute is associated with the most recently added element.
|
/// The attribute is associated with the most recently added element.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="sequence">An integer that represents the position of the instruction in the source code.</param>
|
||||||
/// <param name="name">The name of the attribute.</param>
|
/// <param name="name">The name of the attribute.</param>
|
||||||
/// <param name="value">The value of the attribute.</param>
|
/// <param name="value">The value of the attribute.</param>
|
||||||
public void AddAttribute(string name, UIEventHandler value)
|
public void AddAttribute(int sequence, string name, UIEventHandler value)
|
||||||
{
|
{
|
||||||
AssertCanAddAttribute();
|
AssertCanAddAttribute();
|
||||||
Append(RenderTreeNode.Attribute(name, value));
|
Append(RenderTreeNode.Attribute(sequence, name, value));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Appends a node representing a string-valued attribute.
|
/// Appends a node representing a string-valued attribute.
|
||||||
/// The attribute is associated with the most recently added element.
|
/// The attribute is associated with the most recently added element.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="sequence">An integer that represents the position of the instruction in the source code.</param>
|
||||||
/// <param name="name">The name of the attribute.</param>
|
/// <param name="name">The name of the attribute.</param>
|
||||||
/// <param name="value">The value of the attribute.</param>
|
/// <param name="value">The value of the attribute.</param>
|
||||||
public void AddAttribute(string name, object value)
|
public void AddAttribute(int sequence, string name, object value)
|
||||||
{
|
{
|
||||||
AssertCanAddAttribute();
|
AssertCanAddAttribute();
|
||||||
Append(RenderTreeNode.Attribute(name, value.ToString()));
|
Append(RenderTreeNode.Attribute(sequence, name, value.ToString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Appends a node representing an attribute.
|
/// Appends a node representing an attribute.
|
||||||
/// The attribute is associated with the most recently added element.
|
/// The attribute is associated with the most recently added element.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="sequence">An integer that represents the position of the instruction in the source code.</param>
|
||||||
/// <param name="name">The name of the attribute.</param>
|
/// <param name="name">The name of the attribute.</param>
|
||||||
/// <param name="value">The value of the attribute.</param>
|
/// <param name="value">The value of the attribute.</param>
|
||||||
public void AddAttribute(RenderTreeNode node)
|
public void AddAttribute(int sequence, RenderTreeNode node)
|
||||||
{
|
{
|
||||||
if (node.NodeType != RenderTreeNodeType.Attribute)
|
if (node.NodeType != RenderTreeNodeType.Attribute)
|
||||||
{
|
{
|
||||||
|
|
@ -119,6 +123,7 @@ namespace Microsoft.Blazor.RenderTree
|
||||||
}
|
}
|
||||||
|
|
||||||
AssertCanAddAttribute();
|
AssertCanAddAttribute();
|
||||||
|
node.SetSequence(sequence);
|
||||||
Append(node);
|
Append(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,8 +33,8 @@ namespace Microsoft.Blazor.RenderTree
|
||||||
RenderTreeNode[] oldTree, int oldStartIndex, int oldEndIndexExcl,
|
RenderTreeNode[] oldTree, int oldStartIndex, int oldEndIndexExcl,
|
||||||
RenderTreeNode[] newTree, int newStartIndex, int newEndIndexExcl)
|
RenderTreeNode[] newTree, int newStartIndex, int newEndIndexExcl)
|
||||||
{
|
{
|
||||||
var hasMoreOld = oldEndIndexExcl > 0;
|
var hasMoreOld = oldEndIndexExcl > oldStartIndex;
|
||||||
var hasMoreNew = newEndIndexExcl > 0;
|
var hasMoreNew = newEndIndexExcl > newStartIndex;
|
||||||
var prevOldSeq = -1;
|
var prevOldSeq = -1;
|
||||||
var prevNewSeq = -1;
|
var prevNewSeq = -1;
|
||||||
while (hasMoreOld || hasMoreNew)
|
while (hasMoreOld || hasMoreNew)
|
||||||
|
|
@ -119,15 +119,32 @@ namespace Microsoft.Blazor.RenderTree
|
||||||
|
|
||||||
if (treatAsInsert)
|
if (treatAsInsert)
|
||||||
{
|
{
|
||||||
Append(RenderTreeDiffEntry.PrependNode(newStartIndex));
|
if (newTree[newStartIndex].NodeType == RenderTreeNodeType.Attribute)
|
||||||
newStartIndex = NextSiblingIndex(newTree, newStartIndex);
|
{
|
||||||
|
Append(RenderTreeDiffEntry.SetAttribute(newStartIndex));
|
||||||
|
newStartIndex++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Append(RenderTreeDiffEntry.PrependNode(newStartIndex));
|
||||||
|
newStartIndex = NextSiblingIndex(newTree, newStartIndex);
|
||||||
|
}
|
||||||
|
|
||||||
hasMoreNew = newEndIndexExcl > newStartIndex;
|
hasMoreNew = newEndIndexExcl > newStartIndex;
|
||||||
prevNewSeq = newSeq;
|
prevNewSeq = newSeq;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Append(RenderTreeDiffEntry.RemoveNode());
|
if (oldTree[oldStartIndex].NodeType == RenderTreeNodeType.Attribute)
|
||||||
oldStartIndex = NextSiblingIndex(oldTree, oldStartIndex);
|
{
|
||||||
|
Append(RenderTreeDiffEntry.RemoveAttribute(oldTree[oldStartIndex].AttributeName));
|
||||||
|
oldStartIndex++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Append(RenderTreeDiffEntry.RemoveNode());
|
||||||
|
oldStartIndex = NextSiblingIndex(oldTree, oldStartIndex);
|
||||||
|
}
|
||||||
hasMoreOld = oldEndIndexExcl > oldStartIndex;
|
hasMoreOld = oldEndIndexExcl > oldStartIndex;
|
||||||
prevOldSeq = oldSeq;
|
prevOldSeq = oldSeq;
|
||||||
}
|
}
|
||||||
|
|
@ -170,8 +187,12 @@ namespace Microsoft.Blazor.RenderTree
|
||||||
var newElementName = newTree[newNodeIndex].ElementName;
|
var newElementName = newTree[newNodeIndex].ElementName;
|
||||||
if (string.Equals(oldElementName, newElementName, StringComparison.Ordinal))
|
if (string.Equals(oldElementName, newElementName, StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
// TODO: Compare attributes
|
// Recurse into the element. This covers diffing the attributes as well as
|
||||||
// TODO: Then, recurse into children
|
// the descendants.
|
||||||
|
AppendDiffEntriesForRange(
|
||||||
|
oldTree, oldNodeIndex + 1, oldTree[oldNodeIndex].ElementDescendantsEndIndex + 1,
|
||||||
|
newTree, newNodeIndex + 1, newTree[newNodeIndex].ElementDescendantsEndIndex + 1);
|
||||||
|
|
||||||
Append(RenderTreeDiffEntry.Continue());
|
Append(RenderTreeDiffEntry.Continue());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -205,8 +226,33 @@ namespace Microsoft.Blazor.RenderTree
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case RenderTreeNodeType.Attribute:
|
||||||
|
{
|
||||||
|
var oldName = oldTree[oldNodeIndex].AttributeName;
|
||||||
|
var newName = newTree[newNodeIndex].AttributeName;
|
||||||
|
if (string.Equals(oldName, newName, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
var changed =
|
||||||
|
!string.Equals(oldTree[oldNodeIndex].AttributeValue, newTree[newNodeIndex].AttributeValue, StringComparison.Ordinal)
|
||||||
|
|| oldTree[oldNodeIndex].AttributeEventHandlerValue != newTree[newNodeIndex].AttributeEventHandlerValue;
|
||||||
|
if (changed)
|
||||||
|
{
|
||||||
|
Append(RenderTreeDiffEntry.SetAttribute(newNodeIndex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 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(RenderTreeDiffEntry.SetAttribute(newNodeIndex));
|
||||||
|
Append(RenderTreeDiffEntry.RemoveAttribute(oldName));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new NotImplementedException($"Not yet implemented: diffing for nodes of type {newTree[newNodeIndex].NodeType}");
|
throw new NotImplementedException($"Encountered unsupported node type during diffing: {newTree[newNodeIndex].NodeType}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,16 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Microsoft.Blazor.RenderTree
|
namespace Microsoft.Blazor.RenderTree
|
||||||
{
|
{
|
||||||
internal struct RenderTreeDiffEntry
|
internal struct RenderTreeDiffEntry
|
||||||
{
|
{
|
||||||
public RenderTreeDiffEntryType Type { get; private set; }
|
public RenderTreeDiffEntryType Type { get; private set; }
|
||||||
public int NewTreeIndex { get; private set; }
|
public int NewTreeIndex { get; private set; }
|
||||||
|
public string RemovedAttributeName { get; private set; }
|
||||||
|
|
||||||
public static RenderTreeDiffEntry Continue() => new RenderTreeDiffEntry
|
public static RenderTreeDiffEntry Continue() => new RenderTreeDiffEntry
|
||||||
{
|
{
|
||||||
|
|
@ -29,5 +33,17 @@ namespace Microsoft.Blazor.RenderTree
|
||||||
Type = RenderTreeDiffEntryType.UpdateText,
|
Type = RenderTreeDiffEntryType.UpdateText,
|
||||||
NewTreeIndex = newTreeIndex
|
NewTreeIndex = newTreeIndex
|
||||||
};
|
};
|
||||||
|
|
||||||
|
internal static RenderTreeDiffEntry SetAttribute(int newNodeIndex) => new RenderTreeDiffEntry
|
||||||
|
{
|
||||||
|
Type = RenderTreeDiffEntryType.SetAttribute,
|
||||||
|
NewTreeIndex = newNodeIndex
|
||||||
|
};
|
||||||
|
|
||||||
|
internal static RenderTreeDiffEntry RemoveAttribute(string name) => new RenderTreeDiffEntry
|
||||||
|
{
|
||||||
|
Type = RenderTreeDiffEntryType.RemoveAttribute,
|
||||||
|
RemovedAttributeName = name
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,8 @@ namespace Microsoft.Blazor.RenderTree
|
||||||
Continue = 1,
|
Continue = 1,
|
||||||
PrependNode = 2,
|
PrependNode = 2,
|
||||||
RemoveNode = 3,
|
RemoveNode = 3,
|
||||||
UpdateAttribute = 4,
|
SetAttribute = 4,
|
||||||
UpdateText = 5,
|
RemoveAttribute = 5,
|
||||||
|
UpdateText = 6,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,9 +16,7 @@ namespace Microsoft.Blazor.RenderTree
|
||||||
public struct RenderTreeNode
|
public struct RenderTreeNode
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// If the <see cref="NodeType"/> property equals <see cref="RenderTreeNodeType.Element"/>,
|
/// Gets the sequence number of the node. Sequence numbers indicate the relative source
|
||||||
/// <see cref="RenderTreeNodeType.Text"/>, or <see cref="RenderTreeNodeType.Component"/>,
|
|
||||||
/// gets the sequence number of the node. Sequence numbers indicate the relative source
|
|
||||||
/// positions of the instructions that inserted the nodes. Sequence numbers are only
|
/// positions of the instructions that inserted the nodes. Sequence numbers are only
|
||||||
/// comparable within the same sequence (typically, the same source method).
|
/// comparable within the same sequence (typically, the same source method).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -98,15 +96,17 @@ namespace Microsoft.Blazor.RenderTree
|
||||||
TextContent = textContent ?? string.Empty,
|
TextContent = textContent ?? string.Empty,
|
||||||
};
|
};
|
||||||
|
|
||||||
internal static RenderTreeNode Attribute(string name, string value) => new RenderTreeNode
|
internal static RenderTreeNode Attribute(int sequence, string name, string value) => new RenderTreeNode
|
||||||
{
|
{
|
||||||
|
Sequence = sequence,
|
||||||
NodeType = RenderTreeNodeType.Attribute,
|
NodeType = RenderTreeNodeType.Attribute,
|
||||||
AttributeName = name,
|
AttributeName = name,
|
||||||
AttributeValue = value
|
AttributeValue = value
|
||||||
};
|
};
|
||||||
|
|
||||||
internal static RenderTreeNode Attribute(string name, UIEventHandler value) => new RenderTreeNode
|
internal static RenderTreeNode Attribute(int sequence, string name, UIEventHandler value) => new RenderTreeNode
|
||||||
{
|
{
|
||||||
|
Sequence = sequence,
|
||||||
NodeType = RenderTreeNodeType.Attribute,
|
NodeType = RenderTreeNodeType.Attribute,
|
||||||
AttributeName = name,
|
AttributeName = name,
|
||||||
AttributeEventHandlerValue = value
|
AttributeEventHandlerValue = value
|
||||||
|
|
@ -129,5 +129,13 @@ namespace Microsoft.Blazor.RenderTree
|
||||||
ComponentId = componentId;
|
ComponentId = componentId;
|
||||||
Component = component;
|
Component = component;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal void SetSequence(int sequence)
|
||||||
|
{
|
||||||
|
// This is only used when appending attribute nodes, because helpers such as @onclick
|
||||||
|
// need to construct the attribute node in a context where they don't know the sequence
|
||||||
|
// number, so we assign it later
|
||||||
|
Sequence = sequence;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -174,10 +174,10 @@ namespace Microsoft.Blazor.Test
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
builder.OpenElement(0, "myelement"); // 0: <myelement
|
builder.OpenElement(0, "myelement"); // 0: <myelement
|
||||||
builder.AddAttribute("attribute1", "value 1"); // 1: attribute1="value 1"
|
builder.AddAttribute(0, "attribute1", "value 1"); // 1: attribute1="value 1"
|
||||||
builder.AddAttribute("attribute2", 123); // 2: attribute2=intExpression123>
|
builder.AddAttribute(0, "attribute2", 123); // 2: attribute2=intExpression123>
|
||||||
builder.OpenElement(0, "child"); // 3: <child
|
builder.OpenElement(0, "child"); // 3: <child
|
||||||
builder.AddAttribute("childevent", eventHandler); // 4: childevent=eventHandler>
|
builder.AddAttribute(0, "childevent", eventHandler); // 4: childevent=eventHandler>
|
||||||
builder.AddText(0, "some text"); // 5: some text
|
builder.AddText(0, "some text"); // 5: some text
|
||||||
builder.CloseElement(); // </child>
|
builder.CloseElement(); // </child>
|
||||||
builder.CloseElement(); // </myelement>
|
builder.CloseElement(); // </myelement>
|
||||||
|
|
@ -201,7 +201,7 @@ namespace Microsoft.Blazor.Test
|
||||||
// Act/Assert
|
// Act/Assert
|
||||||
Assert.Throws<InvalidOperationException>(() =>
|
Assert.Throws<InvalidOperationException>(() =>
|
||||||
{
|
{
|
||||||
builder.AddAttribute("name", "value");
|
builder.AddAttribute(0, "name", "value");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -214,7 +214,7 @@ namespace Microsoft.Blazor.Test
|
||||||
// Act/Assert
|
// Act/Assert
|
||||||
Assert.Throws<InvalidOperationException>(() =>
|
Assert.Throws<InvalidOperationException>(() =>
|
||||||
{
|
{
|
||||||
builder.AddAttribute("name", eventInfo => { });
|
builder.AddAttribute(0, "name", eventInfo => { });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -229,7 +229,7 @@ namespace Microsoft.Blazor.Test
|
||||||
{
|
{
|
||||||
builder.OpenElement(0, "some element");
|
builder.OpenElement(0, "some element");
|
||||||
builder.AddText(1, "hello");
|
builder.AddText(1, "hello");
|
||||||
builder.AddAttribute("name", "value");
|
builder.AddAttribute(2, "name", "value");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -244,7 +244,7 @@ namespace Microsoft.Blazor.Test
|
||||||
{
|
{
|
||||||
builder.OpenElement(0, "some element");
|
builder.OpenElement(0, "some element");
|
||||||
builder.AddText(1, "hello");
|
builder.AddText(1, "hello");
|
||||||
builder.AddAttribute("name", eventInfo => { });
|
builder.AddAttribute(2, "name", eventInfo => { });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -255,13 +255,13 @@ namespace Microsoft.Blazor.Test
|
||||||
var builder = new RenderTreeBuilder(new TestRenderer());
|
var builder = new RenderTreeBuilder(new TestRenderer());
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
builder.OpenElement(0, "parent"); // 0: <parent>
|
builder.OpenElement(10, "parent"); // 0: <parent>
|
||||||
builder.AddComponent<TestComponent>(1); // 1: <testcomponent
|
builder.AddComponent<TestComponent>(11); // 1: <testcomponent
|
||||||
builder.AddAttribute("child1attribute1", "A"); // 2: child1attribute1="A"
|
builder.AddAttribute(12, "child1attribute1", "A"); // 2: child1attribute1="A"
|
||||||
builder.AddAttribute("child1attribute2", "B"); // 3: child1attribute2="B" />
|
builder.AddAttribute(13, "child1attribute2", "B"); // 3: child1attribute2="B" />
|
||||||
builder.AddComponent<TestComponent>(2); // 4: <testcomponent
|
builder.AddComponent<TestComponent>(14); // 4: <testcomponent
|
||||||
builder.AddAttribute("child2attribute", "C"); // 5: child2attribute="C" />
|
builder.AddAttribute(15, "child2attribute", "C"); // 5: child2attribute="C" />
|
||||||
builder.CloseElement(); // </parent>
|
builder.CloseElement(); // </parent>
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Collection(builder.GetNodes(),
|
Assert.Collection(builder.GetNodes(),
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ namespace Microsoft.Blazor.Test
|
||||||
builder =>
|
builder =>
|
||||||
{
|
{
|
||||||
builder.OpenElement(0, "Some Element");
|
builder.OpenElement(0, "Some Element");
|
||||||
builder.AddAttribute("My attribute", "My value");
|
builder.AddAttribute(1, "My attribute", "My value");
|
||||||
builder.CloseElement();
|
builder.CloseElement();
|
||||||
},
|
},
|
||||||
builder => builder.AddComponent<FakeComponent>(0)
|
builder => builder.AddComponent<FakeComponent>(0)
|
||||||
|
|
@ -363,6 +363,155 @@ namespace Microsoft.Blazor.Test
|
||||||
entry => Assert.Equal(RenderTreeDiffEntryType.RemoveNode, entry.Type));
|
entry => Assert.Equal(RenderTreeDiffEntryType.RemoveNode, entry.Type));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RecognizesAttributesAdded()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var oldTree = new RenderTreeBuilder(new FakeRenderer());
|
||||||
|
var newTree = new RenderTreeBuilder(new FakeRenderer());
|
||||||
|
var diff = new RenderTreeDiff();
|
||||||
|
oldTree.OpenElement(0, "My element");
|
||||||
|
oldTree.AddAttribute(1, "existing", "existing value");
|
||||||
|
oldTree.CloseElement();
|
||||||
|
newTree.OpenElement(0, "My element");
|
||||||
|
newTree.AddAttribute(1, "existing", "existing value");
|
||||||
|
newTree.AddAttribute(2, "added", "added value");
|
||||||
|
newTree.CloseElement();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = diff.ComputeDifference(oldTree.GetNodes(), newTree.GetNodes());
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Collection(result,
|
||||||
|
entry =>
|
||||||
|
{
|
||||||
|
Assert.Equal(RenderTreeDiffEntryType.SetAttribute, entry.Type);
|
||||||
|
Assert.Equal(2, entry.NewTreeIndex);
|
||||||
|
},
|
||||||
|
entry => Assert.Equal(RenderTreeDiffEntryType.Continue, entry.Type));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RecognizesAttributesRemoved()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var oldTree = new RenderTreeBuilder(new FakeRenderer());
|
||||||
|
var newTree = new RenderTreeBuilder(new FakeRenderer());
|
||||||
|
var diff = new RenderTreeDiff();
|
||||||
|
oldTree.OpenElement(0, "My element");
|
||||||
|
oldTree.AddAttribute(1, "will be removed", "will be removed value");
|
||||||
|
oldTree.AddAttribute(2, "will survive", "surviving value");
|
||||||
|
oldTree.CloseElement();
|
||||||
|
newTree.OpenElement(0, "My element");
|
||||||
|
newTree.AddAttribute(2, "will survive", "surviving value");
|
||||||
|
newTree.CloseElement();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = diff.ComputeDifference(oldTree.GetNodes(), newTree.GetNodes());
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Collection(result,
|
||||||
|
entry =>
|
||||||
|
{
|
||||||
|
Assert.Equal(RenderTreeDiffEntryType.RemoveAttribute, entry.Type);
|
||||||
|
Assert.Equal("will be removed", entry.RemovedAttributeName);
|
||||||
|
},
|
||||||
|
entry => Assert.Equal(RenderTreeDiffEntryType.Continue, entry.Type));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RecognizesAttributeStringValuesChanged()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var oldTree = new RenderTreeBuilder(new FakeRenderer());
|
||||||
|
var newTree = new RenderTreeBuilder(new FakeRenderer());
|
||||||
|
var diff = new RenderTreeDiff();
|
||||||
|
oldTree.OpenElement(0, "My element");
|
||||||
|
oldTree.AddAttribute(1, "will remain", "will remain value");
|
||||||
|
oldTree.AddAttribute(2, "will change", "will change value");
|
||||||
|
oldTree.CloseElement();
|
||||||
|
newTree.OpenElement(0, "My element");
|
||||||
|
newTree.AddAttribute(1, "will remain", "will remain value");
|
||||||
|
newTree.AddAttribute(2, "will change", "did change value");
|
||||||
|
newTree.CloseElement();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = diff.ComputeDifference(oldTree.GetNodes(), newTree.GetNodes());
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Collection(result,
|
||||||
|
entry =>
|
||||||
|
{
|
||||||
|
Assert.Equal(RenderTreeDiffEntryType.SetAttribute, entry.Type);
|
||||||
|
Assert.Equal(2, entry.NewTreeIndex);
|
||||||
|
},
|
||||||
|
entry => Assert.Equal(RenderTreeDiffEntryType.Continue, entry.Type));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RecognizesAttributeEventHandlerValuesChanged()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var oldTree = new RenderTreeBuilder(new FakeRenderer());
|
||||||
|
var newTree = new RenderTreeBuilder(new FakeRenderer());
|
||||||
|
var diff = new RenderTreeDiff();
|
||||||
|
UIEventHandler retainedHandler = _ => { };
|
||||||
|
UIEventHandler removedHandler = _ => { };
|
||||||
|
UIEventHandler addedHandler = _ => { };
|
||||||
|
oldTree.OpenElement(0, "My element");
|
||||||
|
oldTree.AddAttribute(1, "will remain", retainedHandler);
|
||||||
|
oldTree.AddAttribute(2, "will change", removedHandler);
|
||||||
|
oldTree.CloseElement();
|
||||||
|
newTree.OpenElement(0, "My element");
|
||||||
|
newTree.AddAttribute(1, "will remain", retainedHandler);
|
||||||
|
newTree.AddAttribute(2, "will change", addedHandler);
|
||||||
|
newTree.CloseElement();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = diff.ComputeDifference(oldTree.GetNodes(), newTree.GetNodes());
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Collection(result,
|
||||||
|
entry =>
|
||||||
|
{
|
||||||
|
Assert.Equal(RenderTreeDiffEntryType.SetAttribute, entry.Type);
|
||||||
|
Assert.Equal(2, entry.NewTreeIndex);
|
||||||
|
},
|
||||||
|
entry => Assert.Equal(RenderTreeDiffEntryType.Continue, entry.Type));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RecognizesAttributeNamesChangedAtSameSourceSequence()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var oldTree = new RenderTreeBuilder(new FakeRenderer());
|
||||||
|
var newTree = new RenderTreeBuilder(new FakeRenderer());
|
||||||
|
var diff = new RenderTreeDiff();
|
||||||
|
oldTree.OpenElement(0, "My element");
|
||||||
|
oldTree.AddAttribute(1, "oldname", "same value");
|
||||||
|
oldTree.CloseElement();
|
||||||
|
newTree.OpenElement(0, "My element");
|
||||||
|
newTree.AddAttribute(1, "newname", "same value");
|
||||||
|
newTree.CloseElement();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = diff.ComputeDifference(oldTree.GetNodes(), newTree.GetNodes());
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Collection(result,
|
||||||
|
entry =>
|
||||||
|
{
|
||||||
|
Assert.Equal(RenderTreeDiffEntryType.SetAttribute, entry.Type);
|
||||||
|
Assert.Equal(1, entry.NewTreeIndex);
|
||||||
|
},
|
||||||
|
entry =>
|
||||||
|
{
|
||||||
|
Assert.Equal(RenderTreeDiffEntryType.RemoveAttribute, entry.Type);
|
||||||
|
Assert.Equal("oldname", entry.RemovedAttributeName);
|
||||||
|
},
|
||||||
|
entry => Assert.Equal(RenderTreeDiffEntryType.Continue, entry.Type));
|
||||||
|
}
|
||||||
|
|
||||||
private class FakeRenderer : Renderer
|
private class FakeRenderer : Renderer
|
||||||
{
|
{
|
||||||
internal protected override void UpdateDisplay(int componentId, ArraySegment<RenderTreeNode> renderTree)
|
internal protected override void UpdateDisplay(int componentId, ArraySegment<RenderTreeNode> renderTree)
|
||||||
|
|
|
||||||
|
|
@ -361,7 +361,7 @@ namespace Microsoft.Blazor.Test
|
||||||
public void BuildRenderTree(RenderTreeBuilder builder)
|
public void BuildRenderTree(RenderTreeBuilder builder)
|
||||||
{
|
{
|
||||||
builder.OpenElement(0, "some element");
|
builder.OpenElement(0, "some element");
|
||||||
builder.AddAttribute("some event", Handler);
|
builder.AddAttribute(1, "some event", Handler);
|
||||||
builder.CloseElement();
|
builder.CloseElement();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue