@theia/api-tests 1.48.1 → 1.48.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,860 +1,860 @@
1
- // *****************************************************************************
2
- // Copyright (C) 2020 TypeFox and others.
3
- //
4
- // This program and the accompanying materials are made available under the
5
- // terms of the Eclipse Public License v. 2.0 which is available at
6
- // http://www.eclipse.org/legal/epl-2.0.
7
- //
8
- // This Source Code may also be made available under the following Secondary
9
- // Licenses when the conditions for such availability set forth in the Eclipse
10
- // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
- // with the GNU Classpath Exception which is available at
12
- // https://www.gnu.org/software/classpath/license.html.
13
- //
14
- // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
- // *****************************************************************************
16
-
17
- // @ts-check
18
- describe('TypeScript', function () {
19
- this.timeout(30_000);
20
-
21
- const { assert } = chai;
22
- const { timeout } = require('@theia/core/lib/common/promise-util');
23
-
24
- const Uri = require('@theia/core/lib/common/uri');
25
- const { DisposableCollection } = require('@theia/core/lib/common/disposable');
26
- const { BrowserMainMenuFactory } = require('@theia/core/lib/browser/menu/browser-menu-plugin');
27
- const { EditorManager } = require('@theia/editor/lib/browser/editor-manager');
28
- const { EditorWidget } = require('@theia/editor/lib/browser/editor-widget');
29
- const { EDITOR_CONTEXT_MENU } = require('@theia/editor/lib/browser/editor-menu');
30
- const { WorkspaceService } = require('@theia/workspace/lib/browser/workspace-service');
31
- const { MonacoEditor } = require('@theia/monaco/lib/browser/monaco-editor');
32
- const { HostedPluginSupport } = require('@theia/plugin-ext/lib/hosted/browser/hosted-plugin');
33
- const { ContextKeyService } = require('@theia/core/lib/browser/context-key-service');
34
- const { CommandRegistry } = require('@theia/core/lib/common/command');
35
- const { KeybindingRegistry } = require('@theia/core/lib/browser/keybinding');
36
- const { OpenerService, open } = require('@theia/core/lib/browser/opener-service');
37
- const { animationFrame } = require('@theia/core/lib/browser/browser');
38
- const { PreferenceService, PreferenceScope } = require('@theia/core/lib/browser/preferences/preference-service');
39
- const { ProgressStatusBarItem } = require('@theia/core/lib/browser/progress-status-bar-item');
40
- const { PluginViewRegistry } = require('@theia/plugin-ext/lib/main/browser/view/plugin-view-registry');
41
- const { Range } = require('@theia/monaco-editor-core/esm/vs/editor/common/core/range');
42
- const { Selection } = require('@theia/monaco-editor-core/esm/vs/editor/common/core/selection');
43
-
44
- const container = window.theia.container;
45
- const editorManager = container.get(EditorManager);
46
- const workspaceService = container.get(WorkspaceService);
47
- const menuFactory = container.get(BrowserMainMenuFactory);
48
- const pluginService = container.get(HostedPluginSupport);
49
- const contextKeyService = container.get(ContextKeyService);
50
- const commands = container.get(CommandRegistry);
51
- const openerService = container.get(OpenerService);
52
- /** @type {KeybindingRegistry} */
53
- const keybindings = container.get(KeybindingRegistry);
54
- /** @type {import('@theia/core/lib/browser/preferences/preference-service').PreferenceService} */
55
- const preferences = container.get(PreferenceService);
56
- const progressStatusBarItem = container.get(ProgressStatusBarItem);
57
- /** @type {PluginViewRegistry} */
58
- const pluginViewRegistry = container.get(PluginViewRegistry);
59
-
60
- const typescriptPluginId = 'vscode.typescript-language-features';
61
- const referencesPluginId = 'vscode.references-view';
62
- const eslintPluginId = 'dbaeumer.vscode-eslint';
63
- /** @type Uri.URI */
64
- const rootUri = workspaceService.tryGetRoots()[0].resource;
65
- const demoFileUri = rootUri.resolveToAbsolute('../api-tests/test-ts-workspace/demo-file.ts');
66
- const definitionFileUri = rootUri.resolveToAbsolute('../api-tests/test-ts-workspace/demo-definitions-file.ts');
67
- let originalAutoSaveValue = preferences.inspect('files.autoSave').globalValue;
68
-
69
- before(async function () {
70
- await pluginService.didStart;
71
- await Promise.all([typescriptPluginId, referencesPluginId, eslintPluginId].map(async pluginId => {
72
- if (!pluginService.getPlugin(pluginId)) {
73
- throw new Error(pluginId + ' should be started');
74
- }
75
- await pluginService.activatePlugin(pluginId);
76
- }).concat(preferences.set('files.autoSave', 'off', PreferenceScope.User)));
77
- await preferences.set('files.refactoring.autoSave', 'off', PreferenceScope.User);
78
- });
79
-
80
- beforeEach(async function () {
81
- await editorManager.closeAll({ save: false });
82
- await new Promise(resolve => setTimeout(resolve, 500));
83
- });
84
-
85
- const toTearDown = new DisposableCollection();
86
- afterEach(async () => {
87
- toTearDown.dispose();
88
- await editorManager.closeAll({ save: false });
89
- await new Promise(resolve => setTimeout(resolve, 500));
90
- });
91
-
92
- after(async () => {
93
- await preferences.set('files.autoSave', originalAutoSaveValue, PreferenceScope.User);
94
- })
95
-
96
- /**
97
- * @param {Uri.default} uri
98
- * @param {boolean} preview
99
- */
100
- async function openEditor(uri, preview = false) {
101
- const widget = await open(openerService, uri, { mode: 'activate', preview });
102
- const editorWidget = widget instanceof EditorWidget ? widget : undefined;
103
- const editor = MonacoEditor.get(editorWidget);
104
- assert.isDefined(editor);
105
- // wait till tsserver is running, see:
106
- // https://github.com/microsoft/vscode/blob/93cbbc5cae50e9f5f5046343c751b6d010468200/extensions/typescript-language-features/src/extension.ts#L98-L103
107
- await waitForAnimation(() => contextKeyService.match('typescript.isManagedFile'));
108
- // wait till projects are loaded, see:
109
- // https://github.com/microsoft/vscode/blob/4aac84268c6226d23828cc6a1fe45ee3982927f0/extensions/typescript-language-features/src/typescriptServiceClient.ts#L911
110
- await waitForAnimation(() => !progressStatusBarItem.currentProgress);
111
- return /** @type {MonacoEditor} */ (editor);
112
- }
113
-
114
- /**
115
- * @param {() => Promise<unknown> | unknown} condition
116
- * @param {number | undefined} [timeout]
117
- * @param {string | undefined} [message]
118
- * @returns {Promise<void>}
119
- */
120
- function waitForAnimation(condition, timeout, message) {
121
- const success = new Promise(async (resolve, reject) => {
122
- if (timeout === undefined) {
123
- timeout = 100000;
124
- }
125
-
126
- let timedOut = false;
127
- const handle = setTimeout(() => {
128
- console.log(message);
129
- timedOut = true;
130
- }, timeout);
131
-
132
- toTearDown.push({ dispose: () => reject(message ?? 'Test terminated before resolution.') });
133
- do {
134
- await animationFrame();
135
- } while (!timedOut && !condition());
136
- if (timedOut) {
137
- reject(new Error(message ?? 'Wait for animation timed out.'));
138
- } else {
139
- clearTimeout(handle);
140
- resolve(undefined);
141
- }
142
-
143
- });
144
- return success;
145
- }
146
-
147
- /**
148
- * We ignore attributes on purpose since they are not stable.
149
- * But structure is important for us to see whether the plain text is rendered or markdown.
150
- *
151
- * @param {Element} element
152
- * @returns {string}
153
- */
154
- function nodeAsString(element, indentation = '') {
155
- const header = element.tagName;
156
- let body = '';
157
- const childIndentation = indentation + ' ';
158
- for (let i = 0; i < element.childNodes.length; i++) {
159
- const childNode = element.childNodes.item(i);
160
- if (childNode.nodeType === childNode.TEXT_NODE) {
161
- body += childIndentation + `"${childNode.textContent}"` + '\n';
162
- } else if (childNode instanceof HTMLElement) {
163
- body += childIndentation + nodeAsString(childNode, childIndentation) + '\n';
164
- }
165
- }
166
- const result = header + (body ? ' {\n' + body + indentation + '}' : '');
167
- if (indentation) {
168
- return result;
169
- }
170
- return `\n${result}\n`;
171
- }
172
-
173
- /**
174
- * @param {MonacoEditor} editor
175
- */
176
- async function assertPeekOpened(editor) {
177
- /** @type any */
178
- const referencesController = editor.getControl().getContribution('editor.contrib.referencesController');
179
- await waitForAnimation(() => referencesController._widget && referencesController._widget._tree.getFocus().length);
180
-
181
- assert.isFalse(contextKeyService.match('editorTextFocus'));
182
- assert.isTrue(contextKeyService.match('referenceSearchVisible'));
183
- assert.isTrue(contextKeyService.match('listFocus'));
184
- }
185
-
186
- /**
187
- * @param {MonacoEditor} editor
188
- */
189
- async function openPeek(editor) {
190
- assert.isTrue(contextKeyService.match('editorTextFocus'));
191
- assert.isFalse(contextKeyService.match('referenceSearchVisible'));
192
- assert.isFalse(contextKeyService.match('listFocus'));
193
-
194
- await commands.executeCommand('editor.action.peekDefinition');
195
- await assertPeekOpened(editor);
196
- }
197
-
198
- async function openReference() {
199
- keybindings.dispatchKeyDown('Enter');
200
- await waitForAnimation(() => contextKeyService.match('listFocus'));
201
- assert.isFalse(contextKeyService.match('editorTextFocus'));
202
- assert.isTrue(contextKeyService.match('referenceSearchVisible'));
203
- assert.isTrue(contextKeyService.match('listFocus'));
204
- }
205
-
206
- /**
207
- * @param {MonacoEditor} editor
208
- */
209
- async function closePeek(editor) {
210
- await assertPeekOpened(editor);
211
-
212
- console.log('closePeek() - Attempt to close by sending "Escape"');
213
- keybindings.dispatchKeyDown('Escape');
214
- await waitForAnimation(() => {
215
- const isClosed = !contextKeyService.match('listFocus');
216
- if (!isClosed) {
217
- console.log('...');
218
- keybindings.dispatchKeyDown('Escape');
219
- return false;
220
- }
221
- return true;
222
- });
223
- assert.isTrue(contextKeyService.match('editorTextFocus'));
224
- assert.isFalse(contextKeyService.match('referenceSearchVisible'));
225
- assert.isFalse(contextKeyService.match('listFocus'));
226
- }
227
-
228
- it('document formatting should be visible and enabled', async function () {
229
- await openEditor(demoFileUri);
230
- const menu = menuFactory.createContextMenu(EDITOR_CONTEXT_MENU);
231
- const item = menu.items.find(i => i.command === 'editor.action.formatDocument');
232
- if (item) {
233
- assert.isTrue(item.isVisible);
234
- assert.isTrue(item.isEnabled);
235
- } else {
236
- assert.isDefined(item);
237
- }
238
- });
239
-
240
- describe('editor.action.revealDefinition', function () {
241
- for (const preview of [false, true]) {
242
- const from = 'an editor' + (preview ? ' preview' : '');
243
- it('within ' + from, async function () {
244
- const editor = await openEditor(demoFileUri, preview);
245
- // const demoInstance = new Demo|Class('demo');
246
- editor.getControl().setPosition({ lineNumber: 24, column: 30 });
247
- assert.equal(editor.getControl().getModel().getWordAtPosition(editor.getControl().getPosition()).word, 'DemoClass');
248
-
249
- await commands.executeCommand('editor.action.revealDefinition');
250
-
251
- const activeEditor = /** @type {MonacoEditor} */ MonacoEditor.get(editorManager.activeEditor);
252
- assert.equal(editorManager.activeEditor.isPreview, preview);
253
- assert.equal(activeEditor.uri.toString(), demoFileUri.toString());
254
- // constructor(someString: string) {
255
- const { lineNumber, column } = activeEditor.getControl().getPosition();
256
- assert.deepEqual({ lineNumber, column }, { lineNumber: 11, column: 5 });
257
- assert.equal(activeEditor.getControl().getModel().getWordAtPosition({ lineNumber, column }).word, 'constructor');
258
- });
259
-
260
- // Note: this test generate annoying but apparently harmless error traces, during cleanup:
261
- // [Error: Error: Cannot update an unmounted root.
262
- // at ReactDOMRoot.__webpack_modules__.../../node_modules/react-dom/cjs/react-dom.development.js.ReactDOMHydrationRoot.render.ReactDOMRoot.render (http://127.0.0.1:3000/bundle.js:92757:11)
263
- // at BreadcrumbsRenderer.render (http://127.0.0.1:3000/bundle.js:137316:23)
264
- // at BreadcrumbsRenderer.update (http://127.0.0.1:3000/bundle.js:108722:14)
265
- // at BreadcrumbsRenderer.refresh (http://127.0.0.1:3000/bundle.js:108719:14)
266
- // at async ToolbarAwareTabBar.updateBreadcrumbs (http://127.0.0.1:3000/bundle.js:128229:9)]
267
- it(`from ${from} to another editor`, async function () {
268
- await editorManager.open(definitionFileUri, { mode: 'open' });
269
-
270
- const editor = await openEditor(demoFileUri, preview);
271
- // const bar: Defined|Interface = { coolField: [] };
272
- editor.getControl().setPosition({ lineNumber: 32, column: 19 });
273
- assert.equal(editor.getControl().getModel().getWordAtPosition(editor.getControl().getPosition()).word, 'DefinedInterface');
274
-
275
- await commands.executeCommand('editor.action.revealDefinition');
276
-
277
- const activeEditor = /** @type {MonacoEditor} */ MonacoEditor.get(editorManager.activeEditor);
278
- assert.isFalse(editorManager.activeEditor.isPreview);
279
- assert.equal(activeEditor.uri.toString(), definitionFileUri.toString());
280
-
281
- // export interface |DefinedInterface {
282
- const { lineNumber, column } = activeEditor.getControl().getPosition();
283
- assert.deepEqual({ lineNumber, column }, { lineNumber: 2, column: 18 });
284
- assert.equal(activeEditor.getControl().getModel().getWordAtPosition({ lineNumber, column }).word, 'DefinedInterface');
285
- });
286
-
287
- it(`from ${from} to an editor preview`, async function () {
288
- const editor = await openEditor(demoFileUri);
289
- // const bar: Defined|Interface = { coolField: [] };
290
- editor.getControl().setPosition({ lineNumber: 32, column: 19 });
291
- assert.equal(editor.getControl().getModel().getWordAtPosition(editor.getControl().getPosition()).word, 'DefinedInterface');
292
-
293
- await commands.executeCommand('editor.action.revealDefinition');
294
-
295
- const activeEditor = /** @type {MonacoEditor} */ MonacoEditor.get(editorManager.activeEditor);
296
- assert.isTrue(editorManager.activeEditor.isPreview);
297
- assert.equal(activeEditor.uri.toString(), definitionFileUri.toString());
298
- // export interface |DefinedInterface {
299
- const { lineNumber, column } = activeEditor.getControl().getPosition();
300
- assert.deepEqual({ lineNumber, column }, { lineNumber: 2, column: 18 });
301
- assert.equal(activeEditor.getControl().getModel().getWordAtPosition({ lineNumber, column }).word, 'DefinedInterface');
302
- });
303
- }
304
- });
305
-
306
- describe('editor.action.peekDefinition', function () {
307
-
308
- for (const preview of [false, true]) {
309
- const from = 'an editor' + (preview ? ' preview' : '');
310
- it('within ' + from, async function () {
311
- const editor = await openEditor(demoFileUri, preview);
312
- editor.getControl().revealLine(24);
313
- // const demoInstance = new Demo|Class('demo');
314
- editor.getControl().setPosition({ lineNumber: 24, column: 30 });
315
- assert.equal(editor.getControl().getModel().getWordAtPosition(editor.getControl().getPosition()).word, 'DemoClass');
316
-
317
- await openPeek(editor);
318
- await openReference();
319
-
320
- const activeEditor = /** @type {MonacoEditor} */ MonacoEditor.get(editorManager.activeEditor);
321
- assert.equal(editorManager.activeEditor.isPreview, preview);
322
- assert.equal(activeEditor.uri.toString(), demoFileUri.toString());
323
- // constructor(someString: string) {
324
- const { lineNumber, column } = activeEditor.getControl().getPosition();
325
- assert.deepEqual({ lineNumber, column }, { lineNumber: 11, column: 5 });
326
- assert.equal(activeEditor.getControl().getModel().getWordAtPosition({ lineNumber, column }).word, 'constructor');
327
-
328
- await closePeek(activeEditor);
329
- });
330
-
331
- // Note: this test generate annoying but apparently harmless error traces, during cleanup:
332
- // [Error: Error: Cannot update an unmounted root.
333
- // at ReactDOMRoot.__webpack_modules__.../../node_modules/react-dom/cjs/react-dom.development.js.ReactDOMHydrationRoot.render.ReactDOMRoot.render (http://127.0.0.1:3000/bundle.js:92757:11)
334
- // at BreadcrumbsRenderer.render (http://127.0.0.1:3000/bundle.js:137316:23)
335
- // at BreadcrumbsRenderer.update (http://127.0.0.1:3000/bundle.js:108722:14)
336
- // at BreadcrumbsRenderer.refresh (http://127.0.0.1:3000/bundle.js:108719:14)
337
- // at async ToolbarAwareTabBar.updateBreadcrumbs (http://127.0.0.1:3000/bundle.js:128229:9)]
338
- it(`from ${from} to another editor`, async function () {
339
- await editorManager.open(definitionFileUri, { mode: 'open' });
340
-
341
- const editor = await openEditor(demoFileUri, preview);
342
- editor.getControl().revealLine(32);
343
- // const bar: Defined|Interface = { coolField: [] };
344
- editor.getControl().setPosition({ lineNumber: 32, column: 19 });
345
- assert.equal(editor.getControl().getModel().getWordAtPosition(editor.getControl().getPosition()).word, 'DefinedInterface');
346
-
347
- await openPeek(editor);
348
- await openReference();
349
-
350
- const activeEditor = /** @type {MonacoEditor} */ MonacoEditor.get(editorManager.activeEditor);
351
- assert.isFalse(editorManager.activeEditor.isPreview);
352
- assert.equal(activeEditor.uri.toString(), definitionFileUri.toString());
353
- // export interface |DefinedInterface {
354
- const { lineNumber, column } = activeEditor.getControl().getPosition();
355
- assert.deepEqual({ lineNumber, column }, { lineNumber: 2, column: 18 });
356
- assert.equal(activeEditor.getControl().getModel().getWordAtPosition({ lineNumber, column }).word, 'DefinedInterface');
357
-
358
- await closePeek(activeEditor);
359
- });
360
-
361
- it(`from ${from} to an editor preview`, async function () {
362
- const editor = await openEditor(demoFileUri);
363
- editor.getControl().revealLine(32);
364
- // const bar: Defined|Interface = { coolField: [] };
365
- editor.getControl().setPosition({ lineNumber: 32, column: 19 });
366
- assert.equal(editor.getControl().getModel().getWordAtPosition(editor.getControl().getPosition()).word, 'DefinedInterface');
367
-
368
- await openPeek(editor);
369
- await openReference();
370
-
371
- const activeEditor = /** @type {MonacoEditor} */ MonacoEditor.get(editorManager.activeEditor);
372
- assert.isTrue(editorManager.activeEditor.isPreview);
373
- assert.equal(activeEditor.uri.toString(), definitionFileUri.toString());
374
- // export interface |DefinedInterface {
375
- const { lineNumber, column } = activeEditor.getControl().getPosition();
376
- assert.deepEqual({ lineNumber, column }, { lineNumber: 2, column: 18 });
377
- assert.equal(activeEditor.getControl().getModel().getWordAtPosition({ lineNumber, column }).word, 'DefinedInterface');
378
-
379
- await closePeek(activeEditor);
380
- });
381
- }
382
- });
383
-
384
- it('editor.action.triggerSuggest', async function () {
385
- const editor = await openEditor(demoFileUri);
386
- editor.getControl().setPosition({ lineNumber: 26, column: 46 });
387
- editor.getControl().setSelection(new Selection(26, 46, 26, 35));
388
- assert.equal(editor.getControl().getModel().getWordAtPosition(editor.getControl().getPosition()).word, 'stringField');
389
-
390
- assert.isTrue(contextKeyService.match('editorTextFocus'));
391
- assert.isFalse(contextKeyService.match('suggestWidgetVisible'));
392
-
393
- await commands.executeCommand('editor.action.triggerSuggest');
394
- await waitForAnimation(() => contextKeyService.match('suggestWidgetVisible'));
395
-
396
- assert.isTrue(contextKeyService.match('editorTextFocus'));
397
- assert.isTrue(contextKeyService.match('suggestWidgetVisible'));
398
-
399
- // May need a couple extra "Enter" being sent for the suggest to be accepted
400
- keybindings.dispatchKeyDown('Enter');
401
- await waitForAnimation(() => {
402
- const suggestWidgetDismissed = !contextKeyService.match('suggestWidgetVisible');
403
- if (!suggestWidgetDismissed) {
404
- console.log('Re-try accepting suggest using "Enter" key');
405
- keybindings.dispatchKeyDown('Enter');
406
- return false;
407
- }
408
- return true;
409
- }, 5000, 'Suggest widget has not been dismissed despite attempts to accept suggestion');
410
-
411
- assert.isTrue(contextKeyService.match('editorTextFocus'));
412
- assert.isFalse(contextKeyService.match('suggestWidgetVisible'));
413
-
414
- const activeEditor = /** @type {MonacoEditor} */ MonacoEditor.get(editorManager.activeEditor);
415
- assert.equal(activeEditor.uri.toString(), demoFileUri.toString());
416
- // demoInstance.stringField;
417
- const { lineNumber, column } = activeEditor.getControl().getPosition();
418
- assert.deepEqual({ lineNumber, column }, { lineNumber: 26, column: 46 });
419
- assert.equal(activeEditor.getControl().getModel().getWordAtPosition({ lineNumber, column }).word, 'doSomething');
420
- });
421
-
422
- it('editor.action.triggerSuggest navigate', async function () {
423
- const editor = await openEditor(demoFileUri);
424
- // demoInstance.[|stringField];
425
- editor.getControl().setPosition({ lineNumber: 26, column: 46 });
426
- editor.getControl().setSelection(new Selection(26, 46, 26, 35));
427
- assert.equal(editor.getControl().getModel().getWordAtPosition(editor.getControl().getPosition()).word, 'stringField');
428
-
429
- /** @type {import('@theia/monaco-editor-core/src/vs/editor/contrib/suggest/browser/suggestController').SuggestController} */
430
- const suggest = editor.getControl().getContribution('editor.contrib.suggestController');
431
- const getFocusedLabel = () => {
432
- const focusedItem = suggest.widget.value.getFocusedItem();
433
- return focusedItem && focusedItem.item.completion.label;
434
- };
435
-
436
- assert.isUndefined(getFocusedLabel());
437
- assert.isFalse(contextKeyService.match('suggestWidgetVisible'));
438
-
439
- await commands.executeCommand('editor.action.triggerSuggest');
440
- await waitForAnimation(() => contextKeyService.match('suggestWidgetVisible') && getFocusedLabel() === 'doSomething', 5000);
441
-
442
- assert.equal(getFocusedLabel(), 'doSomething');
443
- assert.isTrue(contextKeyService.match('suggestWidgetVisible'));
444
-
445
- keybindings.dispatchKeyDown('ArrowDown');
446
- await waitForAnimation(() => contextKeyService.match('suggestWidgetVisible') && getFocusedLabel() === 'numberField', 2000);
447
-
448
- assert.equal(getFocusedLabel(), 'numberField');
449
- assert.isTrue(contextKeyService.match('suggestWidgetVisible'));
450
-
451
- keybindings.dispatchKeyDown('ArrowUp');
452
- await waitForAnimation(() => contextKeyService.match('suggestWidgetVisible') && getFocusedLabel() === 'doSomething', 2000);
453
-
454
- assert.equal(getFocusedLabel(), 'doSomething');
455
- assert.isTrue(contextKeyService.match('suggestWidgetVisible'));
456
-
457
- keybindings.dispatchKeyDown('Escape');
458
-
459
- // once in a while, a second "Escape" is needed to dismiss widget
460
- await waitForAnimation(() => {
461
- const suggestWidgetDismissed = !contextKeyService.match('suggestWidgetVisible') && getFocusedLabel() === undefined;
462
- if (!suggestWidgetDismissed) {
463
- console.log('Re-try to dismiss suggest using "Escape" key');
464
- keybindings.dispatchKeyDown('Escape');
465
- return false;
466
- }
467
- return true;
468
- }, 5000, 'Suggest widget not dismissed');
469
-
470
- assert.isUndefined(getFocusedLabel());
471
- assert.isFalse(contextKeyService.match('suggestWidgetVisible'));
472
- });
473
-
474
- it('editor.action.rename', async function () {
475
- const editor = await openEditor(demoFileUri);
476
- // const |demoVariable = demoInstance.stringField;
477
- editor.getControl().setPosition({ lineNumber: 26, column: 7 });
478
- assert.equal(editor.getControl().getModel().getWordAtPosition(editor.getControl().getPosition()).word, 'demoVariable');
479
-
480
- assert.isTrue(contextKeyService.match('editorTextFocus'));
481
- assert.isFalse(contextKeyService.match('renameInputVisible'));
482
-
483
- commands.executeCommand('editor.action.rename');
484
- await waitForAnimation(() => contextKeyService.match('renameInputVisible')
485
- && document.activeElement instanceof HTMLInputElement
486
- && document.activeElement.selectionEnd === 'demoVariable'.length);
487
- assert.isFalse(contextKeyService.match('editorTextFocus'));
488
- assert.isTrue(contextKeyService.match('renameInputVisible'));
489
-
490
- const input = document.activeElement;
491
- if (!(input instanceof HTMLInputElement)) {
492
- assert.fail('expected focused input, but: ' + input);
493
- return;
494
- }
495
-
496
- input.value = 'foo';
497
- keybindings.dispatchKeyDown('Enter', input);
498
-
499
- // all rename edits should be grouped in one edit operation and applied in the same tick
500
- await new Promise(resolve => editor.getControl().onDidChangeModelContent(resolve));
501
-
502
- assert.isTrue(contextKeyService.match('editorTextFocus'));
503
- assert.isFalse(contextKeyService.match('renameInputVisible'));
504
-
505
- const activeEditor = /** @type {MonacoEditor} */ MonacoEditor.get(editorManager.activeEditor);
506
- assert.equal(activeEditor.uri.toString(), demoFileUri.toString());
507
- // const |foo = new Container();
508
- const { lineNumber, column } = activeEditor.getControl().getPosition();
509
- assert.deepEqual({ lineNumber, column }, { lineNumber: 26, column: 7 });
510
- assert.equal(activeEditor.getControl().getModel().getWordAtPosition({ lineNumber: 28, column: 1 }).word, 'foo');
511
- });
512
-
513
- it('editor.action.triggerParameterHints', async function () {
514
- const editor = await openEditor(demoFileUri);
515
- // const demoInstance = new DemoClass('|demo');
516
- editor.getControl().setPosition({ lineNumber: 24, column: 37 });
517
- assert.equal(editor.getControl().getModel().getWordAtPosition(editor.getControl().getPosition()).word, "demo");
518
-
519
- assert.isTrue(contextKeyService.match('editorTextFocus'));
520
- assert.isFalse(contextKeyService.match('parameterHintsVisible'));
521
-
522
- await commands.executeCommand('editor.action.triggerParameterHints');
523
- await waitForAnimation(() => contextKeyService.match('parameterHintsVisible'));
524
-
525
- assert.isTrue(contextKeyService.match('editorTextFocus'));
526
- assert.isTrue(contextKeyService.match('parameterHintsVisible'));
527
-
528
- keybindings.dispatchKeyDown('Escape');
529
- await waitForAnimation(() => !contextKeyService.match('parameterHintsVisible'));
530
-
531
- assert.isTrue(contextKeyService.match('editorTextFocus'));
532
- assert.isFalse(contextKeyService.match('parameterHintsVisible'));
533
- });
534
-
535
- it('editor.action.showHover', async function () {
536
- const editor = await openEditor(demoFileUri);
537
- // class |DemoClass);
538
- editor.getControl().setPosition({ lineNumber: 8, column: 7 });
539
- assert.equal(editor.getControl().getModel().getWordAtPosition(editor.getControl().getPosition()).word, 'DemoClass');
540
-
541
- /** @type {import('@theia/monaco-editor-core/src/vs/editor/contrib/hover/browser/hover').ModesHoverController} */
542
- const hover = editor.getControl().getContribution('editor.contrib.hover');
543
-
544
- assert.isTrue(contextKeyService.match('editorTextFocus'));
545
- assert.isFalse(Boolean(hover['_contentWidget']?.['_widget']?.['_visibleData']));
546
- await commands.executeCommand('editor.action.showHover');
547
- let doLog = true;
548
- await waitForAnimation(() => hover['_contentWidget']?.['_widget']?.['_visibleData']);
549
- assert.isTrue(contextKeyService.match('editorTextFocus'));
550
- assert.isTrue(Boolean(hover['_contentWidget']?.['_widget']?.['_visibleData']));
551
- assert.deepEqual(nodeAsString(hover['_contentWidget']?.['_widget']?.['_hover']?.['contentsDomNode']).trim(), `
552
- DIV {
553
- DIV {
554
- DIV {
555
- DIV {
556
- DIV {
557
- SPAN {
558
- DIV {
559
- SPAN {
560
- "class"
561
- }
562
- SPAN {
563
- " "
564
- }
565
- SPAN {
566
- "DemoClass"
567
- }
568
- }
569
- }
570
- }
571
- }
572
- }
573
- }
574
- }`.trim());
575
- keybindings.dispatchKeyDown('Escape');
576
- await waitForAnimation(() => !hover['_contentWidget']?.['_widget']?.['_visibleData']);
577
- assert.isTrue(contextKeyService.match('editorTextFocus'));
578
- assert.isFalse(Boolean(hover['_contentWidget']?.['_widget']?.['_visibleData']));
579
- });
580
-
581
- it('highlight semantic (write) occurrences', async function () {
582
- const editor = await openEditor(demoFileUri);
583
- // const |container = new Container();
584
- const lineNumber = 24;
585
- const column = 7;
586
- const endColumn = column + 'demoInstance'.length;
587
-
588
- const hasWriteDecoration = () => {
589
- for (const decoration of editor.getControl().getModel().getLineDecorations(lineNumber)) {
590
- if (decoration.range.startColumn === column && decoration.range.endColumn === endColumn && decoration.options.className === 'wordHighlightStrong') {
591
- return true;
592
- }
593
- }
594
- return false;
595
- };
596
- assert.isFalse(hasWriteDecoration());
597
-
598
- editor.getControl().setPosition({ lineNumber, column });
599
- assert.equal(editor.getControl().getModel().getWordAtPosition(editor.getControl().getPosition()).word, 'demoInstance');
600
- // highlight occurrences is not trigged on the explicit position change, so move a cursor as a user
601
- keybindings.dispatchKeyDown('ArrowRight');
602
- await waitForAnimation(() => hasWriteDecoration());
603
-
604
- assert.isTrue(hasWriteDecoration());
605
- });
606
-
607
- it('editor.action.goToImplementation', async function () {
608
- const editor = await openEditor(demoFileUri);
609
- // const demoInstance = new Demo|Class('demo');
610
- editor.getControl().setPosition({ lineNumber: 24, column: 30 });
611
- assert.equal(editor.getControl().getModel().getWordAtPosition(editor.getControl().getPosition()).word, 'DemoClass');
612
-
613
- await commands.executeCommand('editor.action.goToImplementation');
614
-
615
- const activeEditor = /** @type {MonacoEditor} */ MonacoEditor.get(editorManager.activeEditor);
616
- assert.equal(activeEditor.uri.toString(), demoFileUri.toString());
617
- // class |DemoClass implements DemoInterface {
618
- const { lineNumber, column } = activeEditor.getControl().getPosition();
619
- assert.deepEqual({ lineNumber, column }, { lineNumber: 8, column: 7 });
620
- assert.equal(activeEditor.getControl().getModel().getWordAtPosition({ lineNumber, column }).word, 'DemoClass');
621
- });
622
-
623
- it('editor.action.goToTypeDefinition', async function () {
624
- const editor = await openEditor(demoFileUri);
625
- // const demoVariable = demo|Instance.stringField;
626
- editor.getControl().setPosition({ lineNumber: 26, column: 26 });
627
- assert.equal(editor.getControl().getModel().getWordAtPosition(editor.getControl().getPosition()).word, 'demoInstance');
628
-
629
- await commands.executeCommand('editor.action.goToTypeDefinition');
630
-
631
- const activeEditor = /** @type {MonacoEditor} */ MonacoEditor.get(editorManager.activeEditor);
632
- assert.equal(activeEditor.uri.toString(), demoFileUri.toString());
633
- // class |DemoClass implements DemoInterface {
634
- const { lineNumber, column } = activeEditor.getControl().getPosition();
635
- assert.deepEqual({ lineNumber, column }, { lineNumber: 8, column: 7 });
636
- assert.equal(activeEditor.getControl().getModel().getWordAtPosition({ lineNumber, column }).word, 'DemoClass');
637
- });
638
-
639
- it('run reference code lens', async function () {
640
- const preferenceName = 'typescript.referencesCodeLens.enabled';
641
- const globalValue = preferences.inspect(preferenceName).globalValue;
642
- toTearDown.push({ dispose: () => preferences.set(preferenceName, globalValue, PreferenceScope.User) });
643
- await preferences.set(preferenceName, false, PreferenceScope.User);
644
-
645
- const editor = await openEditor(demoFileUri);
646
-
647
- /** @type {import('@theia/monaco-editor-core/src/vs/editor/contrib/codelens/browser/codelensController').CodeLensContribution} */
648
- const codeLens = editor.getControl().getContribution('css.editor.codeLens');
649
- const codeLensNode = () => codeLens['_lenses'][0]?.['_contentWidget']?.['_domNode'];
650
- const codeLensNodeVisible = () => {
651
- const n = codeLensNode();
652
- return !!n && n.style.visibility !== 'hidden';
653
- };
654
-
655
- assert.isFalse(codeLensNodeVisible());
656
-
657
- // |interface DemoInterface {
658
- const position = { lineNumber: 2, column: 1 };
659
- await preferences.set(preferenceName, true, PreferenceScope.User);
660
-
661
- editor.getControl().revealPosition(position);
662
- await waitForAnimation(() => codeLensNodeVisible());
663
-
664
- assert.isTrue(codeLensNodeVisible());
665
- const node = codeLensNode();
666
- assert.isDefined(node);
667
- assert.equal(nodeAsString(node), `
668
- SPAN {
669
- A {
670
- "1 reference"
671
- }
672
- }
673
- `);
674
- const link = node.getElementsByTagName('a').item(0);
675
- assert.isDefined(link);
676
- link.dispatchEvent(new MouseEvent('mousedown', { bubbles: true }));
677
- await assertPeekOpened(editor);
678
- await closePeek(editor);
679
- });
680
-
681
- it('editor.action.quickFix', async function () {
682
- const column = 45;
683
- const lineNumber = 26;
684
- const editor = await openEditor(demoFileUri);
685
- const currentChar = () => editor.getControl().getModel().getLineContent(lineNumber).charAt(column - 1);
686
-
687
- editor.getControl().getModel().applyEdits([{
688
- range: {
689
- startLineNumber: lineNumber,
690
- endLineNumber: lineNumber,
691
- startColumn: 45,
692
- endColumn: 46
693
- },
694
- forceMoveMarkers: false,
695
- text: ''
696
- }]);
697
- editor.getControl().setPosition({ lineNumber, column });
698
- editor.getControl().revealPosition({ lineNumber, column });
699
- assert.equal(currentChar(), ';', 'Failed at assert 1');
700
-
701
- /** @type {import('@theia/monaco-editor-core/src/vs/editor/contrib/codeAction/browser/codeActionController').CodeActionController} */
702
- const codeActionController = editor.getControl().getContribution('editor.contrib.codeActionController');
703
- const lightBulbNode = () => {
704
- const lightBulb = codeActionController['_lightBulbWidget'].rawValue;
705
- return lightBulb && lightBulb['_domNode'];
706
- };
707
- const lightBulbVisible = () => {
708
- const node = lightBulbNode();
709
- return !!node && node.style.visibility !== 'hidden';
710
- };
711
-
712
- await timeout(1000); // quick fix is always available: need to wait for the error fix to become available.
713
- await commands.executeCommand('editor.action.quickFix');
714
- const codeActionSelector = '.action-widget';
715
- assert.isFalse(!!document.querySelector(codeActionSelector), 'Failed at assert 3 - codeActionWidget should not be visible');
716
-
717
- console.log('Waiting for Quick Fix widget to be visible');
718
- await waitForAnimation(() => {
719
- const quickFixWidgetVisible = !!document.querySelector(codeActionSelector);
720
- if (!quickFixWidgetVisible) {
721
- // console.log('...');
722
- return false;
723
- }
724
- return true;
725
- }, 10000, 'Timed-out waiting for the QuickFix widget to appear');
726
- await animationFrame();
727
-
728
- assert.isTrue(lightBulbVisible(), 'Failed at assert 4');
729
- keybindings.dispatchKeyDown('Enter');
730
- console.log('Waiting for confirmation that QuickFix has taken effect');
731
-
732
- await waitForAnimation(() => currentChar() === 'd', 10000, 'Failed to detect expected selected char: "d"');
733
- assert.equal(currentChar(), 'd', 'Failed at assert 5');
734
- });
735
-
736
- it('editor.action.formatDocument', async function () {
737
- const lineNumber = 5;
738
- const editor = await openEditor(demoFileUri);
739
- const originalLength = editor.getControl().getModel().getLineLength(lineNumber);
740
-
741
- // doSomething(): number; --> doSomething() : number;
742
- editor.getControl().getModel().applyEdits([{
743
- range: Range.fromPositions({ lineNumber, column: 18 }, { lineNumber, column: 18 }),
744
- forceMoveMarkers: false,
745
- text: ' '
746
- }]);
747
-
748
- assert.equal(editor.getControl().getModel().getLineLength(lineNumber), originalLength + 1);
749
-
750
- await commands.executeCommand('editor.action.formatDocument');
751
-
752
- assert.equal(editor.getControl().getModel().getLineLength(lineNumber), originalLength);
753
- });
754
-
755
- it('editor.action.formatSelection', async function () {
756
- // doSomething(): number {
757
- const lineNumber = 15;
758
- const editor = await openEditor(demoFileUri);
759
- const originalLength /* 28 */ = editor.getControl().getModel().getLineLength(lineNumber);
760
-
761
- // doSomething( ) : number {
762
- editor.getControl().getModel().applyEdits([{
763
- range: Range.fromPositions({ lineNumber, column: 17 }, { lineNumber, column: 18 }),
764
- forceMoveMarkers: false,
765
- text: ' ) '
766
- }]);
767
-
768
- assert.equal(editor.getControl().getModel().getLineLength(lineNumber), originalLength + 4);
769
-
770
- // [const { Container }] = require('inversify');
771
- editor.getControl().setSelection({ startLineNumber: lineNumber, startColumn: 1, endLineNumber: lineNumber, endColumn: 32 });
772
-
773
- await commands.executeCommand('editor.action.formatSelection');
774
-
775
- // [const { Container }] = require('inversify');
776
- assert.equal(editor.getControl().getModel().getLineLength(lineNumber), originalLength);
777
- });
778
-
779
- it('Can execute code actions', async function () {
780
- const editor = await openEditor(demoFileUri);
781
- /** @type {import('@theia/monaco-editor-core/src/vs/editor/contrib/codeAction/browser/codeActionController').CodeActionController} */
782
- const codeActionController = editor.getControl().getContribution('editor.contrib.codeActionController');
783
- const isActionAvailable = () => {
784
- const lightbulbVisibility = codeActionController['_lightBulbWidget'].rawValue?.['_domNode'].style.visibility;
785
- return lightbulbVisibility !== undefined && lightbulbVisibility !== 'hidden';
786
- }
787
- assert.isFalse(isActionAvailable());
788
- assert.strictEqual(editor.getControl().getModel().getLineContent(30), 'import { DefinedInterface } from "./demo-definitions-file";');
789
- editor.getControl().revealLine(30);
790
- editor.getControl().setSelection(new Selection(30, 1, 30, 60));
791
- await waitForAnimation(() => isActionAvailable(), 5000, 'No code action available. (1)');
792
- assert.isTrue(isActionAvailable());
793
-
794
- try { // for some reason, we need to wait a second here, otherwise, we run into some cancellation.
795
- await waitForAnimation(() => false, 1000);
796
- } catch (e) {
797
- }
798
-
799
- await commands.executeCommand('editor.action.quickFix');
800
- await waitForAnimation(() => {
801
- const elements = document.querySelector('.action-widget');
802
- return !!elements;
803
- }, 5000, 'No context menu appeared. (1)');
804
- await animationFrame();
805
-
806
- keybindings.dispatchKeyDown('Enter');
807
-
808
- assert.isNotNull(editor.getControl());
809
- assert.isNotNull(editor.getControl().getModel());
810
- console.log(`content: ${editor.getControl().getModel().getLineContent(30)}`);
811
- await waitForAnimation(() => editor.getControl().getModel().getLineContent(30) === 'import * as demoDefinitionsFile from "./demo-definitions-file";', 5000, 'The namespace import did not take effect.');
812
-
813
- // momentarily toggle selection, waiting for code action to become unavailable.
814
- // Without doing this, the call to the quickfix command would sometimes fail because of an
815
- // unexpected "no code action available" pop-up, which would trip the rest of the testcase
816
- editor.getControl().setSelection(new Selection(30, 1, 30, 1));
817
- console.log('waiting for code action to no longer be available');
818
- await waitForAnimation(() => {
819
- if (!isActionAvailable()) {
820
- return true;
821
- }
822
- editor.getControl().setSelection(new Selection(30, 1, 30, 1));
823
- console.log('...');
824
- return !isActionAvailable();
825
- }, 5000, 'Code action still available with no proper selection.');
826
- // re-establish selection
827
- editor.getControl().setSelection(new Selection(30, 1, 30, 64));
828
- console.log('waiting for code action to become available again');
829
- await waitForAnimation(() => {
830
- console.log('...');
831
- return isActionAvailable()
832
- }, 5000, 'No code action available. (2)');
833
-
834
- // Change import back: https://github.com/eclipse-theia/theia/issues/11059
835
- await commands.executeCommand('editor.action.quickFix');
836
- await waitForAnimation(() => Boolean(document.querySelector('.context-view-pointerBlock')), 5000, 'No context menu appeared. (2)');
837
- await animationFrame();
838
-
839
- keybindings.dispatchKeyDown('Enter');
840
-
841
- assert.isNotNull(editor.getControl());
842
- assert.isNotNull(editor.getControl().getModel());
843
- await waitForAnimation(() => editor.getControl().getModel().getLineContent(30) === 'import { DefinedInterface } from "./demo-definitions-file";', 5000, 'The named import did not take effect.');
844
- });
845
-
846
- for (const referenceViewCommand of ['references-view.find', 'references-view.findImplementations']) {
847
- it(referenceViewCommand, async function () {
848
- let steps = 0;
849
- const editor = await openEditor(demoFileUri);
850
- editor.getControl().setPosition({ lineNumber: 24, column: 11 });
851
- assert.equal(editor.getControl().getModel().getWordAtPosition(editor.getControl().getPosition()).word, 'demoInstance');
852
- await commands.executeCommand(referenceViewCommand);
853
- const view = await pluginViewRegistry.openView('references-view.tree', { reveal: true });
854
- const expectedMessage = referenceViewCommand === 'references-view.find' ? '2 results in 1 file' : '1 result in 1 file';
855
- const getResultText = () => view.node.getElementsByClassName('theia-TreeViewInfo').item(0)?.textContent;
856
- await waitForAnimation(() => getResultText() === expectedMessage, 5000);
857
- assert.equal(getResultText(), expectedMessage);
858
- });
859
- }
860
- });
1
+ // *****************************************************************************
2
+ // Copyright (C) 2020 TypeFox and others.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+
17
+ // @ts-check
18
+ describe('TypeScript', function () {
19
+ this.timeout(30_000);
20
+
21
+ const { assert } = chai;
22
+ const { timeout } = require('@theia/core/lib/common/promise-util');
23
+
24
+ const Uri = require('@theia/core/lib/common/uri');
25
+ const { DisposableCollection } = require('@theia/core/lib/common/disposable');
26
+ const { BrowserMainMenuFactory } = require('@theia/core/lib/browser/menu/browser-menu-plugin');
27
+ const { EditorManager } = require('@theia/editor/lib/browser/editor-manager');
28
+ const { EditorWidget } = require('@theia/editor/lib/browser/editor-widget');
29
+ const { EDITOR_CONTEXT_MENU } = require('@theia/editor/lib/browser/editor-menu');
30
+ const { WorkspaceService } = require('@theia/workspace/lib/browser/workspace-service');
31
+ const { MonacoEditor } = require('@theia/monaco/lib/browser/monaco-editor');
32
+ const { HostedPluginSupport } = require('@theia/plugin-ext/lib/hosted/browser/hosted-plugin');
33
+ const { ContextKeyService } = require('@theia/core/lib/browser/context-key-service');
34
+ const { CommandRegistry } = require('@theia/core/lib/common/command');
35
+ const { KeybindingRegistry } = require('@theia/core/lib/browser/keybinding');
36
+ const { OpenerService, open } = require('@theia/core/lib/browser/opener-service');
37
+ const { animationFrame } = require('@theia/core/lib/browser/browser');
38
+ const { PreferenceService, PreferenceScope } = require('@theia/core/lib/browser/preferences/preference-service');
39
+ const { ProgressStatusBarItem } = require('@theia/core/lib/browser/progress-status-bar-item');
40
+ const { PluginViewRegistry } = require('@theia/plugin-ext/lib/main/browser/view/plugin-view-registry');
41
+ const { Range } = require('@theia/monaco-editor-core/esm/vs/editor/common/core/range');
42
+ const { Selection } = require('@theia/monaco-editor-core/esm/vs/editor/common/core/selection');
43
+
44
+ const container = window.theia.container;
45
+ const editorManager = container.get(EditorManager);
46
+ const workspaceService = container.get(WorkspaceService);
47
+ const menuFactory = container.get(BrowserMainMenuFactory);
48
+ const pluginService = container.get(HostedPluginSupport);
49
+ const contextKeyService = container.get(ContextKeyService);
50
+ const commands = container.get(CommandRegistry);
51
+ const openerService = container.get(OpenerService);
52
+ /** @type {KeybindingRegistry} */
53
+ const keybindings = container.get(KeybindingRegistry);
54
+ /** @type {import('@theia/core/lib/browser/preferences/preference-service').PreferenceService} */
55
+ const preferences = container.get(PreferenceService);
56
+ const progressStatusBarItem = container.get(ProgressStatusBarItem);
57
+ /** @type {PluginViewRegistry} */
58
+ const pluginViewRegistry = container.get(PluginViewRegistry);
59
+
60
+ const typescriptPluginId = 'vscode.typescript-language-features';
61
+ const referencesPluginId = 'vscode.references-view';
62
+ const eslintPluginId = 'dbaeumer.vscode-eslint';
63
+ /** @type Uri.URI */
64
+ const rootUri = workspaceService.tryGetRoots()[0].resource;
65
+ const demoFileUri = rootUri.resolveToAbsolute('../api-tests/test-ts-workspace/demo-file.ts');
66
+ const definitionFileUri = rootUri.resolveToAbsolute('../api-tests/test-ts-workspace/demo-definitions-file.ts');
67
+ let originalAutoSaveValue = preferences.inspect('files.autoSave').globalValue;
68
+
69
+ before(async function () {
70
+ await pluginService.didStart;
71
+ await Promise.all([typescriptPluginId, referencesPluginId, eslintPluginId].map(async pluginId => {
72
+ if (!pluginService.getPlugin(pluginId)) {
73
+ throw new Error(pluginId + ' should be started');
74
+ }
75
+ await pluginService.activatePlugin(pluginId);
76
+ }).concat(preferences.set('files.autoSave', 'off', PreferenceScope.User)));
77
+ await preferences.set('files.refactoring.autoSave', 'off', PreferenceScope.User);
78
+ });
79
+
80
+ beforeEach(async function () {
81
+ await editorManager.closeAll({ save: false });
82
+ await new Promise(resolve => setTimeout(resolve, 500));
83
+ });
84
+
85
+ const toTearDown = new DisposableCollection();
86
+ afterEach(async () => {
87
+ toTearDown.dispose();
88
+ await editorManager.closeAll({ save: false });
89
+ await new Promise(resolve => setTimeout(resolve, 500));
90
+ });
91
+
92
+ after(async () => {
93
+ await preferences.set('files.autoSave', originalAutoSaveValue, PreferenceScope.User);
94
+ })
95
+
96
+ /**
97
+ * @param {Uri.default} uri
98
+ * @param {boolean} preview
99
+ */
100
+ async function openEditor(uri, preview = false) {
101
+ const widget = await open(openerService, uri, { mode: 'activate', preview });
102
+ const editorWidget = widget instanceof EditorWidget ? widget : undefined;
103
+ const editor = MonacoEditor.get(editorWidget);
104
+ assert.isDefined(editor);
105
+ // wait till tsserver is running, see:
106
+ // https://github.com/microsoft/vscode/blob/93cbbc5cae50e9f5f5046343c751b6d010468200/extensions/typescript-language-features/src/extension.ts#L98-L103
107
+ await waitForAnimation(() => contextKeyService.match('typescript.isManagedFile'));
108
+ // wait till projects are loaded, see:
109
+ // https://github.com/microsoft/vscode/blob/4aac84268c6226d23828cc6a1fe45ee3982927f0/extensions/typescript-language-features/src/typescriptServiceClient.ts#L911
110
+ await waitForAnimation(() => !progressStatusBarItem.currentProgress);
111
+ return /** @type {MonacoEditor} */ (editor);
112
+ }
113
+
114
+ /**
115
+ * @param {() => Promise<unknown> | unknown} condition
116
+ * @param {number | undefined} [timeout]
117
+ * @param {string | undefined} [message]
118
+ * @returns {Promise<void>}
119
+ */
120
+ function waitForAnimation(condition, timeout, message) {
121
+ const success = new Promise(async (resolve, reject) => {
122
+ if (timeout === undefined) {
123
+ timeout = 100000;
124
+ }
125
+
126
+ let timedOut = false;
127
+ const handle = setTimeout(() => {
128
+ console.log(message);
129
+ timedOut = true;
130
+ }, timeout);
131
+
132
+ toTearDown.push({ dispose: () => reject(message ?? 'Test terminated before resolution.') });
133
+ do {
134
+ await animationFrame();
135
+ } while (!timedOut && !condition());
136
+ if (timedOut) {
137
+ reject(new Error(message ?? 'Wait for animation timed out.'));
138
+ } else {
139
+ clearTimeout(handle);
140
+ resolve(undefined);
141
+ }
142
+
143
+ });
144
+ return success;
145
+ }
146
+
147
+ /**
148
+ * We ignore attributes on purpose since they are not stable.
149
+ * But structure is important for us to see whether the plain text is rendered or markdown.
150
+ *
151
+ * @param {Element} element
152
+ * @returns {string}
153
+ */
154
+ function nodeAsString(element, indentation = '') {
155
+ const header = element.tagName;
156
+ let body = '';
157
+ const childIndentation = indentation + ' ';
158
+ for (let i = 0; i < element.childNodes.length; i++) {
159
+ const childNode = element.childNodes.item(i);
160
+ if (childNode.nodeType === childNode.TEXT_NODE) {
161
+ body += childIndentation + `"${childNode.textContent}"` + '\n';
162
+ } else if (childNode instanceof HTMLElement) {
163
+ body += childIndentation + nodeAsString(childNode, childIndentation) + '\n';
164
+ }
165
+ }
166
+ const result = header + (body ? ' {\n' + body + indentation + '}' : '');
167
+ if (indentation) {
168
+ return result;
169
+ }
170
+ return `\n${result}\n`;
171
+ }
172
+
173
+ /**
174
+ * @param {MonacoEditor} editor
175
+ */
176
+ async function assertPeekOpened(editor) {
177
+ /** @type any */
178
+ const referencesController = editor.getControl().getContribution('editor.contrib.referencesController');
179
+ await waitForAnimation(() => referencesController._widget && referencesController._widget._tree.getFocus().length);
180
+
181
+ assert.isFalse(contextKeyService.match('editorTextFocus'));
182
+ assert.isTrue(contextKeyService.match('referenceSearchVisible'));
183
+ assert.isTrue(contextKeyService.match('listFocus'));
184
+ }
185
+
186
+ /**
187
+ * @param {MonacoEditor} editor
188
+ */
189
+ async function openPeek(editor) {
190
+ assert.isTrue(contextKeyService.match('editorTextFocus'));
191
+ assert.isFalse(contextKeyService.match('referenceSearchVisible'));
192
+ assert.isFalse(contextKeyService.match('listFocus'));
193
+
194
+ await commands.executeCommand('editor.action.peekDefinition');
195
+ await assertPeekOpened(editor);
196
+ }
197
+
198
+ async function openReference() {
199
+ keybindings.dispatchKeyDown('Enter');
200
+ await waitForAnimation(() => contextKeyService.match('listFocus'));
201
+ assert.isFalse(contextKeyService.match('editorTextFocus'));
202
+ assert.isTrue(contextKeyService.match('referenceSearchVisible'));
203
+ assert.isTrue(contextKeyService.match('listFocus'));
204
+ }
205
+
206
+ /**
207
+ * @param {MonacoEditor} editor
208
+ */
209
+ async function closePeek(editor) {
210
+ await assertPeekOpened(editor);
211
+
212
+ console.log('closePeek() - Attempt to close by sending "Escape"');
213
+ keybindings.dispatchKeyDown('Escape');
214
+ await waitForAnimation(() => {
215
+ const isClosed = !contextKeyService.match('listFocus');
216
+ if (!isClosed) {
217
+ console.log('...');
218
+ keybindings.dispatchKeyDown('Escape');
219
+ return false;
220
+ }
221
+ return true;
222
+ });
223
+ assert.isTrue(contextKeyService.match('editorTextFocus'));
224
+ assert.isFalse(contextKeyService.match('referenceSearchVisible'));
225
+ assert.isFalse(contextKeyService.match('listFocus'));
226
+ }
227
+
228
+ it('document formatting should be visible and enabled', async function () {
229
+ await openEditor(demoFileUri);
230
+ const menu = menuFactory.createContextMenu(EDITOR_CONTEXT_MENU);
231
+ const item = menu.items.find(i => i.command === 'editor.action.formatDocument');
232
+ if (item) {
233
+ assert.isTrue(item.isVisible);
234
+ assert.isTrue(item.isEnabled);
235
+ } else {
236
+ assert.isDefined(item);
237
+ }
238
+ });
239
+
240
+ describe('editor.action.revealDefinition', function () {
241
+ for (const preview of [false, true]) {
242
+ const from = 'an editor' + (preview ? ' preview' : '');
243
+ it('within ' + from, async function () {
244
+ const editor = await openEditor(demoFileUri, preview);
245
+ // const demoInstance = new Demo|Class('demo');
246
+ editor.getControl().setPosition({ lineNumber: 24, column: 30 });
247
+ assert.equal(editor.getControl().getModel().getWordAtPosition(editor.getControl().getPosition()).word, 'DemoClass');
248
+
249
+ await commands.executeCommand('editor.action.revealDefinition');
250
+
251
+ const activeEditor = /** @type {MonacoEditor} */ MonacoEditor.get(editorManager.activeEditor);
252
+ assert.equal(editorManager.activeEditor.isPreview, preview);
253
+ assert.equal(activeEditor.uri.toString(), demoFileUri.toString());
254
+ // constructor(someString: string) {
255
+ const { lineNumber, column } = activeEditor.getControl().getPosition();
256
+ assert.deepEqual({ lineNumber, column }, { lineNumber: 11, column: 5 });
257
+ assert.equal(activeEditor.getControl().getModel().getWordAtPosition({ lineNumber, column }).word, 'constructor');
258
+ });
259
+
260
+ // Note: this test generate annoying but apparently harmless error traces, during cleanup:
261
+ // [Error: Error: Cannot update an unmounted root.
262
+ // at ReactDOMRoot.__webpack_modules__.../../node_modules/react-dom/cjs/react-dom.development.js.ReactDOMHydrationRoot.render.ReactDOMRoot.render (http://127.0.0.1:3000/bundle.js:92757:11)
263
+ // at BreadcrumbsRenderer.render (http://127.0.0.1:3000/bundle.js:137316:23)
264
+ // at BreadcrumbsRenderer.update (http://127.0.0.1:3000/bundle.js:108722:14)
265
+ // at BreadcrumbsRenderer.refresh (http://127.0.0.1:3000/bundle.js:108719:14)
266
+ // at async ToolbarAwareTabBar.updateBreadcrumbs (http://127.0.0.1:3000/bundle.js:128229:9)]
267
+ it(`from ${from} to another editor`, async function () {
268
+ await editorManager.open(definitionFileUri, { mode: 'open' });
269
+
270
+ const editor = await openEditor(demoFileUri, preview);
271
+ // const bar: Defined|Interface = { coolField: [] };
272
+ editor.getControl().setPosition({ lineNumber: 32, column: 19 });
273
+ assert.equal(editor.getControl().getModel().getWordAtPosition(editor.getControl().getPosition()).word, 'DefinedInterface');
274
+
275
+ await commands.executeCommand('editor.action.revealDefinition');
276
+
277
+ const activeEditor = /** @type {MonacoEditor} */ MonacoEditor.get(editorManager.activeEditor);
278
+ assert.isFalse(editorManager.activeEditor.isPreview);
279
+ assert.equal(activeEditor.uri.toString(), definitionFileUri.toString());
280
+
281
+ // export interface |DefinedInterface {
282
+ const { lineNumber, column } = activeEditor.getControl().getPosition();
283
+ assert.deepEqual({ lineNumber, column }, { lineNumber: 2, column: 18 });
284
+ assert.equal(activeEditor.getControl().getModel().getWordAtPosition({ lineNumber, column }).word, 'DefinedInterface');
285
+ });
286
+
287
+ it(`from ${from} to an editor preview`, async function () {
288
+ const editor = await openEditor(demoFileUri);
289
+ // const bar: Defined|Interface = { coolField: [] };
290
+ editor.getControl().setPosition({ lineNumber: 32, column: 19 });
291
+ assert.equal(editor.getControl().getModel().getWordAtPosition(editor.getControl().getPosition()).word, 'DefinedInterface');
292
+
293
+ await commands.executeCommand('editor.action.revealDefinition');
294
+
295
+ const activeEditor = /** @type {MonacoEditor} */ MonacoEditor.get(editorManager.activeEditor);
296
+ assert.isTrue(editorManager.activeEditor.isPreview);
297
+ assert.equal(activeEditor.uri.toString(), definitionFileUri.toString());
298
+ // export interface |DefinedInterface {
299
+ const { lineNumber, column } = activeEditor.getControl().getPosition();
300
+ assert.deepEqual({ lineNumber, column }, { lineNumber: 2, column: 18 });
301
+ assert.equal(activeEditor.getControl().getModel().getWordAtPosition({ lineNumber, column }).word, 'DefinedInterface');
302
+ });
303
+ }
304
+ });
305
+
306
+ describe('editor.action.peekDefinition', function () {
307
+
308
+ for (const preview of [false, true]) {
309
+ const from = 'an editor' + (preview ? ' preview' : '');
310
+ it('within ' + from, async function () {
311
+ const editor = await openEditor(demoFileUri, preview);
312
+ editor.getControl().revealLine(24);
313
+ // const demoInstance = new Demo|Class('demo');
314
+ editor.getControl().setPosition({ lineNumber: 24, column: 30 });
315
+ assert.equal(editor.getControl().getModel().getWordAtPosition(editor.getControl().getPosition()).word, 'DemoClass');
316
+
317
+ await openPeek(editor);
318
+ await openReference();
319
+
320
+ const activeEditor = /** @type {MonacoEditor} */ MonacoEditor.get(editorManager.activeEditor);
321
+ assert.equal(editorManager.activeEditor.isPreview, preview);
322
+ assert.equal(activeEditor.uri.toString(), demoFileUri.toString());
323
+ // constructor(someString: string) {
324
+ const { lineNumber, column } = activeEditor.getControl().getPosition();
325
+ assert.deepEqual({ lineNumber, column }, { lineNumber: 11, column: 5 });
326
+ assert.equal(activeEditor.getControl().getModel().getWordAtPosition({ lineNumber, column }).word, 'constructor');
327
+
328
+ await closePeek(activeEditor);
329
+ });
330
+
331
+ // Note: this test generate annoying but apparently harmless error traces, during cleanup:
332
+ // [Error: Error: Cannot update an unmounted root.
333
+ // at ReactDOMRoot.__webpack_modules__.../../node_modules/react-dom/cjs/react-dom.development.js.ReactDOMHydrationRoot.render.ReactDOMRoot.render (http://127.0.0.1:3000/bundle.js:92757:11)
334
+ // at BreadcrumbsRenderer.render (http://127.0.0.1:3000/bundle.js:137316:23)
335
+ // at BreadcrumbsRenderer.update (http://127.0.0.1:3000/bundle.js:108722:14)
336
+ // at BreadcrumbsRenderer.refresh (http://127.0.0.1:3000/bundle.js:108719:14)
337
+ // at async ToolbarAwareTabBar.updateBreadcrumbs (http://127.0.0.1:3000/bundle.js:128229:9)]
338
+ it(`from ${from} to another editor`, async function () {
339
+ await editorManager.open(definitionFileUri, { mode: 'open' });
340
+
341
+ const editor = await openEditor(demoFileUri, preview);
342
+ editor.getControl().revealLine(32);
343
+ // const bar: Defined|Interface = { coolField: [] };
344
+ editor.getControl().setPosition({ lineNumber: 32, column: 19 });
345
+ assert.equal(editor.getControl().getModel().getWordAtPosition(editor.getControl().getPosition()).word, 'DefinedInterface');
346
+
347
+ await openPeek(editor);
348
+ await openReference();
349
+
350
+ const activeEditor = /** @type {MonacoEditor} */ MonacoEditor.get(editorManager.activeEditor);
351
+ assert.isFalse(editorManager.activeEditor.isPreview);
352
+ assert.equal(activeEditor.uri.toString(), definitionFileUri.toString());
353
+ // export interface |DefinedInterface {
354
+ const { lineNumber, column } = activeEditor.getControl().getPosition();
355
+ assert.deepEqual({ lineNumber, column }, { lineNumber: 2, column: 18 });
356
+ assert.equal(activeEditor.getControl().getModel().getWordAtPosition({ lineNumber, column }).word, 'DefinedInterface');
357
+
358
+ await closePeek(activeEditor);
359
+ });
360
+
361
+ it(`from ${from} to an editor preview`, async function () {
362
+ const editor = await openEditor(demoFileUri);
363
+ editor.getControl().revealLine(32);
364
+ // const bar: Defined|Interface = { coolField: [] };
365
+ editor.getControl().setPosition({ lineNumber: 32, column: 19 });
366
+ assert.equal(editor.getControl().getModel().getWordAtPosition(editor.getControl().getPosition()).word, 'DefinedInterface');
367
+
368
+ await openPeek(editor);
369
+ await openReference();
370
+
371
+ const activeEditor = /** @type {MonacoEditor} */ MonacoEditor.get(editorManager.activeEditor);
372
+ assert.isTrue(editorManager.activeEditor.isPreview);
373
+ assert.equal(activeEditor.uri.toString(), definitionFileUri.toString());
374
+ // export interface |DefinedInterface {
375
+ const { lineNumber, column } = activeEditor.getControl().getPosition();
376
+ assert.deepEqual({ lineNumber, column }, { lineNumber: 2, column: 18 });
377
+ assert.equal(activeEditor.getControl().getModel().getWordAtPosition({ lineNumber, column }).word, 'DefinedInterface');
378
+
379
+ await closePeek(activeEditor);
380
+ });
381
+ }
382
+ });
383
+
384
+ it('editor.action.triggerSuggest', async function () {
385
+ const editor = await openEditor(demoFileUri);
386
+ editor.getControl().setPosition({ lineNumber: 26, column: 46 });
387
+ editor.getControl().setSelection(new Selection(26, 46, 26, 35));
388
+ assert.equal(editor.getControl().getModel().getWordAtPosition(editor.getControl().getPosition()).word, 'stringField');
389
+
390
+ assert.isTrue(contextKeyService.match('editorTextFocus'));
391
+ assert.isFalse(contextKeyService.match('suggestWidgetVisible'));
392
+
393
+ await commands.executeCommand('editor.action.triggerSuggest');
394
+ await waitForAnimation(() => contextKeyService.match('suggestWidgetVisible'));
395
+
396
+ assert.isTrue(contextKeyService.match('editorTextFocus'));
397
+ assert.isTrue(contextKeyService.match('suggestWidgetVisible'));
398
+
399
+ // May need a couple extra "Enter" being sent for the suggest to be accepted
400
+ keybindings.dispatchKeyDown('Enter');
401
+ await waitForAnimation(() => {
402
+ const suggestWidgetDismissed = !contextKeyService.match('suggestWidgetVisible');
403
+ if (!suggestWidgetDismissed) {
404
+ console.log('Re-try accepting suggest using "Enter" key');
405
+ keybindings.dispatchKeyDown('Enter');
406
+ return false;
407
+ }
408
+ return true;
409
+ }, 5000, 'Suggest widget has not been dismissed despite attempts to accept suggestion');
410
+
411
+ assert.isTrue(contextKeyService.match('editorTextFocus'));
412
+ assert.isFalse(contextKeyService.match('suggestWidgetVisible'));
413
+
414
+ const activeEditor = /** @type {MonacoEditor} */ MonacoEditor.get(editorManager.activeEditor);
415
+ assert.equal(activeEditor.uri.toString(), demoFileUri.toString());
416
+ // demoInstance.stringField;
417
+ const { lineNumber, column } = activeEditor.getControl().getPosition();
418
+ assert.deepEqual({ lineNumber, column }, { lineNumber: 26, column: 46 });
419
+ assert.equal(activeEditor.getControl().getModel().getWordAtPosition({ lineNumber, column }).word, 'doSomething');
420
+ });
421
+
422
+ it('editor.action.triggerSuggest navigate', async function () {
423
+ const editor = await openEditor(demoFileUri);
424
+ // demoInstance.[|stringField];
425
+ editor.getControl().setPosition({ lineNumber: 26, column: 46 });
426
+ editor.getControl().setSelection(new Selection(26, 46, 26, 35));
427
+ assert.equal(editor.getControl().getModel().getWordAtPosition(editor.getControl().getPosition()).word, 'stringField');
428
+
429
+ /** @type {import('@theia/monaco-editor-core/src/vs/editor/contrib/suggest/browser/suggestController').SuggestController} */
430
+ const suggest = editor.getControl().getContribution('editor.contrib.suggestController');
431
+ const getFocusedLabel = () => {
432
+ const focusedItem = suggest.widget.value.getFocusedItem();
433
+ return focusedItem && focusedItem.item.completion.label;
434
+ };
435
+
436
+ assert.isUndefined(getFocusedLabel());
437
+ assert.isFalse(contextKeyService.match('suggestWidgetVisible'));
438
+
439
+ await commands.executeCommand('editor.action.triggerSuggest');
440
+ await waitForAnimation(() => contextKeyService.match('suggestWidgetVisible') && getFocusedLabel() === 'doSomething', 5000);
441
+
442
+ assert.equal(getFocusedLabel(), 'doSomething');
443
+ assert.isTrue(contextKeyService.match('suggestWidgetVisible'));
444
+
445
+ keybindings.dispatchKeyDown('ArrowDown');
446
+ await waitForAnimation(() => contextKeyService.match('suggestWidgetVisible') && getFocusedLabel() === 'numberField', 2000);
447
+
448
+ assert.equal(getFocusedLabel(), 'numberField');
449
+ assert.isTrue(contextKeyService.match('suggestWidgetVisible'));
450
+
451
+ keybindings.dispatchKeyDown('ArrowUp');
452
+ await waitForAnimation(() => contextKeyService.match('suggestWidgetVisible') && getFocusedLabel() === 'doSomething', 2000);
453
+
454
+ assert.equal(getFocusedLabel(), 'doSomething');
455
+ assert.isTrue(contextKeyService.match('suggestWidgetVisible'));
456
+
457
+ keybindings.dispatchKeyDown('Escape');
458
+
459
+ // once in a while, a second "Escape" is needed to dismiss widget
460
+ await waitForAnimation(() => {
461
+ const suggestWidgetDismissed = !contextKeyService.match('suggestWidgetVisible') && getFocusedLabel() === undefined;
462
+ if (!suggestWidgetDismissed) {
463
+ console.log('Re-try to dismiss suggest using "Escape" key');
464
+ keybindings.dispatchKeyDown('Escape');
465
+ return false;
466
+ }
467
+ return true;
468
+ }, 5000, 'Suggest widget not dismissed');
469
+
470
+ assert.isUndefined(getFocusedLabel());
471
+ assert.isFalse(contextKeyService.match('suggestWidgetVisible'));
472
+ });
473
+
474
+ it('editor.action.rename', async function () {
475
+ const editor = await openEditor(demoFileUri);
476
+ // const |demoVariable = demoInstance.stringField;
477
+ editor.getControl().setPosition({ lineNumber: 26, column: 7 });
478
+ assert.equal(editor.getControl().getModel().getWordAtPosition(editor.getControl().getPosition()).word, 'demoVariable');
479
+
480
+ assert.isTrue(contextKeyService.match('editorTextFocus'));
481
+ assert.isFalse(contextKeyService.match('renameInputVisible'));
482
+
483
+ commands.executeCommand('editor.action.rename');
484
+ await waitForAnimation(() => contextKeyService.match('renameInputVisible')
485
+ && document.activeElement instanceof HTMLInputElement
486
+ && document.activeElement.selectionEnd === 'demoVariable'.length);
487
+ assert.isFalse(contextKeyService.match('editorTextFocus'));
488
+ assert.isTrue(contextKeyService.match('renameInputVisible'));
489
+
490
+ const input = document.activeElement;
491
+ if (!(input instanceof HTMLInputElement)) {
492
+ assert.fail('expected focused input, but: ' + input);
493
+ return;
494
+ }
495
+
496
+ input.value = 'foo';
497
+ keybindings.dispatchKeyDown('Enter', input);
498
+
499
+ // all rename edits should be grouped in one edit operation and applied in the same tick
500
+ await new Promise(resolve => editor.getControl().onDidChangeModelContent(resolve));
501
+
502
+ assert.isTrue(contextKeyService.match('editorTextFocus'));
503
+ assert.isFalse(contextKeyService.match('renameInputVisible'));
504
+
505
+ const activeEditor = /** @type {MonacoEditor} */ MonacoEditor.get(editorManager.activeEditor);
506
+ assert.equal(activeEditor.uri.toString(), demoFileUri.toString());
507
+ // const |foo = new Container();
508
+ const { lineNumber, column } = activeEditor.getControl().getPosition();
509
+ assert.deepEqual({ lineNumber, column }, { lineNumber: 26, column: 7 });
510
+ assert.equal(activeEditor.getControl().getModel().getWordAtPosition({ lineNumber: 28, column: 1 }).word, 'foo');
511
+ });
512
+
513
+ it('editor.action.triggerParameterHints', async function () {
514
+ const editor = await openEditor(demoFileUri);
515
+ // const demoInstance = new DemoClass('|demo');
516
+ editor.getControl().setPosition({ lineNumber: 24, column: 37 });
517
+ assert.equal(editor.getControl().getModel().getWordAtPosition(editor.getControl().getPosition()).word, "demo");
518
+
519
+ assert.isTrue(contextKeyService.match('editorTextFocus'));
520
+ assert.isFalse(contextKeyService.match('parameterHintsVisible'));
521
+
522
+ await commands.executeCommand('editor.action.triggerParameterHints');
523
+ await waitForAnimation(() => contextKeyService.match('parameterHintsVisible'));
524
+
525
+ assert.isTrue(contextKeyService.match('editorTextFocus'));
526
+ assert.isTrue(contextKeyService.match('parameterHintsVisible'));
527
+
528
+ keybindings.dispatchKeyDown('Escape');
529
+ await waitForAnimation(() => !contextKeyService.match('parameterHintsVisible'));
530
+
531
+ assert.isTrue(contextKeyService.match('editorTextFocus'));
532
+ assert.isFalse(contextKeyService.match('parameterHintsVisible'));
533
+ });
534
+
535
+ it('editor.action.showHover', async function () {
536
+ const editor = await openEditor(demoFileUri);
537
+ // class |DemoClass);
538
+ editor.getControl().setPosition({ lineNumber: 8, column: 7 });
539
+ assert.equal(editor.getControl().getModel().getWordAtPosition(editor.getControl().getPosition()).word, 'DemoClass');
540
+
541
+ /** @type {import('@theia/monaco-editor-core/src/vs/editor/contrib/hover/browser/hover').ModesHoverController} */
542
+ const hover = editor.getControl().getContribution('editor.contrib.hover');
543
+
544
+ assert.isTrue(contextKeyService.match('editorTextFocus'));
545
+ assert.isFalse(Boolean(hover['_contentWidget']?.['_widget']?.['_visibleData']));
546
+ await commands.executeCommand('editor.action.showHover');
547
+ let doLog = true;
548
+ await waitForAnimation(() => hover['_contentWidget']?.['_widget']?.['_visibleData']);
549
+ assert.isTrue(contextKeyService.match('editorTextFocus'));
550
+ assert.isTrue(Boolean(hover['_contentWidget']?.['_widget']?.['_visibleData']));
551
+ assert.deepEqual(nodeAsString(hover['_contentWidget']?.['_widget']?.['_hover']?.['contentsDomNode']).trim(), `
552
+ DIV {
553
+ DIV {
554
+ DIV {
555
+ DIV {
556
+ DIV {
557
+ SPAN {
558
+ DIV {
559
+ SPAN {
560
+ "class"
561
+ }
562
+ SPAN {
563
+ " "
564
+ }
565
+ SPAN {
566
+ "DemoClass"
567
+ }
568
+ }
569
+ }
570
+ }
571
+ }
572
+ }
573
+ }
574
+ }`.trim());
575
+ keybindings.dispatchKeyDown('Escape');
576
+ await waitForAnimation(() => !hover['_contentWidget']?.['_widget']?.['_visibleData']);
577
+ assert.isTrue(contextKeyService.match('editorTextFocus'));
578
+ assert.isFalse(Boolean(hover['_contentWidget']?.['_widget']?.['_visibleData']));
579
+ });
580
+
581
+ it('highlight semantic (write) occurrences', async function () {
582
+ const editor = await openEditor(demoFileUri);
583
+ // const |container = new Container();
584
+ const lineNumber = 24;
585
+ const column = 7;
586
+ const endColumn = column + 'demoInstance'.length;
587
+
588
+ const hasWriteDecoration = () => {
589
+ for (const decoration of editor.getControl().getModel().getLineDecorations(lineNumber)) {
590
+ if (decoration.range.startColumn === column && decoration.range.endColumn === endColumn && decoration.options.className === 'wordHighlightStrong') {
591
+ return true;
592
+ }
593
+ }
594
+ return false;
595
+ };
596
+ assert.isFalse(hasWriteDecoration());
597
+
598
+ editor.getControl().setPosition({ lineNumber, column });
599
+ assert.equal(editor.getControl().getModel().getWordAtPosition(editor.getControl().getPosition()).word, 'demoInstance');
600
+ // highlight occurrences is not trigged on the explicit position change, so move a cursor as a user
601
+ keybindings.dispatchKeyDown('ArrowRight');
602
+ await waitForAnimation(() => hasWriteDecoration());
603
+
604
+ assert.isTrue(hasWriteDecoration());
605
+ });
606
+
607
+ it('editor.action.goToImplementation', async function () {
608
+ const editor = await openEditor(demoFileUri);
609
+ // const demoInstance = new Demo|Class('demo');
610
+ editor.getControl().setPosition({ lineNumber: 24, column: 30 });
611
+ assert.equal(editor.getControl().getModel().getWordAtPosition(editor.getControl().getPosition()).word, 'DemoClass');
612
+
613
+ await commands.executeCommand('editor.action.goToImplementation');
614
+
615
+ const activeEditor = /** @type {MonacoEditor} */ MonacoEditor.get(editorManager.activeEditor);
616
+ assert.equal(activeEditor.uri.toString(), demoFileUri.toString());
617
+ // class |DemoClass implements DemoInterface {
618
+ const { lineNumber, column } = activeEditor.getControl().getPosition();
619
+ assert.deepEqual({ lineNumber, column }, { lineNumber: 8, column: 7 });
620
+ assert.equal(activeEditor.getControl().getModel().getWordAtPosition({ lineNumber, column }).word, 'DemoClass');
621
+ });
622
+
623
+ it('editor.action.goToTypeDefinition', async function () {
624
+ const editor = await openEditor(demoFileUri);
625
+ // const demoVariable = demo|Instance.stringField;
626
+ editor.getControl().setPosition({ lineNumber: 26, column: 26 });
627
+ assert.equal(editor.getControl().getModel().getWordAtPosition(editor.getControl().getPosition()).word, 'demoInstance');
628
+
629
+ await commands.executeCommand('editor.action.goToTypeDefinition');
630
+
631
+ const activeEditor = /** @type {MonacoEditor} */ MonacoEditor.get(editorManager.activeEditor);
632
+ assert.equal(activeEditor.uri.toString(), demoFileUri.toString());
633
+ // class |DemoClass implements DemoInterface {
634
+ const { lineNumber, column } = activeEditor.getControl().getPosition();
635
+ assert.deepEqual({ lineNumber, column }, { lineNumber: 8, column: 7 });
636
+ assert.equal(activeEditor.getControl().getModel().getWordAtPosition({ lineNumber, column }).word, 'DemoClass');
637
+ });
638
+
639
+ it('run reference code lens', async function () {
640
+ const preferenceName = 'typescript.referencesCodeLens.enabled';
641
+ const globalValue = preferences.inspect(preferenceName).globalValue;
642
+ toTearDown.push({ dispose: () => preferences.set(preferenceName, globalValue, PreferenceScope.User) });
643
+ await preferences.set(preferenceName, false, PreferenceScope.User);
644
+
645
+ const editor = await openEditor(demoFileUri);
646
+
647
+ /** @type {import('@theia/monaco-editor-core/src/vs/editor/contrib/codelens/browser/codelensController').CodeLensContribution} */
648
+ const codeLens = editor.getControl().getContribution('css.editor.codeLens');
649
+ const codeLensNode = () => codeLens['_lenses'][0]?.['_contentWidget']?.['_domNode'];
650
+ const codeLensNodeVisible = () => {
651
+ const n = codeLensNode();
652
+ return !!n && n.style.visibility !== 'hidden';
653
+ };
654
+
655
+ assert.isFalse(codeLensNodeVisible());
656
+
657
+ // |interface DemoInterface {
658
+ const position = { lineNumber: 2, column: 1 };
659
+ await preferences.set(preferenceName, true, PreferenceScope.User);
660
+
661
+ editor.getControl().revealPosition(position);
662
+ await waitForAnimation(() => codeLensNodeVisible());
663
+
664
+ assert.isTrue(codeLensNodeVisible());
665
+ const node = codeLensNode();
666
+ assert.isDefined(node);
667
+ assert.equal(nodeAsString(node), `
668
+ SPAN {
669
+ A {
670
+ "1 reference"
671
+ }
672
+ }
673
+ `);
674
+ const link = node.getElementsByTagName('a').item(0);
675
+ assert.isDefined(link);
676
+ link.dispatchEvent(new MouseEvent('mousedown', { bubbles: true }));
677
+ await assertPeekOpened(editor);
678
+ await closePeek(editor);
679
+ });
680
+
681
+ it('editor.action.quickFix', async function () {
682
+ const column = 45;
683
+ const lineNumber = 26;
684
+ const editor = await openEditor(demoFileUri);
685
+ const currentChar = () => editor.getControl().getModel().getLineContent(lineNumber).charAt(column - 1);
686
+
687
+ editor.getControl().getModel().applyEdits([{
688
+ range: {
689
+ startLineNumber: lineNumber,
690
+ endLineNumber: lineNumber,
691
+ startColumn: 45,
692
+ endColumn: 46
693
+ },
694
+ forceMoveMarkers: false,
695
+ text: ''
696
+ }]);
697
+ editor.getControl().setPosition({ lineNumber, column });
698
+ editor.getControl().revealPosition({ lineNumber, column });
699
+ assert.equal(currentChar(), ';', 'Failed at assert 1');
700
+
701
+ /** @type {import('@theia/monaco-editor-core/src/vs/editor/contrib/codeAction/browser/codeActionController').CodeActionController} */
702
+ const codeActionController = editor.getControl().getContribution('editor.contrib.codeActionController');
703
+ const lightBulbNode = () => {
704
+ const lightBulb = codeActionController['_lightBulbWidget'].rawValue;
705
+ return lightBulb && lightBulb['_domNode'];
706
+ };
707
+ const lightBulbVisible = () => {
708
+ const node = lightBulbNode();
709
+ return !!node && node.style.visibility !== 'hidden';
710
+ };
711
+
712
+ await timeout(1000); // quick fix is always available: need to wait for the error fix to become available.
713
+ await commands.executeCommand('editor.action.quickFix');
714
+ const codeActionSelector = '.action-widget';
715
+ assert.isFalse(!!document.querySelector(codeActionSelector), 'Failed at assert 3 - codeActionWidget should not be visible');
716
+
717
+ console.log('Waiting for Quick Fix widget to be visible');
718
+ await waitForAnimation(() => {
719
+ const quickFixWidgetVisible = !!document.querySelector(codeActionSelector);
720
+ if (!quickFixWidgetVisible) {
721
+ // console.log('...');
722
+ return false;
723
+ }
724
+ return true;
725
+ }, 10000, 'Timed-out waiting for the QuickFix widget to appear');
726
+ await animationFrame();
727
+
728
+ assert.isTrue(lightBulbVisible(), 'Failed at assert 4');
729
+ keybindings.dispatchKeyDown('Enter');
730
+ console.log('Waiting for confirmation that QuickFix has taken effect');
731
+
732
+ await waitForAnimation(() => currentChar() === 'd', 10000, 'Failed to detect expected selected char: "d"');
733
+ assert.equal(currentChar(), 'd', 'Failed at assert 5');
734
+ });
735
+
736
+ it('editor.action.formatDocument', async function () {
737
+ const lineNumber = 5;
738
+ const editor = await openEditor(demoFileUri);
739
+ const originalLength = editor.getControl().getModel().getLineLength(lineNumber);
740
+
741
+ // doSomething(): number; --> doSomething() : number;
742
+ editor.getControl().getModel().applyEdits([{
743
+ range: Range.fromPositions({ lineNumber, column: 18 }, { lineNumber, column: 18 }),
744
+ forceMoveMarkers: false,
745
+ text: ' '
746
+ }]);
747
+
748
+ assert.equal(editor.getControl().getModel().getLineLength(lineNumber), originalLength + 1);
749
+
750
+ await commands.executeCommand('editor.action.formatDocument');
751
+
752
+ assert.equal(editor.getControl().getModel().getLineLength(lineNumber), originalLength);
753
+ });
754
+
755
+ it('editor.action.formatSelection', async function () {
756
+ // doSomething(): number {
757
+ const lineNumber = 15;
758
+ const editor = await openEditor(demoFileUri);
759
+ const originalLength /* 28 */ = editor.getControl().getModel().getLineLength(lineNumber);
760
+
761
+ // doSomething( ) : number {
762
+ editor.getControl().getModel().applyEdits([{
763
+ range: Range.fromPositions({ lineNumber, column: 17 }, { lineNumber, column: 18 }),
764
+ forceMoveMarkers: false,
765
+ text: ' ) '
766
+ }]);
767
+
768
+ assert.equal(editor.getControl().getModel().getLineLength(lineNumber), originalLength + 4);
769
+
770
+ // [const { Container }] = require('inversify');
771
+ editor.getControl().setSelection({ startLineNumber: lineNumber, startColumn: 1, endLineNumber: lineNumber, endColumn: 32 });
772
+
773
+ await commands.executeCommand('editor.action.formatSelection');
774
+
775
+ // [const { Container }] = require('inversify');
776
+ assert.equal(editor.getControl().getModel().getLineLength(lineNumber), originalLength);
777
+ });
778
+
779
+ it('Can execute code actions', async function () {
780
+ const editor = await openEditor(demoFileUri);
781
+ /** @type {import('@theia/monaco-editor-core/src/vs/editor/contrib/codeAction/browser/codeActionController').CodeActionController} */
782
+ const codeActionController = editor.getControl().getContribution('editor.contrib.codeActionController');
783
+ const isActionAvailable = () => {
784
+ const lightbulbVisibility = codeActionController['_lightBulbWidget'].rawValue?.['_domNode'].style.visibility;
785
+ return lightbulbVisibility !== undefined && lightbulbVisibility !== 'hidden';
786
+ }
787
+ assert.isFalse(isActionAvailable());
788
+ assert.strictEqual(editor.getControl().getModel().getLineContent(30), 'import { DefinedInterface } from "./demo-definitions-file";');
789
+ editor.getControl().revealLine(30);
790
+ editor.getControl().setSelection(new Selection(30, 1, 30, 60));
791
+ await waitForAnimation(() => isActionAvailable(), 5000, 'No code action available. (1)');
792
+ assert.isTrue(isActionAvailable());
793
+
794
+ try { // for some reason, we need to wait a second here, otherwise, we run into some cancellation.
795
+ await waitForAnimation(() => false, 1000);
796
+ } catch (e) {
797
+ }
798
+
799
+ await commands.executeCommand('editor.action.quickFix');
800
+ await waitForAnimation(() => {
801
+ const elements = document.querySelector('.action-widget');
802
+ return !!elements;
803
+ }, 5000, 'No context menu appeared. (1)');
804
+ await animationFrame();
805
+
806
+ keybindings.dispatchKeyDown('Enter');
807
+
808
+ assert.isNotNull(editor.getControl());
809
+ assert.isNotNull(editor.getControl().getModel());
810
+ console.log(`content: ${editor.getControl().getModel().getLineContent(30)}`);
811
+ await waitForAnimation(() => editor.getControl().getModel().getLineContent(30) === 'import * as demoDefinitionsFile from "./demo-definitions-file";', 5000, 'The namespace import did not take effect.');
812
+
813
+ // momentarily toggle selection, waiting for code action to become unavailable.
814
+ // Without doing this, the call to the quickfix command would sometimes fail because of an
815
+ // unexpected "no code action available" pop-up, which would trip the rest of the testcase
816
+ editor.getControl().setSelection(new Selection(30, 1, 30, 1));
817
+ console.log('waiting for code action to no longer be available');
818
+ await waitForAnimation(() => {
819
+ if (!isActionAvailable()) {
820
+ return true;
821
+ }
822
+ editor.getControl().setSelection(new Selection(30, 1, 30, 1));
823
+ console.log('...');
824
+ return !isActionAvailable();
825
+ }, 5000, 'Code action still available with no proper selection.');
826
+ // re-establish selection
827
+ editor.getControl().setSelection(new Selection(30, 1, 30, 64));
828
+ console.log('waiting for code action to become available again');
829
+ await waitForAnimation(() => {
830
+ console.log('...');
831
+ return isActionAvailable()
832
+ }, 5000, 'No code action available. (2)');
833
+
834
+ // Change import back: https://github.com/eclipse-theia/theia/issues/11059
835
+ await commands.executeCommand('editor.action.quickFix');
836
+ await waitForAnimation(() => Boolean(document.querySelector('.context-view-pointerBlock')), 5000, 'No context menu appeared. (2)');
837
+ await animationFrame();
838
+
839
+ keybindings.dispatchKeyDown('Enter');
840
+
841
+ assert.isNotNull(editor.getControl());
842
+ assert.isNotNull(editor.getControl().getModel());
843
+ await waitForAnimation(() => editor.getControl().getModel().getLineContent(30) === 'import { DefinedInterface } from "./demo-definitions-file";', 5000, 'The named import did not take effect.');
844
+ });
845
+
846
+ for (const referenceViewCommand of ['references-view.find', 'references-view.findImplementations']) {
847
+ it(referenceViewCommand, async function () {
848
+ let steps = 0;
849
+ const editor = await openEditor(demoFileUri);
850
+ editor.getControl().setPosition({ lineNumber: 24, column: 11 });
851
+ assert.equal(editor.getControl().getModel().getWordAtPosition(editor.getControl().getPosition()).word, 'demoInstance');
852
+ await commands.executeCommand(referenceViewCommand);
853
+ const view = await pluginViewRegistry.openView('references-view.tree', { reveal: true });
854
+ const expectedMessage = referenceViewCommand === 'references-view.find' ? '2 results in 1 file' : '1 result in 1 file';
855
+ const getResultText = () => view.node.getElementsByClassName('theia-TreeViewInfo').item(0)?.textContent;
856
+ await waitForAnimation(() => getResultText() === expectedMessage, 5000);
857
+ assert.equal(getResultText(), expectedMessage);
858
+ });
859
+ }
860
+ });