File: AutomaticCompletion\AbstractAutomaticLineEnderTests.cs
Web Access
Project: src\src\EditorFeatures\TestUtilities\Microsoft.CodeAnalysis.EditorFeatures.Test.Utilities.csproj (Microsoft.CodeAnalysis.EditorFeatures.Test.Utilities)
// 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.Linq;
using System.Text.RegularExpressions;
using Microsoft.CodeAnalysis.Editor.UnitTests.Utilities;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.CodeAnalysis.Testing;
using Microsoft.VisualStudio.Commanding;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Text.Editor.Commanding.Commands;
using Roslyn.Test.Utilities;
using Xunit;
 
namespace Microsoft.CodeAnalysis.Editor.UnitTests.AutomaticCompletion
{
    [UseExportProvider]
    public abstract class AbstractAutomaticLineEnderTests
    {
        protected abstract string Language { get; }
        protected abstract Action CreateNextHandler(EditorTestWorkspace workspace);
 
        internal abstract IChainedCommandHandler<AutomaticLineEnderCommandArgs> GetCommandHandler(EditorTestWorkspace workspace);
 
        protected void Test(string expected, string markupCode, bool completionActive = false, bool assertNextHandlerInvoked = false)
        {
            Test(expected, markupCode, useTabs: false, completionActive, assertNextHandlerInvoked);
            Test(expected, markupCode, useTabs: true, completionActive, assertNextHandlerInvoked);
        }
 
        private void Test(string expected, string markupCode, bool useTabs, bool completionActive = false, bool assertNextHandlerInvoked = false)
        {
            if (useTabs)
            {
                expected = ToTabs(expected);
                markupCode = ToTabs(markupCode);
            }
 
            TestFileMarkupParser.GetPositionsAndSpans(markupCode, out var code, out var positions, out _);
            Assert.NotEmpty(positions);
 
            foreach (var position in positions)
            {
                // Run the test once for each input position. All marked positions in the input for a test are expected
                // to have the same result.
                Test(expected, code, position, useTabs, completionActive, assertNextHandlerInvoked);
            }
        }
 
        private static string ToTabs(string value)
        {
            var lines = value.Split('\n');
            for (var i = 0; i < lines.Length; i++)
            {
                lines[i] = Regex.Replace(lines[i], "(?<=^(    )*)(    )", _ => "\t");
            }
 
            return string.Join("\n", lines);
        }
 
#pragma warning disable IDE0060 // Remove unused parameter - https://github.com/dotnet/roslyn/issues/45892
        private void Test(string expected, string code, int position, bool useTabs, bool completionActive = false, bool assertNextHandlerInvoked = false)
#pragma warning restore IDE0060 // Remove unused parameter
        {
            var markupCode = code[0..position] + "$$" + code[position..];
 
            // WPF is required for some reason: https://github.com/dotnet/roslyn/issues/46286
            using var workspace = EditorTestWorkspace.Create(Language, compilationOptions: null, parseOptions: null, [markupCode], composition: EditorTestCompositions.EditorFeaturesWpf);
 
            var view = workspace.Documents.Single().GetTextView();
            var buffer = workspace.Documents.Single().GetTextBuffer();
            var nextHandlerInvoked = false;
 
            view.Options.GlobalOptions.SetOptionValue(DefaultOptions.ConvertTabsToSpacesOptionId, !useTabs);
            view.Options.GlobalOptions.SetOptionValue(DefaultOptions.IndentStyleId, IndentingStyle.Smart);
 
            view.Caret.MoveTo(new SnapshotPoint(buffer.CurrentSnapshot, workspace.Documents.Single(d => d.CursorPosition.HasValue).CursorPosition.Value));
 
            var commandHandler = GetCommandHandler(workspace);
            var nextHandler = assertNextHandlerInvoked ? () => nextHandlerInvoked = true : CreateNextHandler(workspace);
 
            commandHandler.ExecuteCommand(new AutomaticLineEnderCommandArgs(view, buffer), nextHandler, TestCommandExecutionContext.Create());
 
            Test(view, buffer, expected, useTabs);
 
            Assert.Equal(assertNextHandlerInvoked, nextHandlerInvoked);
        }
 
        private static void Test(ITextView view, ITextBuffer buffer, string expectedWithAnnotations, bool useTabs)
        {
            MarkupTestFile.GetPosition(expectedWithAnnotations, out var expected, out int expectedPosition);
 
            // Remove any virtual space from the expected text.
            var virtualPosition = view.Caret.Position.VirtualBufferPosition;
 
            var charactersToRemove = virtualPosition.VirtualSpaces;
            if (useTabs)
            {
                Assert.Equal(0, charactersToRemove % 4);
                charactersToRemove /= 4;
            }
 
            expected = expected.Remove(virtualPosition.Position, charactersToRemove);
 
            Assert.Equal(expected, buffer.CurrentSnapshot.GetText());
            Assert.Equal(expectedPosition, virtualPosition.Position.Position + charactersToRemove);
        }
 
        public static T GetService<T>(TestWorkspace workspace)
            => workspace.GetService<T>();
 
        public static T GetExportedValue<T>(TestWorkspace workspace)
            => workspace.ExportProvider.GetExportedValue<T>();
    }
}