blob: f51a5f6c7d0e3117d1cb38654b781cbaa271ba67 [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 {assert} from 'chai';
6import sinon from 'sinon';
7
8import {store, resetState} from 'reducers/base.js';
9import {hotlists} from 'reducers/hotlists.js';
10import * as projectV0 from 'reducers/projectV0.js';
11import * as sitewide from 'reducers/sitewide.js';
12
13import * as example from 'shared/test/constants-hotlists.js';
14import * as exampleIssues from 'shared/test/constants-issueV0.js';
15import * as exampleUsers from 'shared/test/constants-users.js';
16import {PERMISSION_HOTLIST_EDIT} from 'shared/test/constants-permissions.js';
17
18import {MrHotlistIssuesPage} from './mr-hotlist-issues-page.js';
19
20/** @type {MrHotlistIssuesPage} */
21let element;
22
23describe('mr-hotlist-issues-page (unconnected)', () => {
24 beforeEach(() => {
25 // @ts-ignore
26 element = document.createElement('mr-hotlist-issues-page-base');
27 element._extractFieldValuesFromIssue =
28 projectV0.extractFieldValuesFromIssue({});
29 document.body.appendChild(element);
30 });
31
32 afterEach(() => {
33 document.body.removeChild(element);
34 });
35
36 it('shows hotlist fetch error', async () => {
37 element._fetchError = new Error('This is an important error');
38 element._fetchError.description = 'This is an important error';
39 await element.updateComplete;
40 assert.include(element.shadowRoot.innerHTML, 'important error');
41 });
42
43 it('shows loading message with null hotlist', async () => {
44 await element.updateComplete;
45 assert.include(element.shadowRoot.innerHTML, 'Loading');
46 });
47
48 it('renders hotlist items with one project', async () => {
49 element._hotlist = example.HOTLIST;
50 element._items = [example.HOTLIST_ISSUE];
51 await element.updateComplete;
52
53 const issueList = element.shadowRoot.querySelector('mr-issue-list');
54 assert.deepEqual(issueList.projectName, 'project-name');
55 });
56
57 it('renders hotlist items with multiple projects', async () => {
58 element._hotlist = example.HOTLIST;
59 element._items = [
60 example.HOTLIST_ISSUE,
61 example.HOTLIST_ISSUE_OTHER_PROJECT,
62 ];
63 await element.updateComplete;
64
65 const issueList = element.shadowRoot.querySelector('mr-issue-list');
66 assert.isNull(issueList.projectName);
67 });
68
69 it('needs permissions to rerank', async () => {
70 element._hotlist = example.HOTLIST;
71 await element.updateComplete;
72
73 const issueList = element.shadowRoot.querySelector('mr-issue-list');
74 assert.isNull(issueList.rerank);
75
76 element._permissions = [hotlists.EDIT];
77 await element.updateComplete;
78
79 assert.isNotNull(issueList.rerank);
80 });
81
82 it('memoizes issues', async () => {
83 element._hotlist = example.HOTLIST;
84 element._items = [example.HOTLIST_ISSUE];
85 await element.updateComplete;
86
87 const issueList = element.shadowRoot.querySelector('mr-issue-list');
88 const issues = issueList.issues;
89
90 // Trigger a render without updating the issue list.
91 element._hotlist = example.HOTLIST;
92 await element.updateComplete;
93
94 assert.strictEqual(issues, issueList.issues);
95
96 // Modify the issue list.
97 element._items = [example.HOTLIST_ISSUE];
98 await element.updateComplete;
99
100 assert.notStrictEqual(issues, issueList.issues);
101 });
102
103 it('computes strings for HotlistIssue fields', async () => {
104 const clock = sinon.useFakeTimers(24 * 60 * 60 * 1000);
105
106 try {
107 element._hotlist = example.HOTLIST;
108 element._items = [{
109 ...example.HOTLIST_ISSUE,
110 summary: 'Summary',
111 rank: 52,
112 adder: exampleUsers.USER,
113 createTime: new Date(0).toISOString(),
114 }];
115 element._columns = ['Summary', 'Rank', 'Added', 'Adder'];
116 await element.updateComplete;
117
118 const issueList = element.shadowRoot.querySelector('mr-issue-list');
119 assert.include(issueList.shadowRoot.innerHTML, 'Summary');
120 assert.include(issueList.shadowRoot.innerHTML, '53');
121 assert.include(issueList.shadowRoot.innerHTML, 'a day ago');
122 assert.include(issueList.shadowRoot.innerHTML, exampleUsers.DISPLAY_NAME);
123 } finally {
124 clock.restore();
125 }
126 });
127
128 it('filters and shows closed issues', async () => {
129 element._hotlist = example.HOTLIST;
130 element._items = [example.HOTLIST_ISSUE_CLOSED];
131 await element.updateComplete;
132
133 const issueList = element.shadowRoot.querySelector('mr-issue-list');
134 assert.equal(issueList.issues.length, 0);
135
136 element.shadowRoot.querySelector('chops-filter-chips').select('Closed');
137 await element.updateComplete;
138
139 assert.isTrue(element._filter.Closed);
140 assert.equal(issueList.issues.length, 1);
141 });
142
143 it('updates button bar on list selection', async () => {
144 element._permissions = PERMISSION_HOTLIST_EDIT;
145 element._hotlist = example.HOTLIST;
146 element._items = [example.HOTLIST_ISSUE];
147 await element.updateComplete;
148
149 const buttonBar = element.shadowRoot.querySelector('mr-button-bar');
150 assert.include(buttonBar.shadowRoot.innerHTML, 'Change columns');
151 assert.notInclude(buttonBar.shadowRoot.innerHTML, 'Remove');
152 assert.notInclude(buttonBar.shadowRoot.innerHTML, 'Update');
153 assert.notInclude(buttonBar.shadowRoot.innerHTML, 'Move to...');
154 assert.deepEqual(element._selected, []);
155
156 const issueList = element.shadowRoot.querySelector('mr-issue-list');
157 issueList.shadowRoot.querySelector('input').click();
158 await element.updateComplete;
159
160 assert.notInclude(buttonBar.shadowRoot.innerHTML, 'Change columns');
161 assert.include(buttonBar.shadowRoot.innerHTML, 'Remove');
162 assert.include(buttonBar.shadowRoot.innerHTML, 'Update');
163 assert.include(buttonBar.shadowRoot.innerHTML, 'Move to...');
164 assert.deepEqual(element._selected, [exampleIssues.NAME]);
165 });
166
167 it('hides issues checkboxes if the user cannot edit', async () => {
168 element._permissions = [];
169 element._hotlist = example.HOTLIST;
170 element._items = [example.HOTLIST_ISSUE];
171 await element.updateComplete;
172
173 const issueList = element.shadowRoot.querySelector('mr-issue-list');
174 assert.notInclude(issueList.shadowRoot.innerHTML, 'input');
175 });
176
177 it('opens "Change columns" dialog', async () => {
178 element._hotlist = example.HOTLIST;
179 await element.updateComplete;
180
181 const dialog = element.shadowRoot.querySelector('mr-change-columns');
182 sinon.stub(dialog, 'open');
183 try {
184 element._openColumnsDialog();
185
186 sinon.assert.calledOnce(dialog.open);
187 } finally {
188 dialog.open.restore();
189 }
190 });
191
192 it('opens "Update" dialog', async () => {
193 element._hotlist = example.HOTLIST;
194 await element.updateComplete;
195
196 const dialog = element.shadowRoot.querySelector(
197 'mr-update-issue-hotlists-dialog');
198 sinon.stub(dialog, 'open');
199 try {
200 element._openUpdateIssuesHotlistsDialog();
201
202 sinon.assert.calledOnce(dialog.open);
203 } finally {
204 dialog.open.restore();
205 }
206 });
207
208 it('handles successful save from its update dialog', async () => {
209 sinon.stub(element, '_handleHotlistSaveSuccess');
210 element._hotlist = example.HOTLIST;
211 await element.updateComplete;
212
213 try {
214 const dialog =
215 element.shadowRoot.querySelector('mr-update-issue-hotlists-dialog');
216 dialog.dispatchEvent(new Event('saveSuccess'));
217 sinon.assert.calledOnce(element._handleHotlistSaveSuccess);
218 } finally {
219 element._handleHotlistSaveSuccess.restore();
220 }
221 });
222
223 it('opens "Move to..." dialog', async () => {
224 element._hotlist = example.HOTLIST;
225 await element.updateComplete;
226
227 const dialog = element.shadowRoot.querySelector(
228 'mr-move-issue-hotlists-dialog');
229 sinon.stub(dialog, 'open');
230 try {
231 element._openMoveToHotlistDialog();
232
233 sinon.assert.calledOnce(dialog.open);
234 } finally {
235 dialog.open.restore();
236 }
237 });
238
239 it('handles successful save from its move dialog', async () => {
240 sinon.stub(element, '_handleHotlistSaveSuccess');
241 element._hotlist = example.HOTLIST;
242 await element.updateComplete;
243
244 try {
245 const dialog =
246 element.shadowRoot.querySelector('mr-move-issue-hotlists-dialog');
247 dialog.dispatchEvent(new Event('saveSuccess'));
248 sinon.assert.calledOnce(element._handleHotlistSaveSuccess);
249 } finally {
250 element._handleHotlistSaveSuccess.restore();
251 }
252 });
253});
254
255describe('mr-hotlist-issues-page (connected)', () => {
256 beforeEach(() => {
257 store.dispatch(resetState());
258
259 // @ts-ignore
260 element = document.createElement('mr-hotlist-issues-page');
261 element._extractFieldValuesFromIssue =
262 projectV0.extractFieldValuesFromIssue({});
263 document.body.appendChild(element);
264
265 // Stop Redux from overriding values being tested.
266 sinon.stub(element, 'stateChanged');
267 });
268
269 afterEach(() => {
270 element.stateChanged.restore();
271 document.body.removeChild(element);
272 });
273
274 it('initializes', () => {
275 assert.instanceOf(element, MrHotlistIssuesPage);
276 });
277
278 it('updates page title and header', async () => {
279 element._hotlist = {...example.HOTLIST, displayName: 'Hotlist-Name'};
280 await element.updateComplete;
281
282 const state = store.getState();
283 assert.deepEqual(sitewide.pageTitle(state), 'Issues - Hotlist-Name');
284 assert.deepEqual(sitewide.headerTitle(state), 'Hotlist Hotlist-Name');
285 });
286
287 it('removes items', () => {
288 element._hotlist = example.HOTLIST;
289 element._selected = [exampleIssues.NAME];
290
291 const removeItems = sinon.spy(hotlists, 'removeItems');
292 try {
293 element._removeItems();
294 sinon.assert.calledWith(removeItems, example.NAME, [exampleIssues.NAME]);
295 } finally {
296 removeItems.restore();
297 }
298 });
299
300 it('fetches a hotlist when handling a successful save', () => {
301 element._hotlist = example.HOTLIST;
302
303 const fetchItems = sinon.spy(hotlists, 'fetchItems');
304 try {
305 element._handleHotlistSaveSuccess();
306 sinon.assert.calledWith(fetchItems, example.NAME);
307 } finally {
308 fetchItems.restore();
309 }
310 });
311
312 it('reranks', () => {
313 element._hotlist = example.HOTLIST;
314 element._items = [
315 example.HOTLIST_ISSUE,
316 example.HOTLIST_ISSUE_CLOSED,
317 example.HOTLIST_ISSUE_OTHER_PROJECT,
318 ];
319
320 const rerankItems = sinon.spy(hotlists, 'rerankItems');
321 try {
322 element._rerankItems([example.HOTLIST_ITEM_NAME], 1);
323
324 sinon.assert.calledWith(
325 rerankItems, example.NAME, [example.HOTLIST_ITEM_NAME], 2);
326 } finally {
327 rerankItems.restore();
328 }
329 });
330});
331
332it('mr-hotlist-issues-page (stateChanged)', () => {
333 // @ts-ignore
334 element = document.createElement('mr-hotlist-issues-page');
335 document.body.appendChild(element);
336 assert.instanceOf(element, MrHotlistIssuesPage);
337 document.body.removeChild(element);
338});