diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/AbstractIntegrationTest.cs b/src/VisualStudio/IntegrationTest/New.IntegrationTests/AbstractIntegrationTest.cs index 639044fdc9197..57afa5e9fc765 100644 --- a/src/VisualStudio/IntegrationTest/New.IntegrationTests/AbstractIntegrationTest.cs +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/AbstractIntegrationTest.cs @@ -96,6 +96,9 @@ protected CancellationToken HangMitigatingCancellationToken public virtual async Task InitializeAsync() { TestServices = await CreateTestServicesAsync(); + + await TestServices.StateReset.ResetGlobalOptionsAsync(HangMitigatingCancellationToken); + await TestServices.StateReset.ResetHostSettingsAsync(HangMitigatingCancellationToken); } /// diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/EditorInProcess.cs b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/EditorInProcess.cs index bedab5b9b22f7..a661d4befffa3 100644 --- a/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/EditorInProcess.cs +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/EditorInProcess.cs @@ -4,18 +4,25 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; +using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; using Microsoft; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.Editor.Implementation.Suggestions; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.TestHooks; +using Microsoft.CodeAnalysis.UnitTests; using Microsoft.VisualStudio; using Microsoft.VisualStudio.Editor; using Microsoft.VisualStudio.IntegrationTest.Utilities; +using Microsoft.VisualStudio.IntegrationTest.Utilities.Input; using Microsoft.VisualStudio.Language.Intellisense; using Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion; using Microsoft.VisualStudio.Shell.Interop; @@ -23,7 +30,10 @@ using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.TextManager.Interop; using Microsoft.VisualStudio.Utilities; +using Roslyn.Utilities; +using IObjectWithSite = Microsoft.VisualStudio.OLE.Interop.IObjectWithSite; using IOleCommandTarget = Microsoft.VisualStudio.OLE.Interop.IOleCommandTarget; +using IOleServiceProvider = Microsoft.VisualStudio.OLE.Interop.IServiceProvider; using OLECMDEXECOPT = Microsoft.VisualStudio.OLE.Interop.OLECMDEXECOPT; namespace Roslyn.VisualStudio.IntegrationTests.InProcess @@ -132,6 +142,245 @@ public async Task SetUseSuggestionModeAsync(bool value, CancellationToken cancel } } + #region Navigation bars + + public async Task ExpandProjectNavigationBarAsync(CancellationToken cancellationToken) + { + await ExpandNavigationBarAsync(index: 0, cancellationToken); + } + + public async Task ExpandTypeNavigationBarAsync(CancellationToken cancellationToken) + { + await ExpandNavigationBarAsync(index: 1, cancellationToken); + } + + public async Task ExpandMemberNavigationBarAsync(CancellationToken cancellationToken) + { + await ExpandNavigationBarAsync(index: 2, cancellationToken); + } + + public async Task ExpandNavigationBarAsync(int index, CancellationToken cancellationToken) + { + await TestServices.Workspace.WaitForAsyncOperationsAsync(FeatureAttribute.NavigationBar, cancellationToken); + + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var view = await GetActiveTextViewAsync(cancellationToken); + var combobox = (await GetNavigationBarComboBoxesAsync(view, cancellationToken))[index]; + FocusManager.SetFocusedElement(FocusManager.GetFocusScope(combobox), combobox); + combobox.IsDropDownOpen = true; + } + + public async Task> GetProjectNavigationBarItemsAsync(CancellationToken cancellationToken) + { + return await GetNavigationBarItemsAsync(index: 0, cancellationToken); + } + + public async Task> GetTypeNavigationBarItemsAsync(CancellationToken cancellationToken) + { + return await GetNavigationBarItemsAsync(index: 1, cancellationToken); + } + + public async Task> GetMemberNavigationBarItemsAsync(CancellationToken cancellationToken) + { + return await GetNavigationBarItemsAsync(index: 2, cancellationToken); + } + + public async Task> GetNavigationBarItemsAsync(int index, CancellationToken cancellationToken) + { + await TestServices.Workspace.WaitForAsyncOperationsAsync(FeatureAttribute.NavigationBar, cancellationToken); + + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var view = await GetActiveTextViewAsync(cancellationToken); + var combobox = (await GetNavigationBarComboBoxesAsync(view, cancellationToken))[index]; + return combobox.Items.OfType().SelectAsArray(i => $"{i}"); + } + + public async Task GetProjectNavigationBarSelectionAsync(CancellationToken cancellationToken) + { + return await GetNavigationBarSelectionAsync(index: 0, cancellationToken); + } + + public async Task GetTypeNavigationBarSelectionAsync(CancellationToken cancellationToken) + { + return await GetNavigationBarSelectionAsync(index: 1, cancellationToken); + } + + public async Task GetMemberNavigationBarSelectionAsync(CancellationToken cancellationToken) + { + return await GetNavigationBarSelectionAsync(index: 2, cancellationToken); + } + + public async Task GetNavigationBarSelectionAsync(int index, CancellationToken cancellationToken) + { + await TestServices.Workspace.WaitForAsyncOperationsAsync(FeatureAttribute.NavigationBar, cancellationToken); + + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var view = await GetActiveTextViewAsync(cancellationToken); + var combobox = (await GetNavigationBarComboBoxesAsync(view, cancellationToken))[index]; + return combobox.SelectedItem?.ToString(); + } + + public async Task SelectProjectNavigationBarItemAsync(string item, CancellationToken cancellationToken) + { + await SelectNavigationBarItemAsync(index: 0, item, cancellationToken); + } + + public async Task SelectTypeNavigationBarItemAsync(string item, CancellationToken cancellationToken) + { + await SelectNavigationBarItemAsync(index: 1, item, cancellationToken); + } + + public async Task SelectMemberNavigationBarItemAsync(string item, CancellationToken cancellationToken) + { + await SelectNavigationBarItemAsync(index: 2, item, cancellationToken); + } + + public async Task SelectNavigationBarItemAsync(int index, string item, CancellationToken cancellationToken) + { + await TestServices.Workspace.WaitForAsyncOperationsAsync(FeatureAttribute.NavigationBar, cancellationToken); + + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var itemIndex = await GetNavigationBarItemIndexAsync(index, item, cancellationToken); + if (itemIndex < 0) + { + throw new ArgumentException($"Could not find '{item}' in combobox"); + } + + await ExpandNavigationBarAsync(index, cancellationToken); + await TestServices.Input.SendAsync(VirtualKey.Home); + for (var i = 0; i < itemIndex; i++) + { + await TestServices.Input.SendAsync(VirtualKey.Down); + } + + await TestServices.Input.SendAsync(VirtualKey.Enter); + + // Navigation and/or code generation following selection is tracked under FeatureAttribute.NavigationBar + await TestServices.Workspace.WaitForAsyncOperationsAsync(FeatureAttribute.NavigationBar, cancellationToken); + } + + public async Task GetNavigationBarItemIndexAsync(int index, string item, CancellationToken cancellationToken) + { + var items = await GetNavigationBarItemsAsync(index, cancellationToken); + return items.IndexOf(item); + } + + public async Task IsNavigationBarEnabledAsync(CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var view = await GetActiveTextViewAsync(cancellationToken); + return (await GetNavigationBarMarginAsync(view, cancellationToken)) is not null; + } + + private async Task> GetNavigationBarComboBoxesAsync(IWpfTextView textView, CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var margin = await GetNavigationBarMarginAsync(textView, cancellationToken); + return margin.GetFieldValue>("_combos"); + } + + private async Task GetNavigationBarMarginAsync(IWpfTextView textView, CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var editorAdaptersFactoryService = await GetComponentModelServiceAsync(cancellationToken); + var viewAdapter = editorAdaptersFactoryService.GetViewAdapter(textView); + Assumes.Present(viewAdapter); + + // Make sure we have the top pane + // + // The docs are wrong. When a secondary view exists, it is the secondary view which is on top. The primary + // view is only on top when there is no secondary view. + var codeWindow = TryGetCodeWindow(viewAdapter); + Assumes.Present(codeWindow); + + if (ErrorHandler.Succeeded(codeWindow.GetSecondaryView(out var secondaryViewAdapter))) + { + viewAdapter = secondaryViewAdapter; + } + + var textViewHost = editorAdaptersFactoryService.GetWpfTextViewHost(viewAdapter); + Assumes.Present(textViewHost); + + var dropDownMargin = textViewHost.GetTextViewMargin("DropDownMargin"); + if (dropDownMargin != null) + { + return ((Decorator)dropDownMargin.VisualElement).Child; + } + + return null; + + static IVsCodeWindow? TryGetCodeWindow(IVsTextView textView) + { + if (textView is not IObjectWithSite objectWithSite) + { + return null; + } + + var riid = typeof(IOleServiceProvider).GUID; + objectWithSite.GetSite(ref riid, out var ppvSite); + if (ppvSite == IntPtr.Zero) + { + return null; + } + + IOleServiceProvider? oleServiceProvider = null; + try + { + oleServiceProvider = Marshal.GetObjectForIUnknown(ppvSite) as IOleServiceProvider; + } + finally + { + Marshal.Release(ppvSite); + } + + if (oleServiceProvider == null) + { + return null; + } + + var guidService = typeof(SVsWindowFrame).GUID; + riid = typeof(IVsWindowFrame).GUID; + if (ErrorHandler.Failed(oleServiceProvider.QueryService(ref guidService, ref riid, out var ppvObject)) || ppvObject == IntPtr.Zero) + { + return null; + } + + IVsWindowFrame? frame = null; + try + { + frame = (IVsWindowFrame)Marshal.GetObjectForIUnknown(ppvObject); + } + finally + { + Marshal.Release(ppvObject); + } + + riid = typeof(IVsCodeWindow).GUID; + if (ErrorHandler.Failed(frame.QueryViewInterface(ref riid, out ppvObject)) || ppvObject == IntPtr.Zero) + { + return null; + } + + try + { + return Marshal.GetObjectForIUnknown(ppvObject) as IVsCodeWindow; + } + finally + { + Marshal.Release(ppvObject); + } + } + } + + #endregion + public async Task DismissLightBulbSessionAsync(CancellationToken cancellationToken) { await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/EditorVerifierInProcess.cs b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/EditorVerifierInProcess.cs index 0c39b6fdfe3d4..c24842e19af71 100644 --- a/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/EditorVerifierInProcess.cs +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/EditorVerifierInProcess.cs @@ -12,6 +12,7 @@ using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.VisualStudio.IntegrationTest.Utilities; using Roslyn.Utilities; +using Xunit; namespace Roslyn.VisualStudio.IntegrationTests.InProcess { @@ -22,6 +23,124 @@ public EditorVerifierInProcess(TestServices testServices) { } + public async Task CurrentLineTextAsync( + string expectedText, + bool assertCaretPosition = false, + bool trimWhitespace = true, + CancellationToken cancellationToken = default) + { + if (assertCaretPosition) + { + await CurrentLineTextAndAssertCaretPositionAsync(expectedText, trimWhitespace, cancellationToken); + } + else + { + var view = await TestServices.Editor.GetActiveTextViewAsync(cancellationToken); + var lineText = view.Caret.Position.BufferPosition.GetContainingLine().GetText(); + + if (trimWhitespace) + { + lineText = lineText.Trim(); + } + + Assert.Equal(expectedText, lineText); + } + } + + private async Task CurrentLineTextAndAssertCaretPositionAsync( + string expectedText, + bool trimWhitespace, + CancellationToken cancellationToken) + { + var expectedCaretIndex = expectedText.IndexOf("$$"); + if (expectedCaretIndex < 0) + { + throw new ArgumentException("Expected caret position to be specified with $$", nameof(expectedText)); + } + + var expectedCaretMarkupEndIndex = expectedCaretIndex + "$$".Length; + + var expectedTextBeforeCaret = expectedText.Substring(0, expectedCaretIndex); + var expectedTextAfterCaret = expectedText.Substring(expectedCaretMarkupEndIndex); + + var view = await TestServices.Editor.GetActiveTextViewAsync(cancellationToken); + var bufferPosition = view.Caret.Position.BufferPosition; + var line = bufferPosition.GetContainingLine(); + var lineText = line.GetText(); + var lineTextBeforeCaret = lineText[..(bufferPosition.Position - line.Start)]; + var lineTextAfterCaret = lineText[(bufferPosition.Position - line.Start)..]; + + // Asserts below perform separate verifications of text before and after the caret. + // Depending on the position of the caret, if trimWhitespace, we trim beginning, end or both sides. + if (trimWhitespace) + { + if (expectedCaretIndex == 0) + { + lineText = lineText.TrimEnd(); + lineTextAfterCaret = lineTextAfterCaret.TrimEnd(); + } + else if (expectedCaretMarkupEndIndex == expectedText.Length) + { + lineText = lineText.TrimStart(); + lineTextBeforeCaret = lineTextBeforeCaret.TrimStart(); + } + else + { + lineText = lineText.Trim(); + lineTextBeforeCaret = lineTextBeforeCaret.TrimStart(); + lineTextAfterCaret = lineTextAfterCaret.TrimEnd(); + } + } + + Assert.Equal(expectedTextBeforeCaret, lineTextBeforeCaret); + Assert.Equal(expectedTextAfterCaret, lineTextAfterCaret); + Assert.Equal(expectedTextBeforeCaret.Length + expectedTextAfterCaret.Length, lineText.Length); + } + + public async Task TextContainsAsync( + string expectedText, + bool assertCaretPosition = false, + CancellationToken cancellationToken = default) + { + if (assertCaretPosition) + { + await TextContainsAndAssertCaretPositionAsync(expectedText, cancellationToken); + } + else + { + var view = await TestServices.Editor.GetActiveTextViewAsync(cancellationToken); + var editorText = view.TextSnapshot.GetText(); + Assert.Contains(expectedText, editorText); + } + } + + private async Task TextContainsAndAssertCaretPositionAsync( + string expectedText, + CancellationToken cancellationToken) + { + var caretStartIndex = expectedText.IndexOf("$$"); + if (caretStartIndex < 0) + { + throw new ArgumentException("Expected caret position to be specified with $$", nameof(expectedText)); + } + + var caretEndIndex = caretStartIndex + "$$".Length; + + var expectedTextBeforeCaret = expectedText[..caretStartIndex]; + var expectedTextAfterCaret = expectedText[caretEndIndex..]; + + var expectedTextWithoutCaret = expectedTextBeforeCaret + expectedTextAfterCaret; + + var view = await TestServices.Editor.GetActiveTextViewAsync(cancellationToken); + var editorText = view.TextSnapshot.GetText(); + Assert.Contains(expectedTextWithoutCaret, editorText); + + var index = editorText.IndexOf(expectedTextWithoutCaret); + + var caretPosition = await TestServices.Editor.GetCaretPositionAsync(cancellationToken); + Assert.Equal(caretStartIndex + index, caretPosition); + } + public async Task CodeActionAsync( string expectedItem, bool applyFix = false, @@ -113,5 +232,10 @@ public async Task CodeActionsNotShowingAsync(CancellationToken cancellationToken throw new InvalidOperationException("Expected no light bulb session, but one was found."); } } + + public async Task CaretPositionAsync(int expectedCaretPosition, CancellationToken cancellationToken) + { + Assert.Equal(expectedCaretPosition, await TestServices.Editor.GetCaretPositionAsync(cancellationToken)); + } } } diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/InputInProcess.cs b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/InputInProcess.cs new file mode 100644 index 0000000000000..d1ce153673d90 --- /dev/null +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/InputInProcess.cs @@ -0,0 +1,56 @@ +// 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.Threading; +using System.Threading.Tasks; +using Microsoft.VisualStudio.IntegrationTest.Utilities.Input; +using Microsoft.VisualStudio.Threading; + +namespace Roslyn.VisualStudio.IntegrationTests.InProcess +{ + internal class InputInProcess : InProcComponent + { + public InputInProcess(TestServices testServices) + : base(testServices) + { + SendKeys = new(testServices); + } + + private SendKeysImpl SendKeys { get; } + + internal async Task SendAsync(params object[] keys) + { + // AbstractSendKeys runs synchronously, so switch to a background thread before the call + await TaskScheduler.Default; + + SendKeys.Send(keys); + } + + private class SendKeysImpl : AbstractSendKeys + { + public SendKeysImpl(TestServices testServices) + { + TestServices = testServices; + } + + public TestServices TestServices { get; } + + protected override void ActivateMainWindow() + { + TestServices.JoinableTaskFactory.Run(async () => + { + await TestServices.Editor.ActivateAsync(CancellationToken.None); + }); + } + + protected override void WaitForApplicationIdle(CancellationToken cancellationToken) + { + TestServices.JoinableTaskFactory.Run(async () => + { + await WaitForApplicationIdleAsync(cancellationToken); + }); + } + } + } +} diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/StateResetInProcess.cs b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/StateResetInProcess.cs new file mode 100644 index 0000000000000..41f66aa43ea22 --- /dev/null +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/StateResetInProcess.cs @@ -0,0 +1,38 @@ +// 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.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Editor.Options; +using Microsoft.CodeAnalysis.Options; +using Microsoft.VisualStudio.LanguageServices; + +namespace Roslyn.VisualStudio.IntegrationTests.InProcess +{ + internal class StateResetInProcess : InProcComponent + { + public StateResetInProcess(TestServices testServices) + : base(testServices) + { + } + + public async Task ResetGlobalOptionsAsync(CancellationToken cancellationToken) + { + var globalOptions = await GetComponentModelServiceAsync(cancellationToken); + globalOptions.SetGlobalOption(new OptionKey(NavigationBarViewOptions.ShowNavigationBar, LanguageNames.CSharp), true); + } + + public Task ResetHostSettingsAsync(CancellationToken cancellationToken) + { + _ = cancellationToken; + + return Task.CompletedTask; + } + } +} diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/TestServices.cs b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/TestServices.cs index 53283f3305890..d63a1383bc618 100644 --- a/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/TestServices.cs +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/TestServices.cs @@ -16,8 +16,10 @@ protected TestServices(JoinableTaskFactory joinableTaskFactory) Editor = new EditorInProcess(this); EditorVerifier = new EditorVerifierInProcess(this); ErrorList = new ErrorListInProcess(this); + Input = new InputInProcess(this); SolutionExplorer = new SolutionExplorerInProcess(this); SolutionVerifier = new SolutionVerifierInProcess(this); + StateReset = new StateResetInProcess(this); Workspace = new WorkspaceInProcess(this); } @@ -29,10 +31,14 @@ protected TestServices(JoinableTaskFactory joinableTaskFactory) public ErrorListInProcess ErrorList { get; } + public InputInProcess Input { get; } + public SolutionExplorerInProcess SolutionExplorer { get; } public SolutionVerifierInProcess SolutionVerifier { get; } + public StateResetInProcess StateReset { get; } + public WorkspaceInProcess Workspace { get; } internal static async Task CreateAsync(JoinableTaskFactory joinableTaskFactory) diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/Microsoft.VisualStudio.LanguageServices.New.IntegrationTests.csproj b/src/VisualStudio/IntegrationTest/New.IntegrationTests/Microsoft.VisualStudio.LanguageServices.New.IntegrationTests.csproj index f5b62145f9964..11338cab1c03a 100644 --- a/src/VisualStudio/IntegrationTest/New.IntegrationTests/Microsoft.VisualStudio.LanguageServices.New.IntegrationTests.csproj +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/Microsoft.VisualStudio.LanguageServices.New.IntegrationTests.csproj @@ -7,8 +7,14 @@ net472 - - + + + + + + + + @@ -16,6 +22,7 @@ + diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/VisualBasic/BasicAddMissingReference.cs b/src/VisualStudio/IntegrationTest/New.IntegrationTests/VisualBasic/BasicAddMissingReference.cs index 126faa390ddcd..ffbd1e6bbce4a 100644 --- a/src/VisualStudio/IntegrationTest/New.IntegrationTests/VisualBasic/BasicAddMissingReference.cs +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/VisualBasic/BasicAddMissingReference.cs @@ -7,10 +7,11 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.VisualStudio.IntegrationTest.Utilities; +using Roslyn.VisualStudio.IntegrationTests; using Roslyn.VisualStudio.IntegrationTests.InProcess; using Xunit; -namespace Roslyn.VisualStudio.IntegrationTests.VisualBasic +namespace Roslyn.VisualStudio.NewIntegrationTests.VisualBasic { public class BasicAddMissingReference : AbstractEditorTest { diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/VisualBasic/BasicNavigationBar.cs b/src/VisualStudio/IntegrationTest/New.IntegrationTests/VisualBasic/BasicNavigationBar.cs new file mode 100644 index 0000000000000..541d5cf06da61 --- /dev/null +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/VisualBasic/BasicNavigationBar.cs @@ -0,0 +1,64 @@ +// 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.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Test.Utilities; +using Roslyn.VisualStudio.IntegrationTests; +using Xunit; + +namespace Roslyn.VisualStudio.NewIntegrationTests.VisualBasic +{ + public class BasicNavigationBar : AbstractEditorTest + { + private const string TestSource = @" +Class C + Public WithEvents Domain As AppDomain + Public Sub $$Goo() + End Sub +End Class + +Structure S + Public Property A As Integer + Public Property B As Integer +End Structure"; + + public BasicNavigationBar() + : base(nameof(BasicNavigationBar)) + { + } + + protected override string LanguageName => LanguageNames.VisualBasic; + + [IdeFact] + [Trait(Traits.Feature, Traits.Features.NavigationBar)] + public async Task CodeSpit() + { + await SetUpEditorAsync(TestSource, HangMitigatingCancellationToken); + + await TestServices.Editor.PlaceCaretAsync("C", charsOffset: 1, HangMitigatingCancellationToken); + await VerifyLeftSelectedAsync("C", HangMitigatingCancellationToken); + await TestServices.Editor.ExpandMemberNavigationBarAsync(HangMitigatingCancellationToken); + Assert.Equal(new[] { "New", "Finalize", "Goo" }, await TestServices.Editor.GetMemberNavigationBarItemsAsync(HangMitigatingCancellationToken)); + await TestServices.Editor.SelectMemberNavigationBarItemAsync("New", HangMitigatingCancellationToken); + await TestServices.EditorVerifier.TextContainsAsync(@" + Public Sub New() + + End Sub", cancellationToken: HangMitigatingCancellationToken); + await TestServices.EditorVerifier.CaretPositionAsync(78, HangMitigatingCancellationToken); // Caret is between New() and End Sub() in virtual whitespace + await TestServices.EditorVerifier.CurrentLineTextAsync("$$", assertCaretPosition: true, cancellationToken: HangMitigatingCancellationToken); + } + + private async Task VerifyLeftSelectedAsync(string expected, CancellationToken cancellationToken) + { + Assert.Equal(expected, await TestServices.Editor.GetTypeNavigationBarSelectionAsync(cancellationToken)); + } + + private async Task VerifyRightSelectedAsync(string expected, CancellationToken cancellationToken) + { + Assert.Equal(expected, await TestServices.Editor.GetMemberNavigationBarSelectionAsync(cancellationToken)); + } + } +} diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/XunitAssemblyInfo.cs b/src/VisualStudio/IntegrationTest/New.IntegrationTests/XunitAssemblyInfo.cs new file mode 100644 index 0000000000000..587b233fc4ea4 --- /dev/null +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/XunitAssemblyInfo.cs @@ -0,0 +1,7 @@ +// 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 Xunit; + +[assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly, DisableTestParallelization = true)]