blob: 535dc40b167ce2dbef60232786564d1d095c1fa0 [file] [log] [blame]
Andrew Bonventref8b026d2015-12-09 17:55:54 -05001<!DOCTYPE html>
2<!--
3Copyright (C) 2015 The Android Open Source Project
4
5Licensed under the Apache License, Version 2.0 (the "License");
6you may not use this file except in compliance with the License.
7You may obtain a copy of the License at
8
9http://www.apache.org/licenses/LICENSE-2.0
10
11Unless required by applicable law or agreed to in writing, software
12distributed under the License is distributed on an "AS IS" BASIS,
13WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14See the License for the specific language governing permissions and
15limitations under the License.
16-->
17
18<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
Andrew Bonventre882043f2016-02-22 18:12:27 -050019<title>gr-reply-dialog</title>
Andrew Bonventref8b026d2015-12-09 17:55:54 -050020
Viktar Donich29e1ce52017-03-28 17:02:44 -070021<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
Andrew Bonventre78792e82016-03-04 17:48:22 -050022<script src="../../../bower_components/web-component-tester/browser.js"></script>
Mike Samuele07c4b22017-06-02 13:08:19 -040023<link rel="import" href="../../../test/common-test-setup.html"/>
Andrew Bonventre78792e82016-03-04 17:48:22 -050024<link rel="import" href="gr-reply-dialog.html">
Andrew Bonventref8b026d2015-12-09 17:55:54 -050025
Viktar Donich29e1ce52017-03-28 17:02:44 -070026<script>void(0);</script>
27
Andrew Bonventref8b026d2015-12-09 17:55:54 -050028<test-fixture id="basic">
29 <template>
Andrew Bonventre882043f2016-02-22 18:12:27 -050030 <gr-reply-dialog></gr-reply-dialog>
Andrew Bonventref8b026d2015-12-09 17:55:54 -050031 </template>
32</test-fixture>
33
34<script>
Kasper Nilssonad908b12017-05-11 11:26:10 -070035 suite('gr-reply-dialog tests', () => {
36 let element;
37 let changeNum;
38 let patchNum;
Wyatt Allen4f4b3a72016-07-28 12:05:53 -070039
Kasper Nilssonad908b12017-05-11 11:26:10 -070040 let sandbox;
41 let getDraftCommentStub;
42 let setDraftCommentStub;
43 let eraseDraftCommentStub;
Andrew Bonventref8b026d2015-12-09 17:55:54 -050044
Kasper Nilssonad908b12017-05-11 11:26:10 -070045 let lastId = 0;
46 const makeAccount = function() { return {_account_id: lastId++}; };
47 const makeGroup = function() { return {id: lastId++}; };
Kasper Nilssona8271552017-01-17 11:54:40 -080048
Kasper Nilssonad908b12017-05-11 11:26:10 -070049 setup(() => {
Wyatt Allen4f4b3a72016-07-28 12:05:53 -070050 sandbox = sinon.sandbox.create();
51
52 changeNum = 42;
53 patchNum = 1;
54
Andrew Bonventre5c09ba92016-05-02 17:31:23 -040055 stub('gr-rest-api-interface', {
Kasper Nilssonad908b12017-05-11 11:26:10 -070056 getConfig() { return Promise.resolve({}); },
57 getAccount() { return Promise.resolve({}); },
Andrew Bonventrecfacb802016-03-29 14:06:39 -040058 });
Wyatt Allen4f4b3a72016-07-28 12:05:53 -070059
Andrew Bonventref8b026d2015-12-09 17:55:54 -050060 element = fixture('basic');
Kasper Nilsson352b68f2016-10-11 11:51:35 -070061 element.change = {
62 _number: changeNum,
63 labels: {
Kasper Nilssonad908b12017-05-11 11:26:10 -070064 'Verified': {
Kasper Nilsson352b68f2016-10-11 11:51:35 -070065 values: {
66 '-1': 'Fails',
67 ' 0': 'No score',
68 '+1': 'Verified',
69 },
70 default_value: 0,
Andrew Bonventref8b026d2015-12-09 17:55:54 -050071 },
Kasper Nilsson352b68f2016-10-11 11:51:35 -070072 'Code-Review': {
73 values: {
74 '-2': 'Do not submit',
75 '-1': 'I would prefer that you didn\'t submit this',
76 ' 0': 'No score',
77 '+1': 'Looks good to me, but someone else must approve',
78 '+2': 'Looks good to me, approved',
79 },
80 default_value: 0,
81 },
Andrew Bonventref8b026d2015-12-09 17:55:54 -050082 },
Andrew Bonventref8b026d2015-12-09 17:55:54 -050083 };
Kasper Nilsson352b68f2016-10-11 11:51:35 -070084 element.patchNum = patchNum;
Andrew Bonventref8b026d2015-12-09 17:55:54 -050085 element.permittedLabels = {
86 'Code-Review': [
87 '-1',
88 ' 0',
Kasper Nilsson352b68f2016-10-11 11:51:35 -070089 '+1',
Andrew Bonventref8b026d2015-12-09 17:55:54 -050090 ],
Kasper Nilssonad908b12017-05-11 11:26:10 -070091 'Verified': [
Andrew Bonventref8b026d2015-12-09 17:55:54 -050092 '-1',
93 ' 0',
Kasper Nilsson352b68f2016-10-11 11:51:35 -070094 '+1',
95 ],
Andrew Bonventref8b026d2015-12-09 17:55:54 -050096 };
Logan Hanksa75fb052016-08-01 13:23:38 -070097 element.serverConfig = {};
Andrew Bonventref8b026d2015-12-09 17:55:54 -050098
Wyatt Allen4f4b3a72016-07-28 12:05:53 -070099 getDraftCommentStub = sandbox.stub(element.$.storage, 'getDraftComment');
100 setDraftCommentStub = sandbox.stub(element.$.storage, 'setDraftComment');
101 eraseDraftCommentStub = sandbox.stub(element.$.storage,
102 'eraseDraftComment');
103
Wyatt Allen6cf58752017-04-24 16:59:07 +0200104 sandbox.stub(element, 'fetchIsLatestKnown',
Kasper Nilssonad908b12017-05-11 11:26:10 -0700105 () => { return Promise.resolve(true); });
Wyatt Allen6cf58752017-04-24 16:59:07 +0200106
Andrew Bonventref8b026d2015-12-09 17:55:54 -0500107 // Allow the elements created by dom-repeat to be stamped.
108 flushAsynchronousOperations();
109 });
110
Kasper Nilssonad908b12017-05-11 11:26:10 -0700111 teardown(() => {
Wyatt Allen4f4b3a72016-07-28 12:05:53 -0700112 sandbox.restore();
113 });
114
Logan Hanks02c781e2017-07-18 14:18:01 -0700115 function stubSaveReview(jsonResponseProducer) {
116 return sandbox.stub(element, '_saveReview', review => {
Logan Hanks3e0917e2017-07-19 08:32:25 -0700117 return new Promise((resolve, reject) => {
118 try {
119 const result = jsonResponseProducer(review) || {};
120 const resultStr =
121 element.$.restAPI.JSON_PREFIX + JSON.stringify(result);
122 resolve({
123 ok: true,
124 text() {
125 return Promise.resolve(resultStr);
126 },
127 });
128 } catch (err) {
129 reject(err);
130 }
Logan Hanks02c781e2017-07-18 14:18:01 -0700131 });
132 });
133 }
134
Kasper Nilssonad908b12017-05-11 11:26:10 -0700135 test('default to publishing drafts with reply', done => {
Becky Siegel9efcd572017-03-23 08:28:03 -0700136 // Async tick is needed because iron-selector content is distributed and
137 // distributed content requires an observer to be set up.
138 // Note: Double flush seems to be needed in Safari. {@see Issue 4963}.
Kasper Nilssonad908b12017-05-11 11:26:10 -0700139 flush(() => {
140 flush(() => {
Becky Siegel9efcd572017-03-23 08:28:03 -0700141 element.draft = 'I wholeheartedly disapprove';
142
Logan Hanks02c781e2017-07-18 14:18:01 -0700143 stubSaveReview(review => {
Becky Siegel9efcd572017-03-23 08:28:03 -0700144 assert.deepEqual(review, {
145 drafts: 'PUBLISH_ALL_REVISIONS',
Viktar Donich51bdb8d2017-07-14 11:39:24 -0700146 labels: {
147 'Code-Review': 0,
148 'Verified': 0,
149 },
Becky Siegel9efcd572017-03-23 08:28:03 -0700150 message: 'I wholeheartedly disapprove',
151 reviewers: [],
152 });
153 assert.isFalse(element.$.commentList.hidden);
154 done();
Becky Siegel9efcd572017-03-23 08:28:03 -0700155 });
156
157 // This is needed on non-Blink engines most likely due to the ways in
158 // which the dom-repeat elements are stamped.
Kasper Nilssonad908b12017-05-11 11:26:10 -0700159 flush(() => {
Becky Siegel9efcd572017-03-23 08:28:03 -0700160 MockInteractions.tap(element.$$('.send'));
161 });
162 });
163 });
164 });
165
Kasper Nilssonad908b12017-05-11 11:26:10 -0700166 test('keep drafts with reply', done => {
Becky Siegel9efcd572017-03-23 08:28:03 -0700167 MockInteractions.tap(element.$$('#includeComments'));
168 assert.equal(element._includeComments, false);
169
170 // Async tick is needed because iron-selector content is distributed and
171 // distributed content requires an observer to be set up.
172 // Note: Double flush seems to be needed in Safari. {@see Issue 4963}.
Kasper Nilssonad908b12017-05-11 11:26:10 -0700173 flush(() => {
174 flush(() => {
Becky Siegel9efcd572017-03-23 08:28:03 -0700175 element.draft = 'I wholeheartedly disapprove';
176
Logan Hanks02c781e2017-07-18 14:18:01 -0700177 stubSaveReview(review => {
Becky Siegel9efcd572017-03-23 08:28:03 -0700178 assert.deepEqual(review, {
179 drafts: 'KEEP',
Viktar Donich51bdb8d2017-07-14 11:39:24 -0700180 labels: {
181 'Code-Review': 0,
182 'Verified': 0,
183 },
Becky Siegel9efcd572017-03-23 08:28:03 -0700184 message: 'I wholeheartedly disapprove',
185 reviewers: [],
186 });
187 assert.isTrue(element.$.commentList.hidden);
188 done();
Becky Siegel9efcd572017-03-23 08:28:03 -0700189 });
190
191 // This is needed on non-Blink engines most likely due to the ways in
192 // which the dom-repeat elements are stamped.
Kasper Nilssonad908b12017-05-11 11:26:10 -0700193 flush(() => {
Becky Siegel9efcd572017-03-23 08:28:03 -0700194 MockInteractions.tap(element.$$('.send'));
195 });
196 });
197 });
198 });
199
Kasper Nilssonad908b12017-05-11 11:26:10 -0700200 test('label picker', done => {
Becky Siegela499e3d2017-04-06 16:57:13 -0700201 element.draft = 'I wholeheartedly disapprove';
Logan Hanks02c781e2017-07-18 14:18:01 -0700202 stubSaveReview(review => {
Becky Siegela499e3d2017-04-06 16:57:13 -0700203 assert.deepEqual(review, {
204 drafts: 'PUBLISH_ALL_REVISIONS',
205 labels: {
206 'Code-Review': -1,
207 'Verified': -1,
208 },
209 message: 'I wholeheartedly disapprove',
210 reviewers: [],
Urs Wolferb6036942016-03-06 14:57:02 +0100211 });
Becky Siegela499e3d2017-04-06 16:57:13 -0700212 });
213
Kasper Nilssonad908b12017-05-11 11:26:10 -0700214 sandbox.stub(element.$.labelScores, 'getLabelValues', () => {
Becky Siegela499e3d2017-04-06 16:57:13 -0700215 return {
216 'Code-Review': -1,
217 'Verified': -1,
218 };
219 });
220
Kasper Nilssonad908b12017-05-11 11:26:10 -0700221 element.addEventListener('send', () => {
Logan Hanks3e0917e2017-07-19 08:32:25 -0700222 // Flush to ensure properties are updated.
223 flush(() => {
224 assert.isFalse(element.disabled,
225 'Element should be enabled when done sending reply.');
226 assert.equal(element.draft.length, 0);
227 done();
228 });
Becky Siegela499e3d2017-04-06 16:57:13 -0700229 });
230
231 // This is needed on non-Blink engines most likely due to the ways in
232 // which the dom-repeat elements are stamped.
Kasper Nilssonad908b12017-05-11 11:26:10 -0700233 flush(() => {
Becky Siegela499e3d2017-04-06 16:57:13 -0700234 MockInteractions.tap(element.$$('.send'));
235 assert.isTrue(element.disabled);
Urs Wolferb6036942016-03-06 14:57:02 +0100236 });
Andrew Bonventref8b026d2015-12-09 17:55:54 -0500237 });
Logan Hanksa008aa92016-07-22 10:31:12 -0700238
Viktar Donich7d23f222017-05-17 09:25:06 -0700239 test('getlabelValue returns value', done => {
240 flush(() => {
Becky Siegel61613932017-05-30 15:09:20 -0700241 element.$$('gr-label-scores').$$(`gr-label-score-row[name="Verified"]`)
242 .setSelectedValue(-1);
Viktar Donich7d23f222017-05-17 09:25:06 -0700243 assert.equal('-1', element.getLabelValue('Verified'));
244 done();
245 });
246 });
247
248 test('getlabelValue when no score is selected', done => {
249 flush(() => {
Becky Siegel61613932017-05-30 15:09:20 -0700250 element.$$('gr-label-scores').$$(`gr-label-score-row[name="Code-Review"]`)
251 .setSelectedValue(-1);
Viktar Donich51bdb8d2017-07-14 11:39:24 -0700252 assert.strictEqual(element.getLabelValue('Verified'), ' 0');
Viktar Donich7d23f222017-05-17 09:25:06 -0700253 done();
254 });
255 });
256
Kasper Nilssonad908b12017-05-11 11:26:10 -0700257 test('setlabelValue', () => {
Becky Siegel452a5c62017-05-09 09:59:55 -0700258 element._account = {_account_id: 1};
259 flushAsynchronousOperations();
Kasper Nilssonad908b12017-05-11 11:26:10 -0700260 const label = 'Verified';
261 const value = '+1';
Becky Siegel452a5c62017-05-09 09:59:55 -0700262 element.setLabelValue(label, value);
263 flushAsynchronousOperations();
Kasper Nilssonad908b12017-05-11 11:26:10 -0700264 const labels = element.$.labelScores.getLabelValues();
Viktar Donich51bdb8d2017-07-14 11:39:24 -0700265 assert.deepEqual(labels, {
266 'Code-Review': 0,
267 'Verified': 1,
268 });
Becky Siegel452a5c62017-05-09 09:59:55 -0700269 });
270
Logan Hanksa008aa92016-07-22 10:31:12 -0700271 function getActiveElement() {
272 return Polymer.IronOverlayManager.deepActiveElement;
273 }
274
275 function isVisible(el) {
276 assert.ok(el);
277 return getComputedStyle(el).getPropertyValue('display') != 'none';
278 }
279
280 function overlayObserver(mode) {
Kasper Nilssonad908b12017-05-11 11:26:10 -0700281 return new Promise(resolve => {
Logan Hanksa008aa92016-07-22 10:31:12 -0700282 function listener() {
283 element.removeEventListener('iron-overlay-' + mode, listener);
284 resolve();
285 }
286 element.addEventListener('iron-overlay-' + mode, listener);
287 });
288 }
289
Logan Hanks43d4dbb2016-12-14 18:17:47 -0800290 function testConfirmationDialog(done, cc) {
Kasper Nilssonad908b12017-05-11 11:26:10 -0700291 const yesButton =
Logan Hanksa008aa92016-07-22 10:31:12 -0700292 element.$$('.reviewerConfirmationButtons gr-button:first-child');
Kasper Nilssonad908b12017-05-11 11:26:10 -0700293 const noButton =
Logan Hanksa008aa92016-07-22 10:31:12 -0700294 element.$$('.reviewerConfirmationButtons gr-button:last-child');
295
Logan Hanks43d4dbb2016-12-14 18:17:47 -0800296 element.serverConfig = {note_db_enabled: true};
297 element._ccPendingConfirmation = null;
Logan Hanksa008aa92016-07-22 10:31:12 -0700298 element._reviewerPendingConfirmation = null;
299 flushAsynchronousOperations();
300 assert.isFalse(isVisible(element.$.reviewerConfirmationOverlay));
301
302 // Cause the confirmation dialog to display.
Kasper Nilssonad908b12017-05-11 11:26:10 -0700303 let observer = overlayObserver('opened');
304 const group = {
Logan Hanksa008aa92016-07-22 10:31:12 -0700305 id: 'id',
306 name: 'name',
Logan Hanksa008aa92016-07-22 10:31:12 -0700307 };
Logan Hanks43d4dbb2016-12-14 18:17:47 -0800308 if (cc) {
309 element._ccPendingConfirmation = {
Kasper Nilssonad908b12017-05-11 11:26:10 -0700310 group,
Logan Hanks43d4dbb2016-12-14 18:17:47 -0800311 count: 10,
312 };
313 } else {
314 element._reviewerPendingConfirmation = {
Kasper Nilssonad908b12017-05-11 11:26:10 -0700315 group,
Logan Hanks43d4dbb2016-12-14 18:17:47 -0800316 count: 10,
317 };
318 }
319 flushAsynchronousOperations();
320
321 if (cc) {
322 assert.deepEqual(
323 element._ccPendingConfirmation,
324 element._pendingConfirmationDetails);
325 } else {
326 assert.deepEqual(
327 element._reviewerPendingConfirmation,
328 element._pendingConfirmationDetails);
329 }
Logan Hanksa008aa92016-07-22 10:31:12 -0700330
Kasper Nilssonad908b12017-05-11 11:26:10 -0700331 observer.then(() => {
Logan Hanksa008aa92016-07-22 10:31:12 -0700332 assert.isTrue(isVisible(element.$.reviewerConfirmationOverlay));
333 observer = overlayObserver('closed');
Kasper Nilssonad908b12017-05-11 11:26:10 -0700334 const expected = 'Group name has 10 members';
Logan Hanks43d4dbb2016-12-14 18:17:47 -0800335 assert.notEqual(
336 element.$.reviewerConfirmationOverlay.innerText.indexOf(expected),
337 -1);
Logan Hanksa008aa92016-07-22 10:31:12 -0700338 MockInteractions.tap(noButton); // close the overlay
339 return observer;
Kasper Nilssonad908b12017-05-11 11:26:10 -0700340 }).then(() => {
Logan Hanksa008aa92016-07-22 10:31:12 -0700341 assert.isFalse(isVisible(element.$.reviewerConfirmationOverlay));
342
343 // We should be focused on account entry input.
344 assert.equal(getActiveElement().id, 'input');
345
Logan Hanks43d4dbb2016-12-14 18:17:47 -0800346 // No reviewer/CC should have been added.
347 assert.equal(element.$$('#ccs').additions().length, 0);
348 assert.equal(element.$.reviewers.additions().length, 0);
Logan Hanksa008aa92016-07-22 10:31:12 -0700349
350 // Reopen confirmation dialog.
351 observer = overlayObserver('opened');
Logan Hanks43d4dbb2016-12-14 18:17:47 -0800352 if (cc) {
353 element._ccPendingConfirmation = {
Kasper Nilssonad908b12017-05-11 11:26:10 -0700354 group,
Logan Hanks43d4dbb2016-12-14 18:17:47 -0800355 count: 10,
356 };
357 } else {
358 element._reviewerPendingConfirmation = {
Kasper Nilssonad908b12017-05-11 11:26:10 -0700359 group,
Logan Hanks43d4dbb2016-12-14 18:17:47 -0800360 count: 10,
361 };
362 }
Logan Hanksa008aa92016-07-22 10:31:12 -0700363 return observer;
Kasper Nilssonad908b12017-05-11 11:26:10 -0700364 }).then(() => {
Logan Hanksa008aa92016-07-22 10:31:12 -0700365 assert.isTrue(isVisible(element.$.reviewerConfirmationOverlay));
366 observer = overlayObserver('closed');
Becky Siegel8bb09342016-10-17 17:49:31 -0700367 MockInteractions.tap(yesButton); // Confirm the group.
Logan Hanksa008aa92016-07-22 10:31:12 -0700368 return observer;
Kasper Nilssonad908b12017-05-11 11:26:10 -0700369 }).then(() => {
Logan Hanksa008aa92016-07-22 10:31:12 -0700370 assert.isFalse(isVisible(element.$.reviewerConfirmationOverlay));
Kasper Nilssonad908b12017-05-11 11:26:10 -0700371 const additions = cc ?
Logan Hanks43d4dbb2016-12-14 18:17:47 -0800372 element.$$('#ccs').additions() :
373 element.$.reviewers.additions();
Logan Hanksa008aa92016-07-22 10:31:12 -0700374 assert.deepEqual(
Logan Hanks43d4dbb2016-12-14 18:17:47 -0800375 additions,
Logan Hanksa008aa92016-07-22 10:31:12 -0700376 [
377 {
378 group: {
379 id: 'id',
380 name: 'name',
Logan Hanksa008aa92016-07-22 10:31:12 -0700381 confirmed: true,
382 _group: true,
383 _pendingAdd: true,
384 },
385 },
386 ]);
387
388 // We should be focused on account entry input.
389 assert.equal(getActiveElement().id, 'input');
390 }).then(done);
Kasper Nilssonad908b12017-05-11 11:26:10 -0700391 }
Logan Hanks43d4dbb2016-12-14 18:17:47 -0800392
Kasper Nilssonad908b12017-05-11 11:26:10 -0700393 test('cc confirmation', done => {
Logan Hanks43d4dbb2016-12-14 18:17:47 -0800394 testConfirmationDialog(done, true);
395 });
396
Kasper Nilssonad908b12017-05-11 11:26:10 -0700397 test('reviewer confirmation', done => {
Logan Hanks43d4dbb2016-12-14 18:17:47 -0800398 testConfirmationDialog(done, false);
Logan Hanksa008aa92016-07-22 10:31:12 -0700399 });
Wyatt Allen4f4b3a72016-07-28 12:05:53 -0700400
Kasper Nilssonad908b12017-05-11 11:26:10 -0700401 test('_getStorageLocation', () => {
402 const actual = element._getStorageLocation();
Wyatt Allen4f4b3a72016-07-28 12:05:53 -0700403 assert.equal(actual.changeNum, changeNum);
Wyatt Allene05392a2017-06-01 16:23:51 -0700404 assert.equal(actual.patchNum, '@change');
Wyatt Allen4f4b3a72016-07-28 12:05:53 -0700405 assert.equal(actual.path, '@change');
406 });
407
Kasper Nilssonad908b12017-05-11 11:26:10 -0700408 test('gets draft from storage on open', () => {
409 const storedDraft = 'hello world';
Wyatt Allen4f4b3a72016-07-28 12:05:53 -0700410 getDraftCommentStub.returns({message: storedDraft});
411 element.open();
412 assert.isTrue(getDraftCommentStub.called);
413 assert.equal(element.draft, storedDraft);
414 });
415
Kasper Nilssonad908b12017-05-11 11:26:10 -0700416 test('blank if no stored draft', () => {
Wyatt Allen4f4b3a72016-07-28 12:05:53 -0700417 getDraftCommentStub.returns(null);
418 element.open();
419 assert.isTrue(getDraftCommentStub.called);
420 assert.equal(element.draft, '');
421 });
422
Kasper Nilssonad908b12017-05-11 11:26:10 -0700423 test('updates stored draft on edits', () => {
424 const firstEdit = 'hello';
425 const location = element._getStorageLocation();
Wyatt Allen4f4b3a72016-07-28 12:05:53 -0700426
427 element.draft = firstEdit;
428 element.flushDebouncer('store');
429
430 assert.isTrue(setDraftCommentStub.calledWith(location, firstEdit));
431
432 element.draft = '';
433 element.flushDebouncer('store');
434
435 assert.isTrue(eraseDraftCommentStub.calledWith(location));
436 });
Logan Hanksf1063a22016-07-22 16:05:04 -0700437
Kasper Nilssonad908b12017-05-11 11:26:10 -0700438 test('400 converts to human-readable server-error', done => {
439 sandbox.stub(window, 'fetch', () => {
440 const text = '....{"reviewers":{"id1":{"error":"first error"}},' +
Logan Hanksf1063a22016-07-22 16:05:04 -0700441 '"ccs":{"id2":{"error":"second error"}}}';
442 return Promise.resolve({
443 ok: false,
444 status: 400,
Kasper Nilssonad908b12017-05-11 11:26:10 -0700445 text() { return Promise.resolve(text); },
Logan Hanksf1063a22016-07-22 16:05:04 -0700446 });
447 });
448
Kasper Nilssonad908b12017-05-11 11:26:10 -0700449 element.addEventListener('server-error', event => {
Logan Hanksf1063a22016-07-22 16:05:04 -0700450 if (event.target !== element) {
451 return;
452 }
Kasper Nilssonad908b12017-05-11 11:26:10 -0700453 event.detail.response.text().then(body => {
Logan Hanksf1063a22016-07-22 16:05:04 -0700454 assert.equal(body, 'first error, second error');
Wyatt Allene013aad2017-05-18 16:49:04 -0700455 done();
Logan Hanksf1063a22016-07-22 16:05:04 -0700456 });
457 });
Andrew Bonventreefd0a532016-08-23 17:04:23 -0400458
459 // Async tick is needed because iron-selector content is distributed and
460 // distributed content requires an observer to be set up.
Wyatt Allene013aad2017-05-18 16:49:04 -0700461 flush(() => { element.send(); });
Logan Hanksa75fb052016-08-01 13:23:38 -0700462 });
Logan Hanksf1063a22016-07-22 16:05:04 -0700463
Kasper Nilssonad908b12017-05-11 11:26:10 -0700464 test('ccs are displayed if NoteDb is enabled', () => {
Logan Hanksa75fb052016-08-01 13:23:38 -0700465 function hasCc() {
466 flushAsynchronousOperations();
467 return !!element.$$('#ccs');
468 }
469
470 element.serverConfig = {};
471 assert.isFalse(hasCc());
472
473 element.serverConfig = {note_db_enabled: true};
474 assert.isTrue(hasCc());
475 });
476
Kasper Nilssonad908b12017-05-11 11:26:10 -0700477 test('filterReviewerSuggestion', () => {
478 const owner = makeAccount();
479 const reviewer1 = makeAccount();
480 const reviewer2 = makeGroup();
481 const cc1 = makeAccount();
482 const cc2 = makeGroup();
Kasper Nilssond5896712017-06-01 17:59:02 -0700483 let filter = element._filterReviewerSuggestionGenerator(false);
Logan Hanksa75fb052016-08-01 13:23:38 -0700484
485 element._owner = owner;
486 element._reviewers = [reviewer1, reviewer2];
487 element._ccs = [cc1, cc2];
488
Kasper Nilssond5896712017-06-01 17:59:02 -0700489 assert.isTrue(filter({account: makeAccount()}));
490 assert.isTrue(filter({group: makeGroup()}));
Logan Hanksa75fb052016-08-01 13:23:38 -0700491
492 // Owner should be excluded.
Kasper Nilssond5896712017-06-01 17:59:02 -0700493 assert.isFalse(filter({account: owner}));
Logan Hanksa75fb052016-08-01 13:23:38 -0700494
Kasper Nilssond5896712017-06-01 17:59:02 -0700495 // Existing and pending reviewers should be excluded when isCC = false.
496 assert.isFalse(filter({account: reviewer1}));
497 assert.isFalse(filter({group: reviewer2}));
Logan Hanksa75fb052016-08-01 13:23:38 -0700498
Kasper Nilssond5896712017-06-01 17:59:02 -0700499 filter = element._filterReviewerSuggestionGenerator(true);
500
501 // Existing and pending CCs should be excluded when isCC = true;.
502 assert.isFalse(filter({account: cc1}));
503 assert.isFalse(filter({group: cc2}));
Logan Hanksf1063a22016-07-22 16:05:04 -0700504 });
Logan Hanksfc57acc2016-08-05 13:54:50 -0700505
Kasper Nilssonad908b12017-05-11 11:26:10 -0700506 test('_chooseFocusTarget', () => {
Logan Hanksfc57acc2016-08-05 13:54:50 -0700507 element._account = null;
508 assert.strictEqual(
509 element._chooseFocusTarget(), element.FocusTarget.BODY);
510
511 element._account = {_account_id: 1};
512 assert.strictEqual(
513 element._chooseFocusTarget(), element.FocusTarget.BODY);
514
515 element.change.owner = {_account_id: 2};
516 assert.strictEqual(
517 element._chooseFocusTarget(), element.FocusTarget.BODY);
518
519 element.change.owner._account_id = 1;
520 element.change._reviewers = null;
521 assert.strictEqual(
522 element._chooseFocusTarget(), element.FocusTarget.REVIEWERS);
523
524 element._reviewers = [];
525 assert.strictEqual(
526 element._chooseFocusTarget(), element.FocusTarget.REVIEWERS);
527
528 element._reviewers.push({});
529 assert.strictEqual(
530 element._chooseFocusTarget(), element.FocusTarget.BODY);
531 });
Logan Hanksca3c4972016-09-14 16:25:56 -0700532
Kasper Nilssonad908b12017-05-11 11:26:10 -0700533 test('only send labels that have changed', done => {
534 flush(() => {
Logan Hanks02c781e2017-07-18 14:18:01 -0700535 stubSaveReview(review => {
Viktar Donich51bdb8d2017-07-14 11:39:24 -0700536 assert.deepEqual(review.labels, {
537 'Code-Review': 0,
538 'Verified': -1,
539 });
Kasper Nilsson4416acd2016-09-16 13:27:11 -0700540 });
Logan Hanksca3c4972016-09-14 16:25:56 -0700541
Kasper Nilssonad908b12017-05-11 11:26:10 -0700542 element.addEventListener('send', () => {
Kasper Nilsson4416acd2016-09-16 13:27:11 -0700543 done();
544 });
545 // Without wrapping this test in flush(), the below two calls to
546 // MockInteractions.tap() cause a race in some situations in shadow DOM.
547 // The send button can be tapped before the others, causing the test to
548 // fail.
Becky Siegela499e3d2017-04-06 16:57:13 -0700549
Becky Siegel61613932017-05-30 15:09:20 -0700550 element.$$('gr-label-scores').$$(
551 'gr-label-score-row[name="Verified"]').setSelectedValue(-1);
Kasper Nilsson4416acd2016-09-16 13:27:11 -0700552 MockInteractions.tap(element.$$('.send'));
Logan Hanksca3c4972016-09-14 16:25:56 -0700553 });
Logan Hanksca3c4972016-09-14 16:25:56 -0700554 });
Becky Siegel8bb09342016-10-17 17:49:31 -0700555
Kasper Nilssonad908b12017-05-11 11:26:10 -0700556 test('_processReviewerChange', () => {
557 const mockIndexSplices = function(toRemove) {
Kasper Nilssona8271552017-01-17 11:54:40 -0800558 return [{
559 removed: [toRemove],
560 }];
561 };
562
563 element._processReviewerChange(
564 mockIndexSplices(makeAccount()), 'REVIEWER');
565 assert.equal(element._reviewersPendingRemove.REVIEWER.length, 1);
566 });
567
Kasper Nilssonad908b12017-05-11 11:26:10 -0700568 test('_purgeReviewersPendingRemove', () => {
569 const removeStub = sandbox.stub(element, '_removeAccount');
570 const mock = function() {
Kasper Nilssona8271552017-01-17 11:54:40 -0800571 element._reviewersPendingRemove = {
572 test: [makeAccount()],
573 test2: [makeAccount(), makeAccount()],
574 };
575 };
Kasper Nilssonad908b12017-05-11 11:26:10 -0700576 const checkObjEmpty = function(obj) {
577 for (const prop in obj) {
Kasper Nilssona8271552017-01-17 11:54:40 -0800578 if (obj.hasOwnProperty(prop) && obj[prop].length) { return false; }
579 }
580 return true;
581 };
582 mock();
583 element._purgeReviewersPendingRemove(true); // Cancel
584 assert.isFalse(removeStub.called);
585 assert.isTrue(checkObjEmpty(element._reviewersPendingRemove));
586
587 mock();
588 element._purgeReviewersPendingRemove(false); // Submit
589 assert.isTrue(removeStub.called);
590 assert.isTrue(checkObjEmpty(element._reviewersPendingRemove));
591 });
592
Kasper Nilssonad908b12017-05-11 11:26:10 -0700593 test('_removeAccount', done => {
Kasper Nilssona8271552017-01-17 11:54:40 -0800594 sandbox.stub(element.$.restAPI, 'removeChangeReviewer')
595 .returns(Promise.resolve({ok: true}));
Kasper Nilssonad908b12017-05-11 11:26:10 -0700596 const arr = [makeAccount(), makeAccount()];
Kasper Nilssona8271552017-01-17 11:54:40 -0800597 element.change.reviewers = {
598 REVIEWER: arr.slice(),
599 };
600
Kasper Nilssonad908b12017-05-11 11:26:10 -0700601 element._removeAccount(arr[1], 'REVIEWER').then(() => {
Kasper Nilssona8271552017-01-17 11:54:40 -0800602 assert.equal(element.change.reviewers.REVIEWER.length, 1);
603 assert.deepEqual(element.change.reviewers.REVIEWER, arr.slice(0, 1));
604 done();
605 });
606 });
Logan Hanks64451c02017-03-22 13:39:40 -0700607
Kasper Nilssond5896712017-06-01 17:59:02 -0700608 test('moving from cc to reviewer', () => {
609 element.serverConfig = {note_db_enabled: true};
610 element._reviewersPendingRemove = {
611 CC: [],
612 REVIEWER: [],
613 };
614 flushAsynchronousOperations();
615
616 const reviewer1 = makeAccount();
617 const reviewer2 = makeAccount();
618 const reviewer3 = makeAccount();
619 const cc1 = makeAccount();
620 const cc2 = makeAccount();
621 const cc3 = makeAccount();
622 const cc4 = makeAccount();
623 element._reviewers = [reviewer1, reviewer2, reviewer3];
624 element._ccs = [cc1, cc2, cc3, cc4];
625 element.push('_reviewers', cc1);
626 flushAsynchronousOperations();
627
628 assert.deepEqual(element._reviewers,
629 [reviewer1, reviewer2, reviewer3, cc1]);
630 assert.deepEqual(element._ccs, [cc2, cc3, cc4]);
631 assert.deepEqual(element._reviewersPendingRemove.CC, [cc1]);
632
633 element.push('_reviewers', cc4, cc3);
634 flushAsynchronousOperations();
635
636 assert.deepEqual(element._reviewers,
637 [reviewer1, reviewer2, reviewer3, cc1, cc4, cc3]);
638 assert.deepEqual(element._ccs, [cc2]);
639 assert.deepEqual(element._reviewersPendingRemove.CC, [cc1, cc4, cc3]);
640 });
641
Kasper Nilssonad908b12017-05-11 11:26:10 -0700642 test('migrate reviewers between states', done => {
Logan Hanks64451c02017-03-22 13:39:40 -0700643 element.serverConfig = {note_db_enabled: true};
644 element._reviewersPendingRemove = {
645 CC: [],
646 REVIEWER: [],
647 };
648 flushAsynchronousOperations();
Kasper Nilssonad908b12017-05-11 11:26:10 -0700649 const reviewers = element.$.reviewers;
650 const ccs = element.$$('#ccs');
651 const reviewer1 = makeAccount();
652 const reviewer2 = makeAccount();
653 const cc1 = makeAccount();
654 const cc2 = makeAccount();
Kasper Nilsson7591ceb2017-05-12 16:16:53 -0700655 const cc3 = makeAccount();
Logan Hanks64451c02017-03-22 13:39:40 -0700656 element._reviewers = [reviewer1, reviewer2];
Kasper Nilsson7591ceb2017-05-12 16:16:53 -0700657 element._ccs = [cc1, cc2, cc3];
Logan Hanks64451c02017-03-22 13:39:40 -0700658
Kasper Nilssonad908b12017-05-11 11:26:10 -0700659 const mutations = [];
Logan Hanks64451c02017-03-22 13:39:40 -0700660
Logan Hanks02c781e2017-07-18 14:18:01 -0700661 stubSaveReview(review => mutations.push(...review.reviewers));
Logan Hanks64451c02017-03-22 13:39:40 -0700662
Kasper Nilssonad908b12017-05-11 11:26:10 -0700663 sandbox.stub(element, '_removeAccount', (account, type) => {
664 mutations.push({state: 'REMOVED', account});
Logan Hanks64451c02017-03-22 13:39:40 -0700665 return Promise.resolve();
666 });
667
668 // Remove and add to other field.
669 reviewers.fire('remove', {account: reviewer1});
670 ccs.$.entry.fire('add', {value: {account: reviewer1}});
671 ccs.fire('remove', {account: cc1});
Kasper Nilsson7591ceb2017-05-12 16:16:53 -0700672 ccs.fire('remove', {account: cc3});
Logan Hanks64451c02017-03-22 13:39:40 -0700673 reviewers.$.entry.fire('add', {value: {account: cc1}});
674
675 // Add to other field without removing from former field.
676 // (Currently not possible in UI, but this is a good consistency check).
677 reviewers.$.entry.fire('add', {value: {account: cc2}});
678 ccs.$.entry.fire('add', {value: {account: reviewer2}});
Kasper Nilssonad908b12017-05-11 11:26:10 -0700679 const mapReviewer = function(reviewer, opt_state) {
680 const result = {reviewer: reviewer._account_id, confirmed: undefined};
Logan Hanks64451c02017-03-22 13:39:40 -0700681 if (opt_state) {
682 result.state = opt_state;
683 }
684 return result;
685 };
686
Kasper Nilsson7591ceb2017-05-12 16:16:53 -0700687 // Send and purge and verify moves, delete cc3.
Logan Hanks64451c02017-03-22 13:39:40 -0700688 element.send()
Kasper Nilsson7591ceb2017-05-12 16:16:53 -0700689 .then(keepReviewers =>
690 element._purgeReviewersPendingRemove(false, keepReviewers))
Kasper Nilssonad908b12017-05-11 11:26:10 -0700691 .then(() => {
692 assert.deepEqual(
693 mutations, [
694 mapReviewer(cc1),
695 mapReviewer(cc2),
696 mapReviewer(reviewer1, 'CC'),
697 mapReviewer(reviewer2, 'CC'),
Kasper Nilsson7591ceb2017-05-12 16:16:53 -0700698 {account: cc3, state: 'REMOVED'},
Kasper Nilssonad908b12017-05-11 11:26:10 -0700699 ]);
700 done();
701 });
Logan Hanks64451c02017-03-22 13:39:40 -0700702 });
Kasper Nilssonff0745d2017-03-21 16:41:24 -0700703
Kasper Nilssonad908b12017-05-11 11:26:10 -0700704 test('emits cancel on esc key', () => {
705 const cancelHandler = sandbox.spy();
Kasper Nilssonff0745d2017-03-21 16:41:24 -0700706 element.addEventListener('cancel', cancelHandler);
707 MockInteractions.pressAndReleaseKeyOn(element, 27, null, 'esc');
708 flushAsynchronousOperations();
709
710 assert.isTrue(cancelHandler.called);
711 });
Logan Hanksdc65dde2017-04-27 11:45:23 +0200712
Mike Frysinger81b90bc2017-07-25 18:24:32 -0400713 test('should not send on enter key', () => {
Logan Hanks3e0917e2017-07-19 08:32:25 -0700714 stubSaveReview(() => undefined);
Mike Frysinger81b90bc2017-07-25 18:24:32 -0400715 element.addEventListener('send', () => assert.fail('wrongly called'));
716 MockInteractions.pressAndReleaseKeyOn(element, 13, null, 'enter');
717 flushAsynchronousOperations();
718 });
719
720 test('emit send on ctrl+enter key', done => {
Logan Hanks3e0917e2017-07-19 08:32:25 -0700721 stubSaveReview(() => undefined);
Mike Frysinger81b90bc2017-07-25 18:24:32 -0400722 element.addEventListener('send', () => done());
723 MockInteractions.pressAndReleaseKeyOn(element, 13, 'ctrl', 'enter');
724 flushAsynchronousOperations();
725 });
726
Kasper Nilssonad908b12017-05-11 11:26:10 -0700727 test('_computeMessagePlaceholder', () => {
Logan Hanksdc65dde2017-04-27 11:45:23 +0200728 assert.equal(
729 element._computeMessagePlaceholder(false),
730 'Say something nice...');
731 assert.equal(
732 element._computeMessagePlaceholder(true),
733 'Add a note for your reviewers...');
734 });
735
Kasper Nilssonad908b12017-05-11 11:26:10 -0700736 test('_computeSendButtonLabel', () => {
Logan Hanksdc65dde2017-04-27 11:45:23 +0200737 assert.equal(
738 element._computeSendButtonLabel(false),
739 'Send');
740 assert.equal(
741 element._computeSendButtonLabel(true),
742 'Start review');
743 });
Wyatt Allene013aad2017-05-18 16:49:04 -0700744
745 test('_handle400Error reviewrs and CCs', done => {
746 const error1 = 'error 1';
747 const error2 = 'error 2';
748 const error3 = 'error 3';
749 const response = {
750 status: 400,
751 text() {
752 return Promise.resolve(')]}\'' + JSON.stringify({
753 reviewers: {
754 username1: {
755 input: 'user 1',
756 error: error1,
757 },
758 username2: {
759 input: 'user 2',
760 error: error2,
761 },
762 },
763 ccs: {
764 username3: {
765 input: 'user 3',
766 error: error3,
Kasper Nilsson5a93dbd2017-05-19 10:43:02 -0700767 },
Wyatt Allene013aad2017-05-18 16:49:04 -0700768 },
769 }));
Kasper Nilsson5a93dbd2017-05-19 10:43:02 -0700770 },
Wyatt Allene013aad2017-05-18 16:49:04 -0700771 };
772 element.addEventListener('server-error', e => {
773 e.detail.response.text().then(text => {
774 assert.equal(text, [error1, error2, error3].join(', '));
775 done();
Kasper Nilsson5a93dbd2017-05-19 10:43:02 -0700776 });
Wyatt Allene013aad2017-05-18 16:49:04 -0700777 });
778 element._handle400Error(response);
779 });
780
781 test('_handle400Error CCs only', done => {
782 const error1 = 'error 1';
783 const response = {
784 status: 400,
785 text() {
786 return Promise.resolve(')]}\'' + JSON.stringify({
787 ccs: {
788 username1: {
789 input: 'user 1',
790 error: error1,
Kasper Nilsson5a93dbd2017-05-19 10:43:02 -0700791 },
Wyatt Allene013aad2017-05-18 16:49:04 -0700792 },
793 }));
Kasper Nilsson5a93dbd2017-05-19 10:43:02 -0700794 },
Wyatt Allene013aad2017-05-18 16:49:04 -0700795 };
796 element.addEventListener('server-error', e => {
797 e.detail.response.text().then(text => {
798 assert.equal(text, error1);
799 done();
Kasper Nilsson5a93dbd2017-05-19 10:43:02 -0700800 });
Wyatt Allene013aad2017-05-18 16:49:04 -0700801 });
802 element._handle400Error(response);
803 });
Logan Hanks68b082e2017-07-13 14:19:13 -0700804
Logan Hanks02c781e2017-07-18 14:18:01 -0700805 suite('post review API', () => {
806 let startReviewStub;
Logan Hanks68b082e2017-07-13 14:19:13 -0700807
Logan Hanks02c781e2017-07-18 14:18:01 -0700808 setup(() => {
Logan Hanks3e0917e2017-07-19 08:32:25 -0700809 startReviewStub = sandbox.stub(element.$.restAPI, 'startReview', () => {
810 return Promise.resolve();
811 });
Logan Hanks02c781e2017-07-18 14:18:01 -0700812 });
813
814 test('ready property in review input on start review', () => {
815 stubSaveReview(review => {
816 assert.isTrue(review.ready);
817 return {ready: true};
818 });
819 return element.send(true, true).then(() => {
820 assert.isFalse(startReviewStub.called);
821 });
822 });
823
824 test('no ready property in review input on save review', () => {
825 stubSaveReview(review => {
826 assert.isUndefined(review.ready);
Logan Hanks02c781e2017-07-18 14:18:01 -0700827 });
828 return element.send(true, false).then(() => {
829 assert.isFalse(startReviewStub.called);
830 });
831 });
832
833 test('fall back to start review against old backend', () => {
834 stubSaveReview(review => {
835 return {}; // old backend won't set ready: true
836 });
837
838 return element.send(true, true).then(() => {
839 assert.isTrue(startReviewStub.called);
840 }).then(() => {
841 startReviewStub.reset();
842 return element.send(true, false);
843 }).then(() => {
844 assert.isFalse(startReviewStub.called);
845 });
846 });
Logan Hanks68b082e2017-07-13 14:19:13 -0700847 });
848
Logan Hanks02c781e2017-07-18 14:18:01 -0700849 suite('start review and save buttons', () => {
850 let sendStub;
851
Logan Hanks68b082e2017-07-13 14:19:13 -0700852 setup(() => {
Logan Hanks02c781e2017-07-18 14:18:01 -0700853 sendStub = sandbox.stub(element, 'send', () => Promise.resolve());
Logan Hanks68b082e2017-07-13 14:19:13 -0700854 element.canBeStarted = true;
855 // Flush to make both Start/Save buttons appear in DOM.
856 flushAsynchronousOperations();
857 });
858
859 test('start review sets ready', () => {
860 MockInteractions.tap(element.$$('.send'));
861 flushAsynchronousOperations();
Logan Hanks02c781e2017-07-18 14:18:01 -0700862 assert.isTrue(sendStub.calledWith(true, true));
Logan Hanks68b082e2017-07-13 14:19:13 -0700863 });
864
865 test('save review doesn\'t set ready', () => {
866 MockInteractions.tap(element.$$('.save'));
867 flushAsynchronousOperations();
Logan Hanks02c781e2017-07-18 14:18:01 -0700868 assert.isTrue(sendStub.calledWith(true, false));
Logan Hanks68b082e2017-07-13 14:19:13 -0700869 });
870 });
Logan Hanks3f1a6ff2017-07-25 11:05:23 -0700871
872 test('dummy message to force email on start review', () => {
873 stubSaveReview(review => {
874 assert.equal(review.message, element.START_REVIEW_MESSAGE);
875 return {ready: true};
876 });
877 return element.send(true, true);
878 });
Logan Hanks3e0917e2017-07-19 08:32:25 -0700879
880 test('buttons disabled until all API calls are resolved', () => {
881 stubSaveReview(review => {
882 return {}; // old backend won't set ready: true
883 });
884 // Check that element is disabled asynchronously after the setReady
885 // promise is returned. The element should not be reenabled until
886 // that promise is resolved.
887 sandbox.stub(element, '_maybeSetReady', (startReview, response) => {
888 return new Promise(resolve => {
889 Polymer.Base.async(() => {
890 assert.isTrue(element.disabled);
891 resolve();
892 });
893 });
894 });
895 return element.send(true, true).then(() => {
896 assert.isFalse(element.disabled);
897 });
898 });
899
900 suite('error handling', () => {
901 const expectedDraft = 'draft';
902 const expectedError = new Error('test');
903
904 setup(() => {
905 element.draft = expectedDraft;
906 });
907
908 function assertDialogOpenAndEnabled() {
909 assert.strictEqual(expectedDraft, element.draft);
910 assert.isFalse(element.disabled);
911 }
912
913 function assertDialogClosed() {
914 assert.strictEqual('', element.draft);
915 assert.isFalse(element.disabled);
916 }
917
918 test('error occurs in _saveReview', () => {
919 stubSaveReview(review => {
920 throw expectedError;
921 });
922 return element.send(true, true).catch(err => {
923 assert.strictEqual(expectedError, err);
924 assertDialogOpenAndEnabled();
925 });
926 });
927
928 test('error occurs during startReview', () => {
929 stubSaveReview(review => {
930 return {}; // old backend won't set ready: true
931 });
932 const errorStub = sandbox.stub(
933 console, 'error', (msg, err) => undefined);
934 sandbox.stub(element.$.restAPI, 'startReview', () => {
935 throw expectedError;
936 });
937 return element.send(true, true).then(() => {
938 assertDialogClosed();
939 assert.isTrue(
940 errorStub.calledWith('error setting ready:', expectedError));
941 });
942 });
943
944 test('non-ok response received by startReview', () => {
945 stubSaveReview(review => {
946 return {}; // old backend won't set ready: true
947 });
948 sandbox.stub(element.$.restAPI, 'startReview', (c, b, f) => {
949 f({status: 500});
950 });
951 return element.send(true, true).then(() => {
952 assertDialogClosed();
953 });
954 });
955
956 test('409 response received by startReview', () => {
957 stubSaveReview(review => {
958 return {}; // old backend won't set ready: true
959 });
960 sandbox.stub(element.$.restAPI, 'startReview', (c, b, f) => {
961 f({status: 409});
962 });
963 return element.send(true, true).then(() => {
964 assertDialogClosed();
965 });
966 });
967 });
Andrew Bonventref8b026d2015-12-09 17:55:54 -0500968 });
969</script>