-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Add Integration tests for Inheritance margin #62047
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Cosifne
merged 12 commits into
dotnet:main
from
Cosifne:dev/shech/InheritanceMarginIntegrationTest
Jun 27, 2022
Merged
Changes from 6 commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
1c20e3c
Add first Integration Test for Inheritance Margin
Cosifne aa3e78e
Add second test to test multiple items on same line
Cosifne 1b06960
Add the third test to navigate to metadata
Cosifne cf0cb48
Add a test to navigate across projects
Cosifne 88a9977
Polishing
Cosifne 551e5ea
Add comment
Cosifne b4d31aa
Simplify workspace kind verification
sharwell 06cf7da
Simplify inheritance margin test interaction
sharwell 0ac2dc2
Convert input handling to InputSimulatorPlus
sharwell c44ca85
Use mouse for true input testing
sharwell 30ba06f
Use VirtualKeyCode.RETURN
Cosifne f44920c
Await the navigation operation of inheritance margin
Cosifne File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
26 changes: 26 additions & 0 deletions
26
src/VisualStudio/Core/Def/InheritanceMargin/InheritanceMarginCanvas.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| // 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.Tasks; | ||
| using System.Windows; | ||
| using System.Windows.Controls; | ||
| using Microsoft.VisualStudio.LanguageServices.Implementation.InheritanceMargin.MarginGlyph; | ||
|
|
||
| namespace Microsoft.VisualStudio.LanguageServices.InheritanceMargin | ||
| { | ||
| internal class InheritanceMarginCanvas : Canvas | ||
| { | ||
| public event EventHandler<(InheritanceMarginGlyph? glyphAdded, InheritanceMarginGlyph? glyphRemoved)>? OnGlyphsChanged; | ||
|
|
||
| protected override void OnVisualChildrenChanged(DependencyObject visualAdded, DependencyObject visualRemoved) | ||
| { | ||
| base.OnVisualChildrenChanged(visualAdded, visualRemoved); | ||
| OnGlyphsChanged?.Invoke(this, (visualAdded as InheritanceMarginGlyph, visualRemoved as InheritanceMarginGlyph)); | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
162 changes: 162 additions & 0 deletions
162
src/VisualStudio/IntegrationTest/New.IntegrationTests/CSharp/CSharpInheritanceMarginTests.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,162 @@ | ||
| // 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.Tasks; | ||
| using EnvDTE; | ||
| using Microsoft.CodeAnalysis; | ||
| using Microsoft.VisualStudio.Extensibility.Testing; | ||
| using Microsoft.VisualStudio.IntegrationTest.Utilities; | ||
| using Microsoft.VisualStudio.IntegrationTest.Utilities.Input; | ||
| using Microsoft.VisualStudio.LanguageServices.Implementation.InheritanceMargin; | ||
| using Microsoft.VisualStudio.Text.Tagging; | ||
| using Roslyn.Test.Utilities; | ||
| using Roslyn.VisualStudio.IntegrationTests; | ||
| using Roslyn.VisualStudio.IntegrationTests.InProcess; | ||
| using Xunit; | ||
|
|
||
| namespace Roslyn.VisualStudio.NewIntegrationTests.CSharp | ||
| { | ||
| public class CSharpInheritanceMarginTests : AbstractEditorTest | ||
| { | ||
|
|
||
| protected override string LanguageName => LanguageNames.CSharp; | ||
|
|
||
| public CSharpInheritanceMarginTests() | ||
| : base(nameof(CSharpInheritanceMarginTests)) | ||
| { | ||
| } | ||
|
|
||
| [IdeFact] | ||
| public async Task TestNavigateInSource() | ||
| { | ||
| var project = ProjectName; | ||
| await TestServices.InheritanceMargin.EnableOptionsAsync(LanguageName, cancellationToken: HangMitigatingCancellationToken); | ||
| await TestServices.SolutionExplorer.AddFileAsync(project, "Test.cs", cancellationToken: HangMitigatingCancellationToken); | ||
| await TestServices.SolutionExplorer.OpenFileAsync(project, "Test.cs", HangMitigatingCancellationToken); | ||
|
|
||
| await TestServices.InheritanceMargin.SetTextAndEnsureGlyphsAppearAsync( | ||
| @" | ||
| interface IBar | ||
| { | ||
| } | ||
| class Implementation : IBar | ||
| { | ||
| }", expectedGlyphsNumberInMargin: 2, HangMitigatingCancellationToken); | ||
|
|
||
| await TestServices.InheritanceMargin.ClickTheGlyphOnLine(2, HangMitigatingCancellationToken); | ||
|
|
||
| // Move focus to menu item of 'IBar', the destination is targeting 'class Implementation' | ||
| await TestServices.Input.SendAsync(VirtualKey.Tab); | ||
| // Navigate to the destination | ||
| await TestServices.Input.SendAsync(VirtualKey.Enter); | ||
| await TestServices.EditorVerifier.TextContainsAsync(@"class Implementation$$", assertCaretPosition: true); | ||
| } | ||
|
|
||
| [IdeFact] | ||
| public async Task TestMultipleItemsOnSameLine() | ||
| { | ||
| var project = ProjectName; | ||
| await TestServices.InheritanceMargin.EnableOptionsAsync(LanguageName, cancellationToken: HangMitigatingCancellationToken); | ||
| await TestServices.SolutionExplorer.AddFileAsync(project, "Test.cs", cancellationToken: HangMitigatingCancellationToken); | ||
| await TestServices.SolutionExplorer.OpenFileAsync(project, "Test.cs", HangMitigatingCancellationToken); | ||
|
|
||
| await TestServices.InheritanceMargin.SetTextAndEnsureGlyphsAppearAsync( | ||
| @" | ||
| using System; | ||
| interface IBar | ||
| { | ||
| event EventHandler e1, e2; | ||
| } | ||
| class Implementation : IBar | ||
| { | ||
| public event EventHandler e1, e2; | ||
| }", expectedGlyphsNumberInMargin: 4, HangMitigatingCancellationToken); | ||
|
|
||
| await TestServices.InheritanceMargin.ClickTheGlyphOnLine(5, HangMitigatingCancellationToken); | ||
|
|
||
| // The context menu contains two members, e1 and e2. | ||
| // Move focus to menu item of 'event e1' | ||
| await TestServices.Input.SendAsync(VirtualKey.Tab); | ||
| // Expand the submenu | ||
| await TestServices.Input.SendAsync(VirtualKey.Enter); | ||
| // Navigate to the implemention | ||
| await TestServices.Input.SendAsync(VirtualKey.Enter); | ||
| await TestServices.EditorVerifier.TextContainsAsync(@"public event EventHandler e1$$, e2;", assertCaretPosition: true); | ||
| } | ||
|
|
||
| [IdeFact] | ||
| public async Task TestNavigateToMetadata() | ||
| { | ||
| var project = ProjectName; | ||
| await TestServices.InheritanceMargin.EnableOptionsAsync(LanguageName, cancellationToken: HangMitigatingCancellationToken); | ||
| await TestServices.SolutionExplorer.AddFileAsync(project, "Test.cs", cancellationToken: HangMitigatingCancellationToken); | ||
| await TestServices.SolutionExplorer.OpenFileAsync(project, "Test.cs", HangMitigatingCancellationToken); | ||
|
|
||
| await TestServices.InheritanceMargin.SetTextAndEnsureGlyphsAppearAsync( | ||
| @" | ||
| using System.Collections; | ||
| class Implementation : IEnumerable | ||
| { | ||
| public IEnumerator GetEnumerator() | ||
| { | ||
| throw new NotImplementedException(); | ||
| } | ||
| }", expectedGlyphsNumberInMargin: 2, HangMitigatingCancellationToken); | ||
|
|
||
| await TestServices.InheritanceMargin.ClickTheGlyphOnLine(4, HangMitigatingCancellationToken); | ||
|
|
||
| // Move focus to menu item of 'class Implementation' | ||
| await TestServices.Input.SendAsync(VirtualKey.Tab); | ||
| // Navigate to 'IEnumerable' | ||
| await TestServices.Input.SendAsync(VirtualKey.Enter); | ||
| await TestServices.EditorVerifier.TextContainsAsync(@"public interface IEnumerable$$", assertCaretPosition: true); | ||
| await TestServices.EditorVerifier.VerifyActiveViewIsInMetadataWorkspaceAsync(HangMitigatingCancellationToken); | ||
| } | ||
|
|
||
| [IdeFact] | ||
| public async Task TestNavigateToDifferentProjects() | ||
| { | ||
| await TestServices.InheritanceMargin.EnableOptionsAsync(LanguageNames.CSharp, cancellationToken: HangMitigatingCancellationToken); | ||
| await TestServices.InheritanceMargin.EnableOptionsAsync(LanguageNames.VisualBasic, cancellationToken: HangMitigatingCancellationToken); | ||
|
|
||
| var csharpProjectName = ProjectName; | ||
| var vbProjectName = "TestVBProject"; | ||
| await TestServices.SolutionExplorer.AddProjectAsync( | ||
| vbProjectName, WellKnownProjectTemplates.VisualBasicNetStandardClassLibrary, LanguageNames.VisualBasic, cancellationToken: HangMitigatingCancellationToken); | ||
| await TestServices.SolutionExplorer.AddFileAsync(vbProjectName, "Test.vb", @" | ||
| Namespace MyNs | ||
| Public Interface IBar | ||
| End Interface | ||
| End Namespace"); | ||
|
|
||
| await TestServices.SolutionExplorer.AddFileAsync(csharpProjectName, "Test.cs", cancellationToken: HangMitigatingCancellationToken); | ||
| await TestServices.SolutionExplorer.AddProjectReferenceAsync(csharpProjectName, vbProjectName, cancellationToken: HangMitigatingCancellationToken); | ||
| await TestServices.SolutionExplorer.OpenFileAsync(csharpProjectName, "Test.cs", HangMitigatingCancellationToken); | ||
|
|
||
| await TestServices.InheritanceMargin.SetTextAndEnsureGlyphsAppearAsync( | ||
| @" | ||
| using TestVBProject.MyNs; | ||
| class Implementation : IBar | ||
| { | ||
| }", expectedGlyphsNumberInMargin: 1, HangMitigatingCancellationToken); | ||
|
|
||
| await TestServices.InheritanceMargin.ClickTheGlyphOnLine(4, HangMitigatingCancellationToken); | ||
|
|
||
| // Move focus to menu item of 'class Implementation' | ||
| await TestServices.Input.SendAsync(VirtualKey.Tab); | ||
| // Navigate to 'IBar' | ||
| await TestServices.Input.SendAsync(VirtualKey.Enter); | ||
| await TestServices.EditorVerifier.TextContainsAsync(@"Public Interface IBar$$", assertCaretPosition: true); | ||
| await TestServices.EditorVerifier.VerifyActiveViewIsNotInMetadataWorkspaceAsync(HangMitigatingCancellationToken); | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
137 changes: 137 additions & 0 deletions
137
...VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/InheritanceMarginInProcess.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,137 @@ | ||
| // 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 System.Windows; | ||
| using System.Windows.Controls; | ||
| using System.Windows.Controls.Primitives; | ||
| using System.Windows.Media; | ||
| using Microsoft.CodeAnalysis.Editor.Shared.Options; | ||
| using Microsoft.CodeAnalysis.Options; | ||
| using Microsoft.CodeAnalysis.UnitTests; | ||
| using Microsoft.VisualStudio.CorDebugInterop; | ||
| using Microsoft.VisualStudio.Extensibility.Testing; | ||
| using Microsoft.VisualStudio.LanguageServices.Implementation.InheritanceMargin; | ||
| using Microsoft.VisualStudio.LanguageServices.Implementation.InheritanceMargin.MarginGlyph; | ||
| using Microsoft.VisualStudio.LanguageServices.InheritanceMargin; | ||
| using Microsoft.VisualStudio.Text.Editor; | ||
| using Microsoft.VisualStudio.Text.Tagging; | ||
| using Microsoft.VisualStudio.TextManager.Interop; | ||
| using Roslyn.Test.Utilities; | ||
| using Roslyn.Utilities; | ||
| using Xunit; | ||
|
|
||
| namespace Roslyn.VisualStudio.NewIntegrationTests.InProcess | ||
| { | ||
| [TestService] | ||
| internal partial class InheritanceMarginInProcess | ||
| { | ||
| private const double HeightAndWidthOfTheGlyph = InheritanceMarginViewMargin.HeightAndWidthOfMargin; | ||
| private const string MarginName = nameof(InheritanceMarginViewMargin); | ||
|
|
||
| public async Task EnableOptionsAsync(string languageName, CancellationToken cancellationToken) | ||
| { | ||
| var optionService = await GetComponentModelServiceAsync<IGlobalOptionService>(cancellationToken).ConfigureAwait(false); | ||
| var showInheritanceMargin = optionService.GetOption(FeatureOnOffOptions.ShowInheritanceMargin, languageName); | ||
| var combinedWithIndicatorMargin = optionService.GetOption(FeatureOnOffOptions.InheritanceMarginCombinedWithIndicatorMargin); | ||
|
|
||
| if (showInheritanceMargin != true) | ||
| { | ||
| optionService.SetGlobalOption(new OptionKey(FeatureOnOffOptions.ShowInheritanceMargin, languageName), true); | ||
| } | ||
|
|
||
| if (combinedWithIndicatorMargin) | ||
| { | ||
| // Glyphs in Indicator margin are owned by editor, and we don't know when the glyphs would be added/removed. | ||
| optionService.SetGlobalOption(new OptionKey(FeatureOnOffOptions.InheritanceMarginCombinedWithIndicatorMargin), false); | ||
| } | ||
| } | ||
|
|
||
| public async Task SetTextAndEnsureGlyphsAppearAsync(string text, int expectedGlyphsNumberInMargin, CancellationToken cancellationToken) | ||
| { | ||
| var margin = await GetTextViewMarginAsync(cancellationToken); | ||
| var marginCanvas = (InheritanceMarginCanvas)((Grid)margin.VisualElement).Children[0]; | ||
| var taskCompletionSource = new TaskCompletionSource<bool>(); | ||
| using var _ = cancellationToken.Register(() => taskCompletionSource.SetCanceled()); | ||
|
|
||
| try | ||
| { | ||
| marginCanvas.OnGlyphsChanged += OnGlyphChanged; | ||
|
|
||
| await TestServices.Editor.SetTextAsync(text, cancellationToken); | ||
| await taskCompletionSource.Task; | ||
| } | ||
| finally | ||
| { | ||
| marginCanvas.OnGlyphsChanged -= OnGlyphChanged; | ||
| } | ||
|
|
||
| void OnGlyphChanged(object sender, (InheritanceMarginGlyph? glyphAdded, InheritanceMarginGlyph? glyphRemoved) _) | ||
| { | ||
| if (marginCanvas.Children.Count == expectedGlyphsNumberInMargin) | ||
| { | ||
| taskCompletionSource.SetResult(true); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| public async Task ClickTheGlyphOnLine(int lineNumber, CancellationToken cancellationToken) | ||
| { | ||
| await WaitForApplicationIdleAsync(cancellationToken); | ||
| var glyph = await GetTheGlyphOnLineAsync(lineNumber, cancellationToken); | ||
|
|
||
| // Ideally, we should not rely on creating WPF event, and simulate real mouse click to open the context menu. | ||
| glyph.RaiseEvent(new RoutedEventArgs(ButtonBase.ClickEvent)); | ||
| } | ||
|
|
||
| public async Task<InheritanceMarginGlyph> GetTheGlyphOnLineAsync(int lineNumber, CancellationToken cancellationToken) | ||
| { | ||
| await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); | ||
| var activeView = await TestServices.Editor.GetActiveTextViewAsync(cancellationToken); | ||
| var wpfTextViewLine = activeView.TextViewLines[lineNumber - 1]; | ||
| var midOfTheLine = wpfTextViewLine.TextTop + wpfTextViewLine.Height / 2; | ||
| var margin = await GetTextViewMarginAsync(cancellationToken); | ||
|
|
||
| var grid = (Grid)margin.VisualElement; | ||
| // There will be only one Canvas element. | ||
| Assert.True(grid.Children.Count == 1); | ||
| var containingCanvas = (Canvas)((Grid)margin.VisualElement).Children[0]; | ||
|
|
||
| var glyphsOnLine = new List<InheritanceMarginGlyph>(); | ||
| foreach (var glyph in containingCanvas.Children) | ||
| { | ||
| if (glyph is InheritanceMarginGlyph inheritanceMarginGlyph) | ||
| { | ||
| var glyphTop = Canvas.GetTop(inheritanceMarginGlyph); | ||
| var glyphBottom = glyphTop + HeightAndWidthOfTheGlyph; | ||
| if (midOfTheLine > glyphTop && midOfTheLine < glyphBottom) | ||
| { | ||
| glyphsOnLine.Add(inheritanceMarginGlyph); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| if (glyphsOnLine.Count != 1) | ||
| { | ||
| Assert.False(true, $"{glyphsOnLine.Count} glpyhs are found at line: {lineNumber}."); | ||
| } | ||
|
|
||
| return glyphsOnLine[0]; | ||
| } | ||
|
|
||
| private async Task<IWpfTextViewMargin> GetTextViewMarginAsync(CancellationToken cancellationToken) | ||
| { | ||
| await WaitForApplicationIdleAsync(cancellationToken); | ||
| var vsTextManager = await GetRequiredGlobalServiceAsync<SVsTextManager, IVsTextManager>(cancellationToken); | ||
| var vsTextView = await vsTextManager.GetActiveViewAsync(JoinableTaskFactory, cancellationToken); | ||
| var testViewHost = await vsTextView.GetTextViewHostAsync(JoinableTaskFactory, cancellationToken); | ||
| return testViewHost.GetTextViewMargin(MarginName); | ||
| } | ||
| } | ||
| } |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.