1 /* 2 * Copyright (C) 2015 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.documentsui.selection; 18 import static org.junit.Assert.assertFalse; 19 20 import android.support.test.filters.SmallTest; 21 import android.support.test.runner.AndroidJUnit4; 22 import android.util.SparseBooleanArray; 23 24 import com.android.documentsui.selection.SelectionHelper.SelectionPredicate; 25 import com.android.documentsui.selection.testing.SelectionProbe; 26 import com.android.documentsui.selection.testing.TestAdapter; 27 import com.android.documentsui.selection.testing.TestSelectionObserver; 28 import com.android.documentsui.selection.testing.TestStableIdProvider; 29 30 import org.junit.Before; 31 import org.junit.Test; 32 import org.junit.runner.RunWith; 33 34 import java.util.ArrayList; 35 import java.util.HashSet; 36 import java.util.List; 37 import java.util.Set; 38 39 @RunWith(AndroidJUnit4.class) 40 @SmallTest 41 public class DefaultSelectionHelperTest { 42 43 private List<String> mItems; 44 private Set<String> mIgnored; 45 private TestAdapter mAdapter; 46 private DefaultSelectionHelper mHelper; 47 private TestSelectionObserver mListener; 48 private SelectionProbe mSelection; 49 50 @Before 51 public void setUp() throws Exception { 52 mIgnored = new HashSet<>(); 53 mItems = TestAdapter.createItemList(100); 54 mListener = new TestSelectionObserver(); 55 mAdapter = new TestAdapter(); 56 mAdapter.updateTestModelIds(mItems); 57 58 SelectionPredicate canSelect = new SelectionPredicate() { 59 60 @Override 61 public boolean canSetStateForId(String id, boolean nextState) { 62 return !nextState || !mIgnored.contains(id); 63 } 64 65 @Override 66 public boolean canSetStateAtPosition(int position, boolean nextState) { 67 throw new UnsupportedOperationException("Not implemented."); 68 } 69 }; 70 mHelper = new DefaultSelectionHelper( 71 DefaultSelectionHelper.MODE_MULTIPLE, 72 mAdapter, 73 new TestStableIdProvider(mAdapter), 74 canSelect); 75 76 mHelper.addObserver(mListener); 77 78 mSelection = new SelectionProbe(mHelper, mListener); 79 80 mIgnored.clear(); 81 } 82 83 @Test 84 public void testSelect() { 85 mHelper.select(mItems.get(7)); 86 87 mSelection.assertSelection(7); 88 } 89 90 @Test 91 public void testDeselect() { 92 mHelper.select(mItems.get(7)); 93 mHelper.deselect(mItems.get(7)); 94 95 mSelection.assertNoSelection(); 96 } 97 98 @Test 99 public void testSelection_DoNothingOnUnselectableItem() { 100 mIgnored.add(mItems.get(7)); 101 boolean selected = mHelper.select(mItems.get(7)); 102 103 assertFalse(selected); 104 mSelection.assertNoSelection(); 105 } 106 107 @Test 108 public void testSelect_NotifiesListenersOfChange() { 109 mHelper.select(mItems.get(7)); 110 111 mListener.assertSelectionChanged(); 112 } 113 114 @Test 115 public void testSelect_NotifiesAdapterOfSelect() { 116 mHelper.select(mItems.get(7)); 117 118 mAdapter.assertNotifiedOfSelectionChange(7); 119 } 120 121 @Test 122 public void testSelect_NotifiesAdapterOfDeselect() { 123 mHelper.select(mItems.get(7)); 124 mAdapter.resetSelectionNotifications(); 125 mHelper.deselect(mItems.get(7)); 126 mAdapter.assertNotifiedOfSelectionChange(7); 127 } 128 129 @Test 130 public void testDeselect_NotifiesSelectionChanged() { 131 mHelper.select(mItems.get(7)); 132 mHelper.deselect(mItems.get(7)); 133 134 mListener.assertSelectionChanged(); 135 } 136 137 @Test 138 public void testSelection_PersistsOnUpdate() { 139 mHelper.select(mItems.get(7)); 140 mAdapter.updateTestModelIds(mItems); 141 142 mSelection.assertSelection(7); 143 } 144 145 @Test 146 public void testSelection_IntersectsWithNewDataSet() { 147 mHelper.select(mItems.get(99)); 148 mHelper.select(mItems.get(7)); 149 150 mAdapter.updateTestModelIds(TestAdapter.createItemList(50)); 151 152 mSelection.assertSelection(7); 153 } 154 155 @Test 156 public void testSetItemsSelected() { 157 mHelper.setItemsSelected(getStringIds(6, 7, 8), true); 158 159 mSelection.assertRangeSelected(6, 8); 160 } 161 162 @Test 163 public void testSetItemsSelected_SkipUnselectableItem() { 164 mIgnored.add(mItems.get(7)); 165 166 mHelper.setItemsSelected(getStringIds(6, 7, 8), true); 167 168 mSelection.assertSelected(6); 169 mSelection.assertNotSelected(7); 170 mSelection.assertSelected(8); 171 } 172 173 @Test 174 public void testRangeSelection() { 175 mHelper.startRange(15); 176 mHelper.extendRange(19); 177 mSelection.assertRangeSelection(15, 19); 178 } 179 180 @Test 181 public void testRangeSelection_SkipUnselectableItem() { 182 mIgnored.add(mItems.get(17)); 183 184 mHelper.startRange(15); 185 mHelper.extendRange(19); 186 187 mSelection.assertRangeSelected(15, 16); 188 mSelection.assertNotSelected(17); 189 mSelection.assertRangeSelected(18, 19); 190 } 191 192 @Test 193 public void testRangeSelection_snapExpand() { 194 mHelper.startRange(15); 195 mHelper.extendRange(19); 196 mHelper.extendRange(27); 197 mSelection.assertRangeSelection(15, 27); 198 } 199 200 @Test 201 public void testRangeSelection_snapContract() { 202 mHelper.startRange(15); 203 mHelper.extendRange(27); 204 mHelper.extendRange(19); 205 mSelection.assertRangeSelection(15, 19); 206 } 207 208 @Test 209 public void testRangeSelection_snapInvert() { 210 mHelper.startRange(15); 211 mHelper.extendRange(27); 212 mHelper.extendRange(3); 213 mSelection.assertRangeSelection(3, 15); 214 } 215 216 @Test 217 public void testRangeSelection_multiple() { 218 mHelper.startRange(15); 219 mHelper.extendRange(27); 220 mHelper.endRange(); 221 mHelper.startRange(42); 222 mHelper.extendRange(57); 223 mSelection.assertSelectionSize(29); 224 mSelection.assertRangeSelected(15, 27); 225 mSelection.assertRangeSelected(42, 57); 226 } 227 228 @Test 229 public void testProvisionalRangeSelection() { 230 mHelper.startRange(13); 231 mHelper.extendProvisionalRange(15); 232 mSelection.assertRangeSelection(13, 15); 233 mHelper.getSelection().mergeProvisionalSelection(); 234 mHelper.endRange(); 235 mSelection.assertSelectionSize(3); 236 } 237 238 @Test 239 public void testProvisionalRangeSelection_endEarly() { 240 mHelper.startRange(13); 241 mHelper.extendProvisionalRange(15); 242 mSelection.assertRangeSelection(13, 15); 243 244 mHelper.endRange(); 245 // If we end range selection prematurely for provision selection, nothing should be selected 246 // except the first item 247 mSelection.assertSelectionSize(1); 248 } 249 250 @Test 251 public void testProvisionalRangeSelection_snapExpand() { 252 mHelper.startRange(13); 253 mHelper.extendProvisionalRange(15); 254 mSelection.assertRangeSelection(13, 15); 255 mHelper.getSelection().mergeProvisionalSelection(); 256 mHelper.extendRange(18); 257 mSelection.assertRangeSelection(13, 18); 258 } 259 260 @Test 261 public void testCombinationRangeSelection_IntersectsOldSelection() { 262 mHelper.startRange(13); 263 mHelper.extendRange(15); 264 mSelection.assertRangeSelection(13, 15); 265 266 mHelper.startRange(11); 267 mHelper.extendProvisionalRange(18); 268 mSelection.assertRangeSelected(11, 18); 269 mHelper.endRange(); 270 mSelection.assertRangeSelected(13, 15); 271 mSelection.assertRangeSelected(11, 11); 272 mSelection.assertSelectionSize(4); 273 } 274 275 @Test 276 public void testProvisionalSelection() { 277 Selection s = mHelper.getSelection(); 278 mSelection.assertNoSelection(); 279 280 // Mimicking band selection case -- BandController notifies item callback by itself. 281 mListener.onItemStateChanged(mItems.get(1), true); 282 mListener.onItemStateChanged(mItems.get(2), true); 283 284 SparseBooleanArray provisional = new SparseBooleanArray(); 285 provisional.append(1, true); 286 provisional.append(2, true); 287 s.setProvisionalSelection(getItemIds(provisional)); 288 mSelection.assertSelection(1, 2); 289 } 290 291 @Test 292 public void testProvisionalSelection_Replace() { 293 Selection s = mHelper.getSelection(); 294 295 // Mimicking band selection case -- BandController notifies item callback by itself. 296 mListener.onItemStateChanged(mItems.get(1), true); 297 mListener.onItemStateChanged(mItems.get(2), true); 298 SparseBooleanArray provisional = new SparseBooleanArray(); 299 provisional.append(1, true); 300 provisional.append(2, true); 301 s.setProvisionalSelection(getItemIds(provisional)); 302 303 mListener.onItemStateChanged(mItems.get(1), false); 304 mListener.onItemStateChanged(mItems.get(2), false); 305 provisional.clear(); 306 307 mListener.onItemStateChanged(mItems.get(3), true); 308 mListener.onItemStateChanged(mItems.get(4), true); 309 provisional.append(3, true); 310 provisional.append(4, true); 311 s.setProvisionalSelection(getItemIds(provisional)); 312 mSelection.assertSelection(3, 4); 313 } 314 315 @Test 316 public void testProvisionalSelection_IntersectsExistingProvisionalSelection() { 317 Selection s = mHelper.getSelection(); 318 319 // Mimicking band selection case -- BandController notifies item callback by itself. 320 mListener.onItemStateChanged(mItems.get(1), true); 321 mListener.onItemStateChanged(mItems.get(2), true); 322 SparseBooleanArray provisional = new SparseBooleanArray(); 323 provisional.append(1, true); 324 provisional.append(2, true); 325 s.setProvisionalSelection(getItemIds(provisional)); 326 327 mListener.onItemStateChanged(mItems.get(1), false); 328 mListener.onItemStateChanged(mItems.get(2), false); 329 provisional.clear(); 330 331 mListener.onItemStateChanged(mItems.get(1), true); 332 provisional.append(1, true); 333 s.setProvisionalSelection(getItemIds(provisional)); 334 mSelection.assertSelection(1); 335 } 336 337 @Test 338 public void testProvisionalSelection_Apply() { 339 Selection s = mHelper.getSelection(); 340 341 // Mimicking band selection case -- BandController notifies item callback by itself. 342 mListener.onItemStateChanged(mItems.get(1), true); 343 mListener.onItemStateChanged(mItems.get(2), true); 344 SparseBooleanArray provisional = new SparseBooleanArray(); 345 provisional.append(1, true); 346 provisional.append(2, true); 347 s.setProvisionalSelection(getItemIds(provisional)); 348 s.mergeProvisionalSelection(); 349 350 mSelection.assertSelection(1, 2); 351 } 352 353 @Test 354 public void testProvisionalSelection_Cancel() { 355 mHelper.select(mItems.get(1)); 356 mHelper.select(mItems.get(2)); 357 Selection s = mHelper.getSelection(); 358 359 SparseBooleanArray provisional = new SparseBooleanArray(); 360 provisional.append(3, true); 361 provisional.append(4, true); 362 s.setProvisionalSelection(getItemIds(provisional)); 363 s.clearProvisionalSelection(); 364 365 // Original selection should remain. 366 mSelection.assertSelection(1, 2); 367 } 368 369 @Test 370 public void testProvisionalSelection_IntersectsAppliedSelection() { 371 mHelper.select(mItems.get(1)); 372 mHelper.select(mItems.get(2)); 373 Selection s = mHelper.getSelection(); 374 375 // Mimicking band selection case -- BandController notifies item callback by itself. 376 mListener.onItemStateChanged(mItems.get(3), true); 377 SparseBooleanArray provisional = new SparseBooleanArray(); 378 provisional.append(2, true); 379 provisional.append(3, true); 380 s.setProvisionalSelection(getItemIds(provisional)); 381 mSelection.assertSelection(1, 2, 3); 382 } 383 384 @Test 385 public void testObserverOnChanged_NotifiesListenersOfChange() { 386 mAdapter.notifyDataSetChanged(); 387 388 mListener.assertSelectionChanged(); 389 } 390 391 private Set<String> getItemIds(SparseBooleanArray selection) { 392 Set<String> ids = new HashSet<>(); 393 394 int count = selection.size(); 395 for (int i = 0; i < count; ++i) { 396 ids.add(mItems.get(selection.keyAt(i))); 397 } 398 399 return ids; 400 } 401 402 private Iterable<String> getStringIds(int... ids) { 403 List<String> stringIds = new ArrayList<>(ids.length); 404 for (int id : ids) { 405 stringIds.add(mItems.get(id)); 406 } 407 return stringIds; 408 } 409 } 410