1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.settings.dashboard;
18 
19 import android.support.annotation.NonNull;
20 import android.support.v7.util.DiffUtil;
21 import android.support.v7.util.ListUpdateCallback;
22 import com.android.settings.TestConfig;
23 import com.android.settings.dashboard.conditional.AirplaneModeCondition;
24 import com.android.settings.dashboard.conditional.Condition;
25 import com.android.settingslib.drawer.DashboardCategory;
26 import com.android.settingslib.drawer.Tile;
27 import org.junit.Before;
28 import org.junit.Test;
29 import org.junit.runner.RunWith;
30 import org.mockito.Mock;
31 import org.mockito.MockitoAnnotations;
32 import org.robolectric.RobolectricTestRunner;
33 import org.robolectric.annotation.Config;
34 
35 import java.util.ArrayList;
36 import java.util.Collections;
37 import java.util.List;
38 
39 import static com.google.common.truth.Truth.assertThat;
40 import static org.mockito.Mockito.mock;
41 import static org.mockito.Mockito.when;
42 
43 @RunWith(RobolectricTestRunner.class)
44 @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
45 public class DashboardDataTest {
46     private static final String TEST_SUGGESTION_TITLE = "Use fingerprint";
47     private static final String TEST_CATEGORY_TILE_TITLE = "Display";
48 
49     private DashboardData mDashboardDataWithOneConditions;
50     private DashboardData mDashboardDataWithTwoConditions;
51     private DashboardData mDashboardDataWithNoItems;
52     @Mock
53     private Tile mTestCategoryTile;
54     @Mock
55     private Tile mTestSuggestion;
56     @Mock
57     private DashboardCategory mDashboardCategory;
58     @Mock
59     private Condition mTestCondition;
60     @Mock
61     private Condition mSecondCondition; // condition used to test insert in DiffUtil
62 
63     @Before
SetUp()64     public void SetUp() {
65         MockitoAnnotations.initMocks(this);
66 
67         // Build suggestions
68         final List<Tile> suggestions = new ArrayList<>();
69         mTestSuggestion.title = TEST_SUGGESTION_TITLE;
70         suggestions.add(mTestSuggestion);
71 
72         // Build oneItemConditions
73         final List<Condition> oneItemConditions = new ArrayList<>();
74         when(mTestCondition.shouldShow()).thenReturn(true);
75         oneItemConditions.add(mTestCondition);
76 
77         // Build twoItemConditions
78         final List<Condition> twoItemsConditions = new ArrayList<>();
79         when(mSecondCondition.shouldShow()).thenReturn(true);
80         twoItemsConditions.add(mTestCondition);
81         twoItemsConditions.add(mSecondCondition);
82 
83         // Build categories
84         final List<DashboardCategory> categories = new ArrayList<>();
85         mTestCategoryTile.title = TEST_CATEGORY_TILE_TITLE;
86         mDashboardCategory.title = "test";
87         mDashboardCategory.tiles = new ArrayList<>();
88         mDashboardCategory.tiles.add(mTestCategoryTile);
89         categories.add(mDashboardCategory);
90 
91         // Build DashboardData
92         mDashboardDataWithOneConditions = new DashboardData.Builder()
93                 .setConditions(oneItemConditions)
94                 .setCategories(categories)
95                 .setSuggestions(suggestions)
96                 .build();
97 
98         mDashboardDataWithTwoConditions = new DashboardData.Builder()
99                 .setConditions(twoItemsConditions)
100                 .setCategories(categories)
101                 .setSuggestions(suggestions)
102                 .build();
103 
104         mDashboardDataWithNoItems = new DashboardData.Builder()
105                 .setConditions(null)
106                 .setCategories(null)
107                 .setSuggestions(null)
108                 .build();
109     }
110 
111     @Test
testBuildItemsData_containsAllData()112     public void testBuildItemsData_containsAllData() {
113         final DashboardData.SuggestionHeaderData data =
114                 new DashboardData.SuggestionHeaderData(false, 1, 0);
115         final Object[] expectedObjects = {mTestCondition, null, data, mTestSuggestion,
116                 mDashboardCategory, mTestCategoryTile};
117         final int expectedSize = expectedObjects.length;
118 
119         assertThat(mDashboardDataWithOneConditions.getItemList().size())
120                 .isEqualTo(expectedSize);
121         for (int i = 0; i < expectedSize; i++) {
122             if (mDashboardDataWithOneConditions.getItemEntityByPosition(i)
123                     instanceof DashboardData.SuggestionHeaderData) {
124                 // SuggestionHeaderData is created inside when build, we can only use isEqualTo
125                 assertThat(mDashboardDataWithOneConditions.getItemEntityByPosition(i))
126                         .isEqualTo(expectedObjects[i]);
127             } else {
128                 assertThat(mDashboardDataWithOneConditions.getItemEntityByPosition(i))
129                         .isSameAs(expectedObjects[i]);
130             }
131         }
132     }
133 
134     @Test
testGetPositionByEntity_selfInstance_returnPositionFound()135     public void testGetPositionByEntity_selfInstance_returnPositionFound() {
136         final int position = mDashboardDataWithOneConditions
137                 .getPositionByEntity(mTestCondition);
138         assertThat(position).isNotEqualTo(DashboardData.POSITION_NOT_FOUND);
139     }
140 
141     @Test
testGetPositionByEntity_notExisted_returnNotFound()142     public void testGetPositionByEntity_notExisted_returnNotFound() {
143         final Condition condition = mock(AirplaneModeCondition.class);
144         final int position = mDashboardDataWithOneConditions.getPositionByEntity(condition);
145         assertThat(position).isEqualTo(DashboardData.POSITION_NOT_FOUND);
146     }
147 
148     @Test
testGetPositionByTile_selfInstance_returnPositionFound()149     public void testGetPositionByTile_selfInstance_returnPositionFound() {
150         final int position = mDashboardDataWithOneConditions
151                 .getPositionByTile(mTestCategoryTile);
152         assertThat(position).isNotEqualTo(DashboardData.POSITION_NOT_FOUND);
153     }
154 
155     @Test
testGetPositionByTile_equalTitle_returnPositionFound()156     public void testGetPositionByTile_equalTitle_returnPositionFound() {
157         final Tile tile = mock(Tile.class);
158         tile.title = TEST_CATEGORY_TILE_TITLE;
159         final int position = mDashboardDataWithOneConditions.getPositionByTile(tile);
160         assertThat(position).isNotEqualTo(DashboardData.POSITION_NOT_FOUND);
161     }
162 
163     @Test
testGetPositionByTile_notExisted_returnNotFound()164     public void testGetPositionByTile_notExisted_returnNotFound() {
165         final Tile tile = mock(Tile.class);
166         tile.title = "";
167         final int position = mDashboardDataWithOneConditions.getPositionByTile(tile);
168         assertThat(position).isEqualTo(DashboardData.POSITION_NOT_FOUND);
169     }
170 
171     @Test
testDiffUtil_DataEqual_noResultData()172     public void testDiffUtil_DataEqual_noResultData() {
173         List<ListUpdateResult.ResultData> testResultData = new ArrayList<>();
174         testDiffUtil(mDashboardDataWithOneConditions,
175                 mDashboardDataWithOneConditions, testResultData);
176     }
177 
178     @Test
testDiffUtil_InsertOneCondition_ResultDataOneInserted()179     public void testDiffUtil_InsertOneCondition_ResultDataOneInserted() {
180         //Build testResultData
181         final List<ListUpdateResult.ResultData> testResultData = new ArrayList<>();
182         testResultData.add(new ListUpdateResult.ResultData(
183                 ListUpdateResult.ResultData.TYPE_OPERATION_INSERT, 1, 1));
184 
185         testDiffUtil(mDashboardDataWithOneConditions,
186                 mDashboardDataWithTwoConditions, testResultData);
187     }
188 
189     @Test
testDiffUtil_DeleteAllData_ResultDataOneDeleted()190     public void testDiffUtil_DeleteAllData_ResultDataOneDeleted() {
191         //Build testResultData
192         final List<ListUpdateResult.ResultData> testResultData = new ArrayList<>();
193         testResultData.add(new ListUpdateResult.ResultData(
194                 ListUpdateResult.ResultData.TYPE_OPERATION_REMOVE, 0, 6));
195 
196         testDiffUtil(mDashboardDataWithOneConditions, mDashboardDataWithNoItems, testResultData);
197     }
198 
199     @Test
testPayload_ItemConditionCard_returnNotNull()200     public void testPayload_ItemConditionCard_returnNotNull() {
201         final DashboardData.ItemsDataDiffCallback callback = new DashboardData
202                 .ItemsDataDiffCallback(
203                 mDashboardDataWithOneConditions.getItemList(),
204                 mDashboardDataWithOneConditions.getItemList());
205 
206         // Item in position 0 is condition card, which payload should not be null
207         assertThat(callback.getChangePayload(0, 0)).isNotEqualTo(null);
208     }
209 
210     @Test
testPayload_ItemNotConditionCard_returnNull()211     public void testPayload_ItemNotConditionCard_returnNull() {
212         final DashboardData.ItemsDataDiffCallback callback = new DashboardData
213                 .ItemsDataDiffCallback(
214                 mDashboardDataWithOneConditions.getItemList(),
215                 mDashboardDataWithOneConditions.getItemList());
216 
217         // Only item in position 0 is condition card, so others' payload should be null
218         for (int i = 1; i < mDashboardDataWithOneConditions.getItemList().size(); i++) {
219             assertThat(callback.getChangePayload(i, i)).isEqualTo(null);
220         }
221 
222     }
223 
224     /**
225      * Test when using the
226      * {@link com.android.settings.dashboard.DashboardData.ItemsDataDiffCallback}
227      * to transfer List from {@paramref baseDashboardData} to {@paramref diffDashboardData}, whether
228      * the transform data result is equals to {@paramref testResultData}
229      * <p>
230      * The steps are described below:
231      * 1. Calculate a {@link android.support.v7.util.DiffUtil.DiffResult} from
232      * {@paramref baseDashboardData} to {@paramref diffDashboardData}
233      * <p>
234      * 2. Dispatch the {@link android.support.v7.util.DiffUtil.DiffResult} calculated from step 1
235      * into {@link ListUpdateResult}
236      * <p>
237      * 3. Get result data(a.k.a. baseResultData) from {@link ListUpdateResult} and compare it to
238      * {@paramref testResultData}
239      * <p>
240      * Because baseResultData and {@paramref testResultData} don't have sequence. When do the
241      * comparison, we will sort them first and then compare the inside data from them one by one.
242      *
243      * @param baseDashboardData
244      * @param diffDashboardData
245      * @param testResultData
246      */
testDiffUtil(DashboardData baseDashboardData, DashboardData diffDashboardData, List<ListUpdateResult.ResultData> testResultData)247     private void testDiffUtil(DashboardData baseDashboardData, DashboardData diffDashboardData,
248             List<ListUpdateResult.ResultData> testResultData) {
249         final DiffUtil.DiffResult diffUtilResult = DiffUtil.calculateDiff(
250                 new DashboardData.ItemsDataDiffCallback(
251                         baseDashboardData.getItemList(), diffDashboardData.getItemList()));
252 
253         // Dispatch to listUpdateResult, then listUpdateResult will have result data
254         final ListUpdateResult listUpdateResult = new ListUpdateResult();
255         diffUtilResult.dispatchUpdatesTo(listUpdateResult);
256 
257         final List<ListUpdateResult.ResultData> baseResultData = listUpdateResult.getResultData();
258         assertThat(testResultData.size()).isEqualTo(baseResultData.size());
259 
260         // Sort them so we can compare them one by one using a for loop
261         Collections.sort(baseResultData);
262         Collections.sort(testResultData);
263         final int size = baseResultData.size();
264         for (int i = 0; i < size; i++) {
265             // Refer to equals method in ResultData
266             assertThat(baseResultData.get(i)).isEqualTo(testResultData.get(i));
267         }
268     }
269 
270     /**
271      * This class contains the result about how the changes made to convert one
272      * list to another list. It implements ListUpdateCallback to record the result data.
273      */
274     private static class ListUpdateResult implements ListUpdateCallback {
275         final private List<ResultData> mResultData;
276 
ListUpdateResult()277         public ListUpdateResult() {
278             mResultData = new ArrayList<>();
279         }
280 
getResultData()281         public List<ResultData> getResultData() {
282             return mResultData;
283         }
284 
285         @Override
onInserted(int position, int count)286         public void onInserted(int position, int count) {
287             mResultData.add(new ResultData(ResultData.TYPE_OPERATION_INSERT, position, count));
288         }
289 
290         @Override
onRemoved(int position, int count)291         public void onRemoved(int position, int count) {
292             mResultData.add(new ResultData(ResultData.TYPE_OPERATION_REMOVE, position, count));
293         }
294 
295         @Override
onMoved(int fromPosition, int toPosition)296         public void onMoved(int fromPosition, int toPosition) {
297             mResultData.add(
298                     new ResultData(ResultData.TYPE_OPERATION_MOVE, fromPosition, toPosition));
299         }
300 
301         @Override
onChanged(int position, int count, Object payload)302         public void onChanged(int position, int count, Object payload) {
303             mResultData.add(new ResultData(ResultData.TYPE_OPERATION_CHANGE, position, count));
304         }
305 
306         /**
307          * This class contains general type and field to record the operation data generated
308          * in {@link ListUpdateCallback}. Please refer to {@link ListUpdateCallback} for more info.
309          * <p>
310          * The following are examples about the data stored in this class:
311          * <p>
312          * "The data starts from position(arg1) with count number(arg2) is changed(operation)"
313          * or "The data is moved(operation) from position1(arg1) to position2(arg2)"
314          */
315         private static class ResultData implements Comparable<ResultData> {
316             public static final int TYPE_OPERATION_INSERT = 0;
317             public static final int TYPE_OPERATION_REMOVE = 1;
318             public static final int TYPE_OPERATION_MOVE = 2;
319             public static final int TYPE_OPERATION_CHANGE = 3;
320 
321             public final int operation;
322             public final int arg1;
323             public final int arg2;
324 
ResultData(int operation, int arg1, int arg2)325             public ResultData(int operation, int arg1, int arg2) {
326                 this.operation = operation;
327                 this.arg1 = arg1;
328                 this.arg2 = arg2;
329             }
330 
331             @Override
equals(Object obj)332             public boolean equals(Object obj) {
333                 if (this == obj) {
334                     return true;
335                 }
336 
337                 if (!(obj instanceof ResultData)) {
338                     return false;
339                 }
340 
341                 ResultData targetData = (ResultData) obj;
342 
343                 return operation == targetData.operation && arg1 == targetData.arg1
344                         && arg2 == targetData.arg2;
345             }
346 
347             @Override
compareTo(@onNull ResultData resultData)348             public int compareTo(@NonNull ResultData resultData) {
349                 if (this.operation != resultData.operation) {
350                     return operation - resultData.operation;
351                 }
352 
353                 if (arg1 != resultData.arg1) {
354                     return arg1 - resultData.arg1;
355                 }
356 
357                 return arg2 - resultData.arg2;
358             }
359         }
360     }
361 }
362