UNPKG

24.4 kBPlain TextView Raw
1// *****************************************************************************
2// Copyright (C) 2018 Ericsson and others.
3//
4// This program and the accompanying materials are made available under the
5// terms of the Eclipse Public License v. 2.0 which is available at
6// http://www.eclipse.org/legal/epl-2.0.
7//
8// This Source Code may also be made available under the following Secondary
9// Licenses when the conditions for such availability set forth in the Eclipse
10// Public License v. 2.0 are satisfied: GNU General Public License, version 2
11// with the GNU Classpath Exception which is available at
12// https://www.gnu.org/software/classpath/license.html.
13//
14// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15// *****************************************************************************
16
17/* eslint-disable @typescript-eslint/no-explicit-any */
18
19import { enableJSDOM } from '../test/jsdom';
20
21let disableJSDOM = enableJSDOM();
22
23import * as assert from 'assert';
24import { Container } from 'inversify';
25import { bindPreferenceService } from '../frontend-application-bindings';
26import { bindMockPreferenceProviders, MockPreferenceProvider } from './test';
27import { PreferenceService, PreferenceServiceImpl, PreferenceChange, PreferenceChanges } from './preference-service';
28import { PreferenceSchemaProvider, PreferenceSchema } from './preference-contribution';
29import { PreferenceScope } from './preference-scope';
30import { PreferenceProvider } from './preference-provider';
31import { FrontendApplicationConfigProvider } from '../frontend-application-config-provider';
32import { createPreferenceProxy, PreferenceChangeEvent } from './preference-proxy';
33
34disableJSDOM();
35
36process.on('unhandledRejection', (reason, promise) => {
37 console.error(reason);
38 throw reason;
39});
40
41const { expect } = require('chai');
42let testContainer: Container;
43
44function createTestContainer(): Container {
45 const result = new Container();
46 bindPreferenceService(result.bind.bind(result));
47 bindMockPreferenceProviders(result.bind.bind(result), result.unbind.bind(result));
48 return result;
49}
50
51describe('Preference Service', () => {
52 let prefService: PreferenceServiceImpl;
53 let prefSchema: PreferenceSchemaProvider;
54
55 before(() => {
56 disableJSDOM = enableJSDOM();
57 FrontendApplicationConfigProvider.set({});
58 });
59
60 after(() => {
61 disableJSDOM();
62 });
63
64 beforeEach(async () => {
65 testContainer = createTestContainer();
66 prefSchema = testContainer.get(PreferenceSchemaProvider);
67 prefService = testContainer.get<PreferenceService>(PreferenceService) as PreferenceServiceImpl;
68 getProvider(PreferenceScope.User).markReady();
69 getProvider(PreferenceScope.Workspace).markReady();
70 getProvider(PreferenceScope.Folder).markReady();
71 console.log('before ready');
72 try {
73 await prefService.ready;
74 } catch (e) {
75 console.error(e);
76 }
77 console.log('done');
78 });
79
80 afterEach(() => {
81 });
82
83 function getProvider(scope: PreferenceScope): MockPreferenceProvider {
84 return testContainer.getNamed(PreferenceProvider, scope) as MockPreferenceProvider;
85 }
86
87 it('should return the preference from the more specific scope (user > workspace)', () => {
88 prefSchema.setSchema({
89 properties: {
90 'test.number': {
91 type: 'number',
92 scope: 'resource'
93 }
94 }
95 });
96 const userProvider = getProvider(PreferenceScope.User);
97 const workspaceProvider = getProvider(PreferenceScope.Workspace);
98 const folderProvider = getProvider(PreferenceScope.Folder);
99 userProvider.setPreference('test.number', 1);
100 expect(prefService.get('test.number')).equals(1);
101 workspaceProvider.setPreference('test.number', 0);
102 expect(prefService.get('test.number')).equals(0);
103 folderProvider.setPreference('test.number', 2);
104 expect(prefService.get('test.number')).equals(2);
105
106 // remove property on lower scope
107 folderProvider.setPreference('test.number', undefined);
108 expect(prefService.get('test.number')).equals(0);
109 });
110
111 it('should throw a TypeError if the preference (reference object) is modified', () => {
112 prefSchema.setSchema({
113 properties: {
114 'test.immutable': {
115 type: 'array',
116 items: {
117 type: 'string'
118 },
119 scope: 'resource'
120 }
121 }
122 });
123 const userProvider = getProvider(PreferenceScope.User);
124 userProvider.setPreference('test.immutable', [
125 'test', 'test', 'test'
126 ]);
127 const immutablePref: string[] | undefined = prefService.get('test.immutable');
128 expect(immutablePref).to.not.be.undefined;
129 if (immutablePref !== undefined) {
130 expect(() => immutablePref.push('fails')).to.throw(TypeError);
131 }
132 });
133
134 it('should still report the more specific preference even though the less specific one changed', () => {
135 prefSchema.setSchema({
136 properties: {
137 'test.number': {
138 type: 'number',
139 scope: 'resource'
140 }
141 }
142 });
143 const userProvider = getProvider(PreferenceScope.User);
144 const workspaceProvider = getProvider(PreferenceScope.Workspace);
145 userProvider.setPreference('test.number', 1);
146 workspaceProvider.setPreference('test.number', 0);
147 expect(prefService.get('test.number')).equals(0);
148
149 userProvider.setPreference('test.number', 4);
150 expect(prefService.get('test.number')).equals(0);
151 });
152
153 it('should not fire events if preference schema is unset in the same tick ', async () => {
154 const events: PreferenceChange[] = [];
155 prefService.onPreferenceChanged(event => events.push(event));
156 prefSchema.registerOverrideIdentifier('go');
157
158 const toUnset = prefSchema.setSchema({
159 properties: {
160 'editor.insertSpaces': {
161 type: 'boolean',
162 default: true,
163 overridable: true
164 },
165 '[go]': {
166 type: 'object',
167 default: {
168 'editor.insertSpaces': false
169 }
170 }
171 }
172 });
173 assert.deepStrictEqual([], events.map(e => ({
174 preferenceName: e.preferenceName,
175 newValue: e.newValue,
176 oldValue: e.oldValue
177 })), 'events after set in the same tick');
178 assert.strictEqual(prefService.get('editor.insertSpaces'), true, 'get before');
179 assert.strictEqual(prefService.get('[go].editor.insertSpaces'), false, 'get before overridden');
180
181 toUnset.dispose();
182
183 assert.deepStrictEqual([], events.map(e => ({
184 preferenceName: e.preferenceName,
185 newValue: e.newValue,
186 oldValue: e.oldValue
187 })), 'events after unset in the same tick');
188 assert.strictEqual(prefService.get('editor.insertSpaces'), undefined, 'get after');
189 assert.strictEqual(prefService.get('[go].editor.insertSpaces'), undefined, 'get after overridden');
190
191 assert.deepStrictEqual([], events.map(e => ({
192 preferenceName: e.preferenceName,
193 newValue: e.newValue,
194 oldValue: e.oldValue
195 })), 'events in next tick');
196 });
197
198 it('should fire events if preference schema is unset in another tick', async () => {
199 prefSchema.registerOverrideIdentifier('go');
200
201 let pending = new Promise<PreferenceChanges>(resolve => prefService.onPreferencesChanged(resolve));
202 const toUnset = prefSchema.setSchema({
203 properties: {
204 'editor.insertSpaces': {
205 type: 'boolean',
206 default: true,
207 overridable: true
208 },
209 '[go]': {
210 type: 'object',
211 default: {
212 'editor.insertSpaces': false
213 }
214 }
215 }
216 });
217 let changes = await pending;
218
219 assert.deepStrictEqual([{
220 preferenceName: 'editor.insertSpaces',
221 newValue: true,
222 oldValue: undefined
223 }, {
224 preferenceName: '[go].editor.insertSpaces',
225 newValue: false,
226 oldValue: undefined
227 }], Object.keys(changes).map(key => {
228 const { preferenceName, newValue, oldValue } = changes[key];
229 return { preferenceName, newValue, oldValue };
230 }), 'events before');
231 assert.strictEqual(prefService.get('editor.insertSpaces'), true, 'get before');
232 assert.strictEqual(prefService.get('[go].editor.insertSpaces'), false, 'get before overridden');
233
234 pending = new Promise<PreferenceChanges>(resolve => prefService.onPreferencesChanged(resolve));
235 toUnset.dispose();
236 changes = await pending;
237
238 assert.deepStrictEqual([{
239 preferenceName: 'editor.insertSpaces',
240 newValue: undefined,
241 oldValue: true
242 }, {
243 preferenceName: '[go].editor.insertSpaces',
244 newValue: undefined,
245 oldValue: false
246 }], Object.keys(changes).map(key => {
247 const { preferenceName, newValue, oldValue } = changes[key];
248 return { preferenceName, newValue, oldValue };
249 }), 'events after');
250 assert.strictEqual(prefService.get('editor.insertSpaces'), undefined, 'get after');
251 assert.strictEqual(prefService.get('[go].editor.insertSpaces'), undefined, 'get after overridden');
252 });
253
254 function prepareServices(options?: { schema: PreferenceSchema }): {
255 preferences: PreferenceServiceImpl;
256 schema: PreferenceSchemaProvider;
257 } {
258 prefSchema.setSchema(options && options.schema || {
259 properties: {
260 'editor.tabSize': {
261 type: 'number',
262 description: '',
263 overridable: true,
264 default: 4
265 }
266 }
267 });
268
269 return { preferences: prefService, schema: prefSchema };
270 }
271
272 describe('PreferenceService.updateValues()', () => {
273 const TAB_SIZE = 'editor.tabSize';
274 const DUMMY_URI = 'dummy_uri';
275 async function generateAndCheckValues(
276 preferences: PreferenceService,
277 globalValue: number | undefined,
278 workspaceValue: number | undefined,
279 workspaceFolderValue: number | undefined
280 ): Promise<void> {
281 await preferences.set(TAB_SIZE, globalValue, PreferenceScope.User);
282 await preferences.set(TAB_SIZE, workspaceValue, PreferenceScope.Workspace);
283 await preferences.set(TAB_SIZE, workspaceFolderValue, PreferenceScope.Folder, DUMMY_URI);
284 const expectedValue = workspaceFolderValue ?? workspaceValue ?? globalValue ?? 4;
285 checkValues(preferences, globalValue, workspaceValue, workspaceFolderValue, expectedValue);
286 }
287
288 function checkValues(
289 preferences: PreferenceService,
290 globalValue: number | undefined,
291 workspaceValue: number | undefined,
292 workspaceFolderValue: number | undefined,
293 value: number = 4,
294 ): void {
295 const expected = {
296 preferenceName: 'editor.tabSize',
297 defaultValue: 4,
298 globalValue,
299 workspaceValue,
300 workspaceFolderValue,
301 value,
302 };
303 const inspection = preferences.inspect(TAB_SIZE, DUMMY_URI);
304 assert.deepStrictEqual(inspection, expected);
305 }
306
307 it('should modify the narrowest scope.', async () => {
308 const { preferences } = prepareServices();
309
310 await generateAndCheckValues(preferences, 1, 2, 3);
311 await preferences.updateValue(TAB_SIZE, 8, DUMMY_URI);
312 checkValues(preferences, 1, 2, 8, 8);
313
314 await generateAndCheckValues(preferences, 1, 2, undefined);
315 await preferences.updateValue(TAB_SIZE, 8, DUMMY_URI);
316 checkValues(preferences, 1, 8, undefined, 8);
317
318 await generateAndCheckValues(preferences, 1, undefined, undefined);
319 await preferences.updateValue(TAB_SIZE, 8, DUMMY_URI);
320 checkValues(preferences, 8, undefined, undefined, 8);
321 });
322
323 it('defaults to user scope.', async () => {
324 const { preferences } = prepareServices();
325 checkValues(preferences, undefined, undefined, undefined);
326 await preferences.updateValue(TAB_SIZE, 8, DUMMY_URI);
327 checkValues(preferences, 8, undefined, undefined, 8);
328 });
329
330 it('clears all settings when input is undefined.', async () => {
331 const { preferences } = prepareServices();
332
333 await generateAndCheckValues(preferences, 1, 2, 3);
334 await preferences.updateValue(TAB_SIZE, undefined, DUMMY_URI);
335 checkValues(preferences, undefined, undefined, undefined);
336 });
337
338 it('deletes user setting if user is only defined scope and target is default value', async () => {
339 const { preferences } = prepareServices();
340
341 await generateAndCheckValues(preferences, 8, undefined, undefined);
342 await preferences.updateValue(TAB_SIZE, 4, DUMMY_URI);
343 checkValues(preferences, undefined, undefined, undefined);
344 });
345
346 it('does not delete setting in lower scopes, even if target is default', async () => {
347 const { preferences } = prepareServices();
348
349 await generateAndCheckValues(preferences, undefined, 2, undefined);
350 await preferences.updateValue(TAB_SIZE, 4, DUMMY_URI);
351 checkValues(preferences, undefined, 4, undefined);
352 });
353 });
354
355 describe('overridden preferences', () => {
356
357 it('get #0', () => {
358 const { preferences, schema } = prepareServices();
359
360 preferences.set('[json].editor.tabSize', 2, PreferenceScope.User);
361
362 expect(preferences.get('editor.tabSize')).to.equal(4);
363 expect(preferences.get('[json].editor.tabSize')).to.equal(undefined);
364
365 schema.registerOverrideIdentifier('json');
366
367 expect(preferences.get('editor.tabSize')).to.equal(4);
368 expect(preferences.get('[json].editor.tabSize')).to.equal(2);
369 });
370
371 it('get #1', () => {
372 const { preferences, schema } = prepareServices();
373 schema.registerOverrideIdentifier('json');
374
375 expect(preferences.get('editor.tabSize')).to.equal(4);
376 expect(preferences.get('[json].editor.tabSize')).to.equal(4);
377
378 preferences.set('[json].editor.tabSize', 2, PreferenceScope.User);
379
380 expect(preferences.get('editor.tabSize')).to.equal(4);
381 expect(preferences.get('[json].editor.tabSize')).to.equal(2);
382 });
383
384 it('get #2', () => {
385 const { preferences, schema } = prepareServices();
386 schema.registerOverrideIdentifier('json');
387
388 expect(preferences.get('editor.tabSize')).to.equal(4);
389 expect(preferences.get('[json].editor.tabSize')).to.equal(4);
390
391 preferences.set('editor.tabSize', 2, PreferenceScope.User);
392
393 expect(preferences.get('editor.tabSize')).to.equal(2);
394 expect(preferences.get('[json].editor.tabSize')).to.equal(2);
395 });
396
397 it('has', () => {
398 const { preferences, schema } = prepareServices();
399
400 expect(preferences.has('editor.tabSize')).to.be.true;
401 expect(preferences.has('[json].editor.tabSize')).to.be.false;
402
403 schema.registerOverrideIdentifier('json');
404
405 expect(preferences.has('editor.tabSize')).to.be.true;
406 expect(preferences.has('[json].editor.tabSize')).to.be.true;
407 });
408
409 it('inspect #0', () => {
410 const { preferences, schema } = prepareServices();
411
412 const expected = {
413 preferenceName: 'editor.tabSize',
414 defaultValue: 4,
415 globalValue: undefined,
416 workspaceValue: undefined,
417 workspaceFolderValue: undefined,
418 value: 4,
419 };
420 assert.deepStrictEqual(expected, preferences.inspect('editor.tabSize'));
421 assert.ok(!preferences.has('[json].editor.tabSize'));
422
423 schema.registerOverrideIdentifier('json');
424
425 assert.deepStrictEqual(expected, preferences.inspect('editor.tabSize'));
426 assert.deepStrictEqual({
427 ...expected,
428 preferenceName: '[json].editor.tabSize'
429 }, preferences.inspect('[json].editor.tabSize'));
430 });
431
432 it('inspect #1', () => {
433 const { preferences, schema } = prepareServices();
434
435 const expected = {
436 preferenceName: 'editor.tabSize',
437 defaultValue: 4,
438 globalValue: 2,
439 workspaceValue: undefined,
440 workspaceFolderValue: undefined,
441 value: 2
442 };
443 preferences.set('editor.tabSize', 2, PreferenceScope.User);
444
445 assert.deepStrictEqual(expected, preferences.inspect('editor.tabSize'));
446 assert.ok(!preferences.has('[json].editor.tabSize'));
447
448 schema.registerOverrideIdentifier('json');
449
450 assert.deepStrictEqual(expected, preferences.inspect('editor.tabSize'));
451 assert.deepStrictEqual({
452 ...expected,
453 preferenceName: '[json].editor.tabSize'
454 }, preferences.inspect('[json].editor.tabSize'));
455 });
456
457 it('inspect #2', () => {
458 const { preferences, schema } = prepareServices();
459
460 const expected = {
461 preferenceName: 'editor.tabSize',
462 defaultValue: 4,
463 globalValue: undefined,
464 workspaceValue: undefined,
465 workspaceFolderValue: undefined,
466 value: 4
467 };
468 assert.deepStrictEqual(expected, preferences.inspect('editor.tabSize'));
469 assert.ok(!preferences.has('[json].editor.tabSize'));
470
471 schema.registerOverrideIdentifier('json');
472 preferences.set('[json].editor.tabSize', 2, PreferenceScope.User);
473
474 assert.deepStrictEqual(expected, preferences.inspect('editor.tabSize'));
475 assert.deepStrictEqual({
476 ...expected,
477 preferenceName: '[json].editor.tabSize',
478 globalValue: 2,
479 value: 2,
480 }, preferences.inspect('[json].editor.tabSize'));
481 });
482
483 it('onPreferenceChanged #0', async () => {
484 const { preferences, schema } = prepareServices();
485
486 const events: PreferenceChange[] = [];
487 preferences.onPreferenceChanged(event => events.push(event));
488
489 schema.registerOverrideIdentifier('json');
490 preferences.set('[json].editor.tabSize', 2, PreferenceScope.User);
491 await preferences.set('editor.tabSize', 3, PreferenceScope.User);
492
493 assert.deepStrictEqual([{
494 preferenceName: '[json].editor.tabSize',
495 newValue: 2
496 }, {
497 preferenceName: 'editor.tabSize',
498 newValue: 3
499 }], events.map(e => ({
500 preferenceName: e.preferenceName,
501 newValue: e.newValue
502 })));
503 });
504
505 it('onPreferenceChanged #1', async () => {
506 const { preferences, schema } = prepareServices();
507
508 const events: PreferenceChange[] = [];
509 preferences.onPreferenceChanged(event => events.push(event));
510
511 schema.registerOverrideIdentifier('json');
512 await preferences.set('editor.tabSize', 2, PreferenceScope.User);
513
514 assert.deepStrictEqual([{
515 preferenceName: 'editor.tabSize',
516 newValue: 2
517 }, {
518 preferenceName: '[json].editor.tabSize',
519 newValue: 2
520 }], events.map(e => ({
521 preferenceName: e.preferenceName,
522 newValue: e.newValue
523 })));
524 });
525
526 it('onPreferenceChanged #2', async function (): Promise<void> {
527 const { preferences, schema } = prepareServices();
528
529 schema.registerOverrideIdentifier('json');
530 schema.registerOverrideIdentifier('javascript');
531 preferences.set('[json].editor.tabSize', 2, PreferenceScope.User);
532 await preferences.set('editor.tabSize', 3, PreferenceScope.User);
533
534 const events: PreferenceChangeEvent<{ [key: string]: any }>[] = [];
535 const proxy = createPreferenceProxy<{ [key: string]: any }>(preferences, schema.getCombinedSchema(), { overrideIdentifier: 'json' });
536 proxy.onPreferenceChanged(event => events.push(event));
537
538 await preferences.set('[javascript].editor.tabSize', 4, PreferenceScope.User);
539
540 assert.deepStrictEqual([], events.map(e => ({
541 preferenceName: e.preferenceName,
542 newValue: e.newValue
543 })), 'changes not relevant to json override should be ignored');
544 });
545
546 it('onPreferenceChanged #3', async () => {
547 const { preferences, schema } = prepareServices();
548
549 schema.registerOverrideIdentifier('json');
550 preferences.set('[json].editor.tabSize', 2, PreferenceScope.User);
551 await preferences.set('editor.tabSize', 3, PreferenceScope.User);
552
553 const events: PreferenceChange[] = [];
554 preferences.onPreferenceChanged(event => events.push(event));
555
556 await preferences.set('[json].editor.tabSize', undefined, PreferenceScope.User);
557
558 assert.deepStrictEqual([{
559 preferenceName: '[json].editor.tabSize',
560 newValue: 3
561 }], events.map(e => ({
562 preferenceName: e.preferenceName,
563 newValue: e.newValue
564 })));
565 });
566
567 it('defaultOverrides [go].editor.formatOnSave', () => {
568 const { preferences, schema } = prepareServices({
569 schema: {
570 properties: {
571 'editor.insertSpaces': {
572 type: 'boolean',
573 default: true,
574 overridable: true
575 },
576 'editor.formatOnSave': {
577 type: 'boolean',
578 default: false,
579 overridable: true
580 }
581 }
582 }
583 });
584
585 assert.strictEqual(true, preferences.get('editor.insertSpaces'));
586 assert.strictEqual(undefined, preferences.get('[go].editor.insertSpaces'));
587 assert.strictEqual(false, preferences.get('editor.formatOnSave'));
588 assert.strictEqual(undefined, preferences.get('[go].editor.formatOnSave'));
589
590 schema.registerOverrideIdentifier('go');
591 schema.setSchema({
592 id: 'defaultOverrides',
593 title: 'Default Configuration Overrides',
594 properties: {
595 '[go]': {
596 type: 'object',
597 default: {
598 'editor.insertSpaces': false,
599 'editor.formatOnSave': true
600 },
601 description: 'Configure editor settings to be overridden for go language.'
602 }
603 }
604 });
605
606 assert.strictEqual(true, preferences.get('editor.insertSpaces'));
607 assert.strictEqual(false, preferences.get('[go].editor.insertSpaces'));
608 assert.strictEqual(false, preferences.get('editor.formatOnSave'));
609 assert.strictEqual(true, preferences.get('[go].editor.formatOnSave'));
610 });
611 });
612
613});