Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/test/browser/quickAccess.test.ts
4778 views
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 assert from 'assert';
7
import { Registry } from '../../../platform/registry/common/platform.js';
8
import { IQuickAccessRegistry, Extensions, IQuickAccessProvider, QuickAccessRegistry } from '../../../platform/quickinput/common/quickAccess.js';
9
import { IQuickPick, IQuickPickItem, IQuickInputService } from '../../../platform/quickinput/common/quickInput.js';
10
import { CancellationToken } from '../../../base/common/cancellation.js';
11
import { TestServiceAccessor, workbenchInstantiationService, createEditorPart } from './workbenchTestServices.js';
12
import { DisposableStore, toDisposable, IDisposable } from '../../../base/common/lifecycle.js';
13
import { timeout } from '../../../base/common/async.js';
14
import { PickerQuickAccessProvider, FastAndSlowPicks } from '../../../platform/quickinput/browser/pickerQuickAccess.js';
15
import { URI } from '../../../base/common/uri.js';
16
import { IEditorGroupsService } from '../../services/editor/common/editorGroupsService.js';
17
import { IEditorService } from '../../services/editor/common/editorService.js';
18
import { EditorService } from '../../services/editor/browser/editorService.js';
19
import { PickerEditorState } from '../../browser/quickaccess.js';
20
import { EditorsOrder } from '../../common/editor.js';
21
import { Range } from '../../../editor/common/core/range.js';
22
import { TestInstantiationService } from '../../../platform/instantiation/test/common/instantiationServiceMock.js';
23
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../base/test/common/utils.js';
24
import { IContextKeyService, ContextKeyExpr } from '../../../platform/contextkey/common/contextkey.js';
25
import { ContextKeyService } from '../../../platform/contextkey/browser/contextKeyService.js';
26
import { TestConfigurationService } from '../../../platform/configuration/test/common/testConfigurationService.js';
27
28
suite('QuickAccess', () => {
29
30
const disposables = ensureNoDisposablesAreLeakedInTestSuite();
31
let instantiationService: TestInstantiationService;
32
let accessor: TestServiceAccessor;
33
34
let providerDefaultCalled = false;
35
let providerDefaultCanceled = false;
36
let providerDefaultDisposed = false;
37
38
let provider1Called = false;
39
let provider1Canceled = false;
40
let provider1Disposed = false;
41
42
let provider2Called = false;
43
let provider2Canceled = false;
44
let provider2Disposed = false;
45
46
let provider3Called = false;
47
let provider3Canceled = false;
48
let provider3Disposed = false;
49
50
class TestProviderDefault implements IQuickAccessProvider {
51
52
constructor(@IQuickInputService private readonly quickInputService: IQuickInputService, disposables: DisposableStore) { }
53
54
provide(picker: IQuickPick<IQuickPickItem, { useSeparators: true }>, token: CancellationToken): IDisposable {
55
assert.ok(picker);
56
providerDefaultCalled = true;
57
const store = new DisposableStore();
58
store.add(toDisposable(() => providerDefaultDisposed = true));
59
store.add(token.onCancellationRequested(() => providerDefaultCanceled = true));
60
61
// bring up provider #3
62
setTimeout(() => this.quickInputService.quickAccess.show(providerDescriptor3.prefix));
63
64
return store;
65
}
66
}
67
68
class TestProvider1 implements IQuickAccessProvider {
69
provide(picker: IQuickPick<IQuickPickItem, { useSeparators: true }>, token: CancellationToken): IDisposable {
70
assert.ok(picker);
71
provider1Called = true;
72
const store = new DisposableStore();
73
store.add(token.onCancellationRequested(() => provider1Canceled = true));
74
75
store.add(toDisposable(() => provider1Disposed = true));
76
return store;
77
}
78
}
79
80
class TestProvider2 implements IQuickAccessProvider {
81
provide(picker: IQuickPick<IQuickPickItem, { useSeparators: true }>, token: CancellationToken): IDisposable {
82
assert.ok(picker);
83
provider2Called = true;
84
const store = new DisposableStore();
85
store.add(token.onCancellationRequested(() => provider2Canceled = true));
86
87
store.add(toDisposable(() => provider2Disposed = true));
88
return store;
89
}
90
}
91
92
class TestProvider3 implements IQuickAccessProvider {
93
provide(picker: IQuickPick<IQuickPickItem, { useSeparators: true }>, token: CancellationToken): IDisposable {
94
assert.ok(picker);
95
provider3Called = true;
96
const store = new DisposableStore();
97
store.add(token.onCancellationRequested(() => provider3Canceled = true));
98
99
// hide without picking
100
setTimeout(() => picker.hide());
101
102
store.add(toDisposable(() => provider3Disposed = true));
103
return store;
104
}
105
}
106
107
const providerDescriptorDefault = { ctor: TestProviderDefault, prefix: '', helpEntries: [] };
108
const providerDescriptor1 = { ctor: TestProvider1, prefix: 'test', helpEntries: [] };
109
const providerDescriptor2 = { ctor: TestProvider2, prefix: 'test something', helpEntries: [] };
110
const providerDescriptor3 = { ctor: TestProvider3, prefix: 'changed', helpEntries: [] };
111
112
setup(() => {
113
instantiationService = workbenchInstantiationService(undefined, disposables);
114
accessor = instantiationService.createInstance(TestServiceAccessor);
115
});
116
117
test('registry', () => {
118
const registry = (Registry.as<IQuickAccessRegistry>(Extensions.Quickaccess));
119
const restore = (registry as QuickAccessRegistry).clear();
120
const contextKeyService = instantiationService.get(IContextKeyService);
121
122
assert.ok(!registry.getQuickAccessProvider('test', contextKeyService));
123
124
const disposables = new DisposableStore();
125
126
disposables.add(registry.registerQuickAccessProvider(providerDescriptorDefault));
127
assert(registry.getQuickAccessProvider('', contextKeyService) === providerDescriptorDefault);
128
assert(registry.getQuickAccessProvider('test', contextKeyService) === providerDescriptorDefault);
129
130
const disposable = disposables.add(registry.registerQuickAccessProvider(providerDescriptor1));
131
assert(registry.getQuickAccessProvider('test', contextKeyService) === providerDescriptor1);
132
133
const providers = registry.getQuickAccessProviders(contextKeyService);
134
assert(providers.some(provider => provider.prefix === 'test'));
135
136
disposable.dispose();
137
assert(registry.getQuickAccessProvider('test', contextKeyService) === providerDescriptorDefault);
138
139
disposables.dispose();
140
assert.ok(!registry.getQuickAccessProvider('test', contextKeyService));
141
142
restore();
143
});
144
145
test('registry - when condition', () => {
146
const registry = (Registry.as<IQuickAccessRegistry>(Extensions.Quickaccess));
147
const restore = (registry as QuickAccessRegistry).clear();
148
149
// Use real ContextKeyService that properly evaluates rules
150
const contextKeyService = disposables.add(new ContextKeyService(new TestConfigurationService()));
151
const localDisposables = new DisposableStore();
152
153
// Create a context key that starts as undefined (falsy)
154
const contextKey = contextKeyService.createKey<boolean | undefined>('testQuickAccessContextKey', undefined);
155
156
// Register a provider with a when condition that requires testQuickAccessContextKey to be truthy
157
const providerWithWhen = {
158
ctor: TestProvider1,
159
prefix: 'whentest',
160
helpEntries: [],
161
when: ContextKeyExpr.has('testQuickAccessContextKey')
162
};
163
localDisposables.add(registry.registerQuickAccessProvider(providerWithWhen));
164
165
// Verify the expression works with the context key service
166
assert.strictEqual(contextKeyService.contextMatchesRules(providerWithWhen.when), false);
167
168
// Provider with false when condition should not be found
169
assert.strictEqual(registry.getQuickAccessProvider('whentest', contextKeyService), undefined);
170
171
// Should not appear in the list of providers
172
let providers = registry.getQuickAccessProviders(contextKeyService);
173
assert.ok(!providers.some(p => p.prefix === 'whentest'));
174
175
// Set the context key to true
176
contextKey.set(true);
177
178
// Verify the expression now matches
179
assert.strictEqual(contextKeyService.contextMatchesRules(providerWithWhen.when), true);
180
181
// Now the provider should be found
182
assert.strictEqual(registry.getQuickAccessProvider('whentest', contextKeyService), providerWithWhen);
183
184
// Should appear in the list of providers
185
providers = registry.getQuickAccessProviders(contextKeyService);
186
assert.ok(providers.some(p => p.prefix === 'whentest'));
187
188
// Set context key back to undefined (falsy)
189
contextKey.set(undefined);
190
191
// Provider should not be found again
192
assert.strictEqual(registry.getQuickAccessProvider('whentest', contextKeyService), undefined);
193
194
localDisposables.dispose();
195
196
restore();
197
});
198
199
test('provider', async () => {
200
const registry = (Registry.as<IQuickAccessRegistry>(Extensions.Quickaccess));
201
const restore = (registry as QuickAccessRegistry).clear();
202
203
const disposables = new DisposableStore();
204
205
disposables.add(registry.registerQuickAccessProvider(providerDescriptorDefault));
206
disposables.add(registry.registerQuickAccessProvider(providerDescriptor1));
207
disposables.add(registry.registerQuickAccessProvider(providerDescriptor2));
208
disposables.add(registry.registerQuickAccessProvider(providerDescriptor3));
209
210
accessor.quickInputService.quickAccess.show('test');
211
assert.strictEqual(providerDefaultCalled, false);
212
assert.strictEqual(provider1Called, true);
213
assert.strictEqual(provider2Called, false);
214
assert.strictEqual(provider3Called, false);
215
assert.strictEqual(providerDefaultCanceled, false);
216
assert.strictEqual(provider1Canceled, false);
217
assert.strictEqual(provider2Canceled, false);
218
assert.strictEqual(provider3Canceled, false);
219
assert.strictEqual(providerDefaultDisposed, false);
220
assert.strictEqual(provider1Disposed, false);
221
assert.strictEqual(provider2Disposed, false);
222
assert.strictEqual(provider3Disposed, false);
223
provider1Called = false;
224
225
accessor.quickInputService.quickAccess.show('test something');
226
assert.strictEqual(providerDefaultCalled, false);
227
assert.strictEqual(provider1Called, false);
228
assert.strictEqual(provider2Called, true);
229
assert.strictEqual(provider3Called, false);
230
assert.strictEqual(providerDefaultCanceled, false);
231
assert.strictEqual(provider1Canceled, true);
232
assert.strictEqual(provider2Canceled, false);
233
assert.strictEqual(provider3Canceled, false);
234
assert.strictEqual(providerDefaultDisposed, false);
235
assert.strictEqual(provider1Disposed, true);
236
assert.strictEqual(provider2Disposed, false);
237
assert.strictEqual(provider3Disposed, false);
238
provider2Called = false;
239
provider1Canceled = false;
240
provider1Disposed = false;
241
242
accessor.quickInputService.quickAccess.show('usedefault');
243
assert.strictEqual(providerDefaultCalled, true);
244
assert.strictEqual(provider1Called, false);
245
assert.strictEqual(provider2Called, false);
246
assert.strictEqual(provider3Called, false);
247
assert.strictEqual(providerDefaultCanceled, false);
248
assert.strictEqual(provider1Canceled, false);
249
assert.strictEqual(provider2Canceled, true);
250
assert.strictEqual(provider3Canceled, false);
251
assert.strictEqual(providerDefaultDisposed, false);
252
assert.strictEqual(provider1Disposed, false);
253
assert.strictEqual(provider2Disposed, true);
254
assert.strictEqual(provider3Disposed, false);
255
256
await timeout(1);
257
258
assert.strictEqual(providerDefaultCanceled, true);
259
assert.strictEqual(providerDefaultDisposed, true);
260
assert.strictEqual(provider3Called, true);
261
262
await timeout(1);
263
264
assert.strictEqual(provider3Canceled, true);
265
assert.strictEqual(provider3Disposed, true);
266
267
disposables.dispose();
268
269
restore();
270
});
271
272
let fastProviderCalled = false;
273
let slowProviderCalled = false;
274
let fastAndSlowProviderCalled = false;
275
276
let slowProviderCanceled = false;
277
let fastAndSlowProviderCanceled = false;
278
279
class FastTestQuickPickProvider extends PickerQuickAccessProvider<IQuickPickItem> {
280
281
constructor() {
282
super('fast');
283
}
284
285
protected _getPicks(filter: string, disposables: DisposableStore, token: CancellationToken): Array<IQuickPickItem> {
286
fastProviderCalled = true;
287
288
return [{ label: 'Fast Pick' }];
289
}
290
}
291
292
class SlowTestQuickPickProvider extends PickerQuickAccessProvider<IQuickPickItem> {
293
294
constructor() {
295
super('slow');
296
}
297
298
protected async _getPicks(filter: string, disposables: DisposableStore, token: CancellationToken): Promise<Array<IQuickPickItem>> {
299
slowProviderCalled = true;
300
301
await timeout(1);
302
303
if (token.isCancellationRequested) {
304
slowProviderCanceled = true;
305
}
306
307
return [{ label: 'Slow Pick' }];
308
}
309
}
310
311
class FastAndSlowTestQuickPickProvider extends PickerQuickAccessProvider<IQuickPickItem> {
312
313
constructor() {
314
super('bothFastAndSlow');
315
}
316
317
protected _getPicks(filter: string, disposables: DisposableStore, token: CancellationToken): FastAndSlowPicks<IQuickPickItem> {
318
fastAndSlowProviderCalled = true;
319
320
return {
321
picks: [{ label: 'Fast Pick' }],
322
additionalPicks: (async () => {
323
await timeout(1);
324
325
if (token.isCancellationRequested) {
326
fastAndSlowProviderCanceled = true;
327
}
328
329
return [{ label: 'Slow Pick' }];
330
})()
331
};
332
}
333
}
334
335
const fastProviderDescriptor = { ctor: FastTestQuickPickProvider, prefix: 'fast', helpEntries: [] };
336
const slowProviderDescriptor = { ctor: SlowTestQuickPickProvider, prefix: 'slow', helpEntries: [] };
337
const fastAndSlowProviderDescriptor = { ctor: FastAndSlowTestQuickPickProvider, prefix: 'bothFastAndSlow', helpEntries: [] };
338
339
test('quick pick access - show()', async () => {
340
const registry = (Registry.as<IQuickAccessRegistry>(Extensions.Quickaccess));
341
const restore = (registry as QuickAccessRegistry).clear();
342
343
const disposables = new DisposableStore();
344
345
disposables.add(registry.registerQuickAccessProvider(fastProviderDescriptor));
346
disposables.add(registry.registerQuickAccessProvider(slowProviderDescriptor));
347
disposables.add(registry.registerQuickAccessProvider(fastAndSlowProviderDescriptor));
348
349
accessor.quickInputService.quickAccess.show('fast');
350
assert.strictEqual(fastProviderCalled, true);
351
assert.strictEqual(slowProviderCalled, false);
352
assert.strictEqual(fastAndSlowProviderCalled, false);
353
fastProviderCalled = false;
354
355
accessor.quickInputService.quickAccess.show('slow');
356
await timeout(2);
357
358
assert.strictEqual(fastProviderCalled, false);
359
assert.strictEqual(slowProviderCalled, true);
360
assert.strictEqual(slowProviderCanceled, false);
361
assert.strictEqual(fastAndSlowProviderCalled, false);
362
slowProviderCalled = false;
363
364
accessor.quickInputService.quickAccess.show('bothFastAndSlow');
365
await timeout(2);
366
367
assert.strictEqual(fastProviderCalled, false);
368
assert.strictEqual(slowProviderCalled, false);
369
assert.strictEqual(fastAndSlowProviderCalled, true);
370
assert.strictEqual(fastAndSlowProviderCanceled, false);
371
fastAndSlowProviderCalled = false;
372
373
accessor.quickInputService.quickAccess.show('slow');
374
accessor.quickInputService.quickAccess.show('bothFastAndSlow');
375
accessor.quickInputService.quickAccess.show('fast');
376
377
assert.strictEqual(fastProviderCalled, true);
378
assert.strictEqual(slowProviderCalled, true);
379
assert.strictEqual(fastAndSlowProviderCalled, true);
380
381
await timeout(2);
382
assert.strictEqual(slowProviderCanceled, true);
383
assert.strictEqual(fastAndSlowProviderCanceled, true);
384
385
disposables.dispose();
386
387
restore();
388
});
389
390
test('quick pick access - pick()', async () => {
391
const registry = (Registry.as<IQuickAccessRegistry>(Extensions.Quickaccess));
392
const restore = (registry as QuickAccessRegistry).clear();
393
394
const disposables = new DisposableStore();
395
396
disposables.add(registry.registerQuickAccessProvider(fastProviderDescriptor));
397
398
const result = accessor.quickInputService.quickAccess.pick('fast');
399
assert.strictEqual(fastProviderCalled, true);
400
assert.ok(result instanceof Promise);
401
402
disposables.dispose();
403
404
restore();
405
});
406
407
test('PickerEditorState can properly restore editors', async () => {
408
409
const part = await createEditorPart(instantiationService, disposables.add(new DisposableStore()));
410
instantiationService.stub(IEditorGroupsService, part);
411
412
const editorService = disposables.add(instantiationService.createInstance(EditorService, undefined));
413
instantiationService.stub(IEditorService, editorService);
414
415
const editorViewState = disposables.add(instantiationService.createInstance(PickerEditorState));
416
disposables.add(part);
417
disposables.add(editorService);
418
419
const input1 = {
420
resource: URI.parse('foo://bar1'),
421
options: {
422
pinned: true, preserveFocus: true, selection: new Range(1, 0, 1, 3)
423
}
424
};
425
const input2 = {
426
resource: URI.parse('foo://bar2'),
427
options: {
428
pinned: true, selection: new Range(1, 0, 1, 3)
429
}
430
};
431
const input3 = {
432
resource: URI.parse('foo://bar3')
433
};
434
const input4 = {
435
resource: URI.parse('foo://bar4')
436
};
437
438
const editor = await editorService.openEditor(input1);
439
assert.strictEqual(editor, editorService.activeEditorPane);
440
editorViewState.set();
441
await editorService.openEditor(input2);
442
await editorViewState.openTransientEditor(input3);
443
await editorViewState.openTransientEditor(input4);
444
await editorViewState.restore();
445
446
assert.strictEqual(part.activeGroup.activeEditor?.resource, input1.resource);
447
assert.deepStrictEqual(part.activeGroup.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE).map(e => e.resource), [input1.resource, input2.resource]);
448
if (part.activeGroup.activeEditorPane?.getSelection) {
449
assert.deepStrictEqual(part.activeGroup.activeEditorPane?.getSelection(), input1.options.selection);
450
}
451
await part.activeGroup.closeAllEditors();
452
});
453
});
454
455