UNPKG

@theia/core

Version:

Theia is a cloud & desktop IDE framework implemented in TypeScript.

614 lines (515 loc) • 24.5 kB
// ***************************************************************************** // Copyright (C) 2018 Ericsson and others. // // This program and the accompanying materials are made available under the // terms of the Eclipse Public License v. 2.0 which is available at // http://www.eclipse.org/legal/epl-2.0. // // This Source Code may also be made available under the following Secondary // Licenses when the conditions for such availability set forth in the Eclipse // Public License v. 2.0 are satisfied: GNU General Public License, version 2 // with the GNU Classpath Exception which is available at // https://www.gnu.org/software/classpath/license.html. // // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 // ***************************************************************************** /* eslint-disable @typescript-eslint/no-explicit-any */ import { enableJSDOM } from '../test/jsdom'; let disableJSDOM = enableJSDOM(); import * as assert from 'assert'; import { Container } from 'inversify'; import { bindPreferenceService } from '../frontend-application-bindings'; import { bindMockPreferenceProviders, MockPreferenceProvider } from './test'; import { PreferenceService, PreferenceServiceImpl, PreferenceChange, PreferenceChanges } from './preference-service'; import { PreferenceSchemaProvider, PreferenceSchema } from './preference-contribution'; import { PreferenceScope } from './preference-scope'; import { PreferenceProvider } from './preference-provider'; import { FrontendApplicationConfigProvider } from '../frontend-application-config-provider'; import { createPreferenceProxy, PreferenceChangeEvent } from './preference-proxy'; disableJSDOM(); process.on('unhandledRejection', (reason, promise) => { console.error(reason); throw reason; }); const { expect } = require('chai'); let testContainer: Container; function createTestContainer(): Container { const result = new Container(); bindPreferenceService(result.bind.bind(result)); bindMockPreferenceProviders(result.bind.bind(result), result.unbind.bind(result)); return result; } describe('Preference Service', () => { let prefService: PreferenceServiceImpl; let prefSchema: PreferenceSchemaProvider; before(() => { disableJSDOM = enableJSDOM(); FrontendApplicationConfigProvider.set({}); }); after(() => { disableJSDOM(); }); beforeEach(async () => { testContainer = createTestContainer(); prefSchema = testContainer.get(PreferenceSchemaProvider); prefService = testContainer.get<PreferenceService>(PreferenceService) as PreferenceServiceImpl; getProvider(PreferenceScope.User).markReady(); getProvider(PreferenceScope.Workspace).markReady(); getProvider(PreferenceScope.Folder).markReady(); console.log('before ready'); try { await prefService.ready; } catch (e) { console.error(e); } console.log('done'); }); afterEach(() => { }); function getProvider(scope: PreferenceScope): MockPreferenceProvider { return testContainer.getNamed(PreferenceProvider, scope) as MockPreferenceProvider; } it('should return the preference from the more specific scope (user > workspace)', () => { prefSchema.setSchema({ properties: { 'test.number': { type: 'number', scope: 'resource' } } }); const userProvider = getProvider(PreferenceScope.User); const workspaceProvider = getProvider(PreferenceScope.Workspace); const folderProvider = getProvider(PreferenceScope.Folder); userProvider.setPreference('test.number', 1); expect(prefService.get('test.number')).equals(1); workspaceProvider.setPreference('test.number', 0); expect(prefService.get('test.number')).equals(0); folderProvider.setPreference('test.number', 2); expect(prefService.get('test.number')).equals(2); // remove property on lower scope folderProvider.setPreference('test.number', undefined); expect(prefService.get('test.number')).equals(0); }); it('should throw a TypeError if the preference (reference object) is modified', () => { prefSchema.setSchema({ properties: { 'test.immutable': { type: 'array', items: { type: 'string' }, scope: 'resource' } } }); const userProvider = getProvider(PreferenceScope.User); userProvider.setPreference('test.immutable', [ 'test', 'test', 'test' ]); const immutablePref: string[] | undefined = prefService.get('test.immutable'); expect(immutablePref).to.not.be.undefined; if (immutablePref !== undefined) { expect(() => immutablePref.push('fails')).to.throw(TypeError); } }); it('should still report the more specific preference even though the less specific one changed', () => { prefSchema.setSchema({ properties: { 'test.number': { type: 'number', scope: 'resource' } } }); const userProvider = getProvider(PreferenceScope.User); const workspaceProvider = getProvider(PreferenceScope.Workspace); userProvider.setPreference('test.number', 1); workspaceProvider.setPreference('test.number', 0); expect(prefService.get('test.number')).equals(0); userProvider.setPreference('test.number', 4); expect(prefService.get('test.number')).equals(0); }); it('should not fire events if preference schema is unset in the same tick ', async () => { const events: PreferenceChange[] = []; prefService.onPreferenceChanged(event => events.push(event)); prefSchema.registerOverrideIdentifier('go'); const toUnset = prefSchema.setSchema({ properties: { 'editor.insertSpaces': { type: 'boolean', default: true, overridable: true }, '[go]': { type: 'object', default: { 'editor.insertSpaces': false } } } }); assert.deepStrictEqual([], events.map(e => ({ preferenceName: e.preferenceName, newValue: e.newValue, oldValue: e.oldValue })), 'events after set in the same tick'); assert.strictEqual(prefService.get('editor.insertSpaces'), true, 'get before'); assert.strictEqual(prefService.get('[go].editor.insertSpaces'), false, 'get before overridden'); toUnset.dispose(); assert.deepStrictEqual([], events.map(e => ({ preferenceName: e.preferenceName, newValue: e.newValue, oldValue: e.oldValue })), 'events after unset in the same tick'); assert.strictEqual(prefService.get('editor.insertSpaces'), undefined, 'get after'); assert.strictEqual(prefService.get('[go].editor.insertSpaces'), undefined, 'get after overridden'); assert.deepStrictEqual([], events.map(e => ({ preferenceName: e.preferenceName, newValue: e.newValue, oldValue: e.oldValue })), 'events in next tick'); }); it('should fire events if preference schema is unset in another tick', async () => { prefSchema.registerOverrideIdentifier('go'); let pending = new Promise<PreferenceChanges>(resolve => prefService.onPreferencesChanged(resolve)); const toUnset = prefSchema.setSchema({ properties: { 'editor.insertSpaces': { type: 'boolean', default: true, overridable: true }, '[go]': { type: 'object', default: { 'editor.insertSpaces': false } } } }); let changes = await pending; assert.deepStrictEqual([{ preferenceName: 'editor.insertSpaces', newValue: true, oldValue: undefined }, { preferenceName: '[go].editor.insertSpaces', newValue: false, oldValue: undefined }], Object.keys(changes).map(key => { const { preferenceName, newValue, oldValue } = changes[key]; return { preferenceName, newValue, oldValue }; }), 'events before'); assert.strictEqual(prefService.get('editor.insertSpaces'), true, 'get before'); assert.strictEqual(prefService.get('[go].editor.insertSpaces'), false, 'get before overridden'); pending = new Promise<PreferenceChanges>(resolve => prefService.onPreferencesChanged(resolve)); toUnset.dispose(); changes = await pending; assert.deepStrictEqual([{ preferenceName: 'editor.insertSpaces', newValue: undefined, oldValue: true }, { preferenceName: '[go].editor.insertSpaces', newValue: undefined, oldValue: false }], Object.keys(changes).map(key => { const { preferenceName, newValue, oldValue } = changes[key]; return { preferenceName, newValue, oldValue }; }), 'events after'); assert.strictEqual(prefService.get('editor.insertSpaces'), undefined, 'get after'); assert.strictEqual(prefService.get('[go].editor.insertSpaces'), undefined, 'get after overridden'); }); function prepareServices(options?: { schema: PreferenceSchema }): { preferences: PreferenceServiceImpl; schema: PreferenceSchemaProvider; } { prefSchema.setSchema(options && options.schema || { properties: { 'editor.tabSize': { type: 'number', description: '', overridable: true, default: 4 } } }); return { preferences: prefService, schema: prefSchema }; } describe('PreferenceService.updateValues()', () => { const TAB_SIZE = 'editor.tabSize'; const DUMMY_URI = 'dummy_uri'; async function generateAndCheckValues( preferences: PreferenceService, globalValue: number | undefined, workspaceValue: number | undefined, workspaceFolderValue: number | undefined ): Promise<void> { await preferences.set(TAB_SIZE, globalValue, PreferenceScope.User); await preferences.set(TAB_SIZE, workspaceValue, PreferenceScope.Workspace); await preferences.set(TAB_SIZE, workspaceFolderValue, PreferenceScope.Folder, DUMMY_URI); const expectedValue = workspaceFolderValue ?? workspaceValue ?? globalValue ?? 4; checkValues(preferences, globalValue, workspaceValue, workspaceFolderValue, expectedValue); } function checkValues( preferences: PreferenceService, globalValue: number | undefined, workspaceValue: number | undefined, workspaceFolderValue: number | undefined, value: number = 4, ): void { const expected = { preferenceName: 'editor.tabSize', defaultValue: 4, globalValue, workspaceValue, workspaceFolderValue, value, }; const inspection = preferences.inspect(TAB_SIZE, DUMMY_URI); assert.deepStrictEqual(inspection, expected); } it('should modify the narrowest scope.', async () => { const { preferences } = prepareServices(); await generateAndCheckValues(preferences, 1, 2, 3); await preferences.updateValue(TAB_SIZE, 8, DUMMY_URI); checkValues(preferences, 1, 2, 8, 8); await generateAndCheckValues(preferences, 1, 2, undefined); await preferences.updateValue(TAB_SIZE, 8, DUMMY_URI); checkValues(preferences, 1, 8, undefined, 8); await generateAndCheckValues(preferences, 1, undefined, undefined); await preferences.updateValue(TAB_SIZE, 8, DUMMY_URI); checkValues(preferences, 8, undefined, undefined, 8); }); it('defaults to user scope.', async () => { const { preferences } = prepareServices(); checkValues(preferences, undefined, undefined, undefined); await preferences.updateValue(TAB_SIZE, 8, DUMMY_URI); checkValues(preferences, 8, undefined, undefined, 8); }); it('clears all settings when input is undefined.', async () => { const { preferences } = prepareServices(); await generateAndCheckValues(preferences, 1, 2, 3); await preferences.updateValue(TAB_SIZE, undefined, DUMMY_URI); checkValues(preferences, undefined, undefined, undefined); }); it('deletes user setting if user is only defined scope and target is default value', async () => { const { preferences } = prepareServices(); await generateAndCheckValues(preferences, 8, undefined, undefined); await preferences.updateValue(TAB_SIZE, 4, DUMMY_URI); checkValues(preferences, undefined, undefined, undefined); }); it('does not delete setting in lower scopes, even if target is default', async () => { const { preferences } = prepareServices(); await generateAndCheckValues(preferences, undefined, 2, undefined); await preferences.updateValue(TAB_SIZE, 4, DUMMY_URI); checkValues(preferences, undefined, 4, undefined); }); }); describe('overridden preferences', () => { it('get #0', () => { const { preferences, schema } = prepareServices(); preferences.set('[json].editor.tabSize', 2, PreferenceScope.User); expect(preferences.get('editor.tabSize')).to.equal(4); expect(preferences.get('[json].editor.tabSize')).to.equal(undefined); schema.registerOverrideIdentifier('json'); expect(preferences.get('editor.tabSize')).to.equal(4); expect(preferences.get('[json].editor.tabSize')).to.equal(2); }); it('get #1', () => { const { preferences, schema } = prepareServices(); schema.registerOverrideIdentifier('json'); expect(preferences.get('editor.tabSize')).to.equal(4); expect(preferences.get('[json].editor.tabSize')).to.equal(4); preferences.set('[json].editor.tabSize', 2, PreferenceScope.User); expect(preferences.get('editor.tabSize')).to.equal(4); expect(preferences.get('[json].editor.tabSize')).to.equal(2); }); it('get #2', () => { const { preferences, schema } = prepareServices(); schema.registerOverrideIdentifier('json'); expect(preferences.get('editor.tabSize')).to.equal(4); expect(preferences.get('[json].editor.tabSize')).to.equal(4); preferences.set('editor.tabSize', 2, PreferenceScope.User); expect(preferences.get('editor.tabSize')).to.equal(2); expect(preferences.get('[json].editor.tabSize')).to.equal(2); }); it('has', () => { const { preferences, schema } = prepareServices(); expect(preferences.has('editor.tabSize')).to.be.true; expect(preferences.has('[json].editor.tabSize')).to.be.false; schema.registerOverrideIdentifier('json'); expect(preferences.has('editor.tabSize')).to.be.true; expect(preferences.has('[json].editor.tabSize')).to.be.true; }); it('inspect #0', () => { const { preferences, schema } = prepareServices(); const expected = { preferenceName: 'editor.tabSize', defaultValue: 4, globalValue: undefined, workspaceValue: undefined, workspaceFolderValue: undefined, value: 4, }; assert.deepStrictEqual(expected, preferences.inspect('editor.tabSize')); assert.ok(!preferences.has('[json].editor.tabSize')); schema.registerOverrideIdentifier('json'); assert.deepStrictEqual(expected, preferences.inspect('editor.tabSize')); assert.deepStrictEqual({ ...expected, preferenceName: '[json].editor.tabSize' }, preferences.inspect('[json].editor.tabSize')); }); it('inspect #1', () => { const { preferences, schema } = prepareServices(); const expected = { preferenceName: 'editor.tabSize', defaultValue: 4, globalValue: 2, workspaceValue: undefined, workspaceFolderValue: undefined, value: 2 }; preferences.set('editor.tabSize', 2, PreferenceScope.User); assert.deepStrictEqual(expected, preferences.inspect('editor.tabSize')); assert.ok(!preferences.has('[json].editor.tabSize')); schema.registerOverrideIdentifier('json'); assert.deepStrictEqual(expected, preferences.inspect('editor.tabSize')); assert.deepStrictEqual({ ...expected, preferenceName: '[json].editor.tabSize' }, preferences.inspect('[json].editor.tabSize')); }); it('inspect #2', () => { const { preferences, schema } = prepareServices(); const expected = { preferenceName: 'editor.tabSize', defaultValue: 4, globalValue: undefined, workspaceValue: undefined, workspaceFolderValue: undefined, value: 4 }; assert.deepStrictEqual(expected, preferences.inspect('editor.tabSize')); assert.ok(!preferences.has('[json].editor.tabSize')); schema.registerOverrideIdentifier('json'); preferences.set('[json].editor.tabSize', 2, PreferenceScope.User); assert.deepStrictEqual(expected, preferences.inspect('editor.tabSize')); assert.deepStrictEqual({ ...expected, preferenceName: '[json].editor.tabSize', globalValue: 2, value: 2, }, preferences.inspect('[json].editor.tabSize')); }); it('onPreferenceChanged #0', async () => { const { preferences, schema } = prepareServices(); const events: PreferenceChange[] = []; preferences.onPreferenceChanged(event => events.push(event)); schema.registerOverrideIdentifier('json'); preferences.set('[json].editor.tabSize', 2, PreferenceScope.User); await preferences.set('editor.tabSize', 3, PreferenceScope.User); assert.deepStrictEqual([{ preferenceName: '[json].editor.tabSize', newValue: 2 }, { preferenceName: 'editor.tabSize', newValue: 3 }], events.map(e => ({ preferenceName: e.preferenceName, newValue: e.newValue }))); }); it('onPreferenceChanged #1', async () => { const { preferences, schema } = prepareServices(); const events: PreferenceChange[] = []; preferences.onPreferenceChanged(event => events.push(event)); schema.registerOverrideIdentifier('json'); await preferences.set('editor.tabSize', 2, PreferenceScope.User); assert.deepStrictEqual([{ preferenceName: 'editor.tabSize', newValue: 2 }, { preferenceName: '[json].editor.tabSize', newValue: 2 }], events.map(e => ({ preferenceName: e.preferenceName, newValue: e.newValue }))); }); it('onPreferenceChanged #2', async function (): Promise<void> { const { preferences, schema } = prepareServices(); schema.registerOverrideIdentifier('json'); schema.registerOverrideIdentifier('javascript'); preferences.set('[json].editor.tabSize', 2, PreferenceScope.User); await preferences.set('editor.tabSize', 3, PreferenceScope.User); const events: PreferenceChangeEvent<{ [key: string]: any }>[] = []; const proxy = createPreferenceProxy<{ [key: string]: any }>(preferences, schema.getCombinedSchema(), { overrideIdentifier: 'json' }); proxy.onPreferenceChanged(event => events.push(event)); await preferences.set('[javascript].editor.tabSize', 4, PreferenceScope.User); assert.deepStrictEqual([], events.map(e => ({ preferenceName: e.preferenceName, newValue: e.newValue })), 'changes not relevant to json override should be ignored'); }); it('onPreferenceChanged #3', async () => { const { preferences, schema } = prepareServices(); schema.registerOverrideIdentifier('json'); preferences.set('[json].editor.tabSize', 2, PreferenceScope.User); await preferences.set('editor.tabSize', 3, PreferenceScope.User); const events: PreferenceChange[] = []; preferences.onPreferenceChanged(event => events.push(event)); await preferences.set('[json].editor.tabSize', undefined, PreferenceScope.User); assert.deepStrictEqual([{ preferenceName: '[json].editor.tabSize', newValue: 3 }], events.map(e => ({ preferenceName: e.preferenceName, newValue: e.newValue }))); }); it('defaultOverrides [go].editor.formatOnSave', () => { const { preferences, schema } = prepareServices({ schema: { properties: { 'editor.insertSpaces': { type: 'boolean', default: true, overridable: true }, 'editor.formatOnSave': { type: 'boolean', default: false, overridable: true } } } }); assert.strictEqual(true, preferences.get('editor.insertSpaces')); assert.strictEqual(undefined, preferences.get('[go].editor.insertSpaces')); assert.strictEqual(false, preferences.get('editor.formatOnSave')); assert.strictEqual(undefined, preferences.get('[go].editor.formatOnSave')); schema.registerOverrideIdentifier('go'); schema.setSchema({ id: 'defaultOverrides', title: 'Default Configuration Overrides', properties: { '[go]': { type: 'object', default: { 'editor.insertSpaces': false, 'editor.formatOnSave': true }, description: 'Configure editor settings to be overridden for go language.' } } }); assert.strictEqual(true, preferences.get('editor.insertSpaces')); assert.strictEqual(false, preferences.get('[go].editor.insertSpaces')); assert.strictEqual(false, preferences.get('editor.formatOnSave')); assert.strictEqual(true, preferences.get('[go].editor.formatOnSave')); }); }); });