File: StringCopyPaste\StringCopyPasteCommandHandlerTests.cs
Web Access
Project: src\src\EditorFeatures\CSharpTest\Microsoft.CodeAnalysis.CSharp.EditorFeatures.UnitTests.csproj (Microsoft.CodeAnalysis.CSharp.EditorFeatures.UnitTests)
// 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.Collections.Immutable;
using System.Linq;
using System.Xml.Linq;
using Microsoft.CodeAnalysis.Editor.CSharp.StringCopyPaste;
using Microsoft.CodeAnalysis.Editor.StringCopyPaste;
using Microsoft.CodeAnalysis.Editor.UnitTests;
using Microsoft.CodeAnalysis.Editor.UnitTests.Utilities;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.CodeAnalysis.Testing;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CodeAnalysis.Text.Shared.Extensions;
using Microsoft.VisualStudio.Commanding;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Text.Editor.Commanding.Commands;
using Microsoft.VisualStudio.Text.Operations;
using Roslyn.Utilities;
using Xunit;
 
namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.StringCopyPaste;
 
[UseExportProvider]
public abstract class StringCopyPasteCommandHandlerTests
{
    internal sealed class StringCopyPasteTestState : AbstractCommandHandlerTestState
    {
        private static readonly TestComposition s_composition =
            EditorTestCompositions.EditorFeaturesWpf
                .AddParts(typeof(StringCopyPasteCommandHandler));
 
        private static readonly TestComposition s_compositionWithMockCopyPasteService =
            EditorTestCompositions.EditorFeaturesWpf
                .RemoveExcludedPartTypes(typeof(WpfStringCopyPasteService))
                .AddParts(typeof(TestStringCopyPasteService))
                .AddParts(typeof(StringCopyPasteCommandHandler));
 
        public readonly StringCopyPasteCommandHandler CommandHandler;
 
        public StringCopyPasteTestState(XElement workspaceElement, bool mockCopyPasteService)
            : base(workspaceElement, mockCopyPasteService
                  ? s_compositionWithMockCopyPasteService
                  : s_composition)
        {
            CommandHandler = (StringCopyPasteCommandHandler)GetExportedValues<ICommandHandler>().
                Single(c => c is StringCopyPasteCommandHandler);
        }
 
        public static StringCopyPasteTestState CreateTestState(string? copyFileMarkup, string pasteFileMarkup, bool mockCopyPasteService)
            => new(GetWorkspaceXml(copyFileMarkup, pasteFileMarkup), mockCopyPasteService);
 
        public static XElement GetWorkspaceXml(string? copyFileMarkup, string pasteFileMarkup)
            => XElement.Parse(($@"
<Workspace>
    <Project Language=""C#"" CommonReferences=""true"">
        <Document Markup=""SpansOnly"">{pasteFileMarkup}</Document>
    </Project>
    {(copyFileMarkup == null ? "" : $@"
    <Project Language=""C#"" CommonReferences=""true"">
        <Document Markup=""SpansOnly"">{copyFileMarkup}</Document>
    </Project>")}
</Workspace>"));
 
        internal void AssertCodeIs(string expectedCode)
        {
            TestFileMarkupParser.GetPositionAndSpans(
                expectedCode, out var massaged, out int? caretPosition, out ImmutableDictionary<string, ImmutableArray<TextSpan>> spans);
            Assert.Equal(massaged, TextView.TextSnapshot.GetText());
            Assert.Equal(caretPosition, TextView.Caret.Position.BufferPosition.Position);
 
            var virtualSpaces = spans.SingleOrDefault(kvp => kvp.Key.StartsWith("VirtualSpaces#"));
            if (virtualSpaces.Key != null)
            {
                var virtualOffset = int.Parse(virtualSpaces.Key["VirtualSpaces-".Length..]);
                Assert.True(TextView.Caret.InVirtualSpace);
                Assert.Equal(virtualOffset, TextView.Caret.Position.VirtualBufferPosition.VirtualSpaces);
            }
        }
 
        public void TestCopyPaste(string expectedMarkup, string? pasteText, bool pasteTextIsKnown, string afterUndoMarkup)
        {
            var workspace = this.Workspace;
 
            // Ensure we clear out the clipboard so that a prior copy/paste doesn't corrupt the test.
            var service = workspace.Services.GetRequiredService<IStringCopyPasteService>() as TestStringCopyPasteService;
            service?.TrySetClipboardData(StringCopyPasteCommandHandler.KeyAndVersion, "");
 
            var copyDocument = this.Workspace.Documents.FirstOrDefault(d => d.AnnotatedSpans.ContainsKey("Copy"));
            if (copyDocument != null)
            {
                Assert.Null(pasteText);
                var copySpans = copyDocument.AnnotatedSpans["Copy"];
 
                SetSelection(copyDocument, copySpans, out var copyTextView, out var copyTextBuffer);
 
                CommandHandler.ExecuteCommand(
                    new CopyCommandArgs(copyTextView, copyTextBuffer), () =>
                    {
                        var copyEditorOperations = GetService<IEditorOperationsFactoryService>().GetEditorOperations(copyTextView);
                        Assert.True(copyEditorOperations.CopySelection());
                    }, TestCommandExecutionContext.Create());
            }
            else
            {
                // If we don't have a file to copy text from, then the paste text must be explicitly provided.");
                Assert.NotNull(pasteText);
            }
 
            if (pasteText == null)
            {
                // if the user didn't supply text to paste, then just paste in what we put in the clipboard above.
                CommandHandler.ExecuteCommand(
                    new PasteCommandArgs(this.TextView, this.SubjectBuffer), () => EditorOperations.Paste(), TestCommandExecutionContext.Create());
            }
            else
            {
                // otherwise, this is a test of text coming in from another source.  Do the edit manually.
 
                if (pasteTextIsKnown)
                {
                    // we were given text to directly place on the clipboard without needing to do a copy.
                    Contract.ThrowIfNull(pasteText);
                    var json = new StringCopyPasteData(ImmutableArray.Create(StringCopyPasteContent.ForText(pasteText))).ToJson();
                    Contract.ThrowIfNull(json);
                    service!.TrySetClipboardData(StringCopyPasteCommandHandler.KeyAndVersion, json);
                }
 
                CommandHandler.ExecuteCommand(
                    new PasteCommandArgs(this.TextView, this.SubjectBuffer), () =>
                    {
                        EditorOperations.ReplaceSelection(pasteText);
                    }, TestCommandExecutionContext.Create());
            }
 
            ValidateBefore(expectedMarkup);
 
            this.SendUndo();
 
            ValidateAfter(afterUndoMarkup);
        }
 
        private void ValidateBefore(string expectedMarkup)
        {
            GetCodeAndCaretPosition(expectedMarkup, out var expected, out var caretPosition);
            var finalText = this.SubjectBuffer.CurrentSnapshot.GetText();
 
            Assert.Equal(expected, finalText);
            Assert.Equal(caretPosition, this.TextView.Caret.Position.BufferPosition.Position);
        }
 
        private static void GetCodeAndCaretPosition(string expectedMarkup, out string expected, out int caretPosition)
        {
            // Used so test can contain `$$` (for raw interpolations) without us thinking that it is an actual caret
            // position
            const string NON_TEST_CHARACTER = "\uD7FF";
 
            expectedMarkup = expectedMarkup.Replace("$", NON_TEST_CHARACTER);
 
            TestFileMarkupParser.GetSpan(expectedMarkup, out expected, out var caretSpan);
 
            expected = expected.Replace(NON_TEST_CHARACTER, "$");
 
            caretPosition = caretSpan.Start;
        }
 
        private void ValidateAfter(string afterUndoMarkup)
        {
            GetCodeAndCaretPosition(afterUndoMarkup, out var expected, out var caretPosition);
            var finalText = this.SubjectBuffer.CurrentSnapshot.GetText();
 
            Assert.Equal(expected, finalText);
            Assert.Equal(caretPosition, this.TextView.Caret.Position.BufferPosition.Position);
        }
 
        private static void SetSelection(
            EditorTestHostDocument document,
            ImmutableArray<TextSpan> copySpans, out IWpfTextView textView, out ITextBuffer2 textBuffer2)
        {
            textView = document.GetTextView();
            var textBuffer = document.GetTextBuffer();
            textBuffer2 = textBuffer;
            var broker = textView.GetMultiSelectionBroker();
 
            var selections = copySpans.Select(s => new Selection(s.ToSnapshotSpan(textBuffer.CurrentSnapshot))).ToArray();
            broker.SetSelectionRange(selections, selections.First());
        }
    }
}