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