Skip to content

Commit 21e637f

Browse files
authored
Merge pull request #6416 from dibarbet/goto_definition_tests
Add go to definition integration tests
2 parents c92771c + 3330bb3 commit 21e637f

File tree

5 files changed

+166
-13
lines changed

5 files changed

+166
-13
lines changed
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import * as vscode from 'vscode';
7+
import * as path from 'path';
8+
import testAssetWorkspace from './testAssets/testAssetWorkspace';
9+
import { activateCSharpExtension, openFileInWorkspaceAsync } from './integrationHelpers';
10+
import { describe, beforeAll, beforeEach, afterAll, test, expect } from '@jest/globals';
11+
12+
describe(`[${testAssetWorkspace.description}] Test Go To Definition`, function () {
13+
beforeAll(async function () {
14+
await activateCSharpExtension();
15+
});
16+
17+
beforeEach(async function () {
18+
await openFileInWorkspaceAsync(path.join('src', 'app', 'definition.cs'));
19+
});
20+
21+
afterAll(async () => {
22+
await testAssetWorkspace.cleanupWorkspace();
23+
});
24+
25+
test('Navigates to definition in same file', async () => {
26+
const requestPosition = new vscode.Position(10, 31);
27+
const definitionList = <vscode.Location[]>(
28+
await vscode.commands.executeCommand(
29+
'vscode.executeDefinitionProvider',
30+
vscode.window.activeTextEditor!.document.uri,
31+
requestPosition
32+
)
33+
);
34+
expect(definitionList.length).toEqual(1);
35+
expect(definitionList[0].uri.path).toContain('definition.cs');
36+
expect(definitionList[0].range).toStrictEqual(new vscode.Range(6, 29, 6, 32));
37+
});
38+
39+
test('Navigates to definition in different file', async () => {
40+
const requestPosition = new vscode.Position(16, 19);
41+
const definitionList = <vscode.Location[]>(
42+
await vscode.commands.executeCommand(
43+
'vscode.executeDefinitionProvider',
44+
vscode.window.activeTextEditor!.document.uri,
45+
requestPosition
46+
)
47+
);
48+
expect(definitionList.length).toEqual(1);
49+
expect(definitionList[0].uri.path).toContain('diagnostics.cs');
50+
expect(definitionList[0].range).toStrictEqual(new vscode.Range(4, 17, 4, 23));
51+
52+
await navigate(requestPosition, definitionList, 'diagnostics.cs');
53+
});
54+
55+
test('Navigates to definition in decompiled source', async () => {
56+
await openFileInWorkspaceAsync(path.join('test', 'UnitTest1.cs'));
57+
58+
// Get definitions
59+
const requestPosition = new vscode.Position(13, 9);
60+
const definitionList = <vscode.Location[]>(
61+
await vscode.commands.executeCommand(
62+
'vscode.executeDefinitionProvider',
63+
vscode.window.activeTextEditor!.document.uri,
64+
requestPosition
65+
)
66+
);
67+
expect(definitionList.length).toEqual(1);
68+
const definitionPath = definitionList[0].uri;
69+
expect(definitionPath.fsPath).toContain('FactAttribute.cs');
70+
71+
// Navigate
72+
await navigate(requestPosition, definitionList, 'FactAttribute.cs');
73+
expect(vscode.window.activeTextEditor?.document.getText()).toContain(
74+
'// Decompiled with ICSharpCode.Decompiler'
75+
);
76+
});
77+
78+
test('Navigates to definition in metadata as source', async () => {
79+
// Get definitions
80+
const requestPosition = new vscode.Position(10, 25);
81+
const definitionList = <vscode.Location[]>(
82+
await vscode.commands.executeCommand(
83+
'vscode.executeDefinitionProvider',
84+
vscode.window.activeTextEditor!.document.uri,
85+
requestPosition
86+
)
87+
);
88+
expect(definitionList.length).toEqual(1);
89+
const definitionPath = definitionList[0].uri;
90+
expect(definitionPath.fsPath).toContain('Console.cs');
91+
92+
// Navigate
93+
await navigate(requestPosition, definitionList, 'Console.cs');
94+
expect(vscode.window.activeTextEditor?.document.getText()).not.toContain(
95+
'// Decompiled with ICSharpCode.Decompiler'
96+
);
97+
});
98+
99+
test('Returns multiple definitions for partial types', async () => {
100+
const definitionList = <vscode.Location[]>(
101+
await vscode.commands.executeCommand(
102+
'vscode.executeDefinitionProvider',
103+
vscode.window.activeTextEditor!.document.uri,
104+
new vscode.Position(4, 25)
105+
)
106+
);
107+
expect(definitionList.length).toEqual(2);
108+
expect(definitionList[0].uri.path).toContain('definition.cs');
109+
expect(definitionList[0].range).toStrictEqual(
110+
new vscode.Range(new vscode.Position(4, 25), new vscode.Position(4, 35))
111+
);
112+
expect(definitionList[1].uri.path).toContain('definition.cs');
113+
expect(definitionList[1].range).toStrictEqual(
114+
new vscode.Range(new vscode.Position(14, 25), new vscode.Position(14, 35))
115+
);
116+
});
117+
});
118+
119+
async function navigate(
120+
originalPosition: vscode.Position,
121+
definitionLocations: vscode.Location[],
122+
expectedFileName: string
123+
): Promise<void> {
124+
const windowChanged = new Promise<void>((resolve, _) => {
125+
vscode.window.onDidChangeActiveTextEditor((_e) => {
126+
if (_e?.document.fileName.includes(expectedFileName)) {
127+
resolve();
128+
}
129+
});
130+
});
131+
132+
await vscode.commands.executeCommand(
133+
'editor.action.goToLocations',
134+
vscode.window.activeTextEditor!.document.uri,
135+
originalPosition,
136+
definitionLocations,
137+
'goto',
138+
'Failed to navigate'
139+
);
140+
141+
// Navigation happens asynchronously when a different file is opened, so we need to wait for the window to change.
142+
await windowChanged;
143+
144+
expect(vscode.window.activeTextEditor?.document.fileName).toContain(expectedFileName);
145+
}

test/integrationTests/integrationHelpers.ts

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -42,16 +42,7 @@ export async function activateCSharpExtension(): Promise<void> {
4242
console.log(`Extension Log Directory: ${csharpExtension.exports.logDirectory}`);
4343

4444
if (shouldRestart) {
45-
// Register to wait for initialization events and restart the server.
46-
const waitForInitialProjectLoad = new Promise<void>((resolve, _) => {
47-
csharpExtension.exports.experimental.languageServerEvents.onServerStateChange(async (state) => {
48-
if (state === ServerStateChange.ProjectInitializationComplete) {
49-
resolve();
50-
}
51-
});
52-
});
53-
await vscode.commands.executeCommand('dotnet.restartServer');
54-
await waitForInitialProjectLoad;
45+
await restartLanguageServer();
5546
}
5647
}
5748

@@ -67,6 +58,20 @@ export async function openFileInWorkspaceAsync(relativeFilePath: string): Promis
6758
return uri;
6859
}
6960

61+
export async function restartLanguageServer(): Promise<void> {
62+
const csharpExtension = vscode.extensions.getExtension<CSharpExtensionExports>('ms-dotnettools.csharp');
63+
// Register to wait for initialization events and restart the server.
64+
const waitForInitialProjectLoad = new Promise<void>((resolve, _) => {
65+
csharpExtension!.exports.experimental.languageServerEvents.onServerStateChange(async (state) => {
66+
if (state === ServerStateChange.ProjectInitializationComplete) {
67+
resolve();
68+
}
69+
});
70+
});
71+
await vscode.commands.executeCommand('dotnet.restartServer');
72+
await waitForInitialProjectLoad;
73+
}
74+
7075
export function isRazorWorkspace(workspace: typeof vscode.workspace) {
7176
return isGivenSln(workspace, 'BasicRazorApp2_1');
7277
}

test/integrationTests/lspInlayHints.integration.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import testAssetWorkspace from './testAssets/testAssetWorkspace';
1010
import * as integrationHelpers from './integrationHelpers';
1111
import { InlayHint, InlayHintKind, Position } from 'vscode-languageserver-protocol';
1212

13-
jestLib.describe(`LSP Inlay Hints ${testAssetWorkspace.description}`, function () {
13+
jestLib.describe(`[${testAssetWorkspace.description}] Test LSP Inlay Hints `, function () {
1414
jestLib.beforeAll(async function () {
1515
const editorConfig = vscode.workspace.getConfiguration('editor');
1616
await editorConfig.update('inlayHints.enabled', true);

test/integrationTests/testAssets/slnWithCsproj/src/app/definition.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,8 @@ public void MyMethod()
1212
}
1313
}
1414

15-
public partial class Definition { }
15+
public partial class Definition
16+
{
17+
public Foo.FooBar Bar;
18+
}
1619
}

test/integrationTests/unitTests.integration.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import testAssetWorkspace from './testAssets/testAssetWorkspace';
1010
import { activateCSharpExtension, openFileInWorkspaceAsync } from './integrationHelpers';
1111
import { TestProgress } from '../../src/lsptoolshost/roslynProtocol';
1212

13-
jestLib.describe(`Unit Testing ${testAssetWorkspace.description}`, function () {
13+
jestLib.describe(`[${testAssetWorkspace.description}] Test Unit Testing`, function () {
1414
jestLib.beforeAll(async function () {
1515
await activateCSharpExtension();
1616
});

0 commit comments

Comments
 (0)