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/extensionRecommendationsService.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 * as sinon from 'sinon';
7
import assert from 'assert';
8
import * as uuid from '../../../../../base/common/uuid.js';
9
import {
10
IExtensionGalleryService, IGalleryExtensionAssets, IGalleryExtension, IExtensionManagementService, IExtensionTipsService, getTargetPlatform,
11
} from '../../../../../platform/extensionManagement/common/extensionManagement.js';
12
import { IWorkbenchExtensionEnablementService, IWorkbenchExtensionManagementService } from '../../../../services/extensionManagement/common/extensionManagement.js';
13
import { ExtensionGalleryService } from '../../../../../platform/extensionManagement/common/extensionGalleryService.js';
14
import { TestInstantiationService } from '../../../../../platform/instantiation/test/common/instantiationServiceMock.js';
15
import { Emitter, Event } from '../../../../../base/common/event.js';
16
import { ITelemetryService } from '../../../../../platform/telemetry/common/telemetry.js';
17
import { NullTelemetryService } from '../../../../../platform/telemetry/common/telemetryUtils.js';
18
import { IWorkspaceContextService } from '../../../../../platform/workspace/common/workspace.js';
19
import { TestLifecycleService } from '../../../../test/browser/workbenchTestServices.js';
20
import { TestContextService, TestProductService, TestStorageService } from '../../../../test/common/workbenchTestServices.js';
21
import { TestExtensionTipsService, TestSharedProcessService } from '../../../../test/electron-browser/workbenchTestServices.js';
22
import { TestNotificationService } from '../../../../../platform/notification/test/common/testNotificationService.js';
23
import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js';
24
import { URI } from '../../../../../base/common/uri.js';
25
import { testWorkspace } from '../../../../../platform/workspace/test/common/testWorkspace.js';
26
import { TestConfigurationService } from '../../../../../platform/configuration/test/common/testConfigurationService.js';
27
import { IPager } from '../../../../../base/common/paging.js';
28
import { getGalleryExtensionId } from '../../../../../platform/extensionManagement/common/extensionManagementUtil.js';
29
import { IEnvironmentService } from '../../../../../platform/environment/common/environment.js';
30
import { ConfigurationKey, IExtensionsWorkbenchService } from '../../common/extensions.js';
31
import { TestExtensionEnablementService } from '../../../../services/extensionManagement/test/browser/extensionEnablementService.test.js';
32
import { IURLService } from '../../../../../platform/url/common/url.js';
33
import { ITextModel } from '../../../../../editor/common/model.js';
34
import { IModelService } from '../../../../../editor/common/services/model.js';
35
import { ILifecycleService } from '../../../../services/lifecycle/common/lifecycle.js';
36
import { INotificationService, Severity, IPromptChoice, IPromptOptions } from '../../../../../platform/notification/common/notification.js';
37
import { NativeURLService } from '../../../../../platform/url/common/urlService.js';
38
import { IStorageService, StorageScope, StorageTarget } from '../../../../../platform/storage/common/storage.js';
39
import { ExtensionType } from '../../../../../platform/extensions/common/extensions.js';
40
import { ISharedProcessService } from '../../../../../platform/ipc/electron-browser/services.js';
41
import { FileService } from '../../../../../platform/files/common/fileService.js';
42
import { NullLogService, ILogService } from '../../../../../platform/log/common/log.js';
43
import { IFileService } from '../../../../../platform/files/common/files.js';
44
import { IProductService } from '../../../../../platform/product/common/productService.js';
45
import { ExtensionRecommendationsService } from '../../browser/extensionRecommendationsService.js';
46
import { NoOpWorkspaceTagsService } from '../../../tags/browser/workspaceTagsService.js';
47
import { IWorkspaceTagsService } from '../../../tags/common/workspaceTags.js';
48
import { ExtensionsWorkbenchService } from '../../browser/extensionsWorkbenchService.js';
49
import { IExtensionService } from '../../../../services/extensions/common/extensions.js';
50
import { IWorkspaceExtensionsConfigService, WorkspaceExtensionsConfigService } from '../../../../services/extensionRecommendations/common/workspaceExtensionsConfig.js';
51
import { IExtensionIgnoredRecommendationsService } from '../../../../services/extensionRecommendations/common/extensionRecommendations.js';
52
import { ExtensionIgnoredRecommendationsService } from '../../../../services/extensionRecommendations/common/extensionIgnoredRecommendationsService.js';
53
import { IExtensionRecommendationNotificationService } from '../../../../../platform/extensionRecommendations/common/extensionRecommendations.js';
54
import { ExtensionRecommendationNotificationService } from '../../browser/extensionRecommendationNotificationService.js';
55
import { IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js';
56
import { MockContextKeyService } from '../../../../../platform/keybinding/test/common/mockKeybindingService.js';
57
import { InMemoryFileSystemProvider } from '../../../../../platform/files/common/inMemoryFilesystemProvider.js';
58
import { joinPath } from '../../../../../base/common/resources.js';
59
import { VSBuffer } from '../../../../../base/common/buffer.js';
60
import { platform } from '../../../../../base/common/platform.js';
61
import { arch } from '../../../../../base/common/process.js';
62
import { runWithFakedTimers } from '../../../../../base/test/common/timeTravelScheduler.js';
63
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';
64
import { DisposableStore } from '../../../../../base/common/lifecycle.js';
65
import { timeout } from '../../../../../base/common/async.js';
66
import { IUpdateService, State } from '../../../../../platform/update/common/update.js';
67
import { IUriIdentityService } from '../../../../../platform/uriIdentity/common/uriIdentity.js';
68
import { UriIdentityService } from '../../../../../platform/uriIdentity/common/uriIdentityService.js';
69
70
const ROOT = URI.file('tests').with({ scheme: 'vscode-tests' });
71
72
const mockExtensionGallery: IGalleryExtension[] = [
73
aGalleryExtension('MockExtension1', {
74
displayName: 'Mock Extension 1',
75
version: '1.5',
76
publisherId: 'mockPublisher1Id',
77
publisher: 'mockPublisher1',
78
publisherDisplayName: 'Mock Publisher 1',
79
description: 'Mock Description',
80
installCount: 1000,
81
rating: 4,
82
ratingCount: 100
83
}, {
84
dependencies: ['pub.1'],
85
}, {
86
manifest: { uri: 'uri:manifest', fallbackUri: 'fallback:manifest' },
87
readme: { uri: 'uri:readme', fallbackUri: 'fallback:readme' },
88
changelog: { uri: 'uri:changelog', fallbackUri: 'fallback:changlog' },
89
download: { uri: 'uri:download', fallbackUri: 'fallback:download' },
90
icon: { uri: 'uri:icon', fallbackUri: 'fallback:icon' },
91
license: { uri: 'uri:license', fallbackUri: 'fallback:license' },
92
repository: { uri: 'uri:repository', fallbackUri: 'fallback:repository' },
93
signature: { uri: 'uri:signature', fallbackUri: 'fallback:signature' },
94
coreTranslations: []
95
}),
96
aGalleryExtension('MockExtension2', {
97
displayName: 'Mock Extension 2',
98
version: '1.5',
99
publisherId: 'mockPublisher2Id',
100
publisher: 'mockPublisher2',
101
publisherDisplayName: 'Mock Publisher 2',
102
description: 'Mock Description',
103
installCount: 1000,
104
rating: 4,
105
ratingCount: 100
106
}, {
107
dependencies: ['pub.1', 'pub.2'],
108
}, {
109
manifest: { uri: 'uri:manifest', fallbackUri: 'fallback:manifest' },
110
readme: { uri: 'uri:readme', fallbackUri: 'fallback:readme' },
111
changelog: { uri: 'uri:changelog', fallbackUri: 'fallback:changlog' },
112
download: { uri: 'uri:download', fallbackUri: 'fallback:download' },
113
icon: { uri: 'uri:icon', fallbackUri: 'fallback:icon' },
114
license: { uri: 'uri:license', fallbackUri: 'fallback:license' },
115
repository: { uri: 'uri:repository', fallbackUri: 'fallback:repository' },
116
signature: { uri: 'uri:signature', fallbackUri: 'fallback:signature' },
117
coreTranslations: []
118
})
119
];
120
121
const mockExtensionLocal = [
122
{
123
type: ExtensionType.User,
124
identifier: mockExtensionGallery[0].identifier,
125
manifest: {
126
name: mockExtensionGallery[0].name,
127
publisher: mockExtensionGallery[0].publisher,
128
version: mockExtensionGallery[0].version
129
},
130
metadata: null,
131
path: 'somepath',
132
readmeUrl: 'some readmeUrl',
133
changelogUrl: 'some changelogUrl'
134
},
135
{
136
type: ExtensionType.User,
137
identifier: mockExtensionGallery[1].identifier,
138
manifest: {
139
name: mockExtensionGallery[1].name,
140
publisher: mockExtensionGallery[1].publisher,
141
version: mockExtensionGallery[1].version
142
},
143
metadata: null,
144
path: 'somepath',
145
readmeUrl: 'some readmeUrl',
146
changelogUrl: 'some changelogUrl'
147
}
148
];
149
150
const mockTestData = {
151
recommendedExtensions: [
152
'mockPublisher1.mockExtension1',
153
'MOCKPUBLISHER2.mockextension2',
154
'badlyformattedextension',
155
'MOCKPUBLISHER2.mockextension2',
156
'unknown.extension'
157
],
158
validRecommendedExtensions: [
159
'mockPublisher1.mockExtension1',
160
'MOCKPUBLISHER2.mockextension2'
161
]
162
};
163
164
function aPage<T>(...objects: T[]): IPager<T> {
165
return { firstPage: objects, total: objects.length, pageSize: objects.length, getPage: () => null! };
166
}
167
168
const noAssets: IGalleryExtensionAssets = {
169
changelog: null,
170
download: null!,
171
icon: null!,
172
license: null,
173
manifest: null,
174
readme: null,
175
repository: null,
176
signature: null,
177
coreTranslations: []
178
};
179
180
function aGalleryExtension(name: string, properties: any = {}, galleryExtensionProperties: any = {}, assets: IGalleryExtensionAssets = noAssets): IGalleryExtension {
181
const targetPlatform = getTargetPlatform(platform, arch);
182
const galleryExtension = <IGalleryExtension>Object.create({ name, publisher: 'pub', version: '1.0.0', allTargetPlatforms: [targetPlatform], properties: {}, assets: {}, ...properties });
183
galleryExtension.properties = { ...galleryExtension.properties, dependencies: [], targetPlatform, ...galleryExtensionProperties };
184
galleryExtension.assets = { ...galleryExtension.assets, ...assets };
185
galleryExtension.identifier = { id: getGalleryExtensionId(galleryExtension.publisher, galleryExtension.name), uuid: uuid.generateUuid() };
186
return <IGalleryExtension>galleryExtension;
187
}
188
189
suite('ExtensionRecommendationsService Test', () => {
190
let disposableStore: DisposableStore;
191
let workspaceService: IWorkspaceContextService;
192
let instantiationService: TestInstantiationService;
193
let testConfigurationService: TestConfigurationService;
194
let testObject: ExtensionRecommendationsService;
195
let prompted: boolean;
196
let promptedEmitter: Emitter<void>;
197
let onModelAddedEvent: Emitter<ITextModel>;
198
199
teardown(async () => {
200
disposableStore.dispose();
201
await timeout(0); // allow for async disposables to complete
202
});
203
204
ensureNoDisposablesAreLeakedInTestSuite();
205
206
setup(() => {
207
disposableStore = new DisposableStore();
208
instantiationService = disposableStore.add(new TestInstantiationService());
209
promptedEmitter = disposableStore.add(new Emitter<void>());
210
instantiationService.stub(IExtensionGalleryService, ExtensionGalleryService);
211
instantiationService.stub(ISharedProcessService, TestSharedProcessService);
212
instantiationService.stub(ILifecycleService, disposableStore.add(new TestLifecycleService()));
213
testConfigurationService = new TestConfigurationService();
214
instantiationService.stub(IConfigurationService, testConfigurationService);
215
instantiationService.stub(IProductService, TestProductService);
216
instantiationService.stub(ILogService, NullLogService);
217
const fileService = new FileService(instantiationService.get(ILogService));
218
instantiationService.stub(IFileService, disposableStore.add(fileService));
219
const fileSystemProvider = disposableStore.add(new InMemoryFileSystemProvider());
220
disposableStore.add(fileService.registerProvider(ROOT.scheme, fileSystemProvider));
221
instantiationService.stub(IUriIdentityService, disposableStore.add(new UriIdentityService(instantiationService.get(IFileService))));
222
instantiationService.stub(INotificationService, new TestNotificationService());
223
instantiationService.stub(IContextKeyService, new MockContextKeyService());
224
instantiationService.stub(IWorkbenchExtensionManagementService, {
225
onInstallExtension: Event.None,
226
onDidInstallExtensions: Event.None,
227
onUninstallExtension: Event.None,
228
onDidUninstallExtension: Event.None,
229
onDidUpdateExtensionMetadata: Event.None,
230
onDidChangeProfile: Event.None,
231
onProfileAwareDidInstallExtensions: Event.None,
232
async getInstalled() { return []; },
233
async canInstall() { return true; },
234
async getExtensionsControlManifest() { return { malicious: [], deprecated: {}, search: [], publisherMapping: {} }; },
235
async getTargetPlatform() { return getTargetPlatform(platform, arch); },
236
});
237
instantiationService.stub(IExtensionService, {
238
onDidChangeExtensions: Event.None,
239
extensions: [],
240
async whenInstalledExtensionsRegistered() { return true; }
241
});
242
instantiationService.stub(IWorkbenchExtensionEnablementService, disposableStore.add(new TestExtensionEnablementService(instantiationService)));
243
instantiationService.stub(ITelemetryService, NullTelemetryService);
244
instantiationService.stub(IURLService, NativeURLService);
245
instantiationService.stub(IWorkspaceTagsService, new NoOpWorkspaceTagsService());
246
instantiationService.stub(IStorageService, disposableStore.add(new TestStorageService()));
247
instantiationService.stub(ILogService, new NullLogService());
248
instantiationService.stub(IProductService, {
249
extensionRecommendations: {
250
'ms-python.python': {
251
onFileOpen: [
252
{
253
'pathGlob': '{**/*.py}',
254
important: true
255
}
256
]
257
},
258
'ms-vscode.PowerShell': {
259
onFileOpen: [
260
{
261
'pathGlob': '{**/*.ps,**/*.ps1}',
262
important: true
263
}
264
]
265
},
266
'ms-dotnettools.csharp': {
267
onFileOpen: [
268
{
269
'pathGlob': '{**/*.cs,**/project.json,**/global.json,**/*.csproj,**/*.sln,**/appsettings.json}',
270
}
271
]
272
},
273
'msjsdiag.debugger-for-chrome': {
274
onFileOpen: [
275
{
276
'pathGlob': '{**/*.ts,**/*.tsx,**/*.js,**/*.jsx,**/*.es6,**/*.mjs,**/*.cjs,**/.babelrc}',
277
}
278
]
279
},
280
'lukehoban.Go': {
281
onFileOpen: [
282
{
283
'pathGlob': '**/*.go',
284
}
285
]
286
}
287
},
288
});
289
290
instantiationService.stub(IUpdateService, { onStateChange: Event.None, state: State.Uninitialized });
291
instantiationService.set(IExtensionsWorkbenchService, disposableStore.add(instantiationService.createInstance(ExtensionsWorkbenchService)));
292
instantiationService.stub(IExtensionTipsService, disposableStore.add(instantiationService.createInstance(TestExtensionTipsService)));
293
294
onModelAddedEvent = new Emitter<ITextModel>();
295
296
instantiationService.stub(IEnvironmentService, {});
297
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', []);
298
instantiationService.stub(IExtensionGalleryService, 'isEnabled', true);
299
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage<IGalleryExtension>(...mockExtensionGallery));
300
instantiationService.stubPromise(IExtensionGalleryService, 'getExtensions', mockExtensionGallery);
301
302
prompted = false;
303
304
class TestNotificationService2 extends TestNotificationService {
305
public override prompt(severity: Severity, message: string, choices: IPromptChoice[], options?: IPromptOptions) {
306
prompted = true;
307
promptedEmitter.fire();
308
return super.prompt(severity, message, choices, options);
309
}
310
}
311
312
instantiationService.stub(INotificationService, new TestNotificationService2());
313
314
testConfigurationService.setUserConfiguration(ConfigurationKey, { ignoreRecommendations: false });
315
instantiationService.stub(IModelService, <IModelService>{
316
getModels(): any { return []; },
317
onModelAdded: onModelAddedEvent.event
318
});
319
});
320
321
function setUpFolderWorkspace(folderName: string, recommendedExtensions: string[], ignoredRecommendations: string[] = []): Promise<void> {
322
return setUpFolder(folderName, recommendedExtensions, ignoredRecommendations);
323
}
324
325
async function setUpFolder(folderName: string, recommendedExtensions: string[], ignoredRecommendations: string[] = []): Promise<void> {
326
const fileService = instantiationService.get(IFileService);
327
const folderDir = joinPath(ROOT, folderName);
328
const workspaceSettingsDir = joinPath(folderDir, '.vscode');
329
await fileService.createFolder(workspaceSettingsDir);
330
const configPath = joinPath(workspaceSettingsDir, 'extensions.json');
331
await fileService.writeFile(configPath, VSBuffer.fromString(JSON.stringify({
332
'recommendations': recommendedExtensions,
333
'unwantedRecommendations': ignoredRecommendations,
334
}, null, '\t')));
335
336
const myWorkspace = testWorkspace(folderDir);
337
338
instantiationService.stub(IFileService, fileService);
339
workspaceService = new TestContextService(myWorkspace);
340
instantiationService.stub(IWorkspaceContextService, workspaceService);
341
instantiationService.stub(IWorkspaceExtensionsConfigService, disposableStore.add(instantiationService.createInstance(WorkspaceExtensionsConfigService)));
342
instantiationService.stub(IExtensionIgnoredRecommendationsService, disposableStore.add(instantiationService.createInstance(ExtensionIgnoredRecommendationsService)));
343
instantiationService.stub(IExtensionRecommendationNotificationService, disposableStore.add(instantiationService.createInstance(ExtensionRecommendationNotificationService)));
344
}
345
346
function testNoPromptForValidRecommendations(recommendations: string[]) {
347
return setUpFolderWorkspace('myFolder', recommendations).then(() => {
348
testObject = disposableStore.add(instantiationService.createInstance(ExtensionRecommendationsService));
349
return testObject.activationPromise.then(() => {
350
assert.strictEqual(Object.keys(testObject.getAllRecommendationsWithReason()).length, recommendations.length);
351
assert.ok(!prompted);
352
});
353
});
354
}
355
356
function testNoPromptOrRecommendationsForValidRecommendations(recommendations: string[]) {
357
return setUpFolderWorkspace('myFolder', mockTestData.validRecommendedExtensions).then(() => {
358
testObject = disposableStore.add(instantiationService.createInstance(ExtensionRecommendationsService));
359
assert.ok(!prompted);
360
361
return testObject.getWorkspaceRecommendations().then(() => {
362
assert.strictEqual(Object.keys(testObject.getAllRecommendationsWithReason()).length, 0);
363
assert.ok(!prompted);
364
});
365
});
366
}
367
368
test('ExtensionRecommendationsService: No Prompt for valid workspace recommendations when galleryService is absent', () => runWithFakedTimers<void>({ useFakeTimers: true }, async () => {
369
const galleryQuerySpy = sinon.spy();
370
instantiationService.stub(IExtensionGalleryService, { query: galleryQuerySpy, isEnabled: () => false });
371
372
return testNoPromptOrRecommendationsForValidRecommendations(mockTestData.validRecommendedExtensions)
373
.then(() => assert.ok(galleryQuerySpy.notCalled));
374
}));
375
376
test('ExtensionRecommendationsService: No Prompt for valid workspace recommendations during extension development', () => runWithFakedTimers<void>({ useFakeTimers: true }, async () => {
377
instantiationService.stub(IEnvironmentService, { extensionDevelopmentLocationURI: [URI.file('/folder/file')], isExtensionDevelopment: true });
378
return testNoPromptOrRecommendationsForValidRecommendations(mockTestData.validRecommendedExtensions);
379
}));
380
381
test('ExtensionRecommendationsService: No workspace recommendations or prompts when extensions.json has empty array', () => runWithFakedTimers<void>({ useFakeTimers: true }, async () => {
382
return testNoPromptForValidRecommendations([]);
383
}));
384
385
test('ExtensionRecommendationsService: Prompt for valid workspace recommendations', () => runWithFakedTimers<void>({ useFakeTimers: true }, async () => {
386
await setUpFolderWorkspace('myFolder', mockTestData.recommendedExtensions);
387
testObject = disposableStore.add(instantiationService.createInstance(ExtensionRecommendationsService));
388
389
await Event.toPromise(promptedEmitter.event);
390
const recommendations = Object.keys(testObject.getAllRecommendationsWithReason());
391
const expected = [...mockTestData.validRecommendedExtensions, 'unknown.extension'];
392
assert.strictEqual(recommendations.length, expected.length);
393
expected.forEach(x => {
394
assert.strictEqual(recommendations.indexOf(x.toLowerCase()) > -1, true);
395
});
396
}));
397
398
test('ExtensionRecommendationsService: No Prompt for valid workspace recommendations if they are already installed', () => runWithFakedTimers<void>({ useFakeTimers: true }, async () => {
399
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', mockExtensionLocal);
400
return testNoPromptForValidRecommendations(mockTestData.validRecommendedExtensions);
401
}));
402
403
test('ExtensionRecommendationsService: No Prompt for valid workspace recommendations with casing mismatch if they are already installed', () => runWithFakedTimers<void>({ useFakeTimers: true }, async () => {
404
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', mockExtensionLocal);
405
return testNoPromptForValidRecommendations(mockTestData.validRecommendedExtensions.map(x => x.toUpperCase()));
406
}));
407
408
test('ExtensionRecommendationsService: No Prompt for valid workspace recommendations if ignoreRecommendations is set', () => runWithFakedTimers<void>({ useFakeTimers: true }, async () => {
409
testConfigurationService.setUserConfiguration(ConfigurationKey, { ignoreRecommendations: true });
410
return testNoPromptForValidRecommendations(mockTestData.validRecommendedExtensions);
411
}));
412
413
test('ExtensionRecommendationsService: No Prompt for valid workspace recommendations if showRecommendationsOnlyOnDemand is set', () => runWithFakedTimers<void>({ useFakeTimers: true }, async () => {
414
testConfigurationService.setUserConfiguration(ConfigurationKey, { showRecommendationsOnlyOnDemand: true });
415
return setUpFolderWorkspace('myFolder', mockTestData.validRecommendedExtensions).then(() => {
416
testObject = disposableStore.add(instantiationService.createInstance(ExtensionRecommendationsService));
417
return testObject.activationPromise.then(() => {
418
assert.ok(!prompted);
419
});
420
});
421
}));
422
423
test('ExtensionRecommendationsService: No Prompt for valid workspace recommendations if ignoreRecommendations is set for current workspace', () => runWithFakedTimers<void>({ useFakeTimers: true }, async () => {
424
instantiationService.get(IStorageService).store('extensionsAssistant/workspaceRecommendationsIgnore', true, StorageScope.WORKSPACE, StorageTarget.MACHINE);
425
return testNoPromptForValidRecommendations(mockTestData.validRecommendedExtensions);
426
}));
427
428
test('ExtensionRecommendationsService: No Recommendations of globally ignored recommendations', () => runWithFakedTimers<void>({ useFakeTimers: true }, async () => {
429
instantiationService.get(IStorageService).store('extensionsAssistant/workspaceRecommendationsIgnore', true, StorageScope.WORKSPACE, StorageTarget.MACHINE);
430
instantiationService.get(IStorageService).store('extensionsAssistant/recommendations', '["ms-dotnettools.csharp", "ms-python.python", "ms-vscode.vscode-typescript-tslint-plugin"]', StorageScope.PROFILE, StorageTarget.MACHINE);
431
instantiationService.get(IStorageService).store('extensionsAssistant/ignored_recommendations', '["ms-dotnettools.csharp", "mockpublisher2.mockextension2"]', StorageScope.PROFILE, StorageTarget.MACHINE);
432
433
return setUpFolderWorkspace('myFolder', mockTestData.validRecommendedExtensions).then(() => {
434
testObject = disposableStore.add(instantiationService.createInstance(ExtensionRecommendationsService));
435
return testObject.activationPromise.then(() => {
436
const recommendations = testObject.getAllRecommendationsWithReason();
437
assert.ok(!recommendations['ms-dotnettools.csharp']); // stored recommendation that has been globally ignored
438
assert.ok(recommendations['ms-python.python']); // stored recommendation
439
assert.ok(recommendations['mockpublisher1.mockextension1']); // workspace recommendation
440
assert.ok(!recommendations['mockpublisher2.mockextension2']); // workspace recommendation that has been globally ignored
441
});
442
});
443
}));
444
445
test('ExtensionRecommendationsService: No Recommendations of workspace ignored recommendations', () => runWithFakedTimers<void>({ useFakeTimers: true }, async () => {
446
const ignoredRecommendations = ['ms-dotnettools.csharp', 'mockpublisher2.mockextension2']; // ignore a stored recommendation and a workspace recommendation.
447
const storedRecommendations = '["ms-dotnettools.csharp", "ms-python.python"]';
448
instantiationService.get(IStorageService).store('extensionsAssistant/workspaceRecommendationsIgnore', true, StorageScope.WORKSPACE, StorageTarget.MACHINE);
449
instantiationService.get(IStorageService).store('extensionsAssistant/recommendations', storedRecommendations, StorageScope.PROFILE, StorageTarget.MACHINE);
450
451
return setUpFolderWorkspace('myFolder', mockTestData.validRecommendedExtensions, ignoredRecommendations).then(() => {
452
testObject = disposableStore.add(instantiationService.createInstance(ExtensionRecommendationsService));
453
return testObject.activationPromise.then(() => {
454
const recommendations = testObject.getAllRecommendationsWithReason();
455
assert.ok(!recommendations['ms-dotnettools.csharp']); // stored recommendation that has been workspace ignored
456
assert.ok(recommendations['ms-python.python']); // stored recommendation
457
assert.ok(recommendations['mockpublisher1.mockextension1']); // workspace recommendation
458
assert.ok(!recommendations['mockpublisher2.mockextension2']); // workspace recommendation that has been workspace ignored
459
});
460
});
461
}));
462
463
test('ExtensionRecommendationsService: Able to retrieve collection of all ignored recommendations', async () => runWithFakedTimers<void>({ useFakeTimers: true }, async () => {
464
465
const storageService = instantiationService.get(IStorageService);
466
const workspaceIgnoredRecommendations = ['ms-dotnettools.csharp']; // ignore a stored recommendation and a workspace recommendation.
467
const storedRecommendations = '["ms-dotnettools.csharp", "ms-python.python"]';
468
const globallyIgnoredRecommendations = '["mockpublisher2.mockextension2"]'; // ignore a workspace recommendation.
469
storageService.store('extensionsAssistant/workspaceRecommendationsIgnore', true, StorageScope.WORKSPACE, StorageTarget.MACHINE);
470
storageService.store('extensionsAssistant/recommendations', storedRecommendations, StorageScope.PROFILE, StorageTarget.MACHINE);
471
storageService.store('extensionsAssistant/ignored_recommendations', globallyIgnoredRecommendations, StorageScope.PROFILE, StorageTarget.MACHINE);
472
473
await setUpFolderWorkspace('myFolder', mockTestData.validRecommendedExtensions, workspaceIgnoredRecommendations);
474
testObject = disposableStore.add(instantiationService.createInstance(ExtensionRecommendationsService));
475
await testObject.activationPromise;
476
477
const recommendations = testObject.getAllRecommendationsWithReason();
478
assert.deepStrictEqual(Object.keys(recommendations), ['ms-python.python', 'mockpublisher1.mockextension1']);
479
}));
480
481
test('ExtensionRecommendationsService: Able to dynamically ignore/unignore global recommendations', async () => runWithFakedTimers<void>({ useFakeTimers: true }, async () => {
482
const storageService = instantiationService.get(IStorageService);
483
484
const storedRecommendations = '["ms-dotnettools.csharp", "ms-python.python"]';
485
const globallyIgnoredRecommendations = '["mockpublisher2.mockextension2"]'; // ignore a workspace recommendation.
486
storageService.store('extensionsAssistant/workspaceRecommendationsIgnore', true, StorageScope.WORKSPACE, StorageTarget.MACHINE);
487
storageService.store('extensionsAssistant/recommendations', storedRecommendations, StorageScope.PROFILE, StorageTarget.MACHINE);
488
storageService.store('extensionsAssistant/ignored_recommendations', globallyIgnoredRecommendations, StorageScope.PROFILE, StorageTarget.MACHINE);
489
490
await setUpFolderWorkspace('myFolder', mockTestData.validRecommendedExtensions);
491
const extensionIgnoredRecommendationsService = instantiationService.get(IExtensionIgnoredRecommendationsService);
492
testObject = disposableStore.add(instantiationService.createInstance(ExtensionRecommendationsService));
493
await testObject.activationPromise;
494
495
let recommendations = testObject.getAllRecommendationsWithReason();
496
assert.ok(recommendations['ms-python.python']);
497
assert.ok(recommendations['mockpublisher1.mockextension1']);
498
assert.ok(!recommendations['mockpublisher2.mockextension2']);
499
500
extensionIgnoredRecommendationsService.toggleGlobalIgnoredRecommendation('mockpublisher1.mockextension1', true);
501
502
recommendations = testObject.getAllRecommendationsWithReason();
503
assert.ok(recommendations['ms-python.python']);
504
assert.ok(!recommendations['mockpublisher1.mockextension1']);
505
assert.ok(!recommendations['mockpublisher2.mockextension2']);
506
507
extensionIgnoredRecommendationsService.toggleGlobalIgnoredRecommendation('mockpublisher1.mockextension1', false);
508
509
recommendations = testObject.getAllRecommendationsWithReason();
510
assert.ok(recommendations['ms-python.python']);
511
assert.ok(recommendations['mockpublisher1.mockextension1']);
512
assert.ok(!recommendations['mockpublisher2.mockextension2']);
513
}));
514
515
test('test global extensions are modified and recommendation change event is fired when an extension is ignored', async () => runWithFakedTimers<void>({ useFakeTimers: true }, async () => {
516
const storageService = instantiationService.get(IStorageService);
517
const changeHandlerTarget = sinon.spy();
518
const ignoredExtensionId = 'Some.Extension';
519
520
storageService.store('extensionsAssistant/workspaceRecommendationsIgnore', true, StorageScope.WORKSPACE, StorageTarget.MACHINE);
521
storageService.store('extensionsAssistant/ignored_recommendations', '["ms-vscode.vscode"]', StorageScope.PROFILE, StorageTarget.MACHINE);
522
523
await setUpFolderWorkspace('myFolder', []);
524
testObject = disposableStore.add(instantiationService.createInstance(ExtensionRecommendationsService));
525
const extensionIgnoredRecommendationsService = instantiationService.get(IExtensionIgnoredRecommendationsService);
526
disposableStore.add(extensionIgnoredRecommendationsService.onDidChangeGlobalIgnoredRecommendation(changeHandlerTarget));
527
extensionIgnoredRecommendationsService.toggleGlobalIgnoredRecommendation(ignoredExtensionId, true);
528
await testObject.activationPromise;
529
530
assert.ok(changeHandlerTarget.calledOnce);
531
assert.ok(changeHandlerTarget.getCall(0).calledWithMatch({ extensionId: ignoredExtensionId.toLowerCase(), isRecommended: false }));
532
}));
533
534
test('ExtensionRecommendationsService: Get file based recommendations from storage (old format)', () => runWithFakedTimers<void>({ useFakeTimers: true }, async () => {
535
const storedRecommendations = '["ms-dotnettools.csharp", "ms-python.python", "ms-vscode.vscode-typescript-tslint-plugin"]';
536
instantiationService.get(IStorageService).store('extensionsAssistant/recommendations', storedRecommendations, StorageScope.PROFILE, StorageTarget.MACHINE);
537
538
return setUpFolderWorkspace('myFolder', []).then(() => {
539
testObject = disposableStore.add(instantiationService.createInstance(ExtensionRecommendationsService));
540
return testObject.activationPromise.then(() => {
541
const recommendations = testObject.getFileBasedRecommendations();
542
assert.strictEqual(recommendations.length, 2);
543
assert.ok(recommendations.some(extensionId => extensionId === 'ms-dotnettools.csharp')); // stored recommendation that exists in product.extensionTips
544
assert.ok(recommendations.some(extensionId => extensionId === 'ms-python.python')); // stored recommendation that exists in product.extensionImportantTips
545
assert.ok(recommendations.every(extensionId => extensionId !== 'ms-vscode.vscode-typescript-tslint-plugin')); // stored recommendation that is no longer in neither product.extensionTips nor product.extensionImportantTips
546
});
547
});
548
}));
549
550
test('ExtensionRecommendationsService: Get file based recommendations from storage (new format)', async () => {
551
const milliSecondsInADay = 1000 * 60 * 60 * 24;
552
const now = Date.now();
553
const tenDaysOld = 10 * milliSecondsInADay;
554
const storedRecommendations = `{"ms-dotnettools.csharp": ${now}, "ms-python.python": ${now}, "ms-vscode.vscode-typescript-tslint-plugin": ${now}, "lukehoban.Go": ${tenDaysOld}}`;
555
instantiationService.get(IStorageService).store('extensionsAssistant/recommendations', storedRecommendations, StorageScope.PROFILE, StorageTarget.MACHINE);
556
557
await setUpFolderWorkspace('myFolder', []);
558
testObject = disposableStore.add(instantiationService.createInstance(ExtensionRecommendationsService));
559
await testObject.activationPromise;
560
561
const recommendations = testObject.getFileBasedRecommendations();
562
assert.strictEqual(recommendations.length, 2);
563
assert.ok(recommendations.some(extensionId => extensionId === 'ms-dotnettools.csharp')); // stored recommendation that exists in product.extensionTips
564
assert.ok(recommendations.some(extensionId => extensionId === 'ms-python.python')); // stored recommendation that exists in product.extensionImportantTips
565
assert.ok(recommendations.every(extensionId => extensionId !== 'ms-vscode.vscode-typescript-tslint-plugin')); // stored recommendation that is no longer in neither product.extensionTips nor product.extensionImportantTips
566
assert.ok(recommendations.every(extensionId => extensionId !== 'lukehoban.Go')); //stored recommendation that is older than a week
567
});
568
});
569
570