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