Skip to content

Commit

Permalink
Migrate delegated completion list resolver tests to use C# server. (#…
Browse files Browse the repository at this point in the history
…6624)

- Updated C# portions of our delegated completion list provider tests to utilze the real C# server and simplified the testing model.
- Maintained a lot of pre-existing tests because we don't have similar capabilities for HTML
- These tests will be more widely used upon completion of #6618 where we'll want to resolve text edits (this is a leadup).

Part of #6618
  • Loading branch information
NTaylorMullen authored Jul 22, 2022
1 parent 405fbee commit 2a62139
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.LanguageServer.Test.Common.Extensions;
Expand All @@ -23,6 +24,9 @@ internal class CSharpTestLspServerHelpers
{
private const string EditRangeSetting = "editRange";

public static Task<CSharpTestLspServer> CreateCSharpLspServerAsync(SourceText csharpSourceText, Uri csharpDocumentUri, ServerCapabilities serverCapabilities) =>
CreateCSharpLspServerAsync(csharpSourceText, csharpDocumentUri, serverCapabilities, new EmptyMappingService());

public static async Task<CSharpTestLspServer> CreateCSharpLspServerAsync(
SourceText csharpSourceText,
Uri csharpDocumentUri,
Expand Down Expand Up @@ -111,5 +115,14 @@ private static AdhocWorkspace CreateCSharpTestWorkspace(
}

private record CSharpFile(Uri DocumentUri, SourceText CSharpSourceText);

private class EmptyMappingService : IRazorSpanMappingService
{
public Task<ImmutableArray<RazorMappedSpanResult>> MapSpansAsync(Document document, IEnumerable<TextSpan> spans, CancellationToken cancellationToken)
{
var result = Enumerable.Empty<RazorMappedSpanResult>().ToImmutableArray();
return Task.FromResult(result);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,33 @@
using Microsoft.AspNetCore.Razor.Test.Common;
using Microsoft.VisualStudio.LanguageServer.Protocol;
using Xunit;
using Microsoft.CodeAnalysis.Testing;
using Microsoft.CodeAnalysis.Razor.Workspaces.Extensions;
using System.Linq;
using Microsoft.VisualStudio.TestPlatform.Utilities;
using Microsoft.AspNetCore.Razor.Language;
using Xunit.Sdk;

namespace Microsoft.AspNetCore.Razor.LanguageServer.Completion.Delegation
{
[UseExportProvider]
public class DelegatedCompletionItemResolverTest : LanguageServerTestBase
{
public DelegatedCompletionItemResolverTest()
{
ClientCapabilities = new VSInternalClientCapabilities();
ClientCapabilities = new VSInternalClientCapabilities()
{
TextDocument = new()
{
Completion = new VSInternalCompletionSetting()
{
CompletionList = new()
{
Data = true,
}
}
}
};
var documentContext = TestDocumentContext.From("C:/path/to/file.cshtml");
CSharpCompletionParams = new DelegatedCompletionParams(documentContext.Identifier, new Position(10, 6), RazorLanguageKind.CSharp, new VSInternalCompletionContext(), ProvisionalTextEdit: null);
HtmlCompletionParams = new DelegatedCompletionParams(documentContext.Identifier, new Position(0, 0), RazorLanguageKind.Html, new VSInternalCompletionContext(), ProvisionalTextEdit: null);
Expand All @@ -37,7 +56,7 @@ public DelegatedCompletionItemResolverTest()
public async Task ResolveAsync_CanNotFindCompletionItem_Noops()
{
// Arrange
var server = TestItemResolverServer.Create();
var server = TestDelegatedCompletionItemResolverServer.Create();
var resolver = new DelegatedCompletionItemResolver(server);
var item = new VSInternalCompletionItem();
var notContainingCompletionList = new VSInternalCompletionList();
Expand All @@ -54,7 +73,7 @@ public async Task ResolveAsync_CanNotFindCompletionItem_Noops()
public async Task ResolveAsync_UnknownRequestContext_Noops()
{
// Arrange
var server = TestItemResolverServer.Create();
var server = TestDelegatedCompletionItemResolverServer.Create();
var resolver = new DelegatedCompletionItemResolver(server);
var item = new VSInternalCompletionItem();
var containingCompletionList = new VSInternalCompletionList() { Items = new[] { item, } };
Expand All @@ -71,7 +90,7 @@ public async Task ResolveAsync_UnknownRequestContext_Noops()
public async Task ResolveAsync_UsesItemsData()
{
// Arrange
var server = TestItemResolverServer.Create();
var server = TestDelegatedCompletionItemResolverServer.Create();
var resolver = new DelegatedCompletionItemResolver(server);
var expectedData = new object();
var item = new VSInternalCompletionItem()
Expand All @@ -92,7 +111,7 @@ public async Task ResolveAsync_UsesItemsData()
public async Task ResolveAsync_InheritsOriginalCompletionListData()
{
// Arrange
var server = TestItemResolverServer.Create();
var server = TestDelegatedCompletionItemResolverServer.Create();
var resolver = new DelegatedCompletionItemResolver(server);
var item = new VSInternalCompletionItem();
var containingCompletionList = new VSInternalCompletionList() { Items = new[] { item, }, Data = new object() };
Expand All @@ -109,29 +128,19 @@ public async Task ResolveAsync_InheritsOriginalCompletionListData()
[Fact]
public async Task ResolveAsync_CSharp_Resolves()
{
// Arrange
var expectedResolvedItem = new VSInternalCompletionItem();
var server = TestItemResolverServer.Create(expectedResolvedItem);
var resolver = new DelegatedCompletionItemResolver(server);
var item = new VSInternalCompletionItem();
var containingCompletionList = new VSInternalCompletionList() { Items = new[] { item, } };
var originalRequestContext = new DelegatedCompletionResolutionContext(CSharpCompletionParams, new object());

// Act
var resolvedItem = await resolver.ResolveAsync(item, containingCompletionList, originalRequestContext, ClientCapabilities, CancellationToken.None);
// Arrange & Act
var resolvedItem = await ResolveCompletionItemAsync("@$$", itemToResolve: "typeof", CancellationToken.None).ConfigureAwait(false);

// Assert
Assert.Same(CSharpCompletionParams.HostDocument, server.DelegatedParams.HostDocument);
Assert.Equal(RazorLanguageKind.CSharp, server.DelegatedParams.OriginatingKind);
Assert.Same(expectedResolvedItem, resolvedItem);
Assert.NotNull(resolvedItem.Description);
}

[Fact]
public async Task ResolveAsync_Html_Resolves()
{
// Arrange
var expectedResolvedItem = new VSInternalCompletionItem();
var server = TestItemResolverServer.Create(expectedResolvedItem);
var server = TestDelegatedCompletionItemResolverServer.Create(expectedResolvedItem);
var resolver = new DelegatedCompletionItemResolver(server);
var item = new VSInternalCompletionItem();
var containingCompletionList = new VSInternalCompletionList() { Items = new[] { item, } };
Expand All @@ -146,11 +155,66 @@ public async Task ResolveAsync_Html_Resolves()
Assert.Same(expectedResolvedItem, resolvedItem);
}

internal class TestItemResolverServer : TestOmnisharpLanguageServer
private async Task<VSInternalCompletionItem> ResolveCompletionItemAsync(string content, string itemToResolve, CancellationToken none)
{
TestFileMarkupParser.GetPosition(content, out var documentContent, out var cursorPosition);
var codeDocument = CreateCodeDocument(documentContent);
await using var csharpServer = await CreateCSharpServerAsync(codeDocument).ConfigureAwait(false);

var server = TestDelegatedCompletionItemResolverServer.Create(csharpServer);
var resolver = new DelegatedCompletionItemResolver(server);
var (containingCompletionList, csharpCompletionParams) = await GetCompletionListAndOriginalParamsAsync(cursorPosition, codeDocument, csharpServer).ConfigureAwait(false);

var originalRequestContext = new DelegatedCompletionResolutionContext(csharpCompletionParams, containingCompletionList.Data);
var item = (VSInternalCompletionItem)containingCompletionList.Items.FirstOrDefault(item => item.Label == itemToResolve);

if (item is null)
{
throw new XunitException($"Could not locate completion item '{item.Label}' for completion resolve test");
}

var resolvedItem = await resolver.ResolveAsync(item, containingCompletionList, originalRequestContext, ClientCapabilities, CancellationToken.None).ConfigureAwait(false);
return resolvedItem;
}

private async Task<CSharpTestLspServer> CreateCSharpServerAsync(RazorCodeDocument codeDocument)
{
var csharpSourceText = codeDocument.GetCSharpSourceText();
var csharpDocumentUri = new Uri("C:/path/to/file.razor__virtual.g.cs");
var serverCapabilities = new ServerCapabilities()
{
CompletionProvider = new CompletionOptions
{
ResolveProvider = true,
TriggerCharacters = new[] { " ", "(", "=", "#", ".", "<", "[", "{", "\"", "/", ":", "~" }
}
};
var csharpServer = await CSharpTestLspServerHelpers.CreateCSharpLspServerAsync(csharpSourceText, csharpDocumentUri, serverCapabilities).ConfigureAwait(false);

await csharpServer.OpenDocumentAsync(csharpDocumentUri, csharpSourceText.ToString()).ConfigureAwait(false);

return csharpServer;
}

private async Task<(VSInternalCompletionList, DelegatedCompletionParams)> GetCompletionListAndOriginalParamsAsync(
int cursorPosition,
RazorCodeDocument codeDocument,
CSharpTestLspServer csharpServer)
{
var completionContext = new VSInternalCompletionContext() { TriggerKind = CompletionTriggerKind.Invoked };
var documentContext = TestDocumentContext.From("C:/path/to/file.razor", codeDocument, hostDocumentVersion: 1337);
var provider = TestDelegatedCompletionListProvider.Create(csharpServer);

var completionList = await provider.GetCompletionListAsync(cursorPosition, completionContext, documentContext, ClientCapabilities, CancellationToken.None).ConfigureAwait(false);

return (completionList, provider.DelegatedParams);
}

internal class TestDelegatedCompletionItemResolverServer : TestOmnisharpLanguageServer
{
private readonly DelegatedCompletionResolveRequestHandler _requestHandler;
private readonly CompletionResolveRequestResponseFactory _requestHandler;

private TestItemResolverServer(DelegatedCompletionResolveRequestHandler requestHandler) : base(new Dictionary<string, Func<object, Task<object>>>()
private TestDelegatedCompletionItemResolverServer(CompletionResolveRequestResponseFactory requestHandler) : base(new Dictionary<string, Func<object, Task<object>>>()
{
[LanguageServerConstants.RazorCompletionResolveEndpointName] = requestHandler.OnDelegationAsync,
})
Expand All @@ -160,32 +224,74 @@ private TestItemResolverServer(DelegatedCompletionResolveRequestHandler requestH

public DelegatedCompletionItemResolveParams DelegatedParams => _requestHandler.DelegatedParams;

public static TestItemResolverServer Create(VSInternalCompletionItem resolveResponse = null)
public static TestDelegatedCompletionItemResolverServer Create(CSharpTestLspServer csharpServer)
{
var requestHandler = new DelegatedCSharpCompletionRequestHandler(csharpServer);
var provider = new TestDelegatedCompletionItemResolverServer(requestHandler);
return provider;
}

public static TestDelegatedCompletionItemResolverServer Create(VSInternalCompletionItem resolveResponse = null)
{
resolveResponse ??= new VSInternalCompletionItem();
var requestResponseFactory = new DelegatedCompletionResolveRequestHandler(resolveResponse);
var provider = new TestItemResolverServer(requestResponseFactory);
var requestResponseFactory = new StaticCompletionResolveRequestHandler(resolveResponse);
var provider = new TestDelegatedCompletionItemResolverServer(requestResponseFactory);
return provider;
}

private class DelegatedCompletionResolveRequestHandler
private class StaticCompletionResolveRequestHandler : CompletionResolveRequestResponseFactory
{
private readonly VSInternalCompletionItem _resolveResponse;
private DelegatedCompletionItemResolveParams _delegatedParams;

public DelegatedCompletionResolveRequestHandler(VSInternalCompletionItem resolveResponse)
public StaticCompletionResolveRequestHandler(VSInternalCompletionItem resolveResponse)
{
_resolveResponse = resolveResponse;
}

public DelegatedCompletionItemResolveParams DelegatedParams { get; private set; }
public override DelegatedCompletionItemResolveParams DelegatedParams => _delegatedParams;

public Task<object> OnDelegationAsync(object parameters)
public override Task<object> OnDelegationAsync(object parameters)
{
DelegatedParams = (DelegatedCompletionItemResolveParams)parameters;
var resolveParams = (DelegatedCompletionItemResolveParams)parameters;
_delegatedParams = resolveParams;

return Task.FromResult<object>(_resolveResponse);
}
}

private class DelegatedCSharpCompletionRequestHandler : CompletionResolveRequestResponseFactory
{
private readonly CSharpTestLspServer _csharpServer;
private DelegatedCompletionItemResolveParams _delegatedParams;

public DelegatedCSharpCompletionRequestHandler(CSharpTestLspServer csharpServer)
{
_csharpServer = csharpServer;
}

public override DelegatedCompletionItemResolveParams DelegatedParams => _delegatedParams;

public override async Task<object> OnDelegationAsync(object parameters)
{
var resolveParams = (DelegatedCompletionItemResolveParams)parameters;
_delegatedParams = resolveParams;

var resolvedCompletionItem = await _csharpServer.ExecuteRequestAsync<VSInternalCompletionItem, VSInternalCompletionItem>(
Methods.TextDocumentCompletionResolveName,
_delegatedParams.CompletionItem,
CancellationToken.None).ConfigureAwait(false);

return resolvedCompletionItem;
}
}

private abstract class CompletionResolveRequestResponseFactory
{
public abstract DelegatedCompletionItemResolveParams DelegatedParams { get; }

public abstract Task<object> OnDelegationAsync(object parameters);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ private async Task<VSInternalCompletionList> GetCompletionListAsync(string conte
}
};
await using var csharpServer = await CSharpTestLspServerHelpers.CreateCSharpLspServerAsync(
csharpSourceText, csharpDocumentUri, serverCapabilities, new EmptyMappingService()).ConfigureAwait(false);
csharpSourceText, csharpDocumentUri, serverCapabilities).ConfigureAwait(false);

await csharpServer.OpenDocumentAsync(csharpDocumentUri, csharpSourceText.ToString()).ConfigureAwait(false);

Expand All @@ -292,14 +292,5 @@ private async Task<VSInternalCompletionList> GetCompletionListAsync(string conte
var completionList = await provider.GetCompletionListAsync(absoluteIndex: cursorPosition, completionContext, documentContext, ClientCapabilities, CancellationToken.None);
return completionList;
}

private class EmptyMappingService : IRazorSpanMappingService
{
public Task<ImmutableArray<RazorMappedSpanResult>> MapSpansAsync(Document document, IEnumerable<TextSpan> spans, CancellationToken cancellationToken)
{
var result = Enumerable.Empty<RazorMappedSpanResult>().ToImmutableArray();
return Task.FromResult(result);
}
}
}
}

0 comments on commit 2a62139

Please sign in to comment.