Skip to content

Commit 850778b

Browse files
authored
Cohosting folding range tests (#10638)
Part of #9519 and #10603 This time I did copy the existing tests, because everything was inline so it was easier.
2 parents fa94d4c + e2549ad commit 850778b

File tree

8 files changed

+343
-22
lines changed

8 files changed

+343
-22
lines changed

src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostFoldingRangeEndpoint.cs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,11 @@ internal class CohostFoldingRangeEndpoint(
6262
protected override RazorTextDocumentIdentifier? GetRazorTextDocumentIdentifier(FoldingRangeParams request)
6363
=> request.TextDocument.ToRazorTextDocumentIdentifier();
6464

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

68+
private async Task<FoldingRange[]?> HandleRequestAsync(TextDocument razorDocument, CancellationToken cancellationToken)
69+
{
6970
_logger.LogDebug($"Getting folding ranges for {razorDocument.FilePath}");
7071
// 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?
7172
var htmlRangesResult = await GetHtmlFoldingRangesAsync(razorDocument, cancellationToken).ConfigureAwait(false);
@@ -123,5 +124,13 @@ internal class CohostFoldingRangeEndpoint(
123124

124125
return result.Response.SelectAsArray(RemoteFoldingRange.FromLspFoldingRange);
125126
}
127+
128+
internal TestAccessor GetTestAccessor() => new(this);
129+
130+
internal readonly struct TestAccessor(CohostFoldingRangeEndpoint instance)
131+
{
132+
public Task<FoldingRange[]?> HandleRequestAsync(TextDocument razorDocument, CancellationToken cancellationToken)
133+
=> instance.HandleRequestAsync(razorDocument, cancellationToken);
134+
}
126135
}
127136

src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common.Tooling/Workspaces/WorkspaceTestBase.cs

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the MIT license. See License.txt in the project root for license information.
33

4-
using System.Collections.Generic;
54
using System.Diagnostics.CodeAnalysis;
65
using Microsoft.AspNetCore.Razor.Language;
76
using Microsoft.AspNetCore.Razor.ProjectEngineHost;
87
using Microsoft.AspNetCore.Razor.Test.Common.ProjectSystem;
98
using Microsoft.CodeAnalysis;
109
using Microsoft.CodeAnalysis.Host;
10+
using Microsoft.CodeAnalysis.Host.Mef;
1111
using Microsoft.CodeAnalysis.Razor.Workspaces;
1212
using Xunit.Abstractions;
1313

@@ -63,14 +63,6 @@ private protected TestProjectSnapshotManager CreateProjectSnapshotManager()
6363
private protected TestProjectSnapshotManager CreateProjectSnapshotManager(IProjectEngineFactoryProvider projectEngineFactoryProvider)
6464
=> new(projectEngineFactoryProvider, LoggerFactory, DisposalToken);
6565

66-
protected virtual void ConfigureWorkspaceServices(List<IWorkspaceService> services)
67-
{
68-
}
69-
70-
protected virtual void ConfigureLanguageServices(List<ILanguageService> services)
71-
{
72-
}
73-
7466
protected virtual void ConfigureWorkspace(AdhocWorkspace workspace)
7567
{
7668
}
@@ -96,13 +88,7 @@ private void EnsureInitialized()
9688
Configure = ConfigureProjectEngine,
9789
};
9890

99-
var workspaceServices = new List<IWorkspaceService>();
100-
ConfigureWorkspaceServices(workspaceServices);
101-
102-
var languageServices = new List<ILanguageService>();
103-
ConfigureLanguageServices(languageServices);
104-
105-
_hostServices = TestServices.Create(workspaceServices, languageServices);
91+
_hostServices = MefHostServices.DefaultHost;
10692
_workspace = TestWorkspace.Create(_hostServices, ConfigureWorkspace);
10793
AddDisposable(_workspace);
10894
_workspaceProvider = new TestWorkspaceProvider(_workspace);

src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostTestBase.cs renamed to src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostEndpointTestBase.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost;
2121

22-
public abstract class CohostTestBase(ITestOutputHelper testOutputHelper) : WorkspaceTestBase(testOutputHelper)
22+
public abstract class CohostEndpointTestBase(ITestOutputHelper testOutputHelper) : WorkspaceTestBase(testOutputHelper)
2323
{
2424
private const string CSharpVirtualDocumentSuffix = ".g.cs";
2525
private ExportProvider? _exportProvider;
Lines changed: 289 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,289 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT license. See License.txt in the project root for license information.
3+
4+
using System.Collections.Immutable;
5+
using System.Linq;
6+
using System.Text;
7+
using System.Threading.Tasks;
8+
using Microsoft.AspNetCore.Razor;
9+
using Microsoft.AspNetCore.Razor.Language;
10+
using Microsoft.AspNetCore.Razor.PooledObjects;
11+
using Microsoft.CodeAnalysis.Razor.Workspaces;
12+
using Microsoft.CodeAnalysis.Testing;
13+
using Microsoft.CodeAnalysis.Text;
14+
using Microsoft.VisualStudio.LanguageServer.Protocol;
15+
using Roslyn.Test.Utilities;
16+
using Xunit;
17+
using Xunit.Abstractions;
18+
19+
namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost;
20+
21+
public class CohostFoldingRangeEndpointTest(ITestOutputHelper testOutputHelper) : CohostEndpointTestBase(testOutputHelper)
22+
{
23+
[Fact]
24+
public Task IfStatements()
25+
=> VerifyFoldingRangesAsync("""
26+
<div>
27+
@if (true) {[|
28+
<div>
29+
Hello World
30+
</div>
31+
}|]
32+
</div>
33+
34+
@if (true) {[|
35+
<div>
36+
Hello World
37+
</div>
38+
}|]
39+
40+
@if (true) {[|
41+
}|]
42+
""");
43+
44+
[Fact]
45+
public Task LockStatement()
46+
=> VerifyFoldingRangesAsync("""
47+
@lock (new object()) {[|
48+
}|]
49+
""");
50+
51+
[Fact]
52+
public Task UsingStatement()
53+
=> VerifyFoldingRangesAsync("""
54+
@using (new object()) {[|
55+
}|]
56+
""");
57+
58+
[Fact]
59+
public Task IfElseStatements()
60+
=> VerifyFoldingRangesAsync("""
61+
<div>
62+
@if (true) {[|
63+
<div>
64+
Hello World
65+
</div>
66+
else {[|
67+
<div>
68+
Goodbye World
69+
</div>
70+
}|]
71+
}|]
72+
</div>
73+
""");
74+
75+
[Fact]
76+
public Task Usings()
77+
=> VerifyFoldingRangesAsync("""
78+
@using System[|
79+
@using System.Text|]
80+
81+
<p>hello!</p>
82+
83+
@using System.Buffers[|
84+
@using System.Drawing
85+
@using System.CodeDom|]
86+
87+
<p>hello!</p>
88+
""");
89+
90+
[Fact]
91+
public Task CSharpStatement()
92+
=> VerifyFoldingRangesAsync("""
93+
<p>hello!</p>
94+
95+
@{[|
96+
var helloWorld = "";
97+
}|]
98+
99+
@(DateTime
100+
.Now)
101+
102+
<p>hello!</p>
103+
""");
104+
105+
[Fact]
106+
public Task CSharpStatement_Nested()
107+
=> VerifyFoldingRangesAsync("""
108+
<p>hello!</p>
109+
110+
<div>
111+
112+
@{[|
113+
var helloWorld = "";
114+
}|]
115+
116+
</div>
117+
118+
@(DateTime
119+
.Now)
120+
121+
<p>hello!</p>
122+
""");
123+
124+
[Fact]
125+
public Task CSharpStatement_NotSingleLine()
126+
=> VerifyFoldingRangesAsync("""
127+
<p>hello!</p>
128+
129+
@{ var helloWorld = ""; }
130+
131+
<p>hello!</p>
132+
""");
133+
134+
[Fact]
135+
public Task CodeBlock()
136+
=> VerifyFoldingRangesAsync("""
137+
<p>hello!</p>
138+
139+
@code {[|
140+
var helloWorld = "";
141+
}|]
142+
143+
<p>hello!</p>
144+
""");
145+
146+
[Fact]
147+
public Task CodeBlock_Mvc()
148+
=> VerifyFoldingRangesAsync("""
149+
<p>hello!</p>
150+
151+
@functions {[|
152+
var helloWorld = "";
153+
}|]
154+
155+
<p>hello!</p>
156+
""",
157+
fileKind: FileKinds.Legacy);
158+
159+
[Fact]
160+
public Task Section()
161+
=> VerifyFoldingRangesAsync("""
162+
<p>hello!</p>
163+
164+
@section Hello {[|
165+
<p>Hello</p>
166+
}|]
167+
168+
<p>hello!</p>
169+
""",
170+
fileKind: FileKinds.Legacy);
171+
172+
[Fact]
173+
public Task Section_Invalid()
174+
=> VerifyFoldingRangesAsync("""
175+
<p>hello!</p>
176+
177+
@section {
178+
<p>Hello</p>
179+
}
180+
181+
<p>hello!</p>
182+
""",
183+
fileKind: FileKinds.Legacy);
184+
185+
[Fact]
186+
public Task CSharpCodeInCodeBlocks()
187+
=> VerifyFoldingRangesAsync("""
188+
<div>
189+
Hello @_name
190+
</div>
191+
192+
@code {[|
193+
private string _name = "Dave";
194+
195+
public void M() {[|
196+
}|]
197+
}|]
198+
""");
199+
200+
[Fact]
201+
public Task HtmlAndCSharp()
202+
=> VerifyFoldingRangesAsync("""
203+
<div>{|html:
204+
Hello @_name
205+
206+
<div>{|html:
207+
Nests aren't just for birds!
208+
</div>|}
209+
</div>|}
210+
211+
@code {[|
212+
private string _name = "Dave";
213+
214+
public void M() {[|
215+
}|]
216+
}|]
217+
""");
218+
219+
private async Task VerifyFoldingRangesAsync(string input, string? fileKind = null)
220+
{
221+
TestFileMarkupParser.GetSpans(input, out var source, out ImmutableDictionary<string, ImmutableArray<TextSpan>> spans);
222+
var document = CreateProjectAndRazorDocument(source, fileKind);
223+
var inputText = await document.GetTextAsync(DisposalToken);
224+
225+
var htmlSpans = spans.GetValueOrDefault("html").NullToEmpty();
226+
var htmlRanges = htmlSpans
227+
.Select(span =>
228+
{
229+
inputText.GetLineAndOffset(span.Start, out var startLine, out var startCharacter);
230+
inputText.GetLineAndOffset(span.End, out var endLine, out var endCharacter);
231+
return new FoldingRange()
232+
{
233+
StartLine = startLine,
234+
StartCharacter = startCharacter,
235+
EndLine = endLine,
236+
EndCharacter = endCharacter
237+
};
238+
})
239+
.ToArray();
240+
241+
var requestInvoker = new TestLSPRequestInvoker([(Methods.TextDocumentFoldingRangeName, htmlRanges)]);
242+
243+
var endpoint = new CohostFoldingRangeEndpoint(RemoteServiceInvoker, TestHtmlDocumentSynchronizer.Instance, requestInvoker, LoggerFactory);
244+
245+
var result = await endpoint.GetTestAccessor().HandleRequestAsync(document, DisposalToken);
246+
247+
if (spans.Count == 0)
248+
{
249+
Assert.Null(result);
250+
return;
251+
}
252+
253+
var actual = GenerateTestInput(inputText, htmlSpans, result.AssumeNotNull());
254+
255+
AssertEx.EqualOrDiff(input, actual);
256+
}
257+
258+
private static string GenerateTestInput(SourceText inputText, ImmutableArray<TextSpan> htmlSpans, FoldingRange[] result)
259+
{
260+
var markerPositions = result
261+
.SelectMany(r =>
262+
new[] {
263+
(index: inputText.GetRequiredAbsoluteIndex(r.StartLine, r.StartCharacter.AssumeNotNull()), isStart: true),
264+
(index: inputText.GetRequiredAbsoluteIndex(r.EndLine, r.EndCharacter.AssumeNotNull()), isStart: false)
265+
});
266+
267+
var actual = new StringBuilder(inputText.ToString());
268+
foreach (var marker in markerPositions.OrderByDescending(p => p.index))
269+
{
270+
actual.Insert(marker.index, GetMarker(marker.index, marker.isStart, htmlSpans));
271+
}
272+
273+
static string GetMarker(int index, bool isStart, ImmutableArray<TextSpan> htmlSpans)
274+
{
275+
if (isStart)
276+
{
277+
return htmlSpans.Any(r => r.Start == index)
278+
? "{|html:"
279+
: "[|";
280+
}
281+
282+
return htmlSpans.Any(r => r.End == index)
283+
? "|}"
284+
: "|]";
285+
}
286+
287+
return actual.ToString();
288+
}
289+
}

src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostLinkedEditingRangeEndpointTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost;
1616

17-
public class CohostLinkedEditingRangeEndpointTest(ITestOutputHelper testOutputHelper) : CohostTestBase(testOutputHelper)
17+
public class CohostLinkedEditingRangeEndpointTest(ITestOutputHelper testOutputHelper) : CohostEndpointTestBase(testOutputHelper)
1818
{
1919
[Theory]
2020
[InlineData("$$PageTitle", "PageTitle")]

src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/CohostSemanticTokensRangeEndpointTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121

2222
namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost;
2323

24-
public class CohostSemanticTokensRangeEndpointTest(ITestOutputHelper testOutputHelper) : CohostTestBase(testOutputHelper)
24+
public class CohostSemanticTokensRangeEndpointTest(ITestOutputHelper testOutputHelper) : CohostEndpointTestBase(testOutputHelper)
2525
{
2626
[Theory]
2727
[CombinatorialData]

0 commit comments

Comments
 (0)