File: Cohost\HtmlRequestInvokerTest.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.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor;
using Microsoft.AspNetCore.Razor.Test.Common.Editor;
using Microsoft.AspNetCore.Razor.Test.Common.VisualStudio;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.ExternalAccess.Razor;
using Microsoft.CodeAnalysis.Razor;
using Microsoft.CodeAnalysis.Razor.Protocol.CodeActions;
using Microsoft.CodeAnalysis.Razor.Remote;
using Microsoft.CodeAnalysis.Razor.Telemetry;
using Microsoft.CodeAnalysis.Text;
using Xunit;
using Xunit.Abstractions;
 
namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost;
 
public class HtmlRequestInvokerTest(ITestOutputHelper testOutput) : VisualStudioWorkspaceTestBase(testOutput)
{
    private DocumentId? _documentId;
 
    protected override void ConfigureWorkspace(AdhocWorkspace workspace)
    {
        var project = workspace.CurrentSolution.AddProject("Project", "Project.dll", LanguageNames.CSharp);
        var document = project.AddAdditionalDocument("File.razor", SourceText.From("<div></div>"), filePath: "file://File.razor");
        _documentId = document.Id;
 
        Assert.True(workspace.TryApplyChanges(document.Project.Solution));
    }
 
    [Fact]
    public async Task DiagnosticsRequest_UpdatesUri()
    {
        var document = Workspace.CurrentSolution.GetAdditionalDocument(_documentId).AssumeNotNull();
 
        var htmlDocumentUri = new Uri("file://File.razor.html", UriKind.Absolute);
        var requestValidator = (object request) =>
        {
            var diagnosticParams = Assert.IsType<VSInternalDiagnosticParams>(request);
            Assert.Equal(htmlDocumentUri, diagnosticParams.TextDocument!.DocumentUri.GetRequiredParsedUri());
        };
 
        var diagnosticRequest = new VSInternalDiagnosticParams
        {
            TextDocument = new TextDocumentIdentifier { DocumentUri = document.CreateDocumentUri() }
        };
 
        await MakeHtmlRequestAsync(document, htmlDocumentUri, requestValidator, VSInternalMethods.DocumentPullDiagnosticName, diagnosticRequest);
 
        Assert.Equal(document.CreateDocumentUri(), diagnosticRequest.TextDocument!.DocumentUri);
    }
 
    [Fact]
    public async Task ITextDocumentParamsRequest_UpdatesUri()
    {
        var document = Workspace.CurrentSolution.GetAdditionalDocument(_documentId).AssumeNotNull();
 
        var htmlDocumentUri = new Uri("file://File.razor.html", UriKind.Absolute);
        var requestValidator = (object request) =>
        {
            var hoverParams = Assert.IsAssignableFrom<ITextDocumentParams>(request);
            Assert.Equal(htmlDocumentUri, hoverParams.TextDocument!.DocumentUri.GetRequiredParsedUri());
        };
 
        var hoverRequest = new HoverParams
        {
            TextDocument = new TextDocumentIdentifier { DocumentUri = document.CreateDocumentUri() }
        };
 
        await MakeHtmlRequestAsync(document, htmlDocumentUri, requestValidator, Methods.TextDocumentHoverName, hoverRequest);
 
        Assert.Equal(document.CreateDocumentUri(), hoverRequest.TextDocument!.DocumentUri);
    }
 
    [Fact]
    public async Task VSCodeActionParamsRequest_UpdatesUri()
    {
        var document = Workspace.CurrentSolution.GetAdditionalDocument(_documentId).AssumeNotNull();
 
        var htmlDocumentUri = new Uri("file://File.razor.html", UriKind.Absolute);
        var requestValidator = (object request) =>
        {
            var codeActionParams = Assert.IsType<VSCodeActionParams>(request);
            Assert.Equal(htmlDocumentUri, codeActionParams.TextDocument!.DocumentUri.GetRequiredParsedUri());
        };
 
        var codeActionsRequest = new VSCodeActionParams
        {
            TextDocument = new VSTextDocumentIdentifier { DocumentUri = document.CreateDocumentUri() },
            Range = LspFactory.DefaultRange,
            Context = new VSInternalCodeActionContext()
        };
 
        await MakeHtmlRequestAsync(document, htmlDocumentUri, requestValidator, Methods.TextDocumentCodeActionName, codeActionsRequest);
 
        Assert.Equal(document.CreateDocumentUri(), codeActionsRequest.TextDocument!.DocumentUri);
    }
 
    private async Task MakeHtmlRequestAsync<TRequest>(TextDocument document, Uri htmlDocumentUri, Action<object> requestValidator, string method, TRequest request)
       where TRequest : notnull
    {
        var htmlTextSnapshot = new StringTextSnapshot("");
        var htmlTextBuffer = new TestTextBuffer(htmlTextSnapshot);
        var checksum = await document.GetChecksumAsync(DisposalToken);
        var requestInvoker = new TestLSPRequestInvoker((method, null));
        var lspDocumentManager = new TestDocumentManager();
        var htmlVirtualDocument = new HtmlVirtualDocumentSnapshot(htmlDocumentUri, htmlTextBuffer.CurrentSnapshot, hostDocumentSyncVersion: 1, state: checksum);
        var documentSnapshot = new TestLSPDocumentSnapshot(document.CreateUri(), version: (int)(htmlVirtualDocument.HostDocumentSyncVersion!.Value + 1), htmlVirtualDocument);
        lspDocumentManager.AddDocument(documentSnapshot.Uri, documentSnapshot);
 
        var publisher = new TestHtmlDocumentPublisher();
        var remoteServiceInvoker = new RemoteServiceInvoker();
        var htmlDocumentSynchronizer = new HtmlDocumentSynchronizer(remoteServiceInvoker, publisher, LoggerFactory);
        var invoker = new HtmlRequestInvoker(requestInvoker, lspDocumentManager, htmlDocumentSynchronizer, NoOpTelemetryReporter.Instance, LoggerFactory);
 
        var validated = false;
        requestInvoker.RequestAction = r =>
        {
            validated = true;
            requestValidator(r);
        };
 
        _ = await invoker.MakeHtmlLspRequestAsync<TRequest, object>(
            document,
            method,
            request,
            DisposalToken);
 
        Assert.True(validated);
    }
 
    private class RemoteServiceInvoker : IRemoteServiceInvoker
    {
        public ValueTask<TResult?> TryInvokeAsync<TService, TResult>(Solution solution, Func<TService, RazorPinnedSolutionInfoWrapper, CancellationToken, ValueTask<TResult>> invocation, CancellationToken cancellationToken, [CallerFilePath] string? callerFilePath = null, [CallerMemberName] string? callerMemberName = null) where TService : class
        {
            return new((TResult?)(object)"");
        }
    }
}