Skip to content

Commit

Permalink
Cohosting folding range tests (#10638)
Browse files Browse the repository at this point in the history
Part of #9519 and
#10603

This time I did copy the existing tests, because everything was inline
so it was easier.
  • Loading branch information
davidwengier authored Jul 17, 2024
2 parents fa94d4c + e2549ad commit 850778b
Show file tree
Hide file tree
Showing 8 changed files with 343 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,11 @@ internal class CohostFoldingRangeEndpoint(
protected override RazorTextDocumentIdentifier? GetRazorTextDocumentIdentifier(FoldingRangeParams request)
=> request.TextDocument.ToRazorTextDocumentIdentifier();

protected override async Task<FoldingRange[]?> HandleRequestAsync(FoldingRangeParams request, RazorCohostRequestContext context, CancellationToken cancellationToken)
{
var razorDocument = context.TextDocument.AssumeNotNull();
protected override Task<FoldingRange[]?> HandleRequestAsync(FoldingRangeParams request, RazorCohostRequestContext context, CancellationToken cancellationToken)
=> HandleRequestAsync(context.TextDocument.AssumeNotNull(), cancellationToken);

private async Task<FoldingRange[]?> HandleRequestAsync(TextDocument razorDocument, CancellationToken cancellationToken)
{
_logger.LogDebug($"Getting folding ranges for {razorDocument.FilePath}");
// TODO: Should we have a separate method/service for getting C# ranges, so we can kick off both tasks in parallel? Or are we better off transition to OOP once?
var htmlRangesResult = await GetHtmlFoldingRangesAsync(razorDocument, cancellationToken).ConfigureAwait(false);
Expand Down Expand Up @@ -123,5 +124,13 @@ internal class CohostFoldingRangeEndpoint(

return result.Response.SelectAsArray(RemoteFoldingRange.FromLspFoldingRange);
}

internal TestAccessor GetTestAccessor() => new(this);

internal readonly struct TestAccessor(CohostFoldingRangeEndpoint instance)
{
public Task<FoldingRange[]?> HandleRequestAsync(TextDocument razorDocument, CancellationToken cancellationToken)
=> instance.HandleRequestAsync(razorDocument, cancellationToken);
}
}

Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.

using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.ProjectEngineHost;
using Microsoft.AspNetCore.Razor.Test.Common.ProjectSystem;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Razor.Workspaces;
using Xunit.Abstractions;

Expand Down Expand Up @@ -63,14 +63,6 @@ private protected TestProjectSnapshotManager CreateProjectSnapshotManager()
private protected TestProjectSnapshotManager CreateProjectSnapshotManager(IProjectEngineFactoryProvider projectEngineFactoryProvider)
=> new(projectEngineFactoryProvider, LoggerFactory, DisposalToken);

protected virtual void ConfigureWorkspaceServices(List<IWorkspaceService> services)
{
}

protected virtual void ConfigureLanguageServices(List<ILanguageService> services)
{
}

protected virtual void ConfigureWorkspace(AdhocWorkspace workspace)
{
}
Expand All @@ -96,13 +88,7 @@ private void EnsureInitialized()
Configure = ConfigureProjectEngine,
};

var workspaceServices = new List<IWorkspaceService>();
ConfigureWorkspaceServices(workspaceServices);

var languageServices = new List<ILanguageService>();
ConfigureLanguageServices(languageServices);

_hostServices = TestServices.Create(workspaceServices, languageServices);
_hostServices = MefHostServices.DefaultHost;
_workspace = TestWorkspace.Create(_hostServices, ConfigureWorkspace);
AddDisposable(_workspace);
_workspaceProvider = new TestWorkspaceProvider(_workspace);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost;

public abstract class CohostTestBase(ITestOutputHelper testOutputHelper) : WorkspaceTestBase(testOutputHelper)
public abstract class CohostEndpointTestBase(ITestOutputHelper testOutputHelper) : WorkspaceTestBase(testOutputHelper)
{
private const string CSharpVirtualDocumentSuffix = ".g.cs";
private ExportProvider? _exportProvider;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,289 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.

using System.Collections.Immutable;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.PooledObjects;
using Microsoft.CodeAnalysis.Razor.Workspaces;
using Microsoft.CodeAnalysis.Testing;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.LanguageServer.Protocol;
using Roslyn.Test.Utilities;
using Xunit;
using Xunit.Abstractions;

namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost;

public class CohostFoldingRangeEndpointTest(ITestOutputHelper testOutputHelper) : CohostEndpointTestBase(testOutputHelper)
{
[Fact]
public Task IfStatements()
=> VerifyFoldingRangesAsync("""
<div>
@if (true) {[|
<div>
Hello World
</div>
}|]
</div>

@if (true) {[|
<div>
Hello World
</div>
}|]

@if (true) {[|
}|]
""");

[Fact]
public Task LockStatement()
=> VerifyFoldingRangesAsync("""
@lock (new object()) {[|
}|]
""");

[Fact]
public Task UsingStatement()
=> VerifyFoldingRangesAsync("""
@using (new object()) {[|
}|]
""");

[Fact]
public Task IfElseStatements()
=> VerifyFoldingRangesAsync("""
<div>
@if (true) {[|
<div>
Hello World
</div>
else {[|
<div>
Goodbye World
</div>
}|]
}|]
</div>
""");

[Fact]
public Task Usings()
=> VerifyFoldingRangesAsync("""
@using System[|
@using System.Text|]

<p>hello!</p>

@using System.Buffers[|
@using System.Drawing
@using System.CodeDom|]

<p>hello!</p>
""");

[Fact]
public Task CSharpStatement()
=> VerifyFoldingRangesAsync("""
<p>hello!</p>

@{[|
var helloWorld = "";
}|]

@(DateTime
.Now)

<p>hello!</p>
""");

[Fact]
public Task CSharpStatement_Nested()
=> VerifyFoldingRangesAsync("""
<p>hello!</p>

<div>

@{[|
var helloWorld = "";
}|]

</div>

@(DateTime
.Now)

<p>hello!</p>
""");

[Fact]
public Task CSharpStatement_NotSingleLine()
=> VerifyFoldingRangesAsync("""
<p>hello!</p>

@{ var helloWorld = ""; }

<p>hello!</p>
""");

[Fact]
public Task CodeBlock()
=> VerifyFoldingRangesAsync("""
<p>hello!</p>

@code {[|
var helloWorld = "";
}|]

<p>hello!</p>
""");

[Fact]
public Task CodeBlock_Mvc()
=> VerifyFoldingRangesAsync("""
<p>hello!</p>

@functions {[|
var helloWorld = "";
}|]

<p>hello!</p>
""",
fileKind: FileKinds.Legacy);

[Fact]
public Task Section()
=> VerifyFoldingRangesAsync("""
<p>hello!</p>

@section Hello {[|
<p>Hello</p>
}|]

<p>hello!</p>
""",
fileKind: FileKinds.Legacy);

[Fact]
public Task Section_Invalid()
=> VerifyFoldingRangesAsync("""
<p>hello!</p>

@section {
<p>Hello</p>
}

<p>hello!</p>
""",
fileKind: FileKinds.Legacy);

[Fact]
public Task CSharpCodeInCodeBlocks()
=> VerifyFoldingRangesAsync("""
<div>
Hello @_name
</div>

@code {[|
private string _name = "Dave";

public void M() {[|
}|]
}|]
""");

[Fact]
public Task HtmlAndCSharp()
=> VerifyFoldingRangesAsync("""
<div>{|html:
Hello @_name

<div>{|html:
Nests aren't just for birds!
</div>|}
</div>|}

@code {[|
private string _name = "Dave";

public void M() {[|
}|]
}|]
""");

private async Task VerifyFoldingRangesAsync(string input, string? fileKind = null)
{
TestFileMarkupParser.GetSpans(input, out var source, out ImmutableDictionary<string, ImmutableArray<TextSpan>> spans);
var document = CreateProjectAndRazorDocument(source, fileKind);
var inputText = await document.GetTextAsync(DisposalToken);

var htmlSpans = spans.GetValueOrDefault("html").NullToEmpty();
var htmlRanges = htmlSpans
.Select(span =>
{
inputText.GetLineAndOffset(span.Start, out var startLine, out var startCharacter);
inputText.GetLineAndOffset(span.End, out var endLine, out var endCharacter);
return new FoldingRange()
{
StartLine = startLine,
StartCharacter = startCharacter,
EndLine = endLine,
EndCharacter = endCharacter
};
})
.ToArray();

var requestInvoker = new TestLSPRequestInvoker([(Methods.TextDocumentFoldingRangeName, htmlRanges)]);

var endpoint = new CohostFoldingRangeEndpoint(RemoteServiceInvoker, TestHtmlDocumentSynchronizer.Instance, requestInvoker, LoggerFactory);

var result = await endpoint.GetTestAccessor().HandleRequestAsync(document, DisposalToken);

if (spans.Count == 0)
{
Assert.Null(result);
return;
}

var actual = GenerateTestInput(inputText, htmlSpans, result.AssumeNotNull());

AssertEx.EqualOrDiff(input, actual);
}

private static string GenerateTestInput(SourceText inputText, ImmutableArray<TextSpan> htmlSpans, FoldingRange[] result)
{
var markerPositions = result
.SelectMany(r =>
new[] {
(index: inputText.GetRequiredAbsoluteIndex(r.StartLine, r.StartCharacter.AssumeNotNull()), isStart: true),
(index: inputText.GetRequiredAbsoluteIndex(r.EndLine, r.EndCharacter.AssumeNotNull()), isStart: false)
});

var actual = new StringBuilder(inputText.ToString());
foreach (var marker in markerPositions.OrderByDescending(p => p.index))
{
actual.Insert(marker.index, GetMarker(marker.index, marker.isStart, htmlSpans));
}

static string GetMarker(int index, bool isStart, ImmutableArray<TextSpan> htmlSpans)
{
if (isStart)
{
return htmlSpans.Any(r => r.Start == index)
? "{|html:"
: "[|";
}

return htmlSpans.Any(r => r.End == index)
? "|}"
: "|]";
}

return actual.ToString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost;

public class CohostLinkedEditingRangeEndpointTest(ITestOutputHelper testOutputHelper) : CohostTestBase(testOutputHelper)
public class CohostLinkedEditingRangeEndpointTest(ITestOutputHelper testOutputHelper) : CohostEndpointTestBase(testOutputHelper)
{
[Theory]
[InlineData("$$PageTitle", "PageTitle")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost;

public class CohostSemanticTokensRangeEndpointTest(ITestOutputHelper testOutputHelper) : CohostTestBase(testOutputHelper)
public class CohostSemanticTokensRangeEndpointTest(ITestOutputHelper testOutputHelper) : CohostEndpointTestBase(testOutputHelper)
{
[Theory]
[CombinatorialData]
Expand Down
Loading

0 comments on commit 850778b

Please sign in to comment.