Skip to content

Commit 91dd44b

Browse files
committed
Add trivial test OpenDocumentAndType
1 parent 01bfa96 commit 91dd44b

9 files changed

+308
-0
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
2+
// Licensed under the Apache License, Version 2.0. See LICENSE.txt in the project root for license information.
3+
4+
namespace Tvl.VisualStudio.MouseFastScroll.IntegrationTests
5+
{
6+
internal class Editor_InProc : TextViewWindow_InProc
7+
{
8+
private Editor_InProc()
9+
{
10+
}
11+
12+
public static Editor_InProc Create()
13+
=> new Editor_InProc();
14+
15+
public void Activate()
16+
=> GetDTE().ActiveDocument.Activate();
17+
}
18+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
2+
// Licensed under the Apache License, Version 2.0. See LICENSE.txt in the project root for license information.
3+
4+
namespace Tvl.VisualStudio.MouseFastScroll.IntegrationTests
5+
{
6+
public class Editor_OutOfProc : TextViewWindow_OutOfProc
7+
{
8+
internal Editor_OutOfProc(VisualStudioInstance visualStudioInstance)
9+
: base(visualStudioInstance)
10+
{
11+
EditorInProc = (Editor_InProc)TextViewWindowInProc;
12+
}
13+
14+
internal Editor_InProc EditorInProc
15+
{
16+
get;
17+
}
18+
19+
internal override TextViewWindow_InProc CreateInProcComponent(VisualStudioInstance visualStudioInstance)
20+
=> CreateInProcComponent<Editor_InProc>(visualStudioInstance);
21+
22+
public void Activate()
23+
=> EditorInProc.Activate();
24+
25+
/// <summary>
26+
/// Sends key strokes to the active editor in Visual Studio. Various types are supported by this method:
27+
/// <see cref="string"/> (each character will be sent separately, <see cref="char"/>, and
28+
/// <see cref="VirtualKey"/>).
29+
/// </summary>
30+
public void SendKeys(params object[] keys)
31+
{
32+
Activate();
33+
VisualStudioInstance.SendKeys.Send(keys);
34+
}
35+
}
36+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
2+
// Licensed under the Apache License, Version 2.0. See LICENSE.txt in the project root for license information.
3+
4+
namespace Tvl.VisualStudio.MouseFastScroll.IntegrationTests
5+
{
6+
public abstract class OutOfProcComponent
7+
{
8+
protected OutOfProcComponent(VisualStudioInstance visualStudioInstance)
9+
{
10+
VisualStudioInstance = visualStudioInstance;
11+
}
12+
13+
protected VisualStudioInstance VisualStudioInstance
14+
{
15+
get;
16+
}
17+
18+
internal static TInProcComponent CreateInProcComponent<TInProcComponent>(VisualStudioInstance visualStudioInstance)
19+
where TInProcComponent : InProcComponent
20+
{
21+
return visualStudioInstance.ExecuteInHostProcess<TInProcComponent>(type: typeof(TInProcComponent), methodName: "Create");
22+
}
23+
}
24+
}
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
2+
// Licensed under the Apache License, Version 2.0. See LICENSE.txt in the project root for license information.
3+
4+
namespace Tvl.VisualStudio.MouseFastScroll.IntegrationTests
5+
{
6+
using System;
7+
using System.Runtime.InteropServices;
8+
using WindowsInput;
9+
using WindowsInput.Native;
10+
11+
public class SendKeys
12+
{
13+
private readonly VisualStudioInstance _visualStudioInstance;
14+
15+
public SendKeys(VisualStudioInstance visualStudioInstance)
16+
{
17+
_visualStudioInstance = visualStudioInstance;
18+
}
19+
20+
internal void Send(object[] keys)
21+
{
22+
var foregroundWindow = IntPtr.Zero;
23+
24+
try
25+
{
26+
var foreground = GetForegroundWindow();
27+
_visualStudioInstance.ActivateMainWindow();
28+
29+
var inputSimulator = new InputSimulator();
30+
31+
foreach (var key in keys)
32+
{
33+
switch (key)
34+
{
35+
case string str:
36+
var text = str.Replace("\r\n", "\r").Replace("\n", "\r");
37+
int index = 0;
38+
while (index < text.Length)
39+
{
40+
if (text[index] == '\r')
41+
{
42+
inputSimulator.Keyboard.KeyPress(VirtualKeyCode.RETURN);
43+
index++;
44+
}
45+
else
46+
{
47+
int nextIndex = text.IndexOf('\r', index);
48+
if (nextIndex == -1)
49+
{
50+
nextIndex = text.Length;
51+
}
52+
53+
inputSimulator.Keyboard.TextEntry(text.Substring(index, nextIndex - index));
54+
index = nextIndex;
55+
}
56+
}
57+
58+
break;
59+
60+
case char c:
61+
inputSimulator.Keyboard.TextEntry(c);
62+
break;
63+
64+
case VirtualKey virtualKey:
65+
66+
case null:
67+
throw new ArgumentNullException(nameof(keys));
68+
69+
default:
70+
throw new ArgumentException($"Unexpected type encountered: {key.GetType()}", nameof(keys));
71+
}
72+
}
73+
}
74+
finally
75+
{
76+
if (foregroundWindow != IntPtr.Zero)
77+
{
78+
SetForegroundWindow(foregroundWindow);
79+
}
80+
}
81+
82+
_visualStudioInstance.WaitForApplicationIdle();
83+
}
84+
85+
private static bool AttachThreadInput(uint idAttach, uint idAttachTo)
86+
{
87+
var success = NativeMethods.AttachThreadInput(idAttach, idAttachTo, true);
88+
if (!success)
89+
{
90+
var hresult = Marshal.GetHRForLastWin32Error();
91+
Marshal.ThrowExceptionForHR(hresult);
92+
}
93+
94+
return success;
95+
}
96+
97+
private static bool DetachThreadInput(uint idAttach, uint idAttachTo)
98+
{
99+
var success = NativeMethods.AttachThreadInput(idAttach, idAttachTo, false);
100+
if (!success)
101+
{
102+
var hresult = Marshal.GetHRForLastWin32Error();
103+
Marshal.ThrowExceptionForHR(hresult);
104+
}
105+
106+
return success;
107+
}
108+
109+
private static IntPtr GetForegroundWindow()
110+
{
111+
// Attempt to get the foreground window in a loop, as the NativeMethods function can return IntPtr.Zero
112+
// in certain circumstances, such as when a window is losing activation.
113+
var foregroundWindow = IntPtr.Zero;
114+
115+
do
116+
{
117+
foregroundWindow = NativeMethods.GetForegroundWindow();
118+
}
119+
while (foregroundWindow == IntPtr.Zero);
120+
121+
return foregroundWindow;
122+
}
123+
124+
private static void SetForegroundWindow(IntPtr window, bool skipAttachingThread = false)
125+
{
126+
var foregroundWindow = GetForegroundWindow();
127+
128+
if (window == foregroundWindow)
129+
{
130+
return;
131+
}
132+
133+
var activeThreadId = NativeMethods.GetWindowThreadProcessId(foregroundWindow, IntPtr.Zero);
134+
var currentThreadId = NativeMethods.GetCurrentThreadId();
135+
136+
var threadInputsAttached = false;
137+
138+
try
139+
{
140+
// No need to re-attach threads in case when VS initializaed an UI thread for a debugged application.
141+
if (!skipAttachingThread)
142+
{
143+
// Attach the thread inputs so that 'SetActiveWindow' and 'SetFocus' work
144+
threadInputsAttached = AttachThreadInput(currentThreadId, activeThreadId);
145+
}
146+
147+
// Make the window a top-most window so it will appear above any existing top-most windows
148+
NativeMethods.SetWindowPos(window, (IntPtr)NativeMethods.HWND_TOPMOST, 0, 0, 0, 0, NativeMethods.SWP_NOSIZE | NativeMethods.SWP_NOMOVE);
149+
150+
// Move the window into the foreground as it may not have been achieved by the 'SetWindowPos' call
151+
var success = NativeMethods.SetForegroundWindow(window);
152+
if (!success)
153+
{
154+
throw new InvalidOperationException("Setting the foreground window failed.");
155+
}
156+
157+
// Ensure the window is 'Active' as it may not have been achieved by 'SetForegroundWindow'
158+
NativeMethods.SetActiveWindow(window);
159+
160+
// Give the window the keyboard focus as it may not have been achieved by 'SetActiveWindow'
161+
NativeMethods.SetFocus(window);
162+
163+
// Remove the 'Top-Most' qualification from the window
164+
NativeMethods.SetWindowPos(window, (IntPtr)NativeMethods.HWND_NOTOPMOST, 0, 0, 0, 0, NativeMethods.SWP_NOSIZE | NativeMethods.SWP_NOMOVE);
165+
}
166+
finally
167+
{
168+
if (threadInputsAttached)
169+
{
170+
// Finally, detach the thread inputs from eachother
171+
DetachThreadInput(currentThreadId, activeThreadId);
172+
}
173+
}
174+
}
175+
}
176+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
2+
// Licensed under the Apache License, Version 2.0. See LICENSE.txt in the project root for license information.
3+
4+
namespace Tvl.VisualStudio.MouseFastScroll.IntegrationTests
5+
{
6+
internal abstract class TextViewWindow_InProc : InProcComponent
7+
{
8+
}
9+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
2+
// Licensed under the Apache License, Version 2.0. See LICENSE.txt in the project root for license information.
3+
4+
namespace Tvl.VisualStudio.MouseFastScroll.IntegrationTests
5+
{
6+
public abstract class TextViewWindow_OutOfProc : OutOfProcComponent
7+
{
8+
protected TextViewWindow_OutOfProc(VisualStudioInstance visualStudioInstance)
9+
: base(visualStudioInstance)
10+
{
11+
TextViewWindowInProc = CreateInProcComponent(visualStudioInstance);
12+
}
13+
14+
internal TextViewWindow_InProc TextViewWindowInProc
15+
{
16+
get;
17+
}
18+
19+
internal abstract TextViewWindow_InProc CreateInProcComponent(VisualStudioInstance visualStudioInstance);
20+
}
21+
}

Tvl.VisualStudio.MouseFastScroll.IntegrationTests/TrivialIntegrationTest.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
namespace Tvl.VisualStudio.MouseFastScroll.IntegrationTests
55
{
66
using System;
7+
using System.Threading;
78
using Xunit;
9+
using vsSaveChanges = EnvDTE.vsSaveChanges;
810

911
public abstract class TrivialIntegrationTest : AbstractIntegrationTest
1012
{
@@ -27,6 +29,14 @@ public void TestOpenAndCloseIDE()
2729
Assert.Equal(expectedVersion.ToString(), currentVersion);
2830
}
2931

32+
[Fact]
33+
public void OpenDocumentAndType()
34+
{
35+
var window = VisualStudioInstance.RetryRpcCall(() => VisualStudio.Dte.ItemOperations.NewFile(Name: Guid.NewGuid() + ".txt"));
36+
VisualStudio.Editor.SendKeys(Guid.NewGuid().ToString() + "\n" + Guid.NewGuid().ToString());
37+
VisualStudioInstance.RetryRpcCall(() => window.Close(vsSaveChanges.vsSaveChangesNo));
38+
}
39+
3040
[VersionTrait(typeof(VS2012))]
3141
public sealed class VS2012 : TrivialIntegrationTest
3242
{

Tvl.VisualStudio.MouseFastScroll.IntegrationTests/Tvl.VisualStudio.MouseFastScroll.IntegrationTests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
</ItemGroup>
2121

2222
<ItemGroup>
23+
<PackageReference Include="InputSimulatorPlus" Version="1.0.6" />
2324
<PackageReference Include="VSSDK.ComponentModelHost.11" Version="11.0.4" />
2425
<PackageReference Include="VSSDK.DTE.8" Version="8.0.4" />
2526
<PackageReference Include="VSSDK.Settings.11" Version="11.0.4" />

Tvl.VisualStudio.MouseFastScroll.IntegrationTests/VisualStudioInstance.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ public VisualStudioInstance(Process hostProcess, DTE dte, Version version, Immut
4444
// we start executing any actual code.
4545
_inProc.WaitForSystemIdle();
4646

47+
SendKeys = new SendKeys(this);
48+
Editor = new Editor_OutOfProc(this);
49+
4750
// Ensure we are in a known 'good' state by cleaning up anything changed by the previous instance
4851
CleanUp();
4952
}
@@ -80,6 +83,16 @@ public string InstallationPath
8083
get;
8184
}
8285

86+
public SendKeys SendKeys
87+
{
88+
get;
89+
}
90+
91+
public Editor_OutOfProc Editor
92+
{
93+
get;
94+
}
95+
8396
public int ErrorListErrorCount
8497
=> _inProc.GetErrorListErrorCount();
8598

0 commit comments

Comments
 (0)