|
// 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.
#nullable disable
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeRefactorings;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Editor.Implementation.RenameTracking;
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Editor.UnitTests.Utilities;
using Microsoft.CodeAnalysis.Notification;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CodeAnalysis.Text.Shared.Extensions;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Text.Editor.Commanding.Commands;
using Microsoft.VisualStudio.Text.Operations;
using Microsoft.VisualStudio.Text.Tagging;
using Roslyn.Test.Utilities;
using Roslyn.Utilities;
using Xunit;
namespace Microsoft.CodeAnalysis.Editor.UnitTests.RenameTracking;
internal sealed class RenameTrackingTestState : IDisposable
{
private readonly ITagger<RenameTrackingTag> _tagger;
public readonly EditorTestWorkspace Workspace;
private readonly IWpfTextView _view;
private readonly ITextUndoHistoryRegistry _historyRegistry;
private string _notificationMessage = null;
public EditorTestHostDocument HostDocument { get; }
public IEditorOperations EditorOperations { get; }
public MockRefactorNotifyService RefactorNotifyService { get; }
private readonly RenameTrackingCodeRefactoringProvider _codeRefactoringProvider;
private readonly RenameTrackingCancellationCommandHandler _commandHandler = new RenameTrackingCancellationCommandHandler();
public static RenameTrackingTestState Create(
string markup,
string languageName,
bool onBeforeGlobalSymbolRenamedReturnValue = true,
bool onAfterGlobalSymbolRenamedReturnValue = true)
{
var workspace = CreateTestWorkspace(markup, languageName);
return new RenameTrackingTestState(workspace, languageName, onBeforeGlobalSymbolRenamedReturnValue, onAfterGlobalSymbolRenamedReturnValue);
}
public static RenameTrackingTestState CreateFromWorkspaceXml(
string workspaceXml,
string languageName,
bool onBeforeGlobalSymbolRenamedReturnValue = true,
bool onAfterGlobalSymbolRenamedReturnValue = true)
{
var workspace = CreateTestWorkspace(workspaceXml);
return new RenameTrackingTestState(workspace, languageName, onBeforeGlobalSymbolRenamedReturnValue, onAfterGlobalSymbolRenamedReturnValue);
}
public RenameTrackingTestState(
EditorTestWorkspace workspace,
string languageName,
bool onBeforeGlobalSymbolRenamedReturnValue = true,
bool onAfterGlobalSymbolRenamedReturnValue = true)
{
this.Workspace = workspace;
HostDocument = Workspace.Documents.First();
_view = HostDocument.GetTextView();
_view.Caret.MoveTo(new SnapshotPoint(_view.TextSnapshot, HostDocument.CursorPosition.Value));
EditorOperations = Workspace.GetService<IEditorOperationsFactoryService>().GetEditorOperations(_view);
_historyRegistry = Workspace.ExportProvider.GetExport<ITextUndoHistoryRegistry>().Value;
RefactorNotifyService = new MockRefactorNotifyService
{
OnBeforeSymbolRenamedReturnValue = onBeforeGlobalSymbolRenamedReturnValue,
OnAfterSymbolRenamedReturnValue = onAfterGlobalSymbolRenamedReturnValue
};
// Mock the action taken by the workspace INotificationService
var notificationService = (INotificationServiceCallback)Workspace.Services.GetRequiredService<INotificationService>();
var callback = new Action<string, string, NotificationSeverity>((message, title, severity) => _notificationMessage = message);
notificationService.NotificationCallback = callback;
var tracker = new RenameTrackingTaggerProvider(
Workspace.GetService<IThreadingContext>(),
Workspace.GetService<IInlineRenameService>(),
Workspace.GetService<IDiagnosticAnalyzerService>(),
Workspace.GetService<IGlobalOptionService>(),
Workspace.GetService<IAsynchronousOperationListenerProvider>());
_tagger = tracker.CreateTagger<RenameTrackingTag>(HostDocument.GetTextBuffer());
if (languageName is LanguageNames.CSharp or
LanguageNames.VisualBasic)
{
_codeRefactoringProvider = new RenameTrackingCodeRefactoringProvider(
_historyRegistry,
[RefactorNotifyService]);
}
else
{
throw new ArgumentException("Invalid language name: " + languageName, nameof(languageName));
}
}
private static EditorTestWorkspace CreateTestWorkspace(string code, string languageName)
{
return CreateTestWorkspace(string.Format(@"
<Workspace>
<Project Language=""{0}"" CommonReferences=""true"">
<Document>{1}</Document>
</Project>
</Workspace>", languageName, code));
}
private static EditorTestWorkspace CreateTestWorkspace(string xml)
{
return EditorTestWorkspace.Create(xml, composition: EditorTestCompositions.EditorFeaturesWpf);
}
public void SendEscape()
=> _commandHandler.ExecuteCommand(new EscapeKeyCommandArgs(_view, _view.TextBuffer), TestCommandExecutionContext.Create());
public void MoveCaret(int delta)
{
var position = _view.Caret.Position.BufferPosition.Position;
_view.Caret.MoveTo(new SnapshotPoint(_view.TextSnapshot, position + delta));
}
public void Undo(int count = 1)
{
var history = _historyRegistry.GetHistory(_view.TextBuffer);
history.Undo(count);
}
public void Redo(int count = 1)
{
var history = _historyRegistry.GetHistory(_view.TextBuffer);
history.Redo(count);
}
public async Task AssertNoTag()
{
await WaitForAsyncOperationsAsync();
var tags = _tagger.GetTags(_view.TextBuffer.CurrentSnapshot.GetSnapshotSpanCollection());
Assert.Equal(0, tags.Count());
}
/// <param name="textSpan">If <see langword="null"/> the current caret position will be used.</param>
public async Task<CodeAction> TryGetCodeActionAsync(TextSpan? textSpan = null)
{
var span = textSpan ?? new TextSpan(_view.Caret.Position.BufferPosition, 0);
var document = this.Workspace.CurrentSolution.GetDocument(HostDocument.Id);
var actions = new List<CodeAction>();
var context = new CodeRefactoringContext(
document, span, actions.Add, CancellationToken.None);
await _codeRefactoringProvider.ComputeRefactoringsAsync(context);
return actions.SingleOrDefault();
}
public async Task AssertTag(string expectedFromName, string expectedToName, bool invokeAction = false)
{
await WaitForAsyncOperationsAsync();
var tags = _tagger.GetTags(_view.TextBuffer.CurrentSnapshot.GetSnapshotSpanCollection());
// There should only ever be one tag
Assert.Equal(1, tags.Count());
var tag = tags.Single();
// There should only be one code action for the tag
var codeAction = await TryGetCodeActionAsync(tag.Span.Span.ToTextSpan());
Assert.NotNull(codeAction);
Assert.Equal(string.Format(WorkspacesResources.Rename_0_to_1, expectedFromName, expectedToName), codeAction.Title);
if (invokeAction)
{
var operations = (await codeAction.GetOperationsAsync(CancellationToken.None)).ToArray();
Assert.Equal(1, operations.Length);
await operations[0].TryApplyAsync(this.Workspace, this.Workspace.CurrentSolution, CodeAnalysisProgress.None, CancellationToken.None);
}
}
public void AssertNoNotificationMessage()
=> Assert.Null(_notificationMessage);
public void AssertNotificationMessage()
=> Assert.NotNull(_notificationMessage);
private async Task WaitForAsyncOperationsAsync()
{
var provider = Workspace.ExportProvider.GetExportedValue<AsynchronousOperationListenerProvider>();
await provider.WaitAllDispatcherOperationAndTasksAsync(
Workspace,
FeatureAttribute.RenameTracking,
FeatureAttribute.SolutionCrawlerLegacy,
FeatureAttribute.Workspace,
FeatureAttribute.EventHookup);
}
public void Dispose()
=> Workspace.Dispose();
}
|