diff --git a/src/Microsoft.Blazor/UITree/UITreeBuilder.cs b/src/Microsoft.Blazor/UITree/UITreeBuilder.cs index 641243eea7..7b812009a9 100644 --- a/src/Microsoft.Blazor/UITree/UITreeBuilder.cs +++ b/src/Microsoft.Blazor/UITree/UITreeBuilder.cs @@ -15,6 +15,7 @@ namespace Microsoft.Blazor.UITree private UITreeNode[] _entries = new UITreeNode[100]; private int _entriesInUse = 0; private Stack _openElementIndices = new Stack(); + private UITreeNodeType? _lastNonAttributeNodeType; /// /// Appends a node representing an element, i.e., a container for other nodes. @@ -46,6 +47,24 @@ namespace Microsoft.Blazor.UITree public void AddText(string textContent) => Append(UITreeNode.Text(textContent)); + /// + /// Appends a node representing an attribute. The attribute is associated + /// with the most recently added element. + /// + /// The name of the attribute. + /// The value of the attribute. + public void AddAttribute(string name, string value) + { + if (_lastNonAttributeNodeType == UITreeNodeType.Element) + { + Append(UITreeNode.Attribute(name, value)); + } + else + { + throw new InvalidOperationException($"Attributes may only be added immediately after nodes of type {UITreeNodeType.Element}"); + } + } + /// /// Clears the builder. /// @@ -61,6 +80,7 @@ namespace Microsoft.Blazor.UITree _entriesInUse = 0; _openElementIndices.Clear(); + _lastNonAttributeNodeType = null; } /// @@ -79,6 +99,12 @@ namespace Microsoft.Blazor.UITree } _entries[_entriesInUse++] = node; + + var nodeType = node.NodeType; + if (nodeType != UITreeNodeType.Attribute) + { + _lastNonAttributeNodeType = node.NodeType; + } } } } diff --git a/src/Microsoft.Blazor/UITree/UITreeNode.cs b/src/Microsoft.Blazor/UITree/UITreeNode.cs index a6cc45dfc8..71a917a8a9 100644 --- a/src/Microsoft.Blazor/UITree/UITreeNode.cs +++ b/src/Microsoft.Blazor/UITree/UITreeNode.cs @@ -3,6 +3,10 @@ namespace Microsoft.Blazor.UITree { + // TODO: Consider coalescing properties of compatible types that don't need to be + // used simultaneously. For example, 'ElementName' and 'AttributeName' could be replaced + // by a single 'Name' property. + /// /// Represents an entry in a tree of user interface (UI) items. /// @@ -32,6 +36,18 @@ namespace Microsoft.Blazor.UITree /// public string TextContent { get; private set; } + /// + /// If the property equals , + /// gets the attribute name. Otherwise, the value is . + /// + public string AttributeName { get; private set; } + + /// + /// If the property equals , + /// gets the attribute value. Otherwise, the value is . + /// + public string AttributeValue { get; private set; } + internal static UITreeNode Element(string elementName) => new UITreeNode { NodeType = UITreeNodeType.Element, @@ -44,6 +60,13 @@ namespace Microsoft.Blazor.UITree TextContent = textContent, }; + internal static UITreeNode Attribute(string name, string value) => new UITreeNode + { + NodeType = UITreeNodeType.Attribute, + AttributeName = name, + AttributeValue = value + }; + internal void CloseElement(int descendantsEndIndex) { ElementDescendantsEndIndex = descendantsEndIndex; diff --git a/src/Microsoft.Blazor/UITree/UITreeNodeType.cs b/src/Microsoft.Blazor/UITree/UITreeNodeType.cs index ed83886a51..2ab72502ab 100644 --- a/src/Microsoft.Blazor/UITree/UITreeNodeType.cs +++ b/src/Microsoft.Blazor/UITree/UITreeNodeType.cs @@ -17,5 +17,10 @@ namespace Microsoft.Blazor.UITree /// Represents text content. /// Text = 2, + + /// + /// Represents a key-value pair associated with another . + /// + Attribute = 3, } } diff --git a/test/Microsoft.Blazor.Test/UITreeBuilderTest.cs b/test/Microsoft.Blazor.Test/UITreeBuilderTest.cs index e75d8c43ef..c902eb1b59 100644 --- a/test/Microsoft.Blazor.Test/UITreeBuilderTest.cs +++ b/test/Microsoft.Blazor.Test/UITreeBuilderTest.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.Blazor.UITree; +using System; using System.Linq; using Xunit; @@ -130,6 +131,60 @@ namespace Microsoft.Blazor.Test node => AssertText(node, "standalone text 2")); } + [Fact] + public void CanAddAttributes() + { + // Arrange + var builder = new UITreeBuilder(); + + // Act + builder.OpenElement("myelement"); // 0: + builder.OpenElement("child"); // 3: + builder.AddText("some text"); // 5: some text + builder.CloseElement(); // + builder.CloseElement(); // + + // Assert + Assert.Collection(builder.GetNodes(), + node => AssertElement(node, "myelement", 5), + node => AssertAttribute(node, "attribute1", "value 1"), + node => AssertAttribute(node, "attribute2", "value 2"), + node => AssertElement(node, "child", 5), + node => AssertAttribute(node, "attribute1", "child value"), + node => AssertText(node, "some text")); + } + + [Fact] + public void CannotAddAttributesAtRoot() + { + // Arrange + var builder = new UITreeBuilder(); + + // Act/Assert + Assert.Throws(() => + { + builder.AddAttribute("name", "value"); + }); + } + + [Fact] + public void CannotAddAttributesToText() + { + // Arrange + var builder = new UITreeBuilder(); + + // Act/Assert + Assert.Throws(() => + { + builder.OpenElement("some element"); + builder.AddText("hello"); + builder.AddAttribute("name", "value"); + }); + } + [Fact] public void CanClear() { @@ -160,5 +215,12 @@ namespace Microsoft.Blazor.Test Assert.Equal(elementName, node.ElementName); Assert.Equal(descendantsEndIndex, node.ElementDescendantsEndIndex); } + + void AssertAttribute(UITreeNode node, string attributeName, string attributeValue) + { + Assert.Equal(UITreeNodeType.Attribute, node.NodeType); + Assert.Equal(attributeName, node.AttributeName); + Assert.Equal(attributeValue, node.AttributeValue); + } } }