Introduce gr-rule-editor

This is the most granular component in the project access editor.
It allows the user to modify the value of a particular rule.

Here is the access hierarchy:

Project access
- Sections
  - Permissions
    - Rules <-- This is the component for this change.

It will be fed in the group, permission, optional labels, ruls, and
section from its parent(s).

There are a variety of different ways this can behave that are outlined
in the tests.  Generally they are:
- Some permission types have different option values
- When there are label options, they should be presented
- Some permission types should display a push option, and their labels
  vary based on whether it is an edit or a push.

Change-Id: Iaf0aab09418c4e2589753419d82c375d8d5cf673
This commit is contained in:
Becky Siegel
2017-08-15 17:49:30 -07:00
parent d8290b804b
commit 7198afd8ef
4 changed files with 868 additions and 0 deletions

View File

@@ -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>

View File

@@ -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' : '';
},
});
})();

View File

@@ -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>

View File

@@ -43,6 +43,7 @@ limitations under the License.
'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',