1 // Copyright 2018 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 
5 package org.chromium.chrome.browser.download.home.list;
6 
7 import static org.mockito.ArgumentMatchers.anyString;
8 import static org.mockito.ArgumentMatchers.eq;
9 import static org.mockito.Mockito.times;
10 import static org.mockito.Mockito.verify;
11 import static org.mockito.Mockito.when;
12 
13 import org.junit.After;
14 import org.junit.Assert;
15 import org.junit.Before;
16 import org.junit.Rule;
17 import org.junit.Test;
18 import org.junit.runner.RunWith;
19 import org.mockito.Mock;
20 import org.mockito.junit.MockitoJUnit;
21 import org.mockito.junit.MockitoRule;
22 import org.robolectric.annotation.Config;
23 
24 import org.chromium.base.test.BaseRobolectricTestRunner;
25 import org.chromium.base.test.util.JniMocker;
26 import org.chromium.chrome.browser.download.home.DownloadManagerUiConfig;
27 import org.chromium.chrome.browser.download.home.JustNowProvider;
28 import org.chromium.chrome.browser.download.home.StableIds;
29 import org.chromium.chrome.browser.download.home.filter.Filters;
30 import org.chromium.chrome.browser.download.home.filter.OfflineItemFilterSource;
31 import org.chromium.chrome.browser.download.home.filter.TypeOfflineItemFilter;
32 import org.chromium.chrome.browser.download.home.list.ListItem.OfflineItemListItem;
33 import org.chromium.chrome.browser.download.home.list.ListItem.SectionHeaderListItem;
34 import org.chromium.chrome.browser.download.home.list.ListItem.SectionHeaderType;
35 import org.chromium.chrome.browser.download.home.list.mutator.DateOrderedListMutator;
36 import org.chromium.chrome.browser.download.home.list.mutator.ListMutationController;
37 import org.chromium.components.browser_ui.util.date.CalendarFactory;
38 import org.chromium.components.offline_items_collection.LegacyHelpers;
39 import org.chromium.components.offline_items_collection.OfflineItem;
40 import org.chromium.components.offline_items_collection.OfflineItemFilter;
41 import org.chromium.components.offline_items_collection.OfflineItemSchedule;
42 import org.chromium.components.offline_items_collection.OfflineItemState;
43 import org.chromium.components.url_formatter.SchemeDisplay;
44 import org.chromium.components.url_formatter.UrlFormatter;
45 import org.chromium.components.url_formatter.UrlFormatterJni;
46 import org.chromium.ui.modelutil.ListObservable.ListObserver;
47 
48 import java.util.Arrays;
49 import java.util.Calendar;
50 import java.util.Collections;
51 import java.util.Date;
52 
53 /** Unit tests for the DateOrderedListMutator class. */
54 @RunWith(BaseRobolectricTestRunner.class)
55 @Config(manifest = Config.NONE)
56 public class DateOrderedListMutatorTest {
57     @Mock
58     private OfflineItemFilterSource mSource;
59 
60     @Mock
61     private ListObserver<Void> mObserver;
62     @Rule
63     public JniMocker mJniMocker = new JniMocker();
64     @Mock
65     private UrlFormatter.Natives mUrlFormatterJniMock;
66 
67     private ListItemModel mModel;
68 
69     @Rule
70     public MockitoRule mMockitoRule = MockitoJUnit.rule();
71 
72     private ListMutationController mMutationController;
73 
74     @Before
setUp()75     public void setUp() {
76         mModel = new ListItemModel();
77         mJniMocker.mock(UrlFormatterJni.TEST_HOOKS, mUrlFormatterJniMock);
78         when(mUrlFormatterJniMock.formatStringUrlForSecurityDisplay(
79                      anyString(), eq(SchemeDisplay.OMIT_HTTP_AND_HTTPS)))
80                 .then(inv -> inv.getArgument(0));
81     }
82 
83     @After
tearDown()84     public void tearDown() {
85         mModel = null;
86     }
87 
88     /**
89      * Action                               List
90      * 1. Set()                             [ ]
91      */
92     @Test
testNoItemsAndSetup()93     public void testNoItemsAndSetup() {
94         when(mSource.getItems()).thenReturn(Collections.emptySet());
95         DateOrderedListMutator list = createMutatorWithoutJustNowProvider();
96         verify(mSource, times(1)).addObserver(list);
97 
98         Assert.assertEquals(0, mModel.size());
99     }
100 
101     /**
102      * Action                               List
103      * 1. Set(item1 @ 1:00 1/1/2018)        [ DATE    @ 0:00 1/1/2018,
104      *                                        item1   @ 1:00 1/1/2018 ]
105      */
106     @Test
testSingleItem()107     public void testSingleItem() {
108         OfflineItem item1 = buildItem("1", buildCalendar(2018, 1, 1, 1), OfflineItemFilter.VIDEO);
109         when(mSource.getItems()).thenReturn(Collections.singletonList(item1));
110         DateOrderedListMutator list = createMutatorWithoutJustNowProvider();
111 
112         Assert.assertEquals(2, mModel.size());
113         assertSectionHeader(mModel.get(0), buildCalendar(2018, 1, 1, 0), false);
114         assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 1, 1), item1);
115     }
116 
117     /**
118      * Action                               List
119      * 1. Set(item1 @ 2:00 1/1/2018,        [ DATE    @ 0:00 1/1/2018,
120      *        item2 @ 1:00 1/1/2018)
121      *                                        item1   @ 2:00 1/1/2018,
122      *                                        item2   @ 1:00 1/1/2018 ]
123      */
124     @Test
testTwoItemsSameDay()125     public void testTwoItemsSameDay() {
126         OfflineItem item1 = buildItem("1", buildCalendar(2018, 1, 1, 2), OfflineItemFilter.VIDEO);
127         OfflineItem item2 = buildItem("2", buildCalendar(2018, 1, 1, 1), OfflineItemFilter.VIDEO);
128         when(mSource.getItems()).thenReturn(Arrays.asList(item1, item2));
129         DateOrderedListMutator list = createMutatorWithoutJustNowProvider();
130 
131         Assert.assertEquals(3, mModel.size());
132         assertSectionHeader(mModel.get(0), buildCalendar(2018, 1, 1, 0), false);
133         assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 1, 2), item1);
134         assertOfflineItem(mModel.get(2), buildCalendar(2018, 1, 1, 1), item2);
135     }
136 
137     /**
138      * Action                                     List
139      * 1. Set(item1 @ 2:00 1/1/2018 Video,        [ DATE    @ 0:00 1/1/2018,
140      *        item2 @ 1:00 1/1/2018 Audio)          item1   @ 2:00 1/1/2018,
141      *                                              item2   @ 1:00 1/1/2018 ]
142      */
143     @Test
testTwoItemsSameDayDifferentSection()144     public void testTwoItemsSameDayDifferentSection() {
145         OfflineItem item1 = buildItem("1", buildCalendar(2018, 1, 1, 2), OfflineItemFilter.VIDEO);
146         OfflineItem item2 = buildItem("2", buildCalendar(2018, 1, 1, 1), OfflineItemFilter.AUDIO);
147         when(mSource.getItems()).thenReturn(Arrays.asList(item1, item2));
148         DateOrderedListMutator list = createMutatorWithoutJustNowProvider();
149 
150         Assert.assertEquals(3, mModel.size());
151         assertSectionHeader(mModel.get(0), buildCalendar(2018, 1, 1, 0), false);
152         assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 1, 2), item1);
153         assertOfflineItem(mModel.get(2), buildCalendar(2018, 1, 1, 1), item2);
154     }
155 
156     /**
157      * Action                               List
158      * 1. Set(item1 @ 1:00 1/1/2018         [ DATE    Just Now,
159      *        IN_PROGRESS)
160      *                                        item1   @ 1:00 1/1/2018 ]
161      */
162     @Test
testSingleItemInJustNowSection()163     public void testSingleItemInJustNowSection() {
164         OfflineItem item1 = buildItem("1", buildCalendar(2018, 1, 1, 1), OfflineItemFilter.VIDEO);
165         item1.state = OfflineItemState.IN_PROGRESS;
166         when(mSource.getItems()).thenReturn(Collections.singletonList(item1));
167         DateOrderedListMutator list = createMutatorWithJustNowProvider();
168 
169         Assert.assertEquals(2, mModel.size());
170         assertJustNowSection(mModel.get(0));
171         assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 1, 1), item1);
172     }
173 
174     /**
175      * Action                               List
176      * 1. Set(item1 @ 1:00 1/1/2018         [ DATE    Just Now,
177      *              Video IN_PROGRESS,
178      *        item2 @ 1:00 1/1/2018           item1   @ 1:00 1/1/2018,
179      *              Audio COMPLETE Recent)
180      *                                        item2   @ 1:00 1/1/2018 ]
181      */
182     @Test
testMultipleSectionsInJustNowSection()183     public void testMultipleSectionsInJustNowSection() {
184         Calendar calendar = buildCalendar(2018, 1, 1, 1);
185         OfflineItem item1 = buildItem("1", calendar, OfflineItemFilter.VIDEO);
186         OfflineItem item2 = buildItem("2", calendar, OfflineItemFilter.AUDIO);
187         item1.state = OfflineItemState.IN_PROGRESS;
188         item2.state = OfflineItemState.COMPLETE;
189         item2.completionTimeMs = item2.creationTimeMs;
190         when(mSource.getItems()).thenReturn(Arrays.asList(item1, item2));
191         DateOrderedListMutator list =
192                 createMutatorWithJustNowProvider(buildJustNowProvider(calendar.getTime()));
193 
194         Assert.assertEquals(3, mModel.size());
195         assertJustNowSection(mModel.get(0));
196         assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 1, 1), item1);
197         assertOfflineItem(mModel.get(2), buildCalendar(2018, 1, 1, 1), item2);
198     }
199 
200     /**
201      * Action                               List
202      * 1. Set(item1 @ 0:10 1/2/2018         [ DATE    Just Now,
203      *              Video COMPLETE,
204      *        item2 @ 23:55 1/1/2018         [ DATE    Just Now,
205      *              Video COMPLETE,
206      *
207      *        item3 @ 10:00 1/1/2018           DATE    1/1/2018
208      *              Audio COMPLETE)           item3   @ 1:00 1/1/2018 ]
209      */
210     @Test
testRecentItemBeforeMidnightShowsInJustNowSection()211     public void testRecentItemBeforeMidnightShowsInJustNowSection() {
212         Calendar calendar1 = CalendarFactory.get();
213         calendar1.set(2018, 1, 2, 0, 10);
214         Calendar calendar2 = CalendarFactory.get();
215         calendar2.set(2018, 1, 1, 23, 50);
216         OfflineItem item1 = buildItem("1", calendar1, OfflineItemFilter.VIDEO);
217         OfflineItem item2 = buildItem("2", calendar2, OfflineItemFilter.AUDIO);
218         OfflineItem item3 = buildItem("3", buildCalendar(2018, 1, 1, 10), OfflineItemFilter.AUDIO);
219         item1.completionTimeMs = item1.creationTimeMs;
220         item2.completionTimeMs = item2.creationTimeMs;
221         when(mSource.getItems()).thenReturn(Arrays.asList(item1, item2, item3));
222 
223         Calendar now = CalendarFactory.get();
224         now.set(2018, 1, 2, 0, 15);
225         createMutatorWithJustNowProvider(buildJustNowProvider(now.getTime()));
226         Assert.assertEquals(5, mModel.size());
227         assertJustNowSection(mModel.get(0));
228         assertOfflineItem(mModel.get(1), calendar1, item1);
229         assertOfflineItem(mModel.get(2), calendar2, item2);
230         assertSectionHeader(mModel.get(3), buildCalendar(2018, 1, 1, 0), true);
231         assertOfflineItem(mModel.get(4), buildCalendar(2018, 1, 1, 10), item3);
232     }
233 
234     /**
235      * Action                               List
236      * 1. Set(item1 @ 1:00 1/1/2018         [ DATE    Just Now,
237      *        PAUSED)
238      *                                        item1   @ 1:00 1/1/2018 ]
239      * 2. Update(item1 @ 1:00 1/1/2018      [ DATE    Just Now,
240      *        Resume --> IN_PROGRESS)
241      *                                        item1   @ 1:00 1/1/2018 ]
242      * 3. Update(item1 @ 1:00 1/1/2018      [ DATE    Just Now,
243      *       COMPLETE, completion time now)
244      *                                        item1   @ 1:00 1/1/2018 ]
245      * 4. Update(item1 @ 1:00 1/1/2018      [ DATE    Just Now,
246      *    COMPLETE, completion time 1/1/2017)
247      *                                        item1   @ 1:00 1/1/2018 ]
248      */
249     @Test
testItemDoesNotMoveOutOfJustNowSection()250     public void testItemDoesNotMoveOutOfJustNowSection() {
251         OfflineItem item1 = buildItem("1", buildCalendar(2018, 1, 1, 1), OfflineItemFilter.VIDEO);
252         item1.state = OfflineItemState.PAUSED;
253         when(mSource.getItems()).thenReturn(Collections.singletonList(item1));
254         DateOrderedListMutator list = createMutatorWithJustNowProvider();
255         mModel.addObserver(mObserver);
256 
257         Assert.assertEquals(2, mModel.size());
258         assertJustNowSection(mModel.get(0));
259         assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 1, 1), item1);
260 
261         // Resume the download.
262         OfflineItem update1 = buildItem("1", buildCalendar(2018, 1, 1, 1), OfflineItemFilter.VIDEO);
263         update1.state = OfflineItemState.IN_PROGRESS;
264         when(mSource.getItems()).thenReturn(Collections.singletonList(update1));
265         list.onItemUpdated(item1, update1);
266 
267         Assert.assertEquals(2, mModel.size());
268         assertJustNowSection(mModel.get(0));
269         assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 1, 1), update1);
270 
271         // Complete the download.
272         OfflineItem update2 = buildItem("1", buildCalendar(2018, 1, 1, 1), OfflineItemFilter.VIDEO);
273         update2.state = OfflineItemState.COMPLETE;
274         update2.completionTimeMs = new Date().getTime();
275         when(mSource.getItems()).thenReturn(Collections.singletonList(update2));
276         list.onItemUpdated(update1, update2);
277 
278         Assert.assertEquals(2, mModel.size());
279         assertJustNowSection(mModel.get(0));
280         assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 1, 1), update2);
281 
282         // Too much time has passed since completion of the download.
283         OfflineItem update3 = buildItem("1", buildCalendar(2018, 1, 1, 1), OfflineItemFilter.VIDEO);
284         update3.state = OfflineItemState.COMPLETE;
285         update3.completionTimeMs = buildCalendar(2017, 1, 1, 1).getTimeInMillis();
286         when(mSource.getItems()).thenReturn(Collections.singletonList(update3));
287         list.onItemUpdated(update2, update3);
288 
289         Assert.assertEquals(2, mModel.size());
290         assertJustNowSection(mModel.get(0));
291         assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 1, 1), update3);
292     }
293 
294     /**
295      * Action                               List
296      * 1. Set(item1 @ 1:00 2/1/2018         [ DATE    Just Now,
297      *              Video IN_PROGRESS,
298      *        item2 @ 1:00 1/1/2018           item1   @ 1:00 2/1/2018,
299      *              Audio COMPLETE)           DATE    1/1/2018
300      *                                        item2   @ 1:00 1/1/2018 ]
301      */
302     @Test
testJustNowSectionWithOtherDates()303     public void testJustNowSectionWithOtherDates() {
304         OfflineItem item1 = buildItem("1", buildCalendar(2018, 2, 1, 1), OfflineItemFilter.VIDEO);
305         OfflineItem item2 = buildItem("2", buildCalendar(2018, 1, 1, 1), OfflineItemFilter.AUDIO);
306         item1.state = OfflineItemState.IN_PROGRESS;
307         when(mSource.getItems()).thenReturn(Arrays.asList(item1, item2));
308         DateOrderedListMutator list = createMutatorWithJustNowProvider();
309 
310         Assert.assertEquals(4, mModel.size());
311         assertJustNowSection(mModel.get(0));
312         assertOfflineItem(mModel.get(1), buildCalendar(2018, 2, 1, 1), item1);
313         assertSectionHeader(mModel.get(2), buildCalendar(2018, 1, 1, 0), true);
314         assertOfflineItem(mModel.get(3), buildCalendar(2018, 1, 1, 1), item2);
315     }
316 
317     /**
318      * Action                               List
319      * 1. Set(item1 @ 1:00 1/1/2018         [ Header Scheduled for later,
320      *              Scheduled for later,      item1  @ 1:00 1/1/2018
321      *        item2 @ 1:00 2/1/2018           Header Just Now
322      *              Video IN_PROGRESS         item2  @ 1:00 2/1/2018
323      *        item3 @ 1:00 1/1/2018           DATE   1/1/2018
324      *              Audio COMPLETE)           item3  @ 1:00 1/1/2018 ]
325      */
326     @Test
testScheduledForLaterSection()327     public void testScheduledForLaterSection() {
328         Calendar calendar = buildCalendar(2018, 1, 1, 1);
329         OfflineItem item1 = buildItem("1", calendar, OfflineItemFilter.VIDEO);
330         item1.schedule = new OfflineItemSchedule(false, calendar.getTimeInMillis() + 1000);
331         OfflineItem item2 = buildItem("1", buildCalendar(2018, 2, 1, 1), OfflineItemFilter.VIDEO);
332         OfflineItem item3 = buildItem("2", buildCalendar(2018, 1, 1, 1), OfflineItemFilter.AUDIO);
333         item2.state = OfflineItemState.IN_PROGRESS;
334         when(mSource.getItems()).thenReturn(Arrays.asList(item1, item2, item3));
335         DateOrderedListMutator list = createMutatorWithJustNowProvider();
336 
337         Assert.assertEquals(6, mModel.size());
338         assertScheduledLaterHeader(mModel.get(0));
339         assertOfflineItem(mModel.get(1), calendar, item1);
340         assertJustNowSection(mModel.get(2));
341         assertOfflineItem(mModel.get(3), buildCalendar(2018, 2, 1, 1), item2);
342         assertSectionHeader(mModel.get(4), buildCalendar(2018, 1, 1, 0), true);
343         assertOfflineItem(mModel.get(5), buildCalendar(2018, 1, 1, 1), item3);
344     }
345 
346     /**
347      * Action                               List
348      * 1. Set(item1 @ 0:00 1/2/2018,        [ DATE    @ 0:00 1/2/2018,
349      *        item2 @ 0:00 1/1/2018)
350      *                                        item1   @ 0:00 1/2/2018,
351      *                                        DATE  @ 0:00 1/1/2018,
352      *
353      *                                        item2   @ 0:00 1/1/2018 ]
354      */
355     @Test
testTwoItemsDifferentDayMatchHeader()356     public void testTwoItemsDifferentDayMatchHeader() {
357         OfflineItem item1 = buildItem("1", buildCalendar(2018, 1, 2, 0), OfflineItemFilter.VIDEO);
358         OfflineItem item2 = buildItem("2", buildCalendar(2018, 1, 1, 0), OfflineItemFilter.AUDIO);
359         when(mSource.getItems()).thenReturn(Arrays.asList(item1, item2));
360         DateOrderedListMutator list = createMutatorWithoutJustNowProvider();
361 
362         Assert.assertEquals(4, mModel.size());
363         assertSectionHeader(mModel.get(0), buildCalendar(2018, 1, 2, 0), false);
364         assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 2, 0), item1);
365         assertSectionHeader(mModel.get(2), buildCalendar(2018, 1, 1, 0), true);
366         assertOfflineItem(mModel.get(3), buildCalendar(2018, 1, 1, 0), item2);
367     }
368 
369     /**
370      * Action                               List
371      * 1. Set(item1 @ 4:00 1/1/2018,        [ DATE    @ 0:00 1/1/2018,
372      *        item2 @ 5:00 1/1/2018)
373      *                                        item2   @ 5:00 1/1/2018,
374      *                                        item1   @ 4:00 1/1/2018 ]
375      */
376     @Test
testTwoItemsSameDayOutOfOrder()377     public void testTwoItemsSameDayOutOfOrder() {
378         OfflineItem item1 = buildItem("1", buildCalendar(2018, 1, 1, 4), OfflineItemFilter.VIDEO);
379         OfflineItem item2 = buildItem("2", buildCalendar(2018, 1, 1, 5), OfflineItemFilter.VIDEO);
380         when(mSource.getItems()).thenReturn(Arrays.asList(item1, item2));
381         DateOrderedListMutator list = createMutatorWithoutJustNowProvider();
382 
383         Assert.assertEquals(3, mModel.size());
384         assertSectionHeader(mModel.get(0), buildCalendar(2018, 1, 1, 0), false);
385         assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 1, 5), item2);
386         assertOfflineItem(mModel.get(2), buildCalendar(2018, 1, 1, 4), item1);
387     }
388 
389     /**
390      * Action                                      List
391      * 1. Set(item1 @ 4:00 1/2/2018 Video,         [ DATE      @ 0:00 1/2/2018,
392      *        item2 @ 5:00 1/1/2018 Video)
393      *                                               item2     @ 4:00 1/2/2018,
394      *                                               DATE      @ 0:00 1/1/2018,
395      *                                               item1     @ 5:00 1/1/2018 ]
396      */
397     @Test
testTwoItemsDifferentDaySameSection()398     public void testTwoItemsDifferentDaySameSection() {
399         OfflineItem item1 = buildItem("1", buildCalendar(2018, 1, 2, 4), OfflineItemFilter.VIDEO);
400         OfflineItem item2 = buildItem("2", buildCalendar(2018, 1, 1, 5), OfflineItemFilter.VIDEO);
401         when(mSource.getItems()).thenReturn(Arrays.asList(item1, item2));
402         DateOrderedListMutator list = createMutatorWithoutJustNowProvider();
403 
404         Assert.assertEquals(4, mModel.size());
405         assertSectionHeader(mModel.get(0), buildCalendar(2018, 1, 2, 0), false);
406         assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 2, 4), item1);
407         assertSectionHeader(mModel.get(2), buildCalendar(2018, 1, 1, 0), true);
408         assertOfflineItem(mModel.get(3), buildCalendar(2018, 1, 1, 5), item2);
409     }
410 
411     /**
412      * Action                                      List
413      * 1. Set(item1 @ 4:00 1/2/2018 Video,         [ DATE      @ 0:00 1/2/2018,
414      *        item2 @ 5:00 1/1/2018 Page )
415      *                                               item2     @ 4:00 1/2/2018,
416      *                                               DATE      @ 0:00 1/1/2018,
417      *                                               item1     @ 5:00 1/1/2018 ]
418      */
419     @Test
testTwoItemsDifferentDayDifferentSection()420     public void testTwoItemsDifferentDayDifferentSection() {
421         OfflineItem item1 = buildItem("1", buildCalendar(2018, 1, 2, 4), OfflineItemFilter.VIDEO);
422         OfflineItem item2 = buildItem("2", buildCalendar(2018, 1, 1, 5), OfflineItemFilter.PAGE);
423         when(mSource.getItems()).thenReturn(Arrays.asList(item1, item2));
424         DateOrderedListMutator list = createMutatorWithoutJustNowProvider();
425 
426         Assert.assertEquals(4, mModel.size());
427         assertSectionHeader(mModel.get(0), buildCalendar(2018, 1, 2, 0), false);
428         assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 2, 4), item1);
429         assertSectionHeader(mModel.get(2), buildCalendar(2018, 1, 1, 0), true);
430         assertOfflineItem(mModel.get(3), buildCalendar(2018, 1, 1, 5), item2);
431     }
432 
433     /**
434      * Action                               List
435      * 1. Set(item1 @ 4:00 1/1/2018,        [ DATE   @ 0:00 1/2/2018,
436      *        item2 @ 3:00 1/2/2018)
437      *                                        item2  @ 3:00 1/2/2018,
438      *                                        DATE   @ 0:00 1/1/2018,
439      *                                        item1  @ 4:00 1/1/2018 ]
440      */
441     @Test
testTwoItemsDifferentDayOutOfOrder()442     public void testTwoItemsDifferentDayOutOfOrder() {
443         OfflineItem item1 = buildItem("1", buildCalendar(2018, 1, 1, 4), OfflineItemFilter.VIDEO);
444         OfflineItem item2 = buildItem("2", buildCalendar(2018, 1, 2, 3), OfflineItemFilter.VIDEO);
445         when(mSource.getItems()).thenReturn(Arrays.asList(item1, item2));
446         DateOrderedListMutator list = createMutatorWithoutJustNowProvider();
447 
448         Assert.assertEquals(4, mModel.size());
449         assertSectionHeader(mModel.get(0), buildCalendar(2018, 1, 2, 0), false);
450         assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 2, 3), item2);
451         assertSectionHeader(mModel.get(2), buildCalendar(2018, 1, 1, 0), true);
452         assertOfflineItem(mModel.get(3), buildCalendar(2018, 1, 1, 4), item1);
453     }
454 
455     /**
456      * Action                               List
457      * 1. Set()                             [ ]
458      *
459      * 2. Add(item1 @ 4:00 1/1/2018)        [ DATE    @ 0:00 1/1/2018,
460      *                                        item1  @ 4:00 1/1/2018 ]
461      */
462     @Test
testAddItemToEmptyList()463     public void testAddItemToEmptyList() {
464         when(mSource.getItems()).thenReturn(Collections.emptySet());
465         DateOrderedListMutator list = createMutatorWithoutJustNowProvider();
466         mModel.addObserver(mObserver);
467 
468         OfflineItem item1 = buildItem("1", buildCalendar(2018, 1, 1, 4), OfflineItemFilter.VIDEO);
469         when(mSource.getItems()).thenReturn(Collections.singletonList(item1));
470         list.onItemsAdded(Collections.singletonList(item1));
471 
472         Assert.assertEquals(2, mModel.size());
473         assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 1, 4), item1);
474     }
475 
476     /**
477      * Action                               List
478      * 1. Set(item1 @ 1:00 1/2/2018)        [ DATE    @ 0:00 1/2/2018,
479      *                                        item1  @ 1:00 1/2/2018 ]
480      * 2. Add(item2 @ 2:00 1/2/2018)        [ DATE    @ 0:00 1/2/2018,
481      *                                        item2  @ 2:00 1/2/2018
482      *                                        item1  @ 1:00 1/2/2018 ]
483      * 3. Add(item3 @ 2:00 1/3/2018)        [ DATE    @ 0:00 1/3/2018,
484      *                                        item3  @ 2:00 1/3/2018
485      *                                        DATE    @ 0:00 1/2/2018,
486      *                                        item2  @ 2:00 1/2/2018
487      *                                        item1  @ 1:00 1/2/2018 ]
488      */
489     @Test
testAddFirstItemToList()490     public void testAddFirstItemToList() {
491         OfflineItem item1 = buildItem("1", buildCalendar(2018, 1, 2, 1), OfflineItemFilter.VIDEO);
492         when(mSource.getItems()).thenReturn(Collections.singletonList(item1));
493         DateOrderedListMutator list = createMutatorWithoutJustNowProvider();
494         mModel.addObserver(mObserver);
495 
496         // Add an item on the same day that will be placed first.
497         OfflineItem item2 = buildItem("2", buildCalendar(2018, 1, 2, 2), OfflineItemFilter.VIDEO);
498         when(mSource.getItems()).thenReturn(Arrays.asList(item1, item2));
499         list.onItemsAdded(Collections.singletonList(item2));
500 
501         Assert.assertEquals(3, mModel.size());
502         assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 2, 2), item2);
503         assertOfflineItem(mModel.get(2), buildCalendar(2018, 1, 2, 1), item1);
504 
505         // Add an item on an earlier day that will be placed first.
506         OfflineItem item3 = buildItem("3", buildCalendar(2018, 1, 3, 2), OfflineItemFilter.VIDEO);
507         when(mSource.getItems()).thenReturn(Arrays.asList(item1, item2, item3));
508         list.onItemsAdded(Collections.singletonList(item3));
509 
510         Assert.assertEquals(5, mModel.size());
511         assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 3, 2), item3);
512         assertOfflineItem(mModel.get(3), buildCalendar(2018, 1, 2, 2), item2);
513         assertOfflineItem(mModel.get(4), buildCalendar(2018, 1, 2, 1), item1);
514     }
515 
516     /**
517      * Action                               List
518      * 1. Set(item1 @ 4:00 1/2/2018)        [ DATE    @ 0:00 1/2/2018,
519      *                                        item1  @ 4:00 1/2/2018 ]
520      *
521      * 2. Add(item2 @ 3:00 1/2/2018)        [ DATE    @ 0:00 1/2/2018,
522      *                                        item1  @ 4:00 1/2/2018
523      *                                        item2  @ 3:00 1/2/2018 ]
524      *
525      * 3. Add(item3 @ 4:00 1/1/2018)        [ DATE    @ 0:00 1/2/2018,
526      *                                        item1  @ 4:00 1/2/2018
527      *                                        item2  @ 3:00 1/2/2018,
528      *                                        DATE    @ 0:00 1/1/2018,
529      *                                        item3  @ 4:00 1/1/2018
530      */
531     @Test
testAddLastItemToList()532     public void testAddLastItemToList() {
533         OfflineItem item1 = buildItem("1", buildCalendar(2018, 1, 2, 4), OfflineItemFilter.VIDEO);
534         when(mSource.getItems()).thenReturn(Collections.singletonList(item1));
535         DateOrderedListMutator list = createMutatorWithoutJustNowProvider();
536         mModel.addObserver(mObserver);
537 
538         // Add an item on the same day that will be placed last.
539         OfflineItem item2 = buildItem("2", buildCalendar(2018, 1, 2, 3), OfflineItemFilter.VIDEO);
540         when(mSource.getItems()).thenReturn(Arrays.asList(item1, item2));
541         list.onItemsAdded(Collections.singletonList(item2));
542 
543         Assert.assertEquals(3, mModel.size());
544         assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 2, 4), item1);
545         assertOfflineItem(mModel.get(2), buildCalendar(2018, 1, 2, 3), item2);
546 
547         // Add an item on a later day that will be placed last.
548         OfflineItem item3 = buildItem("3", buildCalendar(2018, 1, 1, 4), OfflineItemFilter.VIDEO);
549         when(mSource.getItems()).thenReturn(Arrays.asList(item1, item2, item3));
550         list.onItemsAdded(Collections.singletonList(item3));
551 
552         Assert.assertEquals(5, mModel.size());
553         assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 2, 4), item1);
554         assertOfflineItem(mModel.get(2), buildCalendar(2018, 1, 2, 3), item2);
555         assertOfflineItem(mModel.get(4), buildCalendar(2018, 1, 1, 4), item3);
556     }
557 
558     /**
559      * Action                               List
560      * 1. Set(item1 @ 2:00 1/2/2018)        [ DATE    @ 0:00 1/2/2018,
561      *
562      *                                        item1  @ 2:00 1/2/2018 ]
563      *
564      * 2. Remove(item1)                     [ ]
565      */
566     @Test
testRemoveOnlyItemInList()567     public void testRemoveOnlyItemInList() {
568         OfflineItem item1 = buildItem("1", buildCalendar(2018, 1, 2, 2), OfflineItemFilter.VIDEO);
569         when(mSource.getItems()).thenReturn(Collections.singletonList(item1));
570         DateOrderedListMutator list = createMutatorWithoutJustNowProvider();
571         mModel.addObserver(mObserver);
572 
573         when(mSource.getItems()).thenReturn(Collections.emptySet());
574         list.onItemsRemoved(Collections.singletonList(item1));
575 
576         Assert.assertEquals(0, mModel.size());
577     }
578 
579     /**
580      * Action                               List
581      * 1. Set(item1 @ 3:00 1/2/2018,        [ DATE    @ 0:00 1/2/2018,
582      *        item2 @ 2:00 1/2/2018)
583      *                                        item1  @ 3:00 1/2/2018,
584      *                                        item2  @ 2:00 1/2/2018 ]
585      *
586      * 2. Remove(item1)                     [ DATE    @ 0:00 1/2/2018,
587      *                                        item2  @ 2:00 1/2/2018 ]
588      */
589     @Test
testRemoveFirstItemInListSameDay()590     public void testRemoveFirstItemInListSameDay() {
591         OfflineItem item1 = buildItem("1", buildCalendar(2018, 1, 2, 3), OfflineItemFilter.VIDEO);
592         OfflineItem item2 = buildItem("2", buildCalendar(2018, 1, 2, 2), OfflineItemFilter.VIDEO);
593         when(mSource.getItems()).thenReturn(Arrays.asList(item1, item2));
594         DateOrderedListMutator list = createMutatorWithoutJustNowProvider();
595         mModel.addObserver(mObserver);
596 
597         when(mSource.getItems()).thenReturn(Collections.singletonList(item2));
598         list.onItemsRemoved(Collections.singletonList(item1));
599 
600         Assert.assertEquals(2, mModel.size());
601         assertSectionHeader(mModel.get(0), buildCalendar(2018, 1, 2, 0), false);
602         assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 2, 2), item2);
603     }
604 
605     /**
606      * Action                               List
607      * 1. Set(item1 @ 3:00 1/2/2018,        [ DATE    @ 0:00 1/2/2018,
608      *        item2 @ 2:00 1/2/2018)
609      *                                        item1  @ 3:00 1/2/2018,
610      *                                        item2  @ 2:00 1/2/2018 ]
611      *
612      * 2. Remove(item2)                     [ DATE    @ 0:00 1/2/2018,
613      *                                        item1  @ 3:00 1/2/2018 ]
614      */
615     @Test
testRemoveLastItemInListSameDay()616     public void testRemoveLastItemInListSameDay() {
617         OfflineItem item1 = buildItem("1", buildCalendar(2018, 1, 2, 3), OfflineItemFilter.VIDEO);
618         OfflineItem item2 = buildItem("2", buildCalendar(2018, 1, 2, 2), OfflineItemFilter.VIDEO);
619         when(mSource.getItems()).thenReturn(Arrays.asList(item1, item2));
620         DateOrderedListMutator list = createMutatorWithoutJustNowProvider();
621         mModel.addObserver(mObserver);
622 
623         when(mSource.getItems()).thenReturn(Collections.singletonList(item1));
624         list.onItemsRemoved(Collections.singletonList(item2));
625 
626         Assert.assertEquals(2, mModel.size());
627         assertSectionHeader(mModel.get(0), buildCalendar(2018, 1, 2, 0), false);
628         assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 2, 3), item1);
629     }
630 
631     /**
632      * Action                               List
633      * 1. Set(item1 @ 3:00 1/3/2018,        [ DATE    @ 0:00 1/3/2018,
634      *        item2 @ 2:00 1/2/2018)
635      *                                        item1  @ 3:00 1/3/2018,
636      *                                        DATE    @ 0:00 1/2/2018,
637      *                                        item2  @ 2:00 1/2/2018 ]
638      *
639      * 2. Remove(item2)                     [ DATE    @ 0:00 1/3/2018,
640      *                                        item1  @ 3:00 1/3/2018 ]
641      */
642     @Test
testRemoveLastItemInListWithMultipleDays()643     public void testRemoveLastItemInListWithMultipleDays() {
644         OfflineItem item1 = buildItem("1", buildCalendar(2018, 1, 3, 3), OfflineItemFilter.VIDEO);
645         OfflineItem item2 = buildItem("2", buildCalendar(2018, 1, 2, 2), OfflineItemFilter.VIDEO);
646         when(mSource.getItems()).thenReturn(Arrays.asList(item1, item2));
647         DateOrderedListMutator list = createMutatorWithoutJustNowProvider();
648         mModel.addObserver(mObserver);
649 
650         when(mSource.getItems()).thenReturn(Collections.singletonList(item1));
651         list.onItemsRemoved(Collections.singletonList(item2));
652 
653         Assert.assertEquals(2, mModel.size());
654         assertSectionHeader(mModel.get(0), buildCalendar(2018, 1, 3, 0), false);
655         assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 3, 3), item1);
656     }
657 
658     /**
659      * Action                               List
660      * 1. Set()                             [ ]
661      *
662      * 2. Add(item1 @ 6:00  1/1/2018,       [ DATE    @ 0:00  1/2/2018,
663      *        item2 @ 4:00  1/1/2018,
664      *        item3 @ 10:00 1/2/2018,         item4  @ 12:00 1/2/2018,
665      *        item4 @ 12:00 1/2/2018)         item3  @ 10:00 1/2/2018
666      *                                        DATE    @ 0:00  1/1/2018,
667      *
668      *                                        item1  @ 6:00  1/1/2018,
669      *                                        item2  @ 4:00  1/1/2018 ]
670      */
671     @Test
testAddMultipleItems()672     public void testAddMultipleItems() {
673         when(mSource.getItems()).thenReturn(Collections.emptySet());
674         DateOrderedListMutator list = createMutatorWithoutJustNowProvider();
675         mModel.addObserver(mObserver);
676 
677         OfflineItem item1 = buildItem("1", buildCalendar(2018, 1, 1, 6), OfflineItemFilter.VIDEO);
678         OfflineItem item2 = buildItem("2", buildCalendar(2018, 1, 1, 4), OfflineItemFilter.VIDEO);
679         OfflineItem item3 = buildItem("3", buildCalendar(2018, 1, 2, 10), OfflineItemFilter.VIDEO);
680         OfflineItem item4 = buildItem("4", buildCalendar(2018, 1, 2, 12), OfflineItemFilter.VIDEO);
681 
682         when(mSource.getItems()).thenReturn(Arrays.asList(item1, item2, item3, item4));
683         list.onItemsAdded(Arrays.asList(item1, item2, item3, item4));
684 
685         Assert.assertEquals(6, mModel.size());
686         assertSectionHeader(mModel.get(0), buildCalendar(2018, 1, 2, 0), false);
687         assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 2, 12), item4);
688         assertOfflineItem(mModel.get(2), buildCalendar(2018, 1, 2, 10), item3);
689         assertSectionHeader(mModel.get(3), buildCalendar(2018, 1, 1, 0), true);
690         assertOfflineItem(mModel.get(4), buildCalendar(2018, 1, 1, 6), item1);
691         assertOfflineItem(mModel.get(5), buildCalendar(2018, 1, 1, 4), item2);
692     }
693 
694     /**
695      * Action                               List
696      * 1. Set()                             [ ]
697      *
698      * 2. Add(item3 @ 4:00 1/1/2018)        [ DATE    @ 0:00 1/1/2018,
699      *                                        item3  @ 4:00 1/1/2018 ]
700      *
701      * 3. Add(item1 @ 4:00 1/1/2018)        [ DATE    @ 0:00 1/1/2018,
702      *                                        item1  @ 4:00 1/1/2018,
703      *                                        item3  @ 4:00 1/1/2018 ]
704      *
705      * 4. Add(item2 @ 4:00 1/1/2018)        [ DATE    @ 0:00 1/1/2018,
706      *                                        item1  @ 4:00 1/1/2018,
707      *                                        item2  @ 4:00 1/1/2018,
708      *                                        item3  @ 4:00 1/1/2018 ]
709      */
710     @Test
testAddMultipleItemsSameTimestamp()711     public void testAddMultipleItemsSameTimestamp() {
712         when(mSource.getItems()).thenReturn(Collections.emptySet());
713         DateOrderedListMutator list = createMutatorWithoutJustNowProvider();
714         mModel.addObserver(mObserver);
715 
716         OfflineItem item3 = buildItem("3", buildCalendar(2018, 1, 1, 4), OfflineItemFilter.VIDEO);
717         when(mSource.getItems()).thenReturn(Collections.singletonList(item3));
718         list.onItemsAdded(Collections.singletonList(item3));
719 
720         Assert.assertEquals(2, mModel.size());
721         assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 1, 4), item3);
722 
723         OfflineItem item1 = buildItem("1", buildCalendar(2018, 1, 1, 4), OfflineItemFilter.VIDEO);
724         when(mSource.getItems()).thenReturn(Arrays.asList(item3, item1));
725         list.onItemsAdded(Collections.singletonList(item1));
726 
727         Assert.assertEquals(3, mModel.size());
728         assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 1, 4), item1);
729         assertOfflineItem(mModel.get(2), buildCalendar(2018, 1, 1, 4), item3);
730 
731         OfflineItem item2 = buildItem("2", buildCalendar(2018, 1, 1, 4), OfflineItemFilter.VIDEO);
732         when(mSource.getItems()).thenReturn(Arrays.asList(item3, item1, item2));
733         list.onItemsAdded(Collections.singletonList(item2));
734 
735         Assert.assertEquals(4, mModel.size());
736         assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 1, 4), item1);
737         assertOfflineItem(mModel.get(2), buildCalendar(2018, 1, 1, 4), item2);
738         assertOfflineItem(mModel.get(3), buildCalendar(2018, 1, 1, 4), item3);
739     }
740 
741     /**
742      * Action                                          List
743      * 1. Set(item1 @ 6:00 IN_PROGRESS 1/1/2018)       [ DATE    @ 0:00 1/1/2018,
744      *
745      *                                                   item1  @ 3:00 1/1/2018 IN_PROGRESS]
746      *
747      * 2. Update(item1 @ 6:00 COMPLETE 1/1/2018)
748      *
749      * 3. Add(item2 @ 4:00 IN_PROGRESS 1/1/2018)       [
750      *                                                   DATE    @ 0:00  1/1/2018,
751      *                                                   item1  @ 6:00  1/1/2018 COMPLETE,
752      *                                                   item2  @ 4:00  1/1/2018 IN_PROGRESS]
753      */
754     @Test
testFirstItemUpdatedAfterSecondItemAdded()755     public void testFirstItemUpdatedAfterSecondItemAdded() {
756         when(mSource.getItems()).thenReturn(Collections.emptySet());
757         DateOrderedListMutator list = createMutatorWithoutJustNowProvider();
758         mModel.addObserver(mObserver);
759 
760         OfflineItem item1 = buildItem("1", buildCalendar(2018, 1, 1, 6), OfflineItemFilter.VIDEO);
761 
762         when(mSource.getItems()).thenReturn(Collections.singletonList(item1));
763         list.onItemsAdded(Collections.singletonList(item1));
764 
765         Assert.assertEquals(2, mModel.size());
766         assertSectionHeader(mModel.get(0), buildCalendar(2018, 1, 1, 0), false);
767         assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 1, 6), item1);
768 
769         // Complete the download.
770         OfflineItem update1 = buildItem("1", buildCalendar(2018, 1, 1, 6), OfflineItemFilter.VIDEO);
771         update1.state = OfflineItemState.COMPLETE;
772         when(mSource.getItems()).thenReturn(Collections.singletonList(update1));
773         list.onItemUpdated(item1, update1);
774 
775         // Add a new download.
776         OfflineItem item2 = buildItem("2", buildCalendar(2018, 1, 1, 4), OfflineItemFilter.VIDEO);
777         list.onItemsAdded(Collections.singletonList(item2));
778 
779         Assert.assertEquals(3, mModel.size());
780         assertSectionHeader(mModel.get(0), buildCalendar(2018, 1, 1, 0), false);
781         assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 1, 6), update1);
782         assertOfflineItem(mModel.get(2), buildCalendar(2018, 1, 1, 4), item2);
783     }
784 
785     /**
786      * Action                               List
787      * 2. Set(item1 @ 6:00  1/1/2018,       [ DATE    @ 0:00  1/2/2018,
788      *        item2 @ 4:00  1/1/2018,
789      *        item3 @ 10:00 1/2/2018,         item4  @ 12:00 1/2/2018,
790      *        item4 @ 12:00 1/2/2018)         item3  @ 10:00 1/2/2018
791      *                                        DATE    @ 0:00  1/1/2018,
792      *
793      *                                        item1  @ 6:00  1/1/2018,
794      *                                        item2  @ 4:00  1/1/2018 ]
795      *
796      * 2. Remove(item2,                     [ DATE    @ 0:00  1/1/2018,
797      *           item3,
798      *           item4)                       item1  @ 6:00  1/1/2018 ]
799      */
800     @Test
testRemoveMultipleItems()801     public void testRemoveMultipleItems() {
802         OfflineItem item1 = buildItem("1", buildCalendar(2018, 1, 1, 6), OfflineItemFilter.VIDEO);
803         OfflineItem item2 = buildItem("2", buildCalendar(2018, 1, 1, 4), OfflineItemFilter.VIDEO);
804         OfflineItem item3 = buildItem("3", buildCalendar(2018, 1, 2, 10), OfflineItemFilter.VIDEO);
805         OfflineItem item4 = buildItem("4", buildCalendar(2018, 1, 2, 12), OfflineItemFilter.VIDEO);
806 
807         when(mSource.getItems()).thenReturn(Arrays.asList(item1, item2, item3, item4));
808         DateOrderedListMutator list = createMutatorWithoutJustNowProvider();
809         mModel.addObserver(mObserver);
810 
811         when(mSource.getItems()).thenReturn(Collections.singletonList(item1));
812         list.onItemsRemoved(Arrays.asList(item2, item3, item4));
813 
814         Assert.assertEquals(2, mModel.size());
815         assertSectionHeader(mModel.get(0), buildCalendar(2018, 1, 1, 0), false);
816         assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 1, 6), item1);
817     }
818 
819     /**
820      * Action                               List
821      * 1. Set(item1 @ 4:00 1/1/2018)        [ DATE      @ 0:00  1/1/2018,
822      *
823      *                                        item1     @ 4:00  1/1/2018 ]
824      *
825      * 2. Update (item1,
826      *            newItem1 @ 4:00 1/1/2018) [ DATE      @ 0:00  1/1/2018,
827      *
828      *                                        newItem1  @ 4:00  1/1/2018 ]
829      */
830     @Test
testItemUpdatedSameTimestamp()831     public void testItemUpdatedSameTimestamp() {
832         OfflineItem item1 = buildItem("1", buildCalendar(2018, 1, 1, 4), OfflineItemFilter.VIDEO);
833 
834         when(mSource.getItems()).thenReturn(Collections.singletonList(item1));
835         DateOrderedListMutator list = createMutatorWithoutJustNowProvider();
836         mModel.addObserver(mObserver);
837 
838         // Update an item with the same timestamp.
839         OfflineItem newItem1 =
840                 buildItem("1", buildCalendar(2018, 1, 1, 4), OfflineItemFilter.VIDEO);
841         when(mSource.getItems()).thenReturn(Collections.singletonList(newItem1));
842         list.onItemUpdated(item1, newItem1);
843 
844         Assert.assertEquals(2, mModel.size());
845         assertSectionHeader(mModel.get(0), buildCalendar(2018, 1, 1, 0), false);
846         assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 1, 4), newItem1);
847     }
848 
849     /**
850      * Action                               List
851      * 1. Set(item1 @ 5:00 1/1/2018,        [ DATE      @ 0:00  1/1/2018,
852      *        item2 @ 4:00 1/1/2018)
853      *                                        item1     @ 5:00  1/1/2018,
854      *                                        item2     @ 4:00  1/1/2018
855      * 2. Update (item1,
856      *            newItem1 @ 3:00 1/1/2018) [ DATE      @ 0:00  1/1/2018,
857      *
858      *                                        item2     @ 4:00  1/1/2018,
859      *                                        newItem1  @ 3:00  1/1/2018 ]
860      */
861     @Test
testItemUpdatedSameDay()862     public void testItemUpdatedSameDay() {
863         OfflineItem item1 = buildItem("1", buildCalendar(2018, 1, 1, 5), OfflineItemFilter.VIDEO);
864         OfflineItem item2 = buildItem("2", buildCalendar(2018, 1, 1, 4), OfflineItemFilter.VIDEO);
865 
866         when(mSource.getItems()).thenReturn(Arrays.asList(item1, item2));
867         DateOrderedListMutator list = createMutatorWithoutJustNowProvider();
868         mModel.addObserver(mObserver);
869 
870         // Update an item with the same timestamp.
871         OfflineItem newItem1 =
872                 buildItem("1", buildCalendar(2018, 1, 1, 3), OfflineItemFilter.VIDEO);
873         when(mSource.getItems()).thenReturn(Arrays.asList(newItem1, item2));
874         list.onItemUpdated(item1, newItem1);
875 
876         Assert.assertEquals(3, mModel.size());
877         assertSectionHeader(mModel.get(0), buildCalendar(2018, 1, 1, 0), false);
878         assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 1, 4), item2);
879         assertOfflineItem(mModel.get(2), buildCalendar(2018, 1, 1, 3), newItem1);
880     }
881 
882     /**
883      * Action                                   List
884      * 1. Set(item1 @ 5:00 1/1/2018,            [ DATE      @ 0:00  1/1/2018,
885      *        item2 @ 4:00 1/1/2018)
886      *                                            item1     @ 5:00  1/1/2018,
887      *                                            item2     @ 4:00  1/1/2018
888      * 2. Update (item1,
889      *            newItem1 @ 3:00 1/1/2018 Image) [ DATE      @ 0:00  1/1/2018,
890      *
891      *                                              item2     @ 4:00  1/1/2018,
892      *                                              newItem1  @ 3:00  1/1/2018 ]
893      */
894     @Test
testItemUpdatedSameDayDifferentSection()895     public void testItemUpdatedSameDayDifferentSection() {
896         OfflineItem item1 = buildItem("1", buildCalendar(2018, 1, 1, 5), OfflineItemFilter.VIDEO);
897         OfflineItem item2 = buildItem("2", buildCalendar(2018, 1, 1, 4), OfflineItemFilter.VIDEO);
898 
899         when(mSource.getItems()).thenReturn(Arrays.asList(item1, item2));
900         DateOrderedListMutator list = createMutatorWithoutJustNowProvider();
901         mModel.addObserver(mObserver);
902 
903         // Update an item with the same timestamp.
904         OfflineItem newItem1 =
905                 buildItem("1", buildCalendar(2018, 1, 1, 3), OfflineItemFilter.IMAGE);
906         when(mSource.getItems()).thenReturn(Arrays.asList(newItem1, item2));
907         list.onItemUpdated(item1, newItem1);
908 
909         Assert.assertEquals(3, mModel.size());
910         assertSectionHeader(mModel.get(0), buildCalendar(2018, 1, 1, 0), false);
911         assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 1, 4), item2);
912         assertOfflineItem(mModel.get(2), buildCalendar(2018, 1, 1, 3), newItem1);
913     }
914 
915     /**
916      * Action                               List
917      * 1. Set(item1 @ 4:00 1/1/2018)        [ DATE      @ 0:00  1/1/2018,
918      *                                        item1     @ 4:00  1/1/2018 ]
919      *
920      * 2. Update (item1,
921      *            newItem1 @ 6:00 1/2/2018) [ DATE      @ 0:00  1/2/2018,
922      *                                        newItem1  @ 6:00  1/2/2018 ]
923      */
924     @Test
testItemUpdatedDifferentDay()925     public void testItemUpdatedDifferentDay() {
926         OfflineItem item1 = buildItem("1", buildCalendar(2018, 1, 1, 4), OfflineItemFilter.VIDEO);
927 
928         when(mSource.getItems()).thenReturn(Collections.singletonList(item1));
929         DateOrderedListMutator list = createMutatorWithoutJustNowProvider();
930         mModel.addObserver(mObserver);
931 
932         // Update an item with the same timestamp.
933         OfflineItem newItem1 =
934                 buildItem("1", buildCalendar(2018, 1, 2, 6), OfflineItemFilter.VIDEO);
935         when(mSource.getItems()).thenReturn(Collections.singletonList(newItem1));
936         list.onItemUpdated(item1, newItem1);
937 
938         Assert.assertEquals(2, mModel.size());
939         assertSectionHeader(mModel.get(0), buildCalendar(2018, 1, 2, 0), false);
940         assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 2, 6), newItem1);
941     }
942 
943     /**
944      * Action                                      List
945      * 1. Set(item1 @ 4:00 1/2/2018 Video,
946      *        item2 @ 4:00 1/2/2018 Video,
947      *        item3 @ 4:00 1/2/2018 Image,
948      *        item4 @ 4:00 1/2/2018 Image,
949      *        item5 @ 4:00 1/2/2018 Image)
950      * 2. Select Video
951      *                                              [Date - 1/2/2018, item1, item2 ]
952      * 2. Select Image
953      *                                              [Date - 1/2/2018, item3, item4, item5 ]
954      * 2. Select ALL
955      *                                              [Date - 1/2/2018, item1, item2,
956      *                                                        item3, item4, item5 ]
957      */
958     @Test
testSwitchChips()959     public void testSwitchChips() {
960         OfflineItem item1 = buildItem("1", buildCalendar(2018, 1, 2, 4), OfflineItemFilter.VIDEO);
961         OfflineItem item2 = buildItem("2", buildCalendar(2018, 1, 2, 4), OfflineItemFilter.VIDEO);
962         OfflineItem item3 = buildItem("3", buildCalendar(2018, 1, 2, 4), OfflineItemFilter.IMAGE);
963         OfflineItem item4 = buildItem("4", buildCalendar(2018, 1, 2, 4), OfflineItemFilter.IMAGE);
964         OfflineItem item5 = buildItem("5", buildCalendar(2018, 1, 2, 4), OfflineItemFilter.IMAGE);
965         when(mSource.getItems()).thenReturn(Arrays.asList(item1, item2, item3, item4, item5));
966         TypeOfflineItemFilter typeOfflineItemFilter = new TypeOfflineItemFilter(mSource);
967         DateOrderedListMutator mutator = createMutatorWithSource(typeOfflineItemFilter);
968         Assert.assertEquals(6, mModel.size());
969 
970         typeOfflineItemFilter.onFilterSelected(Filters.FilterType.VIDEOS);
971         mutator.reload();
972         Assert.assertEquals(3, mModel.size());
973 
974         typeOfflineItemFilter.onFilterSelected(Filters.FilterType.IMAGES);
975         mutator.reload();
976         Assert.assertEquals(4, mModel.size());
977 
978         typeOfflineItemFilter.onFilterSelected(Filters.FilterType.NONE);
979         mutator.reload();
980         Assert.assertEquals(6, mModel.size());
981     }
982 
983     /**
984      * Action                                      List
985      * 1. Set(item1 @ 4:00 1/2/2018 Prefetch,
986      *        item2 @ 5:00 1/1/2018 Prefetch )
987      *                                              [item2     @ 4:00 1/2/2018,
988      *                                               item1     @ 5:00 1/1/2018 ]
989      */
990     @Test
testPrefetchTabBasic()991     public void testPrefetchTabBasic() {
992         OfflineItem item1 = buildPrefetchItem("1", buildCalendar(2018, 1, 2, 4));
993         OfflineItem item2 = buildPrefetchItem("2", buildCalendar(2018, 1, 1, 5));
994         when(mSource.getItems()).thenReturn(Arrays.asList(item1, item2));
995         DateOrderedListMutator mutator = createMutatorWithoutJustNowProvider();
996         mMutationController.onFilterTypeSelected(Filters.FilterType.PREFETCHED);
997         mutator.reload();
998 
999         Assert.assertEquals(2, mModel.size());
1000         assertOfflineItem(mModel.get(0), buildCalendar(2018, 1, 2, 4), item1);
1001         assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 1, 5), item2);
1002     }
1003 
1004     /**
1005      * Action                                      List
1006      * 1. Set(item1 @ 5:00 1/1/2018 Content indexing)
1007      *                                               [item1     1/1/2018]
1008      */
1009     @Test
testContentIndexingStandaloneCards()1010     public void testContentIndexingStandaloneCards() {
1011         OfflineItem item1 =
1012                 buildContentIndexedItem("1", buildCalendar(2018, 1, 1, 5), OfflineItemFilter.PAGE);
1013         when(mSource.getItems()).thenReturn(Collections.singletonList(item1));
1014         DateOrderedListMutator mutator = createMutatorWithoutJustNowProvider();
1015         mMutationController.onFilterTypeSelected(Filters.FilterType.PREFETCHED);
1016         mutator.reload();
1017 
1018         Assert.assertEquals(1, mModel.size());
1019         assertOfflineItem(mModel.get(0), buildCalendar(2018, 1, 1, 5), item1);
1020     }
1021 
1022     /**
1023      * Action                                      List
1024      * 1. Set(item1 @ 5:00 1/1/2018 Content indexing,
1025      *        item2 @ 5:00 1/2/2018 Content indexing,
1026      *        item3 @ 5:00 1/3/2018 Content indexing)
1027      *                                              [item3     1/3/2018,
1028      *                                               item2     1/2/2018,
1029      *                                               item1     1/1/2018]
1030      */
1031     @Test
testContentIndexingDoesNotGroupItemsFromDifferentDays()1032     public void testContentIndexingDoesNotGroupItemsFromDifferentDays() {
1033         OfflineItem item1 =
1034                 buildContentIndexedItem("1", buildCalendar(2018, 1, 1, 5), OfflineItemFilter.AUDIO);
1035         OfflineItem item2 =
1036                 buildContentIndexedItem("2", buildCalendar(2018, 1, 3, 4), OfflineItemFilter.AUDIO);
1037         OfflineItem item3 =
1038                 buildContentIndexedItem("3", buildCalendar(2018, 1, 3, 5), OfflineItemFilter.AUDIO);
1039         when(mSource.getItems()).thenReturn(Arrays.asList(item1, item2, item3));
1040         DateOrderedListMutator mutator = createMutatorWithoutJustNowProvider();
1041         mMutationController.onFilterTypeSelected(Filters.FilterType.PREFETCHED);
1042         mutator.reload();
1043 
1044         Assert.assertEquals(3, mModel.size());
1045         assertOfflineItem(mModel.get(0), buildCalendar(2018, 1, 3, 5), item3);
1046         assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 3, 4), item2);
1047         assertOfflineItem(mModel.get(2), buildCalendar(2018, 1, 1, 5), item1);
1048     }
1049 
1050     /**
1051      * Action                                       List
1052      * 1. Set(item1 @ 1:00 1/4/2018 Content indexing Video,
1053      *        item2 @ 2:00 1/4/2018 Content indexing Video,
1054      *        item3 @ 3:00 1/4/2018 Content indexing Video,
1055      *        item4 @ 4:00 1/4/2018 Content indexing Video)
1056      *                                               [---rounded top divider -----
1057      *                                               HEADER (with domain)
1058      *                                               item4      1/4/2018,
1059      *                                               -----divider------
1060      *                                               item3      1/4/2018,
1061      *                                               -----divider------
1062      *                                               item2      1/4/2018
1063      *                                               -----divider------
1064      *                                               FOOTER (More button)
1065      *                                               -----rounded footer divider ---]
1066      */
1067     @Test
testContentIndexingGroupCardWithAndWithoutFooterAndWithIsolatedCards()1068     public void testContentIndexingGroupCardWithAndWithoutFooterAndWithIsolatedCards() {
1069         OfflineItem item1 =
1070                 buildContentIndexedItem("1", buildCalendar(2018, 1, 4, 1), OfflineItemFilter.VIDEO);
1071         OfflineItem item2 =
1072                 buildContentIndexedItem("2", buildCalendar(2018, 1, 4, 2), OfflineItemFilter.VIDEO);
1073         OfflineItem item3 =
1074                 buildContentIndexedItem("3", buildCalendar(2018, 1, 4, 3), OfflineItemFilter.VIDEO);
1075         OfflineItem item4 =
1076                 buildContentIndexedItem("4", buildCalendar(2018, 1, 4, 4), OfflineItemFilter.VIDEO);
1077         when(mSource.getItems()).thenReturn(Arrays.asList(item1, item2, item3, item4));
1078         DateOrderedListMutator mutator = createMutatorWithoutJustNowProvider();
1079         mMutationController.onFilterTypeSelected(Filters.FilterType.PREFETCHED);
1080         mutator.reload();
1081 
1082         Assert.assertEquals(10, mModel.size());
1083         assertDivider(mModel.get(0), ListItem.CardDividerListItem.Position.TOP);
1084         assertCardHeader(mModel.get(1), buildCalendar(2018, 1, 4, 0), "http://example.com/xyz");
1085         assertOfflineItem(mModel.get(2), buildCalendar(2018, 1, 4, 4), item4);
1086         assertDivider(mModel.get(3), ListItem.CardDividerListItem.Position.MIDDLE);
1087         assertOfflineItem(mModel.get(4), buildCalendar(2018, 1, 4, 3), item3);
1088         assertDivider(mModel.get(5), ListItem.CardDividerListItem.Position.MIDDLE);
1089         assertOfflineItem(mModel.get(6), buildCalendar(2018, 1, 4, 2), item2);
1090         assertDivider(mModel.get(7), ListItem.CardDividerListItem.Position.MIDDLE);
1091         assertCardFooter(mModel.get(8));
1092         assertDivider(mModel.get(9), ListItem.CardDividerListItem.Position.BOTTOM);
1093 
1094         // Remove one item. Now there should be only 3 items. The footer should be gone and get
1095         // replaced by bottom curve.
1096         when(mSource.getItems()).thenReturn(Arrays.asList(item2, item3, item4));
1097         mutator.onItemsRemoved(Collections.singletonList(item1));
1098 
1099         Assert.assertEquals(8, mModel.size());
1100         assertOfflineItem(mModel.get(6), buildCalendar(2018, 1, 4, 2), item2);
1101         assertDivider(mModel.get(7), ListItem.CardDividerListItem.Position.BOTTOM);
1102 
1103         // Remove one more item. Now there should be only 2 items. Now they shouldn't be grouped at
1104         // all.
1105         when(mSource.getItems()).thenReturn(Arrays.asList(item3, item4));
1106         mutator.onItemsRemoved(Collections.singletonList(item2));
1107         Assert.assertEquals(2, mModel.size());
1108     }
1109 
buildCalendar(int year, int month, int dayOfMonth, int hourOfDay)1110     private static Calendar buildCalendar(int year, int month, int dayOfMonth, int hourOfDay) {
1111         Calendar calendar = CalendarFactory.get();
1112         calendar.set(year, month, dayOfMonth, hourOfDay, 0);
1113         return calendar;
1114     }
1115 
buildItem( String id, Calendar calendar, @OfflineItemFilter int filter)1116     private static OfflineItem buildItem(
1117             String id, Calendar calendar, @OfflineItemFilter int filter) {
1118         OfflineItem item = new OfflineItem();
1119         item.id.namespace = "test";
1120         item.id.id = id;
1121         item.creationTimeMs = calendar.getTimeInMillis();
1122         item.filter = filter;
1123         return item;
1124     }
1125 
buildPrefetchItem(String id, Calendar calendar)1126     private static OfflineItem buildPrefetchItem(String id, Calendar calendar) {
1127         OfflineItem item = new OfflineItem();
1128         item.id.namespace = "test";
1129         item.id.id = id;
1130         item.creationTimeMs = calendar.getTimeInMillis();
1131         item.filter = OfflineItemFilter.PAGE;
1132         item.isSuggested = true;
1133         return item;
1134     }
1135 
buildContentIndexedItem( String id, Calendar calendar, @OfflineItemFilter int filter)1136     private static OfflineItem buildContentIndexedItem(
1137             String id, Calendar calendar, @OfflineItemFilter int filter) {
1138         OfflineItem item = new OfflineItem();
1139         item.id.namespace = LegacyHelpers.LEGACY_CONTENT_INDEX_NAMESPACE;
1140         item.id.id = id;
1141         item.isSuggested = true;
1142         item.creationTimeMs = calendar.getTimeInMillis();
1143         item.filter = filter;
1144         item.pageUrl = "http://example.com/xyz";
1145         return item;
1146     }
1147 
createConfig()1148     private DownloadManagerUiConfig createConfig() {
1149         return new DownloadManagerUiConfig.Builder()
1150                 .setUseNewDownloadPath(true)
1151                 .setSupportsGrouping(true)
1152                 .build();
1153     }
1154 
buildJustNowProvider(Date overrideNow)1155     private JustNowProvider buildJustNowProvider(Date overrideNow) {
1156         JustNowProvider justNowProvider = new JustNowProvider(createConfig()) {
1157             @Override
1158             protected Date now() {
1159                 return overrideNow;
1160             }
1161         };
1162         return justNowProvider;
1163     }
1164 
createMutatorWithSource(OfflineItemFilterSource source)1165     private DateOrderedListMutator createMutatorWithSource(OfflineItemFilterSource source) {
1166         DownloadManagerUiConfig config = createConfig();
1167         JustNowProvider justNowProvider = new JustNowProvider(config);
1168         DateOrderedListMutator mutator =
1169                 new DateOrderedListMutator(source, mModel, justNowProvider);
1170         mMutationController = new ListMutationController(config, justNowProvider, mutator, mModel);
1171         return mutator;
1172     }
1173 
createMutatorWithoutJustNowProvider()1174     private DateOrderedListMutator createMutatorWithoutJustNowProvider() {
1175         DownloadManagerUiConfig config = createConfig();
1176         JustNowProvider justNowProvider = new JustNowProvider(config) {
1177             @Override
1178             public boolean isJustNowItem(OfflineItem item) {
1179                 return false;
1180             }
1181         };
1182         DateOrderedListMutator mutator =
1183                 new DateOrderedListMutator(mSource, mModel, justNowProvider);
1184         mMutationController = new ListMutationController(config, justNowProvider, mutator, mModel);
1185         return mutator;
1186     }
1187 
createMutatorWithJustNowProvider()1188     private DateOrderedListMutator createMutatorWithJustNowProvider() {
1189         JustNowProvider justNowProvider = new JustNowProvider(createConfig());
1190         return createMutatorWithJustNowProvider(justNowProvider);
1191     }
1192 
createMutatorWithJustNowProvider( JustNowProvider justNowProvider)1193     private DateOrderedListMutator createMutatorWithJustNowProvider(
1194             JustNowProvider justNowProvider) {
1195         DateOrderedListMutator mutator =
1196                 new DateOrderedListMutator(mSource, mModel, justNowProvider);
1197         mMutationController =
1198                 new ListMutationController(createConfig(), justNowProvider, mutator, mModel);
1199         return mutator;
1200     }
1201 
assertDatesAreEqual(Date date, Calendar calendar)1202     private static void assertDatesAreEqual(Date date, Calendar calendar) {
1203         Calendar calendar2 = CalendarFactory.get();
1204         calendar2.setTime(date);
1205         Assert.assertEquals(calendar.getTimeInMillis(), calendar2.getTimeInMillis());
1206     }
1207 
assertOfflineItem( ListItem item, Calendar calendar, OfflineItem offlineItem)1208     private static void assertOfflineItem(
1209             ListItem item, Calendar calendar, OfflineItem offlineItem) {
1210         Assert.assertTrue(item instanceof OfflineItemListItem);
1211         assertDatesAreEqual(((OfflineItemListItem) item).date, calendar);
1212         Assert.assertEquals(OfflineItemListItem.generateStableId(offlineItem), item.stableId);
1213         Assert.assertEquals(offlineItem, ((OfflineItemListItem) item).item);
1214     }
1215 
assertSectionHeader(ListItem item, Calendar calendar, boolean showDivider)1216     private static void assertSectionHeader(ListItem item, Calendar calendar, boolean showDivider) {
1217         Assert.assertTrue(item instanceof SectionHeaderListItem);
1218         SectionHeaderListItem sectionHeader = (SectionHeaderListItem) item;
1219         assertDatesAreEqual(sectionHeader.date, calendar);
1220         Assert.assertEquals(SectionHeaderListItem.generateStableId(
1221                                     SectionHeaderType.DATE, calendar.getTimeInMillis()),
1222                 item.stableId);
1223         Assert.assertEquals(SectionHeaderListItem.generateStableId(
1224                                     SectionHeaderType.JUST_NOW, calendar.getTimeInMillis()),
1225                 StableIds.JUST_NOW_SECTION);
1226         Assert.assertEquals(SectionHeaderListItem.generateStableId(
1227                                     SectionHeaderType.SCHEDULED_LATER, calendar.getTimeInMillis()),
1228                 StableIds.SCHEDULE_LATER_SECTION);
1229         Assert.assertEquals(sectionHeader.showTopDivider, showDivider);
1230     }
1231 
assertJustNowSection(ListItem item)1232     private static void assertJustNowSection(ListItem item) {
1233         Assert.assertTrue(item instanceof SectionHeaderListItem);
1234         SectionHeaderListItem sectionHeader = (SectionHeaderListItem) item;
1235         Assert.assertEquals(SectionHeaderType.JUST_NOW, sectionHeader.type);
1236         Assert.assertEquals(StableIds.JUST_NOW_SECTION, item.stableId);
1237     }
1238 
assertScheduledLaterHeader(ListItem item)1239     private static void assertScheduledLaterHeader(ListItem item) {
1240         Assert.assertTrue(item instanceof SectionHeaderListItem);
1241         SectionHeaderListItem sectionHeader = (SectionHeaderListItem) item;
1242         Assert.assertEquals("Schedule for later section is at the top of the list", false,
1243                 sectionHeader.showTopDivider);
1244         Assert.assertEquals(SectionHeaderType.SCHEDULED_LATER, sectionHeader.type);
1245         Assert.assertEquals(StableIds.SCHEDULE_LATER_SECTION, item.stableId);
1246     }
1247 
assertCardHeader(ListItem item, Calendar calendar, String domain)1248     private static void assertCardHeader(ListItem item, Calendar calendar, String domain) {
1249         Assert.assertTrue(item instanceof ListItem.CardHeaderListItem);
1250         ListItem.CardHeaderListItem headerItem = (ListItem.CardHeaderListItem) item;
1251         assertDatesAreEqual(headerItem.dateAndDomain.first, calendar);
1252         Assert.assertEquals(headerItem.dateAndDomain.second, domain);
1253     }
1254 
assertCardFooter(ListItem item)1255     private static void assertCardFooter(ListItem item) {
1256         Assert.assertTrue(item instanceof ListItem.CardFooterListItem);
1257     }
1258 
assertDivider( ListItem item, ListItem.CardDividerListItem.Position position)1259     private static void assertDivider(
1260             ListItem item, ListItem.CardDividerListItem.Position position) {
1261         Assert.assertTrue(item instanceof ListItem.CardDividerListItem);
1262         Assert.assertEquals(((ListItem.CardDividerListItem) item).position, position);
1263     }
1264 }
1265