diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.cs index 89474a89fc6..0c22e8336fa 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.cs @@ -2,15 +2,16 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; using System.Collections.ObjectModel; using System.Linq; using System.Threading.Tasks; using Microsoft.Toolkit.Uwp.Deferred; +using Microsoft.Toolkit.Uwp.UI.Automation.Peers; using Microsoft.Toolkit.Uwp.UI.Helpers; using Windows.System; using Windows.UI.Core; using Windows.UI.Xaml; +using Windows.UI.Xaml.Automation.Peers; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Input; @@ -486,6 +487,15 @@ protected void UpdateCurrentTextEdit(ITokenStringContainer edit) Text = edit.Text; // Update our text property. } + /// + /// Creates AutomationPeer () + /// + /// An automation peer for this . + protected override AutomationPeer OnCreateAutomationPeer() + { + return new TokenizingTextBoxAutomationPeer(this); + } + /// /// Remove the specified token from the list. /// diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBoxAutomationPeer.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBoxAutomationPeer.cs new file mode 100644 index 00000000000..5f049a80149 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBoxAutomationPeer.cs @@ -0,0 +1,131 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using Microsoft.Toolkit.Uwp.UI.Controls; +using Windows.UI.Xaml.Automation; +using Windows.UI.Xaml.Automation.Peers; +using Windows.UI.Xaml.Automation.Provider; +using Windows.UI.Xaml.Controls; + +namespace Microsoft.Toolkit.Uwp.UI.Automation.Peers +{ + /// + /// Defines a framework element automation peer for the control. + /// + public class TokenizingTextBoxAutomationPeer : ListViewBaseAutomationPeer, IValueProvider + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The that is associated with this . + /// + public TokenizingTextBoxAutomationPeer(TokenizingTextBox owner) + : base(owner) + { + } + + /// Gets a value indicating whether the value of a control is read-only. + /// **true** if the value is read-only; **false** if it can be modified. + public bool IsReadOnly => !this.OwningTokenizingTextBox.IsEnabled; + + /// Gets the value of the control. + /// The value of the control. + public string Value => this.OwningTokenizingTextBox.Text; + + private TokenizingTextBox OwningTokenizingTextBox + { + get + { + return Owner as TokenizingTextBox; + } + } + + /// Sets the value of a control. + /// The value to set. The provider is responsible for converting the value to the appropriate data type. + /// Thrown if the control is in a read-only state. + public void SetValue(string value) + { + if (IsReadOnly) + { + throw new ElementNotEnabledException($"Could not set the value of the {nameof(TokenizingTextBox)} "); + } + + this.OwningTokenizingTextBox.Text = value; + } + + /// + /// Called by GetClassName that gets a human readable name that, in addition to AutomationControlType, + /// differentiates the control represented by this AutomationPeer. + /// + /// The string that contains the name. + protected override string GetClassNameCore() + { + return Owner.GetType().Name; + } + + /// + /// Called by GetName. + /// + /// + /// Returns the first of these that is not null or empty: + /// - Value returned by the base implementation + /// - Name of the owning TokenizingTextBox + /// - TokenizingTextBox class name + /// + protected override string GetNameCore() + { + string name = this.OwningTokenizingTextBox.Name; + if (!string.IsNullOrWhiteSpace(name)) + { + return name; + } + + name = AutomationProperties.GetName(this.OwningTokenizingTextBox); + return !string.IsNullOrWhiteSpace(name) ? name : base.GetNameCore(); + } + + /// + /// Gets the control pattern that is associated with the specified Windows.UI.Xaml.Automation.Peers.PatternInterface. + /// + /// A value from the Windows.UI.Xaml.Automation.Peers.PatternInterface enumeration. + /// The object that supports the specified pattern, or null if unsupported. + protected override object GetPatternCore(PatternInterface patternInterface) + { + return patternInterface switch + { + PatternInterface.Value => this, + _ => base.GetPatternCore(patternInterface) + }; + } + + /// + /// Gets the collection of elements that are represented in the UI Automation tree as immediate + /// child elements of the automation peer. + /// + /// The children elements. + protected override IList GetChildrenCore() + { + TokenizingTextBox owner = this.OwningTokenizingTextBox; + + ItemCollection items = owner.Items; + if (items.Count <= 0) + { + return null; + } + + List peers = new List(items.Count); + for (int i = 0; i < items.Count; i++) + { + if (owner.ContainerFromIndex(i) is TokenizingTextBoxItem element) + { + peers.Add(FromElement(element) ?? CreatePeerForElement(element)); + } + } + + return peers; + } + } +} diff --git a/UnitTests/UnitTests.UWP/UI/Controls/Test_TokenizingTextBox_AutomationPeer.cs b/UnitTests/UnitTests.UWP/UI/Controls/Test_TokenizingTextBox_AutomationPeer.cs new file mode 100644 index 00000000000..bf6e2ed358c --- /dev/null +++ b/UnitTests/UnitTests.UWP/UI/Controls/Test_TokenizingTextBox_AutomationPeer.cs @@ -0,0 +1,125 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Threading.Tasks; +using Windows.UI.Xaml.Automation; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Windows.UI.Xaml.Automation.Peers; +using Microsoft.Toolkit.Uwp; +using Microsoft.Toolkit.Uwp.UI.Automation.Peers; +using Microsoft.Toolkit.Uwp.UI.Controls; + +namespace UnitTests.UWP.UI.Controls +{ + [TestClass] + [TestCategory("Test_TokenizingTextBox")] + public class Test_TokenizingTextBox_AutomationPeer : VisualUITestBase + { + [TestMethod] + public async Task ShouldConfigureTokenizingTextBoxAutomationPeerAsync() + { + await App.DispatcherQueue.EnqueueAsync(async () => + { + const string expectedAutomationName = "MyAutomationName"; + const string expectedName = "MyName"; + const string expectedValue = "Wor"; + + var items = new ObservableCollection { new() { Title = "Hello" }, new() { Title = "World" } }; + + var tokenizingTextBox = new TokenizingTextBox { ItemsSource = items }; + + await SetTestContentAsync(tokenizingTextBox); + + var tokenizingTextBoxAutomationPeer = + FrameworkElementAutomationPeer.CreatePeerForElement(tokenizingTextBox) as TokenizingTextBoxAutomationPeer; + + Assert.IsNotNull(tokenizingTextBoxAutomationPeer, "Verify that the AutomationPeer is TokenizingTextBoxAutomationPeer."); + + // Asserts the automation peer name based on the Automation Property Name value. + tokenizingTextBox.SetValue(AutomationProperties.NameProperty, expectedAutomationName); + Assert.IsTrue(tokenizingTextBoxAutomationPeer.GetName().Contains(expectedAutomationName), "Verify that the UIA name contains the given AutomationProperties.Name of the TokenizingTextBox."); + + // Asserts the automation peer name based on the element Name property. + tokenizingTextBox.Name = expectedName; + Assert.IsTrue(tokenizingTextBoxAutomationPeer.GetName().Contains(expectedName), "Verify that the UIA name contains the given Name of the TokenizingTextBox."); + + tokenizingTextBoxAutomationPeer.SetValue(expectedValue); + Assert.IsTrue(tokenizingTextBoxAutomationPeer.Value.Equals(expectedValue), "Verify that the Value contains the given Text of the TokenizingTextBox."); + }); + } + + [TestMethod] + public async Task ShouldReturnTokensForTokenizingTextBoxAutomationPeerAsync() + { + await App.DispatcherQueue.EnqueueAsync(async () => + { + var items = new ObservableCollection + { + new() { Title = "Hello" }, new() { Title = "World" } + }; + + var tokenizingTextBox = new TokenizingTextBox { ItemsSource = items }; + + await SetTestContentAsync(tokenizingTextBox); + + tokenizingTextBox + .SelectAllTokensAndText(); // Will be 3 items due to the `AndText` that will select an empty text item. + + var tokenizingTextBoxAutomationPeer = + FrameworkElementAutomationPeer.CreatePeerForElement(tokenizingTextBox) as + TokenizingTextBoxAutomationPeer; + + Assert.IsNotNull( + tokenizingTextBoxAutomationPeer, + "Verify that the AutomationPeer is TokenizingTextBoxAutomationPeer."); + + var selectedItems = tokenizingTextBoxAutomationPeer + .GetChildren() + .Cast() + .Select(peer => peer.Owner as TokenizingTextBoxItem) + .Select(item => item?.Content as TokenizingTextBoxTestItem) + .ToList(); + + Assert.AreEqual(3, selectedItems.Count); + Assert.AreEqual(items[0], selectedItems[0]); + Assert.AreEqual(items[1], selectedItems[1]); + Assert.IsNull(selectedItems[2]); // The 3rd item is the empty text item. + }); + } + + [TestMethod] + public async Task ShouldThrowElementNotEnabledExceptionIfValueSetWhenDisabled() + { + await App.DispatcherQueue.EnqueueAsync(async () => + { + const string expectedValue = "Wor"; + + var tokenizingTextBox = new TokenizingTextBox { IsEnabled = false }; + + await SetTestContentAsync(tokenizingTextBox); + + var tokenizingTextBoxAutomationPeer = + FrameworkElementAutomationPeer.CreatePeerForElement(tokenizingTextBox) as TokenizingTextBoxAutomationPeer; + + Assert.ThrowsException(() => + { + tokenizingTextBoxAutomationPeer.SetValue(expectedValue); + }); + }); + } + + public class TokenizingTextBoxTestItem + { + public string Title { get; set; } + + public override string ToString() + { + return Title; + } + } + } +} \ No newline at end of file diff --git a/UnitTests/UnitTests.UWP/UnitTests.UWP.csproj b/UnitTests/UnitTests.UWP/UnitTests.UWP.csproj index 0323d553a9a..ae9050b2bc8 100644 --- a/UnitTests/UnitTests.UWP/UnitTests.UWP.csproj +++ b/UnitTests/UnitTests.UWP/UnitTests.UWP.csproj @@ -223,6 +223,7 @@ +