Skip to content

Commit

Permalink
Migrate completion list provider tests to utilize C# server where app…
Browse files Browse the repository at this point in the history
…licable. (#6621)

- Found that for trigger character based tests completion results weren't being returned. Meaning, invoking completion at `@if (|)` wouldn't result in any completions. I tried this with a few different samples and all didn't work.
- Updated the delegated completion list provider to take in a CSharp server.
- Shamelessly stole 90% of this from @allisonchou
  • Loading branch information
NTaylorMullen authored Jul 22, 2022
1 parent 2c1e0e0 commit 405fbee
Show file tree
Hide file tree
Showing 5 changed files with 159 additions and 130 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public DelegatedCompletionItemResolverTest()

private DelegatedCompletionParams CSharpCompletionParams { get; }

internal DelegatedCompletionParams HtmlCompletionParams { get; }
private DelegatedCompletionParams HtmlCompletionParams { get; }

[Fact]
public async Task ResolveAsync_CanNotFindCompletionItem_Noops()
Expand Down Expand Up @@ -150,9 +150,9 @@ internal class TestItemResolverServer : TestOmnisharpLanguageServer
{
private readonly DelegatedCompletionResolveRequestHandler _requestHandler;

private TestItemResolverServer(DelegatedCompletionResolveRequestHandler requestHandler) : base(new Dictionary<string, Func<object, object>>()
private TestItemResolverServer(DelegatedCompletionResolveRequestHandler requestHandler) : base(new Dictionary<string, Func<object, Task<object>>>()
{
[LanguageServerConstants.RazorCompletionResolveEndpointName] = requestHandler.OnDelegation,
[LanguageServerConstants.RazorCompletionResolveEndpointName] = requestHandler.OnDelegationAsync,
})
{
_requestHandler = requestHandler;
Expand All @@ -179,11 +179,11 @@ public DelegatedCompletionResolveRequestHandler(VSInternalCompletionItem resolve

public DelegatedCompletionItemResolveParams DelegatedParams { get; private set; }

public object OnDelegation(object parameters)
public Task<object> OnDelegationAsync(object parameters)
{
DelegatedParams = (DelegatedCompletionItemResolveParams)parameters;

return _resolveResponse;
return Task.FromResult<object>(_resolveResponse);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,31 @@

#nullable disable

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.Protocol;
using Microsoft.AspNetCore.Razor.LanguageServer.Test.Common;
using Microsoft.AspNetCore.Razor.Test.Common;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.ExternalAccess.Razor;
using Microsoft.CodeAnalysis.Razor.Workspaces.Extensions;
using Microsoft.CodeAnalysis.Testing;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.LanguageServer.Protocol;
using Xunit;

namespace Microsoft.AspNetCore.Razor.LanguageServer.Completion.Delegation
{
[UseExportProvider]
public class DelegatedCompletionListProviderTest : LanguageServerTestBase
{
public DelegatedCompletionListProviderTest()
{
Provider = TestDelegatedCompletionListProvider.Create(LoggerFactory);
Provider = TestDelegatedCompletionListProvider.Create();
ClientCapabilities = new VSInternalClientCapabilities();
}

Expand All @@ -35,7 +44,7 @@ public async Task ResponseRewritersGetExecutedInOrder()
var documentContext = TestDocumentContext.From("C:/path/to/file.cshtml", codeDocument);
var rewriter1 = new TestResponseRewriter(order: 100);
var rewriter2 = new TestResponseRewriter(order: 20);
var provider = TestDelegatedCompletionListProvider.Create(LoggerFactory, rewriter1, rewriter2);
var provider = TestDelegatedCompletionListProvider.Create(rewriter1, rewriter2);

// Act
var completionList = await provider.GetCompletionListAsync(absoluteIndex: 1, completionContext, documentContext, ClientCapabilities, CancellationToken.None);
Expand Down Expand Up @@ -122,130 +131,50 @@ public async Task HtmlDelegation_UnsupportedTriggerCharacter_TranslatesToInvoked
}

[Fact]
public async Task CSharpDelegation_Invoked()
public async Task CSharp_Invoked()
{
// Arrange
var completionContext = new VSInternalCompletionContext() { TriggerKind = CompletionTriggerKind.Invoked };
var codeDocument = CreateCodeDocument("@");
var documentContext = TestDocumentContext.From("C:/path/to/file.cshtml", codeDocument, hostDocumentVersion: 1337);

// Act
await Provider.GetCompletionListAsync(absoluteIndex: 1, completionContext, documentContext, ClientCapabilities, CancellationToken.None);

// Assert
var delegatedParameters = Provider.DelegatedParams;
Assert.NotNull(delegatedParameters);
Assert.Equal(RazorLanguageKind.CSharp, delegatedParameters.ProjectedKind);

// Just validating that we're generating code in a way that's different from the top-level document. Don't need to be specific.
Assert.True(delegatedParameters.ProjectedPosition.Line > 2);
Assert.Equal(CompletionTriggerKind.Invoked, delegatedParameters.Context.TriggerKind);
Assert.Equal(1337, delegatedParameters.HostDocument.Version);
Assert.Null(delegatedParameters.ProvisionalTextEdit);
}

[Fact]
public async Task RazorDelegation_Noop()
{
// Arrange
var completionContext = new VSInternalCompletionContext() { TriggerKind = CompletionTriggerKind.Invoked };
var codeDocument = CreateCodeDocument("@functions ");
var documentContext = TestDocumentContext.From("C:/path/to/file.razor", codeDocument, hostDocumentVersion: 1337);

// Act
var completionList = await Provider.GetCompletionListAsync(absoluteIndex: 11, completionContext, documentContext, ClientCapabilities, CancellationToken.None);
// Arrange & Act
var completionList = await GetCompletionListAsync("@$$", CompletionTriggerKind.Invoked);

// Assert
Assert.Null(completionList);
var delegatedParameters = Provider.DelegatedParams;
Assert.Null(delegatedParameters);
Assert.Contains(completionList.Items, item => item.Label == "System");
}

[Fact]
public async Task CSharpDelegation_TriggerCharacterAt_TranslatesToInvoked()
public async Task CSharp_At_TranslatesToInvoked_Triggered()
{
// Arrange
var completionContext = new VSInternalCompletionContext()
{
InvokeKind = VSInternalCompletionInvokeKind.Typing,
TriggerKind = CompletionTriggerKind.TriggerCharacter,
TriggerCharacter = "@",
};
var codeDocument = CreateCodeDocument("@");
var documentContext = TestDocumentContext.From("C:/path/to/file.cshtml", codeDocument, hostDocumentVersion: 1337);

// Act
await Provider.GetCompletionListAsync(absoluteIndex: 1, completionContext, documentContext, ClientCapabilities, CancellationToken.None);
// Arrange & Act
var completionList = await GetCompletionListAsync("@$$", CompletionTriggerKind.TriggerCharacter);

// Assert
var delegatedParameters = Provider.DelegatedParams;
Assert.NotNull(delegatedParameters);
Assert.Equal(RazorLanguageKind.CSharp, delegatedParameters.ProjectedKind);

// Just validating that we're generating code in a way that's different from the top-level document. Don't need to be specific.
Assert.True(delegatedParameters.ProjectedPosition.Line > 2);
Assert.Equal(CompletionTriggerKind.Invoked, delegatedParameters.Context.TriggerKind);
Assert.Equal(VSInternalCompletionInvokeKind.Explicit, delegatedParameters.Context.InvokeKind);
Assert.Equal(1337, delegatedParameters.HostDocument.Version);
Assert.Null(delegatedParameters.ProvisionalTextEdit);
Assert.Contains(completionList.Items, item => item.Label == "System");
}

[Fact]
public async Task CSharpDelegation_TriggerCharacter()
[Fact(Skip = "For some reason trigger based completion items aren't returning any items")]
public async Task CSharp_Operator_Triggered()
{
// Arrange
var completionContext = new VSInternalCompletionContext()
{
InvokeKind = VSInternalCompletionInvokeKind.Typing,
TriggerKind = CompletionTriggerKind.TriggerCharacter,
TriggerCharacter = ".",
};
var codeDocument = CreateCodeDocument("@{ var abc = DateTime.;}");
var documentContext = TestDocumentContext.From("C:/path/to/file.cshtml", codeDocument, hostDocumentVersion: 1337);

// Act
await Provider.GetCompletionListAsync(absoluteIndex: 22, completionContext, documentContext, ClientCapabilities, CancellationToken.None);
// Arrange & Act
var completionList = await GetCompletionListAsync("@if ($$)", CompletionTriggerKind.TriggerCharacter);

// Assert
var delegatedParameters = Provider.DelegatedParams;
Assert.NotNull(delegatedParameters);
Assert.Equal(RazorLanguageKind.CSharp, delegatedParameters.ProjectedKind);

// Just validating that we're generating code in a way that's different from the top-level document. Don't need to be specific.
Assert.True(delegatedParameters.ProjectedPosition.Line > 2);
Assert.Equal(CompletionTriggerKind.TriggerCharacter, delegatedParameters.Context.TriggerKind);
Assert.Equal(VSInternalCompletionInvokeKind.Typing, delegatedParameters.Context.InvokeKind);
Assert.Equal(1337, delegatedParameters.HostDocument.Version);
Assert.Null(delegatedParameters.ProvisionalTextEdit);
Assert.Contains(completionList.Items, item => item.Label == "true");
}

[Fact]
public async Task CSharpDelegation_UnsupportedTriggerCharacter_TranslatesToInvoked()
public async Task RazorDelegation_Noop()
{
// Arrange
var completionContext = new VSInternalCompletionContext()
{
InvokeKind= VSInternalCompletionInvokeKind.Typing,
TriggerKind = CompletionTriggerKind.TriggerCharacter,
TriggerCharacter = "o",
};
var codeDocument = CreateCodeDocument("@{ var abc = DateTime.No;}");
var documentContext = TestDocumentContext.From("C:/path/to/file.cshtml", codeDocument, hostDocumentVersion: 1337);
var completionContext = new VSInternalCompletionContext() { TriggerKind = CompletionTriggerKind.Invoked };
var codeDocument = CreateCodeDocument("@functions ");
var documentContext = TestDocumentContext.From("C:/path/to/file.razor", codeDocument, hostDocumentVersion: 1337);

// Act
await Provider.GetCompletionListAsync(absoluteIndex: 24, completionContext, documentContext, ClientCapabilities, CancellationToken.None);
var completionList = await Provider.GetCompletionListAsync(absoluteIndex: 11, completionContext, documentContext, ClientCapabilities, CancellationToken.None);

// Assert
Assert.Null(completionList);
var delegatedParameters = Provider.DelegatedParams;
Assert.NotNull(delegatedParameters);
Assert.Equal(RazorLanguageKind.CSharp, delegatedParameters.ProjectedKind);

// Just validating that we're generating code in a way that's different from the top-level document. Don't need to be specific.
Assert.True(delegatedParameters.ProjectedPosition.Line > 2);
Assert.Equal(CompletionTriggerKind.Invoked, delegatedParameters.Context.TriggerKind);
Assert.Equal(VSInternalCompletionInvokeKind.Typing, delegatedParameters.Context.InvokeKind);
Assert.Equal(1337, delegatedParameters.HostDocument.Version);
Assert.Null(delegatedParameters.ProvisionalTextEdit);
Assert.Null(delegatedParameters);
}

[Fact]
Expand Down Expand Up @@ -328,5 +257,49 @@ public override Task<VSInternalCompletionList> RewriteAsync(VSInternalCompletion
return Task.FromResult(completionList);
}
}

private async Task<VSInternalCompletionList> GetCompletionListAsync(string content, CompletionTriggerKind triggerKind)
{
TestFileMarkupParser.GetPosition(content, out var output, out var cursorPosition);
var codeDocument = CreateCodeDocument(output);
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[] { " ", "(", "=", "#", ".", "<", "[", "{", "\"", "/", ":", "~" }
}
};
await using var csharpServer = await CSharpTestLspServerHelpers.CreateCSharpLspServerAsync(
csharpSourceText, csharpDocumentUri, serverCapabilities, new EmptyMappingService()).ConfigureAwait(false);

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

var triggerCharacter = triggerKind == CompletionTriggerKind.TriggerCharacter ? output[cursorPosition - 1].ToString() : null;
var invocationKind = triggerKind == CompletionTriggerKind.TriggerCharacter ? VSInternalCompletionInvokeKind.Typing : VSInternalCompletionInvokeKind.Explicit;

var completionContext = new VSInternalCompletionContext()
{
TriggerKind = triggerKind,
TriggerCharacter = triggerCharacter,
InvokeKind = invocationKind,
};
var documentContext = TestDocumentContext.From("C:/path/to/file.razor", codeDocument, hostDocumentVersion: 1337);
var provider = TestDelegatedCompletionListProvider.Create(csharpServer);

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);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ protected async Task<VSInternalCompletionList> GetRewrittenCompletionListAsync(i
var completionContext = new VSInternalCompletionContext();
var codeDocument = CreateCodeDocument(documentContent);
var documentContext = TestDocumentContext.From("C:/path/to/file.cshtml", codeDocument);
var provider = TestDelegatedCompletionListProvider.Create(LoggerFactory, initialCompletionList, Rewriter);
var provider = TestDelegatedCompletionListProvider.Create(initialCompletionList, Rewriter);
var clientCapabilities = new VSInternalClientCapabilities();
var completionList = await provider.GetCompletionListAsync(absoluteIndex, completionContext, documentContext, clientCapabilities, CancellationToken.None);
return completionList;
Expand Down
Loading

0 comments on commit 405fbee

Please sign in to comment.