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