blob: 4aa42a25868b31be8429a427812dc9c42564fb11 [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';
7
8import * as hotlists from './hotlists.js';
9import * as example from 'shared/test/constants-hotlists.js';
10import * as exampleIssues from 'shared/test/constants-issueV0.js';
11import * as exampleUsers from 'shared/test/constants-users.js';
12
13import {prpcClient} from 'prpc-client-instance.js';
14
15let dispatch;
16
17describe('hotlist reducers', () => {
18 it('root reducer initial state', () => {
19 const actual = hotlists.reducer(undefined, {type: null});
20 const expected = {
21 name: null,
22 byName: {},
23 hotlistItems: {},
24 requests: {
25 deleteHotlist: {error: null, requesting: false},
26 fetch: {error: null, requesting: false},
27 fetchItems: {error: null, requesting: false},
28 removeEditors: {error: null, requesting: false},
29 removeItems: {error: null, requesting: false},
30 rerankItems: {error: null, requesting: false},
31 update: {error: null, requesting: false},
32 },
33 };
34 assert.deepEqual(actual, expected);
35 });
36
37 it('name updates on SELECT', () => {
38 const action = {type: hotlists.SELECT, name: example.NAME};
39 const actual = hotlists.nameReducer(null, action);
40 assert.deepEqual(actual, example.NAME);
41 });
42
43 it('byName updates on RECEIVE_HOTLIST', () => {
44 const action = {type: hotlists.RECEIVE_HOTLIST, hotlist: example.HOTLIST};
45 const actual = hotlists.byNameReducer({}, action);
46 assert.deepEqual(actual, example.BY_NAME);
47 });
48
49 it('byName fills in missing fields on RECEIVE_HOTLIST', () => {
50 const action = {
51 type: hotlists.RECEIVE_HOTLIST,
52 hotlist: {name: example.NAME},
53 };
54 const actual = hotlists.byNameReducer({}, action);
55
56 const hotlist = {name: example.NAME, defaultColumns: [], editors: []};
57 assert.deepEqual(actual, {[example.NAME]: hotlist});
58 });
59
60 it('hotlistItems updates on FETCH_ITEMS_SUCCESS', () => {
61 const action = {
62 type: hotlists.FETCH_ITEMS_SUCCESS,
63 name: example.NAME,
64 items: [example.HOTLIST_ITEM],
65 };
66 const actual = hotlists.hotlistItemsReducer({}, action);
67 assert.deepEqual(actual, example.HOTLIST_ITEMS);
68 });
69});
70
71describe('hotlist selectors', () => {
72 it('name', () => {
73 const state = {hotlists: {name: example.NAME}};
74 assert.deepEqual(hotlists.name(state), example.NAME);
75 });
76
77 it('byName', () => {
78 const state = {hotlists: {byName: example.BY_NAME}};
79 assert.deepEqual(hotlists.byName(state), example.BY_NAME);
80 });
81
82 it('hotlistItems', () => {
83 const state = {hotlists: {hotlistItems: example.HOTLIST_ITEMS}};
84 assert.deepEqual(hotlists.hotlistItems(state), example.HOTLIST_ITEMS);
85 });
86
87 describe('viewedHotlist', () => {
88 it('normal case', () => {
89 const state = {hotlists: {name: example.NAME, byName: example.BY_NAME}};
90 assert.deepEqual(hotlists.viewedHotlist(state), example.HOTLIST);
91 });
92
93 it('no name', () => {
94 const state = {hotlists: {name: null, byName: example.BY_NAME}};
95 assert.deepEqual(hotlists.viewedHotlist(state), null);
96 });
97
98 it('hotlist not found', () => {
99 const state = {hotlists: {name: example.NAME, byName: {}}};
100 assert.deepEqual(hotlists.viewedHotlist(state), null);
101 });
102 });
103
104 describe('viewedHotlistOwner', () => {
105 it('normal case', () => {
106 const state = {
107 hotlists: {name: example.NAME, byName: example.BY_NAME},
108 users: {byName: exampleUsers.BY_NAME},
109 };
110 assert.deepEqual(hotlists.viewedHotlistOwner(state), exampleUsers.USER);
111 });
112
113 it('no hotlist', () => {
114 const state = {hotlists: {}, users: {}};
115 assert.deepEqual(hotlists.viewedHotlistOwner(state), null);
116 });
117 });
118
119 describe('viewedHotlistEditors', () => {
120 it('normal case', () => {
121 const state = {
122 hotlists: {
123 name: example.NAME,
124 byName: {[example.NAME]: {
125 ...example.HOTLIST,
126 editors: [exampleUsers.NAME, exampleUsers.NAME_2],
127 }},
128 },
129 users: {byName: exampleUsers.BY_NAME},
130 };
131
132 const editors = [exampleUsers.USER, exampleUsers.USER_2];
133 assert.deepEqual(hotlists.viewedHotlistEditors(state), editors);
134 });
135
136 it('no user data', () => {
137 const editors = [exampleUsers.NAME, exampleUsers.NAME_2];
138 const state = {
139 hotlists: {
140 name: example.NAME,
141 byName: {[example.NAME]: {...example.HOTLIST, editors}},
142 },
143 users: {byName: {}},
144 };
145 assert.deepEqual(hotlists.viewedHotlistEditors(state), [null, null]);
146 });
147
148 it('no hotlist', () => {
149 const state = {hotlists: {}, users: {}};
150 assert.deepEqual(hotlists.viewedHotlistEditors(state), null);
151 });
152 });
153
154 describe('viewedHotlistItems', () => {
155 it('normal case', () => {
156 const state = {hotlists: {
157 name: example.NAME,
158 hotlistItems: example.HOTLIST_ITEMS,
159 }};
160 const actual = hotlists.viewedHotlistItems(state);
161 assert.deepEqual(actual, [example.HOTLIST_ITEM]);
162 });
163
164 it('no name', () => {
165 const state = {hotlists: {
166 name: null,
167 hotlistItems: example.HOTLIST_ITEMS,
168 }};
169 assert.deepEqual(hotlists.viewedHotlistItems(state), []);
170 });
171
172 it('hotlist not found', () => {
173 const state = {hotlists: {name: example.NAME, hotlistItems: {}}};
174 assert.deepEqual(hotlists.viewedHotlistItems(state), []);
175 });
176 });
177
178 describe('viewedHotlistIssues', () => {
179 it('normal case', () => {
180 const state = {
181 hotlists: {
182 name: example.NAME,
183 hotlistItems: example.HOTLIST_ITEMS,
184 },
185 issue: {
186 issuesByRefString: {
187 [exampleIssues.ISSUE_REF_STRING]: exampleIssues.ISSUE,
188 },
189 },
190 users: {byName: {[exampleUsers.NAME]: exampleUsers.USER}},
191 };
192 const actual = hotlists.viewedHotlistIssues(state);
193 assert.deepEqual(actual, [example.HOTLIST_ISSUE]);
194 });
195
196 it('no issue', () => {
197 const state = {
198 hotlists: {
199 name: example.NAME,
200 hotlistItems: example.HOTLIST_ITEMS,
201 },
202 issue: {
203 issuesByRefString: {
204 [exampleIssues.ISSUE_OTHER_PROJECT_REF_STRING]: exampleIssues.ISSUE,
205 },
206 },
207 users: {byName: {}},
208 };
209 assert.deepEqual(hotlists.viewedHotlistIssues(state), []);
210 });
211 });
212
213 describe('viewedHotlistColumns', () => {
214 it('sitewide currentColumns overrides hotlist defaultColumns', () => {
215 const state = {
216 sitewide: {queryParams: {colspec: 'Summary+ColumnName'}},
217 hotlists: {},
218 };
219 const actual = hotlists.viewedHotlistColumns(state);
220 assert.deepEqual(actual, ['Summary', 'ColumnName']);
221 });
222
223 it('uses DEFAULT_COLUMNS when no hotlist', () => {
224 const actual = hotlists.viewedHotlistColumns({hotlists: {}});
225 assert.deepEqual(actual, hotlists.DEFAULT_COLUMNS);
226 });
227
228 it('uses DEFAULT_COLUMNS when hotlist has empty defaultColumns', () => {
229 const state = {hotlists: {
230 name: example.HOTLIST.name,
231 byName: {
232 [example.HOTLIST.name]: {...example.HOTLIST, defaultColumns: []},
233 },
234 }};
235 const actual = hotlists.viewedHotlistColumns(state);
236 assert.deepEqual(actual, hotlists.DEFAULT_COLUMNS);
237 });
238
239 it('uses hotlist defaultColumns', () => {
240 const state = {hotlists: {
241 name: example.HOTLIST.name,
242 byName: {[example.HOTLIST.name]: {
243 ...example.HOTLIST,
244 defaultColumns: [{column: 'ID'}, {column: 'ColumnName'}],
245 }},
246 }};
247 const actual = hotlists.viewedHotlistColumns(state);
248 assert.deepEqual(actual, ['ID', 'ColumnName']);
249 });
250 });
251
252 describe('viewedHotlistPermissions', () => {
253 it('normal case', () => {
254 const permissions = [hotlists.ADMINISTER, hotlists.EDIT];
255 const state = {
256 hotlists: {name: example.NAME, byName: example.BY_NAME},
257 permissions: {byName: {[example.NAME]: {permissions}}},
258 };
259 assert.deepEqual(hotlists.viewedHotlistPermissions(state), permissions);
260 });
261
262 it('no issue', () => {
263 const state = {hotlists: {}, permissions: {}};
264 assert.deepEqual(hotlists.viewedHotlistPermissions(state), []);
265 });
266 });
267});
268
269describe('hotlist action creators', () => {
270 beforeEach(() => {
271 sinon.stub(prpcClient, 'call');
272 dispatch = sinon.stub();
273 });
274
275 afterEach(() => {
276 prpcClient.call.restore();
277 });
278
279 it('select', () => {
280 const actual = hotlists.select(example.NAME);
281 const expected = {type: hotlists.SELECT, name: example.NAME};
282 assert.deepEqual(actual, expected);
283 });
284
285 describe('deleteHotlist', () => {
286 it('success', async () => {
287 prpcClient.call.returns(Promise.resolve({}));
288
289 await hotlists.deleteHotlist(example.NAME)(dispatch);
290
291 sinon.assert.calledWith(dispatch, {type: hotlists.DELETE_START});
292
293 const args = {name: example.NAME};
294 sinon.assert.calledWith(
295 prpcClient.call, 'monorail.v3.Hotlists', 'DeleteHotlist', args);
296
297 sinon.assert.calledWith(dispatch, {type: hotlists.DELETE_SUCCESS});
298 });
299
300 it('failure', async () => {
301 prpcClient.call.throws();
302
303 await hotlists.deleteHotlist(example.NAME)(dispatch);
304
305 const action = {
306 type: hotlists.DELETE_FAILURE,
307 error: sinon.match.any,
308 };
309 sinon.assert.calledWith(dispatch, action);
310 });
311 });
312
313 describe('fetch', () => {
314 it('success', async () => {
315 const hotlist = example.HOTLIST;
316 prpcClient.call.returns(Promise.resolve(hotlist));
317
318 await hotlists.fetch(example.NAME)(dispatch);
319
320 const args = {name: example.NAME};
321 sinon.assert.calledWith(
322 prpcClient.call, 'monorail.v3.Hotlists', 'GetHotlist', args);
323
324 sinon.assert.calledWith(dispatch, {type: hotlists.FETCH_START});
325 sinon.assert.calledWith(dispatch, {type: hotlists.FETCH_SUCCESS});
326 sinon.assert.calledWith(
327 dispatch, {type: hotlists.RECEIVE_HOTLIST, hotlist});
328 });
329
330 it('failure', async () => {
331 prpcClient.call.throws();
332
333 await hotlists.fetch(example.NAME)(dispatch);
334
335 const action = {type: hotlists.FETCH_FAILURE, error: sinon.match.any};
336 sinon.assert.calledWith(dispatch, action);
337 });
338 });
339
340 describe('fetchItems', () => {
341 it('success', async () => {
342 const response = {items: [example.HOTLIST_ITEM]};
343 prpcClient.call.returns(Promise.resolve(response));
344
345 const returnValue = await hotlists.fetchItems(example.NAME)(dispatch);
346 assert.deepEqual(returnValue, [{...example.HOTLIST_ITEM, rank: 0}]);
347
348 const args = {parent: example.NAME, orderBy: 'rank'};
349 sinon.assert.calledWith(
350 prpcClient.call, 'monorail.v3.Hotlists', 'ListHotlistItems', args);
351
352 sinon.assert.calledWith(dispatch, {type: hotlists.FETCH_ITEMS_START});
353 const action = {
354 type: hotlists.FETCH_ITEMS_SUCCESS,
355 name: example.NAME,
356 items: [{...example.HOTLIST_ITEM, rank: 0}],
357 };
358 sinon.assert.calledWith(dispatch, action);
359 });
360
361 it('failure', async () => {
362 prpcClient.call.throws();
363
364 await hotlists.fetchItems(example.NAME)(dispatch);
365
366 const action = {
367 type: hotlists.FETCH_ITEMS_FAILURE,
368 error: sinon.match.any,
369 };
370 sinon.assert.calledWith(dispatch, action);
371 });
372
373 it('success with empty hotlist', async () => {
374 const response = {items: []};
375 prpcClient.call.returns(Promise.resolve(response));
376
377 const returnValue = await hotlists.fetchItems(example.NAME)(dispatch);
378 assert.deepEqual(returnValue, []);
379
380 sinon.assert.calledWith(dispatch, {type: hotlists.FETCH_ITEMS_START});
381
382 const args = {parent: example.NAME, orderBy: 'rank'};
383 sinon.assert.calledWith(
384 prpcClient.call, 'monorail.v3.Hotlists', 'ListHotlistItems', args);
385
386 const action = {
387 type: hotlists.FETCH_ITEMS_SUCCESS,
388 name: example.NAME,
389 items: [],
390 };
391 sinon.assert.calledWith(dispatch, action);
392 });
393 });
394
395 describe('removeEditors', () => {
396 it('success', async () => {
397 prpcClient.call.returns(Promise.resolve({}));
398
399 const editors = [exampleUsers.NAME];
400 await hotlists.removeEditors(example.NAME, editors)(dispatch);
401
402 const args = {name: example.NAME, editors};
403 sinon.assert.calledWith(
404 prpcClient.call, 'monorail.v3.Hotlists',
405 'RemoveHotlistEditors', args);
406
407 sinon.assert.calledWith(dispatch, {type: hotlists.REMOVE_EDITORS_START});
408 const action = {type: hotlists.REMOVE_EDITORS_SUCCESS};
409 sinon.assert.calledWith(dispatch, action);
410 });
411
412 it('failure', async () => {
413 prpcClient.call.throws();
414
415 await hotlists.removeEditors(example.NAME, [])(dispatch);
416
417 const action = {
418 type: hotlists.REMOVE_EDITORS_FAILURE,
419 error: sinon.match.any,
420 };
421 sinon.assert.calledWith(dispatch, action);
422 });
423 });
424
425 describe('removeItems', () => {
426 it('success', async () => {
427 prpcClient.call.returns(Promise.resolve({}));
428
429 const issues = [exampleIssues.NAME];
430 await hotlists.removeItems(example.NAME, issues)(dispatch);
431
432 const args = {parent: example.NAME, issues};
433 sinon.assert.calledWith(
434 prpcClient.call, 'monorail.v3.Hotlists',
435 'RemoveHotlistItems', args);
436
437 sinon.assert.calledWith(dispatch, {type: hotlists.REMOVE_ITEMS_START});
438 sinon.assert.calledWith(dispatch, {type: hotlists.REMOVE_ITEMS_SUCCESS});
439 });
440
441 it('failure', async () => {
442 prpcClient.call.throws();
443
444 await hotlists.removeItems(example.NAME, [])(dispatch);
445
446 const action = {
447 type: hotlists.REMOVE_ITEMS_FAILURE,
448 error: sinon.match.any,
449 };
450 sinon.assert.calledWith(dispatch, action);
451 });
452 });
453
454 describe('rerankItems', () => {
455 it('success', async () => {
456 prpcClient.call.returns(Promise.resolve({}));
457
458 const items = [example.HOTLIST_ITEM_NAME];
459 await hotlists.rerankItems(example.NAME, items, 0)(dispatch);
460
461 const args = {
462 name: example.NAME,
463 hotlistItems: items,
464 targetPosition: 0,
465 };
466 sinon.assert.calledWith(
467 prpcClient.call, 'monorail.v3.Hotlists',
468 'RerankHotlistItems', args);
469
470 sinon.assert.calledWith(dispatch, {type: hotlists.RERANK_ITEMS_START});
471 sinon.assert.calledWith(dispatch, {type: hotlists.RERANK_ITEMS_SUCCESS});
472 });
473
474 it('failure', async () => {
475 prpcClient.call.throws();
476
477 await hotlists.rerankItems(example.NAME, [], 0)(dispatch);
478
479 const action = {
480 type: hotlists.RERANK_ITEMS_FAILURE,
481 error: sinon.match.any,
482 };
483 sinon.assert.calledWith(dispatch, action);
484 });
485 });
486
487 describe('update', () => {
488 it('success', async () => {
489 const hotlistOnlyWithUpdates = {
490 displayName: example.HOTLIST.displayName + 'foo',
491 summary: example.HOTLIST.summary + 'abc',
492 };
493 const hotlist = {...example.HOTLIST, ...hotlistOnlyWithUpdates};
494 prpcClient.call.returns(Promise.resolve(hotlist));
495
496 await hotlists.update(
497 example.HOTLIST.name, hotlistOnlyWithUpdates)(dispatch);
498
499 const hotlistArg = {
500 ...hotlistOnlyWithUpdates,
501 name: example.HOTLIST.name,
502 };
503 const fieldMask = 'displayName,summary';
504 const args = {hotlist: hotlistArg, updateMask: fieldMask};
505 sinon.assert.calledWith(
506 prpcClient.call, 'monorail.v3.Hotlists', 'UpdateHotlist', args);
507
508 sinon.assert.calledWith(dispatch, {type: hotlists.UPDATE_START});
509 sinon.assert.calledWith(dispatch, {type: hotlists.UPDATE_SUCCESS});
510 sinon.assert.calledWith(
511 dispatch, {type: hotlists.RECEIVE_HOTLIST, hotlist});
512 });
513
514 it('failure', async () => {
515 prpcClient.call.throws();
516 const hotlistOnlyWithUpdates = {
517 displayName: example.HOTLIST.displayName + 'foo',
518 summary: example.HOTLIST.summary + 'abc',
519 };
520 try {
521 // TODO(crbug.com/monorail/7883): Use Chai Promises plugin
522 // to assert promise rejected.
523 await hotlists.update(
524 example.HOTLIST.name, hotlistOnlyWithUpdates)(dispatch);
525 } catch (e) {}
526
527 const action = {
528 type: hotlists.UPDATE_FAILURE,
529 error: sinon.match.any,
530 };
531 sinon.assert.calledWith(dispatch, action);
532 });
533 });
534});
535
536describe('helpers', () => {
537 beforeEach(() => {
538 sinon.stub(prpcClient, 'call');
539 dispatch = sinon.stub();
540 });
541
542 afterEach(() => {
543 prpcClient.call.restore();
544 });
545
546 describe('getHotlistName', () => {
547 it('success', async () => {
548 const response = {hotlistId: '1234'};
549 prpcClient.call.returns(Promise.resolve(response));
550
551 const name = await hotlists.getHotlistName('foo@bar.com', 'hotlist');
552 assert.deepEqual(name, 'hotlists/1234');
553
554 const args = {hotlistRef: {
555 owner: {displayName: 'foo@bar.com'},
556 name: 'hotlist',
557 }};
558 sinon.assert.calledWith(
559 prpcClient.call, 'monorail.Features', 'GetHotlistID', args);
560 });
561
562 it('failure', async () => {
563 prpcClient.call.throws();
564
565 assert.isNull(await hotlists.getHotlistName('foo@bar.com', 'hotlist'));
566 });
567 });
568});