зеркало из https://github.com/dotnet/fsharp.git
Integration tests for code actions (#15142)
This commit is contained in:
Родитель
8746c3adb4
Коммит
ec122d90ec
|
@ -0,0 +1,103 @@
|
|||
// 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 Microsoft.CodeAnalysis.Testing;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
namespace FSharp.Editor.IntegrationTests;
|
||||
|
||||
public class CodeActionTests : AbstractIntegrationTest
|
||||
{
|
||||
[IdeFact]
|
||||
public async Task UnusedOpenDeclarations()
|
||||
{
|
||||
var template = WellKnownProjectTemplates.FSharpNetCoreClassLibrary;
|
||||
|
||||
var code = """
|
||||
module Library
|
||||
|
||||
open System
|
||||
|
||||
let x = 42
|
||||
""";
|
||||
|
||||
await SolutionExplorer.CreateSingleProjectSolutionAsync("Library", template, TestToken);
|
||||
await SolutionExplorer.RestoreNuGetPackagesAsync(TestToken);
|
||||
await Editor.SetTextAsync(code, TestToken);
|
||||
await Editor.PlaceCaretAsync("open System", TestToken);
|
||||
|
||||
await Workspace.WaitForProjectSystemAsync(TestToken);
|
||||
var codeActions = await Editor.InvokeCodeActionListAsync(TestToken);
|
||||
await Workspace.WaitForProjectSystemAsync(TestToken);
|
||||
|
||||
Assert.Single(codeActions);
|
||||
var actionSet = codeActions.Single();
|
||||
Assert.Equal("CodeFix", actionSet.CategoryName);
|
||||
|
||||
Assert.Single(actionSet.Actions);
|
||||
var codeFix = actionSet.Actions.Single();
|
||||
Assert.Equal("Remove unused open declarations", codeFix.DisplayText);
|
||||
}
|
||||
|
||||
[IdeFact]
|
||||
public async Task AddMissingFunKeyword()
|
||||
{
|
||||
var template = WellKnownProjectTemplates.FSharpNetCoreClassLibrary;
|
||||
|
||||
var code = """
|
||||
module Library
|
||||
|
||||
let original = []
|
||||
let transformed = original |> List.map (x -> x)
|
||||
""";
|
||||
|
||||
await SolutionExplorer.CreateSingleProjectSolutionAsync("Library", template, TestToken);
|
||||
await SolutionExplorer.RestoreNuGetPackagesAsync(TestToken);
|
||||
await Editor.SetTextAsync(code, TestToken);
|
||||
await Editor.PlaceCaretAsync("->", TestToken);
|
||||
|
||||
await Workspace.WaitForProjectSystemAsync(TestToken);
|
||||
var codeActions = await Editor.InvokeCodeActionListAsync(TestToken);
|
||||
await Workspace.WaitForProjectSystemAsync(TestToken);
|
||||
|
||||
Assert.Single(codeActions);
|
||||
var actionSet = codeActions.Single();
|
||||
Assert.Equal("ErrorFix", actionSet.CategoryName);
|
||||
|
||||
Assert.Single(actionSet.Actions);
|
||||
var errorFix = actionSet.Actions.Single();
|
||||
Assert.Equal("Add missing 'fun' keyword", errorFix.DisplayText);
|
||||
}
|
||||
|
||||
[IdeFact]
|
||||
public async Task AddNewKeywordToDisposables()
|
||||
{
|
||||
var template = WellKnownProjectTemplates.FSharpNetCoreClassLibrary;
|
||||
|
||||
var code = """
|
||||
module Library
|
||||
|
||||
let sr = System.IO.StreamReader("")
|
||||
""";
|
||||
|
||||
await SolutionExplorer.CreateSingleProjectSolutionAsync("Library", template, TestToken);
|
||||
await SolutionExplorer.RestoreNuGetPackagesAsync(TestToken);
|
||||
await Editor.SetTextAsync(code, TestToken);
|
||||
await Editor.PlaceCaretAsync("let sr", TestToken);
|
||||
|
||||
await Workspace.WaitForProjectSystemAsync(TestToken);
|
||||
var codeActions = await Editor.InvokeCodeActionListAsync(TestToken);
|
||||
await Workspace.WaitForProjectSystemAsync(TestToken);
|
||||
|
||||
Assert.Single(codeActions);
|
||||
var actionSet = codeActions.Single();
|
||||
Assert.Equal("CodeFix", actionSet.CategoryName);
|
||||
|
||||
Assert.Single(actionSet.Actions);
|
||||
var codeFix = actionSet.Actions.Single();
|
||||
Assert.Equal("Add 'new' keyword", codeFix.DisplayText);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
// 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 Microsoft.VisualStudio.Language.Intellisense;
|
||||
using Microsoft.VisualStudio.Text.Editor;
|
||||
using Microsoft.VisualStudio.Threading;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FSharp.Editor.IntegrationTests.Helpers
|
||||
{
|
||||
// I stole this voodoo from Razor and removed the obscurest bits
|
||||
internal static class LightBulbHelper
|
||||
{
|
||||
public static async Task<IEnumerable<SuggestedActionSet>> WaitForItemsAsync(
|
||||
ILightBulbBroker broker,
|
||||
IWpfTextView view,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var activeSession = broker.GetSession(view);
|
||||
var asyncSession = (IAsyncLightBulbSession)activeSession;
|
||||
var tcs = new TaskCompletionSource<IEnumerable<SuggestedActionSet>>();
|
||||
|
||||
void Handler(object s, SuggestedActionsUpdatedArgs e)
|
||||
{
|
||||
// ignore these. we care about when the lightbulb items are all completed.
|
||||
if (e.Status == QuerySuggestedActionCompletionStatus.InProgress)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.Status == QuerySuggestedActionCompletionStatus.Completed ||
|
||||
e.Status == QuerySuggestedActionCompletionStatus.CompletedWithoutData)
|
||||
{
|
||||
tcs.SetResult(e.ActionSets);
|
||||
}
|
||||
else
|
||||
{
|
||||
tcs.SetException(new InvalidOperationException($"Light bulb transitioned to non-complete state: {e.Status}"));
|
||||
}
|
||||
|
||||
asyncSession.SuggestedActionsUpdated -= Handler;
|
||||
}
|
||||
|
||||
asyncSession.SuggestedActionsUpdated += Handler;
|
||||
|
||||
asyncSession.Dismissed += (_, _) => tcs.TrySetCanceled(new CancellationToken(true));
|
||||
|
||||
if (asyncSession.IsDismissed)
|
||||
{
|
||||
tcs.TrySetCanceled(new CancellationToken(true));
|
||||
}
|
||||
|
||||
// Calling PopulateWithDataAsync ensures the underlying session will call SuggestedActionsUpdated at least once
|
||||
// with the latest data computed. This is needed so that if the lightbulb computation is already complete
|
||||
// that we hear about the results.
|
||||
await asyncSession.PopulateWithDataAsync(overrideRequestedActionCategories: null, operationContext: null).ConfigureAwait(false);
|
||||
|
||||
return await tcs.Task.WithCancellation(cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,9 +3,13 @@
|
|||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using FSharp.Editor.IntegrationTests.Extensions;
|
||||
using FSharp.Editor.IntegrationTests.Helpers;
|
||||
using Microsoft.VisualStudio.Language.Intellisense;
|
||||
using Microsoft.VisualStudio.OLE.Interop;
|
||||
using Microsoft.VisualStudio.Shell.Interop;
|
||||
using Microsoft.VisualStudio.Text;
|
||||
|
||||
|
@ -68,4 +72,23 @@ internal partial class EditorInProcess
|
|||
|
||||
view.Selection.Clear();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<SuggestedActionSet>> InvokeCodeActionListAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
|
||||
|
||||
var shell = await GetRequiredGlobalServiceAsync<SVsUIShell, IVsUIShell>(cancellationToken);
|
||||
var cmdGroup = typeof(VSConstants.VSStd14CmdID).GUID;
|
||||
var cmdExecOpt = OLECMDEXECOPT.OLECMDEXECOPT_DONTPROMPTUSER;
|
||||
|
||||
var cmdID = VSConstants.VSStd14CmdID.ShowQuickFixes;
|
||||
object? obj = null;
|
||||
shell.PostExecCommand(cmdGroup, (uint)cmdID, (uint)cmdExecOpt, ref obj);
|
||||
|
||||
var view = await GetActiveTextViewAsync(cancellationToken);
|
||||
var broker = await GetComponentModelServiceAsync<ILightBulbBroker>(cancellationToken);
|
||||
|
||||
var lightbulbs = await LightBulbHelper.WaitForItemsAsync(broker, view, cancellationToken);
|
||||
return lightbulbs;
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче