blob: 62b4626e1a364c220981ec299c10b87f18ee45f8 [file] [log] [blame]
<!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</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"/>
<script src="../../../scripts/util.js"></script>
<link rel="import" href="gr-change-actions.html">
<script>void(0);</script>
<test-fixture id="basic">
<template>
<gr-change-actions></gr-change-actions>
</template>
</test-fixture>
<script>
suite('gr-change-actions tests', () => {
let element;
let sandbox;
setup(() => {
stub('gr-rest-api-interface', {
getChangeRevisionActions() {
return Promise.resolve({
cherrypick: {
method: 'POST',
label: 'Cherry Pick',
title: 'Cherry pick change to a different branch',
enabled: true,
},
rebase: {
method: 'POST',
label: 'Rebase',
title: 'Rebase onto tip of branch or parent change',
enabled: true,
},
submit: {
method: 'POST',
label: 'Submit',
title: 'Submit patch set 2 into master',
enabled: true,
},
});
},
send(method, url, payload) {
if (method !== 'POST') { return Promise.reject('bad method'); }
if (url === '/changes/test~42/revisions/2/submit') {
return Promise.resolve({
ok: true,
text() { return Promise.resolve(')]}\'\n{}'); },
});
} else if (url === '/changes/test~42/revisions/2/rebase') {
return Promise.resolve({
ok: true,
text() { return Promise.resolve(')]}\'\n{}'); },
});
}
return Promise.reject('bad url');
},
});
element = fixture('basic');
element.change = {};
element.changeNum = '42';
element.patchNum = '2';
element.actions = {
'/': {
method: 'DELETE',
label: 'Delete Change',
title: 'Delete change X_X',
enabled: true,
},
};
sandbox = sinon.sandbox.create();
return element.reload();
});
teardown(() => {
sandbox.restore();
});
test('primary and secondary actions split properly', () => {
assert.equal(element._topLevelPrimaryActions.length, 1);
assert.equal(element._topLevelPrimaryActions[0].label, 'Submit');
assert.equal(element._topLevelSecondaryActions.length, 1);
});
test('_shouldHideActions', () => {
assert.isTrue(element._shouldHideActions(undefined, true));
assert.isTrue(element._shouldHideActions({base: {}}, false));
assert.isFalse(element._shouldHideActions({base: ['test']}, false));
});
test('plugin revision actions', () => {
sandbox.stub(element.$.restAPI, 'getChangeActionURL').returns(
Promise.resolve('the-url'));
element.revisionActions = {
'plugin~action': {},
};
assert.isOk(element.revisionActions['plugin~action']);
flush(() => {
assert.isTrue(element.$.restAPI.getChangeActionURL.calledWith(
element.changeNum, element.patchNum, '/plugin~action'));
assert.equal(element.revisionActions['plugin~action'].__url, 'the-url');
});
});
test('plugin change actions', () => {
sandbox.stub(element.$.restAPI, 'getChangeActionURL').returns(
Promise.resolve('the-url'));
element.actions = {
'plugin~action': {},
};
assert.isOk(element.actions['plugin~action']);
flush(() => {
assert.isTrue(element.$.restAPI.getChangeActionURL.calledWith(
element.changeNum, null, '/plugin~action'));
assert.equal(element.revisionActions['plugin~action'].__url, 'the-url');
});
});
test('not supported actions are filtered out', () => {
element.revisionActions = {
followup: {
},
};
assert.equal(element.querySelectorAll('section gr-button').length, 0);
});
test('getActionDetails', () => {
element.revisionActions = Object.assign({
'plugin~action': {},
}, element.revisionActions);
assert.isUndefined(element.getActionDetails('rubbish'));
assert.strictEqual(element.revisionActions['plugin~action'],
element.getActionDetails('plugin~action'));
assert.strictEqual(element.revisionActions['rebase'],
element.getActionDetails('rebase'));
});
test('hide revision action', done => {
flush(() => {
const buttonEl = element.$$('[data-action-key="submit"]');
assert.isOk(buttonEl);
assert.throws(element.setActionHidden.bind(element, 'invalid type'));
element.setActionHidden(element.ActionType.REVISION,
element.RevisionActions.SUBMIT, true);
assert.lengthOf(element._hiddenActions, 1);
element.setActionHidden(element.ActionType.REVISION,
element.RevisionActions.SUBMIT, true);
assert.lengthOf(element._hiddenActions, 1);
flush(() => {
const buttonEl = element.$$('[data-action-key="submit"]');
assert.isNotOk(buttonEl);
element.setActionHidden(element.ActionType.REVISION,
element.RevisionActions.SUBMIT, false);
flush(() => {
const buttonEl = element.$$('[data-action-key="submit"]');
assert.isOk(buttonEl);
assert.isFalse(buttonEl.hasAttribute('hidden'));
done();
});
});
});
});
test('buttons exist', done => {
element._loading = false;
flush(() => {
const buttonEls = Polymer.dom(element.root)
.querySelectorAll('gr-button');
const menuItems = element.$.moreActions.items;
assert.equal(buttonEls.length + menuItems.length, 6);
assert.isFalse(element.hidden);
done();
});
});
test('delete buttons have explicit labels', done => {
flush(() => {
const deleteItems = element.$.moreActions.items.filter(item => {
return item.id.startsWith('delete');
});
assert.equal(deleteItems.length, 1);
assert.notEqual(deleteItems[0].name);
assert.equal(deleteItems[0].name, 'Delete change');
done();
});
});
test('get revision object from change', () => {
const revObj = {_number: 2, foo: 'bar'};
const change = {
revisions: {
rev1: {_number: 1},
rev2: revObj,
},
};
assert.deepEqual(element._getRevision(change, '2'), revObj);
});
test('_actionComparator sort order', () => {
const actions = [
{label: '123', __type: 'change', __key: 'review'},
{label: 'abc-ro', __type: 'revision'},
{label: 'abc', __type: 'change'},
{label: 'def', __type: 'change'},
{label: 'def-p', __type: 'change', __primary: true},
];
const result = actions.slice();
result.reverse();
result.sort(element._actionComparator.bind(element));
assert.deepEqual(result, actions);
});
test('submit change', done => {
sandbox.stub(element.$.restAPI, 'getFromProjectLookup')
.returns(Promise.resolve('test'));
sandbox.stub(element, 'fetchChangeUpdates',
() => { return Promise.resolve({isLatest: true}); });
element.change = {
revisions: {
rev1: {_number: 1},
rev2: {_number: 2},
},
};
element.patchNum = '2';
flush(() => {
const submitButton = element.$$('gr-button[data-action-key="submit"]');
assert.ok(submitButton);
MockInteractions.tap(submitButton);
// Upon success it should fire the reload-change event.
element.addEventListener('reload-change', () => {
done();
});
});
});
test('submit change with plugin hook', done => {
sandbox.stub(element, '_canSubmitChange',
() => { return false; });
const fireActionStub = sandbox.stub(element, '_fireAction');
flush(() => {
const submitButton = element.$$('gr-button[data-action-key="submit"]');
assert.ok(submitButton);
MockInteractions.tap(submitButton);
assert.equal(fireActionStub.callCount, 0);
done();
});
});
test('chain state', () => {
assert.equal(element._hasKnownChainState, false);
element.hasParent = true;
assert.equal(element._hasKnownChainState, true);
element.hasParent = false;
});
test('_calculateDisabled', () => {
let hasKnownChainState = false;
const action = {__key: 'rebase', enabled: true};
assert.equal(
element._calculateDisabled(action, hasKnownChainState), true);
action.__key = 'delete';
assert.equal(
element._calculateDisabled(action, hasKnownChainState), false);
action.__key = 'rebase';
hasKnownChainState = true;
assert.equal(
element._calculateDisabled(action, hasKnownChainState), false);
action.enabled = false;
assert.equal(
element._calculateDisabled(action, hasKnownChainState), true);
});
test('rebase change', done => {
const fireActionStub = sandbox.stub(element, '_fireAction');
flush(() => {
const rebaseButton = element.$$('gr-button[data-action-key="rebase"]');
MockInteractions.tap(rebaseButton);
const rebaseAction = {
__key: 'rebase',
__type: 'revision',
__primary: false,
enabled: true,
label: 'Rebase',
method: 'POST',
title: 'Rebase onto tip of branch or parent change',
};
// rebase on other
element.$.confirmRebase.base = '1234';
element._handleRebaseConfirm();
assert.deepEqual(fireActionStub.lastCall.args,
['/rebase', rebaseAction, true, {base: '1234'}]);
// rebase on parent
element.$.confirmRebase.base = null;
element._handleRebaseConfirm();
assert.deepEqual(fireActionStub.lastCall.args,
['/rebase', rebaseAction, true, {base: null}]);
// rebase on tip
element.$.confirmRebase.base = '';
element._handleRebaseConfirm();
assert.deepEqual(fireActionStub.lastCall.args,
['/rebase', rebaseAction, true, {base: ''}]);
done();
});
});
test('two dialogs are not shown at the same time', done => {
element._hasKnownChainState = true;
flush(() => {
const rebaseButton = element.$$('gr-button[data-action-key="rebase"]');
assert.ok(rebaseButton);
MockInteractions.tap(rebaseButton);
flushAsynchronousOperations();
assert.isFalse(element.$.confirmRebase.hidden);
element._handleCherrypickTap();
flushAsynchronousOperations();
assert.isTrue(element.$.confirmRebase.hidden);
assert.isFalse(element.$.confirmCherrypick.hidden);
done();
});
});
test('fullscreen-overlay-opened hides content', () => {
sandbox.spy(element, '_handleHideBackgroundContent');
element.$.overlay.fire('fullscreen-overlay-opened');
assert.isTrue(element._handleHideBackgroundContent.called);
assert.isTrue(element.$.mainContent.classList.contains('overlayOpen'));
});
test('fullscreen-overlay-closed shows content', () => {
sandbox.spy(element, '_handleShowBackgroundContent');
element.$.overlay.fire('fullscreen-overlay-closed');
assert.isTrue(element._handleShowBackgroundContent.called);
assert.isFalse(element.$.mainContent.classList.contains('overlayOpen'));
});
suite('change edits', () => {
let fireActionStub;
const deleteEditAction = {
enabled: true,
label: 'Delete edit',
title: 'Delete change edit',
__key: 'deleteEdit',
__primary: false,
__type: 'change',
method: 'DELETE',
};
const publishEditAction = {
enabled: true,
label: 'Publish edit',
title: 'Publish change edit',
__key: 'publishEdit',
__primary: false,
__type: 'change',
method: 'POST',
};
const rebaseEditAction = {
enabled: true,
label: 'Rebase edit',
title: 'Rebase change edit',
__key: 'rebaseEdit',
__primary: false,
__type: 'change',
method: 'POST',
};
setup(() => {
fireActionStub = sandbox.stub(element, '_fireAction');
element.patchNum = 'edit';
element.editLoaded = true;
});
test('does not delete edit on action', () => {
element._handleDeleteEditTap();
assert.isFalse(fireActionStub.called);
});
test('shows confirm dialog for delete edit', () => {
element._handleDeleteEditTap();
assert.isFalse(element.$$('#confirmDeleteEditDialog').hidden);
assert.ok(element.$$('gr-button[data-action-key="deleteEdit"]'));
MockInteractions.tap(
element.$$('#confirmDeleteEditDialog').$$('gr-button[primary]'));
flushAsynchronousOperations();
assert.isTrue(
fireActionStub.calledWith('/edit', deleteEditAction, false));
});
test('show publish edit but rebaseEdit is hidden', () => {
element.change = {
status: 'NEW',
};
const rebaseEditButton =
element.$$('gr-button[data-action-key="rebaseEdit"]');
assert.isNotOk(rebaseEditButton);
const publishEditButton =
element.$$('gr-button[data-action-key="publishEdit"]');
assert.ok(publishEditButton);
MockInteractions.tap(publishEditButton);
element._handlePublishEditTap();
flushAsynchronousOperations();
assert.isTrue(
fireActionStub.calledWith('/edit:publish', publishEditAction, false));
});
test('show rebase edit but publishEdit is hidden', () => {
element.change = {
status: 'NEW',
};
element.editBasedOnCurrentPatchSet = false;
const publishEditButton =
element.$$('gr-button[data-action-key="publishEdit"]');
assert.isNotOk(publishEditButton);
const rebaseEditButton =
element.$$('gr-button[data-action-key="rebaseEdit"]');
assert.ok(rebaseEditButton);
MockInteractions.tap(rebaseEditButton);
element._handleRebaseEditTap();
flushAsynchronousOperations();
assert.isTrue(
fireActionStub.calledWith('/edit:rebase', rebaseEditAction, false));
});
test('hide publishEdit and rebaseEdit if change is not open', () => {
element.change = {
status: 'MERGED',
};
flushAsynchronousOperations();
const publishEditButton =
element.$$('gr-button[data-action-key="publishEdit"]');
assert.isNotOk(publishEditButton);
const rebaseEditButton =
element.$$('gr-button[data-action-key="rebaseEdit"]');
assert.isNotOk(rebaseEditButton);
const deleteEditButton =
element.$$('gr-button[data-action-key="deleteEdit"]');
assert.ok(deleteEditButton);
});
test('do not show delete edit on a non change edit', () => {
element.editLoaded = false;
flushAsynchronousOperations();
const deleteEditButton =
element.$$('gr-button[data-action-key="deleteEdit"]');
assert.isNotOk(deleteEditButton);
});
test('do not show publish edit on a non change edit', () => {
element.change = {
status: 'NEW',
};
element.editLoaded = false;
flushAsynchronousOperations();
const publishEditButton =
element.$$('gr-button[data-action-key="publishEdit"]');
assert.isNotOk(publishEditButton);
});
});
suite('cherry-pick', () => {
let fireActionStub;
setup(() => {
fireActionStub = sandbox.stub(element, '_fireAction');
sandbox.stub(window, 'alert');
});
test('works', () => {
element._handleCherrypickTap();
const action = {
__key: 'cherrypick',
__type: 'revision',
__primary: false,
enabled: true,
label: 'Cherry pick',
method: 'POST',
title: 'Cherry pick change to a different branch',
};
element._handleCherrypickConfirm();
assert.equal(fireActionStub.callCount, 0);
element.$.confirmCherrypick.branch = 'master';
element._handleCherrypickConfirm();
assert.equal(fireActionStub.callCount, 0); // Still needs a message.
// Add attributes that are used to determine the message.
element.$.confirmCherrypick.commitMessage = 'foo message';
element.$.confirmCherrypick.changeStatus = 'OPEN';
element.$.confirmCherrypick.commitNum = '123';
element._handleCherrypickConfirm();
assert.equal(element.$.confirmCherrypick.$.messageInput.value,
'foo message');
assert.deepEqual(fireActionStub.lastCall.args, [
'/cherrypick', action, true, {
destination: 'master',
message: 'foo message',
},
]);
});
test('branch name cleared when re-open cherrypick', () => {
const emptyBranchName = '';
element.$.confirmCherrypick.branch = 'master';
element._handleCherrypickTap();
assert.equal(element.$.confirmCherrypick.branch, emptyBranchName);
});
});
suite('move change', () => {
let fireActionStub;
setup(() => {
fireActionStub = sandbox.stub(element, '_fireAction');
sandbox.stub(window, 'alert');
});
test('works', () => {
element._handleMoveTap();
element._handleMoveConfirm();
assert.equal(fireActionStub.callCount, 0);
element.$.confirmMove.branch = 'master';
element._handleMoveConfirm();
assert.equal(fireActionStub.callCount, 1);
});
test('branch name cleared when re-open move', () => {
const emptyBranchName = '';
element.$.confirmMove.branch = 'master';
element._handleMoveTap();
assert.equal(element.$.confirmMove.branch, emptyBranchName);
});
});
test('custom actions', done => {
// Add a button with the same key as a server-based one to ensure
// collisions are taken care of.
const key = element.addActionButton(element.ActionType.REVISION, 'Bork!');
element.addEventListener(key + '-tap', e => {
assert.equal(e.detail.node.getAttribute('data-action-key'), key);
element.removeActionButton(key);
flush(() => {
assert.notOk(element.$$('[data-action-key="' + key + '"]'));
done();
});
});
flush(() => {
MockInteractions.tap(element.$$('[data-action-key="' + key + '"]'));
});
});
test('_setLoadingOnButtonWithKey top-level', () => {
const key = 'rebase';
const type = 'revision';
const cleanup = element._setLoadingOnButtonWithKey(type, key);
assert.equal(element._actionLoadingMessage, 'Rebasing...');
const button = element.$$('[data-action-key="' + key + '"]');
assert.isTrue(button.hasAttribute('loading'));
assert.isTrue(button.disabled);
assert.isOk(cleanup);
assert.isFunction(cleanup);
cleanup();
assert.isFalse(button.hasAttribute('loading'));
assert.isFalse(button.disabled);
assert.isNotOk(element._actionLoadingMessage);
});
test('_setLoadingOnButtonWithKey overflow menu', () => {
const key = 'cherrypick';
const type = 'revision';
const cleanup = element._setLoadingOnButtonWithKey(type, key);
assert.equal(element._actionLoadingMessage, 'Cherry-picking...');
assert.include(element._disabledMenuActions, 'cherrypick');
assert.isFunction(cleanup);
cleanup();
assert.notOk(element._actionLoadingMessage);
assert.notInclude(element._disabledMenuActions, 'cherrypick');
});
suite('revert change', () => {
let alertStub;
let fireActionStub;
setup(() => {
fireActionStub = sandbox.stub(element, '_fireAction');
alertStub = sandbox.stub(window, 'alert');
element.actions = {
revert: {
method: 'POST',
label: 'Revert',
title: 'Revert the change',
enabled: true,
},
};
return element.reload();
});
test('revert change with plugin hook', done => {
element.change = {
current_revision: 'abc1234',
};
const newRevertMsg = 'Modified revert msg';
sandbox.stub(element, '_modifyRevertMsg',
() => { return newRevertMsg; });
sandbox.stub(element.$.confirmRevertDialog, 'populateRevertMessage',
() => { return 'original msg'; });
flush(() => {
const revertButton =
element.$$('gr-button[data-action-key="revert"]');
MockInteractions.tap(revertButton);
assert.equal(element.$.confirmRevertDialog.message, newRevertMsg);
done();
});
});
test('works', () => {
element.change = {
current_revision: 'abc1234',
};
sandbox.stub(element.$.confirmRevertDialog, 'populateRevertMessage',
() => { return 'original msg'; });
const revertButton = element.$$('gr-button[data-action-key="revert"]');
MockInteractions.tap(revertButton);
element.$.confirmRevertDialog.message = 'foo message';
element._handleRevertDialogConfirm();
assert.notOk(alertStub.called);
const action = {
__key: 'revert',
__type: 'change',
__primary: false,
enabled: true,
label: 'Revert',
method: 'POST',
title: 'Revert the change',
};
assert.deepEqual(fireActionStub.lastCall.args, [
'/revert', action, false, {
message: 'foo message',
}]);
});
});
suite('mark change private', () => {
setup(() => {
const privateAction = {
__key: 'private',
__type: 'change',
__primary: false,
method: 'POST',
label: 'Mark private',
title: 'Working...',
enabled: true,
};
element.actions = {
private: privateAction,
};
element.change.is_private = false;
element.changeNum = '2';
element.patchNum = '2';
return element.reload();
});
test('make sure the mark private change button is not outside of the ' +
'overflow menu', done => {
flush(() => {
assert.isNotOk(element.$$('[data-action-key="private"]'));
done();
});
});
test('private change', done => {
flush(() => {
assert.isOk(
element.$.moreActions.$$('span[data-id="private-change"]'));
element.setActionOverflow('change', 'private', false);
flushAsynchronousOperations();
assert.isOk(element.$$('[data-action-key="private"]'));
assert.isNotOk(
element.$.moreActions.$$('span[data-id="private-change"]'));
done();
});
});
});
suite('unmark private change', () => {
setup(() => {
const unmarkPrivateAction = {
__key: 'private.delete',
__type: 'change',
__primary: false,
method: 'POST',
label: 'Unmark private',
title: 'Working...',
enabled: true,
};
element.actions = {
'private.delete': unmarkPrivateAction,
};
element.change.is_private = true;
element.changeNum = '2';
element.patchNum = '2';
return element.reload();
});
test('make sure the unmark private change button is not outside of the ' +
'overflow menu', done => {
flush(() => {
assert.isNotOk(element.$$('[data-action-key="private.delete"]'));
done();
});
});
test('unmark the private change', done => {
flush(() => {
assert.isOk(
element.$.moreActions.$$('span[data-id="private.delete-change"]')
);
element.setActionOverflow('change', 'private.delete', false);
flushAsynchronousOperations();
assert.isOk(element.$$('[data-action-key="private.delete"]'));
assert.isNotOk(
element.$.moreActions.$$('span[data-id="private.delete-change"]')
);
done();
});
});
});
suite('delete change', () => {
let fireActionStub;
let deleteAction;
setup(() => {
fireActionStub = sandbox.stub(element, '_fireAction');
element.change = {
current_revision: 'abc1234',
};
deleteAction = {
method: 'DELETE',
label: 'Delete Change',
title: 'Delete change X_X',
enabled: true,
};
element.actions = {
'/': deleteAction,
};
});
test('does not delete on action', () => {
element._handleDeleteTap();
assert.isFalse(fireActionStub.called);
});
test('shows confirm dialog', () => {
element._handleDeleteTap();
assert.isFalse(element.$$('#confirmDeleteDialog').hidden);
MockInteractions.tap(
element.$$('#confirmDeleteDialog').$$('gr-button[primary]'));
flushAsynchronousOperations();
assert.isTrue(fireActionStub.calledWith('/', deleteAction, false));
});
test('hides delete confirm on cancel', () => {
element._handleDeleteTap();
MockInteractions.tap(
element.$$('#confirmDeleteDialog').$$('gr-button:not([primary])'));
flushAsynchronousOperations();
assert.isTrue(element.$$('#confirmDeleteDialog').hidden);
assert.isFalse(fireActionStub.called);
});
});
suite('ignore change', () => {
setup(done => {
sandbox.stub(element, '_fireAction');
const IgnoreAction = {
__key: 'ignore',
__type: 'change',
__primary: false,
method: 'PUT',
label: 'Ignore',
title: 'Working...',
enabled: true,
};
element.actions = {
ignore: IgnoreAction,
};
element.changeNum = '2';
element.patchNum = '2';
element.reload().then(() => { flush(done); });
});
test('make sure the ignore button is not outside of the overflow menu',
() => {
assert.isNotOk(element.$$('[data-action-key="ignore"]'));
});
test('ignoring change', () => {
assert.isOk(element.$.moreActions.$$('span[data-id="ignore-change"]'));
element.setActionOverflow('change', 'ignore', false);
flushAsynchronousOperations();
assert.isOk(element.$$('[data-action-key="ignore"]'));
assert.isNotOk(
element.$.moreActions.$$('span[data-id="ignore-change"]'));
});
});
suite('unignore change', () => {
setup(done => {
sandbox.stub(element, '_fireAction');
const UnignoreAction = {
__key: 'unignore',
__type: 'change',
__primary: false,
method: 'PUT',
label: 'Unignore',
title: 'Working...',
enabled: true,
};
element.actions = {
unignore: UnignoreAction,
};
element.changeNum = '2';
element.patchNum = '2';
element.reload().then(() => { flush(done); });
});
test('unignore button is not outside of the overflow menu', () => {
assert.isNotOk(element.$$('[data-action-key="unignore"]'));
});
test('unignoring change', () => {
assert.isOk(
element.$.moreActions.$$('span[data-id="unignore-change"]'));
element.setActionOverflow('change', 'unignore', false);
flushAsynchronousOperations();
assert.isOk(element.$$('[data-action-key="unignore"]'));
assert.isNotOk(
element.$.moreActions.$$('span[data-id="unignore-change"]'));
});
});
suite('reviewed change', () => {
setup(done => {
sandbox.stub(element, '_fireAction');
const ReviewedAction = {
__key: 'reviewed',
__type: 'change',
__primary: false,
method: 'PUT',
label: 'Mark reviewed',
title: 'Working...',
enabled: true,
};
element.actions = {
reviewed: ReviewedAction,
};
element.changeNum = '2';
element.patchNum = '2';
element.reload().then(() => { flush(done); });
});
test('make sure the reviewed button is not outside of the overflow menu',
() => {
assert.isNotOk(element.$$('[data-action-key="reviewed"]'));
});
test('reviewing change', () => {
assert.isOk(
element.$.moreActions.$$('span[data-id="reviewed-change"]'));
element.setActionOverflow('change', 'reviewed', false);
flushAsynchronousOperations();
assert.isOk(element.$$('[data-action-key="reviewed"]'));
assert.isNotOk(
element.$.moreActions.$$('span[data-id="reviewed-change"]'));
});
});
suite('unreviewed change', () => {
setup(done => {
sandbox.stub(element, '_fireAction');
const UnreviewedAction = {
__key: 'unreviewed',
__type: 'change',
__primary: false,
method: 'PUT',
label: 'Mark unreviewed',
title: 'Working...',
enabled: true,
};
element.actions = {
unreviewed: UnreviewedAction,
};
element.changeNum = '2';
element.patchNum = '2';
element.reload().then(() => { flush(done); });
});
test('unreviewed button not outside of the overflow menu', () => {
assert.isNotOk(element.$$('[data-action-key="unreviewed"]'));
});
test('unreviewed change', () => {
assert.isOk(
element.$.moreActions.$$('span[data-id="unreviewed-change"]'));
element.setActionOverflow('change', 'unreviewed', false);
flushAsynchronousOperations();
assert.isOk(element.$$('[data-action-key="unreviewed"]'));
assert.isNotOk(
element.$.moreActions.$$('span[data-id="unreviewed-change"]'));
});
});
suite('quick approve', () => {
setup(() => {
element.change = {
current_revision: 'abc1234',
};
element.change = {
current_revision: 'abc1234',
labels: {
foo: {
values: {
'-1': '',
' 0': '',
'+1': '',
},
},
},
permitted_labels: {
foo: ['-1', ' 0', '+1'],
},
};
flushAsynchronousOperations();
});
test('added when can approve', () => {
const approveButton =
element.$$('gr-button[data-action-key=\'review\']');
assert.isNotNull(approveButton);
});
test('is first in list of secondary actions', () => {
const approveButton = element.$.secondaryActions
.querySelector('gr-button');
assert.equal(approveButton.getAttribute('data-label'), 'foo+1');
});
test('not added when already approved', () => {
element.change = {
current_revision: 'abc1234',
labels: {
foo: {
approved: {},
values: {},
},
},
permitted_labels: {
foo: [' 0', '+1'],
},
};
flushAsynchronousOperations();
const approveButton =
element.$$('gr-button[data-action-key=\'review\']');
assert.isNull(approveButton);
});
test('not added when label not permitted', () => {
element.change = {
current_revision: 'abc1234',
labels: {
foo: {values: {}},
},
permitted_labels: {
bar: [],
},
};
flushAsynchronousOperations();
const approveButton =
element.$$('gr-button[data-action-key=\'review\']');
assert.isNull(approveButton);
});
test('approves when tapped', () => {
const fireActionStub = sandbox.stub(element, '_fireAction');
MockInteractions.tap(
element.$$('gr-button[data-action-key=\'review\']'));
flushAsynchronousOperations();
assert.isTrue(fireActionStub.called);
assert.isTrue(fireActionStub.calledWith('/review'));
const payload = fireActionStub.lastCall.args[3];
assert.deepEqual(payload.labels, {foo: '+1'});
});
test('not added when multiple labels are required', () => {
element.change = {
current_revision: 'abc1234',
labels: {
foo: {values: {}},
bar: {values: {}},
},
permitted_labels: {
foo: [' 0', '+1'],
bar: [' 0', '+1', '+2'],
},
};
flushAsynchronousOperations();
const approveButton =
element.$$('gr-button[data-action-key=\'review\']');
assert.isNull(approveButton);
});
test('button label for missing approval', () => {
element.change = {
current_revision: 'abc1234',
labels: {
foo: {
values: {
' 0': '',
'+1': '',
},
},
bar: {approved: {}, values: {}},
},
permitted_labels: {
foo: [' 0', '+1'],
bar: [' 0', '+1', '+2'],
},
};
flushAsynchronousOperations();
const approveButton =
element.$$('gr-button[data-action-key=\'review\']');
assert.equal(approveButton.getAttribute('data-label'), 'foo+1');
});
test('no quick approve if score is not maximal for a label', () => {
element.change = {
current_revision: 'abc1234',
labels: {
bar: {
value: 1,
values: {
' 0': '',
'+1': '',
'+2': '',
},
},
},
permitted_labels: {
bar: [' 0', '+1'],
},
};
flushAsynchronousOperations();
const approveButton =
element.$$('gr-button[data-action-key=\'review\']');
assert.isNull(approveButton);
});
test('approving label with a non-max score', () => {
element.change = {
current_revision: 'abc1234',
labels: {
bar: {
value: 1,
values: {
' 0': '',
'+1': '',
'+2': '',
},
},
},
permitted_labels: {
bar: [' 0', '+1', '+2'],
},
};
flushAsynchronousOperations();
const approveButton =
element.$$('gr-button[data-action-key=\'review\']');
assert.equal(approveButton.getAttribute('data-label'), 'bar+2');
});
});
test('adds download revision action', () => {
const handler = sandbox.stub();
element.addEventListener('download-tap', handler);
assert.ok(element.revisionActions.download);
element._handleDownloadTap();
flushAsynchronousOperations();
assert.isTrue(handler.called);
});
test('changing changeNum or patchNum does not reload', () => {
const reloadStub = sandbox.stub(element, 'reload');
element.changeNum = 123;
assert.isFalse(reloadStub.called);
element.patchNum = 456;
assert.isFalse(reloadStub.called);
});
test('_toSentenceCase', () => {
assert.equal(element._toSentenceCase('blah blah'), 'Blah blah');
assert.equal(element._toSentenceCase('BLAH BLAH'), 'Blah blah');
assert.equal(element._toSentenceCase('b'), 'B');
assert.equal(element._toSentenceCase(''), '');
assert.equal(element._toSentenceCase('!@#$%^&*()'), '!@#$%^&*()');
});
suite('setActionOverflow', () => {
test('move action from overflow', () => {
assert.isNotOk(element.$$('[data-action-key="cherrypick"]'));
assert.strictEqual(
element.$.moreActions.items[0].id, 'cherrypick-revision');
element.setActionOverflow('revision', 'cherrypick', false);
flushAsynchronousOperations();
assert.isOk(element.$$('[data-action-key="cherrypick"]'));
assert.notEqual(
element.$.moreActions.items[0].id, 'cherrypick-revision');
});
test('move action to overflow', () => {
assert.isOk(element.$$('[data-action-key="submit"]'));
element.setActionOverflow('revision', 'submit', true);
flushAsynchronousOperations();
assert.isNotOk(element.$$('[data-action-key="submit"]'));
assert.strictEqual(
element.$.moreActions.items[3].id, 'submit-revision');
});
suite('_waitForChangeReachable', () => {
setup(() => {
sandbox.stub(element, 'async', fn => fn());
});
const makeGetChange = numTries => {
return () => {
if (numTries === 1) {
return Promise.resolve({_number: 123});
} else {
numTries--;
return Promise.resolve(undefined);
}
};
};
test('succeed', () => {
sandbox.stub(element.$.restAPI, 'getChange', makeGetChange(5));
return element._waitForChangeReachable(123).then(success => {
assert.isTrue(success);
});
});
test('fail', () => {
sandbox.stub(element.$.restAPI, 'getChange', makeGetChange(6));
return element._waitForChangeReachable(123).then(success => {
assert.isFalse(success);
});
});
});
});
suite('_send', () => {
let cleanup;
let payload;
let onShowAlert;
setup(() => {
cleanup = sinon.stub();
element.changeNum = 42;
element.patchNum = 12;
payload = {foo: 'bar'};
onShowAlert = sinon.stub();
element.addEventListener('show-alert', onShowAlert);
});
suite('happy path', () => {
let sendStub;
setup(() => {
sandbox.stub(element, 'fetchChangeUpdates')
.returns(Promise.resolve({isLatest: true}));
sendStub = sandbox.stub(element.$.restAPI, 'getChangeURLAndSend')
.returns(Promise.resolve({}));
});
test('change action', () => {
return element._send('DELETE', payload, '/endpoint', false, cleanup)
.then(() => {
assert.isFalse(onShowAlert.called);
assert.isTrue(cleanup.calledOnce);
assert.isTrue(sendStub.calledWith(42, 'DELETE', null,
'/endpoint', payload));
});
});
test('revision action', () => {
return element._send('DELETE', payload, '/endpoint', true, cleanup)
.then(() => {
assert.isFalse(onShowAlert.called);
assert.isTrue(cleanup.calledOnce);
assert.isTrue(sendStub.calledWith(42, 'DELETE', 12, '/endpoint',
payload));
});
});
});
suite('failure modes', () => {
test('non-latest', () => {
sandbox.stub(element, 'fetchChangeUpdates')
.returns(Promise.resolve({isLatest: false}));
const sendStub = sandbox.stub(element.$.restAPI,
'getChangeURLAndSend');
return element._send('DELETE', payload, '/endpoint', true, cleanup)
.then(() => {
assert.isTrue(onShowAlert.calledOnce);
assert.isTrue(cleanup.calledOnce);
assert.isFalse(sendStub.called);
});
});
test('send fails', () => {
sandbox.stub(element, 'fetchChangeUpdates')
.returns(Promise.resolve({isLatest: true}));
const sendStub = sandbox.stub(element.$.restAPI,
'getChangeURLAndSend',
(num, method, patchNum, endpoint, payload, onErr) => {
onErr();
return Promise.resolve(null);
});
const handleErrorStub = sandbox.stub(element, '_handleResponseError');
return element._send('DELETE', payload, '/endpoint', true, cleanup)
.then(() => {
assert.isFalse(onShowAlert.called);
assert.isTrue(cleanup.called);
assert.isTrue(sendStub.calledOnce);
assert.isTrue(handleErrorStub.called);
});
});
});
});
});
</script>