blob: a3216ca9f2ede04df790f24f090e38e824c4dde8 [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 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';
10
11let element;
12let clock;
13
14describe('mr-edit-issue', () => {
15 beforeEach(() => {
16 element = document.createElement('mr-edit-issue');
17 document.body.appendChild(element);
18 sinon.stub(prpcClient, 'call');
19
20 element.clientLogger = clientLoggerFake();
21 clock = sinon.useFakeTimers();
22 });
23
24 afterEach(() => {
25 document.body.removeChild(element);
26 prpcClient.call.restore();
27
28 clock.restore();
29 });
30
31 it('initializes', () => {
32 assert.instanceOf(element, MrEditIssue);
33 });
34
35 it('scrolls into view on #makechanges hash', async () => {
36 await element.updateComplete;
37
38 const header = element.querySelector('#makechanges');
39 sinon.stub(header, 'scrollIntoView');
40
41 element.focusId = 'makechanges';
42 await element.updateComplete;
43
44 assert.isTrue(header.scrollIntoView.calledOnce);
45
46 header.scrollIntoView.restore();
47 });
48
49 it('shows snackbar and resets form when editing finishes', async () => {
50 sinon.stub(element, 'reset');
51 sinon.stub(element, '_showCommentAddedSnackbar');
52
53 element.updatingIssue = true;
54 await element.updateComplete;
55
56 sinon.assert.notCalled(element._showCommentAddedSnackbar);
57 sinon.assert.notCalled(element.reset);
58
59 element.updatingIssue = false;
60 await element.updateComplete;
61
62 sinon.assert.calledOnce(element._showCommentAddedSnackbar);
63 sinon.assert.calledOnce(element.reset);
64 });
65
66 it('does not show snackbar or reset form on edit error', async () => {
67 sinon.stub(element, 'reset');
68 sinon.stub(element, '_showCommentAddedSnackbar');
69
70 element.updatingIssue = true;
71 await element.updateComplete;
72
73 element.updateError = 'The save failed';
74 element.updatingIssue = false;
75 await element.updateComplete;
76
77 sinon.assert.notCalled(element._showCommentAddedSnackbar);
78 sinon.assert.notCalled(element.reset);
79 });
80
81 it('shows current status even if not defined for project', async () => {
82 await element.updateComplete;
83
84 const editMetadata = element.querySelector('mr-edit-metadata');
85 assert.deepEqual(editMetadata.statuses, []);
86
87 element.projectConfig = {statusDefs: [
88 {status: 'hello'},
89 {status: 'world'},
90 ]};
91
92 await editMetadata.updateComplete;
93
94 assert.deepEqual(editMetadata.statuses, [
95 {status: 'hello'},
96 {status: 'world'},
97 ]);
98
99 element.issue = {
100 statusRef: {status: 'hello'},
101 };
102
103 await editMetadata.updateComplete;
104
105 assert.deepEqual(editMetadata.statuses, [
106 {status: 'hello'},
107 {status: 'world'},
108 ]);
109
110 element.issue = {
111 statusRef: {status: 'weirdStatus'},
112 };
113
114 await editMetadata.updateComplete;
115
116 assert.deepEqual(editMetadata.statuses, [
117 {status: 'weirdStatus'},
118 {status: 'hello'},
119 {status: 'world'},
120 ]);
121 });
122
123 it('ignores deprecated statuses, unless used on current issue', async () => {
124 await element.updateComplete;
125
126 const editMetadata = element.querySelector('mr-edit-metadata');
127 assert.deepEqual(editMetadata.statuses, []);
128
129 element.projectConfig = {statusDefs: [
130 {status: 'new'},
131 {status: 'accepted', deprecated: false},
132 {status: 'compiling', deprecated: true},
133 ]};
134
135 await editMetadata.updateComplete;
136
137 assert.deepEqual(editMetadata.statuses, [
138 {status: 'new'},
139 {status: 'accepted', deprecated: false},
140 ]);
141
142
143 element.issue = {
144 statusRef: {status: 'compiling'},
145 };
146
147 await editMetadata.updateComplete;
148
149 assert.deepEqual(editMetadata.statuses, [
150 {status: 'compiling'},
151 {status: 'new'},
152 {status: 'accepted', deprecated: false},
153 ]);
154 });
155
156 it('filter out empty or deleted user owners', () => {
157 assert.equal(
158 element._ownerDisplayName({displayName: 'a_deleted_user'}),
159 '');
160 assert.equal(
161 element._ownerDisplayName({
162 displayName: 'test@example.com',
163 userId: '1234',
164 }),
165 'test@example.com');
166 });
167
168 it('logs issue-update metrics', async () => {
169 await element.updateComplete;
170
171 const editMetadata = element.querySelector('mr-edit-metadata');
172
173 sinon.stub(editMetadata, 'delta').get(() => ({summary: 'test'}));
174
175 await element.save();
176
177 sinon.assert.calledOnce(element.clientLogger.logStart);
178 sinon.assert.calledWith(element.clientLogger.logStart,
179 'issue-update', 'computer-time');
180
181 // Simulate a response updating the UI.
182 element.issue = {summary: 'test'};
183
184 await element.updateComplete;
185 await element.updateComplete;
186
187 sinon.assert.calledOnce(element.clientLogger.logEnd);
188 sinon.assert.calledWith(element.clientLogger.logEnd,
189 'issue-update', 'computer-time', 120 * 1000);
190 });
191
192 it('presubmits issue on metadata change', async () => {
193 element.issueRef = {};
194
195 await element.updateComplete;
196 const editMetadata = element.querySelector('mr-edit-metadata');
197 editMetadata.dispatchEvent(new CustomEvent('change', {
198 detail: {
199 delta: {
200 summary: 'Summary',
201 },
202 },
203 }));
204
205 // Wait for debouncer.
206 clock.tick(element.presubmitDebounceTimeOut + 1);
207
208 sinon.assert.calledWith(prpcClient.call, 'monorail.Issues',
209 'PresubmitIssue',
210 {issueDelta: {summary: 'Summary'}, issueRef: {}});
211 });
212
213 it('presubmits issue on comment change', async () => {
214 element.issueRef = {};
215
216 await element.updateComplete;
217 const editMetadata = element.querySelector('mr-edit-metadata');
218 editMetadata.dispatchEvent(new CustomEvent('change', {
219 detail: {
220 delta: {},
221 commentContent: 'test',
222 },
223 }));
224
225 // Wait for debouncer.
226 clock.tick(element.presubmitDebounceTimeOut + 1);
227
228 sinon.assert.calledWith(prpcClient.call, 'monorail.Issues',
229 'PresubmitIssue',
230 {issueDelta: {}, issueRef: {}});
231 });
232
233
234 it('does not presubmit issue when no changes', () => {
235 element._presubmitIssue({});
236
237 sinon.assert.notCalled(prpcClient.call);
238 });
239
240 it('editing form runs _presubmitIssue debounced', async () => {
241 sinon.stub(element, '_presubmitIssue');
242
243 await element.updateComplete;
244
245 // User makes some changes.
246 const comment = element.querySelector('#commentText');
247 comment.value = 'Value';
248 comment.dispatchEvent(new Event('keyup'));
249
250 clock.tick(5);
251
252 // User makes more changes before debouncer timeout is done.
253 comment.value = 'more changes';
254 comment.dispatchEvent(new Event('keyup'));
255
256 clock.tick(10);
257
258 sinon.assert.notCalled(element._presubmitIssue);
259
260 // Wait for debouncer.
261 clock.tick(element.presubmitDebounceTimeOut + 1);
262
263 sinon.assert.calledOnce(element._presubmitIssue);
264 });
265});
266
267describe('allowRemovedRestrictions', () => {
268 beforeEach(() => {
269 sinon.stub(window, 'confirm');
270 });
271
272 afterEach(() => {
273 window.confirm.restore();
274 });
275
276 it('returns true if no restrictions removed', () => {
277 assert.isTrue(allowRemovedRestrictions([
278 {label: 'not-restricted'},
279 {label: 'fine'},
280 ]));
281 });
282
283 it('returns false if restrictions removed and confirmation denied', () => {
284 window.confirm.returns(false);
285 assert.isFalse(allowRemovedRestrictions([
286 {label: 'not-restricted'},
287 {label: 'restrict-view-people'},
288 ]));
289 });
290
291 it('returns true if restrictions removed and confirmation accepted', () => {
292 window.confirm.returns(true);
293 assert.isTrue(allowRemovedRestrictions([
294 {label: 'not-restricted'},
295 {label: 'restrict-view-people'},
296 ]));
297 });
298});