blob: 2e4554fb05e942965073603abaa4c599baafc1f0 [file] [log] [blame]
Copybara854996b2021-09-07 19:36:02 +00001// Copyright 2019 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import {assert} from 'chai';
6import sinon from 'sinon';
7import {fireEvent} from '@testing-library/react';
8
9import {MrEditMetadata} from './mr-edit-metadata.js';
10import {ISSUE_EDIT_PERMISSION, ISSUE_EDIT_SUMMARY_PERMISSION,
11 ISSUE_EDIT_STATUS_PERMISSION, ISSUE_EDIT_OWNER_PERMISSION,
12 ISSUE_EDIT_CC_PERMISSION,
13} from 'shared/consts/permissions.js';
14import {FIELD_DEF_VALUE_EDIT} from 'reducers/permissions.js';
15import {store, resetState} from 'reducers/base.js';
16import {enterInput} from 'shared/test/helpers.js';
17
18let element;
19
20xdescribe('mr-edit-metadata', () => {
21 beforeEach(() => {
22 store.dispatch(resetState());
23 element = document.createElement('mr-edit-metadata');
24 document.body.appendChild(element);
25
26 element.issuePermissions = [ISSUE_EDIT_PERMISSION];
27
28 sinon.stub(store, 'dispatch');
29 });
30
31 afterEach(() => {
32 document.body.removeChild(element);
33 store.dispatch.restore();
34 });
35
36 it('initializes', () => {
37 assert.instanceOf(element, MrEditMetadata);
38 });
39
40 describe('updated sets initial values', () => {
41 it('updates owner', async () => {
42 element.ownerName = 'goose@bird.org';
43 await element.updateComplete;
44
45 assert.equal(element._values.owner, 'goose@bird.org');
46 });
47
48 it('updates cc', async () => {
49 element.cc = [
50 {displayName: 'initial-cc@bird.org', userId: '1234'},
51 ];
52 await element.updateComplete;
53
54 assert.deepEqual(element._values.cc, ['initial-cc@bird.org']);
55 });
56
57 it('updates components', async () => {
58 element.components = [{path: 'Hello>World'}];
59
60 await element.updateComplete;
61
62 assert.deepEqual(element._values.components, ['Hello>World']);
63 });
64
65 it('updates labels', async () => {
66 element.labelNames = ['test-label'];
67
68 await element.updateComplete;
69
70 assert.deepEqual(element._values.labels, ['test-label']);
71 });
72 });
73
74 describe('saves edit form', () => {
75 let saveStub;
76
77 beforeEach(() => {
78 saveStub = sinon.stub();
79 element.addEventListener('save', saveStub);
80 });
81
82 it('saves on form submit', async () => {
83 await element.updateComplete;
84
85 element.querySelector('#editForm').dispatchEvent(
86 new Event('submit', {bubbles: true, cancelable: true}));
87
88 sinon.assert.calledOnce(saveStub);
89 });
90
91 it('saves when clicking the save button', async () => {
92 await element.updateComplete;
93
94 element.querySelector('.save-changes').click();
95
96 sinon.assert.calledOnce(saveStub);
97 });
98
99 it('does not save on random keydowns', async () => {
100 await element.updateComplete;
101
102 element.querySelector('#editForm').dispatchEvent(
103 new KeyboardEvent('keydown', {key: 'a', ctrlKey: true}));
104 element.querySelector('#editForm').dispatchEvent(
105 new KeyboardEvent('keydown', {key: 'b', ctrlKey: false}));
106 element.querySelector('#editForm').dispatchEvent(
107 new KeyboardEvent('keydown', {key: 'c', metaKey: true}));
108
109 sinon.assert.notCalled(saveStub);
110 });
111
112 it('does not save on Enter without Ctrl', async () => {
113 await element.updateComplete;
114
115 element.querySelector('#editForm').dispatchEvent(
116 new KeyboardEvent('keydown', {key: 'Enter', ctrlKey: false}));
117
118 sinon.assert.notCalled(saveStub);
119 });
120
121 it('saves on Ctrl+Enter', async () => {
122 await element.updateComplete;
123
124 element.querySelector('#editForm').dispatchEvent(
125 new KeyboardEvent('keydown', {key: 'Enter', ctrlKey: true}));
126
127 sinon.assert.calledOnce(saveStub);
128 });
129
130 it('saves on Ctrl+Meta', async () => {
131 await element.updateComplete;
132
133 element.querySelector('#editForm').dispatchEvent(
134 new KeyboardEvent('keydown', {key: 'Enter', metaKey: true}));
135
136 sinon.assert.calledOnce(saveStub);
137 });
138 });
139
140 it('disconnecting element reports form is not dirty', () => {
141 element.formName = 'test';
142
143 assert.isFalse(store.dispatch.calledOnce);
144
145 document.body.removeChild(element);
146
147 assert.isTrue(store.dispatch.calledOnce);
148 sinon.assert.calledWith(
149 store.dispatch,
150 {
151 type: 'REPORT_DIRTY_FORM',
152 name: 'test',
153 isDirty: false,
154 },
155 );
156
157 document.body.appendChild(element);
158 });
159
160 it('_processChanges fires change event', async () => {
161 await element.updateComplete;
162
163 const changeStub = sinon.stub();
164 element.addEventListener('change', changeStub);
165
166 element._processChanges();
167
168 sinon.assert.calledOnce(changeStub);
169 });
170
171 it('save button disabled when disabled is true', async () => {
172 // Check that save button is initially disabled.
173 await element.updateComplete;
174
175 const button = element.querySelector('.save-changes');
176
177 assert.isTrue(element.disabled);
178 assert.isTrue(button.disabled);
179
180 element.isDirty = true;
181
182 await element.updateComplete;
183
184 assert.isFalse(element.disabled);
185 assert.isFalse(button.disabled);
186 });
187
188 it('editing form sets isDirty to true or false', async () => {
189 await element.updateComplete;
190
191 assert.isFalse(element.isDirty);
192
193 // User makes some changes.
194 const comment = element.querySelector('#commentText');
195 comment.value = 'Value';
196 comment.dispatchEvent(new Event('keyup'));
197
198 assert.isTrue(element.isDirty);
199
200 // User undoes the changes.
201 comment.value = '';
202 comment.dispatchEvent(new Event('keyup'));
203
204 assert.isFalse(element.isDirty);
205 });
206
207 it('reseting form disables save button', async () => {
208 // Check that save button is initially disabled.
209 assert.isTrue(element.disabled);
210
211 // User makes some changes.
212 element.isDirty = true;
213
214 // Check that save button is not disabled.
215 assert.isFalse(element.disabled);
216
217 // Reset form.
218 await element.updateComplete;
219 await element.reset();
220
221 // Check that save button is still disabled.
222 assert.isTrue(element.disabled);
223 });
224
225 it('save button is enabled if request fails', async () => {
226 // Check that save button is initially disabled.
227 assert.isTrue(element.disabled);
228
229 // User makes some changes.
230 element.isDirty = true;
231
232 // Check that save button is not disabled.
233 assert.isFalse(element.disabled);
234
235 // User submits the change.
236 element.saving = true;
237
238 // Check that save button is disabled.
239 assert.isTrue(element.disabled);
240
241 // Request fails.
242 element.saving = false;
243 element.error = 'error';
244
245 // Check that save button is re-enabled.
246 assert.isFalse(element.disabled);
247 });
248
249 it('delta empty when no changes', async () => {
250 await element.updateComplete;
251 assert.deepEqual(element.delta, {});
252 });
253
254 it('toggling checkbox toggles sendEmail', async () => {
255 element.sendEmail = false;
256
257 await element.updateComplete;
258 const checkbox = element.querySelector('#sendEmail');
259
260 await checkbox.updateComplete;
261
262 checkbox.click();
263 await element.updateComplete;
264
265 assert.equal(checkbox.checked, true);
266 assert.equal(element.sendEmail, true);
267
268 checkbox.click();
269 await element.updateComplete;
270
271 assert.equal(checkbox.checked, false);
272 assert.equal(element.sendEmail, false);
273
274 checkbox.click();
275 await element.updateComplete;
276
277 assert.equal(checkbox.checked, true);
278 assert.equal(element.sendEmail, true);
279 });
280
281 it('changing status produces delta change (lit-element)', async () => {
282 element.statuses = [
283 {'status': 'New'},
284 {'status': 'Old'},
285 {'status': 'Test'},
286 ];
287 element.status = 'New';
288
289 await element.updateComplete;
290
291 const statusComponent = element.querySelector('#statusInput');
292 statusComponent.status = 'Old';
293
294 await element.updateComplete;
295
296 assert.deepEqual(element.delta, {
297 status: 'Old',
298 });
299 });
300
301 it('changing owner produces delta change (React)', async () => {
302 element.ownerName = 'initial-owner@bird.org';
303 await element.updateComplete;
304
305 const input = element.querySelector('#ownerInput');
306 enterInput(input, 'new-owner@bird.org');
307 await element.updateComplete;
308
309 const expected = {ownerRef: {displayName: 'new-owner@bird.org'}};
310 assert.deepEqual(element.delta, expected);
311 });
312
313 it('adding CC produces delta change (React)', async () => {
314 element.cc = [
315 {displayName: 'initial-cc@bird.org', userId: '1234'},
316 ];
317
318 await element.updateComplete;
319
320 const input = element.querySelector('#ccInput');
321 enterInput(input, 'another@bird.org');
322 await element.updateComplete;
323
324 const expected = {
325 ccRefsAdd: [{displayName: 'another@bird.org'}],
326 ccRefsRemove: [{displayName: 'initial-cc@bird.org'}],
327 };
328 assert.deepEqual(element.delta, expected);
329 });
330
331 it('invalid status throws', async () => {
332 element.statuses = [
333 {'status': 'New'},
334 {'status': 'Old'},
335 {'status': 'Duplicate'},
336 ];
337 element.status = 'Duplicate';
338
339 await element.updateComplete;
340
341 const statusComponent = element.querySelector('#statusInput');
342 statusComponent.shadowRoot.querySelector('#mergedIntoInput').value = 'xx';
343 assert.deepEqual(element.delta, {});
344 assert.equal(
345 element.error,
346 'Invalid issue ref: xx. Expected [projectName:]issueId.');
347 });
348
349 it('cannot block an issue on itself', async () => {
350 element.projectName = 'proj';
351 element.issueRef = {projectName: 'proj', localId: 123};
352
353 await element.updateComplete;
354
355 for (const fieldName of ['blockedOn', 'blocking']) {
356 const input =
357 element.querySelector(`#${fieldName}Input`);
358 enterInput(input, '123');
359 await element.updateComplete;
360
361 assert.deepEqual(element.delta, {});
362 assert.equal(
363 element.error,
364 `Invalid issue ref: 123. Cannot merge or block an issue on itself.`);
365 fireEvent.keyDown(input, {key: 'Backspace', code: 'Backspace'});
366 await element.updateComplete;
367
368 enterInput(input, 'proj:123');
369 await element.updateComplete;
370
371 assert.deepEqual(element.delta, {});
372 assert.equal(
373 element.error,
374 `Invalid issue ref: proj:123. ` +
375 'Cannot merge or block an issue on itself.');
376 fireEvent.keyDown(input, {key: 'Backspace', code: 'Backspace'});
377 await element.updateComplete;
378
379 enterInput(input, 'proj2:123');
380 await element.updateComplete;
381
382 assert.notDeepEqual(element.delta, {});
383 assert.equal(element.error, '');
384
385 fireEvent.keyDown(input, {key: 'Backspace', code: 'Backspace'});
386 await element.updateComplete;
387 }
388 });
389
390 it('cannot merge an issue into itself', async () => {
391 element.statuses = [
392 {'status': 'New'},
393 {'status': 'Duplicate'},
394 ];
395 element.status = 'New';
396 element.projectName = 'proj';
397 element.issueRef = {projectName: 'proj', localId: 123};
398
399 await element.updateComplete;
400
401 const statusComponent = element.querySelector('#statusInput');
402 const root = statusComponent.shadowRoot;
403 const statusInput = root.querySelector('#statusInput');
404 statusInput.value = 'Duplicate';
405 statusInput.dispatchEvent(new Event('change'));
406
407 await element.updateComplete;
408
409 root.querySelector('#mergedIntoInput').value = 'proj:123';
410 assert.deepEqual(element.delta, {});
411 assert.equal(
412 element.error,
413 `Invalid issue ref: proj:123. Cannot merge or block an issue on itself.`);
414
415 root.querySelector('#mergedIntoInput').value = '123';
416 assert.deepEqual(element.delta, {});
417 assert.equal(
418 element.error,
419 `Invalid issue ref: 123. Cannot merge or block an issue on itself.`);
420
421 root.querySelector('#mergedIntoInput').value = 'proj2:123';
422 assert.notDeepEqual(element.delta, {});
423 assert.equal(element.error, '');
424 });
425
426 it('cannot set invalid emails', async () => {
427 await element.updateComplete;
428
429 const ccInput = element.querySelector('#ccInput');
430 enterInput(ccInput, 'invalid!email');
431 await element.updateComplete;
432
433 assert.deepEqual(element.delta, {});
434 assert.equal(
435 element.error,
436 `Invalid email address: invalid!email`);
437
438 const input = element.querySelector('#ownerInput');
439 enterInput(input, 'invalid!email2');
440 await element.updateComplete;
441
442 assert.deepEqual(element.delta, {});
443 assert.equal(
444 element.error,
445 `Invalid email address: invalid!email2`);
446 });
447
448 it('can remove invalid values', async () => {
449 element.projectName = 'proj';
450 element.issueRef = {projectName: 'proj', localId: 123};
451
452 element.statuses = [
453 {'status': 'Duplicate'},
454 ];
455 element.status = 'Duplicate';
456 element.mergedInto = element.issueRef;
457
458 element.blockedOn = [element.issueRef];
459 element.blocking = [element.issueRef];
460
461 await element.updateComplete;
462
463 const blockedOnInput = element.querySelector('#blockedOnInput');
464 const blockingInput = element.querySelector('#blockingInput');
465 const statusInput = element.querySelector('#statusInput');
466
467 await element.updateComplete;
468
469 const mergedIntoInput =
470 statusInput.shadowRoot.querySelector('#mergedIntoInput');
471
472 fireEvent.keyDown(blockedOnInput, {key: 'Backspace', code: 'Backspace'});
473 await element.updateComplete;
474 fireEvent.keyDown(blockingInput, {key: 'Backspace', code: 'Backspace'});
475 await element.updateComplete;
476 mergedIntoInput.value = 'proj:124';
477 await element.updateComplete;
478
479 assert.deepEqual(
480 element.delta,
481 {
482 blockedOnRefsRemove: [{projectName: 'proj', localId: 123}],
483 blockingRefsRemove: [{projectName: 'proj', localId: 123}],
484 mergedIntoRef: {projectName: 'proj', localId: 124},
485 });
486 assert.equal(element.error, '');
487 });
488
489 it('not changing status produces no delta', async () => {
490 element.statuses = [
491 {'status': 'Duplicate'},
492 ];
493 element.status = 'Duplicate';
494
495 element.mergedInto = {
496 projectName: 'chromium',
497 localId: 1234,
498 };
499
500 element.projectName = 'chromium';
501
502 await element.updateComplete;
503 await element.updateComplete; // Merged input updates its value.
504
505 assert.deepEqual(element.delta, {});
506 });
507
508 it('changing status to duplicate produces delta change', async () => {
509 element.statuses = [
510 {'status': 'New'},
511 {'status': 'Duplicate'},
512 ];
513 element.status = 'New';
514
515 await element.updateComplete;
516
517 const statusComponent = element.querySelector(
518 '#statusInput');
519 const root = statusComponent.shadowRoot;
520 const statusInput = root.querySelector('#statusInput');
521 statusInput.value = 'Duplicate';
522 statusInput.dispatchEvent(new Event('change'));
523
524 await element.updateComplete;
525
526 root.querySelector('#mergedIntoInput').value = 'chromium:1234';
527 assert.deepEqual(element.delta, {
528 status: 'Duplicate',
529 mergedIntoRef: {
530 projectName: 'chromium',
531 localId: 1234,
532 },
533 });
534 });
535
536 it('changing summary produces delta change', async () => {
537 element.summary = 'Old summary';
538
539 await element.updateComplete;
540
541 element.querySelector(
542 '#summaryInput').value = 'newfangled fancy summary';
543 assert.deepEqual(element.delta, {
544 summary: 'newfangled fancy summary',
545 });
546 });
547
548 it('custom fields the user cannot edit should be hidden', async () => {
549 element.projectName = 'proj';
550 const fieldName = 'projects/proj/fieldDefs/1';
551 const restrictedFieldName = 'projects/proj/fieldDefs/2';
552 element._permissions = {
553 [fieldName]: {permissions: [FIELD_DEF_VALUE_EDIT]},
554 [restrictedFieldName]: {permissions: []}};
555 element.fieldDefs = [
556 {
557 fieldRef: {
558 fieldName: 'normalFd',
559 fieldId: 1,
560 type: 'ENUM_TYPE',
561 },
562 },
563 {
564 fieldRef: {
565 fieldName: 'cantEditFd',
566 fieldId: 2,
567 type: 'ENUM_TYPE',
568 },
569 },
570 ];
571
572 await element.updateComplete;
573 assert.isFalse(element.querySelector('#normalFdInput').hidden);
574 assert.isTrue(element.querySelector('#cantEditFdInput').hidden);
575 });
576
577 it('changing enum custom fields produces delta', async () => {
578 element.fieldValueMap = new Map([['fakefield', ['prev value']]]);
579 element.fieldDefs = [
580 {
581 fieldRef: {
582 fieldName: 'testField',
583 fieldId: 1,
584 type: 'ENUM_TYPE',
585 },
586 },
587 {
588 fieldRef: {
589 fieldName: 'fakeField',
590 fieldId: 2,
591 type: 'ENUM_TYPE',
592 },
593 },
594 ];
595
596 await element.updateComplete;
597
598 const input1 = element.querySelector('#testFieldInput');
599 const input2 = element.querySelector('#fakeFieldInput');
600
601 input1.values = ['test value'];
602 input2.values = [];
603
604 await element.updateComplete;
605
606 assert.deepEqual(element.delta, {
607 fieldValsAdd: [
608 {
609 fieldRef: {
610 fieldName: 'testField',
611 fieldId: 1,
612 type: 'ENUM_TYPE',
613 },
614 value: 'test value',
615 },
616 ],
617 fieldValsRemove: [
618 {
619 fieldRef: {
620 fieldName: 'fakeField',
621 fieldId: 2,
622 type: 'ENUM_TYPE',
623 },
624 value: 'prev value',
625 },
626 ],
627 });
628 });
629
630 it('changing approvers produces delta', async () => {
631 element.isApproval = true;
632 element.hasApproverPrivileges = true;
633 element.approvers = [
634 {displayName: 'foo@example.com', userId: '1'},
635 {displayName: 'bar@example.com', userId: '2'},
636 {displayName: 'baz@example.com', userId: '3'},
637 ];
638
639 await element.updateComplete;
640 await element.updateComplete;
641
642 element.querySelector('#approversInput').values =
643 ['chicken@example.com', 'foo@example.com', 'dog@example.com'];
644
645 await element.updateComplete;
646
647 assert.deepEqual(element.delta, {
648 approverRefsAdd: [
649 {displayName: 'chicken@example.com'},
650 {displayName: 'dog@example.com'},
651 ],
652 approverRefsRemove: [
653 {displayName: 'bar@example.com'},
654 {displayName: 'baz@example.com'},
655 ],
656 });
657 });
658
659 it('changing blockedon produces delta change (React)', async () => {
660 element.blockedOn = [
661 {projectName: 'chromium', localId: '1234'},
662 {projectName: 'monorail', localId: '4567'},
663 ];
664 element.projectName = 'chromium';
665
666 await element.updateComplete;
667 await element.updateComplete;
668
669 const input = element.querySelector('#blockedOnInput');
670
671 fireEvent.keyDown(input, {key: 'Backspace', code: 'Backspace'});
672 await element.updateComplete;
673
674 enterInput(input, 'v8:5678');
675 await element.updateComplete;
676
677 assert.deepEqual(element.delta, {
678 blockedOnRefsAdd: [{
679 projectName: 'v8',
680 localId: 5678,
681 }],
682 blockedOnRefsRemove: [{
683 projectName: 'monorail',
684 localId: 4567,
685 }],
686 });
687 });
688
689 it('_optionsForField computes options', () => {
690 const optionsPerEnumField = new Map([
691 ['enumfield', [{optionName: 'one'}, {optionName: 'two'}]],
692 ]);
693 assert.deepEqual(
694 element._optionsForField(optionsPerEnumField, new Map(), 'enumField'), [
695 {
696 optionName: 'one',
697 },
698 {
699 optionName: 'two',
700 },
701 ]);
702 });
703
704 it('changing enum fields produces delta', async () => {
705 element.fieldDefs = [
706 {
707 fieldRef: {
708 fieldName: 'enumField',
709 fieldId: 1,
710 type: 'ENUM_TYPE',
711 },
712 isMultivalued: true,
713 },
714 ];
715
716 element.optionsPerEnumField = new Map([
717 ['enumfield', [{optionName: 'one'}, {optionName: 'two'}]],
718 ]);
719
720 await element.updateComplete;
721 await element.updateComplete;
722
723 element.querySelector(
724 '#enumFieldInput').values = ['one', 'two'];
725
726 await element.updateComplete;
727
728 assert.deepEqual(element.delta, {
729 fieldValsAdd: [
730 {
731 fieldRef: {
732 fieldName: 'enumField',
733 fieldId: 1,
734 type: 'ENUM_TYPE',
735 },
736 value: 'one',
737 },
738 {
739 fieldRef: {
740 fieldName: 'enumField',
741 fieldId: 1,
742 type: 'ENUM_TYPE',
743 },
744 value: 'two',
745 },
746 ],
747 });
748 });
749
750 it('changing multiple single valued enum fields', async () => {
751 element.fieldDefs = [
752 {
753 fieldRef: {
754 fieldName: 'enumField',
755 fieldId: 1,
756 type: 'ENUM_TYPE',
757 },
758 },
759 {
760 fieldRef: {
761 fieldName: 'enumField2',
762 fieldId: 2,
763 type: 'ENUM_TYPE',
764 },
765 },
766 ];
767
768 element.optionsPerEnumField = new Map([
769 ['enumfield', [{optionName: 'one'}, {optionName: 'two'}]],
770 ['enumfield2', [{optionName: 'three'}, {optionName: 'four'}]],
771 ]);
772
773 await element.updateComplete;
774
775 element.querySelector('#enumFieldInput').values = ['two'];
776 element.querySelector('#enumField2Input').values = ['three'];
777
778 await element.updateComplete;
779
780 assert.deepEqual(element.delta, {
781 fieldValsAdd: [
782 {
783 fieldRef: {
784 fieldName: 'enumField',
785 fieldId: 1,
786 type: 'ENUM_TYPE',
787 },
788 value: 'two',
789 },
790 {
791 fieldRef: {
792 fieldName: 'enumField2',
793 fieldId: 2,
794 type: 'ENUM_TYPE',
795 },
796 value: 'three',
797 },
798 ],
799 });
800 });
801
802 it('adding components produces delta', async () => {
803 await element.updateComplete;
804
805 element.isApproval = false;
806 element.issuePermissions = [ISSUE_EDIT_PERMISSION];
807
808 element.components = [];
809
810 await element.updateComplete;
811
812 element._values.components = ['Hello>World'];
813
814 await element.updateComplete;
815
816 assert.deepEqual(element.delta, {
817 compRefsAdd: [
818 {path: 'Hello>World'},
819 ],
820 });
821
822 element._values.components = ['Hello>World', 'Test', 'Multi'];
823
824 await element.updateComplete;
825
826 assert.deepEqual(element.delta, {
827 compRefsAdd: [
828 {path: 'Hello>World'},
829 {path: 'Test'},
830 {path: 'Multi'},
831 ],
832 });
833
834 element._values.components = [];
835
836 await element.updateComplete;
837
838 assert.deepEqual(element.delta, {});
839 });
840
841 it('removing components produces delta', async () => {
842 await element.updateComplete;
843
844 element.isApproval = false;
845 element.issuePermissions = [ISSUE_EDIT_PERMISSION];
846
847 element.components = [{path: 'Hello>World'}];
848
849 await element.updateComplete;
850
851 element._values.components = [];
852
853 await element.updateComplete;
854
855 assert.deepEqual(element.delta, {
856 compRefsRemove: [
857 {path: 'Hello>World'},
858 ],
859 });
860 });
861
862 it('approver input appears when user has privileges', async () => {
863 assert.isNull(element.querySelector('#approversInput'));
864 element.isApproval = true;
865 element.hasApproverPrivileges = true;
866
867 await element.updateComplete;
868
869 assert.isNotNull(element.querySelector('#approversInput'));
870 });
871
872 it('reset sets controlled values to default', async () => {
873 element.ownerName = 'burb@bird.com';
874 element.cc = [
875 {displayName: 'flamingo@bird.com', userId: '1234'},
876 {displayName: 'penguin@bird.com', userId: '5678'},
877 ];
878 element.components = [{path: 'Bird>Penguin'}];
879 element.labelNames = ['chickadee-chirp'];
880 element.blockedOn = [{localId: 1234, projectName: 'project'}];
881 element.blocking = [{localId: 5678, projectName: 'other-project'}];
882 element.projectName = 'project';
883
884 // Update cycle is needed because <mr-edit-metadata> initializes
885 // this.values in updated().
886 await element.updateComplete;
887
888 const initialValues = {
889 owner: 'burb@bird.com',
890 cc: ['flamingo@bird.com', 'penguin@bird.com'],
891 components: ['Bird>Penguin'],
892 labels: ['chickadee-chirp'],
893 blockedOn: ['1234'],
894 blocking: ['other-project:5678'],
895 };
896
897 assert.deepEqual(element._values, initialValues);
898
899 element._values = {
900 owner: 'newburb@hello.com',
901 cc: ['noburbs@wings.com'],
902 };
903 element.reset();
904
905 assert.deepEqual(element._values, initialValues);
906 })
907
908 it('reset empties form values', async () => {
909 element.fieldDefs = [
910 {
911 fieldRef: {
912 fieldName: 'testField',
913 fieldId: 1,
914 type: 'ENUM_TYPE',
915 },
916 },
917 {
918 fieldRef: {
919 fieldName: 'fakeField',
920 fieldId: 2,
921 type: 'ENUM_TYPE',
922 },
923 },
924 ];
925
926 await element.updateComplete;
927
928 const uploader = element.querySelector('mr-upload');
929 uploader.files = [
930 {name: 'test.png'},
931 {name: 'rutabaga.png'},
932 ];
933
934 element.querySelector('#testFieldInput').values = 'testy test';
935 element.querySelector('#fakeFieldInput').values = 'hello world';
936
937 await element.reset();
938
939 assert.lengthOf(element.querySelector('#testFieldInput').value, 0);
940 assert.lengthOf(element.querySelector('#fakeFieldInput').value, 0);
941 assert.lengthOf(uploader.files, 0);
942 });
943
944 it('reset results in empty delta', async () => {
945 element.ownerName = 'goose@bird.org';
946 await element.updateComplete;
947
948 element._values.owner = 'penguin@bird.org';
949 const expected = {ownerRef: {displayName: 'penguin@bird.org'}};
950 assert.deepEqual(element.delta, expected);
951
952 await element.reset();
953 assert.deepEqual(element.delta, {});
954 });
955
956 it('edit issue permissions', async () => {
957 const allFields = ['summary', 'status', 'owner', 'cc'];
958 const testCases = [
959 {permissions: [], nonNull: []},
960 {permissions: [ISSUE_EDIT_PERMISSION], nonNull: allFields},
961 {permissions: [ISSUE_EDIT_SUMMARY_PERMISSION], nonNull: ['summary']},
962 {permissions: [ISSUE_EDIT_STATUS_PERMISSION], nonNull: ['status']},
963 {permissions: [ISSUE_EDIT_OWNER_PERMISSION], nonNull: ['owner']},
964 {permissions: [ISSUE_EDIT_CC_PERMISSION], nonNull: ['cc']},
965 ];
966 element.statuses = [{'status': 'Foo'}];
967
968 for (const testCase of testCases) {
969 element.issuePermissions = testCase.permissions;
970 await element.updateComplete;
971
972 allFields.forEach((fieldName) => {
973 const field = element.querySelector(`#${fieldName}Input`);
974 if (testCase.nonNull.includes(fieldName)) {
975 assert.isNotNull(field);
976 } else {
977 assert.isNull(field);
978 }
979 });
980 }
981 });
982
983 it('duplicate issue is rendered correctly', async () => {
984 element.statuses = [
985 {'status': 'Duplicate'},
986 ];
987 element.status = 'Duplicate';
988 element.projectName = 'chromium';
989 element.mergedInto = {
990 projectName: 'chromium',
991 localId: 1234,
992 };
993
994 await element.updateComplete;
995 await element.updateComplete;
996
997 const statusComponent = element.querySelector('#statusInput');
998 const root = statusComponent.shadowRoot;
999 assert.equal(
1000 root.querySelector('#mergedIntoInput').value, '1234');
1001 });
1002
1003 it('duplicate issue on different project is rendered correctly', async () => {
1004 element.statuses = [
1005 {'status': 'Duplicate'},
1006 ];
1007 element.status = 'Duplicate';
1008 element.projectName = 'chromium';
1009 element.mergedInto = {
1010 projectName: 'monorail',
1011 localId: 1234,
1012 };
1013
1014 await element.updateComplete;
1015 await element.updateComplete;
1016
1017 const statusComponent = element.querySelector('#statusInput');
1018 const root = statusComponent.shadowRoot;
1019 assert.equal(
1020 root.querySelector('#mergedIntoInput').value, 'monorail:1234');
1021 });
1022
1023 it('filter out deleted users', async () => {
1024 element.cc = [
1025 {displayName: 'test@example.com', userId: '1234'},
1026 {displayName: 'a_deleted_user'},
1027 {displayName: 'someone@example.com', userId: '5678'},
1028 ];
1029
1030 await element.updateComplete;
1031
1032 assert.deepEqual(element._values.cc, [
1033 'test@example.com',
1034 'someone@example.com',
1035 ]);
1036 });
1037
1038 it('renders valid markdown description with preview', async () => {
1039 await element.updateComplete;
1040
1041 element.prefs = new Map([['render_markdown', true]]);
1042 element.projectName = 'monkeyrail';
1043 sinon.stub(element, 'getCommentContent').returns('# h1');
1044
1045 await element.updateComplete;
1046
1047 assert.isTrue(element._renderMarkdown);
1048
1049 const previewMarkdown = element.querySelector('.markdown-preview');
1050 assert.isNotNull(previewMarkdown);
1051
1052 const headerText = previewMarkdown.querySelector('h1').textContent;
1053 assert.equal(headerText, 'h1');
1054 });
1055
1056 it('does not show preview when markdown is disabled', async () => {
1057 element.prefs = new Map([['render_markdown', false]]);
1058 element.projectName = 'monkeyrail';
1059 sinon.stub(element, 'getCommentContent').returns('# h1');
1060
1061 await element.updateComplete;
1062
1063 const previewMarkdown = element.querySelector('.markdown-preview');
1064 assert.isNull(previewMarkdown);
1065 });
1066
1067 it('does not show preview when no input', async () => {
1068 element.prefs = new Map([['render_markdown', true]]);
1069 element.projectName = 'monkeyrail';
1070 sinon.stub(element, 'getCommentContent').returns('');
1071
1072 await element.updateComplete;
1073
1074 const previewMarkdown = element.querySelector('.markdown-preview');
1075 assert.isNull(previewMarkdown);
1076 });
1077});
1078