Add change view actions JS interface
This provides a simple interface for gr-change-actions for use by
the JS API. It allows the author to not have to worry about
implementation details of the element itself, while still
providing a reduced-surface API contract that can be tested as
the underlying element evolves.
Feature: Issue 3915
Change-Id: I2f82060ea14adef93a7018b79bf7cf10b84e5735
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html
index d2026f6..b741784 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html
@@ -59,7 +59,7 @@
}
</style>
<div>
- <section hidden$="[[!_keyCount(actions)]]" hidden>
+ <section hidden$="[[!_actionCount(actions.*, _additionalActions.*)]]">
<div class="groupLabel">Change</div>
<template is="dom-repeat" items="[[_changeActionValues]]" as="action">
<gr-button title$="[[action.title]]"
@@ -72,7 +72,7 @@
on-tap="_handleActionTap"></gr-button>
</template>
</section>
- <section hidden$="[[!_keyCount(_revisionActions)]]" hidden>
+ <section hidden$="[[!_actionCount(_revisionActions.*, _additionalActions.*)]]">
<div class="groupLabel">Revision</div>
<template is="dom-repeat" items="[[_revisionActionValues]]" as="action">
<gr-button title$="[[action.title]]"
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
index d195241..9783831 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
@@ -59,7 +59,10 @@
*/
properties: {
- actions: Object,
+ actions: {
+ type: Object,
+ value: function() { return {}; },
+ },
primaryActionKeys: {
type: Array,
value: function() {
@@ -77,7 +80,10 @@
type: Boolean,
value: true,
},
- _revisionActions: Object,
+ _revisionActions: {
+ type: Object,
+ value: function() { return {}; },
+ },
_revisionActionValues: {
type: Array,
computed: '_computeRevisionActionValues(_revisionActions.*, ' +
@@ -103,7 +109,7 @@
],
observers: [
- '_actionsChanged(actions, _revisionActions, _additionalActions)',
+ '_actionsChanged(actions.*, _revisionActions.*, _additionalActions.*)',
],
ready: function() {
@@ -129,7 +135,7 @@
}.bind(this));
},
- addActionButton: function(key, type, label) {
+ addActionButton: function(type, label) {
if (type !== ActionType.CHANGE && type !== ActionType.REVISION) {
throw Error('Invalid action type: ' + type);
}
@@ -137,39 +143,59 @@
enabled: true,
label: label,
__type: type,
- __key: ADDITIONAL_ACTION_KEY_PREFIX + key + Math.random().toString(36),
+ __key: ADDITIONAL_ACTION_KEY_PREFIX + Math.random().toString(36),
};
this.push('_additionalActions', action);
return action.__key;
},
removeActionButton: function(key) {
- var idx = -1;
- for (var i = 0; i < this._additionalActions.length; i++) {
- if (this._additionalActions[i].__key === key) {
- idx = i;
- break;
- }
- }
+ var idx = this._indexOfActionButtonWithKey(key);
if (idx === -1) {
- console.error('Could not find action button with key:', key);
+ return;
}
this.splice('_additionalActions', idx, 1);
},
+ setActionButtonProp: function(key, prop, value) {
+ this.set([
+ '_additionalActions',
+ this._indexOfActionButtonWithKey(key),
+ prop,
+ ], value);
+ },
+
+ _indexOfActionButtonWithKey: function(key) {
+ for (var i = 0; i < this._additionalActions.length; i++) {
+ if (this._additionalActions[i].__key === key) {
+ return i;
+ }
+ }
+ return -1;
+ },
+
_getRevisionActions: function() {
return this.$.restAPI.getChangeRevisionActions(this.changeNum,
this.patchNum);
},
- _keyCount: function(obj) {
- return Object.keys(obj).length;
+ _actionCount: function(actionsChangeRecord, additionalActionsChangeRecord) {
+ var additionalActions = (additionalActionsChangeRecord &&
+ additionalActionsChangeRecord.base) || [];
+ return this._keyCount(actionsChangeRecord) + additionalActions.length;
},
- _actionsChanged: function(actions, revisionActions, additionalActions) {
- this.hidden = this._keyCount(actions) === 0 &&
- this._keyCount(revisionActions) === 0 &&
- this._keyCount(additionalActions) === 0;
+ _keyCount: function(changeRecord) {
+ return Object.keys((changeRecord && changeRecord.base) || {}).length;
+ },
+
+ _actionsChanged: function(actionsChangeRecord, revisionActionsChangeRecord,
+ additionalActionsChangeRecord) {
+ var additionalActions = (additionalActionsChangeRecord &&
+ additionalActionsChangeRecord.base) || [];
+ this.hidden = this._keyCount(actionsChangeRecord) === 0 &&
+ this._keyCount(revisionActionsChangeRecord) === 0 &&
+ additionalActions.length === 0;
},
_getValuesFor: function(obj) {
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html
index fe04843..462b01b 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html
@@ -203,8 +203,7 @@
test('custom actions', function(done) {
// Add a button with the same key as a server-based one to ensure
// collisions are taken care of.
- var key = element.addActionButton('submit', element.ActionType.REVISION,
- 'Bork!');
+ var key = element.addActionButton(element.ActionType.REVISION, 'Bork!');
element.addEventListener(key + '-tap', function(e) {
assert.equal(e.detail.node.getAttribute('data-action-key'), key);
element.removeActionButton(key);
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api.js
new file mode 100644
index 0000000..f7c337b
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api.js
@@ -0,0 +1,62 @@
+// Copyright (C) 2016 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';
+
+ function GrChangeActionsInterface(el) {
+ this._el = el;
+ this.RevisionActions = el.RevisionActions;
+ this.ChangeActions = el.ChangeActions;
+ this.ActionType = el.ActionType;
+ }
+
+ GrChangeActionsInterface.prototype.addPrimaryActionKey = function(key) {
+ if (this._el.primaryActionKeys.indexOf(key) !== -1) { return; }
+
+ this._el.push('primaryActionKeys', key);
+ };
+
+ GrChangeActionsInterface.prototype.removePrimaryActionKey = function(key) {
+ this._el.primaryActionKeys = this._el.primaryActionKeys.filter(function(k) {
+ return k !== key;
+ });
+ };
+
+ GrChangeActionsInterface.prototype.add = function(type, label) {
+ return this._el.addActionButton(type, label);
+ };
+
+ GrChangeActionsInterface.prototype.remove = function(key) {
+ return this._el.removeActionButton(key);
+ };
+
+ GrChangeActionsInterface.prototype.addTapListener = function(key, handler) {
+ this._el.addEventListener(key + '-tap', handler);
+ };
+
+ GrChangeActionsInterface.prototype.removeTapListener = function(key,
+ handler) {
+ this._el.removeEventListener(key + '-tap', handler);
+ };
+
+ GrChangeActionsInterface.prototype.setLabel = function(key, text) {
+ this._el.setActionButtonProp(key, 'label', text);
+ };
+
+ GrChangeActionsInterface.prototype.setEnabled = function(key, enabled) {
+ this._el.setActionButtonProp(key, 'enabled', enabled);
+ };
+
+ window.GrChangeActionsInterface = GrChangeActionsInterface;
+})(window);
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api_test.html
new file mode 100644
index 0000000..3030870
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api_test.html
@@ -0,0 +1,123 @@
+<!DOCTYPE html>
+<!--
+Copyright (C) 2016 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-change-actions-js-api</title>
+
+<script src="../../../bower_components/webcomponentsjs/webcomponents.min.js"></script>
+<script src="../../../bower_components/web-component-tester/browser.js"></script>
+
+<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
+<!--
+This must refer to the element this interface is wrapping around. Otherwise
+breaking changes to gr-change-actions won’t be noticed.
+-->
+<link rel="import" href="../../change/gr-change-actions/gr-change-actions.html">
+
+<test-fixture id="basic">
+ <template>
+ <gr-change-actions></gr-change-actions>
+ </template>
+</test-fixture>
+
+<script>
+ suite('gr-js-api-interface tests', function() {
+ var element;
+ var changeActions;
+
+ setup(function() {
+ element = fixture('basic');
+ var plugin;
+ Gerrit.install(function(p) { plugin = p; }, '0.1',
+ 'http://test.com/plugins/testplugin/static/test.js');
+ changeActions = plugin.changeActions();
+ });
+
+ teardown(function() {
+ changeActions = null;
+ });
+
+ test('property existence', function() {
+ [
+ 'ActionType',
+ 'ChangeActions',
+ 'RevisionActions',
+ ].forEach(function(p) {
+ assertArraysEqual(changeActions[p], element[p]);
+ });
+ });
+
+ // Because deepEqual doesn’t behave in Safari.
+ function assertArraysEqual(actual, expected) {
+ assert.equal(actual.length, expected.length);
+ for (var i = 0; i < actual.length; i++) {
+ assert.equal(actual[i], expected[i]);
+ }
+ }
+
+ test('add/remove primary action keys', function() {
+ element.primaryActionKeys = [];
+ changeActions.addPrimaryActionKey('foo');
+ assertArraysEqual(element.primaryActionKeys, ['foo']);
+ changeActions.addPrimaryActionKey('foo');
+ assertArraysEqual(element.primaryActionKeys, ['foo']);
+ changeActions.addPrimaryActionKey('bar');
+ assertArraysEqual(element.primaryActionKeys, ['foo', 'bar']);
+ changeActions.removePrimaryActionKey('foo');
+ assertArraysEqual(element.primaryActionKeys, ['bar']);
+ changeActions.removePrimaryActionKey('baz');
+ assertArraysEqual(element.primaryActionKeys, ['bar']);
+ changeActions.removePrimaryActionKey('bar');
+ assertArraysEqual(element.primaryActionKeys, []);
+ });
+
+ test('action buttons', function(done) {
+ var key = changeActions.add(changeActions.ActionType.REVISION, 'Bork!');
+ var handler = sinon.spy();
+ changeActions.addTapListener(key, handler);
+ flush(function() {
+ MockInteractions.tap(element.$$('[data-action-key="' + key + '"]'));
+ assert(handler.calledOnce);
+ changeActions.removeTapListener(key, handler);
+ MockInteractions.tap(element.$$('[data-action-key="' + key + '"]'));
+ assert(handler.calledOnce);
+ changeActions.remove(key);
+ flush(function() {
+ assert.isNull(element.$$('[data-action-key="' + key + '"]'));
+ done();
+ });
+ });
+ });
+
+ test('action button properties', function(done) {
+ var key = changeActions.add(changeActions.ActionType.REVISION, 'Bork!');
+ flush(function() {
+ var button = element.$$('[data-action-key="' + key + '"]');
+ assert.isOk(button);
+ assert.equal(button.getAttribute('data-label'), 'Bork!');
+ assert.isFalse(button.disabled);
+ changeActions.setLabel(key, 'Yo');
+ changeActions.setEnabled(key, false);
+ flush(function() {
+ assert.equal(button.getAttribute('data-label'), 'Yo');
+ assert.isTrue(button.disabled);
+ done();
+ });
+ });
+ });
+ });
+</script>
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.html
index a3b489d..a77a4ea 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.html
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.html
@@ -17,6 +17,7 @@
<dom-module id="gr-js-api-interface">
<template></template>
+ <script src="gr-change-actions-js-api.js"></script>
<script src="gr-js-api-interface.js"></script>
<script src="gr-public-js-api.js"></script>
</dom-module>
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js
index c63221d..e60876c 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js
@@ -52,9 +52,9 @@
return this._url.origin + '/plugins/' + this._name + (opt_path || '/');
};
- Plugin.prototype.getChangeActionsElement = function() {
- return Plugin._sharedAPIElement.getElement(
- Plugin._sharedAPIElement.Element.CHANGE_ACTIONS);
+ Plugin.prototype.changeActions = function() {
+ return new GrChangeActionsInterface(Plugin._sharedAPIElement.getElement(
+ Plugin._sharedAPIElement.Element.CHANGE_ACTIONS));
};
var Gerrit = window.Gerrit || {};
diff --git a/polygerrit-ui/app/test/index.html b/polygerrit-ui/app/test/index.html
index 96b97cc..8757ddf 100644
--- a/polygerrit-ui/app/test/index.html
+++ b/polygerrit-ui/app/test/index.html
@@ -74,6 +74,7 @@
'shared/gr-date-formatter/gr-date-formatter_test.html',
'shared/gr-editable-content/gr-editable-content_test.html',
'shared/gr-editable-label/gr-editable-label_test.html',
+ 'shared/gr-js-api-interface/gr-change-actions-js-api_test.html',
'shared/gr-js-api-interface/gr-js-api-interface_test.html',
'shared/gr-linked-text/gr-linked-text_test.html',
'shared/gr-rest-api-interface/gr-rest-api-interface_test.html',