UNPKG

17.8 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 } 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 { PreferenceProxyOptions, PreferenceProxy, PreferenceChangeEvent, createPreferenceProxy } from './preference-proxy';
33import { PreferenceProxyFactory } from './injectable-preference-proxy';
34import { waitForEvent } from '../../common/promise-util';
35
36disableJSDOM();
37
38process.on('unhandledRejection', (reason, promise) => {
39 console.error(reason);
40 throw reason;
41});
42
43import { expect } from 'chai';
44let testContainer: Container;
45
46function createTestContainer(): Container {
47 const result = new Container();
48 bindPreferenceService(result.bind.bind(result));
49 bindMockPreferenceProviders(result.bind.bind(result), result.unbind.bind(result));
50 return result;
51}
52
53describe('Preference Proxy', () => {
54 let prefService: PreferenceServiceImpl;
55 let prefSchema: PreferenceSchemaProvider;
56
57 before(() => {
58 disableJSDOM = enableJSDOM();
59 FrontendApplicationConfigProvider.set({});
60 });
61
62 after(() => {
63 disableJSDOM();
64 });
65
66 beforeEach(async () => {
67 testContainer = createTestContainer();
68 prefSchema = testContainer.get(PreferenceSchemaProvider);
69 prefService = testContainer.get<PreferenceService>(PreferenceService) as PreferenceServiceImpl;
70 getProvider(PreferenceScope.User).markReady();
71 getProvider(PreferenceScope.Workspace).markReady();
72 getProvider(PreferenceScope.Folder).markReady();
73 try {
74 await prefService.ready;
75 } catch (e) {
76 console.error(e);
77 }
78 });
79
80 afterEach(() => {
81 });
82
83 // Actually run the test suite with different parameters:
84 testPreferenceProxy('Synchronous Schema Definition + createPreferenceProxy', { asyncSchema: false });
85 testPreferenceProxy('Asynchronous Schema Definition (1s delay) + createPreferenceProxy', { asyncSchema: true });
86 testPreferenceProxy('Synchronous Schema Definition + Injectable Preference Proxy', { asyncSchema: false, useFactory: true });
87 testPreferenceProxy('Asynchronous Schema Definition (1s delay) + Injectable Preference Proxy', { asyncSchema: true, useFactory: true });
88
89 function getProvider(scope: PreferenceScope): MockPreferenceProvider {
90 return testContainer.getNamed(PreferenceProvider, scope) as MockPreferenceProvider;
91 }
92
93 function testPreferenceProxy(testDescription: string, testOptions: { asyncSchema: boolean, useFactory?: boolean }): void {
94
95 describe(testDescription, () => {
96
97 function getProxy(schema?: PreferenceSchema, options?: PreferenceProxyOptions): {
98 proxy: PreferenceProxy<{ [key: string]: any }>,
99 /** Only set if we are using a schema asynchronously. */
100 promisedSchema?: Promise<PreferenceSchema>
101 } {
102 const s: PreferenceSchema = schema || {
103 properties: {
104 'my.pref': {
105 type: 'string',
106 defaultValue: 'foo'
107 }
108 }
109 };
110 if (testOptions.asyncSchema) {
111 const promisedSchema = new Promise<PreferenceSchema>(resolve => setTimeout(() => {
112 prefSchema.setSchema(s);
113 resolve(s);
114 }, 1000));
115 const proxy = testOptions.useFactory
116 ? testContainer.get<PreferenceProxyFactory>(PreferenceProxyFactory)(promisedSchema, options)
117 : createPreferenceProxy(prefService, promisedSchema, options);
118 return { proxy, promisedSchema };
119 } else {
120 prefSchema.setSchema(s);
121 const proxy = testOptions.useFactory
122 ? testContainer.get<PreferenceProxyFactory>(PreferenceProxyFactory)(s, options)
123 : createPreferenceProxy(prefService, s, options);
124 return { proxy };
125 }
126 }
127
128 if (testOptions.asyncSchema) {
129 it('using the proxy before the schema is set should be no-op', async () => {
130 const { proxy, promisedSchema } = getProxy();
131 let changed = 0;
132 proxy.onPreferenceChanged(event => {
133 changed += 1;
134 });
135 expect(proxy['my.pref']).to.equal(undefined);
136 expect(Object.keys(proxy).length).to.equal(0);
137 // The proxy doesn't know the schema, so events shouldn't be forwarded:
138 await getProvider(PreferenceScope.User).setPreference('my.pref', 'bar');
139 expect(changed).to.equal(0);
140 expect(proxy['my.pref']).to.equal(undefined);
141 expect(Object.keys(proxy).length).to.equal(0);
142 // Once the schema is resolved, operations should be working:
143 await promisedSchema!;
144 expect(proxy['my.pref']).to.equal('bar');
145 expect(Object.keys(proxy)).members(['my.pref']);
146 await getProvider(PreferenceScope.User).setPreference('my.pref', 'fizz');
147 expect(changed).to.equal(1);
148 expect(proxy['my.pref']).to.equal('fizz');
149
150 });
151 }
152
153 it('by default, it should provide access in flat style but not deep', async () => {
154 const { proxy, promisedSchema } = getProxy();
155 if (promisedSchema) {
156 await promisedSchema;
157 }
158 expect(proxy['my.pref']).to.equal('foo');
159 expect(proxy.my).to.equal(undefined);
160 expect(Object.keys(proxy).join()).to.equal(['my.pref'].join());
161 });
162
163 it('it should provide access in deep style but not flat', async () => {
164 const { proxy, promisedSchema } = getProxy(undefined, { style: 'deep' });
165 if (promisedSchema) {
166 await promisedSchema;
167 }
168 expect(proxy['my.pref']).to.equal(undefined);
169 expect(proxy.my.pref).to.equal('foo');
170 expect(Object.keys(proxy).join()).equal('my');
171 });
172
173 it('it should provide access in to both styles', async () => {
174 const { proxy, promisedSchema } = getProxy(undefined, { style: 'both' });
175 if (promisedSchema) {
176 await promisedSchema;
177 }
178 expect(proxy['my.pref']).to.equal('foo');
179 expect(proxy.my.pref).to.equal('foo');
180 expect(Object.keys(proxy).join()).to.equal(['my', 'my.pref'].join());
181 });
182
183 it('it should forward change events', async () => {
184 const { proxy, promisedSchema } = getProxy(undefined, { style: 'both' });
185 if (promisedSchema) {
186 await promisedSchema;
187 }
188 let theChange: PreferenceChangeEvent<{ [key: string]: any }>;
189 proxy.onPreferenceChanged(change => {
190 expect(theChange).to.equal(undefined);
191 theChange = change;
192 });
193 let theSecondChange: PreferenceChangeEvent<{ [key: string]: any }>;
194 (proxy.my as PreferenceProxy<{ [key: string]: any }>).onPreferenceChanged(change => {
195 expect(theSecondChange).to.equal(undefined);
196 theSecondChange = change;
197 });
198
199 await getProvider(PreferenceScope.User).setPreference('my.pref', 'bar');
200
201 expect(theChange!.newValue).to.equal('bar');
202 expect(theChange!.oldValue).to.equal(undefined);
203 expect(theChange!.preferenceName).to.equal('my.pref');
204 expect(theSecondChange!.newValue).to.equal('bar');
205 expect(theSecondChange!.oldValue).to.equal(undefined);
206 expect(theSecondChange!.preferenceName).to.equal('my.pref');
207 });
208
209 it("should not forward changes that don't match the proxy's language override", async () => {
210 const { proxy, promisedSchema } = getProxy({
211 properties: {
212 'my.pref': {
213 type: 'string',
214 defaultValue: 'foo',
215 overridable: true,
216 }
217 }
218 }, { style: 'both', overrideIdentifier: 'typescript' });
219 await promisedSchema;
220 let changeEventsEmittedByProxy = 0;
221 let changeEventsEmittedByService = 0;
222 prefSchema.registerOverrideIdentifier('swift');
223 prefSchema.registerOverrideIdentifier('typescript');
224 // The service will emit events related to updating the overrides - those are irrelevant
225 await waitForEvent(prefService.onPreferencesChanged, 500);
226 prefService.onPreferencesChanged(() => changeEventsEmittedByService++);
227 proxy.onPreferenceChanged(() => changeEventsEmittedByProxy++);
228 await prefService.set(prefService.overridePreferenceName({ overrideIdentifier: 'swift', preferenceName: 'my.pref' }), 'boo', PreferenceScope.User);
229 expect(changeEventsEmittedByService, 'The service should have emitted an event for the non-matching override.').to.equal(1);
230 expect(changeEventsEmittedByProxy, 'The proxy should not have emitted an event for the non-matching override.').to.equal(0);
231 await prefService.set('my.pref', 'far', PreferenceScope.User);
232 expect(changeEventsEmittedByService, 'The service should have emitted an event for the base name.').to.equal(2);
233 expect(changeEventsEmittedByProxy, 'The proxy should have emitted for an event for the base name.').to.equal(1);
234 await prefService.set(prefService.overridePreferenceName({ preferenceName: 'my.pref', overrideIdentifier: 'typescript' }), 'faz', PreferenceScope.User);
235 expect(changeEventsEmittedByService, 'The service should have emitted an event for the matching override.').to.equal(3);
236 expect(changeEventsEmittedByProxy, 'The proxy should have emitted an event for the matching override.').to.equal(2);
237 await prefService.set('my.pref', 'yet another value', PreferenceScope.User);
238 expect(changeEventsEmittedByService, 'The service should have emitted another event for the base name.').to.equal(4);
239 expect(changeEventsEmittedByProxy, 'The proxy should not have emitted an event, because the value for TS has been overridden.').to.equal(2);
240 });
241
242 it('`affects` should only return `true` if the language overrides match', async () => {
243 const { proxy, promisedSchema } = getProxy({
244 properties: {
245 'my.pref': {
246 type: 'string',
247 defaultValue: 'foo',
248 overridable: true,
249 }
250 }
251 }, { style: 'both' });
252 await promisedSchema;
253 prefSchema.registerOverrideIdentifier('swift');
254 prefSchema.registerOverrideIdentifier('typescript');
255 let changesNotAffectingTypescript = 0;
256 let changesAffectingTypescript = 0;
257 proxy.onPreferenceChanged(change => {
258 if (change.affects(undefined, 'typescript')) {
259 changesAffectingTypescript++;
260 } else {
261 changesNotAffectingTypescript++;
262 }
263 });
264 await prefService.set('my.pref', 'bog', PreferenceScope.User);
265 expect(changesNotAffectingTypescript, 'Two events (one for `my.pref` and one for `[swift].my.pref`) should not have affected TS').to.equal(2);
266 expect(changesAffectingTypescript, 'One event should have been fired that does affect typescript.').to.equal(1);
267 });
268
269 it('toJSON with deep', async () => {
270 const { proxy, promisedSchema } = getProxy({
271 properties: {
272 'foo.baz': {
273 type: 'number',
274 default: 4
275 },
276 'foo.bar.x': {
277 type: 'boolean',
278 default: true
279 },
280 'foo.bar.y': {
281 type: 'boolean',
282 default: false
283 },
284 'a': {
285 type: 'string',
286 default: 'a'
287 }
288 }
289 }, { style: 'deep' });
290 if (promisedSchema) {
291 await promisedSchema;
292 }
293 assert.deepStrictEqual(JSON.stringify(proxy, undefined, 2), JSON.stringify({
294 foo: {
295 baz: 4,
296 bar: {
297 x: true,
298 y: false
299 }
300 },
301 a: 'a'
302 }, undefined, 2), 'there should not be foo.bar.x to avoid sending excessive data to remote clients');
303 });
304
305 it('get nested default', async () => {
306 const { proxy, promisedSchema } = getProxy({
307 properties: {
308 'foo': {
309 'anyOf': [
310 {
311 'enum': [
312 false
313 ]
314 },
315 {
316 'properties': {
317 'bar': {
318 'anyOf': [
319 {
320 'enum': [
321 false
322 ]
323 },
324 {
325 'properties': {
326 'x': {
327 type: 'boolean'
328 },
329 'y': {
330 type: 'boolean'
331 }
332 }
333 }
334 ]
335 }
336 }
337 }
338 ],
339 default: {
340 bar: {
341 x: true,
342 y: false
343 }
344 }
345 }
346 }
347 }, { style: 'both' });
348 if (promisedSchema) {
349 await promisedSchema;
350 }
351 assert.deepStrictEqual(proxy['foo'], {
352 bar: {
353 x: true,
354 y: false
355 }
356 });
357 assert.deepStrictEqual(proxy['foo.bar'], {
358 x: true,
359 y: false
360 });
361 assert.strictEqual(proxy['foo.bar.x'], true);
362 assert.strictEqual(proxy['foo.bar.y'], false);
363 });
364 });
365 }
366
367});
368
\No newline at end of file