Skip to content

Commit 3209b7a

Browse files
Merge pull request #3746 from michael-hawker/tests-visualtree
Setup Unit Test Infrastructure for VisualTree related tests
2 parents 5660681 + 4349660 commit 3209b7a

File tree

7 files changed

+191
-10
lines changed

7 files changed

+191
-10
lines changed

Microsoft.Toolkit.Uwp.UI/Extensions/Tree/LogicalTree.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,8 @@ public static IEnumerable<T> FindChildren<T>(this FrameworkElement element)
200200
}
201201

202202
/// <summary>
203-
/// Finds the logical parent element with the given name or returns null.
203+
/// Finds the logical parent element with the given name or returns null. Note: Parent may only be set when the control is added to the VisualTree.
204+
/// <seealso href="https://docs.microsoft.com/uwp/api/windows.ui.xaml.frameworkelement.parent#remarks"/>
204205
/// </summary>
205206
/// <param name="element">Child element.</param>
206207
/// <param name="name">Name of the control to find.</param>
@@ -226,7 +227,8 @@ public static FrameworkElement FindParentByName(this FrameworkElement element, s
226227
}
227228

228229
/// <summary>
229-
/// Find first logical parent control of a specified type.
230+
/// Find first logical parent control of a specified type. Note: Parent may only be set when the control is added to the VisualTree.
231+
/// <seealso href="https://docs.microsoft.com/uwp/api/windows.ui.xaml.frameworkelement.parent#remarks"/>
230232
/// </summary>
231233
/// <typeparam name="T">Type to search for.</typeparam>
232234
/// <param name="element">Child element.</param>

Microsoft.Toolkit.Uwp.UI/Helpers/CompositionTargetHelper.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,20 @@ public static class CompositionTargetHelper
1616
/// <summary>
1717
/// Provides a method to execute code after the rendering pass is completed.
1818
/// <seealso href="https://github.com/microsoft/microsoft-ui-xaml/blob/c045cde57c5c754683d674634a0baccda34d58c4/dev/dll/SharedHelpers.cpp#L399"/>
19+
/// <seealso href="https://devblogs.microsoft.com/premier-developer/the-danger-of-taskcompletionsourcet-class/"/>
1920
/// </summary>
2021
/// <param name="action">Action to be executed after render pass</param>
22+
/// <param name="options"><see cref="TaskCreationOptions"/> for how to handle async calls with <see cref="TaskCompletionSource{TResult}"/>.</param>
2123
/// <returns>Awaitable Task</returns>
22-
public static Task<bool> ExecuteAfterCompositionRenderingAsync(Action action)
24+
public static Task<bool> ExecuteAfterCompositionRenderingAsync(Action action, TaskCreationOptions? options = null)
2325
{
2426
if (action is null)
2527
{
2628
ThrowArgumentNullException();
2729
}
2830

29-
var taskCompletionSource = new TaskCompletionSource<bool>();
31+
var taskCompletionSource = options.HasValue ? new TaskCompletionSource<bool>(options.Value)
32+
: new TaskCompletionSource<bool>();
3033

3134
try
3235
{
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
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.Linq;
6+
using System.Threading.Tasks;
7+
using Microsoft.Toolkit.Uwp.Extensions;
8+
using Microsoft.Toolkit.Uwp.UI.Extensions;
9+
using Microsoft.VisualStudio.TestTools.UnitTesting;
10+
using Windows.UI.Xaml;
11+
using Windows.UI.Xaml.Controls;
12+
using Windows.UI.Xaml.Markup;
13+
14+
namespace UnitTests.Extensions
15+
{
16+
[TestClass]
17+
public class Test_LogicalTreeExtensions : VisualUITestBase
18+
{
19+
[TestCategory("LogicalTree")]
20+
[TestMethod]
21+
public async Task Test_LogicalTree_FindParent_Exists()
22+
{
23+
await App.DispatcherQueue.EnqueueAsync(async () =>
24+
{
25+
var treeRoot = XamlReader.Load(@"<Page
26+
xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""
27+
xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml"">
28+
<Grid>
29+
<Grid> <!-- Target -->
30+
<Border/>
31+
<Border>
32+
<TextBlock/> <!-- Starting Point -->
33+
</Border>
34+
</Grid>
35+
</Grid>
36+
</Page>") as Page;
37+
38+
// Test Setup
39+
Assert.IsNotNull(treeRoot, "XAML Failed to Load");
40+
41+
// Initialize Visual Tree
42+
await SetTestContentAsync(treeRoot);
43+
44+
var outerGrid = treeRoot.Content as Grid;
45+
46+
Assert.IsNotNull(outerGrid, "Couldn't find Page content.");
47+
48+
var targetGrid = outerGrid.Children.FirstOrDefault() as Grid;
49+
Assert.IsNotNull(targetGrid, "Couldn't find Target Grid");
50+
Assert.AreEqual(2, targetGrid.Children.Count, "Grid doesn't have right number of children.");
51+
52+
var secondBorder = targetGrid.Children[1] as Border;
53+
Assert.IsNotNull(secondBorder, "Border not found.");
54+
55+
var startingPoint = secondBorder.Child as FrameworkElement;
56+
Assert.IsNotNull(startingPoint, "Could not find starting element.");
57+
58+
// Main Test
59+
var grid = startingPoint.FindParent<Grid>();
60+
61+
Assert.IsNotNull(grid, "Expected to find Grid");
62+
Assert.AreEqual(targetGrid, grid, "Grid didn't match expected.");
63+
});
64+
}
65+
}
66+
}

UnitTests/UnitTests.UWP/UI/Controls/Test_TextToolbar_Localization.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ public void Test_TextToolbar_Localization_Override()
7979
[TestMethod]
8080
public async Task Test_TextToolbar_Localization_Override_Fr()
8181
{
82-
await CoreApplication.MainView.DispatcherQueue.EnqueueAsync(async () =>
82+
await App.DispatcherQueue.EnqueueAsync(async () =>
8383
{
8484
// Just double-check we've got the right environment setup in our tests.
8585
CollectionAssert.AreEquivalent(new string[] { "en-US", "fr" }, ApplicationLanguages.ManifestLanguages.ToArray(), "Missing locales for test");

UnitTests/UnitTests.UWP/UnitTestApp.xaml.cs

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,14 @@
33
// See the LICENSE file in the project root for more information.
44

55
using System;
6-
6+
using UnitTests.Extensions;
77
using Windows.ApplicationModel;
88
using Windows.ApplicationModel.Activation;
9+
using Windows.ApplicationModel.Core;
10+
using Windows.System;
911
using Windows.UI.Xaml;
1012
using Windows.UI.Xaml.Controls;
13+
using Windows.UI.Xaml.Media;
1114
using Windows.UI.Xaml.Navigation;
1215

1316
namespace UnitTests
@@ -17,6 +20,31 @@ namespace UnitTests
1720
/// </summary>
1821
public partial class App : Application
1922
{
23+
// Holder for test content to abstract Window.Current.Content
24+
public static FrameworkElement ContentRoot
25+
{
26+
get
27+
{
28+
var rootFrame = Window.Current.Content as Frame;
29+
return rootFrame.Content as FrameworkElement;
30+
}
31+
32+
set
33+
{
34+
var rootFrame = Window.Current.Content as Frame;
35+
rootFrame.Content = value;
36+
}
37+
}
38+
39+
// Abstract CoreApplication.MainView.DispatcherQueue
40+
public static DispatcherQueue DispatcherQueue
41+
{
42+
get
43+
{
44+
return CoreApplication.MainView.DispatcherQueue;
45+
}
46+
}
47+
2048
/// <summary>
2149
/// Initializes a new instance of the <see cref="App"/> class.
2250
/// Initializes the singleton application object. This is the first line of authored code
@@ -50,7 +78,10 @@ protected override void OnLaunched(LaunchActivatedEventArgs e)
5078
if (rootFrame == null)
5179
{
5280
// Create a Frame to act as the navigation context and navigate to the first page
53-
rootFrame = new Frame();
81+
rootFrame = new Frame()
82+
{
83+
CacheSize = 0 // Prevent any test pages from being cached
84+
};
5485

5586
rootFrame.NavigationFailed += OnNavigationFailed;
5687

UnitTests/UnitTests.UWP/UnitTests.UWP.csproj

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
<ProjectTypeGuids>{A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
1919
<UnitTestPlatformVersion Condition="'$(UnitTestPlatformVersion)' == ''">$(VisualStudioVersion)</UnitTestPlatformVersion>
2020
<AppxPackageSigningEnabled>false</AppxPackageSigningEnabled>
21-
<LangVersion>8.0</LangVersion>
21+
<LangVersion>9.0</LangVersion>
2222
</PropertyGroup>
2323
<Target Name="Pack">
2424
</Target>
@@ -114,10 +114,10 @@
114114
<Version>6.2.10</Version>
115115
</PackageReference>
116116
<PackageReference Include="MSTest.TestAdapter">
117-
<Version>2.1.0</Version>
117+
<Version>2.1.2</Version>
118118
</PackageReference>
119119
<PackageReference Include="MSTest.TestFramework">
120-
<Version>2.1.0</Version>
120+
<Version>2.1.2</Version>
121121
</PackageReference>
122122
<PackageReference Include="Newtonsoft.Json">
123123
<Version>10.0.3</Version>
@@ -150,6 +150,7 @@
150150
<Compile Include="Extensions\Test_FontIconExtensionMarkupExtension.cs" />
151151
<Compile Include="Extensions\Test_EnumValuesExtension.cs" />
152152
<Compile Include="Extensions\Test_NullableBoolMarkupExtension.cs" />
153+
<Compile Include="Extensions\Test_LogicalTreeExtensions.cs" />
153154
<Compile Include="Geometry\Test_CanvasPathGeometry.cs" />
154155
<Compile Include="Geometry\Test_RegexFactory.cs" />
155156
<Compile Include="Geometry\Test_Utils.cs" />
@@ -208,6 +209,7 @@
208209
<Compile Include="UnitTestApp.xaml.cs">
209210
<DependentUpon>UnitTestApp.xaml</DependentUpon>
210211
</Compile>
212+
<Compile Include="VisualUITestBase.cs" />
211213
</ItemGroup>
212214
<ItemGroup>
213215
<ApplicationDefinition Include="UnitTestApp.xaml">
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
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 Microsoft.Toolkit.Uwp.Extensions;
6+
using Microsoft.Toolkit.Uwp.UI.Helpers;
7+
using Microsoft.VisualStudio.TestTools.UnitTesting;
8+
using System;
9+
using System.Threading.Tasks;
10+
using Windows.UI.Xaml;
11+
12+
namespace UnitTests
13+
{
14+
/// <summary>
15+
/// Base class to be used in API tests which require UI layout or rendering to occur first.
16+
/// For more E2E scenarios or testing components for user interation, see integration test suite instead.
17+
/// Use this class when an API needs direct access to test functions of the UI itself in more simplistic scenarios (i.e. visual tree helpers).
18+
/// </summary>
19+
public class VisualUITestBase
20+
{
21+
/// <summary>
22+
/// Sets the content of the test app to a simple <see cref="FrameworkElement"/> to load into the visual tree.
23+
/// Waits for that element to be loaded and rendered before returning.
24+
/// </summary>
25+
/// <param name="content">Content to set in test app.</param>
26+
/// <returns>When UI is loaded.</returns>
27+
protected Task SetTestContentAsync(FrameworkElement content)
28+
{
29+
return App.DispatcherQueue.EnqueueAsync(() =>
30+
{
31+
var taskCompletionSource = new TaskCompletionSource<bool>();
32+
33+
async void Callback(object sender, RoutedEventArgs args)
34+
{
35+
content.Loaded -= Callback;
36+
37+
// Wait for first Render pass
38+
await CompositionTargetHelper.ExecuteAfterCompositionRenderingAsync(() => { });
39+
40+
taskCompletionSource.SetResult(true);
41+
}
42+
43+
// Going to wait for our original content to unload
44+
content.Loaded += Callback;
45+
46+
// Trigger that now
47+
try
48+
{
49+
App.ContentRoot = content;
50+
}
51+
catch (Exception e)
52+
{
53+
taskCompletionSource.SetException(e);
54+
}
55+
56+
return taskCompletionSource.Task;
57+
});
58+
}
59+
60+
[TestCleanup]
61+
public async Task Cleanup()
62+
{
63+
var taskCompletionSource = new TaskCompletionSource<bool>();
64+
65+
await App.DispatcherQueue.EnqueueAsync(() =>
66+
{
67+
// Going to wait for our original content to unload
68+
App.ContentRoot.Unloaded += (_, _) => taskCompletionSource.SetResult(true);
69+
70+
// Trigger that now
71+
App.ContentRoot = null;
72+
});
73+
74+
await taskCompletionSource.Task;
75+
}
76+
}
77+
}

0 commit comments

Comments
 (0)