Merge "Introduce gr-rule-editor"
diff --git a/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor.html b/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor.html
new file mode 100644
index 0000000..60b9ddf
--- /dev/null
+++ b/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor.html
@@ -0,0 +1,127 @@
+<!--
+Copyright (C) 2017 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.
+-->
+
+<link rel="import" href="../../../bower_components/polymer/polymer.html">
+
+<link rel="import" href="../../../styles/gr-form-styles.html">
+<link rel="import" href="../../../styles/shared-styles.html">
+<link rel="import" href="../../shared/gr-button/gr-button.html">
+<link rel="import" href="../../shared/gr-select/gr-select.html">
+
+<dom-module id="gr-rule-editor">
+ <template>
+ <style include="shared-styles">
+ :host {
+ border-bottom: 1px solid #d1d2d3;
+ padding: .7em;
+ display: block;
+ }
+ .buttons {
+ display: flex;
+ }
+ .buttons gr-button {
+ margin-left: .3em;
+ }
+ #mainContainer {
+ align-items: baseline;
+ display: flex;
+ flex-wrap: nowrap;
+ justify-content: space-between;
+ }
+ .buttons gr-button {
+ float: left;
+ margin-left: .3em;
+ }
+ #undoBtn,
+ #force,
+ #deletedContainer,
+ #mainContainer.deleted {
+ display: none;
+ }
+ #undoBtn.modified,
+ #force.force,
+ #deletedContainer.deleted {
+ display: block;
+ }
+ </style>
+ <style include="gr-form-styles"></style>
+ <div id="mainContainer"
+ class$="gr-form-styles [[_computeDeletedClass(_deleted)]]">
+ <div id="options">
+ <gr-select id="action"
+ bind-value="{{rule.value.action}}"
+ on-change="_handleValueChange">
+ <select>
+ <template is="dom-repeat" items="[[_computeOptions(permission)]]">
+ <option value="[[item]]">[[item]]</option>
+ </template>
+ </select>
+ </gr-select>
+ <template is="dom-if" if="[[label]]">
+ <gr-select
+ id="labelMin"
+ bind-value="{{rule.value.min}}"
+ on-change="_handleValueChange">
+ <select>
+ <template is="dom-repeat" items="[[label.values]]">
+ <option value="[[item.value]]">[[item.value]]</option>
+ </template>
+ </select>
+ </gr-select>
+ <gr-select
+ id="labelMax"
+ bind-value="{{rule.value.max}}"
+ on-change="_handleValueChange">
+ <select>
+ <template is="dom-repeat" items="[[label.values]]">
+ <option value="[[item.value]]">[[item.value]]</option>
+ </template>
+ </select>
+ </gr-select>
+ </template>
+ [[group]]
+ <gr-select
+ id="force"
+ class$="[[_computeForceClass(permission)]]"
+ bind-value="{{rule.value.force}}"
+ on-change="_handleValueChange">
+ <select>
+ <template
+ is="dom-repeat"
+ items="[[_computeForceOptions(permission)]]">
+ <option value="[[item.value]]">[[item.name]]</option>
+ </template>
+ </select>
+ </gr-select>
+ </div>
+ <div class="buttons">
+ <gr-button
+ id="undoBtn"
+ on-tap="_handleUndoChange"
+ class$="[[_computeModifiedClass(_modified)]]">Undo</gr-button>
+ <gr-button id="removeBtn" on-tap="_handleRemoveRule">Remove</gr-button>
+ </div>
+ </div>
+ <div
+ id="deletedContainer"
+ class$="gr-form-styles [[_computeDeletedClass(_deleted)]]">
+ [[group]] was deleted
+ <gr-button id="undoRemoveBtn" on-tap="_handleUndoRemove">Undo</gr-button>
+ </div>
+ <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
+ </template>
+ <script src="gr-rule-editor.js"></script>
+</dom-module>
diff --git a/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor.js b/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor.js
new file mode 100644
index 0000000..da026db
--- /dev/null
+++ b/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor.js
@@ -0,0 +1,169 @@
+// Copyright (C) 2017 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() {
+ 'use strict';
+
+ const PRIORITY_OPTIONS = [
+ 'BATCH',
+ 'INTERACTIVE',
+ ];
+
+ const DROPDOWN_OPTIONS = [
+ 'ALLOW',
+ 'DENY',
+ 'BLOCK',
+ ];
+
+ const FORCE_PUSH_OPTIONS = [
+ {
+ name: 'No Force Push',
+ value: false,
+ },
+ {
+ name: 'Force Push',
+ value: true,
+ },
+ ];
+
+ const FORCE_EDIT_OPTIONS = [
+ {
+ name: 'No Force Edit',
+ value: false,
+ },
+ {
+ name: 'Force Edit',
+ value: true,
+ },
+ ];
+
+ Polymer({
+ is: 'gr-rule-editor',
+
+ properties: {
+ /** @type {?} */
+ label: Object,
+ group: String,
+ permission: String,
+ /** @type {?} */
+ rule: {
+ type: Object,
+ notify: true,
+ },
+ section: String,
+ _modified: {
+ type: Boolean,
+ value: false,
+ },
+ _originalRuleValues: Object,
+ _deleted: {
+ type: Boolean,
+ value: false,
+ },
+ },
+
+ observers: [
+ '_handleValueChange(rule.value.*)',
+ ],
+
+ ready() {
+ // Called on ready rather than the observer because when new rules are
+ // added, the observer is triggered prior to being ready.
+ if (!this.rule) { return; } // Check needed for test purposes.
+ this._setupValues(this.rule);
+ },
+
+ _setupValues(rule) {
+ if (!rule.value) {
+ this._setDefaultRuleValues();
+ }
+ this._setOriginalRuleValues(rule.value);
+ },
+
+ _computeForce(permission) {
+ return 'push' === permission || 'editTopicName' === permission;
+ },
+
+ _computeForceClass(permission) {
+ return this._computeForce(permission) ? 'force' : '';
+ },
+
+ _computeDeletedClass(deleted) {
+ return deleted ? 'deleted' : '';
+ },
+
+ _computeForceOptions(permission) {
+ if (permission === 'push') {
+ return FORCE_PUSH_OPTIONS;
+ } else if (permission === 'editTopicName') {
+ return FORCE_EDIT_OPTIONS;
+ }
+ return [];
+ },
+
+ _getDefaultRuleValues(permission, label) {
+ const value = {};
+ if (permission === 'priority') {
+ value.action = PRIORITY_OPTIONS[0];
+ return value;
+ } else if (label) {
+ value.min = label.values[0].value;
+ value.max = label.values[label.values.length - 1].value;
+ } else if (this._computeForce(permission)) {
+ value.force = this._computeForceOptions(permission)[0].value;
+ }
+ value.action = DROPDOWN_OPTIONS[0];
+ return value;
+ },
+
+ _setDefaultRuleValues() {
+ this.set('rule.value',
+ this._getDefaultRuleValues(this.permission, this.label));
+ },
+
+ _computeOptions(permission) {
+ if (permission === 'priority') {
+ return PRIORITY_OPTIONS;
+ }
+ return DROPDOWN_OPTIONS;
+ },
+
+ _handleRemoveRule() {
+ this._deleted = true;
+ this.rule.deleted = true;
+ },
+
+ _handleUndoRemove() {
+ this._deleted = false;
+ delete this.rule.deleted;
+ },
+
+ _handleUndoChange() {
+ this.set('rule.value', Object.assign({}, this._originalRuleValues));
+ this._modified = false;
+ },
+
+ _handleValueChange() {
+ if (!this._originalRuleValues) { return; }
+ this._modified = true;
+ },
+
+ _setOriginalRuleValues(value) {
+ this._originalRuleValues = Object.assign({}, value);
+ },
+
+ _computeModifiedClass(modified) {
+ return modified ? 'modified' : '';
+ },
+ });
+})();
\ No newline at end of file
diff --git a/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor_test.html b/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor_test.html
new file mode 100644
index 0000000..376fb79
--- /dev/null
+++ b/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor_test.html
@@ -0,0 +1,571 @@
+<!DOCTYPE html>
+<!--
+Copyright (C) 2017 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-rule-editor</title>
+
+<script src="../../../bower_components/page/page.js"></script>
+<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-rule-editor.html">
+
+<script>void(0);</script>
+
+<test-fixture id="basic">
+ <template>
+ <gr-rule-editor></gr-rule-editor>
+ </template>
+</test-fixture>
+
+<script>
+ suite('gr-rule-editor tests', () => {
+ let element;
+ let sandbox;
+
+ setup(() => {
+ sandbox = sinon.sandbox.create();
+ element = fixture('basic');
+ });
+
+ teardown(() => {
+ sandbox.restore();
+ });
+
+ suite('unit tests', () => {
+ test('_computeForce, _computeForceClass, and _computeForceOptions',
+ () => {
+ const FORCE_PUSH_OPTIONS = [
+ {
+ name: 'No Force Push',
+ value: false,
+ },
+ {
+ name: 'Force Push',
+ value: true,
+ },
+ ];
+
+ const FORCE_EDIT_OPTIONS = [
+ {
+ name: 'No Force Edit',
+ value: false,
+ },
+ {
+ name: 'Force Edit',
+ value: true,
+ },
+ ];
+ let permission = 'push';
+ assert.isTrue(element._computeForce(permission));
+ assert.equal(element._computeForceClass(permission), 'force');
+ assert.deepEqual(element._computeForceOptions(permission),
+ FORCE_PUSH_OPTIONS);
+ permission = 'editTopicName';
+ assert.isTrue(element._computeForce(permission));
+ assert.equal(element._computeForceClass(permission), 'force');
+ assert.deepEqual(element._computeForceOptions(permission),
+ FORCE_EDIT_OPTIONS);
+ permission = 'submit';
+ assert.isFalse(element._computeForce(permission));
+ assert.equal(element._computeForceClass(permission), '');
+ assert.deepEqual(element._computeForceOptions(permission), []);
+ });
+
+ test('_computeDeletedClass', () => {
+ assert.equal(element._computeDeletedClass(true), 'deleted');
+ assert.equal(element._computeDeletedClass(false), '');
+ });
+
+ test('_getDefaultRuleValues', () => {
+ let permission = 'priority';
+ let label;
+ assert.deepEqual(element._getDefaultRuleValues(permission, label),
+ {action: 'BATCH'});
+ permission = 'label-Code-Review';
+ label = {values: [
+ {value: -2, text: 'This shall not be merged'},
+ {value: -1, text: 'I would prefer this is not merged as is'},
+ {value: -0, text: 'No score'},
+ {value: 1, text: 'Looks good to me, but someone else must approve'},
+ {value: 2, text: 'Looks good to me, approved'},
+ ]};
+ assert.deepEqual(element._getDefaultRuleValues(permission, label),
+ {action: 'ALLOW', max: 2, min: -2});
+ permission = 'push';
+ label = undefined;
+ assert.deepEqual(element._getDefaultRuleValues(permission, label),
+ {action: 'ALLOW', force: false});
+ permission = 'submit';
+ assert.deepEqual(element._getDefaultRuleValues(permission, label),
+ {action: 'ALLOW'});
+ });
+
+ test('_setDefaultRuleValues', () => {
+ element.rule = {id: 123};
+ const defaultValue = {action: 'ALLOW'};
+ sandbox.stub(element, '_getDefaultRuleValues').returns(defaultValue);
+ element._setDefaultRuleValues();
+ assert.isTrue(element._getDefaultRuleValues.called);
+ assert.equal(element.rule.value, defaultValue);
+ });
+
+ test('_computeOptions', () => {
+ const PRIORITY_OPTIONS = [
+ 'BATCH',
+ 'INTERACTIVE',
+ ];
+ const DROPDOWN_OPTIONS = [
+ 'ALLOW',
+ 'DENY',
+ 'BLOCK',
+ ];
+ let permission = 'priority';
+ assert.deepEqual(element._computeOptions(permission), PRIORITY_OPTIONS);
+ permission = 'submit';
+ assert.deepEqual(element._computeOptions(permission), DROPDOWN_OPTIONS);
+ });
+
+ test('_handleValueChange', () => {
+ element._handleValueChange();
+ assert.isFalse(element._modified);
+ element._originalRuleValues = {};
+ element._handleValueChange();
+ assert.isTrue(element._modified);
+ });
+
+ test('_setOriginalRuleValues', () => {
+ const value = {
+ action: 'ALLOW',
+ force: false,
+ };
+ element._setOriginalRuleValues(value);
+ assert.deepEqual(element._originalRuleValues, value);
+ });
+ });
+
+ suite('already existing generic rule', () => {
+ setup(() => {
+ element.group = 'Group Name';
+ element.permission = 'submit';
+ element.rule = {
+ id: '123',
+ value: {
+ action: 'ALLOW',
+ force: false,
+ },
+ };
+ element.section = 'refs/*';
+
+ // Typically called on ready since elements will have properies defined
+ // by the parent element.
+ element._setupValues(element.rule);
+ flushAsynchronousOperations();
+ });
+
+ test('_ruleValues and _originalRuleValues are set correctly', () => {
+ assert.deepEqual(element._originalRuleValues, element.rule.value);
+ });
+
+ test('values are set correctly', () => {
+ assert.equal(element.$.action.bindValue, element.rule.value.action);
+ assert.isNotOk(Polymer.dom(element.root).querySelector('#labelMin'));
+ assert.isNotOk(Polymer.dom(element.root).querySelector('#labelMax'));
+ assert.isFalse(element.$.force.classList.contains('force'));
+ });
+
+ test('modify and undo value', () => {
+ assert.isFalse(element._modified);
+ assert.isFalse(element.$.undoBtn.classList.contains('modified'));
+ element.$.action.bindValue = 'DENY';
+ flushAsynchronousOperations();
+ assert.isTrue(element._modified);
+ assert.isTrue(element.$.undoBtn.classList.contains('modified'));
+
+ // The original value should now differ from the rule values.
+ assert.notDeepEqual(element._originalRuleValues, element.rule.value);
+
+ // After undoing the change, the original value should get reset.
+ MockInteractions.tap(element.$.undoBtn);
+ assert.deepEqual(element._originalRuleValues, element.rule.value);
+ assert.equal(element.$.action.bindValue, 'ALLOW');
+ assert.isFalse(element._modified);
+ });
+
+ test('remove rule and undo remove', () => {
+ element.rule = {id: 123};
+ assert.isFalse(
+ element.$.deletedContainer.classList.contains('deleted'));
+ MockInteractions.tap(element.$.removeBtn);
+ assert.isTrue(element.$.deletedContainer.classList.contains('deleted'));
+ assert.isTrue(element.rule.deleted);
+
+ MockInteractions.tap(element.$.undoRemoveBtn);
+ assert.isNotOk(element.rule.deleted);
+ });
+ });
+
+ suite('new edit rule', () => {
+ setup(() => {
+ element.group = 'Group Name';
+ element.permission = 'editTopicName';
+ element.rule = {
+ id: '123',
+ };
+ element.section = 'refs/*';
+ element._setupValues(element.rule);
+ flushAsynchronousOperations();
+ });
+
+ test('_ruleValues and _originalRuleValues are set correctly', () => {
+ // Since the element does not already have default values, they should
+ // be set. The original values should be set to those too.
+ assert.isFalse(element._modified);
+ const expectedRuleValue = {
+ action: 'ALLOW',
+ force: false,
+ };
+ assert.deepEqual(element.rule.value, expectedRuleValue);
+ assert.deepEqual(element._originalRuleValues, expectedRuleValue);
+ test('values are set correctly', () => {
+ assert.equal(element.$.action.bindValue, expectedRuleValue.action);
+ assert.equal(element.$.force.bindValue, expectedRuleValue.action);
+ });
+ });
+
+ test('modify and undo value', () => {
+ assert.isFalse(element._modified);
+ assert.isFalse(element.$.undoBtn.classList.contains('modified'));
+ element.$.force.bindValue = true;
+ flushAsynchronousOperations();
+ assert.isTrue(element._modified);
+ assert.isTrue(element.$.undoBtn.classList.contains('modified'));
+
+ // The original value should now differ from the rule values.
+ assert.notDeepEqual(element._originalRuleValues, element.rule.value);
+
+ // After undoing the change, the original value should get reset.
+ MockInteractions.tap(element.$.undoBtn);
+ assert.deepEqual(element._originalRuleValues, element.rule.value);
+ });
+ });
+
+ suite('already existing rule with labels', () => {
+ setup(() => {
+ element.label = {values: [
+ {value: -2, text: 'This shall not be merged'},
+ {value: -1, text: 'I would prefer this is not merged as is'},
+ {value: -0, text: 'No score'},
+ {value: 1, text: 'Looks good to me, but someone else must approve'},
+ {value: 2, text: 'Looks good to me, approved'},
+ ]};
+ element.group = 'Group Name';
+ element.permission = 'label-Code-Review';
+ element.rule = {
+ id: '123',
+ value: {
+ action: 'ALLOW',
+ force: false,
+ max: 2,
+ min: -2,
+ },
+ };
+ element.section = 'refs/*';
+ element._setupValues(element.rule);
+ flushAsynchronousOperations();
+ });
+
+ test('_ruleValues and _originalRuleValues are set correctly', () => {
+ assert.deepEqual(element._originalRuleValues, element.rule.value);
+ });
+
+ test('values are set correctly', () => {
+ assert.equal(element.$.action.bindValue, element.rule.value.action);
+ assert.equal(
+ Polymer.dom(element.root).querySelector('#labelMin').bindValue,
+ element.rule.value.min);
+ assert.equal(
+ Polymer.dom(element.root).querySelector('#labelMax').bindValue,
+ element.rule.value.max);
+ assert.isFalse(element.$.force.classList.contains('force'));
+ });
+
+ test('modify and undo value', () => {
+ assert.isFalse(element._modified);
+ assert.isFalse(element.$.undoBtn.classList.contains('modified'));
+ Polymer.dom(element.root).querySelector('#labelMin').bindValue = 1;
+ flushAsynchronousOperations();
+ assert.isTrue(element._modified);
+ assert.isTrue(element.$.undoBtn.classList.contains('modified'));
+
+ // The original value should now differ from the rule values.
+ assert.notDeepEqual(element._originalRuleValues, element.rule.value);
+
+ // After undoing the change, the original value should get reset.
+ MockInteractions.tap(element.$.undoBtn);
+ assert.deepEqual(element._originalRuleValues, element.rule.value);
+ });
+ });
+
+ suite('new rule with labels', () => {
+ setup(() => {
+ sandbox.spy(element, '_setDefaultRuleValues');
+ element.label = {values: [
+ {value: -2, text: 'This shall not be merged'},
+ {value: -1, text: 'I would prefer this is not merged as is'},
+ {value: -0, text: 'No score'},
+ {value: 1, text: 'Looks good to me, but someone else must approve'},
+ {value: 2, text: 'Looks good to me, approved'},
+ ]};
+ element.group = 'Group Name';
+ element.permission = 'label-Code-Review';
+ element.rule = {
+ id: '123',
+ };
+ element.section = 'refs/*';
+ element._setupValues(element.rule);
+ flushAsynchronousOperations();
+ });
+
+ test('_ruleValues and _originalRuleValues are set correctly', () => {
+ // Since the element does not already have default values, they should
+ // be set. The original values should be set to those too.
+ assert.isFalse(element._modified);
+ assert.isTrue(element._setDefaultRuleValues.called);
+
+ const expectedRuleValue = {
+ max: element.label.values[element.label.values.length - 1].value,
+ min: element.label.values[0].value,
+ action: 'ALLOW',
+ };
+ assert.deepEqual(element.rule.value, expectedRuleValue);
+ assert.deepEqual(element._originalRuleValues, expectedRuleValue);
+ test('values are set correctly', () => {
+ assert.equal(
+ element.$.action.bindValue,
+ expectedRuleValue.action);
+ assert.equal(
+ Polymer.dom(element.root).querySelector('#labelMin').bindValue,
+ expectedRuleValue.min);
+ assert.equal(
+ Polymer.dom(element.root).querySelector('#labelMax').bindValue,
+ expectedRuleValue.max);
+ });
+ });
+
+ test('modify and undo value', () => {
+ assert.isFalse(element._modified);
+ assert.isFalse(element.$.undoBtn.classList.contains('modified'));
+ Polymer.dom(element.root).querySelector('#labelMin').bindValue = 1;
+ flushAsynchronousOperations();
+ assert.isTrue(element._modified);
+ assert.isTrue(element.$.undoBtn.classList.contains('modified'));
+
+ // The original value should now differ from the rule values.
+ assert.notDeepEqual(element._originalRuleValues, element.rule.value);
+
+ // After undoing the change, the original value should get reset.
+ MockInteractions.tap(element.$.undoBtn);
+ assert.deepEqual(element._originalRuleValues, element.rule.value);
+ });
+ });
+
+ suite('already existing push rule', () => {
+ setup(() => {
+ element.group = 'Group Name';
+ element.permission = 'push';
+ element.rule = {
+ id: '123',
+ value: {
+ action: 'ALLOW',
+ force: true,
+ },
+ };
+ element.section = 'refs/*';
+ element._setupValues(element.rule);
+ flushAsynchronousOperations();
+ });
+
+ test('_ruleValues and _originalRuleValues are set correctly', () => {
+ assert.deepEqual(element._originalRuleValues, element.rule.value);
+ });
+
+ test('values are set correctly', () => {
+ assert.isTrue(element.$.force.classList.contains('force'));
+ assert.equal(element.$.action.bindValue, element.rule.value.action);
+ assert.equal(
+ Polymer.dom(element.root).querySelector('#force').bindValue,
+ element.rule.value.force);
+ assert.isNotOk(Polymer.dom(element.root).querySelector('#labelMin'));
+ assert.isNotOk(Polymer.dom(element.root).querySelector('#labelMax'));
+ });
+
+ test('modify and undo value', () => {
+ assert.isFalse(element._modified);
+ assert.isFalse(element.$.undoBtn.classList.contains('modified'));
+ element.$.action.bindValue = false;
+ flushAsynchronousOperations();
+ assert.isTrue(element._modified);
+ assert.isTrue(element.$.undoBtn.classList.contains('modified'));
+
+ // The original value should now differ from the rule values.
+ assert.notDeepEqual(element._originalRuleValues, element.rule.value);
+
+ // After undoing the change, the original value should get reset.
+ MockInteractions.tap(element.$.undoBtn);
+ assert.deepEqual(element._originalRuleValues, element.rule.value);
+ });
+ });
+
+ suite('new push rule', () => {
+ setup(() => {
+ element.group = 'Group Name';
+ element.permission = 'push';
+ element.rule = {
+ id: '123',
+ };
+ element.section = 'refs/*';
+ element._setupValues(element.rule);
+ flushAsynchronousOperations();
+ });
+
+ test('_ruleValues and _originalRuleValues are set correctly', () => {
+ // Since the element does not already have default values, they should
+ // be set. The original values should be set to those too.
+ assert.isFalse(element._modified);
+ const expectedRuleValue = {
+ action: 'ALLOW',
+ force: false,
+ };
+ assert.deepEqual(element.rule.value, expectedRuleValue);
+ assert.deepEqual(element._originalRuleValues, expectedRuleValue);
+ test('values are set correctly', () => {
+ assert.equal(element.$.action.bindValue, expectedRuleValue.action);
+ assert.equal(element.$.force.bindValue, expectedRuleValue.action);
+ });
+ });
+
+ test('modify and undo value', () => {
+ assert.isFalse(element._modified);
+ assert.isFalse(element.$.undoBtn.classList.contains('modified'));
+ element.$.force.bindValue = true;
+ flushAsynchronousOperations();
+ assert.isTrue(element._modified);
+ assert.isTrue(element.$.undoBtn.classList.contains('modified'));
+
+ // The original value should now differ from the rule values.
+ assert.notDeepEqual(element._originalRuleValues, element.rule.value);
+
+ // After undoing the change, the original value should get reset.
+ MockInteractions.tap(element.$.undoBtn);
+ assert.deepEqual(element._originalRuleValues, element.rule.value);
+ });
+ });
+
+ suite('already existing edit rule', () => {
+ setup(() => {
+ element.group = 'Group Name';
+ element.permission = 'editTopicName';
+ element.rule = {
+ id: '123',
+ value: {
+ action: 'ALLOW',
+ force: true,
+ },
+ };
+ element.section = 'refs/*';
+ element._setupValues(element.rule);
+ flushAsynchronousOperations();
+ });
+
+ test('_ruleValues and _originalRuleValues are set correctly', () => {
+ assert.deepEqual(element._originalRuleValues, element.rule.value);
+ });
+
+ test('values are set correctly', () => {
+ assert.isTrue(element.$.force.classList.contains('force'));
+ assert.equal(element.$.action.bindValue, element.rule.value.action);
+ assert.equal(
+ Polymer.dom(element.root).querySelector('#force').bindValue,
+ element.rule.value.force);
+ assert.isNotOk(Polymer.dom(element.root).querySelector('#labelMin'));
+ assert.isNotOk(Polymer.dom(element.root).querySelector('#labelMax'));
+ });
+
+ test('modify and undo value', () => {
+ assert.isFalse(element._modified);
+ assert.isFalse(element.$.undoBtn.classList.contains('modified'));
+ element.$.action.bindValue = false;
+ flushAsynchronousOperations();
+ assert.isTrue(element._modified);
+ assert.isTrue(element.$.undoBtn.classList.contains('modified'));
+
+ // The original value should now differ from the rule values.
+ assert.notDeepEqual(element._originalRuleValues, element.rule.value);
+
+ // After undoing the change, the original value should get reset.
+ MockInteractions.tap(element.$.undoBtn);
+ assert.deepEqual(element._originalRuleValues, element.rule.value);
+ });
+ });
+
+ suite('new edit rule', () => {
+ setup(() => {
+ element.group = 'Group Name';
+ element.permission = 'editTopicName';
+ element.rule = {
+ id: '123',
+ };
+ element.section = 'refs/*';
+ element._setupValues(element.rule);
+ flushAsynchronousOperations();
+ });
+
+ test('_ruleValues and _originalRuleValues are set correctly', () => {
+ // Since the element does not already have default values, they should
+ // be set. The original values should be set to those too.
+ assert.isFalse(element._modified);
+ const expectedRuleValue = {
+ action: 'ALLOW',
+ force: false,
+ };
+ assert.deepEqual(element.rule.value, expectedRuleValue);
+ assert.deepEqual(element._originalRuleValues, expectedRuleValue);
+ test('values are set correctly', () => {
+ assert.equal(element.$.action.bindValue, expectedRuleValue.action);
+ assert.equal(element.$.force.bindValue, expectedRuleValue.action);
+ });
+ });
+
+ test('modify and undo value', () => {
+ assert.isFalse(element._modified);
+ assert.isFalse(element.$.undoBtn.classList.contains('modified'));
+ element.$.force.bindValue = true;
+ flushAsynchronousOperations();
+ assert.isTrue(element._modified);
+ assert.isTrue(element.$.undoBtn.classList.contains('modified'));
+
+ // The original value should now differ from the rule values.
+ assert.notDeepEqual(element._originalRuleValues, element.rule.value);
+
+ // After undoing the change, the original value should get reset.
+ MockInteractions.tap(element.$.undoBtn);
+ assert.deepEqual(element._originalRuleValues, element.rule.value);
+ });
+ });
+ });
+</script>
diff --git a/polygerrit-ui/app/test/index.html b/polygerrit-ui/app/test/index.html
index 634dfd6..b171e9c 100644
--- a/polygerrit-ui/app/test/index.html
+++ b/polygerrit-ui/app/test/index.html
@@ -43,6 +43,7 @@
'admin/gr-plugin-list/gr-plugin-list_test.html',
'admin/gr-project/gr-project_test.html',
'admin/gr-project-detail-list/gr-project-detail-list_test.html',
+ 'admin/gr-rule-editor/gr-rule-editor_test.html',
'change-list/gr-change-list-item/gr-change-list-item_test.html',
'change-list/gr-change-list-view/gr-change-list-view_test.html',
'change-list/gr-change-list/gr-change-list_test.html',