|
// 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;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.VisualStudio.Extensibility.Testing;
using Microsoft.VisualStudio.IntegrationTest.Utilities.Interop;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Threading;
using WindowsInput;
using WindowsInput.Native;
using Xunit;
namespace Roslyn.VisualStudio.IntegrationTests.InProcess
{
[TestService]
internal partial class InputInProcess
{
internal Task SendAsync(InputKey key, CancellationToken cancellationToken)
=> SendAsync(new InputKey[] { key }, cancellationToken);
internal Task SendAsync(InputKey[] keys, CancellationToken cancellationToken)
{
return SendAsync(
simulator =>
{
foreach (var key in keys)
{
key.Apply(simulator);
}
}, cancellationToken);
}
internal async Task SendAsync(Action<IInputSimulator> callback, CancellationToken cancellationToken)
{
// AbstractSendKeys runs synchronously, so switch to a background thread before the call
await TaskScheduler.Default;
TestServices.JoinableTaskFactory.Run(async () =>
{
await TestServices.Editor.ActivateAsync(cancellationToken);
});
callback(new InputSimulator());
TestServices.JoinableTaskFactory.Run(async () =>
{
await WaitForApplicationIdleAsync(cancellationToken);
});
}
internal Task SendWithoutActivateAsync(InputKey key, CancellationToken cancellationToken)
=> SendWithoutActivateAsync(new[] { key }, cancellationToken);
internal Task SendWithoutActivateAsync(InputKey[] keys, CancellationToken cancellationToken)
{
return SendWithoutActivateAsync(
simulator =>
{
foreach (var key in keys)
{
key.Apply(simulator);
}
}, cancellationToken);
}
internal async Task SendWithoutActivateAsync(Action<IInputSimulator> callback, CancellationToken cancellationToken)
{
// AbstractSendKeys runs synchronously, so switch to a background thread before the call
await TaskScheduler.Default;
callback(new InputSimulator());
TestServices.JoinableTaskFactory.Run(async () =>
{
await WaitForApplicationIdleAsync(cancellationToken);
});
}
internal async Task SendToNavigateToAsync(InputKey[] keys, CancellationToken cancellationToken)
{
// AbstractSendKeys runs synchronously, so switch to a background thread before the call
await TaskScheduler.Default;
// Take no direct action regarding activation, but assert the correct item already has focus
TestServices.JoinableTaskFactory.Run(async () =>
{
await TestServices.JoinableTaskFactory.SwitchToMainThreadAsync();
var searchBox = Assert.IsAssignableFrom<Control>(Keyboard.FocusedElement);
// Validate the focused control against the "old" search experience as well as the
// all-in-one search experience.
Assert.Contains(searchBox.Name, new[] { "PART_SearchBox", "SearchBoxControl" });
});
var inputSimulator = new InputSimulator();
foreach (var key in keys)
{
// If it is enter key, we need to wait for search item shows up in the search dialog.
if (key.VirtualKeyCode == VirtualKeyCode.RETURN)
{
await WaitNavigationItemShowsUpAsync(cancellationToken);
}
key.Apply(inputSimulator);
}
TestServices.JoinableTaskFactory.Run(async () =>
{
await WaitForApplicationIdleAsync(cancellationToken);
});
}
private async Task WaitNavigationItemShowsUpAsync(CancellationToken cancellationToken)
{
// Wait for the NavigateTo Features completes on Roslyn side.
await TestServices.Workspace.WaitForAllAsyncOperationsAsync(new[] { FeatureAttribute.NavigateTo }, cancellationToken);
// Since the all-in-one search experience populates its results asychronously we need
// to give it time to update the UI. Note: This is not a perfect solution.
await Task.Delay(1000);
await WaitForApplicationIdleAsync(cancellationToken);
}
internal async Task MoveMouseAsync(Point point, CancellationToken cancellationToken)
{
var horizontalResolution = NativeMethods.GetSystemMetrics(NativeMethods.SM_CXSCREEN);
var verticalResolution = NativeMethods.GetSystemMetrics(NativeMethods.SM_CYSCREEN);
var virtualPoint = new ScaleTransform(65535.0 / horizontalResolution, 65535.0 / verticalResolution).Transform(point);
await SendAsync(simulator => simulator.Mouse.MoveMouseTo(virtualPoint.X, virtualPoint.Y), cancellationToken);
// ⚠ The call to GetCursorPos is required for correct behavior.
var actualPoint = NativeMethods.GetCursorPos();
Assert.True(Math.Abs(actualPoint.X - point.X) <= 1, $"Expected '{Math.Abs(actualPoint.X - point.X)}' <= '1'. Move to '({point.X}, {point.Y})' produced '({actualPoint.X}, {actualPoint.Y})'.");
Assert.True(Math.Abs(actualPoint.Y - point.Y) <= 1, $"Expected '{Math.Abs(actualPoint.Y - point.Y)}' <= '1'. Move to '({point.X}, {point.Y})' produced '({actualPoint.X}, {actualPoint.Y})'.");
}
}
}
|