diff --git a/src/Microsoft.Blazor.Build/Core/RazorCompilation/Engine/BlazorIntermediateNodeWriter.cs b/src/Microsoft.Blazor.Build/Core/RazorCompilation/Engine/BlazorIntermediateNodeWriter.cs index 57768306a7..7d8c09cb92 100644 --- a/src/Microsoft.Blazor.Build/Core/RazorCompilation/Engine/BlazorIntermediateNodeWriter.cs +++ b/src/Microsoft.Blazor.Build/Core/RazorCompilation/Engine/BlazorIntermediateNodeWriter.cs @@ -30,10 +30,22 @@ namespace Microsoft.Blazor.Build.Core.RazorCompilation.Engine private string _unconsumedHtml; private IList _currentAttributeValues; - private IDictionary _currentElementAttributes = new Dictionary(); - private IList _currentElementAttributeTokens = new List(); + private IDictionary _currentElementAttributes = new Dictionary(); + private IList _currentElementAttributeTokens = new List(); 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) { throw new System.NotImplementedException(nameof(BeginWriterScope)); @@ -100,7 +112,11 @@ namespace Microsoft.Blazor.Build.Core.RazorCompilation.Engine if (_unconsumedHtml != null) { var token = (IntermediateToken)node.Children.Single(); - _currentElementAttributeTokens.Add(token); + _currentElementAttributeTokens.Add(new PendingAttributeToken + { + SourceSequence = _sourceSequence++, + AttributeValue = token + }); return; } @@ -149,9 +165,14 @@ namespace Microsoft.Blazor.Build.Core.RazorCompilation.Engine public override void WriteHtmlAttribute(CodeRenderingContext context, HtmlAttributeIntermediateNode node) { + var attributeSourceSequence = _sourceSequence++; _currentAttributeValues = new List(); context.RenderChildren(node); - _currentElementAttributes[node.AttributeName] = _currentAttributeValues; + _currentElementAttributes[node.AttributeName] = new PendingAttribute + { + SourceSequence = attributeSourceSequence, + AttributeValue = _currentAttributeValues + }; _currentAttributeValues = null; } @@ -213,14 +234,14 @@ namespace Microsoft.Blazor.Build.Core.RazorCompilation.Engine foreach (var attribute in nextTag.Attributes) { - WriteAttribute(codeWriter, attribute.Key, attribute.Value); + WriteAttribute(codeWriter, _sourceSequence++, attribute.Key, attribute.Value); } if (_currentElementAttributes.Count > 0) { foreach (var pair in _currentElementAttributes) { - WriteAttribute(codeWriter, pair.Key, pair.Value); + WriteAttribute(codeWriter, pair.Value.SourceSequence, pair.Key, pair.Value.AttributeValue); } _currentElementAttributes.Clear(); } @@ -231,7 +252,9 @@ namespace Microsoft.Blazor.Build.Core.RazorCompilation.Engine { codeWriter .WriteStartMethodInvocation($"{builderVarName}.{nameof(RenderTreeBuilder.AddAttribute)}") - .Write(token.Content) + .Write(token.SourceSequence.ToString()) + .WriteParameterSeparator() + .Write(token.AttributeValue.Content) .WriteEndMethodInvocation(); } _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 .WriteStartMethodInvocation($"{builderVarName}.{nameof(RenderTreeBuilder.AddAttribute)}") + .Write(sourceSequence.ToString()) + .WriteParameterSeparator() .WriteStringLiteral(key) .WriteParameterSeparator(); WriteAttributeValue(codeWriter, value); diff --git a/src/Microsoft.Blazor/Components/BlazorComponent.cs b/src/Microsoft.Blazor/Components/BlazorComponent.cs index 5b9e3cab59..b78d445422 100644 --- a/src/Microsoft.Blazor/Components/BlazorComponent.cs +++ b/src/Microsoft.Blazor/Components/BlazorComponent.cs @@ -41,6 +41,7 @@ namespace Microsoft.Blazor.Components /// The handler to be invoked when the event occurs. /// A that represents the event 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()); } } diff --git a/src/Microsoft.Blazor/RenderTree/RenderTreeBuilder.cs b/src/Microsoft.Blazor/RenderTree/RenderTreeBuilder.cs index adb314d904..83acd927a5 100644 --- a/src/Microsoft.Blazor/RenderTree/RenderTreeBuilder.cs +++ b/src/Microsoft.Blazor/RenderTree/RenderTreeBuilder.cs @@ -73,45 +73,49 @@ namespace Microsoft.Blazor.RenderTree /// Appends a node representing a string-valued attribute. /// The attribute is associated with the most recently added element. /// + /// An integer that represents the position of the instruction in the source code. /// The name of the attribute. /// The value of the attribute. - public void AddAttribute(string name, string value) + public void AddAttribute(int sequence, string name, string value) { AssertCanAddAttribute(); - Append(RenderTreeNode.Attribute(name, value)); + Append(RenderTreeNode.Attribute(sequence, name, value)); } /// /// Appends a node representing an -valued attribute. /// The attribute is associated with the most recently added element. /// + /// An integer that represents the position of the instruction in the source code. /// The name of the attribute. /// The value of the attribute. - public void AddAttribute(string name, UIEventHandler value) + public void AddAttribute(int sequence, string name, UIEventHandler value) { AssertCanAddAttribute(); - Append(RenderTreeNode.Attribute(name, value)); + Append(RenderTreeNode.Attribute(sequence, name, value)); } /// /// Appends a node representing a string-valued attribute. /// The attribute is associated with the most recently added element. /// + /// An integer that represents the position of the instruction in the source code. /// The name of the attribute. /// The value of the attribute. - public void AddAttribute(string name, object value) + public void AddAttribute(int sequence, string name, object value) { AssertCanAddAttribute(); - Append(RenderTreeNode.Attribute(name, value.ToString())); + Append(RenderTreeNode.Attribute(sequence, name, value.ToString())); } /// /// Appends a node representing an attribute. /// The attribute is associated with the most recently added element. /// + /// An integer that represents the position of the instruction in the source code. /// The name of the attribute. /// The value of the attribute. - public void AddAttribute(RenderTreeNode node) + public void AddAttribute(int sequence, RenderTreeNode node) { if (node.NodeType != RenderTreeNodeType.Attribute) { @@ -119,6 +123,7 @@ namespace Microsoft.Blazor.RenderTree } AssertCanAddAttribute(); + node.SetSequence(sequence); Append(node); } diff --git a/src/Microsoft.Blazor/RenderTree/RenderTreeDiff.cs b/src/Microsoft.Blazor/RenderTree/RenderTreeDiff.cs index 899417e627..9605ac4003 100644 --- a/src/Microsoft.Blazor/RenderTree/RenderTreeDiff.cs +++ b/src/Microsoft.Blazor/RenderTree/RenderTreeDiff.cs @@ -33,8 +33,8 @@ namespace Microsoft.Blazor.RenderTree RenderTreeNode[] oldTree, int oldStartIndex, int oldEndIndexExcl, RenderTreeNode[] newTree, int newStartIndex, int newEndIndexExcl) { - var hasMoreOld = oldEndIndexExcl > 0; - var hasMoreNew = newEndIndexExcl > 0; + var hasMoreOld = oldEndIndexExcl > oldStartIndex; + var hasMoreNew = newEndIndexExcl > newStartIndex; var prevOldSeq = -1; var prevNewSeq = -1; while (hasMoreOld || hasMoreNew) @@ -119,15 +119,32 @@ namespace Microsoft.Blazor.RenderTree if (treatAsInsert) { - Append(RenderTreeDiffEntry.PrependNode(newStartIndex)); - newStartIndex = NextSiblingIndex(newTree, newStartIndex); + if (newTree[newStartIndex].NodeType == RenderTreeNodeType.Attribute) + { + Append(RenderTreeDiffEntry.SetAttribute(newStartIndex)); + newStartIndex++; + } + else + { + Append(RenderTreeDiffEntry.PrependNode(newStartIndex)); + newStartIndex = NextSiblingIndex(newTree, newStartIndex); + } + hasMoreNew = newEndIndexExcl > newStartIndex; prevNewSeq = newSeq; } else { - Append(RenderTreeDiffEntry.RemoveNode()); - oldStartIndex = NextSiblingIndex(oldTree, oldStartIndex); + if (oldTree[oldStartIndex].NodeType == RenderTreeNodeType.Attribute) + { + Append(RenderTreeDiffEntry.RemoveAttribute(oldTree[oldStartIndex].AttributeName)); + oldStartIndex++; + } + else + { + Append(RenderTreeDiffEntry.RemoveNode()); + oldStartIndex = NextSiblingIndex(oldTree, oldStartIndex); + } hasMoreOld = oldEndIndexExcl > oldStartIndex; prevOldSeq = oldSeq; } @@ -170,8 +187,12 @@ namespace Microsoft.Blazor.RenderTree var newElementName = newTree[newNodeIndex].ElementName; if (string.Equals(oldElementName, newElementName, StringComparison.Ordinal)) { - // TODO: Compare attributes - // TODO: Then, recurse into children + // 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); + Append(RenderTreeDiffEntry.Continue()); } else @@ -205,8 +226,33 @@ namespace Microsoft.Blazor.RenderTree 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: - 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}"); } } diff --git a/src/Microsoft.Blazor/RenderTree/RenderTreeDiffEntry.cs b/src/Microsoft.Blazor/RenderTree/RenderTreeDiffEntry.cs index 67fb5de7d7..e718e2278f 100644 --- a/src/Microsoft.Blazor/RenderTree/RenderTreeDiffEntry.cs +++ b/src/Microsoft.Blazor/RenderTree/RenderTreeDiffEntry.cs @@ -1,12 +1,16 @@ // 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; +using System.Collections.Generic; + namespace Microsoft.Blazor.RenderTree { internal struct RenderTreeDiffEntry { public RenderTreeDiffEntryType Type { get; private set; } public int NewTreeIndex { get; private set; } + public string RemovedAttributeName { get; private set; } public static RenderTreeDiffEntry Continue() => new RenderTreeDiffEntry { @@ -29,5 +33,17 @@ namespace Microsoft.Blazor.RenderTree Type = RenderTreeDiffEntryType.UpdateText, 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 + }; } } diff --git a/src/Microsoft.Blazor/RenderTree/RenderTreeDiffEntryType.cs b/src/Microsoft.Blazor/RenderTree/RenderTreeDiffEntryType.cs index 20302fa4db..25f95a15b6 100644 --- a/src/Microsoft.Blazor/RenderTree/RenderTreeDiffEntryType.cs +++ b/src/Microsoft.Blazor/RenderTree/RenderTreeDiffEntryType.cs @@ -8,7 +8,8 @@ namespace Microsoft.Blazor.RenderTree Continue = 1, PrependNode = 2, RemoveNode = 3, - UpdateAttribute = 4, - UpdateText = 5, + SetAttribute = 4, + RemoveAttribute = 5, + UpdateText = 6, } } diff --git a/src/Microsoft.Blazor/RenderTree/RenderTreeNode.cs b/src/Microsoft.Blazor/RenderTree/RenderTreeNode.cs index 0d0cf04230..3f46f78dad 100644 --- a/src/Microsoft.Blazor/RenderTree/RenderTreeNode.cs +++ b/src/Microsoft.Blazor/RenderTree/RenderTreeNode.cs @@ -16,9 +16,7 @@ namespace Microsoft.Blazor.RenderTree public struct RenderTreeNode { /// - /// If the property equals , - /// , or , - /// gets the sequence number of the node. Sequence numbers indicate the relative source + /// 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 /// comparable within the same sequence (typically, the same source method). /// @@ -98,15 +96,17 @@ namespace Microsoft.Blazor.RenderTree 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, AttributeName = name, 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, AttributeName = name, AttributeEventHandlerValue = value @@ -129,5 +129,13 @@ namespace Microsoft.Blazor.RenderTree ComponentId = componentId; 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; + } } } diff --git a/test/Microsoft.Blazor.Test/RenderTreeBuilderTest.cs b/test/Microsoft.Blazor.Test/RenderTreeBuilderTest.cs index f11f480ef7..1a7832d5e7 100644 --- a/test/Microsoft.Blazor.Test/RenderTreeBuilderTest.cs +++ b/test/Microsoft.Blazor.Test/RenderTreeBuilderTest.cs @@ -174,10 +174,10 @@ namespace Microsoft.Blazor.Test // Act builder.OpenElement(0, "myelement"); // 0: + builder.AddAttribute(0, "attribute1", "value 1"); // 1: attribute1="value 1" + builder.AddAttribute(0, "attribute2", 123); // 2: attribute2=intExpression123> builder.OpenElement(0, "child"); // 3: + builder.AddAttribute(0, "childevent", eventHandler); // 4: childevent=eventHandler> builder.AddText(0, "some text"); // 5: some text builder.CloseElement(); // builder.CloseElement(); // @@ -201,7 +201,7 @@ namespace Microsoft.Blazor.Test // Act/Assert Assert.Throws(() => { - builder.AddAttribute("name", "value"); + builder.AddAttribute(0, "name", "value"); }); } @@ -214,7 +214,7 @@ namespace Microsoft.Blazor.Test // Act/Assert Assert.Throws(() => { - builder.AddAttribute("name", eventInfo => { }); + builder.AddAttribute(0, "name", eventInfo => { }); }); } @@ -229,7 +229,7 @@ namespace Microsoft.Blazor.Test { builder.OpenElement(0, "some element"); 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.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()); // Act - builder.OpenElement(0, "parent"); // 0: - builder.AddComponent(1); // 1: - builder.AddComponent(2); // 4: - builder.CloseElement(); // + builder.OpenElement(10, "parent"); // 0: + builder.AddComponent(11); // 1: + builder.AddComponent(14); // 4: + builder.CloseElement(); // // Assert Assert.Collection(builder.GetNodes(), diff --git a/test/Microsoft.Blazor.Test/RenderTreeDiffTest.cs b/test/Microsoft.Blazor.Test/RenderTreeDiffTest.cs index 84868258f3..98cd620eba 100644 --- a/test/Microsoft.Blazor.Test/RenderTreeDiffTest.cs +++ b/test/Microsoft.Blazor.Test/RenderTreeDiffTest.cs @@ -40,7 +40,7 @@ namespace Microsoft.Blazor.Test builder => { builder.OpenElement(0, "Some Element"); - builder.AddAttribute("My attribute", "My value"); + builder.AddAttribute(1, "My attribute", "My value"); builder.CloseElement(); }, builder => builder.AddComponent(0) @@ -363,6 +363,155 @@ namespace Microsoft.Blazor.Test 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 { internal protected override void UpdateDisplay(int componentId, ArraySegment renderTree) diff --git a/test/Microsoft.Blazor.Test/RendererTest.cs b/test/Microsoft.Blazor.Test/RendererTest.cs index 0d00d8e60d..8737a539e1 100644 --- a/test/Microsoft.Blazor.Test/RendererTest.cs +++ b/test/Microsoft.Blazor.Test/RendererTest.cs @@ -361,7 +361,7 @@ namespace Microsoft.Blazor.Test public void BuildRenderTree(RenderTreeBuilder builder) { builder.OpenElement(0, "some element"); - builder.AddAttribute("some event", Handler); + builder.AddAttribute(1, "some event", Handler); builder.CloseElement(); } }