diff --git a/Tvl.VisualStudio.MouseFastScroll.IntegrationTests/AbstractIdeIntegrationTest.cs b/Tvl.VisualStudio.MouseFastScroll.IntegrationTests/AbstractIdeIntegrationTest.cs deleted file mode 100644 index 86900ce..0000000 --- a/Tvl.VisualStudio.MouseFastScroll.IntegrationTests/AbstractIdeIntegrationTest.cs +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. -// Licensed under the Apache License, Version 2.0. See LICENSE.txt in the project root for license information. - -namespace Tvl.VisualStudio.MouseFastScroll.IntegrationTests -{ - using System; - using System.Threading.Tasks; - using System.Windows; - using System.Windows.Threading; - using Microsoft.VisualStudio.Shell.Interop; - using Microsoft.VisualStudio.Threading; - using Tvl.VisualStudio.MouseFastScroll.IntegrationTests.Threading; - using Xunit; - - public abstract class AbstractIdeIntegrationTest : IAsyncLifetime, IDisposable - { - private JoinableTaskContext _joinableTaskContext; - private JoinableTaskCollection _joinableTaskCollection; - private JoinableTaskFactory _joinableTaskFactory; - - protected AbstractIdeIntegrationTest() - { - Assert.True(Application.Current.Dispatcher.CheckAccess()); - - if (ServiceProvider.GetService(typeof(SVsTaskSchedulerService)) is IVsTaskSchedulerService2 taskSchedulerService) - { - JoinableTaskContext = (JoinableTaskContext)taskSchedulerService.GetAsyncTaskContext(); - } - else - { - JoinableTaskContext = new JoinableTaskContext(); - } - } - - protected static IServiceProvider ServiceProvider => Microsoft.VisualStudio.Shell.ServiceProvider.GlobalProvider; - - protected JoinableTaskContext JoinableTaskContext - { - get - { - return _joinableTaskContext ?? throw new InvalidOperationException(); - } - - private set - { - if (value == _joinableTaskContext) - { - return; - } - - if (value is null) - { - _joinableTaskContext = null; - _joinableTaskCollection = null; - _joinableTaskFactory = null; - } - else - { - _joinableTaskContext = value; - _joinableTaskCollection = value.CreateCollection(); - _joinableTaskFactory = value.CreateFactory(_joinableTaskCollection).WithPriority(Application.Current.Dispatcher, DispatcherPriority.Background); - } - } - } - - protected JoinableTaskFactory JoinableTaskFactory => _joinableTaskFactory ?? throw new InvalidOperationException(); - - public virtual Task InitializeAsync() - { - return Task.CompletedTask; - } - - public virtual async Task DisposeAsync() - { - await _joinableTaskCollection.JoinTillEmptyAsync(); - JoinableTaskContext = null; - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - protected virtual void Dispose(bool disposing) - { - } - } -} diff --git a/Tvl.VisualStudio.MouseFastScroll.IntegrationTests/Harness/IntegrationHelper.cs b/Tvl.VisualStudio.MouseFastScroll.IntegrationTests/Harness/IntegrationHelper.cs deleted file mode 100644 index 063e7df..0000000 --- a/Tvl.VisualStudio.MouseFastScroll.IntegrationTests/Harness/IntegrationHelper.cs +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. -// Licensed under the Apache License, Version 2.0. See LICENSE.txt in the project root for license information. - -namespace Tvl.VisualStudio.MouseFastScroll.IntegrationTests.Harness -{ - using System; - using System.Runtime.InteropServices; - - /// - /// Provides some helper functions used by the other classes in the project. - /// - internal static class IntegrationHelper - { - public static bool AttachThreadInput(uint idAttach, uint idAttachTo) - { - var success = NativeMethods.AttachThreadInput(idAttach, idAttachTo, true); - - if (!success) - { - var hresult = Marshal.GetHRForLastWin32Error(); - Marshal.ThrowExceptionForHR(hresult); - } - - return success; - } - - public static bool DetachThreadInput(uint idAttach, uint idAttachTo) - { - var success = NativeMethods.AttachThreadInput(idAttach, idAttachTo, false); - - if (!success) - { - var hresult = Marshal.GetHRForLastWin32Error(); - Marshal.ThrowExceptionForHR(hresult); - } - - return success; - } - - public static IntPtr GetForegroundWindow() - { - // Attempt to get the foreground window in a loop, as the NativeMethods function can return IntPtr.Zero - // in certain circumstances, such as when a window is losing activation. - var foregroundWindow = IntPtr.Zero; - - do - { - foregroundWindow = NativeMethods.GetForegroundWindow(); - } - while (foregroundWindow == IntPtr.Zero); - - return foregroundWindow; - } - - public static void SetForegroundWindow(IntPtr window) - { - var foregroundWindow = GetForegroundWindow(); - - if (window == foregroundWindow) - { - return; - } - - var activeThreadId = NativeMethods.GetWindowThreadProcessId(foregroundWindow, IntPtr.Zero); - var currentThreadId = NativeMethods.GetCurrentThreadId(); - - var threadInputsAttached = false; - - try - { - // No need to re-attach threads in case when VS initializaed an UI thread for a debugged application. - if (activeThreadId != currentThreadId) - { - // Attach the thread inputs so that 'SetActiveWindow' and 'SetFocus' work - threadInputsAttached = AttachThreadInput(currentThreadId, activeThreadId); - } - - // Make the window a top-most window so it will appear above any existing top-most windows - NativeMethods.SetWindowPos(window, (IntPtr)NativeMethods.HWND_TOPMOST, 0, 0, 0, 0, NativeMethods.SWP_NOSIZE | NativeMethods.SWP_NOMOVE); - - // Move the window into the foreground as it may not have been achieved by the 'SetWindowPos' call - var success = NativeMethods.SetForegroundWindow(window); - - if (!success) - { - throw new InvalidOperationException("Setting the foreground window failed."); - } - - // Ensure the window is 'Active' as it may not have been achieved by 'SetForegroundWindow' - NativeMethods.SetActiveWindow(window); - - // Give the window the keyboard focus as it may not have been achieved by 'SetActiveWindow' - NativeMethods.SetFocus(window); - - // Remove the 'Top-Most' qualification from the window - NativeMethods.SetWindowPos(window, (IntPtr)NativeMethods.HWND_NOTOPMOST, 0, 0, 0, 0, NativeMethods.SWP_NOSIZE | NativeMethods.SWP_NOMOVE); - } - finally - { - if (threadInputsAttached) - { - // Finally, detach the thread inputs from eachother - DetachThreadInput(currentThreadId, activeThreadId); - } - } - } - } -} diff --git a/Tvl.VisualStudio.MouseFastScroll.IntegrationTests/Harness/NativeMethods.cs b/Tvl.VisualStudio.MouseFastScroll.IntegrationTests/Harness/NativeMethods.cs index 1581f07..76d477d 100644 --- a/Tvl.VisualStudio.MouseFastScroll.IntegrationTests/Harness/NativeMethods.cs +++ b/Tvl.VisualStudio.MouseFastScroll.IntegrationTests/Harness/NativeMethods.cs @@ -5,106 +5,14 @@ namespace Tvl.VisualStudio.MouseFastScroll.IntegrationTests.Harness { using System; using System.Runtime.InteropServices; - using System.Text; - using IBindCtx = Microsoft.VisualStudio.OLE.Interop.IBindCtx; - using IRunningObjectTable = Microsoft.VisualStudio.OLE.Interop.IRunningObjectTable; internal static class NativeMethods { - private const string Kernel32 = "kernel32.dll"; - private const string Ole32 = "ole32.dll"; private const string User32 = "User32.dll"; - public const int RPC_E_CALL_REJECTED = unchecked((int)0x80010001); - public const int RPC_E_SERVERCALL_RETRYLATER = unchecked((int)0x8001010A); - - public const uint GA_PARENT = 1; - public const uint GA_ROOT = 2; - public const uint GA_ROOTOWNER = 3; - - public const uint GW_HWNDFIRST = 0; - public const uint GW_HWNDLAST = 1; - public const uint GW_HWNDNEXT = 2; - public const uint GW_HWNDPREV = 3; - public const uint GW_OWNER = 4; - public const uint GW_CHILD = 5; - public const uint GW_ENABLEDPOPUP = 6; - - public const int HWND_NOTOPMOST = -2; - public const int HWND_TOPMOST = -1; - public const int HWND_TOP = 0; - public const int HWND_BOTTOM = 1; - - public const uint INPUT_MOUSE = 0; - public const uint INPUT_KEYBOARD = 1; - public const uint INPUT_HARDWARE = 2; - - public const uint KEYEVENTF_NONE = 0x0000; - public const uint KEYEVENTF_EXTENDEDKEY = 0x0001; - public const uint KEYEVENTF_KEYUP = 0x0002; - public const uint KEYEVENTF_UNICODE = 0x0004; - public const uint KEYEVENTF_SCANCODE = 0x0008; - - public const uint SWP_NOSIZE = 0x0001; - public const uint SWP_NOMOVE = 0x0002; - public const uint SWP_NOZORDER = 0x0004; - public const uint SWP_NOREDRAW = 0x008; - public const uint SWP_NOACTIVATE = 0x0010; - public const uint SWP_DRAWFRAME = 0x0020; - public const uint SWP_FRAMECHANGED = 0x0020; - public const uint SWP_SHOWWINDOW = 0x0040; - public const uint SWP_HIDEWINDOW = 0x0080; - public const uint SWP_NOCOPYBITS = 0x0100; - public const uint SWP_NOOWNERZORDER = 0x0200; - public const uint SWP_NOREPOSITION = 0x0200; - public const uint SWP_NOSENDCHANGING = 0x0400; - public const uint SWP_DEFERERASE = 0x2000; - public const uint SWP_ASYNCWINDOWPOS = 0x4000; - - public const uint WM_GETTEXT = 0x000D; - public const uint WM_GETTEXTLENGTH = 0x000E; - - public const uint MAPVK_VK_TO_VSC = 0; - public const uint MAPVK_VSC_TO_VK = 1; - public const uint MAPVK_VK_TO_CHAR = 2; - public const uint MAPVK_VSC_TO_KV_EX = 3; - public const int SM_CXSCREEN = 0; public const int SM_CYSCREEN = 1; - public static readonly int SizeOf_INPUT = Marshal.SizeOf(); - - [UnmanagedFunctionPointer(CallingConvention.Winapi, SetLastError = false)] - [return: MarshalAs(UnmanagedType.Bool)] - public delegate bool WNDENUMPROC(IntPtr hWnd, IntPtr lParam); - - [DllImport(Kernel32)] - public static extern uint GetCurrentThreadId(); - - [DllImport(User32, SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool AttachThreadInput(uint idAttach, uint idAttachTo, [MarshalAs(UnmanagedType.Bool)] bool fAttach); - - [DllImport(User32)] - public static extern IntPtr GetForegroundWindow(); - - [DllImport(User32)] - public static extern uint GetWindowThreadProcessId(IntPtr hWnd, [Optional] IntPtr lpdwProcessId); - - [DllImport(User32, SetLastError = true)] - public static extern IntPtr SetActiveWindow(IntPtr hWnd); - - [DllImport(User32, SetLastError = true)] - public static extern IntPtr SetFocus(IntPtr hWnd); - - [DllImport(User32, SetLastError = false)] - [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool SetForegroundWindow(IntPtr hWnd); - - [DllImport(User32, SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool SetWindowPos(IntPtr hWnd, [Optional] IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags); - [DllImport(User32, CharSet = CharSet.Unicode)] public static extern int GetSystemMetrics(int nIndex); @@ -127,50 +35,5 @@ public struct POINT public IntPtr x; public IntPtr y; } - - [StructLayout(LayoutKind.Explicit, CharSet = CharSet.Unicode, Pack = 8)] - public struct INPUT - { - [FieldOffset(0)] - public uint Type; - - [FieldOffset(4)] - public MOUSEINPUT mi; - - [FieldOffset(4)] - public KEYBDINPUT ki; - - [FieldOffset(4)] - public HARDWAREINPUT hi; - } - - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode, Pack = 8)] - public struct HARDWAREINPUT - { - public uint uMsg; - public ushort wParamL; - public ushort wParamH; - } - - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode, Pack = 8)] - public struct KEYBDINPUT - { - public ushort wVk; - public ushort wScan; - public uint dwFlags; - public uint time; - public IntPtr dwExtraInfo; - } - - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode, Pack = 8)] - public struct MOUSEINPUT - { - public int dx; - public int dy; - public uint mouseData; - public uint dwFlags; - public uint time; - public IntPtr dwExtraInfo; - } } } diff --git a/Tvl.VisualStudio.MouseFastScroll.IntegrationTests/IdeFactTest.cs b/Tvl.VisualStudio.MouseFastScroll.IntegrationTests/IdeFactTest.cs deleted file mode 100644 index 8b0b70c..0000000 --- a/Tvl.VisualStudio.MouseFastScroll.IntegrationTests/IdeFactTest.cs +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. -// Licensed under the Apache License, Version 2.0. See LICENSE.txt in the project root for license information. - -namespace Tvl.VisualStudio.MouseFastScroll.IntegrationTests -{ - using System.Diagnostics; - using System.Threading; - using System.Threading.Tasks; - using System.Windows; - using Microsoft.VisualStudio.Shell.Interop; - using Microsoft.VisualStudio.Threading; - using Xunit; - using _DTE = EnvDTE._DTE; - using DTE = EnvDTE.DTE; - using ServiceProvider = Microsoft.VisualStudio.Shell.ServiceProvider; - - public class IdeFactTest : AbstractIdeIntegrationTest - { - [IdeFact] - public void TestOpenAndCloseIDE() - { - Assert.Equal("devenv", Process.GetCurrentProcess().ProcessName); - var dte = (DTE)ServiceProvider.GetService(typeof(_DTE)); - Assert.NotNull(dte); - } - - [IdeFact] - public void TestRunsOnUIThread() - { - Assert.True(Application.Current.Dispatcher.CheckAccess()); - } - - [IdeFact] - public async Task TestRunsOnUIThreadAsync() - { - Assert.True(Application.Current.Dispatcher.CheckAccess()); - await Task.Yield(); - Assert.True(Application.Current.Dispatcher.CheckAccess()); - } - - [IdeFact] - public async Task TestYieldsToWorkAsync() - { - Assert.True(Application.Current.Dispatcher.CheckAccess()); - - var task = Application.Current.Dispatcher.InvokeAsync(() => { }).Task; - Assert.False(task.IsCompleted); - await task; - - Assert.True(Application.Current.Dispatcher.CheckAccess()); - } - - [IdeFact] - public async Task TestJoinableTaskFactoryAsync() - { - Assert.NotNull(JoinableTaskContext); - Assert.NotNull(JoinableTaskFactory); - Assert.Equal(Thread.CurrentThread, JoinableTaskContext.MainThread); - - await TaskScheduler.Default; - - Assert.NotEqual(Thread.CurrentThread, JoinableTaskContext.MainThread); - - await JoinableTaskFactory.SwitchToMainThreadAsync(); - - Assert.Equal(Thread.CurrentThread, JoinableTaskContext.MainThread); - } - - [IdeFact(MinVersion = VisualStudioVersion.VS2012, MaxVersion = VisualStudioVersion.VS2012)] - public void TestJoinableTaskFactoryProvidedByTest() - { - var taskSchedulerServiceObject = ServiceProvider.GetService(typeof(SVsTaskSchedulerService)); - Assert.NotNull(taskSchedulerServiceObject); - - var taskSchedulerService = taskSchedulerServiceObject as IVsTaskSchedulerService; - Assert.NotNull(taskSchedulerService); - - var taskSchedulerService2 = taskSchedulerServiceObject as IVsTaskSchedulerService2; - Assert.Null(taskSchedulerService2); - - Assert.NotNull(JoinableTaskContext); - } - - [IdeFact(MinVersion = VisualStudioVersion.VS2013)] - public void TestJoinableTaskFactoryObtainedFromEnvironment() - { - var taskSchedulerServiceObject = ServiceProvider.GetService(typeof(SVsTaskSchedulerService)); - Assert.NotNull(taskSchedulerServiceObject); - - var taskSchedulerService = taskSchedulerServiceObject as IVsTaskSchedulerService2; - Assert.NotNull(taskSchedulerService); - - Assert.Same(JoinableTaskContext, taskSchedulerService.GetAsyncTaskContext()); - } - } -} diff --git a/Tvl.VisualStudio.MouseFastScroll.IntegrationTests/InProcess/EditorInProcess.cs b/Tvl.VisualStudio.MouseFastScroll.IntegrationTests/InProcess/EditorInProcess.cs new file mode 100644 index 0000000..3794882 --- /dev/null +++ b/Tvl.VisualStudio.MouseFastScroll.IntegrationTests/InProcess/EditorInProcess.cs @@ -0,0 +1,120 @@ +// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE.txt in the project root for license information. + +namespace Microsoft.VisualStudio.Extensibility.Testing +{ + using System.Threading; + using System.Threading.Tasks; + using System.Windows; + using Microsoft.VisualStudio.Shell.Interop; + using Microsoft.VisualStudio.Text; + using Microsoft.VisualStudio.Text.Formatting; + using Tvl.VisualStudio.MouseFastScroll.IntegrationTests.InProcess; + + internal partial class EditorInProcess + { + public async Task GetTextAsync(CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var view = await GetActiveTextViewAsync(cancellationToken); + return view.TextSnapshot.GetText(); + } + + public async Task SetTextAsync(string text, CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var view = await GetActiveTextViewAsync(cancellationToken); + var textSnapshot = view.TextSnapshot; + var replacementSpan = new SnapshotSpan(textSnapshot, 0, textSnapshot.Length); + view.TextBuffer.Replace(replacementSpan, text); + } + + public async Task ActivateAsync(CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var dte = await GetRequiredGlobalServiceAsync(cancellationToken); + dte.ActiveDocument.Activate(); + } + + public async Task GetCaretPositionAsync(CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var view = await GetActiveTextViewAsync(cancellationToken); + + var subjectBuffer = view.GetBufferContainingCaret(); + Assumes.Present(subjectBuffer); + + var bufferPosition = view.Caret.Position.BufferPosition; + return bufferPosition.Position; + } + + public async Task MoveCaretAsync(int position, CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var view = await GetActiveTextViewAsync(cancellationToken); + var subjectBuffer = view.GetBufferContainingCaret(); + var point = new SnapshotPoint(subjectBuffer.CurrentSnapshot, position); + + view.Caret.MoveTo(point); + } + + public async Task IsCaretOnScreenAsync(CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var view = await GetActiveTextViewAsync(cancellationToken); + var caret = view.Caret; + + return caret.Left >= view.ViewportLeft + && caret.Right <= view.ViewportRight + && caret.Top >= view.ViewportTop + && caret.Bottom <= view.ViewportBottom; + } + + public async Task GetFirstVisibleLineAsync(CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var view = await GetActiveTextViewAsync(cancellationToken); + return view.TextViewLines.FirstVisibleLine.Start.GetContainingLine().LineNumber; + } + + public async Task GetLastVisibleLineAsync(CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var view = await GetActiveTextViewAsync(cancellationToken); + return view.TextViewLines.LastVisibleLine.Start.GetContainingLine().LineNumber; + } + + public async Task GetLastVisibleLineStateAsync(CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var view = await GetActiveTextViewAsync(cancellationToken); + return view.TextViewLines.LastVisibleLine.VisibilityState; + } + + public async Task GetCenterOfEditorOnScreenAsync(CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var view = await GetActiveTextViewAsync(cancellationToken); + var center = new Point(view.VisualElement.ActualWidth / 2, view.VisualElement.ActualHeight / 2); + return view.VisualElement.PointToScreen(center); + } + + public async Task GetZoomLevelAsync(CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var view = await GetActiveTextViewAsync(cancellationToken); + return view.ZoomLevel; + } + } +} diff --git a/Tvl.VisualStudio.MouseFastScroll.IntegrationTests/InProcess/Editor_InProc2.cs b/Tvl.VisualStudio.MouseFastScroll.IntegrationTests/InProcess/Editor_InProc2.cs deleted file mode 100644 index 68326ba..0000000 --- a/Tvl.VisualStudio.MouseFastScroll.IntegrationTests/InProcess/Editor_InProc2.cs +++ /dev/null @@ -1,151 +0,0 @@ -// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. -// Licensed under the Apache License, Version 2.0. See LICENSE.txt in the project root for license information. - -namespace Tvl.VisualStudio.MouseFastScroll.IntegrationTests.InProcess -{ - using System; - using System.Runtime.InteropServices; - using System.Threading.Tasks; - using System.Windows; - using Microsoft.VisualStudio.Text; - using Microsoft.VisualStudio.Text.Editor; - using Microsoft.VisualStudio.Text.Formatting; - using Microsoft.VisualStudio.TextManager.Interop; - using Microsoft.VisualStudio.Threading; - - internal class Editor_InProc2 : TextViewWindow_InProc2 - { - private static readonly Guid IWpfTextViewId = new Guid("8C40265E-9FDB-4F54-A0FD-EBB72B7D0476"); - - public Editor_InProc2(JoinableTaskFactory joinableTaskFactory) - : base(joinableTaskFactory) - { - } - - protected override async Task GetActiveTextViewAsync() - { - return (await GetActiveTextViewHostAsync()).TextView; - } - - private async Task GetActiveVsTextViewAsync() - { - await JoinableTaskFactory.SwitchToMainThreadAsync(); - - var vsTextManager = await GetGlobalServiceAsync(); - - var hresult = vsTextManager.GetActiveView(fMustHaveFocus: 1, pBuffer: null, ppView: out var vsTextView); - Marshal.ThrowExceptionForHR(hresult); - - return vsTextView; - } - - private async Task GetActiveTextViewHostAsync() - { - // The active text view might not have finished composing yet, waiting for the application to 'idle' - // means that it is done pumping messages (including WM_PAINT) and the window should return the correct text view - await WaitForApplicationIdleAsync(); - - await JoinableTaskFactory.SwitchToMainThreadAsync(); - - var activeVsTextView = (IVsUserData)await GetActiveVsTextViewAsync(); - - var hresult = activeVsTextView.GetData(IWpfTextViewId, out var wpfTextViewHost); - Marshal.ThrowExceptionForHR(hresult); - - return (IWpfTextViewHost)wpfTextViewHost; - } - - public async Task ActivateAsync() - { - (await GetDTEAsync()).ActiveDocument.Activate(); - } - - public async Task GetTextAsync() - { - await JoinableTaskFactory.SwitchToMainThreadAsync(); - - var view = await GetActiveTextViewAsync(); - return view.TextSnapshot.GetText(); - } - - public async Task SetTextAsync(string text) - { - await JoinableTaskFactory.SwitchToMainThreadAsync(); - - var view = await GetActiveTextViewAsync(); - var textSnapshot = view.TextSnapshot; - var replacementSpan = new SnapshotSpan(textSnapshot, 0, textSnapshot.Length); - view.TextBuffer.Replace(replacementSpan, text); - } - - public async Task MoveCaretAsync(int position) - { - await JoinableTaskFactory.SwitchToMainThreadAsync(); - - var view = await GetActiveTextViewAsync(); - var subjectBuffer = view.GetBufferContainingCaret(); - var point = new SnapshotPoint(subjectBuffer.CurrentSnapshot, position); - - view.Caret.MoveTo(point); - } - - public async Task IsCaretOnScreenAsync() - { - await JoinableTaskFactory.SwitchToMainThreadAsync(); - - var view = await GetActiveTextViewAsync(); - var caret = view.Caret; - - return caret.Left >= view.ViewportLeft - && caret.Right <= view.ViewportRight - && caret.Top >= view.ViewportTop - && caret.Bottom <= view.ViewportBottom; - } - - protected override Task GetBufferContainingCaretAsync(IWpfTextView view) - { - return Task.FromResult(view.GetBufferContainingCaret()); - } - - public async Task GetFirstVisibleLineAsync() - { - await JoinableTaskFactory.SwitchToMainThreadAsync(); - - var view = await GetActiveTextViewAsync(); - return view.TextViewLines.FirstVisibleLine.Start.GetContainingLine().LineNumber; - } - - public async Task GetLastVisibleLineAsync() - { - await JoinableTaskFactory.SwitchToMainThreadAsync(); - - var view = await GetActiveTextViewAsync(); - return view.TextViewLines.LastVisibleLine.Start.GetContainingLine().LineNumber; - } - - public async Task GetLastVisibleLineStateAsync() - { - await JoinableTaskFactory.SwitchToMainThreadAsync(); - - var view = await GetActiveTextViewAsync(); - return view.TextViewLines.LastVisibleLine.VisibilityState; - } - - public async Task GetCenterOfEditorOnScreenAsync() - { - await JoinableTaskFactory.SwitchToMainThreadAsync(); - - var view = await GetActiveTextViewAsync(); - var center = new Point(view.VisualElement.ActualWidth / 2, view.VisualElement.ActualHeight / 2); - return view.VisualElement.PointToScreen(center); - } - - public async Task GetZoomLevelAsync() - { - await JoinableTaskFactory.SwitchToMainThreadAsync(); - - var view = await GetActiveTextViewAsync(); - return view.ZoomLevel; - } - } -} diff --git a/Tvl.VisualStudio.MouseFastScroll.IntegrationTests/InProcess/IdeSendKeys.cs b/Tvl.VisualStudio.MouseFastScroll.IntegrationTests/InProcess/IdeSendKeys.cs index 87cff0c..002adeb 100644 --- a/Tvl.VisualStudio.MouseFastScroll.IntegrationTests/InProcess/IdeSendKeys.cs +++ b/Tvl.VisualStudio.MouseFastScroll.IntegrationTests/InProcess/IdeSendKeys.cs @@ -4,189 +4,80 @@ namespace Tvl.VisualStudio.MouseFastScroll.IntegrationTests.Harness { using System; - using System.Runtime.InteropServices; + using System.Threading; using System.Threading.Tasks; - using Microsoft.VisualStudio.Threading; - using Tvl.VisualStudio.MouseFastScroll.IntegrationTests.InProcess; + using Microsoft.VisualStudio.Extensibility.Testing; using WindowsInput; using WindowsInput.Native; - public class IdeSendKeys + [TestService] + internal partial class IdeSendKeys : InProcComponent { - private readonly VisualStudio_InProc2 _visualStudio; - - public IdeSendKeys(JoinableTaskFactory joinableTaskFactory) - { - _visualStudio = new VisualStudio_InProc2(joinableTaskFactory); - } - internal async Task SendAsync(params object[] keys) { - await SendAsync(inputSimulator => - { - foreach (var key in keys) + await SendAsync( + inputSimulator => { - switch (key) + foreach (var key in keys) { - case string str: - var text = str.Replace("\r\n", "\r").Replace("\n", "\r"); - int index = 0; - while (index < text.Length) + switch (key) { - if (text[index] == '\r') - { - inputSimulator.Keyboard.KeyPress(VirtualKeyCode.RETURN); - index++; - } - else + case string str: + var text = str.Replace("\r\n", "\r").Replace("\n", "\r"); + int index = 0; + while (index < text.Length) { - int nextIndex = text.IndexOf('\r', index); - if (nextIndex == -1) + if (text[index] == '\r') { - nextIndex = text.Length; + inputSimulator.Keyboard.KeyPress(VirtualKeyCode.RETURN); + index++; + } + else + { + int nextIndex = text.IndexOf('\r', index); + if (nextIndex == -1) + { + nextIndex = text.Length; + } + + inputSimulator.Keyboard.TextEntry(text.Substring(index, nextIndex - index)); + index = nextIndex; } - - inputSimulator.Keyboard.TextEntry(text.Substring(index, nextIndex - index)); - index = nextIndex; } - } - break; + break; - case char c: - inputSimulator.Keyboard.TextEntry(c); - break; + case char c: + inputSimulator.Keyboard.TextEntry(c); + break; - case VirtualKeyCode virtualKeyCode: - inputSimulator.Keyboard.KeyPress(virtualKeyCode); - break; + case VirtualKeyCode virtualKeyCode: + inputSimulator.Keyboard.KeyPress(virtualKeyCode); + break; - case null: - throw new ArgumentNullException(nameof(keys)); + case null: + throw new ArgumentNullException(nameof(keys)); - default: - throw new ArgumentException($"Unexpected type encountered: {key.GetType()}", nameof(keys)); + default: + throw new ArgumentException($"Unexpected type encountered: {key.GetType()}", nameof(keys)); + } } - } - }); + }, + CancellationToken.None); } - internal async Task SendAsync(Action actions) + internal async Task SendAsync(Action actions, CancellationToken cancellationToken) { if (actions == null) { throw new ArgumentNullException(nameof(actions)); } - var foregroundWindow = IntPtr.Zero; - - try - { - var foreground = GetForegroundWindow(); - await _visualStudio.ActivateMainWindowAsync(); - - await Task.Run(() => actions(new InputSimulator())); - } - finally - { - if (foregroundWindow != IntPtr.Zero) - { - SetForegroundWindow(foregroundWindow); - } - } - - await InProcComponent2.WaitForApplicationIdleAsync(); - } - - private static bool AttachThreadInput(uint idAttach, uint idAttachTo) - { - var success = NativeMethods.AttachThreadInput(idAttach, idAttachTo, true); - if (!success) - { - var hresult = Marshal.GetHRForLastWin32Error(); - Marshal.ThrowExceptionForHR(hresult); - } - - return success; - } - - private static bool DetachThreadInput(uint idAttach, uint idAttachTo) - { - var success = NativeMethods.AttachThreadInput(idAttach, idAttachTo, false); - if (!success) - { - var hresult = Marshal.GetHRForLastWin32Error(); - Marshal.ThrowExceptionForHR(hresult); - } - - return success; - } - - private static IntPtr GetForegroundWindow() - { - // Attempt to get the foreground window in a loop, as the NativeMethods function can return IntPtr.Zero - // in certain circumstances, such as when a window is losing activation. - var foregroundWindow = IntPtr.Zero; + await TestServices.Editor.ActivateAsync(cancellationToken); - do - { - foregroundWindow = NativeMethods.GetForegroundWindow(); - } - while (foregroundWindow == IntPtr.Zero); - - return foregroundWindow; - } - - private static void SetForegroundWindow(IntPtr window, bool skipAttachingThread = false) - { - var foregroundWindow = GetForegroundWindow(); - - if (window == foregroundWindow) - { - return; - } + await Task.Run(() => actions(new InputSimulator())); - var activeThreadId = NativeMethods.GetWindowThreadProcessId(foregroundWindow, IntPtr.Zero); - var currentThreadId = NativeMethods.GetCurrentThreadId(); - - var threadInputsAttached = false; - - try - { - // No need to re-attach threads in case when VS initializaed an UI thread for a debugged application. - if (!skipAttachingThread) - { - // Attach the thread inputs so that 'SetActiveWindow' and 'SetFocus' work - threadInputsAttached = AttachThreadInput(currentThreadId, activeThreadId); - } - - // Make the window a top-most window so it will appear above any existing top-most windows - NativeMethods.SetWindowPos(window, (IntPtr)NativeMethods.HWND_TOPMOST, 0, 0, 0, 0, NativeMethods.SWP_NOSIZE | NativeMethods.SWP_NOMOVE); - - // Move the window into the foreground as it may not have been achieved by the 'SetWindowPos' call - var success = NativeMethods.SetForegroundWindow(window); - if (!success) - { - throw new InvalidOperationException("Setting the foreground window failed."); - } - - // Ensure the window is 'Active' as it may not have been achieved by 'SetForegroundWindow' - NativeMethods.SetActiveWindow(window); - - // Give the window the keyboard focus as it may not have been achieved by 'SetActiveWindow' - NativeMethods.SetFocus(window); - - // Remove the 'Top-Most' qualification from the window - NativeMethods.SetWindowPos(window, (IntPtr)NativeMethods.HWND_NOTOPMOST, 0, 0, 0, 0, NativeMethods.SWP_NOSIZE | NativeMethods.SWP_NOMOVE); - } - finally - { - if (threadInputsAttached) - { - // Finally, detach the thread inputs from eachother - DetachThreadInput(currentThreadId, activeThreadId); - } - } + await WaitForApplicationIdleAsync(cancellationToken); } } } diff --git a/Tvl.VisualStudio.MouseFastScroll.IntegrationTests/InProcess/InProcComponent2.cs b/Tvl.VisualStudio.MouseFastScroll.IntegrationTests/InProcess/InProcComponent2.cs deleted file mode 100644 index 0623e65..0000000 --- a/Tvl.VisualStudio.MouseFastScroll.IntegrationTests/InProcess/InProcComponent2.cs +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. -// Licensed under the Apache License, Version 2.0. See LICENSE.txt in the project root for license information. - -namespace Tvl.VisualStudio.MouseFastScroll.IntegrationTests.InProcess -{ - using System; - using System.Threading.Tasks; - using System.Windows; - using System.Windows.Threading; - using Microsoft.VisualStudio.Threading; - using static Microsoft.VisualStudio.Shell.VsTaskLibraryHelper; - using DTE = EnvDTE.DTE; - using IAsyncServiceProvider = Microsoft.VisualStudio.Shell.Interop.IAsyncServiceProvider; - using SAsyncServiceProvider = Microsoft.VisualStudio.Shell.Interop.SAsyncServiceProvider; - using SDTE = Microsoft.VisualStudio.Shell.Interop.SDTE; - using ServiceProvider = Microsoft.VisualStudio.Shell.ServiceProvider; - - internal abstract class InProcComponent2 - { - protected InProcComponent2(JoinableTaskFactory joinableTaskFactory) - { - JoinableTaskFactory = joinableTaskFactory ?? throw new ArgumentNullException(nameof(joinableTaskFactory)); - } - - protected JoinableTaskFactory JoinableTaskFactory - { - get; - } - - protected async Task GetGlobalServiceAsync() - where TService : class - where TInterface : class - { - await JoinableTaskFactory.SwitchToMainThreadAsync(); - - if (ServiceProvider.GlobalProvider.GetService(typeof(SAsyncServiceProvider)) is IAsyncServiceProvider asyncServiceProvider) - { - return (TInterface)await asyncServiceProvider.QueryServiceAsync(typeof(TService).GUID); - } - else - { - return (TInterface)ServiceProvider.GlobalProvider.GetService(typeof(TService)); - } - } - - protected async Task GetDTEAsync() - { - return await GetGlobalServiceAsync(); - } - - /// - /// Waiting for the application to 'idle' means that it is done pumping messages (including WM_PAINT). - /// - protected internal static async Task WaitForApplicationIdleAsync() - { - await Application.Current.Dispatcher.InvokeAsync(() => { }, DispatcherPriority.ApplicationIdle); - } - } -} diff --git a/Tvl.VisualStudio.MouseFastScroll.IntegrationTests/InProcess/TextViewWindow_InProc2.cs b/Tvl.VisualStudio.MouseFastScroll.IntegrationTests/InProcess/TextViewWindow_InProc2.cs deleted file mode 100644 index 2c9f6c4..0000000 --- a/Tvl.VisualStudio.MouseFastScroll.IntegrationTests/InProcess/TextViewWindow_InProc2.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. -// Licensed under the Apache License, Version 2.0. See LICENSE.txt in the project root for license information. - -namespace Tvl.VisualStudio.MouseFastScroll.IntegrationTests.InProcess -{ - using System.Threading.Tasks; - using Microsoft.VisualStudio.Text; - using Microsoft.VisualStudio.Text.Editor; - using Microsoft.VisualStudio.Threading; - - internal abstract class TextViewWindow_InProc2 : InProcComponent2 - { - protected TextViewWindow_InProc2(JoinableTaskFactory joinableTaskFactory) - : base(joinableTaskFactory) - { - } - - protected abstract Task GetActiveTextViewAsync(); - - protected abstract Task GetBufferContainingCaretAsync(IWpfTextView view); - - public async Task GetCaretPositionAsync() - { - await JoinableTaskFactory.SwitchToMainThreadAsync(); - - var view = await GetActiveTextViewAsync(); - var subjectBuffer = await GetBufferContainingCaretAsync(view); - var bufferPosition = view.Caret.Position.BufferPosition; - return bufferPosition.Position; - } - } -} diff --git a/Tvl.VisualStudio.MouseFastScroll.IntegrationTests/InProcess/VisualStudio_InProc2.cs b/Tvl.VisualStudio.MouseFastScroll.IntegrationTests/InProcess/VisualStudio_InProc2.cs deleted file mode 100644 index 703042e..0000000 --- a/Tvl.VisualStudio.MouseFastScroll.IntegrationTests/InProcess/VisualStudio_InProc2.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. -// Licensed under the Apache License, Version 2.0. See LICENSE.txt in the project root for license information. - -namespace Tvl.VisualStudio.MouseFastScroll.IntegrationTests.InProcess -{ - using System; - using System.Diagnostics; - using System.Threading.Tasks; - using Microsoft.VisualStudio.Threading; - using Tvl.VisualStudio.MouseFastScroll.IntegrationTests.Harness; - - internal partial class VisualStudio_InProc2 : InProcComponent2 - { - public VisualStudio_InProc2(JoinableTaskFactory joinableTaskFactory) - : base(joinableTaskFactory) - { - } - - public async Task ActivateMainWindowAsync() - { - await JoinableTaskFactory.SwitchToMainThreadAsync(); - - var dte = await GetDTEAsync(); - - var activeVisualStudioWindow = (IntPtr)dte.ActiveWindow.HWnd; - Debug.WriteLine($"DTE.ActiveWindow.HWnd = {activeVisualStudioWindow}"); - - if (activeVisualStudioWindow == IntPtr.Zero) - { - activeVisualStudioWindow = (IntPtr)dte.MainWindow.HWnd; - Debug.WriteLine($"DTE.MainWindow.HWnd = {activeVisualStudioWindow}"); - } - - IntegrationHelper.SetForegroundWindow(activeVisualStudioWindow); - } - } -} diff --git a/Tvl.VisualStudio.MouseFastScroll.IntegrationTests/Properties/AssemblyInfo.cs b/Tvl.VisualStudio.MouseFastScroll.IntegrationTests/Properties/AssemblyInfo.cs index 5c5cfac..2194433 100644 --- a/Tvl.VisualStudio.MouseFastScroll.IntegrationTests/Properties/AssemblyInfo.cs +++ b/Tvl.VisualStudio.MouseFastScroll.IntegrationTests/Properties/AssemblyInfo.cs @@ -3,6 +3,7 @@ using System; using System.Runtime.InteropServices; +using Xunit; // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information @@ -13,3 +14,5 @@ // to COM components. If you need to access a type in this assembly from // COM, set the ComVisible attribute to true on that type. [assembly: ComVisible(false)] + +[assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly, DisableTestParallelization = true)] diff --git a/Tvl.VisualStudio.MouseFastScroll.IntegrationTests/ScrollingIntegrationTest.cs b/Tvl.VisualStudio.MouseFastScroll.IntegrationTests/ScrollingIntegrationTest.cs index 1ca9136..e7aa7ce 100644 --- a/Tvl.VisualStudio.MouseFastScroll.IntegrationTests/ScrollingIntegrationTest.cs +++ b/Tvl.VisualStudio.MouseFastScroll.IntegrationTests/ScrollingIntegrationTest.cs @@ -8,13 +8,13 @@ namespace Tvl.VisualStudio.MouseFastScroll.IntegrationTests using System.Threading.Tasks; using System.Windows; using System.Windows.Media; + using Microsoft.VisualStudio.Extensibility.Testing; + using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio.Text.Formatting; using Tvl.VisualStudio.MouseFastScroll.IntegrationTests.Harness; - using Tvl.VisualStudio.MouseFastScroll.IntegrationTests.InProcess; using WindowsInput.Native; using Xunit; using Xunit.Abstractions; - using _DTE = EnvDTE._DTE; using DTE = EnvDTE.DTE; using vsSaveChanges = EnvDTE.vsSaveChanges; @@ -23,8 +23,6 @@ public class ScrollingIntegrationTest : AbstractIdeIntegrationTest public ScrollingIntegrationTest(ITestOutputHelper testOutputHelper) { TestOutputHelper = testOutputHelper; - Editor = new Editor_InProc2(JoinableTaskFactory); - SendKeys = new IdeSendKeys(JoinableTaskFactory); } protected ITestOutputHelper TestOutputHelper @@ -32,95 +30,91 @@ protected ITestOutputHelper TestOutputHelper get; } - private Editor_InProc2 Editor - { - get; - } - - private IdeSendKeys SendKeys - { - get; - } - [IdeFact] public async Task BasicScrollingBehaviorAsync() { - var dte = (DTE)ServiceProvider.GetService(typeof(_DTE)); + var dte = await TestServices.Shell.GetRequiredGlobalServiceAsync(HangMitigatingCancellationToken); var window = dte.ItemOperations.NewFile(Name: Guid.NewGuid() + ".txt"); string initialText = string.Join(string.Empty, Enumerable.Range(0, 400).Select(i => Guid.NewGuid() + Environment.NewLine)); - await Editor.SetTextAsync(initialText); + await TestServices.Editor.SetTextAsync(initialText, HangMitigatingCancellationToken); string additionalTypedText = Guid.NewGuid().ToString() + "\n" + Guid.NewGuid().ToString(); - await Editor.ActivateAsync(); - await SendKeys.SendAsync(additionalTypedText); + await TestServices.Editor.ActivateAsync(HangMitigatingCancellationToken); + await TestServices.IdeSendKeys.SendAsync(additionalTypedText); string expected = initialText + additionalTypedText.Replace("\n", Environment.NewLine); - Assert.Equal(expected, await Editor.GetTextAsync()); + Assert.Equal(expected, await TestServices.Editor.GetTextAsync(HangMitigatingCancellationToken)); - Assert.Equal(expected.Length, await Editor.GetCaretPositionAsync()); + Assert.Equal(expected.Length, await TestServices.Editor.GetCaretPositionAsync(HangMitigatingCancellationToken)); // Move the caret and verify the final position. Note that the MoveCaret operation does not scroll the view. - int firstVisibleLine = await Editor.GetFirstVisibleLineAsync(); + int firstVisibleLine = await TestServices.Editor.GetFirstVisibleLineAsync(HangMitigatingCancellationToken); Assert.True(firstVisibleLine > 0, "Expected the view to start after the first line at this point."); - await Editor.MoveCaretAsync(0); - Assert.Equal(0, await Editor.GetCaretPositionAsync()); - Assert.Equal(firstVisibleLine, await Editor.GetFirstVisibleLineAsync()); - - await SendKeys.SendAsync(inputSimulator => - { - inputSimulator.Keyboard - .KeyDown(VirtualKeyCode.CONTROL) - .KeyPress(VirtualKeyCode.HOME) - .KeyUp(VirtualKeyCode.CONTROL); - }); - - Assert.True(await Editor.IsCaretOnScreenAsync()); - firstVisibleLine = await Editor.GetFirstVisibleLineAsync(); + await TestServices.Editor.MoveCaretAsync(0, HangMitigatingCancellationToken); + Assert.Equal(0, await TestServices.Editor.GetCaretPositionAsync(HangMitigatingCancellationToken)); + Assert.Equal(firstVisibleLine, await TestServices.Editor.GetFirstVisibleLineAsync(HangMitigatingCancellationToken)); + + await TestServices.IdeSendKeys.SendAsync( + inputSimulator => + { + inputSimulator.Keyboard + .KeyDown(VirtualKeyCode.CONTROL) + .KeyPress(VirtualKeyCode.HOME) + .KeyUp(VirtualKeyCode.CONTROL); + }, + HangMitigatingCancellationToken); + + Assert.True(await TestServices.Editor.IsCaretOnScreenAsync(HangMitigatingCancellationToken)); + firstVisibleLine = await TestServices.Editor.GetFirstVisibleLineAsync(HangMitigatingCancellationToken); Assert.Equal(0, firstVisibleLine); - int lastVisibleLine = await Editor.GetLastVisibleLineAsync(); - var lastVisibleLineState = await Editor.GetLastVisibleLineStateAsync(); + int lastVisibleLine = await TestServices.Editor.GetLastVisibleLineAsync(HangMitigatingCancellationToken); + var lastVisibleLineState = await TestServices.Editor.GetLastVisibleLineStateAsync(HangMitigatingCancellationToken); Assert.True(firstVisibleLine < lastVisibleLine); - Point point = await Editor.GetCenterOfEditorOnScreenAsync(); + Point point = await TestServices.Editor.GetCenterOfEditorOnScreenAsync(HangMitigatingCancellationToken); await MoveMouseAsync(point); - await SendKeys.SendAsync(inputSimulator => inputSimulator.Mouse.VerticalScroll(-1)); + await TestServices.IdeSendKeys.SendAsync(inputSimulator => inputSimulator.Mouse.VerticalScroll(-1), HangMitigatingCancellationToken); - Assert.Equal(0, await Editor.GetCaretPositionAsync()); - Assert.Equal(3, await Editor.GetFirstVisibleLineAsync()); + Assert.Equal(0, await TestServices.Editor.GetCaretPositionAsync(HangMitigatingCancellationToken)); + Assert.Equal(3, await TestServices.Editor.GetFirstVisibleLineAsync(HangMitigatingCancellationToken)); await MoveMouseAsync(point); - await SendKeys.SendAsync(inputSimulator => inputSimulator.Mouse.VerticalScroll(1)); + await TestServices.IdeSendKeys.SendAsync(inputSimulator => inputSimulator.Mouse.VerticalScroll(1), HangMitigatingCancellationToken); - Assert.Equal(0, await Editor.GetCaretPositionAsync()); - Assert.Equal(0, await Editor.GetFirstVisibleLineAsync()); + Assert.Equal(0, await TestServices.Editor.GetCaretPositionAsync(HangMitigatingCancellationToken)); + Assert.Equal(0, await TestServices.Editor.GetFirstVisibleLineAsync(HangMitigatingCancellationToken)); await MoveMouseAsync(point); - await SendKeys.SendAsync(inputSimulator => - { - inputSimulator - .Keyboard.KeyDown(VirtualKeyCode.CONTROL) - .Mouse.VerticalScroll(-1) - .Keyboard.Sleep(10).KeyUp(VirtualKeyCode.CONTROL); - }); + await TestServices.IdeSendKeys.SendAsync( + inputSimulator => + { + inputSimulator + .Keyboard.KeyDown(VirtualKeyCode.CONTROL) + .Mouse.VerticalScroll(-1) + .Keyboard.Sleep(10).KeyUp(VirtualKeyCode.CONTROL); + }, + HangMitigatingCancellationToken); int expectedLastVisibleLine = lastVisibleLine + (lastVisibleLineState == VisibilityState.FullyVisible ? 1 : 0); - Assert.Equal(0, await Editor.GetCaretPositionAsync()); - Assert.Equal(expectedLastVisibleLine, await Editor.GetFirstVisibleLineAsync()); + Assert.Equal(0, await TestServices.Editor.GetCaretPositionAsync(HangMitigatingCancellationToken)); + Assert.Equal(expectedLastVisibleLine, await TestServices.Editor.GetFirstVisibleLineAsync(HangMitigatingCancellationToken)); await MoveMouseAsync(point); - await SendKeys.SendAsync(inputSimulator => - { - inputSimulator - .Keyboard.KeyDown(VirtualKeyCode.CONTROL) - .Mouse.VerticalScroll(1) - .Keyboard.Sleep(10).KeyUp(VirtualKeyCode.CONTROL); - }); - - Assert.Equal(0, await Editor.GetCaretPositionAsync()); - Assert.Equal(0, await Editor.GetFirstVisibleLineAsync()); + await TestServices.IdeSendKeys.SendAsync( + inputSimulator => + { + inputSimulator + .Keyboard.KeyDown(VirtualKeyCode.CONTROL) + .Mouse.VerticalScroll(1) + .Keyboard.Sleep(10).KeyUp(VirtualKeyCode.CONTROL); + }, + HangMitigatingCancellationToken); + + Assert.Equal(0, await TestServices.Editor.GetCaretPositionAsync(HangMitigatingCancellationToken)); + Assert.Equal(0, await TestServices.Editor.GetFirstVisibleLineAsync(HangMitigatingCancellationToken)); window.Close(vsSaveChanges.vsSaveChangesNo); } @@ -131,65 +125,71 @@ await SendKeys.SendAsync(inputSimulator => [IdeFact] public async Task ZoomDisabledAsync() { - var dte = (DTE)ServiceProvider.GetService(typeof(_DTE)); + var dte = await TestServices.Shell.GetRequiredGlobalServiceAsync(HangMitigatingCancellationToken); var window = dte.ItemOperations.NewFile(Name: Guid.NewGuid() + ".txt"); string initialText = string.Join(string.Empty, Enumerable.Range(0, 400).Select(i => Guid.NewGuid() + Environment.NewLine)); - await Editor.SetTextAsync(initialText); + await TestServices.Editor.SetTextAsync(initialText, HangMitigatingCancellationToken); string additionalTypedText = Guid.NewGuid().ToString() + "\n" + Guid.NewGuid().ToString(); - await SendKeys.SendAsync(additionalTypedText); + await TestServices.IdeSendKeys.SendAsync(additionalTypedText); string expected = initialText + additionalTypedText.Replace("\n", Environment.NewLine); - Assert.Equal(expected, await Editor.GetTextAsync()); + Assert.Equal(expected, await TestServices.Editor.GetTextAsync(HangMitigatingCancellationToken)); - Assert.Equal(expected.Length, await Editor.GetCaretPositionAsync()); + Assert.Equal(expected.Length, await TestServices.Editor.GetCaretPositionAsync(HangMitigatingCancellationToken)); - await SendKeys.SendAsync(inputSimulator => - { - inputSimulator.Keyboard - .KeyDown(VirtualKeyCode.CONTROL) - .KeyPress(VirtualKeyCode.HOME) - .KeyUp(VirtualKeyCode.CONTROL); - }); + await TestServices.IdeSendKeys.SendAsync( + inputSimulator => + { + inputSimulator.Keyboard + .KeyDown(VirtualKeyCode.CONTROL) + .KeyPress(VirtualKeyCode.HOME) + .KeyUp(VirtualKeyCode.CONTROL); + }, + HangMitigatingCancellationToken); - int firstVisibleLine = await Editor.GetFirstVisibleLineAsync(); + int firstVisibleLine = await TestServices.Editor.GetFirstVisibleLineAsync(HangMitigatingCancellationToken); Assert.Equal(0, firstVisibleLine); - int lastVisibleLine = await Editor.GetLastVisibleLineAsync(); - var lastVisibleLineState = await Editor.GetLastVisibleLineStateAsync(); + int lastVisibleLine = await TestServices.Editor.GetLastVisibleLineAsync(HangMitigatingCancellationToken); + var lastVisibleLineState = await TestServices.Editor.GetLastVisibleLineStateAsync(HangMitigatingCancellationToken); Assert.True(firstVisibleLine < lastVisibleLine); - double zoomLevel = await Editor.GetZoomLevelAsync(); + double zoomLevel = await TestServices.Editor.GetZoomLevelAsync(HangMitigatingCancellationToken); - Point point = await Editor.GetCenterOfEditorOnScreenAsync(); + Point point = await TestServices.Editor.GetCenterOfEditorOnScreenAsync(HangMitigatingCancellationToken); await MoveMouseAsync(point); - await SendKeys.SendAsync(inputSimulator => - { - inputSimulator - .Keyboard.KeyDown(VirtualKeyCode.CONTROL) - .Mouse.VerticalScroll(-1) - .Keyboard.Sleep(10).KeyUp(VirtualKeyCode.CONTROL); - }); + await TestServices.IdeSendKeys.SendAsync( + inputSimulator => + { + inputSimulator + .Keyboard.KeyDown(VirtualKeyCode.CONTROL) + .Mouse.VerticalScroll(-1) + .Keyboard.Sleep(10).KeyUp(VirtualKeyCode.CONTROL); + }, + HangMitigatingCancellationToken); int expectedLastVisibleLine = lastVisibleLine + (lastVisibleLineState == VisibilityState.FullyVisible ? 1 : 0); - Assert.Equal(0, await Editor.GetCaretPositionAsync()); - Assert.Equal(expectedLastVisibleLine, await Editor.GetFirstVisibleLineAsync()); - Assert.Equal(zoomLevel, await Editor.GetZoomLevelAsync()); + Assert.Equal(0, await TestServices.Editor.GetCaretPositionAsync(HangMitigatingCancellationToken)); + Assert.Equal(expectedLastVisibleLine, await TestServices.Editor.GetFirstVisibleLineAsync(HangMitigatingCancellationToken)); + Assert.Equal(zoomLevel, await TestServices.Editor.GetZoomLevelAsync(HangMitigatingCancellationToken)); await MoveMouseAsync(point); - await SendKeys.SendAsync(inputSimulator => - { - inputSimulator - .Keyboard.KeyDown(VirtualKeyCode.CONTROL) - .Mouse.VerticalScroll(1) - .Keyboard.Sleep(10).KeyUp(VirtualKeyCode.CONTROL); - }); - - Assert.Equal(0, await Editor.GetCaretPositionAsync()); - Assert.Equal(0, await Editor.GetFirstVisibleLineAsync()); - Assert.Equal(zoomLevel, await Editor.GetZoomLevelAsync()); + await TestServices.IdeSendKeys.SendAsync( + inputSimulator => + { + inputSimulator + .Keyboard.KeyDown(VirtualKeyCode.CONTROL) + .Mouse.VerticalScroll(1) + .Keyboard.Sleep(10).KeyUp(VirtualKeyCode.CONTROL); + }, + HangMitigatingCancellationToken); + + Assert.Equal(0, await TestServices.Editor.GetCaretPositionAsync(HangMitigatingCancellationToken)); + Assert.Equal(0, await TestServices.Editor.GetFirstVisibleLineAsync(HangMitigatingCancellationToken)); + Assert.Equal(zoomLevel, await TestServices.Editor.GetZoomLevelAsync(HangMitigatingCancellationToken)); window.Close(vsSaveChanges.vsSaveChangesNo); } @@ -202,7 +202,7 @@ private async Task MoveMouseAsync(Point point) var virtualPoint = new ScaleTransform(65535.0 / horizontalResolution, 65535.0 / verticalResolution).Transform(point); TestOutputHelper.WriteLine($"Screen resolution of ({horizontalResolution}, {verticalResolution}) translates mouse to ({virtualPoint.X}, {virtualPoint.Y})."); - await SendKeys.SendAsync(inputSimulator => inputSimulator.Mouse.MoveMouseTo(virtualPoint.X, virtualPoint.Y)); + await TestServices.IdeSendKeys.SendAsync(inputSimulator => inputSimulator.Mouse.MoveMouseTo(virtualPoint.X, virtualPoint.Y), HangMitigatingCancellationToken); // ⚠ The call to GetCursorPos is required for correct behavior. var actualPoint = NativeMethods.GetCursorPos(); diff --git a/Tvl.VisualStudio.MouseFastScroll.IntegrationTests/Threading/JoinableTaskFactoryExtensions.cs b/Tvl.VisualStudio.MouseFastScroll.IntegrationTests/Threading/JoinableTaskFactoryExtensions.cs deleted file mode 100644 index 86fee12..0000000 --- a/Tvl.VisualStudio.MouseFastScroll.IntegrationTests/Threading/JoinableTaskFactoryExtensions.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. -// Licensed under the Apache License, Version 2.0. See LICENSE.txt in the project root for license information. - -namespace Tvl.VisualStudio.MouseFastScroll.IntegrationTests.Threading -{ - using System.Threading; - using System.Windows.Threading; - using Microsoft; - using Microsoft.VisualStudio.Threading; - - // JoinableTaskFactory.WithPriority is available in later releases of vs-threading, but we reference 1.2.0.0 for - // compatibility with Visual Studio 2013. - // https://github.com/Microsoft/vs-threading/pull/142 - internal static class JoinableTaskFactoryExtensions - { - internal static JoinableTaskFactory WithPriority(this JoinableTaskFactory joinableTaskFactory, Dispatcher dispatcher, DispatcherPriority priority) - { - Requires.NotNull(joinableTaskFactory, nameof(joinableTaskFactory)); - Requires.NotNull(dispatcher, nameof(dispatcher)); - - return new DispatcherJoinableTaskFactory(joinableTaskFactory, dispatcher, priority); - } - - private class DispatcherJoinableTaskFactory : DelegatingJoinableTaskFactory - { - private readonly Dispatcher _dispatcher; - private readonly DispatcherPriority _priority; - - public DispatcherJoinableTaskFactory(JoinableTaskFactory innerFactory, Dispatcher dispatcher, DispatcherPriority priority) - : base(innerFactory) - { - _dispatcher = dispatcher; - _priority = priority; - } - - protected override void PostToUnderlyingSynchronizationContext(SendOrPostCallback callback, object state) - { - _dispatcher.BeginInvoke(_priority, callback, state); - } - } - } -} diff --git a/Tvl.VisualStudio.MouseFastScroll.IntegrationTests/Tvl.VisualStudio.MouseFastScroll.IntegrationTests.csproj b/Tvl.VisualStudio.MouseFastScroll.IntegrationTests/Tvl.VisualStudio.MouseFastScroll.IntegrationTests.csproj index 48f170e..84d7c98 100644 --- a/Tvl.VisualStudio.MouseFastScroll.IntegrationTests/Tvl.VisualStudio.MouseFastScroll.IntegrationTests.csproj +++ b/Tvl.VisualStudio.MouseFastScroll.IntegrationTests/Tvl.VisualStudio.MouseFastScroll.IntegrationTests.csproj @@ -2,8 +2,9 @@ - net46 + net472;net46 true + 9 Mouse Fast Scroll extension for Visual Studio integration tests @@ -14,18 +15,34 @@ - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + <_Parameter1>Tvl.VisualStudio.MouseFastScroll.vsix @@ -36,6 +53,12 @@ + + + PreserveNewest + + + diff --git a/Tvl.VisualStudio.MouseFastScroll.IntegrationTests/xunit.runner.json b/Tvl.VisualStudio.MouseFastScroll.IntegrationTests/xunit.runner.json new file mode 100644 index 0000000..1c72a42 --- /dev/null +++ b/Tvl.VisualStudio.MouseFastScroll.IntegrationTests/xunit.runner.json @@ -0,0 +1,3 @@ +{ + "shadowCopy": false +} diff --git a/appveyor.yml b/appveyor.yml index 1db2c42..eb0a8e2 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,5 +1,5 @@ version: 1.0.{build} -image: Visual Studio 2019 +image: Visual Studio 2022 configuration: Release platform: Any CPU init: @@ -11,7 +11,7 @@ build: test_script: - ps: | $unitTestAssembly = "Tvl.VisualStudio.MouseFastScroll.UnitTests\bin\$env:CONFIGURATION\net452\Tvl.VisualStudio.MouseFastScroll.UnitTests.dll" - $integrationTestAssembly = "Tvl.VisualStudio.MouseFastScroll.IntegrationTests\bin\$env:CONFIGURATION\net46\Tvl.VisualStudio.MouseFastScroll.IntegrationTests.dll" + $integrationTestAssembly = "Tvl.VisualStudio.MouseFastScroll.IntegrationTests\bin\$env:CONFIGURATION\net472\Tvl.VisualStudio.MouseFastScroll.IntegrationTests.dll" $buildProperties = [xml](Get-Content Directory.Build.props) $openCoverVersion = $buildProperties.SelectSingleNode('/Project/ItemGroup/PackageReference[@Include="OpenCover"]').Version $openCoverConsole = Join-Path (Join-Path $env:UserProfile '.nuget\packages\') "opencover\$openCoverVersion\tools\OpenCover.Console.exe"