blob: a73794eb4d9248a165905238b9b784a0e191b53c [file] [log] [blame]
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +01001// Copyright 2019 The Chromium Authors
Copybara854996b2021-09-07 19:36:02 +00002// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import sinon from 'sinon';
6import {assert} from 'chai';
7import {prpcClient} from 'prpc-client-instance.js';
8import {MrEditIssue, allowRemovedRestrictions} from './mr-edit-issue.js';
9import {clientLoggerFake} from 'shared/test/fakes.js';
Adrià Vilanova Martínez9f9ade52022-10-10 23:20:11 +020010import {migratedTypes} from 'shared/issue-fields.js';
Copybara854996b2021-09-07 19:36:02 +000011
12let element;
13let clock;
14
15describe('mr-edit-issue', () => {
16 beforeEach(() => {
17 element = document.createElement('mr-edit-issue');
18 document.body.appendChild(element);
19 sinon.stub(prpcClient, 'call');
20
21 element.clientLogger = clientLoggerFake();
22 clock = sinon.useFakeTimers();
23 });
24
25 afterEach(() => {
26 document.body.removeChild(element);
27 prpcClient.call.restore();
28
29 clock.restore();
30 });
31
32 it('initializes', () => {
33 assert.instanceOf(element, MrEditIssue);
34 });
35
36 it('scrolls into view on #makechanges hash', async () => {
37 await element.updateComplete;
38
39 const header = element.querySelector('#makechanges');
40 sinon.stub(header, 'scrollIntoView');
41
42 element.focusId = 'makechanges';
43 await element.updateComplete;
44
45 assert.isTrue(header.scrollIntoView.calledOnce);
46
47 header.scrollIntoView.restore();
48 });
49
50 it('shows snackbar and resets form when editing finishes', async () => {
51 sinon.stub(element, 'reset');
52 sinon.stub(element, '_showCommentAddedSnackbar');
53
54 element.updatingIssue = true;
55 await element.updateComplete;
56
57 sinon.assert.notCalled(element._showCommentAddedSnackbar);
58 sinon.assert.notCalled(element.reset);
59
60 element.updatingIssue = false;
61 await element.updateComplete;
62
63 sinon.assert.calledOnce(element._showCommentAddedSnackbar);
64 sinon.assert.calledOnce(element.reset);
65 });
66
67 it('does not show snackbar or reset form on edit error', async () => {
68 sinon.stub(element, 'reset');
69 sinon.stub(element, '_showCommentAddedSnackbar');
70
71 element.updatingIssue = true;
72 await element.updateComplete;
73
74 element.updateError = 'The save failed';
75 element.updatingIssue = false;
76 await element.updateComplete;
77
78 sinon.assert.notCalled(element._showCommentAddedSnackbar);
79 sinon.assert.notCalled(element.reset);
80 });
81
82 it('shows current status even if not defined for project', async () => {
83 await element.updateComplete;
84
85 const editMetadata = element.querySelector('mr-edit-metadata');
86 assert.deepEqual(editMetadata.statuses, []);
87
88 element.projectConfig = {statusDefs: [
89 {status: 'hello'},
90 {status: 'world'},
91 ]};
92
93 await editMetadata.updateComplete;
94
95 assert.deepEqual(editMetadata.statuses, [
96 {status: 'hello'},
97 {status: 'world'},
98 ]);
99
100 element.issue = {
101 statusRef: {status: 'hello'},
102 };
103
104 await editMetadata.updateComplete;
105
106 assert.deepEqual(editMetadata.statuses, [
107 {status: 'hello'},
108 {status: 'world'},
109 ]);
110
111 element.issue = {
112 statusRef: {status: 'weirdStatus'},
113 };
114
115 await editMetadata.updateComplete;
116
117 assert.deepEqual(editMetadata.statuses, [
118 {status: 'weirdStatus'},
119 {status: 'hello'},
120 {status: 'world'},
121 ]);
122 });
123
124 it('ignores deprecated statuses, unless used on current issue', async () => {
125 await element.updateComplete;
126
127 const editMetadata = element.querySelector('mr-edit-metadata');
128 assert.deepEqual(editMetadata.statuses, []);
129
130 element.projectConfig = {statusDefs: [
131 {status: 'new'},
132 {status: 'accepted', deprecated: false},
133 {status: 'compiling', deprecated: true},
134 ]};
135
136 await editMetadata.updateComplete;
137
138 assert.deepEqual(editMetadata.statuses, [
139 {status: 'new'},
140 {status: 'accepted', deprecated: false},
141 ]);
142
143
144 element.issue = {
145 statusRef: {status: 'compiling'},
146 };
147
148 await editMetadata.updateComplete;
149
150 assert.deepEqual(editMetadata.statuses, [
151 {status: 'compiling'},
152 {status: 'new'},
153 {status: 'accepted', deprecated: false},
154 ]);
155 });
156
157 it('filter out empty or deleted user owners', () => {
158 assert.equal(
159 element._ownerDisplayName({displayName: 'a_deleted_user'}),
160 '');
161 assert.equal(
162 element._ownerDisplayName({
163 displayName: 'test@example.com',
164 userId: '1234',
165 }),
166 'test@example.com');
167 });
168
169 it('logs issue-update metrics', async () => {
170 await element.updateComplete;
171
172 const editMetadata = element.querySelector('mr-edit-metadata');
173
174 sinon.stub(editMetadata, 'delta').get(() => ({summary: 'test'}));
175
176 await element.save();
177
178 sinon.assert.calledOnce(element.clientLogger.logStart);
179 sinon.assert.calledWith(element.clientLogger.logStart,
180 'issue-update', 'computer-time');
181
182 // Simulate a response updating the UI.
183 element.issue = {summary: 'test'};
184
185 await element.updateComplete;
186 await element.updateComplete;
187
188 sinon.assert.calledOnce(element.clientLogger.logEnd);
189 sinon.assert.calledWith(element.clientLogger.logEnd,
190 'issue-update', 'computer-time', 120 * 1000);
191 });
192
193 it('presubmits issue on metadata change', async () => {
194 element.issueRef = {};
195
196 await element.updateComplete;
197 const editMetadata = element.querySelector('mr-edit-metadata');
198 editMetadata.dispatchEvent(new CustomEvent('change', {
199 detail: {
200 delta: {
201 summary: 'Summary',
202 },
203 },
204 }));
205
206 // Wait for debouncer.
207 clock.tick(element.presubmitDebounceTimeOut + 1);
208
209 sinon.assert.calledWith(prpcClient.call, 'monorail.Issues',
210 'PresubmitIssue',
211 {issueDelta: {summary: 'Summary'}, issueRef: {}});
212 });
213
214 it('presubmits issue on comment change', async () => {
215 element.issueRef = {};
216
217 await element.updateComplete;
218 const editMetadata = element.querySelector('mr-edit-metadata');
219 editMetadata.dispatchEvent(new CustomEvent('change', {
220 detail: {
221 delta: {},
222 commentContent: 'test',
223 },
224 }));
225
226 // Wait for debouncer.
227 clock.tick(element.presubmitDebounceTimeOut + 1);
228
229 sinon.assert.calledWith(prpcClient.call, 'monorail.Issues',
230 'PresubmitIssue',
231 {issueDelta: {}, issueRef: {}});
232 });
233
234
235 it('does not presubmit issue when no changes', () => {
236 element._presubmitIssue({});
237
238 sinon.assert.notCalled(prpcClient.call);
239 });
240
241 it('editing form runs _presubmitIssue debounced', async () => {
242 sinon.stub(element, '_presubmitIssue');
243
244 await element.updateComplete;
245
246 // User makes some changes.
247 const comment = element.querySelector('#commentText');
248 comment.value = 'Value';
249 comment.dispatchEvent(new Event('keyup'));
250
251 clock.tick(5);
252
253 // User makes more changes before debouncer timeout is done.
254 comment.value = 'more changes';
255 comment.dispatchEvent(new Event('keyup'));
256
257 clock.tick(10);
258
259 sinon.assert.notCalled(element._presubmitIssue);
260
261 // Wait for debouncer.
262 clock.tick(element.presubmitDebounceTimeOut + 1);
263
264 sinon.assert.calledOnce(element._presubmitIssue);
265 });
266});
267
268describe('allowRemovedRestrictions', () => {
269 beforeEach(() => {
270 sinon.stub(window, 'confirm');
271 });
272
273 afterEach(() => {
274 window.confirm.restore();
275 });
276
277 it('returns true if no restrictions removed', () => {
278 assert.isTrue(allowRemovedRestrictions([
279 {label: 'not-restricted'},
280 {label: 'fine'},
281 ]));
282 });
283
284 it('returns false if restrictions removed and confirmation denied', () => {
285 window.confirm.returns(false);
286 assert.isFalse(allowRemovedRestrictions([
287 {label: 'not-restricted'},
288 {label: 'restrict-view-people'},
289 ]));
290 });
291
292 it('returns true if restrictions removed and confirmation accepted', () => {
293 window.confirm.returns(true);
294 assert.isTrue(allowRemovedRestrictions([
295 {label: 'not-restricted'},
296 {label: 'restrict-view-people'},
297 ]));
298 });
Adrià Vilanova Martínezde942802022-07-15 14:06:55 +0200299
300 describe('migrated issue', () => {
301 it('does not show notice if issue not migrated', async () => {
302 element.migratedId = '';
303
304 await element.updateComplete;
305
306 assert.isNull(element.querySelector('.migrated-banner'));
307 assert.isNull(element.querySelector('.legacy-edit'));
308 });
309
310 it('shows notice if issue migrated', async () => {
311 element.migratedId = '1234';
Adrià Vilanova Martínez9f9ade52022-10-10 23:20:11 +0200312 element.migratedType = migratedTypes.LAUNCH_TYPE
Adrià Vilanova Martínezde942802022-07-15 14:06:55 +0200313 await element.updateComplete;
314
315 assert.isNotNull(element.querySelector('.migrated-banner'));
316 assert.isNotNull(element.querySelector('.legacy-edit'));
317 });
318
Adrià Vilanova Martínez9f9ade52022-10-10 23:20:11 +0200319 it('shows buganizer link when migrated to buganizer', async () => {
320 element.migratedId = '1234';
321 element.migratedType = migratedTypes.BUGANIZER_TYPE
322 await element.updateComplete;
323
324 const link = element.querySelector('.migrated-banner a');
325 assert.include(link.textContent, 'b/1234');
326 });
327
328 it('shows launch banner when migrated to launch', async () => {
329 element.migratedId = '1234';
330 element.migratedType = migratedTypes.LAUNCH_TYPE
331 await element.updateComplete;
332
333 const link = element.querySelector('.migrated-banner');
334 assert.include(link.textContent, 'This issue has been migrated to Launch, see link in final comment below');
335 });
336
Adrià Vilanova Martínezde942802022-07-15 14:06:55 +0200337 it('hides edit form if issue migrated', async () => {
338 element.migratedId = '1234';
Adrià Vilanova Martínez9f9ade52022-10-10 23:20:11 +0200339 element.migratedType = migratedTypes.LAUNCH_TYPE
Adrià Vilanova Martínezde942802022-07-15 14:06:55 +0200340 await element.updateComplete;
341
342 const editForm = element.querySelector('mr-edit-metadata');
343 assert.isTrue(editForm.hasAttribute('hidden'));
344 });
345
346 it('unhides edit form on button click', async () => {
347 element.migratedId = '1234';
Adrià Vilanova Martínez9f9ade52022-10-10 23:20:11 +0200348 element.migratedType = migratedTypes.LAUNCH_TYPE
Adrià Vilanova Martínezde942802022-07-15 14:06:55 +0200349 await element.updateComplete;
350
351 const button = element.querySelector('.legacy-edit');
352 button.click();
353
354 await element.updateComplete;
355
356 const editForm = element.querySelector('mr-edit-metadata');
357 assert.isFalse(editForm.hasAttribute('hidden'));
358 });
359 });
Copybara854996b2021-09-07 19:36:02 +0000360});