diff --git a/src/Razor/test/Microsoft.VisualStudio.Razor.IntegrationTests/CompletionIntegrationTests.cs b/src/Razor/test/Microsoft.VisualStudio.Razor.IntegrationTests/CompletionIntegrationTests.cs index 2c38985a94a..495d10db1c4 100644 --- a/src/Razor/test/Microsoft.VisualStudio.Razor.IntegrationTests/CompletionIntegrationTests.cs +++ b/src/Razor/test/Microsoft.VisualStudio.Razor.IntegrationTests/CompletionIntegrationTests.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See License.txt in the project root for license information. using System; +using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using Roslyn.Test.Utilities; @@ -212,7 +213,8 @@ private void IncrementCount() """, search: "", stringsToType: ["{ENTER}", "{ENTER}", "<", "s", "p", "a"], - commitChar: '>'); + commitChar: '>', + "span"); } [IdeFact] @@ -253,7 +255,7 @@ private void IncrementCount() stringsToType: ["{ENTER}", "{ENTER}", "m", "y", "C", "u", "r"]); } - private async Task VerifyTypeAndCommitCompletionAsync(string input, string output, string search, string[] stringsToType, char? commitChar = null) + private async Task VerifyTypeAndCommitCompletionAsync(string input, string output, string search, string[] stringsToType, char? commitChar = null, string? expectedSelectedItemLabel = null) { await TestServices.SolutionExplorer.AddFileAsync( RazorProjectConstants.BlazorProjectName, @@ -270,7 +272,14 @@ await TestServices.SolutionExplorer.AddFileAsync( TestServices.Input.Send(stringToType); } - await CommitCompletionAndVerifyAsync(output, commitChar); + if (expectedSelectedItemLabel is not null) + { + await CommitCompletionAndVerifyAsync(output, expectedSelectedItemLabel, commitChar); + } + else + { + await CommitCompletionAndVerifyAsync(output, commitChar); + } } [IdeFact] @@ -457,4 +466,41 @@ private async Task CommitCompletionAndVerifyAsync(string expected, char? commitC // tests allow for it as long as the content is correct AssertEx.AssertEqualToleratingWhitespaceDifferences(expected, text); } + + private async Task CommitCompletionAndVerifyAsync(string expected, string expectedSelectedItemLabel, char? commitChar = null) + { + // Actually open completion UI and wait for it have selected item we are interested in + var session = await TestServices.Editor.OpenCompletionSessionAndWaitForItemAsync(TimeSpan.FromSeconds(10), expectedSelectedItemLabel, HangMitigatingCancellationToken); + + Assert.NotNull(session); + if (commitChar.HasValue) + { + // Commit using the specified commit character + session.Commit(commitChar.Value, HangMitigatingCancellationToken); + + // session.Commit call above commits as if the commit character was typed, + // but doesn't actually insert the character into the buffer. + // So we still need to insert the character into the buffer ourselves. + TestServices.Input.Send(commitChar.Value.ToString()); + } + else + { + Assert.True(session.CommitIfUnique(HangMitigatingCancellationToken)); + } + + var textView = await TestServices.Editor.GetActiveTextViewAsync(HangMitigatingCancellationToken); + + var stopwatch = new Stopwatch(); + string text; + while ((text = textView.TextBuffer.CurrentSnapshot.GetText()) != expected && stopwatch.ElapsedMilliseconds < 10000) + { + // Text might get updated *after* completion by something like auto-insert, so wait for the desired text + await Task.Delay(100); + } + + // Snippets may have slight whitespace differences due to line endings. These + // tests allow for it as long as the content is correct + Assert.Equal(expected, text); + } + } diff --git a/src/Razor/test/Microsoft.VisualStudio.Razor.IntegrationTests/InProcess/EditorInProcess_Completion.cs b/src/Razor/test/Microsoft.VisualStudio.Razor.IntegrationTests/InProcess/EditorInProcess_Completion.cs index 838e2eb79e8..10f9aef9634 100644 --- a/src/Razor/test/Microsoft.VisualStudio.Razor.IntegrationTests/InProcess/EditorInProcess_Completion.cs +++ b/src/Razor/test/Microsoft.VisualStudio.Razor.IntegrationTests/InProcess/EditorInProcess_Completion.cs @@ -55,4 +55,43 @@ public async Task DismissCompletionSessionsAsync(CancellationToken cancellationT return session; } + + /// + /// Open completion pop-up window UI and wait for the specified item to be present selected + /// + /// + /// + /// + /// Completion session that has matching selected item, or null otherwise + public async Task OpenCompletionSessionAndWaitForItemAsync(TimeSpan timeOut, string selectedItemLabel, CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + // Returns completion session that might or might not be visible in the IDE + var session = await WaitForCompletionSessionAsync(timeOut, cancellationToken); + + if (session is null) + { + return null; + } + + var textView = await GetActiveTextViewAsync(cancellationToken); + var stopWatch = Stopwatch.StartNew(); + + // Actually open the completion pop-up window and force visible items to be computed or re-computed + session.OpenOrUpdate(new CompletionTrigger(CompletionTriggerReason.Insertion, textView.TextSnapshot), textView.Caret.Position.BufferPosition, cancellationToken); + while (session.GetComputedItems(cancellationToken).SelectedItem?.DisplayText != selectedItemLabel) + { + if (stopWatch.ElapsedMilliseconds >= timeOut.TotalMilliseconds) + { + return null; + } + + await Task.Delay(TimeSpan.FromMilliseconds(100), cancellationToken); + + session.OpenOrUpdate(new CompletionTrigger(CompletionTriggerReason.Insertion, textView.TextSnapshot), textView.Caret.Position.BufferPosition, cancellationToken); + } + + return session; + } }