Merge branch 'stable-2.16' into stable-3.0
* stable-2.16: Add event interface to Gerrit Change-Id: I3d65950598cc04922c19188ce020d504d109e817
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
(function(window) {
|
||||
'use strict';
|
||||
|
||||
// Avoid duplicate registeration
|
||||
if (window.EventEmitter) return;
|
||||
|
||||
/**
|
||||
* An lite implementation of
|
||||
* https://nodejs.org/api/events.html#events_class_eventemitter.
|
||||
*
|
||||
* This is unrelated to the native DOM events, you should use it when you want
|
||||
* to enable EventEmitter interface on any class.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* class YourClass extends EventEmitter {
|
||||
* // now all instance of YourClass will have this EventEmitter interface
|
||||
* }
|
||||
*
|
||||
*/
|
||||
class EventEmitter {
|
||||
constructor() {
|
||||
/**
|
||||
* Shared events map from name to the listeners.
|
||||
* @type {!Object<string, Array<eventCallback>>}
|
||||
*/
|
||||
this._listenersMap = new Map();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an event listener to an event.
|
||||
*
|
||||
* @param {string} eventName
|
||||
* @param {eventCallback} cb
|
||||
* @returns {Function} Unsubscribe method
|
||||
*/
|
||||
addListener(eventName, cb) {
|
||||
if (!eventName || !cb) {
|
||||
console.warn('A valid eventname and callback is required!');
|
||||
return;
|
||||
}
|
||||
|
||||
const listeners = this._listenersMap.get(eventName) || [];
|
||||
listeners.push(cb);
|
||||
this._listenersMap.set(eventName, listeners);
|
||||
|
||||
return () => {
|
||||
this.off(eventName, cb);
|
||||
};
|
||||
}
|
||||
|
||||
// Alias for addListener.
|
||||
on(eventName, cb) {
|
||||
return this.addListener(eventName, cb);
|
||||
}
|
||||
|
||||
// Attach event handler only once. Automatically removed.
|
||||
once(eventName, cb) {
|
||||
const onceWrapper = (...args) => {
|
||||
cb(...args);
|
||||
this.off(eventName, onceWrapper);
|
||||
};
|
||||
return this.on(eventName, onceWrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* De-register an event listener to an event.
|
||||
*
|
||||
* @param {string} eventName
|
||||
* @param {eventCallback} cb
|
||||
*/
|
||||
removeListener(eventName, cb) {
|
||||
let listeners = this._listenersMap.get(eventName) || [];
|
||||
listeners = listeners.filter(listener => listener !== cb);
|
||||
this._listenersMap.set(eventName, listeners);
|
||||
}
|
||||
|
||||
// Alias to removeListener
|
||||
off(eventName, cb) {
|
||||
this.removeListener(eventName, cb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronously calls each of the listeners registered for
|
||||
* the event named eventName, in the order they were registered,
|
||||
* passing the supplied detail to each.
|
||||
*
|
||||
* Returns true if the event had listeners, false otherwise.
|
||||
*
|
||||
* @param {string} eventName
|
||||
* @param {*} detail
|
||||
*/
|
||||
emit(eventName, detail) {
|
||||
const listeners = this._listenersMap.get(eventName) || [];
|
||||
for (const listener of listeners) {
|
||||
try {
|
||||
listener(detail);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
return listeners.length !== 0;
|
||||
}
|
||||
|
||||
// Alias to emit.
|
||||
dispatch(eventName, detail) {
|
||||
return this.emit(eventName, detail);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove listeners for a specific event or all.
|
||||
*
|
||||
* @param {string} eventName if not provided, will remove all
|
||||
*/
|
||||
removeAllListeners(eventName) {
|
||||
if (eventName) {
|
||||
this._listenersMap.set(eventName, []);
|
||||
} else {
|
||||
this._listenersMap = new Map();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.EventEmitter = EventEmitter;
|
||||
})(window);
|
@@ -0,0 +1,146 @@
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
@license
|
||||
Copyright (C) 2019 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
|
||||
<title>gr-api-interface</title>
|
||||
|
||||
<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
|
||||
<script src="../../../bower_components/web-component-tester/browser.js"></script>
|
||||
<link rel="import" href="../../../test/common-test-setup.html"/>
|
||||
<link rel="import" href="../gr-js-api-interface/gr-js-api-interface.html">
|
||||
|
||||
<script>void(0);</script>
|
||||
|
||||
<test-fixture id="basic">
|
||||
<template>
|
||||
<gr-js-api-interface></gr-js-api-interface>
|
||||
</template>
|
||||
</test-fixture>
|
||||
|
||||
<script>
|
||||
suite('gr-event-interface tests', () => {
|
||||
let sandbox;
|
||||
|
||||
setup(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
suite('test on Gerrit', () => {
|
||||
setup(() => {
|
||||
fixture('basic');
|
||||
Gerrit.removeAllListeners();
|
||||
});
|
||||
|
||||
test('communicate between plugin and Gerrit', done => {
|
||||
const eventName = 'test-plugin-event';
|
||||
let p;
|
||||
Gerrit.on(eventName, e => {
|
||||
assert.equal(e.value, 'test');
|
||||
assert.equal(e.plugin, p);
|
||||
done();
|
||||
});
|
||||
Gerrit.install(plugin => {
|
||||
p = plugin;
|
||||
Gerrit.emit(eventName, {value: 'test', plugin});
|
||||
}, '0.1',
|
||||
'http://test.com/plugins/testplugin/static/test.js');
|
||||
});
|
||||
|
||||
test('listen on events from core', done => {
|
||||
const eventName = 'test-plugin-event';
|
||||
Gerrit.on(eventName, e => {
|
||||
assert.equal(e.value, 'test');
|
||||
done();
|
||||
});
|
||||
|
||||
Gerrit.emit(eventName, {value: 'test'});
|
||||
});
|
||||
|
||||
test('communicate across plugins', done => {
|
||||
const eventName = 'test-plugin-event';
|
||||
Gerrit.install(plugin => {
|
||||
Gerrit.on(eventName, e => {
|
||||
assert.equal(e.plugin.getPluginName(), 'testB');
|
||||
done();
|
||||
});
|
||||
}, '0.1',
|
||||
'http://test.com/plugins/testA/static/testA.js');
|
||||
|
||||
Gerrit.install(plugin => {
|
||||
Gerrit.emit(eventName, {plugin});
|
||||
}, '0.1',
|
||||
'http://test.com/plugins/testB/static/testB.js');
|
||||
});
|
||||
});
|
||||
|
||||
suite('test on interfaces', () => {
|
||||
let testObj;
|
||||
class TestClass extends EventEmitter {
|
||||
}
|
||||
setup(() => {
|
||||
testObj = new TestClass();
|
||||
});
|
||||
|
||||
test('on', () => {
|
||||
const cbStub = sinon.stub();
|
||||
testObj.on('test', cbStub);
|
||||
testObj.emit('test');
|
||||
testObj.emit('test');
|
||||
assert.isTrue(cbStub.calledTwice);
|
||||
});
|
||||
|
||||
test('once', () => {
|
||||
const cbStub = sinon.stub();
|
||||
testObj.once('test', cbStub);
|
||||
testObj.emit('test');
|
||||
testObj.emit('test');
|
||||
assert.isTrue(cbStub.calledOnce);
|
||||
});
|
||||
|
||||
test('unsubscribe', () => {
|
||||
const cbStub = sinon.stub();
|
||||
const unsubscribe = testObj.on('test', cbStub);
|
||||
testObj.emit('test');
|
||||
unsubscribe();
|
||||
testObj.emit('test');
|
||||
assert.isTrue(cbStub.calledOnce);
|
||||
});
|
||||
|
||||
test('off', () => {
|
||||
const cbStub = sinon.stub();
|
||||
testObj.on('test', cbStub);
|
||||
testObj.emit('test');
|
||||
testObj.off('test', cbStub);
|
||||
testObj.emit('test');
|
||||
assert.isTrue(cbStub.calledOnce);
|
||||
});
|
||||
|
||||
test('removeAllListeners', () => {
|
||||
const cbStub = sinon.stub();
|
||||
testObj.on('test', cbStub);
|
||||
testObj.removeAllListeners('test');
|
||||
testObj.emit('test');
|
||||
assert.isTrue(cbStub.notCalled);
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
@@ -30,6 +30,7 @@ limitations under the License.
|
||||
<link rel="import" href="../gr-rest-api-interface/gr-rest-api-interface.html">
|
||||
|
||||
<dom-module id="gr-js-api-interface">
|
||||
<script src="../gr-event-interface/gr-event-interface.js"></script>
|
||||
<script src="gr-annotation-actions-context.js"></script>
|
||||
<script src="gr-annotation-actions-js-api.js"></script>
|
||||
<script src="gr-change-actions-js-api.js"></script>
|
||||
|
@@ -668,6 +668,43 @@
|
||||
}
|
||||
};
|
||||
|
||||
// TODO(taoalpha): List all internal supported event names.
|
||||
// Also convert this to inherited class once we move Gerrit to class.
|
||||
Gerrit._eventEmitter = new EventEmitter();
|
||||
['addListener',
|
||||
'dispatch',
|
||||
'emit',
|
||||
'off',
|
||||
'on',
|
||||
'once',
|
||||
'removeAllListeners',
|
||||
'removeListener',
|
||||
].forEach(method => {
|
||||
/**
|
||||
* Enabling EventEmitter interface on Gerrit.
|
||||
*
|
||||
* This will enable to signal across different parts of js code without relying on DOM,
|
||||
* including core to core, plugin to plugin and also core to plugin.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* // Emit this event from pluginA
|
||||
* Gerrit.install(pluginA => {
|
||||
* fetch("some-api").then(() => {
|
||||
* Gerrit.on("your-special-event", {plugin: pluginA});
|
||||
* });
|
||||
* });
|
||||
*
|
||||
* // Listen on your-special-event from pluignB
|
||||
* Gerrit.install(pluginB => {
|
||||
* Gerrit.on("your-special-event", ({plugin}) => {
|
||||
* // do something, plugin is pluginA
|
||||
* });
|
||||
* });
|
||||
*/
|
||||
Gerrit[method] = Gerrit._eventEmitter[method].bind(Gerrit._eventEmitter);
|
||||
});
|
||||
|
||||
window.Gerrit = Gerrit;
|
||||
|
||||
// Preloaded plugins should be installed after Gerrit.install() is set,
|
||||
|
@@ -148,6 +148,7 @@ limitations under the License.
|
||||
'settings/gr-settings-view/gr-settings-view_test.html',
|
||||
'settings/gr-ssh-editor/gr-ssh-editor_test.html',
|
||||
'settings/gr-watched-projects-editor/gr-watched-projects-editor_test.html',
|
||||
'shared/gr-event-interface/gr-event-interface_test.html',
|
||||
'shared/gr-account-label/gr-account-label_test.html',
|
||||
'shared/gr-account-link/gr-account-link_test.html',
|
||||
'shared/gr-alert/gr-alert_test.html',
|
||||
|
Reference in New Issue
Block a user