Skip to content

Commit

Permalink
core: lazy preference proxy
Browse files Browse the repository at this point in the history
It is possible to register a preference schema to the
`PreferenceSchemaProvider` after the initial inversify binding phase.
This is problematic when wanting to do that late schema registration and
at the same time trying to bind a preference proxy because proxies must
be bound early on.

This commit fixes the issue by allowing `createPreferenceProxy` to
process a promise for its `schema` parameter (now `promisedSchema`) and
still return a proxy synchronously. Until the promise resolves, most
operations on the proxy will be no-ops.

Signed-off-by: Paul Maréchal <paul.marechal@ericsson.com>
  • Loading branch information
paul-marechal committed Mar 9, 2021
1 parent 159f175 commit b9344a9
Show file tree
Hide file tree
Showing 2 changed files with 262 additions and 187 deletions.
315 changes: 187 additions & 128 deletions packages/core/src/browser/preferences/preference-proxy.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,167 +73,226 @@ describe('Preference Proxy', () => {
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(() => {
});

// Actually run the test suite with different parameters:
testPreferenceProxy('Synchronous Schema Definition', { asyncSchema: false });
testPreferenceProxy('Asynchronous Schema Definition (1s delay)', { asyncSchema: true });

function getProvider(scope: PreferenceScope): MockPreferenceProvider {
return testContainer.getNamed(PreferenceProvider, scope) as MockPreferenceProvider;
}

function getProxy(schema?: PreferenceSchema, options?: PreferenceProxyOptions): PreferenceProxy<{ [key: string]: any }> {
const s: PreferenceSchema = schema || {
properties: {
'my.pref': {
type: 'string',
defaultValue: 'foo'
function testPreferenceProxy(testDescription: string, testOptions: { asyncSchema: boolean }): void {

describe(testDescription, () => {

function getProxy(schema?: PreferenceSchema, options?: PreferenceProxyOptions): {
proxy: PreferenceProxy<{ [key: string]: any }>,
/** Only set if we are using a schema asynchronously. */
promisedSchema?: Promise<PreferenceSchema>
} {
const s: PreferenceSchema = schema || {
properties: {
'my.pref': {
type: 'string',
defaultValue: 'foo'
}
}
};
if (testOptions.asyncSchema) {
const promisedSchema = new Promise<PreferenceSchema>(resolve => setTimeout(() => {
prefSchema.setSchema(s);
resolve(s);
}, 1000));
const proxy = createPreferenceProxy(prefService, promisedSchema, options);
return { proxy, promisedSchema };
} else {
prefSchema.setSchema(s);
const proxy = createPreferenceProxy(prefService, s, options);
return { proxy };
}
}
};
prefSchema.setSchema(s);
return createPreferenceProxy(prefService, s, options);
}

it('by default, it should get provide access in flat style but not deep', () => {
const proxy = getProxy();
expect(proxy['my.pref']).to.equal('foo');
expect(proxy.my).to.equal(undefined);
expect(Object.keys(proxy).join()).to.equal(['my.pref'].join());
});
if (testOptions.asyncSchema) {
it('using the proxy before the schema is set should be no-op', async () => {
const { proxy } = getProxy();
let changed = 0;
proxy.onPreferenceChanged(event => {
changed += 1;
});
expect(proxy['my.pref']).to.equal(undefined);
expect(proxy.my).to.equal(undefined);
expect(Object.keys(proxy).length).to.equal(0);
// The proxy doesn't know the schema, so events shouldn't be forwarded:
await getProvider(PreferenceScope.User).setPreference('my.pref', 'foo');
expect(changed).to.equal(0);
expect(proxy['my.pref']).to.equal(undefined);
expect(proxy.my).to.equal(undefined);
expect(Object.keys(proxy).length).to.equal(0);
});
}

it('it should get provide access in deep style but not flat', () => {
const proxy = getProxy(undefined, { style: 'deep' });
expect(proxy['my.pref']).to.equal(undefined);
expect(proxy.my.pref).to.equal('foo');
expect(Object.keys(proxy).join()).to.equal(['my'].join());
});
it('by default, it should get provide access in flat style but not deep', async () => {
const { proxy, promisedSchema } = getProxy();
if (promisedSchema) {
await promisedSchema;
}
expect(proxy['my.pref']).to.equal('foo');
expect(proxy.my).to.equal(undefined);
expect(Object.keys(proxy).join()).to.equal(['my.pref'].join());
});

it('it should get provide access in to both styles', () => {
const proxy = getProxy(undefined, { style: 'both' });
expect(proxy['my.pref']).to.equal('foo');
expect(proxy.my.pref).to.equal('foo');
expect(Object.keys(proxy).join()).to.equal(['my', 'my.pref'].join());
});
it('it should get provide access in deep style but not flat', async () => {
const { proxy, promisedSchema } = getProxy(undefined, { style: 'deep' });
if (promisedSchema) {
await promisedSchema;
}
expect(proxy['my.pref']).to.equal(undefined);
expect(proxy.my.pref).to.equal('foo');
expect(Object.keys(proxy).join()).to.equal(['my'].join());
});

it('it should forward change events', async () => {
const proxy = getProxy(undefined, { style: 'both' });
let theChange: PreferenceChangeEvent<{ [key: string]: any }>;
proxy.onPreferenceChanged(change => {
expect(theChange).to.equal(undefined);
theChange = change;
});
let theSecondChange: PreferenceChangeEvent<{ [key: string]: any }>;
(proxy.my as PreferenceProxy<{ [key: string]: any }>).onPreferenceChanged(change => {
expect(theSecondChange).to.equal(undefined);
theSecondChange = change;
});
it('it should get provide access in to both styles', async () => {
const { proxy, promisedSchema } = getProxy(undefined, { style: 'both' });
if (promisedSchema) {
await promisedSchema;
}
expect(proxy['my.pref']).to.equal('foo');
expect(proxy.my.pref).to.equal('foo');
expect(Object.keys(proxy).join()).to.equal(['my', 'my.pref'].join());
});

await getProvider(PreferenceScope.User).setPreference('my.pref', 'bar');
it('it should forward change events', async () => {
const { proxy, promisedSchema } = getProxy(undefined, { style: 'both' });
if (promisedSchema) {
await promisedSchema;
}
let theChange: PreferenceChangeEvent<{ [key: string]: any }>;
proxy.onPreferenceChanged(change => {
expect(theChange).to.equal(undefined);
theChange = change;
});
let theSecondChange: PreferenceChangeEvent<{ [key: string]: any }>;
(proxy.my as PreferenceProxy<{ [key: string]: any }>).onPreferenceChanged(change => {
expect(theSecondChange).to.equal(undefined);
theSecondChange = change;
});

expect(theChange!.newValue).to.equal('bar');
expect(theChange!.oldValue).to.equal(undefined);
expect(theChange!.preferenceName).to.equal('my.pref');
expect(theSecondChange!.newValue).to.equal('bar');
expect(theSecondChange!.oldValue).to.equal(undefined);
expect(theSecondChange!.preferenceName).to.equal('my.pref');
});
await getProvider(PreferenceScope.User).setPreference('my.pref', 'bar');

it('toJSON with deep', () => {
const proxy = getProxy({
properties: {
'foo.baz': {
type: 'number',
default: 4
},
'foo.bar.x': {
type: 'boolean',
default: true
},
'foo.bar.y': {
type: 'boolean',
default: false
},
'a': {
type: 'string',
default: 'a'
}
}
}, { style: 'deep' });
assert.deepStrictEqual(JSON.stringify(proxy, undefined, 2), JSON.stringify({
foo: {
baz: 4,
bar: {
x: true,
y: false
}
},
a: 'a'
}, undefined, 2), 'there should not be foo.bar.x to avoid sending excessive data to remote clients');
});
expect(theChange!.newValue).to.equal('bar');
expect(theChange!.oldValue).to.equal(undefined);
expect(theChange!.preferenceName).to.equal('my.pref');
expect(theSecondChange!.newValue).to.equal('bar');
expect(theSecondChange!.oldValue).to.equal(undefined);
expect(theSecondChange!.preferenceName).to.equal('my.pref');
});

it('get nested default', () => {
const proxy = getProxy({
properties: {
'foo': {
'anyOf': [
{
'enum': [
false
]
it('toJSON with deep', async () => {
const { proxy, promisedSchema } = getProxy({
properties: {
'foo.baz': {
type: 'number',
default: 4
},
{
'properties': {
'bar': {
'anyOf': [
{
'enum': [
false
]
},
{
'properties': {
'x': {
type: 'boolean'
'foo.bar.x': {
type: 'boolean',
default: true
},
'foo.bar.y': {
type: 'boolean',
default: false
},
'a': {
type: 'string',
default: 'a'
}
}
}, { style: 'deep' });
if (promisedSchema) {
await promisedSchema;
}
assert.deepStrictEqual(JSON.stringify(proxy, undefined, 2), JSON.stringify({
foo: {
baz: 4,
bar: {
x: true,
y: false
}
},
a: 'a'
}, undefined, 2), 'there should not be foo.bar.x to avoid sending excessive data to remote clients');
});

it('get nested default', async () => {
const { proxy, promisedSchema } = getProxy({
properties: {
'foo': {
'anyOf': [
{
'enum': [
false
]
},
{
'properties': {
'bar': {
'anyOf': [
{
'enum': [
false
]
},
'y': {
type: 'boolean'
{
'properties': {
'x': {
type: 'boolean'
},
'y': {
type: 'boolean'
}
}
}
}
]
}
]
}
}
],
default: {
bar: {
x: true,
y: false
}
}
}
],
default: {
bar: {
x: true,
y: false
}
}
}, { style: 'both' });
if (promisedSchema) {
await promisedSchema;
}
}
}, { style: 'both' });
assert.deepStrictEqual(proxy['foo'], {
bar: {
x: true,
y: false
}
});
assert.deepStrictEqual(proxy['foo.bar'], {
x: true,
y: false
assert.deepStrictEqual(proxy['foo'], {
bar: {
x: true,
y: false
}
});
assert.deepStrictEqual(proxy['foo.bar'], {
x: true,
y: false
});
assert.strictEqual(proxy['foo.bar.x'], true);
assert.strictEqual(proxy['foo.bar.y'], false);
});
});
assert.strictEqual(proxy['foo.bar.x'], true);
assert.strictEqual(proxy['foo.bar.y'], false);
});
}

});
Loading

0 comments on commit b9344a9

Please sign in to comment.