File: Cohost\CohostOnTypeFormattingEndpointTest.cs
Web Access
Project: src\src\Razor\src\Razor\test\Microsoft.VisualStudio.LanguageServices.Razor.UnitTests\Microsoft.VisualStudio.LanguageServices.Razor.UnitTests.csproj (Microsoft.VisualStudio.LanguageServices.Razor.UnitTests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Test.Common;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.ExternalAccess.Razor;
using Microsoft.CodeAnalysis.Razor.Cohost;
using Microsoft.CodeAnalysis.Razor.Formatting;
using Microsoft.CodeAnalysis.Razor.Protocol;
using Microsoft.CodeAnalysis.Razor.Remote;
using Roslyn.Test.Utilities;
using Xunit;
using Xunit.Abstractions;
 
namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost;
 
[Collection(HtmlFormattingCollection.Name)]
public class CohostOnTypeFormattingEndpointTest(HtmlFormattingFixture htmlFormattingFixture, ITestOutputHelper testOutputHelper)
    : CohostEndpointTestBase(testOutputHelper)
{
    [Fact]
    public async Task InvalidTrigger()
    {
        await VerifyOnTypeFormattingAsync(
            input: """
                    @{
                     if(true){}$$
                    }
                    """,
            expected: """
                    @{
                     if(true){}
                    }
                    """,
            triggerCharacter: 'h');
    }
 
    [Fact]
    public async Task CSharp_InvalidTrigger()
    {
        await VerifyOnTypeFormattingAsync(
            input: """
                    @{
                     if(true){}$$
                    }
                    """,
            expected: """
                    @{
                     if(true){}
                    }
                    """,
            triggerCharacter: '\n');
    }
 
    [Fact]
    public async Task CSharp()
    {
        await VerifyOnTypeFormattingAsync(
            input: """
                    @{
                     if(true){}$$
                    }
                    """,
            expected: """
                    @{
                        if (true) { }
                    }
                    """,
            triggerCharacter: '}');
    }
 
    [Fact]
    public async Task FormatsSimpleHtmlTag_OnType()
    {
        await VerifyOnTypeFormattingAsync(
            input: """
                    <html>
                    <head>
                        <title>Hello</title>
                            <script>
                                var x = 2;$$
                            </script>
                    </head>
                    </html>
                    """,
            expected: """
                    <html>
                    <head>
                        <title>Hello</title>
                        <script>
                            var x = 2;
                        </script>
                    </head>
                    </html>
                    """,
            triggerCharacter: ';',
            html: true);
    }
 
    [Fact]
    public async Task FormattingDisabled()
    {
        ClientSettingsManager.Update(ClientSettingsManager.GetClientSettings().AdvancedSettings with { FormatOnType = false });
 
        await VerifyOnTypeFormattingAsync(
            input: """
                @{
                    if(true){}$$
                }
                """,
            expected: """
                @{
                    if(true){}
                }
                """,
            triggerCharacter: '}');
    }
 
    private async Task VerifyOnTypeFormattingAsync(TestCode input, string expected, char triggerCharacter, bool html = false)
    {
        var document = CreateProjectAndRazorDocument(input.Text);
        var inputText = await document.GetTextAsync(DisposalToken);
        var position = inputText.GetPosition(input.Position);
 
        IHtmlRequestInvoker requestInvoker;
        if (html)
        {
            var generatedHtml = await RemoteServiceInvoker.TryInvokeAsync<IRemoteHtmlDocumentService, string?>(document.Project.Solution,
                (service, solutionInfo, ct) => service.GetHtmlDocumentTextAsync(solutionInfo, document.Id, ct),
                DisposalToken).ConfigureAwait(false);
            Assert.NotNull(generatedHtml);
 
            var uri = new Uri(document.CreateUri(), $"{document.FilePath}{LanguageServerConstants.HtmlVirtualDocumentSuffix}");
            var htmlEdits = await htmlFormattingFixture.Service.GetOnTypeFormattingEditsAsync(LoggerFactory, uri, generatedHtml, position, insertSpaces: true, tabSize: 4);
 
            requestInvoker = new TestHtmlRequestInvoker([(Methods.TextDocumentOnTypeFormattingName, htmlEdits)]);
        }
        else
        {
            // We use a mock here so that it will throw if called
            requestInvoker = StrictMock.Of<IHtmlRequestInvoker>();
        }
 
        var endpoint = new CohostOnTypeFormattingEndpoint(IncompatibleProjectService, RemoteServiceInvoker, requestInvoker, ClientSettingsManager, LoggerFactory);
 
        var request = new DocumentOnTypeFormattingParams()
        {
            TextDocument = new TextDocumentIdentifier() { DocumentUri = document.CreateDocumentUri() },
            Options = new FormattingOptions()
            {
                TabSize = 4,
                InsertSpaces = true
            },
            Character = triggerCharacter.ToString(),
            Position = position
        };
 
        var edits = await endpoint.GetTestAccessor().HandleRequestAsync(request, document, DisposalToken);
 
        if (edits is null)
        {
            Assert.Equal(expected, input.Text);
            return;
        }
 
        var changes = edits.Select(inputText.GetTextChange);
        var finalText = inputText.WithChanges(changes);
 
        AssertEx.EqualOrDiff(expected, finalText.ToString());
    }
}