Skip to content

Commit f995c16

Browse files
Merge pull request #3891 from CommunityToolkit/jamesmcroft/tokenizingtextbox-automationpeer
Added TokenizingTextBox automation peer
2 parents c5adae9 + 5b4bd23 commit f995c16

File tree

4 files changed

+268
-1
lines changed

4 files changed

+268
-1
lines changed

Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,16 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

5-
using System;
65
using System.Collections.ObjectModel;
76
using System.Linq;
87
using System.Threading.Tasks;
98
using Microsoft.Toolkit.Uwp.Deferred;
9+
using Microsoft.Toolkit.Uwp.UI.Automation.Peers;
1010
using Microsoft.Toolkit.Uwp.UI.Helpers;
1111
using Windows.System;
1212
using Windows.UI.Core;
1313
using Windows.UI.Xaml;
14+
using Windows.UI.Xaml.Automation.Peers;
1415
using Windows.UI.Xaml.Controls;
1516
using Windows.UI.Xaml.Input;
1617

@@ -486,6 +487,15 @@ protected void UpdateCurrentTextEdit(ITokenStringContainer edit)
486487
Text = edit.Text; // Update our text property.
487488
}
488489

490+
/// <summary>
491+
/// Creates AutomationPeer (<see cref="UIElement.OnCreateAutomationPeer"/>)
492+
/// </summary>
493+
/// <returns>An automation peer for this <see cref="TokenizingTextBox"/>.</returns>
494+
protected override AutomationPeer OnCreateAutomationPeer()
495+
{
496+
return new TokenizingTextBoxAutomationPeer(this);
497+
}
498+
489499
/// <summary>
490500
/// Remove the specified token from the list.
491501
/// </summary>
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System.Collections.Generic;
6+
using Microsoft.Toolkit.Uwp.UI.Controls;
7+
using Windows.UI.Xaml.Automation;
8+
using Windows.UI.Xaml.Automation.Peers;
9+
using Windows.UI.Xaml.Automation.Provider;
10+
using Windows.UI.Xaml.Controls;
11+
12+
namespace Microsoft.Toolkit.Uwp.UI.Automation.Peers
13+
{
14+
/// <summary>
15+
/// Defines a framework element automation peer for the <see cref="TokenizingTextBox"/> control.
16+
/// </summary>
17+
public class TokenizingTextBoxAutomationPeer : ListViewBaseAutomationPeer, IValueProvider
18+
{
19+
/// <summary>
20+
/// Initializes a new instance of the <see cref="TokenizingTextBoxAutomationPeer"/> class.
21+
/// </summary>
22+
/// <param name="owner">
23+
/// The <see cref="TokenizingTextBox" /> that is associated with this <see cref="T:Microsoft.Toolkit.Uwp.UI.Automation.Peers.TokenizingTextBoxAutomationPeer" />.
24+
/// </param>
25+
public TokenizingTextBoxAutomationPeer(TokenizingTextBox owner)
26+
: base(owner)
27+
{
28+
}
29+
30+
/// <summary>Gets a value indicating whether the value of a control is read-only.</summary>
31+
/// <returns>**true** if the value is read-only; **false** if it can be modified.</returns>
32+
public bool IsReadOnly => !this.OwningTokenizingTextBox.IsEnabled;
33+
34+
/// <summary>Gets the value of the control.</summary>
35+
/// <returns>The value of the control.</returns>
36+
public string Value => this.OwningTokenizingTextBox.Text;
37+
38+
private TokenizingTextBox OwningTokenizingTextBox
39+
{
40+
get
41+
{
42+
return Owner as TokenizingTextBox;
43+
}
44+
}
45+
46+
/// <summary>Sets the value of a control.</summary>
47+
/// <param name="value">The value to set. The provider is responsible for converting the value to the appropriate data type.</param>
48+
/// <exception cref="T:Windows.UI.Xaml.Automation.ElementNotEnabledException">Thrown if the control is in a read-only state.</exception>
49+
public void SetValue(string value)
50+
{
51+
if (IsReadOnly)
52+
{
53+
throw new ElementNotEnabledException($"Could not set the value of the {nameof(TokenizingTextBox)} ");
54+
}
55+
56+
this.OwningTokenizingTextBox.Text = value;
57+
}
58+
59+
/// <summary>
60+
/// Called by GetClassName that gets a human readable name that, in addition to AutomationControlType,
61+
/// differentiates the control represented by this AutomationPeer.
62+
/// </summary>
63+
/// <returns>The string that contains the name.</returns>
64+
protected override string GetClassNameCore()
65+
{
66+
return Owner.GetType().Name;
67+
}
68+
69+
/// <summary>
70+
/// Called by GetName.
71+
/// </summary>
72+
/// <returns>
73+
/// Returns the first of these that is not null or empty:
74+
/// - Value returned by the base implementation
75+
/// - Name of the owning TokenizingTextBox
76+
/// - TokenizingTextBox class name
77+
/// </returns>
78+
protected override string GetNameCore()
79+
{
80+
string name = this.OwningTokenizingTextBox.Name;
81+
if (!string.IsNullOrWhiteSpace(name))
82+
{
83+
return name;
84+
}
85+
86+
name = AutomationProperties.GetName(this.OwningTokenizingTextBox);
87+
return !string.IsNullOrWhiteSpace(name) ? name : base.GetNameCore();
88+
}
89+
90+
/// <summary>
91+
/// Gets the control pattern that is associated with the specified Windows.UI.Xaml.Automation.Peers.PatternInterface.
92+
/// </summary>
93+
/// <param name="patternInterface">A value from the Windows.UI.Xaml.Automation.Peers.PatternInterface enumeration.</param>
94+
/// <returns>The object that supports the specified pattern, or null if unsupported.</returns>
95+
protected override object GetPatternCore(PatternInterface patternInterface)
96+
{
97+
return patternInterface switch
98+
{
99+
PatternInterface.Value => this,
100+
_ => base.GetPatternCore(patternInterface)
101+
};
102+
}
103+
104+
/// <summary>
105+
/// Gets the collection of elements that are represented in the UI Automation tree as immediate
106+
/// child elements of the automation peer.
107+
/// </summary>
108+
/// <returns>The children elements.</returns>
109+
protected override IList<AutomationPeer> GetChildrenCore()
110+
{
111+
TokenizingTextBox owner = this.OwningTokenizingTextBox;
112+
113+
ItemCollection items = owner.Items;
114+
if (items.Count <= 0)
115+
{
116+
return null;
117+
}
118+
119+
List<AutomationPeer> peers = new List<AutomationPeer>(items.Count);
120+
for (int i = 0; i < items.Count; i++)
121+
{
122+
if (owner.ContainerFromIndex(i) is TokenizingTextBoxItem element)
123+
{
124+
peers.Add(FromElement(element) ?? CreatePeerForElement(element));
125+
}
126+
}
127+
128+
return peers;
129+
}
130+
}
131+
}
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System.Collections.Generic;
6+
using System.Collections.ObjectModel;
7+
using System.Linq;
8+
using System.Threading.Tasks;
9+
using Windows.UI.Xaml.Automation;
10+
using Microsoft.VisualStudio.TestTools.UnitTesting;
11+
using Windows.UI.Xaml.Automation.Peers;
12+
using Microsoft.Toolkit.Uwp;
13+
using Microsoft.Toolkit.Uwp.UI.Automation.Peers;
14+
using Microsoft.Toolkit.Uwp.UI.Controls;
15+
16+
namespace UnitTests.UWP.UI.Controls
17+
{
18+
[TestClass]
19+
[TestCategory("Test_TokenizingTextBox")]
20+
public class Test_TokenizingTextBox_AutomationPeer : VisualUITestBase
21+
{
22+
[TestMethod]
23+
public async Task ShouldConfigureTokenizingTextBoxAutomationPeerAsync()
24+
{
25+
await App.DispatcherQueue.EnqueueAsync(async () =>
26+
{
27+
const string expectedAutomationName = "MyAutomationName";
28+
const string expectedName = "MyName";
29+
const string expectedValue = "Wor";
30+
31+
var items = new ObservableCollection<TokenizingTextBoxTestItem> { new() { Title = "Hello" }, new() { Title = "World" } };
32+
33+
var tokenizingTextBox = new TokenizingTextBox { ItemsSource = items };
34+
35+
await SetTestContentAsync(tokenizingTextBox);
36+
37+
var tokenizingTextBoxAutomationPeer =
38+
FrameworkElementAutomationPeer.CreatePeerForElement(tokenizingTextBox) as TokenizingTextBoxAutomationPeer;
39+
40+
Assert.IsNotNull(tokenizingTextBoxAutomationPeer, "Verify that the AutomationPeer is TokenizingTextBoxAutomationPeer.");
41+
42+
// Asserts the automation peer name based on the Automation Property Name value.
43+
tokenizingTextBox.SetValue(AutomationProperties.NameProperty, expectedAutomationName);
44+
Assert.IsTrue(tokenizingTextBoxAutomationPeer.GetName().Contains(expectedAutomationName), "Verify that the UIA name contains the given AutomationProperties.Name of the TokenizingTextBox.");
45+
46+
// Asserts the automation peer name based on the element Name property.
47+
tokenizingTextBox.Name = expectedName;
48+
Assert.IsTrue(tokenizingTextBoxAutomationPeer.GetName().Contains(expectedName), "Verify that the UIA name contains the given Name of the TokenizingTextBox.");
49+
50+
tokenizingTextBoxAutomationPeer.SetValue(expectedValue);
51+
Assert.IsTrue(tokenizingTextBoxAutomationPeer.Value.Equals(expectedValue), "Verify that the Value contains the given Text of the TokenizingTextBox.");
52+
});
53+
}
54+
55+
[TestMethod]
56+
public async Task ShouldReturnTokensForTokenizingTextBoxAutomationPeerAsync()
57+
{
58+
await App.DispatcherQueue.EnqueueAsync(async () =>
59+
{
60+
var items = new ObservableCollection<TokenizingTextBoxTestItem>
61+
{
62+
new() { Title = "Hello" }, new() { Title = "World" }
63+
};
64+
65+
var tokenizingTextBox = new TokenizingTextBox { ItemsSource = items };
66+
67+
await SetTestContentAsync(tokenizingTextBox);
68+
69+
tokenizingTextBox
70+
.SelectAllTokensAndText(); // Will be 3 items due to the `AndText` that will select an empty text item.
71+
72+
var tokenizingTextBoxAutomationPeer =
73+
FrameworkElementAutomationPeer.CreatePeerForElement(tokenizingTextBox) as
74+
TokenizingTextBoxAutomationPeer;
75+
76+
Assert.IsNotNull(
77+
tokenizingTextBoxAutomationPeer,
78+
"Verify that the AutomationPeer is TokenizingTextBoxAutomationPeer.");
79+
80+
var selectedItems = tokenizingTextBoxAutomationPeer
81+
.GetChildren()
82+
.Cast<ListViewItemAutomationPeer>()
83+
.Select(peer => peer.Owner as TokenizingTextBoxItem)
84+
.Select(item => item?.Content as TokenizingTextBoxTestItem)
85+
.ToList();
86+
87+
Assert.AreEqual(3, selectedItems.Count);
88+
Assert.AreEqual(items[0], selectedItems[0]);
89+
Assert.AreEqual(items[1], selectedItems[1]);
90+
Assert.IsNull(selectedItems[2]); // The 3rd item is the empty text item.
91+
});
92+
}
93+
94+
[TestMethod]
95+
public async Task ShouldThrowElementNotEnabledExceptionIfValueSetWhenDisabled()
96+
{
97+
await App.DispatcherQueue.EnqueueAsync(async () =>
98+
{
99+
const string expectedValue = "Wor";
100+
101+
var tokenizingTextBox = new TokenizingTextBox { IsEnabled = false };
102+
103+
await SetTestContentAsync(tokenizingTextBox);
104+
105+
var tokenizingTextBoxAutomationPeer =
106+
FrameworkElementAutomationPeer.CreatePeerForElement(tokenizingTextBox) as TokenizingTextBoxAutomationPeer;
107+
108+
Assert.ThrowsException<ElementNotEnabledException>(() =>
109+
{
110+
tokenizingTextBoxAutomationPeer.SetValue(expectedValue);
111+
});
112+
});
113+
}
114+
115+
public class TokenizingTextBoxTestItem
116+
{
117+
public string Title { get; set; }
118+
119+
public override string ToString()
120+
{
121+
return Title;
122+
}
123+
}
124+
}
125+
}

UnitTests/UnitTests.UWP/UnitTests.UWP.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,7 @@
236236
<Compile Include="UI\Controls\Test_RadialGauge.cs" />
237237
<Compile Include="UI\Controls\Test_TextToolbar_Localization.cs" />
238238
<Compile Include="UI\Controls\Test_InfiniteCanvas_Regression.cs" />
239+
<Compile Include="UI\Controls\Test_TokenizingTextBox_AutomationPeer.cs" />
239240
<Compile Include="UI\Controls\Test_TokenizingTextBox_General.cs" />
240241
<Compile Include="UI\Controls\Test_TokenizingTextBox_InterspersedCollection.cs" />
241242
<Compile Include="UI\Controls\Test_ListDetailsView.cs" />

0 commit comments

Comments
 (0)