1 /*
   2  * Copyright (c) 2010, 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package javafx.scene.control;
  27 
  28 import static org.junit.Assert.assertEquals;
  29 import static org.junit.Assert.assertFalse;
  30 import static org.junit.Assert.assertNotNull;
  31 import static org.junit.Assert.assertNull;
  32 import static org.junit.Assert.assertSame;
  33 import static org.junit.Assert.assertTrue;
  34 import static org.junit.Assert.fail;
  35 
  36 import java.util.*;
  37 
  38 import com.sun.javafx.scene.control.infrastructure.StageLoader;
  39 import javafx.collections.FXCollections;
  40 import javafx.collections.ListChangeListener;
  41 import javafx.collections.ObservableList;
  42 import javafx.scene.control.ListView.ListViewFocusModel;
  43 import javafx.scene.control.TableView.TableViewFocusModel;
  44 import javafx.scene.control.TableView.TableViewSelectionModel;
  45 import javafx.scene.control.TreeTableView.TreeTableViewFocusModel;
  46 import javafx.scene.control.TreeTableView.TreeTableViewSelectionModel;
  47 import javafx.scene.control.TreeView.TreeViewFocusModel;
  48 
  49 import org.junit.After;
  50 import org.junit.AfterClass;
  51 import org.junit.Assert;
  52 import org.junit.Before;
  53 import org.junit.Ignore;
  54 import org.junit.Test;
  55 import org.junit.runner.RunWith;
  56 import org.junit.runners.Parameterized;
  57 import org.junit.runners.Parameterized.Parameters;
  58 
  59 import com.sun.javafx.scene.control.infrastructure.VirtualFlowTestUtils;
  60 
  61 /**
  62  * Unit tests for the SelectionModel abstract class used by ListView
  63  * and TreeView. This unit test attempts to test all known implementations, and
  64  * as such contains some conditional logic to handle the various controls as
  65  * simply as possible.
  66  */
  67 @RunWith(Parameterized.class)
  68 public class MultipleSelectionModelImplTest {
  69 
  70     private MultipleSelectionModel model;
  71     private FocusModel focusModel;
  72 
  73     private Class<? extends MultipleSelectionModel> modelClass;
  74     private Control currentControl;
  75 
  76     // ListView
  77     private ListView<String> listView;
  78 
  79     // ListView model data
  80     private static ObservableList<String> defaultData = FXCollections.<String>observableArrayList();
  81     private static ObservableList<String> data = FXCollections.<String>observableArrayList();
  82     private static final String ROW_1_VALUE = "Row 1";
  83     private static final String ROW_2_VALUE = "Row 2";
  84     private static final String ROW_3_VALUE = "Long Row 3";
  85     private static final String ROW_5_VALUE = "Row 5";
  86     private static final String ROW_20_VALUE = "Row 20";
  87 
  88     // TreeView
  89     private TreeView treeView;
  90     private TreeItem<String> root;
  91     private TreeItem<String> ROW_2_TREE_VALUE;
  92     private TreeItem<String> ROW_5_TREE_VALUE;
  93     
  94     // TreeTableView
  95     private TreeTableView treeTableView;
  96 
  97     // TableView
  98     private TableView tableView;
  99 
 100     @Parameters public static Collection implementations() {
 101         return Arrays.asList(new Object[][] {
 102             { ListView.ListViewBitSetSelectionModel.class },
 103             { TreeView.TreeViewBitSetSelectionModel.class },
 104             { TableView.TableViewArrayListSelectionModel.class },
 105             { TreeTableView.TreeTableViewArrayListSelectionModel.class },
 106         });
 107     }
 108 
 109     public MultipleSelectionModelImplTest(Class<? extends MultipleSelectionModel> modelClass) {
 110         this.modelClass = modelClass;
 111     }
 112 
 113     @AfterClass public static void tearDownClass() throws Exception {    }
 114 
 115     @Before public void setUp() {
 116         // ListView init
 117         defaultData.setAll(ROW_1_VALUE, ROW_2_VALUE, ROW_3_VALUE, "Row 4", ROW_5_VALUE, "Row 6",
 118                 "Row 7", "Row 8", "Row 9", "Row 10", "Row 11", "Row 12", "Row 13",
 119                 "Row 14", "Row 15", "Row 16", "Row 17", "Row 18", "Row 19", ROW_20_VALUE);
 120 
 121         data.setAll(defaultData);
 122         listView = new ListView<>(data);
 123         // --- ListView init
 124 
 125         // TreeView init
 126         root = new TreeItem<>(ROW_1_VALUE);
 127         root.setExpanded(true);
 128         for (int i = 1; i < data.size(); i++) {
 129             root.getChildren().add(new TreeItem<>(data.get(i)));
 130         }
 131         ROW_2_TREE_VALUE = root.getChildren().get(0);
 132         ROW_5_TREE_VALUE = root.getChildren().get(3);
 133 
 134         treeView = new TreeView(root);
 135         // --- TreeView init
 136 
 137         // TreeTableView init
 138         treeTableView = new TreeTableView(root);
 139         // --- TreeView init
 140 
 141         // TableView init
 142         tableView = new TableView();
 143         tableView.setItems(data);
 144         // --- TableView init
 145 
 146         try {
 147             // reset the data model
 148             data = FXCollections.<String>observableArrayList();
 149             data.setAll(defaultData);
 150 
 151             // we create a new SelectionModel per test to ensure it is always back
 152             // at the default settings
 153             if (modelClass.equals(ListView.ListViewBitSetSelectionModel.class)) {
 154                 // recreate the selection model
 155                 model = modelClass.getConstructor(ListView.class).newInstance(listView);
 156                 listView.setSelectionModel((MultipleSelectionModel<String>)model);
 157 
 158                 // create a new focus model
 159                 focusModel = new ListViewFocusModel(listView);
 160                 listView.setFocusModel(focusModel);
 161                 currentControl = listView;
 162             } else if (modelClass.equals(TreeView.TreeViewBitSetSelectionModel.class)) {
 163                 model = modelClass.getConstructor(TreeView.class).newInstance(treeView);
 164                 treeView.setSelectionModel((MultipleSelectionModel<String>)model);
 165                 focusModel = treeView.getFocusModel();
 166 
 167                 // create a new focus model
 168                 focusModel = new TreeViewFocusModel(treeView);
 169                 treeView.setFocusModel(focusModel);
 170                 currentControl = treeView;
 171             } else if (TableViewSelectionModel.class.isAssignableFrom(modelClass)) {
 172                 // recreate the selection model
 173                 model = modelClass.getConstructor(TableView.class).newInstance(tableView);
 174                 tableView.setSelectionModel((TableViewSelectionModel) model);
 175 
 176                 // create a new focus model
 177                 focusModel = new TableViewFocusModel(tableView);
 178                 tableView.setFocusModel((TableViewFocusModel) focusModel);
 179                 currentControl = tableView;
 180             } else if (TreeTableViewSelectionModel.class.isAssignableFrom(modelClass)) {
 181                 // recreate the selection model
 182                 model = modelClass.getConstructor(TreeTableView.class).newInstance(treeTableView);
 183                 treeTableView.setSelectionModel((TreeTableViewSelectionModel) model);
 184 
 185                 // create a new focus model
 186                 focusModel = new TreeTableViewFocusModel(treeTableView);
 187                 treeTableView.setFocusModel((TreeTableViewFocusModel) focusModel);
 188                 currentControl = treeTableView;
 189             }
 190 
 191             // ensure the selection mode is set to multiple
 192             model.setSelectionMode(SelectionMode.MULTIPLE);
 193         } catch (Exception ex) {
 194             ex.printStackTrace();
 195         }
 196     }
 197 
 198     @After public void tearDown() {
 199         model = null;
 200     }
 201 
 202     private Object getValue(Object item) {
 203         if (item instanceof TreeItem) {
 204             return ((TreeItem)item).getValue();
 205         }
 206         return item;
 207     }
 208 
 209     private String indices(MultipleSelectionModel sm) {
 210         return "Selected Indices: " + sm.getSelectedIndices();
 211     }
 212 
 213     private String items(MultipleSelectionModel sm) {
 214         return "Selected Items: " + sm.getSelectedItems();
 215     }
 216 
 217     private MultipleSelectionModel msModel() {
 218         return model;
 219     }
 220     
 221     private boolean isTree() {
 222         return model instanceof TreeView.TreeViewBitSetSelectionModel ||
 223                model instanceof TreeTableView.TreeTableViewArrayListSelectionModel;
 224     }
 225 
 226     private void ensureInEmptyState() {
 227         assertEquals(-1, model.getSelectedIndex());
 228         assertNull(model.getSelectedItem());
 229 
 230         if (focusModel != null) {
 231             assertEquals(-1, focusModel.getFocusedIndex());
 232             assertNull(focusModel.getFocusedItem());
 233         }
 234 
 235         assertNotNull(msModel().getSelectedIndices());
 236         assertNotNull(msModel().getSelectedItems());
 237         assertEquals(0, msModel().getSelectedIndices().size());
 238         assertEquals(0, msModel().getSelectedItems().size());
 239     }
 240 
 241     @Test public void ensureInDefaultState() {
 242         assertEquals(-1, model.getSelectedIndex());
 243         assertNull(model.getSelectedItem());
 244 
 245         if (focusModel != null) {
 246             assertEquals(0, focusModel.getFocusedIndex());
 247             assertNotNull(focusModel.getFocusedItem());
 248         }
 249 
 250         assertNotNull(msModel().getSelectedIndices());
 251         assertNotNull(msModel().getSelectedItems());
 252         assertEquals(0, msModel().getSelectedIndices().size());
 253         assertEquals(0, msModel().getSelectedItems().size());
 254     }
 255 
 256     @Test public void selectValidIndex() {
 257         int index = 4;
 258         model.clearSelection();
 259         model.select(index);
 260 
 261         assertEquals(index, model.getSelectedIndex());
 262         assertNotNull(model.getSelectedItem());
 263         assertEquals(ROW_5_VALUE, getValue(model.getSelectedItem()));
 264 
 265         if (focusModel != null) {
 266             assertEquals(index, focusModel.getFocusedIndex());
 267             assertNotNull(focusModel.getFocusedItem());
 268             assertEquals(ROW_5_VALUE, getValue(focusModel.getFocusedItem()));
 269         }
 270 
 271         assertNotNull(msModel().getSelectedIndices());
 272         assertNotNull(msModel().getSelectedItems());
 273         assertEquals(1, msModel().getSelectedIndices().size());
 274         assertEquals(index, msModel().getSelectedIndices().get(0));
 275         assertEquals(1, msModel().getSelectedItems().size());
 276         assertEquals(ROW_5_VALUE, getValue(msModel().getSelectedItems().get(0)));
 277     }
 278 
 279     @Test public void testSelectAllWithSingleSelection() {
 280         msModel().setSelectionMode(SelectionMode.SINGLE);
 281         msModel().selectAll();
 282         ensureInDefaultState();
 283     }
 284 
 285     @Test public void testSelectAllWithMultipleSelection() {
 286         msModel().clearSelection();
 287         msModel().selectAll();
 288 
 289         assertEquals(19, model.getSelectedIndex());
 290         assertNotNull(model.getSelectedItem());
 291         assertEquals(ROW_20_VALUE, getValue(model.getSelectedItem()));
 292         assertEquals(19, focusModel.getFocusedIndex());
 293         assertNotNull(focusModel.getFocusedItem());
 294         assertEquals(ROW_20_VALUE, getValue(focusModel.getFocusedItem()));
 295         assertNotNull(msModel().getSelectedIndices());
 296         assertNotNull(msModel().getSelectedItems());
 297         assertEquals(20, msModel().getSelectedIndices().size());
 298         assertEquals(20, msModel().getSelectedItems().size());
 299     }
 300 
 301     @Test public void clearAllSelection() {
 302         msModel().selectAll();
 303         model.clearSelection();
 304         ensureInEmptyState();
 305     }
 306 
 307     @Test public void clearPartialSelectionWithSingleSelection() {
 308         model.clearSelection();
 309         assertFalse(model.isSelected(5));
 310         model.select(5);
 311         assertTrue(model.isSelected(5));
 312         model.clearSelection(5);
 313         assertFalse(model.isSelected(5));
 314 
 315         assertEquals(0, msModel().getSelectedIndices().size());
 316         assertEquals(0, msModel().getSelectedItems().size());
 317     }
 318 
 319     @Test public void clearPartialSelectionWithMultipleSelection() {
 320         msModel().setSelectionMode(SelectionMode.MULTIPLE);
 321         msModel().selectAll();
 322         model.clearSelection(5);
 323 
 324         assertTrue(model.isSelected(4));
 325         assertFalse(model.isSelected(5));
 326         assertTrue(model.isSelected(6));
 327     }
 328 
 329     @Test public void testSelectedIndicesObservableListIsEmpty() {
 330         assertTrue(msModel().getSelectedIndices().isEmpty());
 331     }
 332 
 333     @Test public void testSelectedIndicesIteratorIsNotNull() {
 334         assertNotNull(msModel().getSelectedIndices().iterator());
 335     }
 336 
 337     @Test public void testSelectedIndicesIteratorHasNoNext() {
 338         assertFalse(msModel().getSelectedIndices().iterator().hasNext());
 339     }
 340 
 341     @Test public void testSelectedIndicesIteratorWorksWithSingleSelection() {
 342         msModel().clearSelection();
 343         model.select(5);
 344 
 345         Iterator<Integer> it = msModel().getSelectedIndices().iterator();
 346         assertEquals(5, (int) it.next());
 347         assertFalse(it.hasNext());
 348     }
 349 
 350     @Test public void testSelectedIndicesIteratorWorksWithMultipleSelection() {
 351         msModel().clearSelection();
 352         msModel().setSelectionMode(SelectionMode.MULTIPLE);
 353         msModel().selectIndices(1, 2, 5);
 354 
 355         Iterator<Integer> it = msModel().getSelectedIndices().iterator();
 356         assertEquals(indices(msModel()), 1, (int) it.next());
 357         assertEquals(indices(msModel()), 2, (int) it.next());
 358         assertEquals(indices(msModel()), 5, (int) it.next());
 359         assertFalse(it.hasNext());
 360     }
 361 
 362     @Test public void testSelectedIndicesContains() {
 363         model.select(5);
 364         assertTrue(msModel().getSelectedIndices().contains(5));
 365     }
 366 
 367     @Test public void testSelectedItemsObservableListIsEmpty() {
 368         assertTrue(msModel().getSelectedItems().isEmpty());
 369     }
 370 
 371     @Test public void testSelectedItemsIndexOf() {
 372         msModel().clearSelection();
 373         if (isTree()) {
 374             model.select(ROW_2_TREE_VALUE);
 375             assertEquals(1, msModel().getSelectedItems().size());
 376             assertEquals(0, msModel().getSelectedItems().indexOf(ROW_2_TREE_VALUE));
 377         } else {
 378             model.select(ROW_2_VALUE);
 379             assertEquals(1, msModel().getSelectedItems().size());
 380             assertEquals(0, msModel().getSelectedItems().indexOf(ROW_2_VALUE));
 381         }
 382     }
 383 
 384     @Test public void testSelectedItemsIteratorIsNotNull() {
 385         assertNotNull(msModel().getSelectedIndices().iterator());
 386     }
 387 
 388     @Test public void testSelectedItemsLastIndexOf() {
 389         msModel().clearSelection();
 390         if (isTree()) {
 391             model.select(ROW_2_TREE_VALUE);
 392             assertEquals(1, msModel().getSelectedItems().size());
 393             assertEquals(0, msModel().getSelectedItems().lastIndexOf(ROW_2_TREE_VALUE));
 394         } else {
 395             model.select(ROW_2_VALUE);
 396             assertEquals(1, msModel().getSelectedItems().size());
 397             assertEquals(0, msModel().getSelectedItems().lastIndexOf(ROW_2_VALUE));
 398         }
 399     }
 400 
 401     @Test public void testSelectedItemsContains() {
 402         model.select(5);
 403         assertTrue(msModel().getSelectedIndices().contains(5));
 404     }
 405 
 406     @Test public void testSingleSelectionMode() {
 407         msModel().clearSelection();
 408         msModel().setSelectionMode(SelectionMode.SINGLE);
 409         assertTrue(model.isEmpty());
 410 
 411         model.select(5);
 412         assertTrue(model.isSelected(5));
 413 
 414         model.select(10);
 415         assertTrue(model.isSelected(10));
 416         assertFalse(model.isSelected(5));
 417 
 418         assertEquals(1, msModel().getSelectedIndices().size());
 419         assertEquals(1, msModel().getSelectedItems().size());
 420     }
 421 
 422     @Test public void testMultipleSelectionMode() {
 423         msModel().clearSelection();
 424         msModel().setSelectionMode(SelectionMode.MULTIPLE);
 425         assertTrue(model.isEmpty());
 426 
 427         model.select(5);
 428         assertTrue(model.isSelected(5));
 429 
 430         model.select(10);
 431         assertTrue(model.isSelected(10));
 432         assertTrue(model.isSelected(5));
 433         assertEquals(2, msModel().getSelectedIndices().size());
 434         assertEquals(2, msModel().getSelectedItems().size());
 435     }
 436 
 437     @Test public void testChangeSelectionMode() {
 438         msModel().clearSelection();
 439         msModel().setSelectionMode(SelectionMode.MULTIPLE);
 440         msModel().selectIndices(5, 10, 15);
 441         assertEquals(indices(msModel()), 3, msModel().getSelectedIndices().size());
 442         assertEquals(3, msModel().getSelectedItems().size());
 443         assertTrue(model.isSelected(5));
 444         assertTrue(model.isSelected(10));
 445         assertTrue(model.isSelected(15));
 446 
 447         msModel().setSelectionMode(SelectionMode.SINGLE);
 448         assertEquals(1, msModel().getSelectedIndices().size());
 449         assertEquals(1, msModel().getSelectedItems().size());
 450         assertFalse(indices(msModel()), model.isSelected(5));
 451         assertFalse(indices(msModel()), model.isSelected(10));
 452         assertTrue(indices(msModel()), model.isSelected(15));
 453     }
 454 
 455 //    @Test public void testSelectNullObject() {
 456 //        model.select(null);
 457 //    }
 458 
 459     @Test public void testSelectRange() {
 460         // should select all indices starting at 5, and finishing just before
 461         // the 10th item
 462         msModel().clearSelection();
 463         msModel().setSelectionMode(SelectionMode.MULTIPLE);
 464         msModel().selectRange(5, 10);
 465         assertEquals(indices(msModel()), 5, msModel().getSelectedIndices().size());
 466         assertEquals(5, msModel().getSelectedItems().size());
 467         assertFalse(model.isSelected(4));
 468         assertTrue(model.isSelected(5));
 469         assertTrue(model.isSelected(6));
 470         assertTrue(model.isSelected(7));
 471         assertTrue(model.isSelected(8));
 472         assertTrue(model.isSelected(9));
 473         assertFalse(model.isSelected(10));
 474     }
 475 
 476     @Test public void testDeselectionFromASelectionRange() {
 477         msModel().setSelectionMode(SelectionMode.MULTIPLE);
 478         msModel().selectRange(2, 10);
 479         model.clearSelection(5);
 480         assertTrue(indices(msModel()), model.isSelected(4));
 481         assertFalse(indices(msModel()), model.isSelected(5));
 482         assertTrue(indices(msModel()), model.isSelected(6));
 483     }
 484 
 485     @Test public void testAccurateItemSelection() {
 486         msModel().clearSelection();
 487         msModel().setSelectionMode(SelectionMode.MULTIPLE);
 488         msModel().selectRange(2, 5);
 489         ObservableList selectedItems = msModel().getSelectedItems();
 490         assertEquals(3, selectedItems.size());
 491 
 492         if (isTree()) {
 493             assertFalse(selectedItems.contains(root.getChildren().get(0)));
 494             assertTrue(selectedItems.contains(root.getChildren().get(1)));
 495             assertTrue(selectedItems.contains(root.getChildren().get(2)));
 496             assertTrue(selectedItems.contains(root.getChildren().get(3)));
 497             assertFalse(selectedItems.contains(root.getChildren().get(4)));
 498         } else {
 499             assertFalse(selectedItems.contains(data.get(1)));
 500             assertTrue(selectedItems.contains(data.get(2)));
 501             assertTrue(selectedItems.contains(data.get(3)));
 502             assertTrue(selectedItems.contains(data.get(4)));
 503             assertFalse(selectedItems.contains(data.get(5)));
 504         }
 505     }
 506 
 507     @Test public void ensureSelectedIndexAndItemIsAlwaysTheLastSelectionWithSelect() {
 508         msModel().setSelectionMode(SelectionMode.MULTIPLE);
 509         model.select(3);
 510         assertEquals(3, model.getSelectedIndex());
 511         if (isTree()) {
 512             assertEquals(root.getChildren().get(2), model.getSelectedItem());
 513         } else {
 514             assertEquals(data.get(3), model.getSelectedItem());
 515         }
 516 
 517         model.select(1);
 518         assertEquals(1, model.getSelectedIndex());
 519         if (isTree()) {
 520             assertEquals(root.getChildren().get(0), model.getSelectedItem());
 521         } else {
 522             assertEquals(data.get(1), model.getSelectedItem());
 523         }
 524 
 525         model.select(5);
 526         assertEquals(5, model.getSelectedIndex());
 527         if (isTree()) {
 528             assertEquals(root.getChildren().get(4), model.getSelectedItem());
 529         } else {
 530             assertEquals(data.get(5), model.getSelectedItem());
 531         }
 532 
 533         model.select(1);
 534         assertEquals(1, model.getSelectedIndex());
 535         if (isTree()) {
 536             assertEquals(root.getChildren().get(0), model.getSelectedItem());
 537         } else {
 538             assertEquals(data.get(1), model.getSelectedItem());
 539         }
 540     }
 541 
 542     @Test public void ensureSelectedIndexAndItemIsAlwaysTheLastSelectionWithMultipleSelect() {
 543         msModel().setSelectionMode(SelectionMode.MULTIPLE);
 544         msModel().selectIndices(3,4,5);
 545         assertEquals(5, model.getSelectedIndex());
 546 
 547         if (isTree()) {
 548             assertEquals(root.getChildren().get(4), model.getSelectedItem());
 549         } else {
 550             assertEquals(data.get(5), model.getSelectedItem());
 551         }
 552 
 553         model.select(1);
 554         assertEquals(1, model.getSelectedIndex());
 555 
 556         if (isTree()) {
 557             assertEquals(root.getChildren().get(0), model.getSelectedItem());
 558         } else {
 559             assertEquals(data.get(1), model.getSelectedItem());
 560         }
 561 
 562         msModel().selectIndices(8,7,6);
 563         assertEquals(6, model.getSelectedIndex());
 564         if (isTree()) {
 565             assertEquals(root.getChildren().get(5), model.getSelectedItem());
 566         } else {
 567             assertEquals(data.get(6), model.getSelectedItem());
 568         }
 569     }
 570 
 571     @Test public void ensureSelectedIndexAndItemIsAlwaysTheLastSelectionWithSelectRange() {
 572         msModel().setSelectionMode(SelectionMode.MULTIPLE);
 573         msModel().selectRange(3,10);
 574         assertEquals(9, model.getSelectedIndex());
 575         if (isTree()) {
 576             assertEquals(root.getChildren().get(8), model.getSelectedItem());
 577         } else {
 578             assertEquals(data.get(9), model.getSelectedItem());
 579         }
 580 
 581         model.select(1);
 582         assertEquals(1, model.getSelectedIndex());
 583         if (isTree()) {
 584             assertEquals(root.getChildren().get(0), model.getSelectedItem());
 585         } else {
 586             assertEquals(data.get(1), model.getSelectedItem());
 587         }
 588 
 589         msModel().selectRange(6, 8);
 590         assertEquals(7, model.getSelectedIndex());
 591         if (isTree()) {
 592             assertEquals(root.getChildren().get(6), model.getSelectedItem());
 593         } else {
 594             assertEquals(data.get(7), model.getSelectedItem());
 595         }
 596     }
 597 
 598     @Test public void testMultipleSelectionWithEmptyArray() {
 599         msModel().clearSelection();
 600         msModel().setSelectionMode(SelectionMode.MULTIPLE);
 601         msModel().selectIndices(3,new int[] { });
 602         assertEquals(3, model.getSelectedIndex());
 603         assertEquals(1, msModel().getSelectedIndices().size());
 604         assertEquals(1, msModel().getSelectedItems().size());
 605 
 606         if (isTree()) {
 607             assertEquals(root.getChildren().get(2), model.getSelectedItem());
 608         } else {
 609             assertEquals(data.get(3), model.getSelectedItem());
 610         }
 611     }
 612 
 613     @Test public void selectOnlyValidIndicesInSingleSelection() {
 614         msModel().setSelectionMode(SelectionMode.SINGLE);
 615         msModel().selectIndices(750397, 3, 709709375, 4, 8597998, 47929);
 616         assertEquals(4, model.getSelectedIndex());
 617         assertFalse(model.isSelected(3));
 618         assertTrue(model.isSelected(4));
 619         assertEquals(1, msModel().getSelectedIndices().size());
 620         assertEquals(1, msModel().getSelectedItems().size());
 621 
 622         if (isTree()) {
 623             assertEquals(root.getChildren().get(3), model.getSelectedItem());
 624         } else {
 625             assertEquals(data.get(4), model.getSelectedItem());
 626         }
 627     }
 628 
 629     @Test public void selectOnlyValidIndicesInMultipleSelection() {
 630         msModel().setSelectionMode(SelectionMode.MULTIPLE);
 631         model.clearSelection();
 632         msModel().selectIndices(750397, 3, 709709375, 4, 8597998, 47929);
 633         assertEquals(4, model.getSelectedIndex());
 634         assertTrue(model.isSelected(3));
 635         assertTrue(model.isSelected(4));
 636         assertEquals(2, msModel().getSelectedIndices().size());
 637         assertEquals(2, msModel().getSelectedItems().size());
 638 
 639         if (isTree()) {
 640             assertEquals(root.getChildren().get(3), model.getSelectedItem());
 641         } else {
 642             assertEquals(data.get(4), model.getSelectedItem());
 643         }
 644     }
 645 
 646     @Test public void testNullArrayInMultipleSelection() {
 647         msModel().clearSelection();
 648         msModel().setSelectionMode(SelectionMode.MULTIPLE);
 649         msModel().selectIndices(-20, null);
 650         assertEquals(-1, model.getSelectedIndex());
 651         assertNull(model.getSelectedItem());
 652         assertEquals(indices(msModel()), 0, msModel().getSelectedIndices().size());
 653         assertEquals(items(msModel()), 0, msModel().getSelectedItems().size());
 654     }
 655 
 656     @Test public void testMultipleSelectionWithInvalidIndices() {
 657         msModel().clearSelection();
 658         msModel().setSelectionMode(SelectionMode.MULTIPLE);
 659         msModel().selectIndices(-20, 23505, 78125);
 660         assertEquals(-1, model.getSelectedIndex());
 661         assertNull(model.getSelectedItem());
 662         assertEquals(indices(msModel()), 0, msModel().getSelectedIndices().size());
 663         assertEquals(items(msModel()), 0, msModel().getSelectedItems().size());
 664     }
 665 
 666     @Test public void testInvalidSelection() {
 667         msModel().clearSelection();
 668         msModel().setSelectionMode(SelectionMode.SINGLE);
 669         msModel().selectIndices(-20, null);
 670         assertEquals(-1, model.getSelectedIndex());
 671         assertNull(model.getSelectedItem());
 672         assertEquals(indices(msModel()), 0, msModel().getSelectedIndices().size());
 673         assertEquals(items(msModel()), 0, msModel().getSelectedItems().size());
 674     }
 675 
 676     @Test public void ensureSwappedSelectRangeWorks() {
 677         // first test a valid range - there should be 6 selected items
 678         model.clearSelection();
 679         model.setSelectionMode(SelectionMode.MULTIPLE);
 680         model.selectRange(3, 10);
 681         assertEquals(indices(model), 7, model.getSelectedIndices().size());
 682         assertEquals(9, model.getSelectedIndex());
 683         if (isTree()) {
 684             assertEquals(root.getChildren().get(8), model.getSelectedItem());
 685         } else {
 686             assertEquals(data.get(9), model.getSelectedItem());
 687         }
 688        
 689         model.clearSelection();
 690         
 691         // we should select the range from 10 - 4 inclusive
 692         model.selectRange(10, 3);
 693         assertEquals(7, model.getSelectedIndices().size());
 694         assertEquals(indices(model), 4, model.getSelectedIndex());
 695         if (isTree()) {
 696             assertEquals(root.getChildren().get(3), model.getSelectedItem());
 697         } else {
 698             assertEquals(data.get(4), model.getSelectedItem());
 699         }
 700     }
 701 
 702     @Test public void testInvalidSelectRange() {
 703         msModel().clearSelection();
 704         msModel().setSelectionMode(SelectionMode.MULTIPLE);
 705         msModel().selectRange(200, 220);
 706         assertEquals(-1, model.getSelectedIndex());
 707         assertEquals(null, model.getSelectedItem());
 708         assertEquals(indices(msModel()), 0, msModel().getSelectedIndices().size());
 709         assertEquals(items(msModel()), 0, msModel().getSelectedItems().size());
 710     }
 711 
 712     @Test public void testEmptySelectRange() {
 713         msModel().clearSelection();
 714         msModel().setSelectionMode(SelectionMode.MULTIPLE);
 715         msModel().selectRange(10, 10);
 716         assertEquals(-1, model.getSelectedIndex());
 717         assertEquals(null, model.getSelectedItem());
 718         assertEquals(indices(msModel()), 0, msModel().getSelectedIndices().size());
 719         assertEquals(items(msModel()), 0, msModel().getSelectedItems().size());
 720     }
 721 
 722     @Test public void testNegativeSelectRange() {
 723         msModel().clearSelection();
 724         msModel().setSelectionMode(SelectionMode.MULTIPLE);
 725         msModel().selectRange(-10, -1);
 726         assertEquals(-1, model.getSelectedIndex());
 727         assertEquals(null, model.getSelectedItem());
 728         assertEquals(indices(msModel()), 0, msModel().getSelectedIndices().size());
 729         assertEquals(items(msModel()), 0, msModel().getSelectedItems().size());
 730     }
 731 
 732     @Test(expected=IllegalArgumentException.class)
 733     public void testNullListViewInSelectionModel() {
 734         new ListView.ListViewBitSetSelectionModel(null);
 735     }
 736 
 737     @Test(expected=IllegalArgumentException.class)
 738     public void testNullTreeViewInSelectionModel() {
 739         new TreeView.TreeViewBitSetSelectionModel(null);
 740     }
 741 
 742     @Test public void selectAllInEmptySingleSelectionMode() {
 743         model.clearSelection();
 744         msModel().setSelectionMode(SelectionMode.SINGLE);
 745         assertTrue(model.isEmpty());
 746         msModel().selectAll();
 747         assertTrue(model.isEmpty());
 748     }
 749 
 750     @Test public void selectAllInSingleSelectionModeWithSelectedRow() {
 751         msModel().setSelectionMode(SelectionMode.SINGLE);
 752         model.clearSelection();
 753         assertTrue(model.isEmpty());
 754         model.select(3);
 755         msModel().selectAll();
 756         assertTrue(model.isSelected(3));
 757         assertEquals(1, msModel().getSelectedIndices().size());
 758     }
 759 
 760     @Test public void selectionModePropertyHasReferenceToBean() {
 761         assertSame(model, model.selectionModeProperty().getBean());
 762     }
 763 
 764     @Test public void selectionModePropertyHasName() {
 765         assertSame("selectionMode", model.selectionModeProperty().getName());
 766     }
 767 
 768     @Ignore("Not yet implemented in TreeView and TableView")
 769     @Test public void testSelectionChangesWhenItemIsInsertedAtStartOfModel() {
 770         /* Select the fourth item, and insert a new item at the start of the
 771          * data model. The end result should be that the fourth item should NOT
 772          * be selected, and the fifth item SHOULD be selected.
 773          */
 774         model.select(3);
 775         assertTrue(model.isSelected(3));
 776         data.add(0, "Inserted String");
 777         assertFalse(model.isSelected(3));
 778         assertTrue(model.isSelected(4));
 779     }
 780     
 781     private int rt_28615_row_1_hit_count = 0;
 782     private int rt_28615_row_2_hit_count = 0;
 783     @Test public void test_rt_28615() {
 784         model.clearSelection();
 785         msModel().setSelectionMode(SelectionMode.MULTIPLE);
 786 
 787         msModel().getSelectedItems().addListener((ListChangeListener.Change change) -> {
 788             while (change.next()) {
 789                 if (change.wasAdded()) {
 790                     for (Object item : change.getAddedSubList()) {
 791                         if (isTree()) {
 792                             if (root.equals(item)) {
 793                                 rt_28615_row_1_hit_count++;
 794                             } else if (ROW_2_TREE_VALUE.equals(item)) {
 795                                 rt_28615_row_2_hit_count++;
 796                             }
 797                         } else {
 798                             if (ROW_1_VALUE.equals(item)) {
 799                                 rt_28615_row_1_hit_count++;
 800                             } else if (ROW_2_VALUE.equals(item)) {
 801                                 rt_28615_row_2_hit_count++;
 802                             }
 803                         }
 804                     }
 805                 }
 806             }
 807         });
 808 
 809         assertEquals(0, rt_28615_row_1_hit_count);
 810         assertEquals(0, rt_28615_row_2_hit_count);
 811         
 812         msModel().select(0);
 813         assertEquals(1, rt_28615_row_1_hit_count);
 814         assertEquals(0, rt_28615_row_2_hit_count);
 815         
 816         msModel().select(1);
 817         assertEquals(1, rt_28615_row_1_hit_count);
 818         assertEquals(1, rt_28615_row_2_hit_count);
 819     }
 820     
 821     private int rt_29860_size_count = 0;
 822     @Test public void test_rt_29860_add() {
 823         model.clearSelection();
 824         msModel().setSelectionMode(SelectionMode.MULTIPLE);
 825 
 826         msModel().getSelectedIndices().addListener((ListChangeListener.Change change) -> {
 827             while (change.next()) {
 828                 if (change.wasAdded()) {
 829                     rt_29860_size_count += change.getAddedSize();
 830                 }
 831             }
 832         });
 833 
 834         assertEquals(0, rt_29860_size_count);
 835         
 836         // 0,1,2,3 are all selected. The bug is not that the msModel().getSelectedIndices()
 837         // list is wrong (it isn't - it's correct). The bug is that the addedSize
 838         // reported in the callback above is incorrect.
 839         msModel().selectIndices(0, 1, 2, 3);
 840         assertEquals(msModel().getSelectedIndices().toString(), 4, rt_29860_size_count);   
 841         rt_29860_size_count = 0;
 842         
 843         msModel().selectIndices(0,1,2,3,4);
 844         assertEquals(msModel().getSelectedIndices().toString(), 1, rt_29860_size_count);   // only 4 was selected
 845         rt_29860_size_count = 0;
 846         
 847         msModel().selectIndices(6,7,8);
 848         assertEquals(3, rt_29860_size_count);   // 6,7,8 was selected
 849     }
 850     
 851     @Test public void test_rt_29821() {
 852         msModel().setSelectionMode(SelectionMode.MULTIPLE);
 853         
 854         IndexedCell cell_3 = VirtualFlowTestUtils.getCell(currentControl, 3);
 855         assertNotNull(cell_3);
 856         assertFalse(cell_3.isSelected());
 857 
 858         msModel().clearSelection();
 859         msModel().select(3);
 860         assertTrue(cell_3.isSelected());
 861         assertEquals(1, msModel().getSelectedIndices().size());
 862 
 863         // in multiple selection passing in select(null) is a no-op. In single
 864         // selection (tested elsewhere), this would result in a clearSelection() 
 865         // call
 866         msModel().select(null);
 867         assertTrue(msModel().isSelected(3));
 868         assertTrue(cell_3.isSelected());
 869     }
 870 
 871     private int rt_32411_add_count = 0;
 872     private int rt_32411_remove_count = 0;
 873     @Test public void test_rt_32411_selectedItems() {
 874         model.clearSelection();
 875 
 876         model.getSelectedItems().addListener((ListChangeListener.Change change) -> {
 877             while (change.next()) {
 878                 rt_32411_remove_count += change.getRemovedSize();
 879                 rt_32411_add_count += change.getAddedSize();
 880             }
 881         });
 882 
 883         // reset fields
 884         rt_32411_add_count = 0;
 885         rt_32411_remove_count = 0;
 886 
 887         // select a row - no problems here
 888         model.select(2);
 889         assertEquals(1, rt_32411_add_count);
 890         assertEquals(0, rt_32411_remove_count);
 891 
 892         // clear and select a new row. We should receive a remove event followed
 893         // by an added event - but this bug shows we don't get the remove event.
 894         model.clearAndSelect(4);
 895         assertEquals(2, rt_32411_add_count);
 896         assertEquals(1, rt_32411_remove_count);
 897     }
 898 
 899     @Test public void test_rt_32411_selectedIndices() {
 900         model.clearSelection();
 901 
 902         model.getSelectedIndices().addListener((ListChangeListener.Change change) -> {
 903             while (change.next()) {
 904                 rt_32411_remove_count += change.getRemovedSize();
 905                 rt_32411_add_count += change.getAddedSize();
 906             }
 907         });
 908 
 909         // reset fields
 910         rt_32411_add_count = 0;
 911         rt_32411_remove_count = 0;
 912 
 913         // select a row - no problems here
 914         model.select(2);
 915         assertEquals(1, rt_32411_add_count);
 916         assertEquals(0, rt_32411_remove_count);
 917 
 918         // clear and select a new row. We should receive a remove event followed
 919         // by an added event - but this bug shows we don't get the remove event.
 920         model.clearAndSelect(4);
 921         assertEquals(2, rt_32411_add_count);
 922         assertEquals(1, rt_32411_remove_count);
 923     }
 924 
 925     private int rt32618_count = 0;
 926     @Test public void test_rt32618_multipleSelection() {
 927         model.selectedItemProperty().addListener((ov, t, t1) -> rt32618_count++);
 928 
 929         assertEquals(0, rt32618_count);
 930 
 931         model.select(1);
 932         assertEquals(1, rt32618_count);
 933         assertEquals(ROW_2_VALUE, getValue(model.getSelectedItem()));
 934 
 935         model.clearAndSelect(2);
 936         assertEquals(2, rt32618_count);
 937         assertEquals(ROW_3_VALUE, getValue(model.getSelectedItem()));
 938     }
 939 
 940     @Test public void test_rt33324_selectedIndices() {
 941         // pre-select item 0
 942         model.select(0);
 943 
 944         // install listener
 945         model.getSelectedIndices().addListener((ListChangeListener.Change change) -> {
 946             while (change.next()) {
 947                 assertTrue(change.wasRemoved());
 948                 assertTrue(change.wasAdded());
 949                 assertTrue(change.wasReplaced());
 950 
 951                 assertFalse(change.wasPermutated());
 952             }
 953         });
 954 
 955         // change selection to index 1. This should result in a change event
 956         // being fired where wasAdded() is true, wasRemoved() is true, but most
 957         // importantly, wasReplaced() is true
 958         model.clearAndSelect(1);
 959     }
 960 
 961     @Test public void test_rt33324_selectedItems() {
 962         // pre-select item 0
 963         model.select(0);
 964 
 965         // install listener
 966         model.getSelectedItems().addListener((ListChangeListener.Change change) -> {
 967             while (change.next()) {
 968                 assertTrue(change.wasRemoved());
 969                 assertTrue(change.wasAdded());
 970                 assertTrue(change.wasReplaced());
 971 
 972                 assertFalse(change.wasPermutated());
 973             }
 974         });
 975 
 976         // change selection to index 1. This should result in a change event
 977         // being fired where wasAdded() is true, wasRemoved() is true, but most
 978         // importantly, wasReplaced() is true
 979         model.clearAndSelect(1);
 980     }
 981 
 982     @Test public void test_rt33324_selectedCells() {
 983         if (! (msModel() instanceof TableViewSelectionModel)) {
 984             return;
 985         }
 986 
 987         TableViewSelectionModel tableSM = (TableViewSelectionModel) msModel();
 988 
 989         // pre-select item 0
 990         tableSM.select(0);
 991 
 992         // install listener
 993         tableSM.getSelectedCells().addListener((ListChangeListener.Change change) -> {
 994             while (change.next()) {
 995                 assertTrue(change.wasRemoved());
 996                 assertTrue(change.wasAdded());
 997                 assertTrue(change.wasReplaced());
 998 
 999                 assertFalse(change.wasPermutated());
1000             }
1001         });
1002 
1003         // change selection to index 1. This should result in a change event
1004         // being fired where wasAdded() is true, wasRemoved() is true, but most
1005         // importantly, wasReplaced() is true
1006         tableSM.clearAndSelect(1);
1007     }
1008 
1009     @Test public void test_rt35624_selectedIndices_downwards() {
1010         model.clearSelection();
1011         model.select(2);
1012 
1013         msModel().getSelectedIndices().addListener((ListChangeListener.Change change) -> {
1014             while (change.next()) {
1015                 // we expect two items in the added list: 3 and 4, as index
1016                 // 2 has previously been selected
1017                 Assert.assertEquals(2, change.getAddedSize());
1018                 Assert.assertEquals(FXCollections.observableArrayList(3, 4), change.getAddedSubList());
1019             }
1020 
1021             // In the actual list, we expect three items: 2, 3 and 4
1022             Assert.assertEquals(3, change.getList().size());
1023             Assert.assertEquals(FXCollections.observableArrayList(2, 3, 4), change.getList());
1024         });
1025 
1026         model.selectIndices(2, 3, 4);
1027     }
1028 
1029     @Test public void test_rt35624_selectedIndices_upwards() {
1030         model.clearSelection();
1031         model.select(4);
1032 
1033         msModel().getSelectedIndices().addListener(((ListChangeListener.Change change) -> {
1034             while (change.next()) {
1035                 // we expect two items in the added list: 3 and 2, as index
1036                 // 4 has previously been selected
1037                 assertEquals(2, change.getAddedSize());
1038                 assertEquals(FXCollections.observableArrayList(2, 3), change.getAddedSubList());
1039             }
1040 
1041             // In the actual list, we expect three items: 2, 3 and 4
1042             assertEquals(3, change.getList().size());
1043             assertEquals(FXCollections.observableArrayList(2, 3, 4), change.getList());
1044         }));
1045 
1046         model.selectIndices(4, 3, 2);
1047     }
1048 
1049     @Test public void test_rt35624_selectedItems_downwards() {
1050         model.clearSelection();
1051         model.select(2);
1052 
1053         msModel().getSelectedItems().addListener(((ListChangeListener.Change change) -> {
1054             while (change.next()) {
1055                 // we expect two items in the added list: the items in index
1056                 // 3 and 4, as index 2 has previously been selected
1057                 assertEquals(2, change.getAddedSize());
1058 
1059                 if (isTree()) {
1060                     assertEquals(FXCollections.observableArrayList(
1061                             root.getChildren().get(2),
1062                             root.getChildren().get(3)
1063                     ), change.getAddedSubList());
1064                 } else {
1065                     assertEquals(FXCollections.observableArrayList(
1066                             data.get(3),
1067                             data.get(4)
1068                     ), change.getAddedSubList());
1069                 }
1070             }
1071 
1072             // In the actual list, we expect three items: the values at index
1073             // 2, 3 and 4
1074             assertEquals(3, change.getList().size());
1075 
1076             if (isTree()) {
1077                 assertEquals(FXCollections.observableArrayList(
1078                         root.getChildren().get(1),
1079                         root.getChildren().get(2),
1080                         root.getChildren().get(3)
1081                 ), change.getList());
1082             } else {
1083                 assertEquals(FXCollections.observableArrayList(
1084                         data.get(2),
1085                         data.get(3),
1086                         data.get(4)
1087                 ), change.getList());
1088             }
1089         }));
1090 
1091         model.selectIndices(2, 3, 4);
1092     }
1093 
1094     @Test public void test_rt35624_selectedItems_upwards() {
1095         model.clearSelection();
1096         model.select(4);
1097 
1098         msModel().getSelectedItems().addListener(((ListChangeListener.Change change) -> {
1099             while (change.next()) {
1100                 // we expect two items in the added list: the items in index
1101                 // 2 and 3, as index 4 has previously been selected
1102                 assertEquals(2, change.getAddedSize());
1103 
1104                 if (isTree()) {
1105                     assertEquals(FXCollections.observableArrayList(
1106                             root.getChildren().get(1),
1107                             root.getChildren().get(2)
1108                     ), change.getAddedSubList());
1109                 } else {
1110                     assertEquals(FXCollections.observableArrayList(
1111                             data.get(2),
1112                             data.get(3)
1113                     ), change.getAddedSubList());
1114                 }
1115             }
1116 
1117             // In the actual list, we expect three items: the values at index
1118             // 2, 3 and 4
1119             assertEquals(3, change.getList().size());
1120 
1121             if (isTree()) {
1122                 assertEquals(FXCollections.observableArrayList(
1123                         root.getChildren().get(1),
1124                         root.getChildren().get(2),
1125                         root.getChildren().get(3)
1126                 ), change.getList());
1127             } else {
1128                 assertEquals(FXCollections.observableArrayList(
1129                         data.get(2),
1130                         data.get(3),
1131                         data.get(4)
1132                 ), change.getList());
1133             }
1134         }));
1135 
1136         model.selectIndices(4, 3, 2);
1137     }
1138 
1139     @Test public void test_rt39548_positiveValue_outOfRange() {
1140         // for this test we want there to be no data in the controls
1141         clearModelData();
1142 
1143         model.clearAndSelect(10);
1144     }
1145 
1146     @Test public void test_rt39548_negativeValue() {
1147         // for this test we want there to be no data in the controls
1148         clearModelData();
1149 
1150         model.clearAndSelect(-1);
1151     }
1152 
1153     @Ignore
1154     @Test public void test_rt38884_invalidChange() {
1155         model.select(3);
1156         int removedSize = model.getSelectedItems().size();
1157         ListChangeListener l = (ListChangeListener.Change c) -> {
1158             c.next();
1159             assertEquals(removedSize, c.getRemovedSize());
1160         };
1161         model.getSelectedItems().addListener(l);
1162         clearModelData();
1163     }
1164 
1165     private void clearModelData() {
1166         listView.getItems().clear();
1167         tableView.getItems().clear();
1168         treeView.setRoot(null);
1169         treeTableView.setRoot(null);
1170     }
1171 
1172     @Test public void test_rt40804() {
1173         final Thread.UncaughtExceptionHandler exceptionHandler = Thread.currentThread().getUncaughtExceptionHandler();
1174         Thread.currentThread().setUncaughtExceptionHandler((t, e) -> {
1175             e.printStackTrace();
1176             fail("We don't expect any exceptions in this test!");
1177         });
1178 
1179         StageLoader sl = new StageLoader(currentControl);
1180         model.setSelectionMode(SelectionMode.MULTIPLE);
1181         model.select(0);
1182         model.select(1);
1183         model.clearSelection();
1184         model.select(3); // this is where the test failed
1185 
1186         sl.dispose();
1187 
1188         // reset the exception handler
1189         Thread.currentThread().setUncaughtExceptionHandler(exceptionHandler);
1190     }
1191 }