Skip to content

Commit 99ef969

Browse files
Add initial touch injection API test
See comments in #124
1 parent ed6bd23 commit 99ef969

File tree

7 files changed

+262
-0
lines changed

7 files changed

+262
-0
lines changed

common/CommunityToolkit.Labs.Tests.Shared/App.xaml.cs

+2
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ public sealed partial class App : Application
4242
private static Windows.UI.Xaml.Window currentWindow = Windows.UI.Xaml.Window.Current;
4343
#endif
4444

45+
public static Rect Bounds => currentWindow.Bounds;
46+
4547
// Holder for test content to abstract Window.Current.Content
4648
public static FrameworkElement? ContentRoot
4749
{

common/CommunityToolkit.Labs.Tests.Shared/CommunityToolkit.Labs.Tests.Shared.projitems

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
<DependentUpon>App.xaml</DependentUpon>
2020
</Compile>
2121
<Compile Include="$(MSBuildThisFileDirectory)Log.cs" />
22+
<Compile Include="$(MSBuildThisFileDirectory)SimulateInput.cs" />
2223
<Compile Include="$(MSBuildThisFileDirectory)VisualUITestBase.cs" />
2324
</ItemGroup>
2425
</Project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
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;
6+
using System.Collections.Generic;
7+
using System.Text;
8+
using Microsoft.VisualStudio.TestTools.UnitTesting;
9+
using Windows.UI.Input.Preview.Injection;
10+
using Windows.Foundation;
11+
12+
#if !WINAPPSDK
13+
using Windows.UI.Input;
14+
using Windows.UI.Xaml;
15+
using Windows.UI.Xaml.Media;
16+
#else
17+
using Microsoft.UI.Input;
18+
using Microsoft.UI.Xaml;
19+
using Microsoft.UI.Xaml.Media;
20+
#endif
21+
22+
namespace CommunityToolkit.Labs.UnitTests;
23+
24+
public static class SimulateInput
25+
{
26+
private static InputInjector _input = InputInjector.TryCreate();
27+
private static uint _currentPointerId = 0;
28+
29+
public static void StartTouch()
30+
{
31+
Assert.IsNotNull(_input);
32+
33+
_input.InitializeTouchInjection(
34+
InjectedInputVisualizationMode.Default);
35+
}
36+
37+
/// <summary>
38+
/// Simulates a touch press on screen at the coordinates provided, in app-local coordinates. For instance use <code>App.ContentRoot.CoordinatesTo(element)</code>.
39+
/// </summary>
40+
/// <param name="point"></param>
41+
/// <returns></returns>
42+
public static uint TouchDown(Point point)
43+
{
44+
// Create a unique pointer ID for the injected touch pointer.
45+
// Multiple input pointers would require more robust handling.
46+
uint pointerId = _currentPointerId++;
47+
48+
var injectionPoint = TranslatePointForWindow(point);
49+
50+
// Create a touch data point for pointer down.
51+
// Each element in the touch data list represents a single touch contact.
52+
// For this example, we're mirroring a single mouse pointer.
53+
List<InjectedInputTouchInfo> touchData = new()
54+
{
55+
new()
56+
{
57+
Contact = new InjectedInputRectangle
58+
{
59+
Left = 30, Top = 30, Bottom = 30, Right = 30
60+
},
61+
PointerInfo = new InjectedInputPointerInfo
62+
{
63+
PointerId = pointerId,
64+
PointerOptions =
65+
InjectedInputPointerOptions.PointerDown |
66+
InjectedInputPointerOptions.InContact |
67+
InjectedInputPointerOptions.New,
68+
TimeOffsetInMilliseconds = 0,
69+
PixelLocation = new InjectedInputPoint
70+
{
71+
PositionX = (int)injectionPoint.X ,
72+
PositionY = (int)injectionPoint.Y
73+
}
74+
},
75+
Pressure = 1.0,
76+
TouchParameters =
77+
InjectedInputTouchParameters.Pressure |
78+
InjectedInputTouchParameters.Contact
79+
}
80+
};
81+
82+
// Inject the touch input.
83+
_input.InjectTouchInput(touchData);
84+
85+
return pointerId;
86+
}
87+
88+
public static void TouchMove(uint pointerId, int cX, int cY)
89+
{
90+
// Create a touch data point for pointer up.
91+
List<InjectedInputTouchInfo> touchData = new()
92+
{
93+
new()
94+
{
95+
Contact = new InjectedInputRectangle
96+
{
97+
Left = 30, Top = 30, Bottom = 30, Right = 30
98+
},
99+
PointerInfo = new InjectedInputPointerInfo
100+
{
101+
PointerId = pointerId,
102+
PointerOptions =
103+
InjectedInputPointerOptions.InRange |
104+
InjectedInputPointerOptions.InContact,
105+
TimeOffsetInMilliseconds = 0,
106+
PixelLocation = new InjectedInputPoint
107+
{
108+
PositionX = (int)cX ,
109+
PositionY = (int)cY
110+
}
111+
},
112+
Pressure = 1.0,
113+
TouchParameters =
114+
InjectedInputTouchParameters.Pressure |
115+
InjectedInputTouchParameters.Contact
116+
}
117+
};
118+
119+
// Inject the touch input.
120+
_input.InjectTouchInput(touchData);
121+
}
122+
123+
public static void TouchUp(uint pointerId)
124+
{
125+
Assert.IsNotNull(_input);
126+
127+
// Create a touch data point for pointer up.
128+
List<InjectedInputTouchInfo> touchData = new()
129+
{
130+
new()
131+
{
132+
PointerInfo = new InjectedInputPointerInfo
133+
{
134+
PointerId = pointerId,
135+
PointerOptions = InjectedInputPointerOptions.PointerUp
136+
}
137+
}
138+
};
139+
140+
// Inject the touch input.
141+
_input.InjectTouchInput(touchData);
142+
}
143+
144+
public static void StopTouch()
145+
{
146+
// Shut down the virtual input device.
147+
_input.UninitializeTouchInjection();
148+
}
149+
150+
private static Point TranslatePointForWindow(Point point)
151+
{
152+
// TODO: Do we want a ToPoint extension in the Toolkit? (is there an existing enum we can use to specify which corner/point of the rect? e.g. topleft, center, middleright, etc...?
153+
154+
// Get the top left screen coordinates of the app window rect.
155+
Point appBoundsTopLeft = new Point(App.Bounds.Left, App.Bounds.Top);
156+
157+
// Create the point for input injection and calculate its screen location.
158+
return new Point(
159+
appBoundsTopLeft.X + point.X,
160+
appBoundsTopLeft.Y + point.Y);
161+
162+
}
163+
}

labs/SizerBase/tests/SizerBase.Tests/ExampleSizerBaseTestClass.cs

+28
Original file line numberDiff line numberDiff line change
@@ -57,4 +57,32 @@ public void PropertySizer_TestChangeBinding(PropertySizerTestInitialBinding test
5757
// Set in XAML Page LINK: PropertySizerTestInitialBinding.xaml#L14
5858
Assert.AreEqual(200, propertySizer.Binding, "Property Sizer not at expected changed value.");
5959
}
60+
61+
[LabsUITestMethod]
62+
public async Task InputInjection_TestClickButton(TouchInjectionTest testControl)
63+
{
64+
var button = testControl.FindDescendant<Button>();
65+
66+
Assert.IsNotNull(button, "Could not find button control.");
67+
Assert.IsFalse(testControl.WasButtonClicked, "Initial state unexpected. Button shouldn't be clicked yet.");
68+
69+
// Get location to button.
70+
var location = App.ContentRoot.CoordinatesTo(button); // TODO: Write a `CoordinatesToCenter` helper?
71+
72+
SimulateInput.StartTouch();
73+
// Offset location slightly to ensure we're inside the button.
74+
var pointerId = SimulateInput.TouchDown(new Point(location.X + 25, location.Y + 25));
75+
await Task.Delay(50);
76+
SimulateInput.TouchUp(pointerId);
77+
78+
// Ensure UI event is processed by our button
79+
await CompositionTargetHelper.ExecuteAfterCompositionRenderingAsync(() => { });
80+
81+
// Optional delay for us to be able to see button content change before test shuts down.
82+
await Task.Delay(250);
83+
84+
Assert.IsTrue(testControl.WasButtonClicked, "Button wasn't clicked.");
85+
86+
SimulateInput.StopTouch();
87+
}
6088
}

labs/SizerBase/tests/SizerBase.Tests/SizerBase.Tests.projitems

+7
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,18 @@
1313
<Compile Include="$(MSBuildThisFileDirectory)PropertySizerTestInitialBinding.xaml.cs">
1414
<DependentUpon>PropertySizerTestInitialBinding.xaml</DependentUpon>
1515
</Compile>
16+
<Compile Include="$(MSBuildThisFileDirectory)TouchInjectionTest.xaml.cs">
17+
<DependentUpon>TouchInjectionTest.xaml</DependentUpon>
18+
</Compile>
1619
</ItemGroup>
1720
<ItemGroup>
1821
<Page Include="$(MSBuildThisFileDirectory)PropertySizerTestInitialBinding.xaml">
1922
<SubType>Designer</SubType>
2023
<Generator>MSBuild:Compile</Generator>
2124
</Page>
25+
<Page Include="$(MSBuildThisFileDirectory)TouchInjectionTest.xaml">
26+
<SubType>Designer</SubType>
27+
<Generator>MSBuild:Compile</Generator>
28+
</Page>
2229
</ItemGroup>
2330
</Project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<Page x:Class="SizerBase.Tests.TouchInjectionTest"
2+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4+
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
5+
xmlns:local="using:CommunityToolkit.Labs.UnitTests.Shared"
6+
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
7+
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
8+
mc:Ignorable="d">
9+
10+
<Grid>
11+
<Button x:Name="TestButton"
12+
Width="100"
13+
Height="50"
14+
Margin="100,100,0,0"
15+
Click="TestButton_Click"
16+
Content="Not Clicked" />
17+
</Grid>
18+
</Page>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
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+
#if !WINAPPSDK
6+
using Windows.UI.Xaml;
7+
using Windows.UI.Xaml.Controls;
8+
using Windows.UI.Xaml.Controls.Primitives;
9+
using Windows.UI.Xaml.Data;
10+
using Windows.UI.Xaml.Input;
11+
using Windows.UI.Xaml.Media;
12+
using Windows.UI.Xaml.Navigation;
13+
#else
14+
using Microsoft.UI.Xaml;
15+
using Microsoft.UI.Xaml.Controls;
16+
using Microsoft.UI.Xaml.Controls.Primitives;
17+
using Microsoft.UI.Xaml.Data;
18+
using Microsoft.UI.Xaml.Input;
19+
using Microsoft.UI.Xaml.Media;
20+
using Microsoft.UI.Xaml.Navigation;
21+
#endif
22+
23+
namespace SizerBase.Tests;
24+
25+
/// <summary>
26+
/// An empty page that can be used on its own or navigated to within a Frame.
27+
/// </summary>
28+
public sealed partial class TouchInjectionTest : Page
29+
{
30+
public bool WasButtonClicked { get; set; }
31+
32+
public TouchInjectionTest()
33+
{
34+
this.InitializeComponent();
35+
}
36+
37+
private void TestButton_Click(object sender, RoutedEventArgs args)
38+
{
39+
WasButtonClicked = true;
40+
41+
TestButton.Content = "Clicked!";
42+
}
43+
}

0 commit comments

Comments
 (0)