Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts
3296 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 { generateUuid } from '../../../../../base/common/uuid.js';
8
import { ExtensionsListView } from '../../browser/extensionsViews.js';
9
import { TestInstantiationService } from '../../../../../platform/instantiation/test/common/instantiationServiceMock.js';
10
import { IExtensionsWorkbenchService } from '../../common/extensions.js';
11
import { ExtensionsWorkbenchService } from '../../browser/extensionsWorkbenchService.js';
12
import {
13
IExtensionManagementService, IExtensionGalleryService, ILocalExtension, IGalleryExtension, IQueryOptions,
14
getTargetPlatform, SortBy
15
} from '../../../../../platform/extensionManagement/common/extensionManagement.js';
16
import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer, IProfileAwareExtensionManagementService, IWorkbenchExtensionManagementService } from '../../../../services/extensionManagement/common/extensionManagement.js';
17
import { IExtensionRecommendationsService, ExtensionRecommendationReason } from '../../../../services/extensionRecommendations/common/extensionRecommendations.js';
18
import { getGalleryExtensionId } from '../../../../../platform/extensionManagement/common/extensionManagementUtil.js';
19
import { TestExtensionEnablementService } from '../../../../services/extensionManagement/test/browser/extensionEnablementService.test.js';
20
import { ExtensionGalleryService } from '../../../../../platform/extensionManagement/common/extensionGalleryService.js';
21
import { IURLService } from '../../../../../platform/url/common/url.js';
22
import { Event } from '../../../../../base/common/event.js';
23
import { IPager } from '../../../../../base/common/paging.js';
24
import { ITelemetryService } from '../../../../../platform/telemetry/common/telemetry.js';
25
import { NullTelemetryService } from '../../../../../platform/telemetry/common/telemetryUtils.js';
26
import { IExtensionService, toExtensionDescription } from '../../../../services/extensions/common/extensions.js';
27
import { IWorkspaceContextService } from '../../../../../platform/workspace/common/workspace.js';
28
import { TestMenuService } from '../../../../test/browser/workbenchTestServices.js';
29
import { TestSharedProcessService } from '../../../../test/electron-browser/workbenchTestServices.js';
30
import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js';
31
import { ILogService, NullLogService } from '../../../../../platform/log/common/log.js';
32
import { NativeURLService } from '../../../../../platform/url/common/urlService.js';
33
import { URI } from '../../../../../base/common/uri.js';
34
import { TestConfigurationService } from '../../../../../platform/configuration/test/common/testConfigurationService.js';
35
import { SinonStub } from 'sinon';
36
import { IRemoteAgentService } from '../../../../services/remote/common/remoteAgentService.js';
37
import { RemoteAgentService } from '../../../../services/remote/electron-browser/remoteAgentService.js';
38
import { ExtensionType, IExtension } from '../../../../../platform/extensions/common/extensions.js';
39
import { ISharedProcessService } from '../../../../../platform/ipc/electron-browser/services.js';
40
import { IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js';
41
import { MockContextKeyService } from '../../../../../platform/keybinding/test/common/mockKeybindingService.js';
42
import { IMenuService } from '../../../../../platform/actions/common/actions.js';
43
import { TestContextService } from '../../../../test/common/workbenchTestServices.js';
44
import { IViewDescriptorService, ViewContainerLocation } from '../../../../common/views.js';
45
import { Schemas } from '../../../../../base/common/network.js';
46
import { platform } from '../../../../../base/common/platform.js';
47
import { arch } from '../../../../../base/common/process.js';
48
import { IProductService } from '../../../../../platform/product/common/productService.js';
49
import { CancellationToken } from '../../../../../base/common/cancellation.js';
50
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';
51
import { IUpdateService, State } from '../../../../../platform/update/common/update.js';
52
import { IFileService } from '../../../../../platform/files/common/files.js';
53
import { FileService } from '../../../../../platform/files/common/fileService.js';
54
import { IUserDataProfileService } from '../../../../services/userDataProfile/common/userDataProfile.js';
55
import { UserDataProfileService } from '../../../../services/userDataProfile/common/userDataProfileService.js';
56
import { toUserDataProfile } from '../../../../../platform/userDataProfile/common/userDataProfile.js';
57
58
suite('ExtensionsViews Tests', () => {
59
60
const disposableStore = ensureNoDisposablesAreLeakedInTestSuite();
61
62
let instantiationService: TestInstantiationService;
63
let testableView: ExtensionsListView;
64
65
const localEnabledTheme = aLocalExtension('first-enabled-extension', { categories: ['Themes', 'random'] }, { installedTimestamp: 123456 });
66
const localEnabledLanguage = aLocalExtension('second-enabled-extension', { categories: ['Programming languages'], version: '1.0.0' }, { installedTimestamp: Date.now(), updated: false });
67
const localDisabledTheme = aLocalExtension('first-disabled-extension', { categories: ['themes'] }, { installedTimestamp: 234567 });
68
const localDisabledLanguage = aLocalExtension('second-disabled-extension', { categories: ['programming languages'] }, { installedTimestamp: Date.now() - 50000, updated: true });
69
const localRandom = aLocalExtension('random-enabled-extension', { categories: ['random'] }, { installedTimestamp: 345678 });
70
const builtInTheme = aLocalExtension('my-theme', { categories: ['Themes'], contributes: { themes: ['my-theme'] } }, { type: ExtensionType.System, installedTimestamp: 222 });
71
const builtInBasic = aLocalExtension('my-lang', { categories: ['Programming Languages'], contributes: { grammars: [{ language: 'my-language' }] } }, { type: ExtensionType.System, installedTimestamp: 666666 });
72
73
let queryPage = aPage([]);
74
const galleryExtensions: IGalleryExtension[] = [];
75
76
const workspaceRecommendationA = aGalleryExtension('workspace-recommendation-A');
77
const workspaceRecommendationB = aGalleryExtension('workspace-recommendation-B');
78
const configBasedRecommendationA = aGalleryExtension('configbased-recommendation-A');
79
const configBasedRecommendationB = aGalleryExtension('configbased-recommendation-B');
80
const fileBasedRecommendationA = aGalleryExtension('filebased-recommendation-A');
81
const fileBasedRecommendationB = aGalleryExtension('filebased-recommendation-B');
82
const otherRecommendationA = aGalleryExtension('other-recommendation-A');
83
84
setup(async () => {
85
instantiationService = disposableStore.add(new TestInstantiationService());
86
instantiationService.stub(ITelemetryService, NullTelemetryService);
87
instantiationService.stub(ILogService, NullLogService);
88
instantiationService.stub(IFileService, disposableStore.add(new FileService(new NullLogService())));
89
instantiationService.stub(IProductService, {});
90
91
instantiationService.stub(IWorkspaceContextService, new TestContextService());
92
instantiationService.stub(IConfigurationService, new TestConfigurationService());
93
94
instantiationService.stub(IExtensionGalleryService, ExtensionGalleryService);
95
instantiationService.stub(ISharedProcessService, TestSharedProcessService);
96
97
instantiationService.stub(IWorkbenchExtensionManagementService, {
98
onInstallExtension: Event.None,
99
onDidInstallExtensions: Event.None,
100
onUninstallExtension: Event.None,
101
onDidUninstallExtension: Event.None,
102
onDidUpdateExtensionMetadata: Event.None,
103
onDidChangeProfile: Event.None,
104
onProfileAwareDidInstallExtensions: Event.None,
105
async getInstalled() { return []; },
106
async getInstalledWorkspaceExtensions() { return []; },
107
async canInstall() { return true; },
108
async getExtensionsControlManifest() { return { malicious: [], deprecated: {}, search: [], publisherMapping: {} }; },
109
async getTargetPlatform() { return getTargetPlatform(platform, arch); },
110
async updateMetadata(local) { return local; }
111
});
112
instantiationService.stub(IRemoteAgentService, RemoteAgentService);
113
instantiationService.stub(IContextKeyService, new MockContextKeyService());
114
instantiationService.stub(IMenuService, new TestMenuService());
115
116
const localExtensionManagementServer = { extensionManagementService: instantiationService.get(IExtensionManagementService) as IProfileAwareExtensionManagementService, label: 'local', id: 'vscode-local' };
117
instantiationService.stub(IExtensionManagementServerService, {
118
get localExtensionManagementServer(): IExtensionManagementServer {
119
return localExtensionManagementServer;
120
},
121
getExtensionManagementServer(extension: IExtension): IExtensionManagementServer | null {
122
if (extension.location.scheme === Schemas.file) {
123
return localExtensionManagementServer;
124
}
125
throw new Error(`Invalid Extension ${extension.location}`);
126
}
127
});
128
129
instantiationService.stub(IWorkbenchExtensionEnablementService, disposableStore.add(new TestExtensionEnablementService(instantiationService)));
130
instantiationService.stub(IUserDataProfileService, disposableStore.add(new UserDataProfileService(toUserDataProfile('test', 'test', URI.file('foo'), URI.file('cache')))));
131
132
const reasons: { [key: string]: any } = {};
133
reasons[workspaceRecommendationA.identifier.id] = { reasonId: ExtensionRecommendationReason.Workspace };
134
reasons[workspaceRecommendationB.identifier.id] = { reasonId: ExtensionRecommendationReason.Workspace };
135
reasons[fileBasedRecommendationA.identifier.id] = { reasonId: ExtensionRecommendationReason.File };
136
reasons[fileBasedRecommendationB.identifier.id] = { reasonId: ExtensionRecommendationReason.File };
137
reasons[otherRecommendationA.identifier.id] = { reasonId: ExtensionRecommendationReason.Executable };
138
reasons[configBasedRecommendationA.identifier.id] = { reasonId: ExtensionRecommendationReason.WorkspaceConfig };
139
instantiationService.stub(IExtensionRecommendationsService, {
140
getWorkspaceRecommendations() {
141
return Promise.resolve([
142
workspaceRecommendationA.identifier.id,
143
workspaceRecommendationB.identifier.id]);
144
},
145
getConfigBasedRecommendations() {
146
return Promise.resolve({
147
important: [configBasedRecommendationA.identifier.id],
148
others: [configBasedRecommendationB.identifier.id],
149
});
150
},
151
getImportantRecommendations(): Promise<string[]> {
152
return Promise.resolve([]);
153
},
154
getFileBasedRecommendations() {
155
return [
156
fileBasedRecommendationA.identifier.id,
157
fileBasedRecommendationB.identifier.id
158
];
159
},
160
getOtherRecommendations() {
161
return Promise.resolve([
162
configBasedRecommendationB.identifier.id,
163
otherRecommendationA.identifier.id
164
]);
165
},
166
getAllRecommendationsWithReason() {
167
return reasons;
168
}
169
});
170
instantiationService.stub(IURLService, NativeURLService);
171
172
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [localEnabledTheme, localEnabledLanguage, localRandom, localDisabledTheme, localDisabledLanguage, builtInTheme, builtInBasic]);
173
instantiationService.stubPromise(IExtensionManagementService, 'getExtensgetExtensionsControlManifestionsReport', {});
174
175
instantiationService.stub(IExtensionGalleryService, <Partial<IExtensionGalleryService>>{
176
query: async () => {
177
return queryPage;
178
},
179
getCompatibleExtension: async (gallery) => {
180
return gallery;
181
},
182
getExtensions: async (infos) => {
183
const result: IGalleryExtension[] = [];
184
for (const info of infos) {
185
const extension = galleryExtensions.find(e => e.identifier.id === info.id);
186
if (extension) {
187
result.push(extension);
188
}
189
}
190
return result;
191
},
192
isEnabled: () => true,
193
isExtensionCompatible: async () => true,
194
});
195
196
instantiationService.stub(IViewDescriptorService, {
197
getViewLocationById(): ViewContainerLocation {
198
return ViewContainerLocation.Sidebar;
199
},
200
onDidChangeLocation: Event.None
201
});
202
203
instantiationService.stub(IExtensionService, {
204
onDidChangeExtensions: Event.None,
205
extensions: [
206
toExtensionDescription(localEnabledTheme),
207
toExtensionDescription(localEnabledLanguage),
208
toExtensionDescription(localRandom),
209
toExtensionDescription(builtInTheme),
210
toExtensionDescription(builtInBasic)
211
],
212
canAddExtension: (extension) => true,
213
whenInstalledExtensionsRegistered: () => Promise.resolve(true)
214
});
215
await (<TestExtensionEnablementService>instantiationService.get(IWorkbenchExtensionEnablementService)).setEnablement([localDisabledTheme], EnablementState.DisabledGlobally);
216
await (<TestExtensionEnablementService>instantiationService.get(IWorkbenchExtensionEnablementService)).setEnablement([localDisabledLanguage], EnablementState.DisabledGlobally);
217
218
instantiationService.stub(IUpdateService, { onStateChange: Event.None, state: State.Uninitialized });
219
instantiationService.set(IExtensionsWorkbenchService, disposableStore.add(instantiationService.createInstance(ExtensionsWorkbenchService)));
220
testableView = disposableStore.add(instantiationService.createInstance(ExtensionsListView, {}, { id: '', title: '' }));
221
queryPage = aPage([]);
222
223
galleryExtensions.splice(0, galleryExtensions.length, ...[
224
workspaceRecommendationA,
225
workspaceRecommendationB,
226
configBasedRecommendationA,
227
configBasedRecommendationB,
228
fileBasedRecommendationA,
229
fileBasedRecommendationB,
230
otherRecommendationA
231
]);
232
});
233
234
test('Test query types', () => {
235
assert.strictEqual(ExtensionsListView.isBuiltInExtensionsQuery('@builtin'), true);
236
assert.strictEqual(ExtensionsListView.isLocalExtensionsQuery('@installed'), true);
237
assert.strictEqual(ExtensionsListView.isLocalExtensionsQuery('@enabled'), true);
238
assert.strictEqual(ExtensionsListView.isLocalExtensionsQuery('@disabled'), true);
239
assert.strictEqual(ExtensionsListView.isLocalExtensionsQuery('@outdated'), true);
240
assert.strictEqual(ExtensionsListView.isLocalExtensionsQuery('@updates'), true);
241
assert.strictEqual(ExtensionsListView.isLocalExtensionsQuery('@sort:name'), true);
242
assert.strictEqual(ExtensionsListView.isLocalExtensionsQuery('@sort:updateDate'), true);
243
assert.strictEqual(ExtensionsListView.isLocalExtensionsQuery('@installed searchText'), true);
244
assert.strictEqual(ExtensionsListView.isLocalExtensionsQuery('@enabled searchText'), true);
245
assert.strictEqual(ExtensionsListView.isLocalExtensionsQuery('@disabled searchText'), true);
246
assert.strictEqual(ExtensionsListView.isLocalExtensionsQuery('@outdated searchText'), true);
247
assert.strictEqual(ExtensionsListView.isLocalExtensionsQuery('@updates searchText'), true);
248
});
249
250
test('Test empty query equates to sort by install count', async () => {
251
const target = <SinonStub>instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage());
252
await testableView.show('');
253
assert.ok(target.calledOnce);
254
const options: IQueryOptions = target.args[0][0];
255
assert.strictEqual(options.sortBy, SortBy.InstallCount);
256
});
257
258
test('Test non empty query without sort doesnt use sortBy', async () => {
259
const target = <SinonStub>instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage());
260
await testableView.show('some extension');
261
assert.ok(target.calledOnce);
262
const options: IQueryOptions = target.args[0][0];
263
assert.strictEqual(options.sortBy, undefined);
264
});
265
266
test('Test query with sort uses sortBy', async () => {
267
const target = <SinonStub>instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage());
268
await testableView.show('some extension @sort:rating');
269
assert.ok(target.calledOnce);
270
const options: IQueryOptions = target.args[0][0];
271
assert.strictEqual(options.sortBy, SortBy.WeightedRating);
272
});
273
274
test('Test default view actions required sorting', async () => {
275
queryPage = aPage([aGalleryExtension(localEnabledLanguage.manifest.name, { ...localEnabledLanguage.manifest, version: '1.0.1', identifier: localDisabledLanguage.identifier })]);
276
277
const workbenchService = instantiationService.get(IExtensionsWorkbenchService);
278
const extension = (await workbenchService.queryLocal()).find(ex => ex.identifier.id === localEnabledLanguage.identifier.id);
279
280
await new Promise<void>(c => {
281
const disposable = workbenchService.onChange(() => {
282
if (extension?.outdated) {
283
disposable.dispose();
284
c();
285
}
286
});
287
instantiationService.get(IExtensionsWorkbenchService).queryGallery(CancellationToken.None);
288
});
289
290
const result = await testableView.show('@installed');
291
assert.strictEqual(result.length, 5, 'Unexpected number of results for @installed query');
292
const actual = [result.get(0).name, result.get(1).name, result.get(2).name, result.get(3).name, result.get(4).name];
293
const expected = [localEnabledLanguage.manifest.name, localEnabledTheme.manifest.name, localRandom.manifest.name, localDisabledTheme.manifest.name, localDisabledLanguage.manifest.name];
294
for (let i = 0; i < result.length; i++) {
295
assert.strictEqual(actual[i], expected[i], 'Unexpected extension for @installed query with outadted extension.');
296
}
297
});
298
299
test('Test installed query results', async () => {
300
await testableView.show('@installed').then(result => {
301
assert.strictEqual(result.length, 5, 'Unexpected number of results for @installed query');
302
const actual = [result.get(0).name, result.get(1).name, result.get(2).name, result.get(3).name, result.get(4).name].sort();
303
const expected = [localDisabledTheme.manifest.name, localEnabledTheme.manifest.name, localRandom.manifest.name, localDisabledLanguage.manifest.name, localEnabledLanguage.manifest.name];
304
for (let i = 0; i < result.length; i++) {
305
assert.strictEqual(actual[i], expected[i], 'Unexpected extension for @installed query.');
306
}
307
});
308
309
await testableView.show('@installed first').then(result => {
310
assert.strictEqual(result.length, 2, 'Unexpected number of results for @installed query');
311
assert.strictEqual(result.get(0).name, localEnabledTheme.manifest.name, 'Unexpected extension for @installed query with search text.');
312
assert.strictEqual(result.get(1).name, localDisabledTheme.manifest.name, 'Unexpected extension for @installed query with search text.');
313
});
314
315
await testableView.show('@disabled').then(result => {
316
assert.strictEqual(result.length, 2, 'Unexpected number of results for @disabled query');
317
assert.strictEqual(result.get(0).name, localDisabledTheme.manifest.name, 'Unexpected extension for @disabled query.');
318
assert.strictEqual(result.get(1).name, localDisabledLanguage.manifest.name, 'Unexpected extension for @disabled query.');
319
});
320
321
await testableView.show('@enabled').then(result => {
322
assert.strictEqual(result.length, 3, 'Unexpected number of results for @enabled query');
323
assert.strictEqual(result.get(0).name, localEnabledTheme.manifest.name, 'Unexpected extension for @enabled query.');
324
assert.strictEqual(result.get(1).name, localRandom.manifest.name, 'Unexpected extension for @enabled query.');
325
assert.strictEqual(result.get(2).name, localEnabledLanguage.manifest.name, 'Unexpected extension for @enabled query.');
326
});
327
328
await testableView.show('@builtin category:themes').then(result => {
329
assert.strictEqual(result.length, 1, 'Unexpected number of results for @builtin category:themes query');
330
assert.strictEqual(result.get(0).name, builtInTheme.manifest.name, 'Unexpected extension for @builtin:themes query.');
331
});
332
333
await testableView.show('@builtin category:"programming languages"').then(result => {
334
assert.strictEqual(result.length, 1, 'Unexpected number of results for @builtin:basics query');
335
assert.strictEqual(result.get(0).name, builtInBasic.manifest.name, 'Unexpected extension for @builtin:basics query.');
336
});
337
338
await testableView.show('@builtin').then(result => {
339
assert.strictEqual(result.length, 2, 'Unexpected number of results for @builtin query');
340
assert.strictEqual(result.get(0).name, builtInBasic.manifest.name, 'Unexpected extension for @builtin query.');
341
assert.strictEqual(result.get(1).name, builtInTheme.manifest.name, 'Unexpected extension for @builtin query.');
342
});
343
344
await testableView.show('@builtin my-theme').then(result => {
345
assert.strictEqual(result.length, 1, 'Unexpected number of results for @builtin query');
346
assert.strictEqual(result.get(0).name, builtInTheme.manifest.name, 'Unexpected extension for @builtin query.');
347
});
348
});
349
350
test('Test installed query with category', async () => {
351
await testableView.show('@installed category:themes').then(result => {
352
assert.strictEqual(result.length, 2, 'Unexpected number of results for @installed query with category');
353
assert.strictEqual(result.get(0).name, localEnabledTheme.manifest.name, 'Unexpected extension for @installed query with category.');
354
assert.strictEqual(result.get(1).name, localDisabledTheme.manifest.name, 'Unexpected extension for @installed query with category.');
355
});
356
357
await testableView.show('@installed category:"themes"').then(result => {
358
assert.strictEqual(result.length, 2, 'Unexpected number of results for @installed query with quoted category');
359
assert.strictEqual(result.get(0).name, localEnabledTheme.manifest.name, 'Unexpected extension for @installed query with quoted category.');
360
assert.strictEqual(result.get(1).name, localDisabledTheme.manifest.name, 'Unexpected extension for @installed query with quoted category.');
361
});
362
363
await testableView.show('@installed category:"programming languages"').then(result => {
364
assert.strictEqual(result.length, 2, 'Unexpected number of results for @installed query with quoted category including space');
365
assert.strictEqual(result.get(0).name, localEnabledLanguage.manifest.name, 'Unexpected extension for @installed query with quoted category including space.');
366
assert.strictEqual(result.get(1).name, localDisabledLanguage.manifest.name, 'Unexpected extension for @installed query with quoted category inlcuding space.');
367
});
368
369
await testableView.show('@installed category:themes category:random').then(result => {
370
assert.strictEqual(result.length, 3, 'Unexpected number of results for @installed query with multiple category');
371
assert.strictEqual(result.get(0).name, localEnabledTheme.manifest.name, 'Unexpected extension for @installed query with multiple category.');
372
assert.strictEqual(result.get(1).name, localRandom.manifest.name, 'Unexpected extension for @installed query with multiple category.');
373
assert.strictEqual(result.get(2).name, localDisabledTheme.manifest.name, 'Unexpected extension for @installed query with multiple category.');
374
});
375
376
await testableView.show('@enabled category:themes').then(result => {
377
assert.strictEqual(result.length, 1, 'Unexpected number of results for @enabled query with category');
378
assert.strictEqual(result.get(0).name, localEnabledTheme.manifest.name, 'Unexpected extension for @enabled query with category.');
379
});
380
381
await testableView.show('@enabled category:"themes"').then(result => {
382
assert.strictEqual(result.length, 1, 'Unexpected number of results for @enabled query with quoted category');
383
assert.strictEqual(result.get(0).name, localEnabledTheme.manifest.name, 'Unexpected extension for @enabled query with quoted category.');
384
});
385
386
await testableView.show('@enabled category:"programming languages"').then(result => {
387
assert.strictEqual(result.length, 1, 'Unexpected number of results for @enabled query with quoted category inlcuding space');
388
assert.strictEqual(result.get(0).name, localEnabledLanguage.manifest.name, 'Unexpected extension for @enabled query with quoted category including space.');
389
});
390
391
await testableView.show('@disabled category:themes').then(result => {
392
assert.strictEqual(result.length, 1, 'Unexpected number of results for @disabled query with category');
393
assert.strictEqual(result.get(0).name, localDisabledTheme.manifest.name, 'Unexpected extension for @disabled query with category.');
394
});
395
396
await testableView.show('@disabled category:"themes"').then(result => {
397
assert.strictEqual(result.length, 1, 'Unexpected number of results for @disabled query with quoted category');
398
assert.strictEqual(result.get(0).name, localDisabledTheme.manifest.name, 'Unexpected extension for @disabled query with quoted category.');
399
});
400
401
await testableView.show('@disabled category:"programming languages"').then(result => {
402
assert.strictEqual(result.length, 1, 'Unexpected number of results for @disabled query with quoted category inlcuding space');
403
assert.strictEqual(result.get(0).name, localDisabledLanguage.manifest.name, 'Unexpected extension for @disabled query with quoted category including space.');
404
});
405
});
406
407
test('Test local query with sorting order', async () => {
408
await testableView.show('@recentlyUpdated').then(result => {
409
assert.strictEqual(result.length, 1, 'Unexpected number of results for @recentlyUpdated');
410
assert.strictEqual(result.get(0).name, localDisabledLanguage.manifest.name, 'Unexpected default sort order of extensions for @recentlyUpdate query');
411
});
412
413
await testableView.show('@installed @sort:updateDate').then(result => {
414
assert.strictEqual(result.length, 5, 'Unexpected number of results for @sort:updateDate. Expected all localy installed Extension which are not builtin');
415
const actual = [result.get(0).local?.installedTimestamp, result.get(1).local?.installedTimestamp, result.get(2).local?.installedTimestamp, result.get(3).local?.installedTimestamp, result.get(4).local?.installedTimestamp];
416
const expected = [localEnabledLanguage.installedTimestamp, localDisabledLanguage.installedTimestamp, localRandom.installedTimestamp, localDisabledTheme.installedTimestamp, localEnabledTheme.installedTimestamp];
417
for (let i = 0; i < result.length; i++) {
418
assert.strictEqual(actual[i], expected[i], 'Unexpected extension sorting for @sort:updateDate query.');
419
}
420
});
421
});
422
423
test('Test @recommended:workspace query', () => {
424
const workspaceRecommendedExtensions = [
425
workspaceRecommendationA,
426
workspaceRecommendationB,
427
configBasedRecommendationA,
428
];
429
430
return testableView.show('@recommended:workspace').then(result => {
431
assert.strictEqual(result.length, workspaceRecommendedExtensions.length);
432
for (let i = 0; i < workspaceRecommendedExtensions.length; i++) {
433
assert.strictEqual(result.get(i).identifier.id, workspaceRecommendedExtensions[i].identifier.id);
434
}
435
});
436
});
437
438
test('Test @recommended query', async () => {
439
const allRecommendedExtensions = [
440
fileBasedRecommendationA,
441
fileBasedRecommendationB,
442
configBasedRecommendationB,
443
otherRecommendationA
444
];
445
446
const result = await testableView.show('@recommended');
447
assert.strictEqual(result.length, allRecommendedExtensions.length);
448
for (let i = 0; i < allRecommendedExtensions.length; i++) {
449
assert.strictEqual(result.get(i).identifier.id, allRecommendedExtensions[i].identifier.id);
450
}
451
});
452
453
454
test('Test @recommended:all query', async () => {
455
const allRecommendedExtensions = [
456
workspaceRecommendationA,
457
workspaceRecommendationB,
458
configBasedRecommendationA,
459
fileBasedRecommendationA,
460
fileBasedRecommendationB,
461
configBasedRecommendationB,
462
otherRecommendationA,
463
];
464
465
const result = await testableView.show('@recommended:all');
466
assert.strictEqual(result.length, allRecommendedExtensions.length);
467
for (let i = 0; i < allRecommendedExtensions.length; i++) {
468
assert.strictEqual(result.get(i).identifier.id, allRecommendedExtensions[i].identifier.id);
469
}
470
});
471
472
test('Test search', async () => {
473
const results = [
474
fileBasedRecommendationA,
475
workspaceRecommendationA,
476
otherRecommendationA,
477
workspaceRecommendationB
478
];
479
queryPage = aPage(results);
480
const result = await testableView.show('search-me');
481
assert.strictEqual(result.length, results.length);
482
for (let i = 0; i < results.length; i++) {
483
assert.strictEqual(result.get(i).identifier.id, results[i].identifier.id);
484
}
485
});
486
487
test('Test preferred search experiment', async () => {
488
queryPage = aPage([
489
fileBasedRecommendationA,
490
workspaceRecommendationA,
491
otherRecommendationA,
492
workspaceRecommendationB
493
], 5);
494
const notInFirstPage = aGalleryExtension('not-in-first-page');
495
galleryExtensions.push(notInFirstPage);
496
const expected = [
497
workspaceRecommendationA,
498
notInFirstPage,
499
workspaceRecommendationB,
500
fileBasedRecommendationA,
501
otherRecommendationA,
502
];
503
504
instantiationService.stubPromise(IWorkbenchExtensionManagementService, 'getExtensionsControlManifest', {
505
malicious: [], deprecated: {},
506
search: [{
507
query: 'search-me',
508
preferredResults: [
509
workspaceRecommendationA.identifier.id,
510
notInFirstPage.identifier.id,
511
workspaceRecommendationB.identifier.id
512
]
513
}]
514
});
515
516
const testObject = disposableStore.add(instantiationService.createInstance(ExtensionsListView, {}, { id: '', title: '' }));
517
const result = await testObject.show('search-me');
518
assert.strictEqual(result.length, expected.length);
519
for (let i = 0; i < expected.length; i++) {
520
assert.strictEqual(result.get(i).identifier.id, expected[i].identifier.id);
521
}
522
});
523
524
test('Skip preferred search experiment when user defines sort order', async () => {
525
const realResults = [
526
fileBasedRecommendationA,
527
workspaceRecommendationA,
528
otherRecommendationA,
529
workspaceRecommendationB
530
];
531
queryPage = aPage(realResults);
532
533
const result = await testableView.show('search-me @sort:installs');
534
assert.strictEqual(result.length, realResults.length);
535
for (let i = 0; i < realResults.length; i++) {
536
assert.strictEqual(result.get(i).identifier.id, realResults[i].identifier.id);
537
}
538
});
539
540
function aLocalExtension(name: string = 'someext', manifest: any = {}, properties: any = {}): ILocalExtension {
541
manifest = { name, publisher: 'pub', version: '1.0.0', ...manifest };
542
properties = {
543
type: ExtensionType.User,
544
location: URI.file(`pub.${name}`),
545
identifier: { id: getGalleryExtensionId(manifest.publisher, manifest.name) },
546
metadata: { id: getGalleryExtensionId(manifest.publisher, manifest.name), publisherId: manifest.publisher, publisherDisplayName: 'somename' },
547
...properties,
548
isValid: properties.isValid ?? true,
549
};
550
properties.isBuiltin = properties.type === ExtensionType.System;
551
return <ILocalExtension>Object.create({ manifest, ...properties });
552
}
553
554
function aGalleryExtension(name: string, properties: any = {}, galleryExtensionProperties: any = {}, assets: any = {}): IGalleryExtension {
555
const targetPlatform = getTargetPlatform(platform, arch);
556
const galleryExtension = <IGalleryExtension>Object.create({ name, publisher: 'pub', version: '1.0.0', allTargetPlatforms: [targetPlatform], properties: {}, assets: {}, ...properties });
557
galleryExtension.properties = { ...galleryExtension.properties, dependencies: [], targetPlatform, ...galleryExtensionProperties };
558
galleryExtension.assets = { ...galleryExtension.assets, ...assets };
559
galleryExtension.identifier = { id: getGalleryExtensionId(galleryExtension.publisher, galleryExtension.name), uuid: generateUuid() };
560
return <IGalleryExtension>galleryExtension;
561
}
562
563
function aPage<T>(objects: IGalleryExtension[] = [], total?: number): IPager<IGalleryExtension> {
564
return { firstPage: objects, total: total ?? objects.length, pageSize: objects.length, getPage: () => null! };
565
}
566
567
});
568
569