File: AutomaticCompletion\AbstractAutomaticBraceCompletionTests.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 Microsoft.CodeAnalysis.AutomaticCompletion;
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.BraceCompletion;
using Microsoft.VisualStudio.Text.Editor;
using Roslyn.Test.Utilities;
using Xunit;
 
namespace Microsoft.CodeAnalysis.Editor.UnitTests.AutomaticCompletion;
 
[UseExportProvider]
public abstract class AbstractAutomaticBraceCompletionTests
{
    internal static void CheckStart(IBraceCompletionSession session, bool expectValidSession = true)
    {
        Type(session, session.OpeningBrace.ToString());
 
        session.Start();
 
        if (expectValidSession)
        {
            var closingPoint = session.ClosingPoint.GetPoint(session.SubjectBuffer.CurrentSnapshot).Subtract(1);
            Assert.Equal(closingPoint.GetChar(), session.ClosingBrace);
        }
        else
        {
            Assert.Null(session.OpeningPoint);
            Assert.Null(session.ClosingPoint);
        }
    }
 
    internal static void CheckBackspace(IBraceCompletionSession session)
    {
        session.TextView.TryMoveCaretToAndEnsureVisible(session.OpeningPoint.GetPoint(session.SubjectBuffer.CurrentSnapshot).Add(1));
        session.PreBackspace(out var handled);
        if (!handled)
        {
            session.PostBackspace();
        }
 
        Assert.Null(session.OpeningPoint);
        Assert.Null(session.ClosingPoint);
    }
 
    internal static void CheckTab(IBraceCompletionSession session, bool allowTab = true)
    {
        session.PreTab(out var handled);
        if (!handled)
        {
            session.PostTab();
        }
 
        var caret = session.TextView.GetCaretPoint(session.SubjectBuffer).Value;
        if (allowTab)
        {
            Assert.Equal(session.ClosingPoint.GetPosition(session.SubjectBuffer.CurrentSnapshot), caret.Position);
        }
        else
        {
            Assert.True(caret.Position < session.ClosingPoint.GetPosition(session.SubjectBuffer.CurrentSnapshot));
        }
    }
 
    internal static void CheckReturn(IBraceCompletionSession session, int indentation, string result = null)
    {
        session.PreReturn(out var handled);
 
        Type(session, Environment.NewLine);
 
        if (!handled)
        {
            session.PostReturn();
        }
 
        var virtualCaret = session.TextView.GetVirtualCaretPoint(session.SubjectBuffer).Value;
        Assert.True(indentation == virtualCaret.VirtualSpaces, $"Expected indentation was {indentation}, but the actual indentation was {virtualCaret.VirtualSpaces}");
 
        if (result != null)
        {
            AssertEx.EqualOrDiff(result, session.SubjectBuffer.CurrentSnapshot.GetText());
        }
    }
 
    internal static void CheckText(IBraceCompletionSession session, string result)
        => Assert.Equal(result, session.SubjectBuffer.CurrentSnapshot.GetText());
 
    internal static void CheckReturnOnNonEmptyLine(IBraceCompletionSession session, int expectedVirtualSpace)
    {
        session.PreReturn(out var handled);
 
        Type(session, Environment.NewLine);
 
        if (!handled)
        {
            session.PostReturn();
        }
 
        var virtualCaret = session.TextView.GetVirtualCaretPoint(session.SubjectBuffer).Value;
        Assert.Equal(expectedVirtualSpace, virtualCaret.VirtualSpaces);
    }
 
    internal static void CheckOverType(IBraceCompletionSession session, bool allowOverType = true)
    {
        var preClosingPoint = session.ClosingPoint.GetPoint(session.SubjectBuffer.CurrentSnapshot);
        Assert.Equal(session.ClosingBrace, preClosingPoint.Subtract(1).GetChar());
        session.PreOverType(out var handled);
        if (!handled)
        {
            session.PostOverType();
        }
 
        var postClosingPoint = session.ClosingPoint.GetPoint(session.SubjectBuffer.CurrentSnapshot);
        Assert.Equal(postClosingPoint.Subtract(1).GetChar(), session.ClosingBrace);
 
        var caret = session.TextView.GetCaretPoint(session.SubjectBuffer).Value;
        if (allowOverType)
        {
            Assert.Equal(postClosingPoint.Position, caret.Position);
        }
        else
        {
            Assert.True(caret.Position < postClosingPoint.Position);
        }
    }
 
    internal static void Type(IBraceCompletionSession session, string text)
    {
        var buffer = session.SubjectBuffer;
        var caret = session.TextView.GetCaretPoint(buffer).Value;
 
        using var edit = buffer.CreateEdit();
        edit.Insert(caret.Position, text);
        edit.Apply();
    }
 
    internal static Holder CreateSession(EditorTestWorkspace workspace, char opening, char closing, OptionsCollection globalOptions = null)
    {
        workspace.SetAnalyzerFallbackAndGlobalOptions(globalOptions);
 
        var document = workspace.Documents.First();
 
        var provider = Assert.IsType<BraceCompletionSessionProvider>(workspace.GetService<IBraceCompletionSessionProvider>());
 
        var openingPoint = new SnapshotPoint(document.GetTextBuffer().CurrentSnapshot, document.CursorPosition.Value);
        var textView = document.GetTextView();
 
        workspace.GlobalOptions.SetEditorOptions(textView.Options.GlobalOptions, document.Project.Language);
 
        // Disable responsive-completion so that we don't cancel our work in the event that brace completion is
        // taking too long.
        textView.Options.SetOptionValue(DefaultOptions.ResponsiveCompletionOptionId, false);
        if (provider.TryCreateSession(textView, openingPoint, opening, closing, out var session))
        {
            return new Holder(workspace, session);
        }
 
        workspace.Dispose();
        return null;
    }
 
    internal sealed class Holder(EditorTestWorkspace workspace, IBraceCompletionSession session) : IDisposable
    {
        public readonly EditorTestWorkspace Workspace = workspace;
        public readonly IBraceCompletionSession Session = session;
 
        public void Dispose()
            => this.Workspace.Dispose();
    }
}