Skip to content

Commit

Permalink
Merge pull request #3746 from michael-hawker/tests-visualtree
Browse files Browse the repository at this point in the history
Setup Unit Test Infrastructure for VisualTree related tests
  • Loading branch information
michael-hawker authored Feb 13, 2021
2 parents 5660681 + 4349660 commit 3209b7a
Show file tree
Hide file tree
Showing 7 changed files with 191 additions and 10 deletions.
6 changes: 4 additions & 2 deletions Microsoft.Toolkit.Uwp.UI/Extensions/Tree/LogicalTree.cs
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,8 @@ public static IEnumerable<T> FindChildren<T>(this FrameworkElement element)
}

/// <summary>
/// Finds the logical parent element with the given name or returns null.
/// 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.
/// <seealso href="https://docs.microsoft.com/uwp/api/windows.ui.xaml.frameworkelement.parent#remarks"/>
/// </summary>
/// <param name="element">Child element.</param>
/// <param name="name">Name of the control to find.</param>
Expand All @@ -226,7 +227,8 @@ public static FrameworkElement FindParentByName(this FrameworkElement element, s
}

/// <summary>
/// Find first logical parent control of a specified type.
/// Find first logical parent control of a specified type. Note: Parent may only be set when the control is added to the VisualTree.
/// <seealso href="https://docs.microsoft.com/uwp/api/windows.ui.xaml.frameworkelement.parent#remarks"/>
/// </summary>
/// <typeparam name="T">Type to search for.</typeparam>
/// <param name="element">Child element.</param>
Expand Down
7 changes: 5 additions & 2 deletions Microsoft.Toolkit.Uwp.UI/Helpers/CompositionTargetHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,20 @@ public static class CompositionTargetHelper
/// <summary>
/// Provides a method to execute code after the rendering pass is completed.
/// <seealso href="https://github.com/microsoft/microsoft-ui-xaml/blob/c045cde57c5c754683d674634a0baccda34d58c4/dev/dll/SharedHelpers.cpp#L399"/>
/// <seealso href="https://devblogs.microsoft.com/premier-developer/the-danger-of-taskcompletionsourcet-class/"/>
/// </summary>
/// <param name="action">Action to be executed after render pass</param>
/// <param name="options"><see cref="TaskCreationOptions"/> for how to handle async calls with <see cref="TaskCompletionSource{TResult}"/>.</param>
/// <returns>Awaitable Task</returns>
public static Task<bool> ExecuteAfterCompositionRenderingAsync(Action action)
public static Task<bool> ExecuteAfterCompositionRenderingAsync(Action action, TaskCreationOptions? options = null)
{
if (action is null)
{
ThrowArgumentNullException();
}

var taskCompletionSource = new TaskCompletionSource<bool>();
var taskCompletionSource = options.HasValue ? new TaskCompletionSource<bool>(options.Value)
: new TaskCompletionSource<bool>();

try
{
Expand Down
66 changes: 66 additions & 0 deletions UnitTests/UnitTests.UWP/Extensions/Test_LogicalTreeExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// 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.Linq;
using System.Threading.Tasks;
using Microsoft.Toolkit.Uwp.Extensions;
using Microsoft.Toolkit.Uwp.UI.Extensions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Markup;

namespace UnitTests.Extensions
{
[TestClass]
public class Test_LogicalTreeExtensions : VisualUITestBase
{
[TestCategory("LogicalTree")]
[TestMethod]
public async Task Test_LogicalTree_FindParent_Exists()
{
await App.DispatcherQueue.EnqueueAsync(async () =>
{
var treeRoot = XamlReader.Load(@"<Page
xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""
xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml"">
<Grid>
<Grid> <!-- Target -->
<Border/>
<Border>
<TextBlock/> <!-- Starting Point -->
</Border>
</Grid>
</Grid>
</Page>") as Page;

// Test Setup
Assert.IsNotNull(treeRoot, "XAML Failed to Load");

// Initialize Visual Tree
await SetTestContentAsync(treeRoot);

var outerGrid = treeRoot.Content as Grid;

Assert.IsNotNull(outerGrid, "Couldn't find Page content.");

var targetGrid = outerGrid.Children.FirstOrDefault() as Grid;
Assert.IsNotNull(targetGrid, "Couldn't find Target Grid");
Assert.AreEqual(2, targetGrid.Children.Count, "Grid doesn't have right number of children.");

var secondBorder = targetGrid.Children[1] as Border;
Assert.IsNotNull(secondBorder, "Border not found.");

var startingPoint = secondBorder.Child as FrameworkElement;
Assert.IsNotNull(startingPoint, "Could not find starting element.");

// Main Test
var grid = startingPoint.FindParent<Grid>();

Assert.IsNotNull(grid, "Expected to find Grid");
Assert.AreEqual(targetGrid, grid, "Grid didn't match expected.");
});
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ public void Test_TextToolbar_Localization_Override()
[TestMethod]
public async Task Test_TextToolbar_Localization_Override_Fr()
{
await CoreApplication.MainView.DispatcherQueue.EnqueueAsync(async () =>
await App.DispatcherQueue.EnqueueAsync(async () =>
{
// Just double-check we've got the right environment setup in our tests.
CollectionAssert.AreEquivalent(new string[] { "en-US", "fr" }, ApplicationLanguages.ManifestLanguages.ToArray(), "Missing locales for test");
Expand Down
35 changes: 33 additions & 2 deletions UnitTests/UnitTests.UWP/UnitTestApp.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@
// See the LICENSE file in the project root for more information.

using System;

using UnitTests.Extensions;
using Windows.ApplicationModel;
using Windows.ApplicationModel.Activation;
using Windows.ApplicationModel.Core;
using Windows.System;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;

namespace UnitTests
Expand All @@ -17,6 +20,31 @@ namespace UnitTests
/// </summary>
public partial class App : Application
{
// Holder for test content to abstract Window.Current.Content
public static FrameworkElement ContentRoot
{
get
{
var rootFrame = Window.Current.Content as Frame;
return rootFrame.Content as FrameworkElement;
}

set
{
var rootFrame = Window.Current.Content as Frame;
rootFrame.Content = value;
}
}

// Abstract CoreApplication.MainView.DispatcherQueue
public static DispatcherQueue DispatcherQueue
{
get
{
return CoreApplication.MainView.DispatcherQueue;
}
}

/// <summary>
/// Initializes a new instance of the <see cref="App"/> class.
/// Initializes the singleton application object. This is the first line of authored code
Expand Down Expand Up @@ -50,7 +78,10 @@ protected override void OnLaunched(LaunchActivatedEventArgs e)
if (rootFrame == null)
{
// Create a Frame to act as the navigation context and navigate to the first page
rootFrame = new Frame();
rootFrame = new Frame()
{
CacheSize = 0 // Prevent any test pages from being cached
};

rootFrame.NavigationFailed += OnNavigationFailed;

Expand Down
8 changes: 5 additions & 3 deletions UnitTests/UnitTests.UWP/UnitTests.UWP.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
<ProjectTypeGuids>{A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<UnitTestPlatformVersion Condition="'$(UnitTestPlatformVersion)' == ''">$(VisualStudioVersion)</UnitTestPlatformVersion>
<AppxPackageSigningEnabled>false</AppxPackageSigningEnabled>
<LangVersion>8.0</LangVersion>
<LangVersion>9.0</LangVersion>
</PropertyGroup>
<Target Name="Pack">
</Target>
Expand Down Expand Up @@ -114,10 +114,10 @@
<Version>6.2.10</Version>
</PackageReference>
<PackageReference Include="MSTest.TestAdapter">
<Version>2.1.0</Version>
<Version>2.1.2</Version>
</PackageReference>
<PackageReference Include="MSTest.TestFramework">
<Version>2.1.0</Version>
<Version>2.1.2</Version>
</PackageReference>
<PackageReference Include="Newtonsoft.Json">
<Version>10.0.3</Version>
Expand Down Expand Up @@ -150,6 +150,7 @@
<Compile Include="Extensions\Test_FontIconExtensionMarkupExtension.cs" />
<Compile Include="Extensions\Test_EnumValuesExtension.cs" />
<Compile Include="Extensions\Test_NullableBoolMarkupExtension.cs" />
<Compile Include="Extensions\Test_LogicalTreeExtensions.cs" />
<Compile Include="Geometry\Test_CanvasPathGeometry.cs" />
<Compile Include="Geometry\Test_RegexFactory.cs" />
<Compile Include="Geometry\Test_Utils.cs" />
Expand Down Expand Up @@ -208,6 +209,7 @@
<Compile Include="UnitTestApp.xaml.cs">
<DependentUpon>UnitTestApp.xaml</DependentUpon>
</Compile>
<Compile Include="VisualUITestBase.cs" />
</ItemGroup>
<ItemGroup>
<ApplicationDefinition Include="UnitTestApp.xaml">
Expand Down
77 changes: 77 additions & 0 deletions UnitTests/UnitTests.UWP/VisualUITestBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// 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 Microsoft.Toolkit.Uwp.Extensions;
using Microsoft.Toolkit.Uwp.UI.Helpers;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Threading.Tasks;
using Windows.UI.Xaml;

namespace UnitTests
{
/// <summary>
/// Base class to be used in API tests which require UI layout or rendering to occur first.
/// For more E2E scenarios or testing components for user interation, see integration test suite instead.
/// 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).
/// </summary>
public class VisualUITestBase
{
/// <summary>
/// Sets the content of the test app to a simple <see cref="FrameworkElement"/> to load into the visual tree.
/// Waits for that element to be loaded and rendered before returning.
/// </summary>
/// <param name="content">Content to set in test app.</param>
/// <returns>When UI is loaded.</returns>
protected Task SetTestContentAsync(FrameworkElement content)
{
return App.DispatcherQueue.EnqueueAsync(() =>
{
var taskCompletionSource = new TaskCompletionSource<bool>();

async void Callback(object sender, RoutedEventArgs args)
{
content.Loaded -= Callback;

// Wait for first Render pass
await CompositionTargetHelper.ExecuteAfterCompositionRenderingAsync(() => { });

taskCompletionSource.SetResult(true);
}

// Going to wait for our original content to unload
content.Loaded += Callback;

// Trigger that now
try
{
App.ContentRoot = content;
}
catch (Exception e)
{
taskCompletionSource.SetException(e);
}

return taskCompletionSource.Task;
});
}

[TestCleanup]
public async Task Cleanup()
{
var taskCompletionSource = new TaskCompletionSource<bool>();

await App.DispatcherQueue.EnqueueAsync(() =>
{
// Going to wait for our original content to unload
App.ContentRoot.Unloaded += (_, _) => taskCompletionSource.SetResult(true);

// Trigger that now
App.ContentRoot = null;
});

await taskCompletionSource.Task;
}
}
}

0 comments on commit 3209b7a

Please sign in to comment.