
This change fixes some tests in Polymer 2. It touches only tests files. All changes are compatible with Polymer 1. Bug: Issue 10930 Change-Id: I058b8070dba34368f4b19ff74790957be5422696
1272 lines
40 KiB
HTML
1272 lines
40 KiB
HTML
<!DOCTYPE html>
|
|
<!--
|
|
@license
|
|
Copyright (C) 2015 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-reply-dialog</title>
|
|
<script src="/test/common-test-setup.js"></script>
|
|
<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
|
|
|
<script src="/bower_components/webcomponentsjs/webcomponents-lite.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-reply-dialog.html">
|
|
|
|
<script>void(0);</script>
|
|
|
|
<test-fixture id="basic">
|
|
<template>
|
|
<gr-reply-dialog></gr-reply-dialog>
|
|
</template>
|
|
</test-fixture>
|
|
|
|
<script>
|
|
function cloneableResponse(status, text) {
|
|
return {
|
|
ok: false,
|
|
status,
|
|
text() {
|
|
return Promise.resolve(text);
|
|
},
|
|
clone() {
|
|
return {
|
|
ok: false,
|
|
status,
|
|
text() {
|
|
return Promise.resolve(text);
|
|
},
|
|
};
|
|
},
|
|
};
|
|
}
|
|
|
|
suite('gr-reply-dialog tests', () => {
|
|
let element;
|
|
let changeNum;
|
|
let patchNum;
|
|
|
|
let sandbox;
|
|
let getDraftCommentStub;
|
|
let setDraftCommentStub;
|
|
let eraseDraftCommentStub;
|
|
|
|
let lastId = 0;
|
|
const makeAccount = function() { return {_account_id: lastId++}; };
|
|
const makeGroup = function() { return {id: lastId++}; };
|
|
|
|
setup(() => {
|
|
sandbox = sinon.sandbox.create();
|
|
|
|
changeNum = 42;
|
|
patchNum = 1;
|
|
|
|
stub('gr-rest-api-interface', {
|
|
getConfig() { return Promise.resolve({}); },
|
|
getAccount() { return Promise.resolve({}); },
|
|
getChange() { return Promise.resolve({}); },
|
|
getChangeSuggestedReviewers() { return Promise.resolve([]); },
|
|
});
|
|
|
|
element = fixture('basic');
|
|
element.change = {
|
|
_number: changeNum,
|
|
labels: {
|
|
'Verified': {
|
|
values: {
|
|
'-1': 'Fails',
|
|
' 0': 'No score',
|
|
'+1': 'Verified',
|
|
},
|
|
default_value: 0,
|
|
},
|
|
'Code-Review': {
|
|
values: {
|
|
'-2': 'Do not submit',
|
|
'-1': 'I would prefer that you didn\'t submit this',
|
|
' 0': 'No score',
|
|
'+1': 'Looks good to me, but someone else must approve',
|
|
'+2': 'Looks good to me, approved',
|
|
},
|
|
default_value: 0,
|
|
},
|
|
},
|
|
};
|
|
element.patchNum = patchNum;
|
|
element.permittedLabels = {
|
|
'Code-Review': [
|
|
'-1',
|
|
' 0',
|
|
'+1',
|
|
],
|
|
'Verified': [
|
|
'-1',
|
|
' 0',
|
|
'+1',
|
|
],
|
|
};
|
|
|
|
getDraftCommentStub = sandbox.stub(element.$.storage, 'getDraftComment');
|
|
setDraftCommentStub = sandbox.stub(element.$.storage, 'setDraftComment');
|
|
eraseDraftCommentStub = sandbox.stub(element.$.storage,
|
|
'eraseDraftComment');
|
|
|
|
sandbox.stub(element, 'fetchChangeUpdates')
|
|
.returns(Promise.resolve({isLatest: true}));
|
|
|
|
// Allow the elements created by dom-repeat to be stamped.
|
|
flushAsynchronousOperations();
|
|
});
|
|
|
|
teardown(() => {
|
|
sandbox.restore();
|
|
});
|
|
|
|
function stubSaveReview(jsonResponseProducer) {
|
|
return sandbox.stub(element, '_saveReview', review => {
|
|
return new Promise((resolve, reject) => {
|
|
try {
|
|
const result = jsonResponseProducer(review) || {};
|
|
const resultStr =
|
|
element.$.restAPI.JSON_PREFIX + JSON.stringify(result);
|
|
resolve({
|
|
ok: true,
|
|
text() {
|
|
return Promise.resolve(resultStr);
|
|
},
|
|
});
|
|
} catch (err) {
|
|
reject(err);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
test('default to publishing drafts with reply', done => {
|
|
// Async tick is needed because iron-selector content is distributed and
|
|
// distributed content requires an observer to be set up.
|
|
// Note: Double flush seems to be needed in Safari. {@see Issue 4963}.
|
|
flush(() => {
|
|
flush(() => {
|
|
element.draft = 'I wholeheartedly disapprove';
|
|
|
|
stubSaveReview(review => {
|
|
assert.deepEqual(review, {
|
|
drafts: 'PUBLISH_ALL_REVISIONS',
|
|
labels: {
|
|
'Code-Review': 0,
|
|
'Verified': 0,
|
|
},
|
|
message: 'I wholeheartedly disapprove',
|
|
reviewers: [],
|
|
});
|
|
assert.isFalse(element.$.commentList.hidden);
|
|
done();
|
|
});
|
|
|
|
// This is needed on non-Blink engines most likely due to the ways in
|
|
// which the dom-repeat elements are stamped.
|
|
flush(() => {
|
|
MockInteractions.tap(element.$$('.send'));
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
test('keep drafts with reply', done => {
|
|
MockInteractions.tap(element.$$('#includeComments'));
|
|
assert.equal(element._includeComments, false);
|
|
|
|
// Async tick is needed because iron-selector content is distributed and
|
|
// distributed content requires an observer to be set up.
|
|
// Note: Double flush seems to be needed in Safari. {@see Issue 4963}.
|
|
flush(() => {
|
|
flush(() => {
|
|
element.draft = 'I wholeheartedly disapprove';
|
|
|
|
stubSaveReview(review => {
|
|
assert.deepEqual(review, {
|
|
drafts: 'KEEP',
|
|
labels: {
|
|
'Code-Review': 0,
|
|
'Verified': 0,
|
|
},
|
|
message: 'I wholeheartedly disapprove',
|
|
reviewers: [],
|
|
});
|
|
assert.isTrue(element.$.commentList.hidden);
|
|
done();
|
|
});
|
|
|
|
// This is needed on non-Blink engines most likely due to the ways in
|
|
// which the dom-repeat elements are stamped.
|
|
flush(() => {
|
|
MockInteractions.tap(element.$$('.send'));
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
test('label picker', done => {
|
|
element.draft = 'I wholeheartedly disapprove';
|
|
stubSaveReview(review => {
|
|
assert.deepEqual(review, {
|
|
drafts: 'PUBLISH_ALL_REVISIONS',
|
|
labels: {
|
|
'Code-Review': -1,
|
|
'Verified': -1,
|
|
},
|
|
message: 'I wholeheartedly disapprove',
|
|
reviewers: [],
|
|
});
|
|
});
|
|
|
|
sandbox.stub(element.$.labelScores, 'getLabelValues', () => {
|
|
return {
|
|
'Code-Review': -1,
|
|
'Verified': -1,
|
|
};
|
|
});
|
|
|
|
element.addEventListener('send', () => {
|
|
// Flush to ensure properties are updated.
|
|
flush(() => {
|
|
assert.isFalse(element.disabled,
|
|
'Element should be enabled when done sending reply.');
|
|
assert.equal(element.draft.length, 0);
|
|
done();
|
|
});
|
|
});
|
|
|
|
// This is needed on non-Blink engines most likely due to the ways in
|
|
// which the dom-repeat elements are stamped.
|
|
flush(() => {
|
|
MockInteractions.tap(element.$$('.send'));
|
|
assert.isTrue(element.disabled);
|
|
});
|
|
});
|
|
|
|
test('getlabelValue returns value', done => {
|
|
flush(() => {
|
|
element.$$('gr-label-scores').$$(`gr-label-score-row[name="Verified"]`)
|
|
.setSelectedValue(-1);
|
|
assert.equal('-1', element.getLabelValue('Verified'));
|
|
done();
|
|
});
|
|
});
|
|
|
|
test('getlabelValue when no score is selected', done => {
|
|
flush(() => {
|
|
element.$$('gr-label-scores')
|
|
.$$(`gr-label-score-row[name="Code-Review"]`).setSelectedValue(-1);
|
|
assert.strictEqual(element.getLabelValue('Verified'), ' 0');
|
|
done();
|
|
});
|
|
});
|
|
|
|
test('setlabelValue', done => {
|
|
element._account = {_account_id: 1};
|
|
flush(() => {
|
|
const label = 'Verified';
|
|
const value = '+1';
|
|
element.setLabelValue(label, value);
|
|
|
|
const labels = element.$.labelScores.getLabelValues();
|
|
assert.deepEqual(labels, {
|
|
'Code-Review': 0,
|
|
'Verified': 1,
|
|
});
|
|
done();
|
|
});
|
|
});
|
|
|
|
function getActiveElement() {
|
|
return Polymer.IronOverlayManager.deepActiveElement;
|
|
}
|
|
|
|
function isVisible(el) {
|
|
assert.ok(el);
|
|
return getComputedStyle(el).getPropertyValue('display') != 'none';
|
|
}
|
|
|
|
function overlayObserver(mode) {
|
|
return new Promise(resolve => {
|
|
function listener() {
|
|
element.removeEventListener('iron-overlay-' + mode, listener);
|
|
resolve();
|
|
}
|
|
element.addEventListener('iron-overlay-' + mode, listener);
|
|
});
|
|
}
|
|
|
|
function isFocusInsideElement(element) {
|
|
// In Polymer 2 focused element either <paper-input> or nested
|
|
// native input <input> element depending on the current focus
|
|
// in browser window.
|
|
// For example, the focus is changed if the developer console
|
|
// get a focus.
|
|
let activeElement = getActiveElement();
|
|
while (activeElement) {
|
|
if (activeElement === element) {
|
|
return true;
|
|
}
|
|
if (activeElement.parentElement) {
|
|
activeElement = activeElement.parentElement;
|
|
} else {
|
|
activeElement = activeElement.getRootNode().host;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function testConfirmationDialog(done, cc) {
|
|
const yesButton =
|
|
element.$$('.reviewerConfirmationButtons gr-button:first-child');
|
|
const noButton =
|
|
element.$$('.reviewerConfirmationButtons gr-button:last-child');
|
|
|
|
element._ccPendingConfirmation = null;
|
|
element._reviewerPendingConfirmation = null;
|
|
flushAsynchronousOperations();
|
|
assert.isFalse(isVisible(element.$.reviewerConfirmationOverlay));
|
|
|
|
// Cause the confirmation dialog to display.
|
|
let observer = overlayObserver('opened');
|
|
const group = {
|
|
id: 'id',
|
|
name: 'name',
|
|
};
|
|
if (cc) {
|
|
element._ccPendingConfirmation = {
|
|
group,
|
|
count: 10,
|
|
};
|
|
} else {
|
|
element._reviewerPendingConfirmation = {
|
|
group,
|
|
count: 10,
|
|
};
|
|
}
|
|
flushAsynchronousOperations();
|
|
|
|
if (cc) {
|
|
assert.deepEqual(
|
|
element._ccPendingConfirmation,
|
|
element._pendingConfirmationDetails);
|
|
} else {
|
|
assert.deepEqual(
|
|
element._reviewerPendingConfirmation,
|
|
element._pendingConfirmationDetails);
|
|
}
|
|
|
|
observer.then(() => {
|
|
assert.isTrue(isVisible(element.$.reviewerConfirmationOverlay));
|
|
observer = overlayObserver('closed');
|
|
const expected = 'Group name has 10 members';
|
|
assert.notEqual(
|
|
element.$.reviewerConfirmationOverlay.innerText.indexOf(expected),
|
|
-1);
|
|
MockInteractions.tap(noButton); // close the overlay
|
|
return observer;
|
|
}).then(() => {
|
|
assert.isFalse(isVisible(element.$.reviewerConfirmationOverlay));
|
|
|
|
// We should be focused on account entry input.
|
|
assert.isTrue(
|
|
isFocusInsideElement(element.$.reviewers.$.entry.$.input.$.input));
|
|
|
|
// No reviewer/CC should have been added.
|
|
assert.equal(element.$$('#ccs').additions().length, 0);
|
|
assert.equal(element.$.reviewers.additions().length, 0);
|
|
|
|
// Reopen confirmation dialog.
|
|
observer = overlayObserver('opened');
|
|
if (cc) {
|
|
element._ccPendingConfirmation = {
|
|
group,
|
|
count: 10,
|
|
};
|
|
} else {
|
|
element._reviewerPendingConfirmation = {
|
|
group,
|
|
count: 10,
|
|
};
|
|
}
|
|
return observer;
|
|
}).then(() => {
|
|
assert.isTrue(isVisible(element.$.reviewerConfirmationOverlay));
|
|
observer = overlayObserver('closed');
|
|
MockInteractions.tap(yesButton); // Confirm the group.
|
|
return observer;
|
|
}).then(() => {
|
|
assert.isFalse(isVisible(element.$.reviewerConfirmationOverlay));
|
|
const additions = cc ?
|
|
element.$$('#ccs').additions() :
|
|
element.$.reviewers.additions();
|
|
assert.deepEqual(
|
|
additions,
|
|
[
|
|
{
|
|
group: {
|
|
id: 'id',
|
|
name: 'name',
|
|
confirmed: true,
|
|
_group: true,
|
|
_pendingAdd: true,
|
|
},
|
|
},
|
|
]);
|
|
|
|
// We should be focused on account entry input.
|
|
if (cc) {
|
|
assert.isTrue(
|
|
isFocusInsideElement(element.$.ccs.$.entry.$.input.$.input));
|
|
} else {
|
|
assert.isTrue(
|
|
isFocusInsideElement(element.$.reviewers.$.entry.$.input.$.input));
|
|
}
|
|
}).then(done);
|
|
}
|
|
|
|
test('cc confirmation', done => {
|
|
testConfirmationDialog(done, true);
|
|
});
|
|
|
|
test('reviewer confirmation', done => {
|
|
testConfirmationDialog(done, false);
|
|
});
|
|
|
|
test('_getStorageLocation', () => {
|
|
const actual = element._getStorageLocation();
|
|
assert.equal(actual.changeNum, changeNum);
|
|
assert.equal(actual.patchNum, '@change');
|
|
assert.equal(actual.path, '@change');
|
|
});
|
|
|
|
test('_reviewersMutated when account-text-change is fired from ccs', () => {
|
|
flushAsynchronousOperations();
|
|
assert.isFalse(element._reviewersMutated);
|
|
assert.isTrue(element.$$('#ccs').allowAnyInput);
|
|
assert.isFalse(element.$$('#reviewers').allowAnyInput);
|
|
element.$$('#ccs').dispatchEvent(new CustomEvent('account-text-changed',
|
|
{bubbles: true, composed: true}));
|
|
assert.isTrue(element._reviewersMutated);
|
|
});
|
|
|
|
test('gets draft from storage on open', () => {
|
|
const storedDraft = 'hello world';
|
|
getDraftCommentStub.returns({message: storedDraft});
|
|
element.open();
|
|
assert.isTrue(getDraftCommentStub.called);
|
|
assert.equal(element.draft, storedDraft);
|
|
});
|
|
|
|
test('gets draft from storage even when text is already present', () => {
|
|
const storedDraft = 'hello world';
|
|
getDraftCommentStub.returns({message: storedDraft});
|
|
element.draft = 'foo bar';
|
|
element.open();
|
|
assert.isTrue(getDraftCommentStub.called);
|
|
assert.equal(element.draft, storedDraft);
|
|
});
|
|
|
|
test('blank if no stored draft', () => {
|
|
getDraftCommentStub.returns(null);
|
|
element.draft = 'foo bar';
|
|
element.open();
|
|
assert.isTrue(getDraftCommentStub.called);
|
|
assert.equal(element.draft, '');
|
|
});
|
|
|
|
test('does not check stored draft when quote is present', () => {
|
|
const storedDraft = 'hello world';
|
|
const quote = '> foo bar';
|
|
getDraftCommentStub.returns({message: storedDraft});
|
|
element.quote = quote;
|
|
element.open();
|
|
assert.isFalse(getDraftCommentStub.called);
|
|
assert.equal(element.draft, quote);
|
|
assert.isNotOk(element.quote);
|
|
});
|
|
|
|
test('updates stored draft on edits', () => {
|
|
const firstEdit = 'hello';
|
|
const location = element._getStorageLocation();
|
|
|
|
element.draft = firstEdit;
|
|
element.flushDebouncer('store');
|
|
|
|
assert.isTrue(setDraftCommentStub.calledWith(location, firstEdit));
|
|
|
|
element.draft = '';
|
|
element.flushDebouncer('store');
|
|
|
|
assert.isTrue(eraseDraftCommentStub.calledWith(location));
|
|
});
|
|
|
|
test('400 converts to human-readable server-error', done => {
|
|
sandbox.stub(window, 'fetch', () => {
|
|
const text = '....{"reviewers":{"id1":{"error":"first error"}},' +
|
|
'"ccs":{"id2":{"error":"second error"}}}';
|
|
return Promise.resolve(cloneableResponse(400, text));
|
|
});
|
|
|
|
element.addEventListener('server-error', event => {
|
|
if (event.target !== element) {
|
|
return;
|
|
}
|
|
event.detail.response.text().then(body => {
|
|
assert.equal(body, 'first error, second error');
|
|
done();
|
|
});
|
|
});
|
|
|
|
// Async tick is needed because iron-selector content is distributed and
|
|
// distributed content requires an observer to be set up.
|
|
flush(() => { element.send(); });
|
|
});
|
|
|
|
test('non-json 400 is treated as a normal server-error', done => {
|
|
sandbox.stub(window, 'fetch', () => {
|
|
const text = 'Comment validation error!';
|
|
return Promise.resolve(cloneableResponse(400, text));
|
|
});
|
|
|
|
element.addEventListener('server-error', event => {
|
|
if (event.target !== element) {
|
|
return;
|
|
}
|
|
event.detail.response.text().then(body => {
|
|
assert.equal(body, 'Comment validation error!');
|
|
done();
|
|
});
|
|
});
|
|
|
|
// Async tick is needed because iron-selector content is distributed and
|
|
// distributed content requires an observer to be set up.
|
|
flush(() => { element.send(); });
|
|
});
|
|
|
|
test('filterReviewerSuggestion', () => {
|
|
const owner = makeAccount();
|
|
const reviewer1 = makeAccount();
|
|
const reviewer2 = makeGroup();
|
|
const cc1 = makeAccount();
|
|
const cc2 = makeGroup();
|
|
let filter = element._filterReviewerSuggestionGenerator(false);
|
|
|
|
element._owner = owner;
|
|
element._reviewers = [reviewer1, reviewer2];
|
|
element._ccs = [cc1, cc2];
|
|
|
|
assert.isTrue(filter({account: makeAccount()}));
|
|
assert.isTrue(filter({group: makeGroup()}));
|
|
|
|
// Owner should be excluded.
|
|
assert.isFalse(filter({account: owner}));
|
|
|
|
// Existing and pending reviewers should be excluded when isCC = false.
|
|
assert.isFalse(filter({account: reviewer1}));
|
|
assert.isFalse(filter({group: reviewer2}));
|
|
|
|
filter = element._filterReviewerSuggestionGenerator(true);
|
|
|
|
// Existing and pending CCs should be excluded when isCC = true;.
|
|
assert.isFalse(filter({account: cc1}));
|
|
assert.isFalse(filter({group: cc2}));
|
|
});
|
|
|
|
test('_focusOn', () => {
|
|
sandbox.spy(element, '_chooseFocusTarget');
|
|
flushAsynchronousOperations();
|
|
const textareaStub = sandbox.stub(element.$.textarea, 'async');
|
|
const reviewerEntryStub = sandbox.stub(element.$.reviewers.focusStart,
|
|
'async');
|
|
const ccStub = sandbox.stub(element.$$('#ccs').focusStart, 'async');
|
|
element._focusOn();
|
|
assert.equal(element._chooseFocusTarget.callCount, 1);
|
|
assert.deepEqual(textareaStub.callCount, 1);
|
|
assert.deepEqual(reviewerEntryStub.callCount, 0);
|
|
assert.deepEqual(ccStub.callCount, 0);
|
|
|
|
element._focusOn(element.FocusTarget.ANY);
|
|
assert.equal(element._chooseFocusTarget.callCount, 2);
|
|
assert.deepEqual(textareaStub.callCount, 2);
|
|
assert.deepEqual(reviewerEntryStub.callCount, 0);
|
|
assert.deepEqual(ccStub.callCount, 0);
|
|
|
|
element._focusOn(element.FocusTarget.BODY);
|
|
assert.equal(element._chooseFocusTarget.callCount, 2);
|
|
assert.deepEqual(textareaStub.callCount, 3);
|
|
assert.deepEqual(reviewerEntryStub.callCount, 0);
|
|
assert.deepEqual(ccStub.callCount, 0);
|
|
|
|
element._focusOn(element.FocusTarget.REVIEWERS);
|
|
assert.equal(element._chooseFocusTarget.callCount, 2);
|
|
assert.deepEqual(textareaStub.callCount, 3);
|
|
assert.deepEqual(reviewerEntryStub.callCount, 1);
|
|
assert.deepEqual(ccStub.callCount, 0);
|
|
|
|
element._focusOn(element.FocusTarget.CCS);
|
|
assert.equal(element._chooseFocusTarget.callCount, 2);
|
|
assert.deepEqual(textareaStub.callCount, 3);
|
|
assert.deepEqual(reviewerEntryStub.callCount, 1);
|
|
assert.deepEqual(ccStub.callCount, 1);
|
|
});
|
|
|
|
test('_chooseFocusTarget', () => {
|
|
element._account = null;
|
|
assert.strictEqual(
|
|
element._chooseFocusTarget(), element.FocusTarget.BODY);
|
|
|
|
element._account = {_account_id: 1};
|
|
assert.strictEqual(
|
|
element._chooseFocusTarget(), element.FocusTarget.BODY);
|
|
|
|
element.change.owner = {_account_id: 2};
|
|
assert.strictEqual(
|
|
element._chooseFocusTarget(), element.FocusTarget.BODY);
|
|
|
|
element.change.owner._account_id = 1;
|
|
element.change._reviewers = null;
|
|
assert.strictEqual(
|
|
element._chooseFocusTarget(), element.FocusTarget.REVIEWERS);
|
|
|
|
element._reviewers = [];
|
|
assert.strictEqual(
|
|
element._chooseFocusTarget(), element.FocusTarget.REVIEWERS);
|
|
|
|
element._reviewers.push({});
|
|
assert.strictEqual(
|
|
element._chooseFocusTarget(), element.FocusTarget.BODY);
|
|
});
|
|
|
|
test('only send labels that have changed', done => {
|
|
flush(() => {
|
|
stubSaveReview(review => {
|
|
assert.deepEqual(review.labels, {
|
|
'Code-Review': 0,
|
|
'Verified': -1,
|
|
});
|
|
});
|
|
|
|
element.addEventListener('send', () => {
|
|
done();
|
|
});
|
|
// Without wrapping this test in flush(), the below two calls to
|
|
// MockInteractions.tap() cause a race in some situations in shadow DOM.
|
|
// The send button can be tapped before the others, causing the test to
|
|
// fail.
|
|
|
|
element.$$('gr-label-scores').$$(
|
|
'gr-label-score-row[name="Verified"]').setSelectedValue(-1);
|
|
MockInteractions.tap(element.$$('.send'));
|
|
});
|
|
});
|
|
|
|
test('_processReviewerChange', () => {
|
|
const mockIndexSplices = function(toRemove) {
|
|
return [{
|
|
removed: [toRemove],
|
|
}];
|
|
};
|
|
|
|
element._processReviewerChange(
|
|
mockIndexSplices(makeAccount()), 'REVIEWER');
|
|
assert.equal(element._reviewersPendingRemove.REVIEWER.length, 1);
|
|
});
|
|
|
|
test('_purgeReviewersPendingRemove', () => {
|
|
const removeStub = sandbox.stub(element, '_removeAccount');
|
|
const mock = function() {
|
|
element._reviewersPendingRemove = {
|
|
test: [makeAccount()],
|
|
test2: [makeAccount(), makeAccount()],
|
|
};
|
|
};
|
|
const checkObjEmpty = function(obj) {
|
|
for (const prop in obj) {
|
|
if (obj.hasOwnProperty(prop) && obj[prop].length) { return false; }
|
|
}
|
|
return true;
|
|
};
|
|
mock();
|
|
element._purgeReviewersPendingRemove(true); // Cancel
|
|
assert.isFalse(removeStub.called);
|
|
assert.isTrue(checkObjEmpty(element._reviewersPendingRemove));
|
|
|
|
mock();
|
|
element._purgeReviewersPendingRemove(false); // Submit
|
|
assert.isTrue(removeStub.called);
|
|
assert.isTrue(checkObjEmpty(element._reviewersPendingRemove));
|
|
});
|
|
|
|
test('_removeAccount', done => {
|
|
sandbox.stub(element.$.restAPI, 'removeChangeReviewer')
|
|
.returns(Promise.resolve({ok: true}));
|
|
const arr = [makeAccount(), makeAccount()];
|
|
element.change.reviewers = {
|
|
REVIEWER: arr.slice(),
|
|
};
|
|
|
|
element._removeAccount(arr[1], 'REVIEWER').then(() => {
|
|
assert.equal(element.change.reviewers.REVIEWER.length, 1);
|
|
assert.deepEqual(element.change.reviewers.REVIEWER, arr.slice(0, 1));
|
|
done();
|
|
});
|
|
});
|
|
|
|
test('moving from cc to reviewer', () => {
|
|
element._reviewersPendingRemove = {
|
|
CC: [],
|
|
REVIEWER: [],
|
|
};
|
|
flushAsynchronousOperations();
|
|
|
|
const reviewer1 = makeAccount();
|
|
const reviewer2 = makeAccount();
|
|
const reviewer3 = makeAccount();
|
|
const cc1 = makeAccount();
|
|
const cc2 = makeAccount();
|
|
const cc3 = makeAccount();
|
|
const cc4 = makeAccount();
|
|
element._reviewers = [reviewer1, reviewer2, reviewer3];
|
|
element._ccs = [cc1, cc2, cc3, cc4];
|
|
element.push('_reviewers', cc1);
|
|
flushAsynchronousOperations();
|
|
|
|
assert.deepEqual(element._reviewers,
|
|
[reviewer1, reviewer2, reviewer3, cc1]);
|
|
assert.deepEqual(element._ccs, [cc2, cc3, cc4]);
|
|
assert.deepEqual(element._reviewersPendingRemove.CC, [cc1]);
|
|
|
|
element.push('_reviewers', cc4, cc3);
|
|
flushAsynchronousOperations();
|
|
|
|
assert.deepEqual(element._reviewers,
|
|
[reviewer1, reviewer2, reviewer3, cc1, cc4, cc3]);
|
|
assert.deepEqual(element._ccs, [cc2]);
|
|
assert.deepEqual(element._reviewersPendingRemove.CC, [cc1, cc4, cc3]);
|
|
});
|
|
|
|
test('moving from reviewer to cc', () => {
|
|
element._reviewersPendingRemove = {
|
|
CC: [],
|
|
REVIEWER: [],
|
|
};
|
|
flushAsynchronousOperations();
|
|
|
|
const reviewer1 = makeAccount();
|
|
const reviewer2 = makeAccount();
|
|
const reviewer3 = makeAccount();
|
|
const cc1 = makeAccount();
|
|
const cc2 = makeAccount();
|
|
const cc3 = makeAccount();
|
|
const cc4 = makeAccount();
|
|
element._reviewers = [reviewer1, reviewer2, reviewer3];
|
|
element._ccs = [cc1, cc2, cc3, cc4];
|
|
element.push('_ccs', reviewer1);
|
|
flushAsynchronousOperations();
|
|
|
|
assert.deepEqual(element._reviewers,
|
|
[reviewer2, reviewer3]);
|
|
assert.deepEqual(element._ccs, [cc1, cc2, cc3, cc4, reviewer1]);
|
|
assert.deepEqual(element._reviewersPendingRemove.REVIEWER, [reviewer1]);
|
|
|
|
element.push('_ccs', reviewer3, reviewer2);
|
|
flushAsynchronousOperations();
|
|
|
|
assert.deepEqual(element._reviewers, []);
|
|
assert.deepEqual(element._ccs,
|
|
[cc1, cc2, cc3, cc4, reviewer1, reviewer3, reviewer2]);
|
|
assert.deepEqual(element._reviewersPendingRemove.REVIEWER,
|
|
[reviewer1, reviewer3, reviewer2]);
|
|
});
|
|
|
|
test('migrate reviewers between states', done => {
|
|
element._reviewersPendingRemove = {
|
|
CC: [],
|
|
REVIEWER: [],
|
|
};
|
|
flushAsynchronousOperations();
|
|
const reviewers = element.$.reviewers;
|
|
const ccs = element.$$('#ccs');
|
|
const reviewer1 = makeAccount();
|
|
const reviewer2 = makeAccount();
|
|
const cc1 = makeAccount();
|
|
const cc2 = makeAccount();
|
|
const cc3 = makeAccount();
|
|
element._reviewers = [reviewer1, reviewer2];
|
|
element._ccs = [cc1, cc2, cc3];
|
|
|
|
const mutations = [];
|
|
|
|
stubSaveReview(review => mutations.push(...review.reviewers));
|
|
|
|
sandbox.stub(element, '_removeAccount', (account, type) => {
|
|
mutations.push({state: 'REMOVED', account});
|
|
return Promise.resolve();
|
|
});
|
|
|
|
// Remove and add to other field.
|
|
reviewers.fire('remove', {account: reviewer1});
|
|
ccs.$.entry.fire('add', {value: {account: reviewer1}});
|
|
ccs.fire('remove', {account: cc1});
|
|
ccs.fire('remove', {account: cc3});
|
|
reviewers.$.entry.fire('add', {value: {account: cc1}});
|
|
|
|
// Add to other field without removing from former field.
|
|
// (Currently not possible in UI, but this is a good consistency check).
|
|
reviewers.$.entry.fire('add', {value: {account: cc2}});
|
|
ccs.$.entry.fire('add', {value: {account: reviewer2}});
|
|
const mapReviewer = function(reviewer, opt_state) {
|
|
const result = {reviewer: reviewer._account_id, confirmed: undefined};
|
|
if (opt_state) {
|
|
result.state = opt_state;
|
|
}
|
|
return result;
|
|
};
|
|
|
|
// Send and purge and verify moves, delete cc3.
|
|
element.send()
|
|
.then(keepReviewers =>
|
|
element._purgeReviewersPendingRemove(false, keepReviewers))
|
|
.then(() => {
|
|
assert.deepEqual(
|
|
mutations, [
|
|
mapReviewer(cc1),
|
|
mapReviewer(cc2),
|
|
mapReviewer(reviewer1, 'CC'),
|
|
mapReviewer(reviewer2, 'CC'),
|
|
{account: cc3, state: 'REMOVED'},
|
|
]);
|
|
done();
|
|
});
|
|
});
|
|
|
|
test('emits cancel on esc key', () => {
|
|
const cancelHandler = sandbox.spy();
|
|
element.addEventListener('cancel', cancelHandler);
|
|
MockInteractions.pressAndReleaseKeyOn(element, 27, null, 'esc');
|
|
flushAsynchronousOperations();
|
|
|
|
assert.isTrue(cancelHandler.called);
|
|
});
|
|
|
|
test('should not send on enter key', () => {
|
|
stubSaveReview(() => undefined);
|
|
element.addEventListener('send', () => assert.fail('wrongly called'));
|
|
MockInteractions.pressAndReleaseKeyOn(element, 13, null, 'enter');
|
|
flushAsynchronousOperations();
|
|
});
|
|
|
|
test('emit send on ctrl+enter key', done => {
|
|
stubSaveReview(() => undefined);
|
|
element.addEventListener('send', () => done());
|
|
MockInteractions.pressAndReleaseKeyOn(element, 13, 'ctrl', 'enter');
|
|
flushAsynchronousOperations();
|
|
});
|
|
|
|
test('_computeMessagePlaceholder', () => {
|
|
assert.equal(
|
|
element._computeMessagePlaceholder(false),
|
|
'Say something nice...');
|
|
assert.equal(
|
|
element._computeMessagePlaceholder(true),
|
|
'Add a note for your reviewers...');
|
|
});
|
|
|
|
test('_computeSendButtonLabel', () => {
|
|
assert.equal(
|
|
element._computeSendButtonLabel(false),
|
|
'Send');
|
|
assert.equal(
|
|
element._computeSendButtonLabel(true),
|
|
'Start review');
|
|
});
|
|
|
|
test('_handle400Error reviewrs and CCs', done => {
|
|
const error1 = 'error 1';
|
|
const error2 = 'error 2';
|
|
const error3 = 'error 3';
|
|
const text = ')]}\'' + JSON.stringify({
|
|
reviewers: {
|
|
username1: {
|
|
input: 'user 1',
|
|
error: error1,
|
|
},
|
|
username2: {
|
|
input: 'user 2',
|
|
error: error2,
|
|
},
|
|
},
|
|
ccs: {
|
|
username3: {
|
|
input: 'user 3',
|
|
error: error3,
|
|
},
|
|
},
|
|
});
|
|
element.addEventListener('server-error', e => {
|
|
e.detail.response.text().then(text => {
|
|
assert.equal(text, [error1, error2, error3].join(', '));
|
|
done();
|
|
});
|
|
});
|
|
element._handle400Error(cloneableResponse(400, text));
|
|
});
|
|
|
|
test('_handle400Error CCs only', done => {
|
|
const error1 = 'error 1';
|
|
const text = ')]}\'' + JSON.stringify({
|
|
ccs: {
|
|
username1: {
|
|
input: 'user 1',
|
|
error: error1,
|
|
},
|
|
},
|
|
});
|
|
element.addEventListener('server-error', e => {
|
|
e.detail.response.text().then(text => {
|
|
assert.equal(text, error1);
|
|
done();
|
|
});
|
|
});
|
|
element._handle400Error(cloneableResponse(400, text));
|
|
});
|
|
|
|
test('fires height change when the drafts load', done => {
|
|
// Flush DOM operations before binding to the autogrow event so we don't
|
|
// catch the events fired from the initial layout.
|
|
flush(() => {
|
|
const autoGrowHandler = sinon.stub();
|
|
element.addEventListener('autogrow', autoGrowHandler);
|
|
element.diffDrafts = {};
|
|
flush(() => {
|
|
assert.isTrue(autoGrowHandler.called);
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
suite('post review API', () => {
|
|
let startReviewStub;
|
|
|
|
setup(() => {
|
|
startReviewStub = sandbox.stub(element.$.restAPI, 'startReview', () => {
|
|
return Promise.resolve();
|
|
});
|
|
});
|
|
|
|
test('ready property in review input on start review', () => {
|
|
stubSaveReview(review => {
|
|
assert.isTrue(review.ready);
|
|
return {ready: true};
|
|
});
|
|
return element.send(true, true).then(() => {
|
|
assert.isFalse(startReviewStub.called);
|
|
});
|
|
});
|
|
|
|
test('no ready property in review input on save review', () => {
|
|
stubSaveReview(review => {
|
|
assert.isUndefined(review.ready);
|
|
});
|
|
return element.send(true, false).then(() => {
|
|
assert.isFalse(startReviewStub.called);
|
|
});
|
|
});
|
|
|
|
test('fall back to start review against old backend', () => {
|
|
stubSaveReview(review => {
|
|
return {}; // old backend won't set ready: true
|
|
});
|
|
|
|
return element.send(true, true).then(() => {
|
|
assert.isTrue(startReviewStub.called);
|
|
}).then(() => {
|
|
startReviewStub.reset();
|
|
return element.send(true, false);
|
|
}).then(() => {
|
|
assert.isFalse(startReviewStub.called);
|
|
});
|
|
});
|
|
});
|
|
|
|
suite('start review and save buttons', () => {
|
|
let sendStub;
|
|
|
|
setup(() => {
|
|
sendStub = sandbox.stub(element, 'send', () => Promise.resolve());
|
|
element.canBeStarted = true;
|
|
// Flush to make both Start/Save buttons appear in DOM.
|
|
flushAsynchronousOperations();
|
|
});
|
|
|
|
test('start review sets ready', () => {
|
|
MockInteractions.tap(element.$$('.send'));
|
|
flushAsynchronousOperations();
|
|
assert.isTrue(sendStub.calledWith(true, true));
|
|
});
|
|
|
|
test('save review doesn\'t set ready', () => {
|
|
MockInteractions.tap(element.$$('.save'));
|
|
flushAsynchronousOperations();
|
|
assert.isTrue(sendStub.calledWith(true, false));
|
|
});
|
|
});
|
|
|
|
test('dummy message to force email on start review', () => {
|
|
stubSaveReview(review => {
|
|
assert.equal(review.message, element.START_REVIEW_MESSAGE);
|
|
return {ready: true};
|
|
});
|
|
return element.send(true, true);
|
|
});
|
|
|
|
test('buttons disabled until all API calls are resolved', () => {
|
|
stubSaveReview(review => {
|
|
return {}; // old backend won't set ready: true
|
|
});
|
|
// Check that element is disabled asynchronously after the setReady
|
|
// promise is returned. The element should not be reenabled until
|
|
// that promise is resolved.
|
|
sandbox.stub(element, '_maybeSetReady', (startReview, response) => {
|
|
return new Promise(resolve => {
|
|
Polymer.Base.async(() => {
|
|
assert.isTrue(element.disabled);
|
|
resolve();
|
|
});
|
|
});
|
|
});
|
|
return element.send(true, true).then(() => {
|
|
assert.isFalse(element.disabled);
|
|
});
|
|
});
|
|
|
|
suite('error handling', () => {
|
|
const expectedDraft = 'draft';
|
|
const expectedError = new Error('test');
|
|
|
|
setup(() => {
|
|
element.draft = expectedDraft;
|
|
});
|
|
|
|
function assertDialogOpenAndEnabled() {
|
|
assert.strictEqual(expectedDraft, element.draft);
|
|
assert.isFalse(element.disabled);
|
|
}
|
|
|
|
function assertDialogClosed() {
|
|
assert.strictEqual('', element.draft);
|
|
assert.isFalse(element.disabled);
|
|
}
|
|
|
|
test('error occurs in _saveReview', () => {
|
|
stubSaveReview(review => {
|
|
throw expectedError;
|
|
});
|
|
return element.send(true, true).catch(err => {
|
|
assert.strictEqual(expectedError, err);
|
|
assertDialogOpenAndEnabled();
|
|
});
|
|
});
|
|
|
|
test('error occurs during startReview', () => {
|
|
stubSaveReview(review => {
|
|
return {}; // old backend won't set ready: true
|
|
});
|
|
const errorStub = sandbox.stub(
|
|
console, 'error', (msg, err) => undefined);
|
|
sandbox.stub(element.$.restAPI, 'startReview', () => {
|
|
throw expectedError;
|
|
});
|
|
return element.send(true, true).then(() => {
|
|
assertDialogClosed();
|
|
assert.isTrue(
|
|
errorStub.calledWith('error setting ready:', expectedError));
|
|
});
|
|
});
|
|
|
|
test('non-ok response received by startReview', () => {
|
|
stubSaveReview(review => {
|
|
return {}; // old backend won't set ready: true
|
|
});
|
|
sandbox.stub(element.$.restAPI, 'startReview', (c, b, f) => {
|
|
f({status: 500});
|
|
});
|
|
return element.send(true, true).then(() => {
|
|
assertDialogClosed();
|
|
});
|
|
});
|
|
|
|
test('409 response received by startReview', () => {
|
|
stubSaveReview(review => {
|
|
return {}; // old backend won't set ready: true
|
|
});
|
|
sandbox.stub(element.$.restAPI, 'startReview', (c, b, f) => {
|
|
f({status: 409});
|
|
});
|
|
return element.send(true, true).then(() => {
|
|
assertDialogClosed();
|
|
});
|
|
});
|
|
|
|
suite('pending diff drafts?', () => {
|
|
test('yes', () => {
|
|
const promise = mockPromise();
|
|
const refreshHandler = sandbox.stub();
|
|
|
|
element.addEventListener('comment-refresh', refreshHandler);
|
|
sandbox.stub(element.$.restAPI, 'hasPendingDiffDrafts').returns(true);
|
|
element.$.restAPI._pendingRequests.sendDiffDraft = [promise];
|
|
element.open();
|
|
|
|
assert.isFalse(refreshHandler.called);
|
|
assert.isTrue(element._savingComments);
|
|
|
|
promise.resolve();
|
|
|
|
return element.$.restAPI.awaitPendingDiffDrafts().then(() => {
|
|
assert.isTrue(refreshHandler.called);
|
|
assert.isFalse(element._savingComments);
|
|
});
|
|
});
|
|
|
|
test('no', () => {
|
|
sandbox.stub(element.$.restAPI, 'hasPendingDiffDrafts').returns(false);
|
|
element.open();
|
|
assert.notOk(element._savingComments);
|
|
});
|
|
});
|
|
});
|
|
|
|
test('_computeSendButtonDisabled', () => {
|
|
const fn = element._computeSendButtonDisabled.bind(element);
|
|
assert.isFalse(fn(
|
|
/* buttonLabel= */ 'Start review',
|
|
/* drafts= */ {},
|
|
/* text= */ '',
|
|
/* reviewersMutated= */ false,
|
|
/* labelsChanged= */ false,
|
|
/* includeComments= */ false,
|
|
/* disabled= */ false,
|
|
));
|
|
assert.isTrue(fn(
|
|
/* buttonLabel= */ 'Send',
|
|
/* drafts= */ {},
|
|
/* text= */ '',
|
|
/* reviewersMutated= */ false,
|
|
/* labelsChanged= */ false,
|
|
/* includeComments= */ false,
|
|
/* disabled= */ false,
|
|
));
|
|
// Mock nonempty comment draft array, with seding comments.
|
|
assert.isFalse(fn(
|
|
/* buttonLabel= */ 'Send',
|
|
/* drafts= */ {file: ['draft']},
|
|
/* text= */ '',
|
|
/* reviewersMutated= */ false,
|
|
/* labelsChanged= */ false,
|
|
/* includeComments= */ true,
|
|
/* disabled= */ false,
|
|
));
|
|
// Mock nonempty comment draft array, without seding comments.
|
|
assert.isTrue(fn(
|
|
/* buttonLabel= */ 'Send',
|
|
/* drafts= */ {file: ['draft']},
|
|
/* text= */ '',
|
|
/* reviewersMutated= */ false,
|
|
/* labelsChanged= */ false,
|
|
/* includeComments= */ false,
|
|
/* disabled= */ false,
|
|
));
|
|
// Mock nonempty change message.
|
|
assert.isFalse(fn(
|
|
/* buttonLabel= */ 'Send',
|
|
/* drafts= */ {},
|
|
/* text= */ 'test',
|
|
/* reviewersMutated= */ false,
|
|
/* labelsChanged= */ false,
|
|
/* includeComments= */ false,
|
|
/* disabled= */ false,
|
|
));
|
|
// Mock reviewers mutated.
|
|
assert.isFalse(fn(
|
|
/* buttonLabel= */ 'Send',
|
|
/* drafts= */ {},
|
|
/* text= */ '',
|
|
/* reviewersMutated= */ true,
|
|
/* labelsChanged= */ false,
|
|
/* includeComments= */ false,
|
|
/* disabled= */ false,
|
|
));
|
|
// Mock labels changed.
|
|
assert.isFalse(fn(
|
|
/* buttonLabel= */ 'Send',
|
|
/* drafts= */ {},
|
|
/* text= */ '',
|
|
/* reviewersMutated= */ false,
|
|
/* labelsChanged= */ true,
|
|
/* includeComments= */ false,
|
|
/* disabled= */ false,
|
|
));
|
|
// Whole dialog is disabled.
|
|
assert.isTrue(fn(
|
|
/* buttonLabel= */ 'Send',
|
|
/* drafts= */ {},
|
|
/* text= */ '',
|
|
/* reviewersMutated= */ false,
|
|
/* labelsChanged= */ true,
|
|
/* includeComments= */ false,
|
|
/* disabled= */ true,
|
|
));
|
|
});
|
|
|
|
test('_submit blocked when no mutations exist', () => {
|
|
const sendStub = sandbox.stub(element, 'send').returns(Promise.resolve());
|
|
// Stub the below function to avoid side effects from the send promise
|
|
// resolving.
|
|
sandbox.stub(element, '_purgeReviewersPendingRemove');
|
|
element.diffDrafts = {};
|
|
flushAsynchronousOperations();
|
|
|
|
MockInteractions.tap(element.$$('gr-button.send'));
|
|
assert.isFalse(sendStub.called);
|
|
|
|
element.diffDrafts = {test: true};
|
|
flushAsynchronousOperations();
|
|
|
|
MockInteractions.tap(element.$$('gr-button.send'));
|
|
assert.isTrue(sendStub.called);
|
|
});
|
|
|
|
test('getFocusStops', () => {
|
|
// Setting diffDrafts to an empty object causes _sendDisabled to be
|
|
// computed to false.
|
|
element.diffDrafts = {};
|
|
assert.equal(element.getFocusStops().end, element.$.cancelButton);
|
|
element.diffDrafts = {test: true};
|
|
assert.equal(element.getFocusStops().end, element.$.sendButton);
|
|
});
|
|
|
|
test('setPluginMessage', () => {
|
|
element.setPluginMessage('foo');
|
|
assert.equal(element.$.pluginMessage.textContent, 'foo');
|
|
});
|
|
});
|
|
</script>
|