Wait for plugin endpoint params to be defined
Wait for all endpoint params to be set (not `undefined`) when attaching plugin-provided element to the DOM at the plugin extension endpoint. Practically, this ensures that all endpoint params are set for plugin modules. Change-Id: I8b2be9fe76eba27b9976d48867594c492e1b54e5
This commit is contained in:
@@ -14,6 +14,8 @@
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
const INIT_PROPERTIES_TIMEOUT_MS = 10000;
|
||||
|
||||
Polymer({
|
||||
is: 'gr-endpoint-decorator',
|
||||
|
||||
@@ -40,59 +42,73 @@
|
||||
|
||||
_initDecoration(name, plugin) {
|
||||
const el = document.createElement(name);
|
||||
this._initProperties(el, plugin, this.getContentChildren().find(
|
||||
el => el.nodeName !== 'GR-ENDPOINT-PARAM'));
|
||||
this._appendChild(el);
|
||||
return el;
|
||||
return this._initProperties(el, plugin,
|
||||
this.getContentChildren().find(
|
||||
el => el.nodeName !== 'GR-ENDPOINT-PARAM'))
|
||||
.then(el => this._appendChild(el));
|
||||
},
|
||||
|
||||
_initReplacement(name, plugin) {
|
||||
this.getContentChildNodes().forEach(node => node.remove());
|
||||
this.getContentChildNodes()
|
||||
.filter(node => node.nodeName !== 'GR-ENDPOINT-PARAM')
|
||||
.forEach(node => node.remove());
|
||||
const el = document.createElement(name);
|
||||
this._initProperties(el, plugin);
|
||||
this._appendChild(el);
|
||||
return el;
|
||||
return this._initProperties(el, plugin).then(
|
||||
el => this._appendChild(el));
|
||||
},
|
||||
|
||||
_getEndpointParams() {
|
||||
return Polymer.dom(this).querySelectorAll('gr-endpoint-param').map(el => {
|
||||
return {name: el.getAttribute('name'), value: el.value};
|
||||
});
|
||||
return Polymer.dom(this).querySelectorAll('gr-endpoint-param');
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {!Element} el
|
||||
* @param {!Object} plugin
|
||||
* @param {!Element=} opt_content
|
||||
* @return {!Promise<Element>}
|
||||
*/
|
||||
_initProperties(el, plugin, opt_content) {
|
||||
el.plugin = plugin;
|
||||
if (opt_content) {
|
||||
el.content = opt_content;
|
||||
}
|
||||
for (const {name, value} of this._getEndpointParams()) {
|
||||
el[name] = value;
|
||||
}
|
||||
const expectProperties = this._getEndpointParams().map(
|
||||
paramEl => plugin.attributeHelper(paramEl).get('value')
|
||||
.then(value => el[paramEl.getAttribute('name')] = value)
|
||||
);
|
||||
const timeout = new Promise(
|
||||
resolve => setTimeout(() => {
|
||||
console.warn(
|
||||
'Timeout waiting for endpoint properties initialization.' +
|
||||
`plugin ${plugin.getPluginName()}, endpoint ${this.name}`);
|
||||
resolve();
|
||||
}, INIT_PROPERTIES_TIMEOUT_MS));
|
||||
return Promise.race([timeout, Promise.all(expectProperties)])
|
||||
.then(() => el);
|
||||
},
|
||||
|
||||
_appendChild(el) {
|
||||
Polymer.dom(this.root).appendChild(el);
|
||||
return Polymer.dom(this.root).appendChild(el);
|
||||
},
|
||||
|
||||
_initModule({moduleName, plugin, type, domHook}) {
|
||||
let el;
|
||||
let initPromise;
|
||||
switch (type) {
|
||||
case 'decorate':
|
||||
el = this._initDecoration(moduleName, plugin);
|
||||
initPromise = this._initDecoration(moduleName, plugin);
|
||||
break;
|
||||
case 'replace':
|
||||
el = this._initReplacement(moduleName, plugin);
|
||||
initPromise = this._initReplacement(moduleName, plugin);
|
||||
break;
|
||||
}
|
||||
if (el) {
|
||||
domHook.handleInstanceAttached(el);
|
||||
if (!initPromise) {
|
||||
console.warn('Unable to initialize module' +
|
||||
`${moduleName} from ${plugin.getPluginName()}`);
|
||||
}
|
||||
this._domHooks.set(el, domHook);
|
||||
initPromise.then(el => {
|
||||
domHook.handleInstanceAttached(el);
|
||||
this._domHooks.set(el, domHook);
|
||||
});
|
||||
},
|
||||
|
||||
ready() {
|
||||
|
||||
@@ -28,48 +28,45 @@ limitations under the License.
|
||||
|
||||
<test-fixture id="basic">
|
||||
<template>
|
||||
<gr-endpoint-decorator name="foo">
|
||||
<gr-endpoint-param name="someparam" value="barbar"></gr-endpoint-param>
|
||||
</gr-endpoint-decorator>
|
||||
<div>
|
||||
<gr-endpoint-decorator name="first">
|
||||
<gr-endpoint-param name="someparam" value="barbar"></gr-endpoint-param>
|
||||
</gr-endpoint-decorator>
|
||||
<gr-endpoint-decorator name="second">
|
||||
<gr-endpoint-param name="someparam" value="foofoo"></gr-endpoint-param>
|
||||
</gr-endpoint-decorator>
|
||||
<gr-endpoint-decorator name="banana">
|
||||
<gr-endpoint-param name="someParam" value="yes"></gr-endpoint-param>
|
||||
</gr-endpoint-decorator>
|
||||
</div>
|
||||
</template>
|
||||
</test-fixture>
|
||||
|
||||
<script>
|
||||
suite('gr-endpoint-decorator', () => {
|
||||
let container;
|
||||
let sandbox;
|
||||
let element;
|
||||
let plugin;
|
||||
let domHookStub;
|
||||
let decorationHook;
|
||||
let replacementHook;
|
||||
|
||||
setup(done => {
|
||||
Gerrit._endpoints = new GrPluginEndpoints();
|
||||
|
||||
sandbox = sinon.sandbox.create();
|
||||
|
||||
domHookStub = {
|
||||
handleInstanceAttached: sandbox.stub(),
|
||||
handleInstanceDetached: sandbox.stub(),
|
||||
getPublicAPI: () => domHookStub,
|
||||
};
|
||||
sandbox.stub(
|
||||
GrDomHooksManager.prototype, 'getDomHook').returns(domHookStub);
|
||||
|
||||
// NB: Order is important.
|
||||
Gerrit.install(p => {
|
||||
plugin = p;
|
||||
plugin.registerCustomComponent('foo', 'some-module');
|
||||
plugin.registerCustomComponent('foo', 'other-module', {replace: true});
|
||||
plugin.registerCustomComponent('bar', 'some-module');
|
||||
}, '0.1', 'http://some/plugin/url.html');
|
||||
|
||||
sandbox.stub(Gerrit, '_arePluginsLoaded').returns(true);
|
||||
sandbox.stub(Gerrit, 'awaitPluginsLoaded').returns(Promise.resolve());
|
||||
|
||||
element = fixture('basic');
|
||||
sandbox.stub(element, '_initDecoration').returns({});
|
||||
sandbox.stub(element, '_initReplacement').returns({});
|
||||
sandbox.stub(element, 'importHref', (url, resolve) => resolve());
|
||||
|
||||
stub('gr-endpoint-decorator', {
|
||||
_import: sandbox.stub().returns(Promise.resolve()),
|
||||
});
|
||||
// Since _endpoints are global, must reset state.
|
||||
Gerrit._endpoints = new GrPluginEndpoints();
|
||||
container = fixture('basic');
|
||||
Gerrit.install(p => plugin = p, '0.1', 'http://some/plugin/url.html');
|
||||
hooks = [];
|
||||
// Decoration
|
||||
decorationHook = plugin.registerCustomComponent('first', 'some-module');
|
||||
// Replacement
|
||||
replacementHook = plugin.registerCustomComponent(
|
||||
'second', 'other-module', {replace: true});
|
||||
// Mimic all plugins loaded.
|
||||
Gerrit._setPluginsCount(0);
|
||||
flush(done);
|
||||
});
|
||||
|
||||
@@ -77,51 +74,79 @@ limitations under the License.
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
test('imports plugin-provided module', () => {
|
||||
assert.isTrue(
|
||||
element.importHref.calledWith(new URL('http://some/plugin/url.html')));
|
||||
test('imports plugin-provided modules into endpoints', () => {
|
||||
const endpoints =
|
||||
Array.from(container.querySelectorAll('gr-endpoint-decorator'));
|
||||
assert.equal(endpoints.length, 3);
|
||||
endpoints.forEach(element => {
|
||||
assert.isTrue(
|
||||
element._import.calledWith(new URL('http://some/plugin/url.html')));
|
||||
});
|
||||
});
|
||||
|
||||
test('inits decoration dom hook', () => {
|
||||
assert.strictEqual(
|
||||
element._initDecoration.lastCall.args[0], 'some-module');
|
||||
assert.strictEqual(
|
||||
element._initDecoration.lastCall.args[1], plugin);
|
||||
test('decoration', () => {
|
||||
const element =
|
||||
container.querySelector('gr-endpoint-decorator[name="first"]');
|
||||
const module = Polymer.dom(element.root).children.find(
|
||||
element => element.nodeName === 'SOME-MODULE');
|
||||
assert.isOk(module);
|
||||
assert.equal(module['someparam'], 'barbar');
|
||||
return decorationHook.getLastAttached().then(element => {
|
||||
assert.strictEqual(element, module);
|
||||
}).then(() => {
|
||||
element.remove();
|
||||
assert.equal(decorationHook.getAllAttached().length, 0);
|
||||
});
|
||||
});
|
||||
|
||||
test('inits replacement dom hook', () => {
|
||||
assert.strictEqual(
|
||||
element._initReplacement.lastCall.args[0], 'other-module');
|
||||
assert.strictEqual(
|
||||
element._initReplacement.lastCall.args[1], plugin);
|
||||
test('replacement', () => {
|
||||
const element =
|
||||
container.querySelector('gr-endpoint-decorator[name="second"]');
|
||||
const module = Polymer.dom(element.root).children.find(
|
||||
element => element.nodeName === 'OTHER-MODULE');
|
||||
assert.isOk(module);
|
||||
assert.equal(module['someparam'], 'foofoo');
|
||||
return replacementHook.getLastAttached().then(element => {
|
||||
assert.strictEqual(element, module);
|
||||
}).then(() => {
|
||||
element.remove();
|
||||
assert.equal(replacementHook.getAllAttached().length, 0);
|
||||
});
|
||||
});
|
||||
|
||||
test('calls dom hook handleInstanceAttached', () => {
|
||||
assert.equal(domHookStub.handleInstanceAttached.callCount, 2);
|
||||
});
|
||||
|
||||
test('calls dom hook handleInstanceDetached', () => {
|
||||
element.detached();
|
||||
assert.equal(domHookStub.handleInstanceDetached.callCount, 2);
|
||||
});
|
||||
|
||||
test('installs modules on late registration', done => {
|
||||
domHookStub.handleInstanceAttached.reset();
|
||||
plugin.registerCustomComponent('foo', 'noob-noob');
|
||||
test('late registration', done => {
|
||||
plugin.registerCustomComponent('banana', 'noob-noob');
|
||||
flush(() => {
|
||||
assert.equal(domHookStub.handleInstanceAttached.callCount, 1);
|
||||
assert.strictEqual(
|
||||
element._initDecoration.lastCall.args[0], 'noob-noob');
|
||||
assert.strictEqual(
|
||||
element._initDecoration.lastCall.args[1], plugin);
|
||||
const element =
|
||||
container.querySelector('gr-endpoint-decorator[name="banana"]');
|
||||
const module = Polymer.dom(element.root).children.find(
|
||||
element => element.nodeName === 'NOOB-NOOB');
|
||||
assert.isOk(module);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('params', () => {
|
||||
const instance = document.createElement('foo');
|
||||
element._initProperties(instance, plugin);
|
||||
assert.equal(instance.someparam, 'barbar');
|
||||
test('late param setup', done => {
|
||||
const element =
|
||||
container.querySelector('gr-endpoint-decorator[name="banana"]');
|
||||
const param = Polymer.dom(element).querySelector('gr-endpoint-param');
|
||||
param['value'] = undefined;
|
||||
plugin.registerCustomComponent('banana', 'noob-noob');
|
||||
flush(() => {
|
||||
let module = Polymer.dom(element.root).children.find(
|
||||
element => element.nodeName === 'NOOB-NOOB');
|
||||
// Module waits for param to be defined.
|
||||
assert.isNotOk(module);
|
||||
const value = {abc: 'def'};
|
||||
param.value = value;
|
||||
flush(() => {
|
||||
module = Polymer.dom(element.root).children.find(
|
||||
element => element.nodeName === 'NOOB-NOOB');
|
||||
assert.isOk(module);
|
||||
assert.strictEqual(module['someParam'], value);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -18,7 +18,10 @@
|
||||
is: 'gr-endpoint-param',
|
||||
properties: {
|
||||
name: String,
|
||||
value: Object,
|
||||
value: {
|
||||
type: Object,
|
||||
notify: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user