File: Cohost\CohostSnippetCompletionTest.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.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.PooledObjects;
using Microsoft.AspNetCore.Razor.Test.Common;
using Microsoft.CodeAnalysis.ExternalAccess.Razor;
using Microsoft.CodeAnalysis.Razor.Completion;
using Microsoft.CodeAnalysis.Razor.Protocol;
using Microsoft.CodeAnalysis.Razor.Telemetry;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.Razor.Snippets;
using Xunit;
using Xunit.Abstractions;
 
namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost;
 
public class CohostSnippetCompletionTest(ITestOutputHelper testOutputHelper) : CohostEndpointTestBase(testOutputHelper)
{
    [Fact]
    public async Task SnippetResolve()
    {
        TestCode input = """
                This is a Razor document.
 
                $$
 
                The end.
                """;
 
        var document = CreateProjectAndRazorDocument(input.Text);
        var sourceText = await document.GetTextAsync(DisposalToken);
 
        const string InvalidLabel = "_INVALID_";
 
        var response = new RazorVSInternalCompletionList()
        {
            Items = [new VSInternalCompletionItem() { Label = InvalidLabel }],
            IsIncomplete = true
        };
 
        var requestInvoker = new TestHtmlRequestInvoker([(Methods.TextDocumentCompletionName, response)]);
 
        var snippetCompletionItemProvider = new TestSnippetCompletionItemProvider();
 
        var completionListCache = new CompletionListCache();
        var completionEndpoint = new CohostDocumentCompletionEndpoint(
            IncompatibleProjectService,
            RemoteServiceInvoker,
            ClientSettingsManager,
            ClientCapabilitiesService,
            snippetCompletionItemProvider,
            requestInvoker,
            completionListCache,
            NoOpTelemetryReporter.Instance,
            LoggerFactory);
 
        var request = new RazorVSInternalCompletionParams()
        {
            TextDocument = new TextDocumentIdentifier()
            {
                DocumentUri = document.CreateDocumentUri()
            },
            Position = sourceText.GetPosition(input.Position),
            Context = new VSInternalCompletionContext()
            {
                InvokeKind = VSInternalCompletionInvokeKind.Explicit,
                TriggerKind = CompletionTriggerKind.Invoked
            }
        };
 
        var result = await completionEndpoint.GetTestAccessor().HandleRequestAsync(request, document, DisposalToken);
 
        Assert.NotNull(result);
 
        var resolveEndpoint = new CohostDocumentCompletionResolveEndpoint(
            IncompatibleProjectService,
            completionListCache,
            RemoteServiceInvoker,
            requestInvoker,
            ClientCapabilitiesService,
            snippetCompletionItemProvider,
            LoggerFactory);
 
        // We clone the item first though, to ensure us setting the data doesn't hide a bug in our caching logic, around wrapping" the data.
        var itemToResolve = result.Items.First();
        itemToResolve = JsonSerializer.Deserialize<VSInternalCompletionItem>(JsonSerializer.SerializeToElement(itemToResolve, JsonHelpers.JsonSerializerOptions), JsonHelpers.JsonSerializerOptions)!;
 
        var tdi = resolveEndpoint.GetTestAccessor().GetRazorTextDocumentIdentifier(itemToResolve);
        Assert.NotNull(tdi);
        Assert.Equal(document.CreateUri(), tdi.Value.Uri);
 
        var resolvedItem = await resolveEndpoint.GetTestAccessor().HandleRequestAsync(itemToResolve, document, DisposalToken);
 
        Assert.NotNull(resolvedItem);
        Assert.Equal(itemToResolve.Label, resolvedItem.Label);
        Assert.Equal("ResolvedSnippetItem", resolvedItem.InsertText);
    }
 
    private class TestSnippetCompletionItemProvider : ISnippetCompletionItemProvider
    {
        public void AddSnippetCompletions(ref PooledArrayBuilder<VSInternalCompletionItem> builder, RazorLanguageKind projectedKind, VSInternalCompletionInvokeKind invokeKind, string? triggerCharacter)
        {
            Assert.Equal(RazorLanguageKind.Html, projectedKind);
            builder.Add(new VSInternalCompletionItem()
            {
                Label = "SnippetItem",
                InsertText = "SnippetItem",
                InsertTextFormat = InsertTextFormat.Snippet,
                Data = new SnippetCompletionData("SnippetPath")
            });
        }
 
        public bool TryResolveInsertString(VSInternalCompletionItem completionItem, [NotNullWhen(true)] out string? insertString)
        {
            Assert.Equal("SnippetItem", completionItem.Label);
            Assert.Equal("SnippetItem", completionItem.InsertText);
 
            Assert.True(SnippetCompletionData.TryParse(completionItem.Data, out var snippetCompletionData));
            Assert.Equal("SnippetPath", snippetCompletionData.Path);
 
            insertString = "ResolvedSnippetItem";
 
            return true;
        }
    }
}