1 /*
   2  * Copyright (c) 2011, 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 com.sun.javafx.scene.control.infrastructure.ControlTestUtils.assertStyleClassContains;
  29 import static org.junit.Assert.assertEquals;
  30 import static org.junit.Assert.assertFalse;
  31 import static org.junit.Assert.assertNotNull;
  32 import static org.junit.Assert.assertNull;
  33 import static org.junit.Assert.assertSame;
  34 import static org.junit.Assert.assertTrue;
  35 import static org.junit.Assert.fail;
  36 
  37 import java.util.ArrayList;
  38 import java.util.Arrays;
  39 import java.util.List;
  40 
  41 import com.sun.javafx.scene.control.behavior.ListCellBehavior;
  42 import com.sun.javafx.scene.control.behavior.TableCellBehavior;
  43 import com.sun.javafx.scene.control.infrastructure.KeyEventFirer;
  44 import com.sun.javafx.scene.control.infrastructure.KeyModifier;
  45 import javafx.application.Platform;
  46 import javafx.beans.binding.Bindings;
  47 import javafx.beans.property.ObjectProperty;
  48 import javafx.beans.property.ReadOnlyBooleanWrapper;
  49 import javafx.beans.property.ReadOnlyStringWrapper;
  50 import javafx.beans.property.SimpleObjectProperty;
  51 import javafx.collections.FXCollections;
  52 import javafx.collections.ListChangeListener;
  53 import javafx.collections.ObservableList;
  54 import javafx.event.ActionEvent;
  55 import javafx.scene.Parent;
  56 import javafx.scene.Scene;
  57 import javafx.scene.control.cell.CheckBoxListCell;
  58 import javafx.scene.control.cell.ComboBoxListCell;
  59 import javafx.scene.control.cell.TextFieldListCell;
  60 import javafx.scene.control.cell.TextFieldTableCell;
  61 import javafx.scene.image.ImageView;
  62 import javafx.scene.input.KeyCode;
  63 import javafx.scene.layout.HBox;
  64 import javafx.scene.layout.VBox;
  65 import javafx.scene.paint.Color;
  66 import javafx.scene.shape.Rectangle;
  67 import javafx.util.Callback;
  68 
  69 import org.junit.Before;
  70 import org.junit.Ignore;
  71 import org.junit.Test;
  72 
  73 import com.sun.javafx.scene.control.infrastructure.VirtualFlowTestUtils;
  74 import com.sun.javafx.scene.control.infrastructure.StageLoader;
  75 import com.sun.javafx.scene.control.skin.VirtualScrollBar;
  76 import com.sun.javafx.scene.control.test.Person;
  77 import com.sun.javafx.scene.control.test.RT_22463_Person;
  78 import com.sun.javafx.tk.Toolkit;
  79 
  80 public class ListViewTest {
  81     private ListView<String> listView;
  82     private MultipleSelectionModel<String> sm;
  83     private FocusModel<String> fm;
  84 
  85     @Before public void setup() {
  86         listView = new ListView<>();
  87         sm = listView.getSelectionModel();
  88         fm = listView.getFocusModel();
  89     }
  90 
  91 
  92     /*********************************************************************
  93      * Tests for the constructors                                        *
  94      ********************************************************************/
  95 
  96     @Test public void noArgConstructorSetsTheStyleClass() {
  97         assertStyleClassContains(listView, "list-view");
  98     }
  99 
 100     @Test public void noArgConstructorSetsNonNullSelectionModel() {
 101         assertNotNull(sm);
 102     }
 103 
 104     @Test public void noArgConstructorSetsNonNullItems() {
 105         assertNotNull(listView.getItems());
 106     }
 107 
 108     @Test public void noArgConstructor_selectedItemIsNull() {
 109         assertNull(sm.getSelectedItem());
 110     }
 111 
 112     @Test public void noArgConstructor_selectedIndexIsNegativeOne() {
 113         assertEquals(-1, sm.getSelectedIndex());
 114     }
 115 
 116     @Test public void singleArgConstructorSetsTheStyleClass() {
 117         final ListView<String> b2 = new ListView<>(FXCollections.observableArrayList("Hi"));
 118         assertStyleClassContains(b2, "list-view");
 119     }
 120 
 121     @Test public void singleArgConstructorSetsNonNullSelectionModel() {
 122         final ListView<String> b2 = new ListView<>(FXCollections.<String>observableArrayList("Hi"));
 123         assertNotNull(b2.getSelectionModel());
 124     }
 125 
 126     @Test public void singleArgConstructorAllowsNullItems() {
 127         final ListView<String> b2 = new ListView<String>(null);
 128         assertNull(b2.getItems());
 129     }
 130 
 131     @Test public void singleArgConstructorTakesItems() {
 132         ObservableList<String> items = FXCollections.observableArrayList("Hi");
 133         final ListView<String> b2 = new ListView<>(items);
 134         assertSame(items, b2.getItems());
 135     }
 136 
 137     @Test public void singleArgConstructor_selectedItemIsNull() {
 138         final ListView<String> b2 = new ListView<>(FXCollections.observableArrayList("Hi"));
 139         assertNull(b2.getSelectionModel().getSelectedItem());
 140     }
 141 
 142     @Test public void singleArgConstructor_selectedIndexIsNegativeOne() {
 143         final ListView<String> b2 = new ListView<>(FXCollections.observableArrayList("Hi"));
 144         assertEquals(-1, b2.getSelectionModel().getSelectedIndex());
 145     }
 146 
 147     /*********************************************************************
 148      * Tests for selection model                                         *
 149      ********************************************************************/
 150 
 151     @Test public void selectionModelCanBeNull() {
 152         listView.setSelectionModel(null);
 153         assertNull(listView.getSelectionModel());
 154     }
 155 
 156     @Test public void selectionModelCanBeBound() {
 157         MultipleSelectionModel<String> sm = new ListView.ListViewBitSetSelectionModel<String>(listView);
 158         ObjectProperty<MultipleSelectionModel<String>> other = new SimpleObjectProperty<MultipleSelectionModel<String>>(sm);
 159         listView.selectionModelProperty().bind(other);
 160         assertSame(sm, sm);
 161     }
 162 
 163     @Test public void selectionModelCanBeChanged() {
 164         MultipleSelectionModel<String> sm = new ListView.ListViewBitSetSelectionModel<String>(listView);
 165         listView.setSelectionModel(sm);
 166         assertSame(sm, sm);
 167     }
 168 
 169     @Test public void canSetSelectedItemToAnItemEvenWhenThereAreNoItems() {
 170         final String randomString = new String("I AM A CRAZY RANDOM STRING");
 171         sm.select(randomString);
 172         assertEquals(-1, sm.getSelectedIndex());
 173         assertSame(randomString, sm.getSelectedItem());
 174     }
 175 
 176     @Test public void canSetSelectedItemToAnItemNotInTheDataModel() {
 177         listView.getItems().addAll("Apple", "Orange", "Banana");
 178         final String randomString = new String("I AM A CRAZY RANDOM STRING");
 179         sm.select(randomString);
 180         assertEquals(-1, sm.getSelectedIndex());
 181         assertSame(randomString, sm.getSelectedItem());
 182     }
 183 
 184     @Test public void settingTheSelectedItemToAnItemInItemsResultsInTheCorrectSelectedIndex() {
 185         listView.getItems().addAll("Apple", "Orange", "Banana");
 186         sm.select("Orange");
 187         assertEquals(1, sm.getSelectedIndex());
 188         assertSame("Orange", sm.getSelectedItem());
 189     }
 190 
 191     @Test public void settingTheSelectedItemToANonexistantItemAndThenSettingItemsWhichContainsItResultsInCorrectSelectedIndex() {
 192         sm.select("Orange");
 193         listView.getItems().addAll("Apple", "Orange", "Banana");
 194         assertEquals(1, sm.getSelectedIndex());
 195         assertSame("Orange", sm.getSelectedItem());
 196     }
 197 
 198     @Test public void ensureSelectionClearsWhenAllItemsAreRemoved_selectIndex0() {
 199         listView.getItems().addAll("Apple", "Orange", "Banana");
 200         sm.select(0);
 201         listView.getItems().clear();
 202         assertEquals(-1, sm.getSelectedIndex());
 203         assertEquals(null, sm.getSelectedItem());
 204     }
 205 
 206     @Test public void ensureSelectionClearsWhenAllItemsAreRemoved_selectIndex2() {
 207         listView.getItems().addAll("Apple", "Orange", "Banana");
 208         sm.select(2);
 209         listView.getItems().clear();
 210         assertEquals(-1, sm.getSelectedIndex());
 211         assertEquals(null, sm.getSelectedItem());
 212     }
 213 
 214     @Test public void ensureSelectedItemRemainsAccurateWhenItemsAreCleared() {
 215         listView.getItems().addAll("Apple", "Orange", "Banana");
 216         sm.select(2);
 217         listView.getItems().clear();
 218         assertNull(sm.getSelectedItem());
 219         assertEquals(-1, sm.getSelectedIndex());
 220 
 221         listView.getItems().addAll("Kiwifruit", "Mandarin", "Pineapple");
 222         sm.select(2);
 223         assertEquals("Pineapple", sm.getSelectedItem());
 224     }
 225 
 226     @Test public void ensureSelectionShiftsDownWhenOneNewItemIsAdded() {
 227         listView.getItems().addAll("Apple", "Orange", "Banana");
 228         sm.select(1);
 229         assertEquals(1, sm.getSelectedIndex());
 230         assertEquals("Orange", sm.getSelectedItem());
 231 
 232         listView.getItems().add(0, "Kiwifruit");
 233         assertEquals(2, sm.getSelectedIndex());
 234         assertEquals("Orange", sm.getSelectedItem());
 235     }
 236 
 237     @Test public void ensureSelectionShiftsDownWhenMultipleNewItemAreAdded() {
 238         listView.getItems().addAll("Apple", "Orange", "Banana");
 239         sm.select(1);
 240         assertEquals(1, sm.getSelectedIndex());
 241         assertEquals("Orange", sm.getSelectedItem());
 242 
 243         listView.getItems().addAll(0, Arrays.asList("Kiwifruit", "Pineapple", "Mandarin"));
 244         assertEquals("Orange", sm.getSelectedItem());
 245         assertEquals(4, sm.getSelectedIndex());
 246     }
 247 
 248     @Test public void ensureSelectionShiftsUpWhenOneItemIsRemoved() {
 249         listView.getItems().addAll("Apple", "Orange", "Banana");
 250         sm.select(1);
 251         assertEquals(1, sm.getSelectedIndex());
 252         assertEquals("Orange", sm.getSelectedItem());
 253 
 254         listView.getItems().remove("Apple");
 255         assertEquals(0, sm.getSelectedIndex());
 256         assertEquals("Orange", sm.getSelectedItem());
 257     }
 258 
 259     @Test public void ensureSelectionShiftsUpWheMultipleItemsAreRemoved() {
 260         listView.getItems().addAll("Apple", "Orange", "Banana");
 261         sm.select(2);
 262         assertEquals(2, sm.getSelectedIndex());
 263         assertEquals("Banana", sm.getSelectedItem());
 264 
 265         listView.getItems().removeAll(Arrays.asList("Apple", "Orange"));
 266         assertEquals(0, sm.getSelectedIndex());
 267         assertEquals("Banana", sm.getSelectedItem());
 268     }
 269 
 270     @Test public void ensureSelectionIsCorrectWhenItemsChange() {
 271         listView.setItems(FXCollections.observableArrayList("Item 1"));
 272         sm.select(0);
 273         assertEquals("Item 1", sm.getSelectedItem());
 274 
 275         listView.setItems(FXCollections.observableArrayList("Item 2"));
 276         assertEquals(-1, sm.getSelectedIndex());
 277         assertNull(sm.getSelectedItem());
 278         assertEquals(0, fm.getFocusedIndex());
 279         assertEquals("Item 2", fm.getFocusedItem());
 280     }
 281 
 282     @Test public void test_rt15793() {
 283         // ListView selectedIndex is 0 although the items list is empty
 284         final ListView lv = new ListView();
 285         final ObservableList list = FXCollections.observableArrayList();
 286         lv.setItems(list);
 287         list.add("toto");
 288         lv.getSelectionModel().select(0);
 289         assertEquals(0, lv.getSelectionModel().getSelectedIndex());
 290         list.remove(0);
 291         assertEquals(-1, lv.getSelectionModel().getSelectedIndex());
 292     }
 293 
 294     @Test public void test_rt17522_focusShouldMoveWhenItemAddedAtFocusIndex() {
 295         final ListView lv = new ListView();
 296         FocusModel fm = lv.getFocusModel();
 297         lv.getItems().add("row1");
 298         fm.focus(0);
 299         assertTrue(fm.isFocused(0));
 300 
 301         lv.getItems().add(0, "row0");
 302         assertTrue(fm.isFocused(1));
 303     }
 304 
 305     @Test public void test_rt17522_focusShouldMoveWhenItemAddedBeforeFocusIndex() {
 306         final ListView lv = new ListView();
 307         FocusModel fm = lv.getFocusModel();
 308         lv.getItems().addAll("row1", "row2");
 309         fm.focus(1);
 310         assertTrue(fm.isFocused(1));
 311         assertEquals("row2", fm.getFocusedItem());
 312 
 313         lv.getItems().add(1, "row0");
 314         assertTrue(fm.isFocused(2));
 315         assertEquals("row2", fm.getFocusedItem());
 316         assertFalse(fm.isFocused(1));
 317     }
 318 
 319     @Test public void test_rt17522_focusShouldNotMoveWhenItemAddedAfterFocusIndex() {
 320         final ListView lv = new ListView();
 321         FocusModel fm = lv.getFocusModel();
 322         lv.getItems().addAll("row1");
 323         fm.focus(0);
 324         assertTrue(fm.isFocused(0));
 325         assertEquals("row1", fm.getFocusedItem());
 326 
 327         lv.getItems().add(1, "row2");
 328         assertTrue(fm.isFocused(0));
 329         assertEquals("row1", fm.getFocusedItem());
 330         assertFalse(fm.isFocused(1));
 331     }
 332 
 333     @Test public void test_rt17522_focusShouldBeResetWhenFocusedItemIsRemoved() {
 334         final ListView lv = new ListView();
 335         FocusModel fm = lv.getFocusModel();
 336         lv.getItems().add("row1");
 337         fm.focus(0);
 338         assertTrue(fm.isFocused(0));
 339 
 340         lv.getItems().remove("row1");
 341         assertTrue(fm.getFocusedIndex() == -1);
 342         assertNull(fm.getFocusedItem());
 343     }
 344 
 345     @Test public void test_rt17522_focusShouldMoveWhenItemRemovedBeforeFocusIndex() {
 346         final ListView lv = new ListView();
 347         FocusModel fm = lv.getFocusModel();
 348         lv.getItems().addAll("row1", "row2");
 349         fm.focus(1);
 350         assertTrue(fm.isFocused(1));
 351         assertEquals("row2", fm.getFocusedItem());
 352 
 353         lv.getItems().remove("row1");
 354         assertTrue(fm.isFocused(0));
 355         assertEquals("row2", fm.getFocusedItem());
 356     }
 357 
 358     @Test public void test_rt17522_focusShouldNotMoveWhenItemRemovedAfterFocusIndex() {
 359         final ListView lv = new ListView();
 360         FocusModel fm = lv.getFocusModel();
 361         lv.getItems().addAll("row1", "row2");
 362         fm.focus(0);
 363         assertTrue(fm.isFocused(0));
 364         assertEquals("row1", fm.getFocusedItem());
 365 
 366         lv.getItems().remove("row2");
 367         assertTrue(fm.isFocused(0));
 368         assertEquals("row1", fm.getFocusedItem());
 369     }
 370 
 371     @Test public void test_rt18385() {
 372         listView.getItems().addAll("row1", "row2", "row3");
 373         sm.select(1);
 374         listView.getItems().add("Another Row");
 375         assertEquals(1, sm.getSelectedIndices().size());
 376         assertEquals(1, sm.getSelectedItems().size());
 377     }
 378 
 379     @Test public void test_rt18339_onlyEditWhenListViewIsEditable_editableIsFalse() {
 380         listView.setEditable(false);
 381         listView.edit(1);
 382         assertEquals(-1, listView.getEditingIndex());
 383     }
 384 
 385     @Test public void test_rt18339_onlyEditWhenListViewIsEditable_editableIsTrue() {
 386         listView.setEditable(true);
 387         listView.edit(1);
 388         assertEquals(1, listView.getEditingIndex());
 389     }
 390 
 391     @Test public void test_rt14451() {
 392         listView.getItems().addAll("Apple", "Orange", "Banana");
 393         sm.setSelectionMode(SelectionMode.MULTIPLE);
 394         sm.selectRange(0, 2); // select from 0 (inclusive) to 2 (exclusive)
 395         assertEquals(2, sm.getSelectedIndices().size());
 396     }
 397 
 398     private int rt_18969_hitCount = 0;
 399     @Test public void test_rt18969() {
 400         rt_18969_hitCount = 0;
 401         ObservableList<String> emptyModel = FXCollections.observableArrayList();
 402         listView.setItems(emptyModel);
 403         assertTrue(listView.getItems().isEmpty());
 404 
 405         sm.selectedItemProperty().addListener((observable, oldValue, newValue) -> {
 406             rt_18969_hitCount++;
 407         });
 408 
 409         ObservableList<String> mod = FXCollections.observableArrayList();
 410         mod.add(System.currentTimeMillis()+"");
 411         listView.getItems().setAll(mod);
 412 
 413         sm.select(0);
 414         assertTrue(sm.isSelected(0));
 415         assertEquals(1, rt_18969_hitCount);
 416 
 417         // sleep for 100ms so that the currentTimeMillis is guaranteed to be
 418         // a different value than the first one
 419         try {
 420             Thread.sleep(100);
 421         } catch (InterruptedException ex) {
 422             ex.printStackTrace();
 423         }
 424 
 425         // the list is totally changing (it is being cleared), so we should 
 426         // be nulling out the selection model state
 427         mod = FXCollections.observableArrayList();
 428         mod.add(System.currentTimeMillis()+"");
 429         listView.getItems().setAll(mod);
 430 
 431         // it should be two, as there is no null event in between (although there
 432         // used to be, so the test used to be for three hits)
 433         assertEquals(2, rt_18969_hitCount);
 434     }
 435 
 436     @Test public void test_rt21586() {
 437         listView.getItems().setAll("Apple", "Orange", "Banana");
 438         listView.getSelectionModel().select(1);
 439         assertEquals(1, listView.getSelectionModel().getSelectedIndex());
 440         assertEquals("Orange", listView.getSelectionModel().getSelectedItem());
 441 
 442         listView.getItems().setAll("Kiwifruit", "Pineapple", "Grape");
 443         assertEquals(-1, listView.getSelectionModel().getSelectedIndex());
 444         assertNull(listView.getSelectionModel().getSelectedItem());
 445     }
 446 
 447     @Test public void test_rt27820_1() {
 448         listView.getItems().setAll("Apple", "Orange");
 449         listView.getSelectionModel().select(0);
 450         assertEquals(1, listView.getSelectionModel().getSelectedItems().size());
 451         assertEquals("Apple", listView.getSelectionModel().getSelectedItem());
 452 
 453         listView.getItems().clear();
 454         assertEquals(0, listView.getSelectionModel().getSelectedItems().size());
 455         assertNull(listView.getSelectionModel().getSelectedItem());
 456     }
 457 
 458     @Test public void test_rt27820_2() {
 459         listView.getItems().setAll("Apple", "Orange");
 460         listView.getSelectionModel().select(1);
 461         assertEquals(1, listView.getSelectionModel().getSelectedItems().size());
 462         assertEquals("Orange", listView.getSelectionModel().getSelectedItem());
 463 
 464         listView.getItems().clear();
 465         assertEquals(0, listView.getSelectionModel().getSelectedItems().size());
 466         assertNull(listView.getSelectionModel().getSelectedItem());
 467     }
 468 
 469     @Test public void test_rt28534() {
 470         ListView<Person> list = new ListView<Person>();
 471         list.setItems(FXCollections.observableArrayList(
 472                 new Person("Jacob", "Smith", "jacob.smith@example.com"),
 473                 new Person("Isabella", "Johnson", "isabella.johnson@example.com"),
 474                 new Person("Ethan", "Williams", "ethan.williams@example.com"),
 475                 new Person("Emma", "Jones", "emma.jones@example.com"),
 476                 new Person("Michael", "Brown", "michael.brown@example.com")));
 477 
 478         VirtualFlowTestUtils.assertRowsNotEmpty(list, 0, 5); // rows 0 - 5 should be filled
 479         VirtualFlowTestUtils.assertRowsEmpty(list, 5, -1); // rows 5+ should be empty
 480 
 481         // now we replace the data and expect the cells that have no data
 482         // to be empty
 483         list.setItems(FXCollections.observableArrayList(
 484                 new Person("*_*Emma", "Jones", "emma.jones@example.com"),
 485                 new Person("_Michael", "Brown", "michael.brown@example.com")));
 486 
 487         VirtualFlowTestUtils.assertRowsNotEmpty(list, 0, 2); // rows 0 - 2 should be filled
 488         VirtualFlowTestUtils.assertRowsEmpty(list, 2, -1); // rows 2+ should be empty
 489     }
 490 
 491     @Test public void test_rt22463() {
 492         final ListView<RT_22463_Person> list = new ListView<RT_22463_Person>();
 493 
 494         // before the change things display fine
 495         RT_22463_Person p1 = new RT_22463_Person();
 496         p1.setId(1l);
 497         p1.setName("name1");
 498         RT_22463_Person p2 = new RT_22463_Person();
 499         p2.setId(2l);
 500         p2.setName("name2");
 501         list.setItems(FXCollections.observableArrayList(p1, p2));
 502         VirtualFlowTestUtils.assertCellTextEquals(list, 0, "name1");
 503         VirtualFlowTestUtils.assertCellTextEquals(list, 1, "name2");
 504 
 505         // now we change the persons but they are still equal as the ID's don't
 506         // change - but the items list is cleared so the cells should update
 507         RT_22463_Person new_p1 = new RT_22463_Person();
 508         new_p1.setId(1l);
 509         new_p1.setName("updated name1");
 510         RT_22463_Person new_p2 = new RT_22463_Person();
 511         new_p2.setId(2l);
 512         new_p2.setName("updated name2");
 513         list.getItems().clear();
 514         list.setItems(FXCollections.observableArrayList(new_p1, new_p2));
 515         VirtualFlowTestUtils.assertCellTextEquals(list, 0, "updated name1");
 516         VirtualFlowTestUtils.assertCellTextEquals(list, 1, "updated name2");
 517     }
 518 
 519     @Test public void test_rt28637() {
 520         ObservableList<String> items = FXCollections.observableArrayList("String1", "String2", "String3", "String4");
 521 
 522         final ListView<String> listView = new ListView<String>();
 523         listView.setItems(items);
 524 
 525         listView.getSelectionModel().select(0);
 526         assertEquals("String1", listView.getSelectionModel().getSelectedItem());
 527         assertEquals("String1", listView.getSelectionModel().getSelectedItems().get(0));
 528         assertEquals(0, listView.getSelectionModel().getSelectedIndex());
 529 
 530         items.remove(listView.getSelectionModel().getSelectedItem());
 531         assertEquals("String2", listView.getSelectionModel().getSelectedItem());
 532         assertEquals("String2", listView.getSelectionModel().getSelectedItems().get(0));
 533         assertEquals(0, listView.getSelectionModel().getSelectedIndex());
 534     }
 535 
 536     @Test public void test_rt28819_1() {
 537         ObservableList<String> emptyModel = FXCollections.observableArrayList();
 538 
 539         final ListView<String> listView = new ListView<String>();
 540         listView.setItems(emptyModel);
 541         VirtualFlowTestUtils.assertRowsEmpty(listView, 0, 5);
 542 
 543         ObservableList<String> mod = FXCollections.observableArrayList();
 544         String value = System.currentTimeMillis()+"";
 545         mod.add(value);
 546         listView.setItems(mod);
 547         VirtualFlowTestUtils.assertCellCount(listView, 1);
 548         VirtualFlowTestUtils.assertCellTextEquals(listView, 0, value);
 549     }
 550 
 551     @Test public void test_rt28819_2() {
 552         ObservableList<String> emptyModel = FXCollections.observableArrayList();
 553 
 554         final ListView<String> listView = new ListView<String>();
 555         listView.setItems(emptyModel);
 556         VirtualFlowTestUtils.assertRowsEmpty(listView, 0, 5);
 557 
 558         ObservableList<String> mod1 = FXCollections.observableArrayList();
 559         String value1 = System.currentTimeMillis()+"";
 560         mod1.add(value1);
 561         listView.getItems().setAll(mod1);
 562         VirtualFlowTestUtils.assertCellCount(listView, 1);
 563         VirtualFlowTestUtils.assertCellTextEquals(listView, 0, value1);
 564     }
 565 
 566     @Test public void test_rt29390() {
 567         ObservableList<String> items = FXCollections.observableArrayList(
 568                 "String1", "String2", "String3", "String4",
 569                 "String1", "String2", "String3", "String4",
 570                 "String1", "String2", "String3", "String4",
 571                 "String1", "String2", "String3", "String4"
 572         );
 573 
 574         final ListView<String> listView = new ListView<String>(items);
 575         listView.setMaxHeight(50);
 576         listView.setPrefHeight(50);
 577 
 578         // we want the vertical scrollbar
 579         VirtualScrollBar scrollBar = VirtualFlowTestUtils.getVirtualFlowVerticalScrollbar(listView);
 580 
 581         assertNotNull(scrollBar);
 582         assertTrue(scrollBar.isVisible());
 583         assertTrue(scrollBar.getVisibleAmount() > 0.0);
 584         assertTrue(scrollBar.getVisibleAmount() < 1.0);
 585 
 586         // this next test is likely to be brittle, but we'll see...If it is the
 587         // cause of failure then it can be commented out
 588         assertEquals(0.125, scrollBar.getVisibleAmount(), 0.0);
 589     }
 590 
 591     @Test public void test_rt30400() {
 592         // create a listview that'll render cells using the check box cell factory
 593         ObservableList<String> items = FXCollections.observableArrayList("String1");
 594         final ListView<String> listView = new ListView<String>(items);
 595         listView.setMinHeight(100);
 596         listView.setPrefHeight(100);
 597         listView.setCellFactory(CheckBoxListCell.forListView(param -> new ReadOnlyBooleanWrapper(true)));
 598 
 599         // because only the first row has data, all other rows should be 
 600         // empty (and not contain check boxes - we just check the first four here)
 601         VirtualFlowTestUtils.assertRowsNotEmpty(listView, 0, 1);
 602         VirtualFlowTestUtils.assertCellNotEmpty(VirtualFlowTestUtils.getCell(listView, 0));
 603         VirtualFlowTestUtils.assertCellEmpty(VirtualFlowTestUtils.getCell(listView, 1));
 604         VirtualFlowTestUtils.assertCellEmpty(VirtualFlowTestUtils.getCell(listView, 2));
 605         VirtualFlowTestUtils.assertCellEmpty(VirtualFlowTestUtils.getCell(listView, 3));
 606     }
 607 
 608     @Test public void test_rt29420() {
 609         final ListView<String> listView = new ListView<String>();
 610 
 611         VBox vbox = new VBox(listView);
 612         StageLoader sl = new StageLoader(vbox);
 613 
 614         // the initial width of a ListView should be the golden rectangle where
 615         // the height is hardcoded to be 400
 616         final double initialWidth = listView.prefWidth(-1);
 617         assertEquals(400 * 0.618033987, initialWidth, 0.00);
 618 
 619         // add in some items, and re-measure - seeing as the items are narrow,
 620         // the width shouldn't change
 621         listView.getItems().addAll("one", "two", "three", "four", "five", "six");
 622         Toolkit.getToolkit().firePulse();
 623         final double withContentWidth = listView.prefWidth(-1);
 624         assertEquals(initialWidth, withContentWidth, 0.00);
 625 
 626         // remove the items - and the width should remain the same
 627         listView.getItems().clear();
 628         Toolkit.getToolkit().firePulse();
 629         final double afterEmptiedWidth = listView.prefWidth(-1);
 630         assertEquals(initialWidth, afterEmptiedWidth, 0.00);
 631 
 632         sl.dispose();
 633     }
 634 
 635     @Test public void test_rt31165() {
 636         final ObservableList names = FXCollections.observableArrayList("Adam", "Alex", "Alfred", "Albert");
 637         final ObservableList data = FXCollections.observableArrayList();
 638         for (int i = 0; i < 18; i++) {
 639             data.add(""+i);
 640         }
 641 
 642         final ListView listView = new ListView(data);
 643         listView.setPrefSize(200, 250);
 644         listView.setEditable(true);
 645         listView.setCellFactory(ComboBoxListCell.forListView(names));
 646 
 647         IndexedCell cell = VirtualFlowTestUtils.getCell(listView, 1);
 648         assertEquals("1", cell.getText());
 649         assertFalse(cell.isEditing());
 650 
 651         listView.edit(1);
 652 
 653         assertEquals(1, listView.getEditingIndex());
 654         assertTrue(cell.isEditing());
 655 
 656         VirtualFlowTestUtils.getVirtualFlow(listView).requestLayout();
 657         Toolkit.getToolkit().firePulse();
 658 
 659         assertEquals(1, listView.getEditingIndex());
 660         assertTrue(cell.isEditing());
 661     }
 662 
 663     @Test public void test_rt31471() {
 664         final ObservableList names = FXCollections.observableArrayList("Adam", "Alex", "Alfred", "Albert");
 665         final ListView listView = new ListView(names);
 666 
 667         IndexedCell cell = VirtualFlowTestUtils.getCell(listView, 0);
 668         assertEquals("Adam", cell.getItem());
 669 
 670         listView.setFixedCellSize(50);
 671 
 672         VirtualFlowTestUtils.getVirtualFlow(listView).requestLayout();
 673         Toolkit.getToolkit().firePulse();
 674 
 675         assertEquals("Adam", cell.getItem());
 676         assertEquals(50, cell.getHeight(), 0.00);
 677     }
 678 
 679     private int rt_31200_count = 0;
 680     @Test public void test_rt_31200() {
 681         final ListView listView = new ListView();
 682         listView.setCellFactory(new Callback<ListView<String>, ListCell<String>>() {
 683             @Override
 684             public ListCell<String> call(ListView<String> param) {
 685                 return new ListCell<String>() {
 686                     ImageView view = new ImageView();
 687                     { setGraphic(view); };
 688 
 689                     @Override
 690                     protected void updateItem(String item, boolean empty) {
 691                         if (getItem() == null ? item == null : getItem().equals(item)) {
 692                             rt_31200_count++;
 693                         }
 694                         super.updateItem(item, empty);
 695                         if (item == null || empty) {
 696                             view.setImage(null);
 697                             setText(null);
 698                         } else {
 699                             setText(item);
 700                         }
 701                     }
 702                 };
 703             }
 704         });
 705         listView.getItems().setAll("one", "two", "three", "four", "five");
 706 
 707         StageLoader sl = new StageLoader(listView);
 708 
 709         assertEquals(24, rt_31200_count);
 710 
 711         // resize the stage
 712         sl.getStage().setHeight(250);
 713         Toolkit.getToolkit().firePulse();
 714         sl.getStage().setHeight(50);
 715         Toolkit.getToolkit().firePulse();
 716         assertEquals(24, rt_31200_count);
 717 
 718         sl.dispose();
 719     }
 720 
 721     @Test public void test_rt_30484() {
 722         final ListView listView = new ListView();
 723         listView.setCellFactory(new Callback<ListView<String>, ListCell<String>>() {
 724             @Override public ListCell<String> call(ListView<String> param) {
 725                 return new ListCell<String>() {
 726                     Rectangle graphic = new Rectangle(10, 10, Color.RED);
 727                     { setGraphic(graphic); };
 728 
 729                     @Override protected void updateItem(String item, boolean empty) {
 730                         super.updateItem(item, empty);
 731                         if (item == null || empty) {
 732                             graphic.setVisible(false);
 733                             setText(null);
 734                         } else {
 735                             graphic.setVisible(true);
 736                             setText(item);
 737                         }
 738                     }
 739                 };
 740             }
 741         });
 742 
 743         // First two rows have content, so the graphic should show.
 744         // All other rows have no content, so graphic should not show.
 745         listView.getItems().setAll("one", "two");
 746 
 747         VirtualFlowTestUtils.assertGraphicIsVisible(listView, 0);
 748         VirtualFlowTestUtils.assertGraphicIsVisible(listView, 1);
 749         VirtualFlowTestUtils.assertGraphicIsNotVisible(listView, 2);
 750         VirtualFlowTestUtils.assertGraphicIsNotVisible(listView, 3);
 751         VirtualFlowTestUtils.assertGraphicIsNotVisible(listView, 4);
 752         VirtualFlowTestUtils.assertGraphicIsNotVisible(listView, 5);
 753     }
 754 
 755     private int rt_29650_start_count = 0;
 756     private int rt_29650_commit_count = 0;
 757     private int rt_29650_cancel_count = 0;
 758     @Test public void test_rt_29650() {
 759         listView.setOnEditStart(t -> {
 760             rt_29650_start_count++;
 761         });
 762         listView.setOnEditCommit(t -> {
 763             rt_29650_commit_count++;
 764         });
 765         listView.setOnEditCancel(t -> {
 766             rt_29650_cancel_count++;
 767         });
 768 
 769         listView.getItems().setAll("one", "two", "three", "four", "five");
 770         listView.setEditable(true);
 771         listView.setCellFactory(TextFieldListCell.forListView());
 772 
 773         StageLoader sl = new StageLoader(listView);
 774 
 775         listView.edit(0);
 776 
 777         Toolkit.getToolkit().firePulse();
 778 
 779         ListCell rootCell = (ListCell) VirtualFlowTestUtils.getCell(listView, 0);
 780         TextField textField = (TextField) rootCell.getGraphic();
 781         textField.setText("Testing!");
 782         KeyEventFirer keyboard = new KeyEventFirer(textField);
 783         keyboard.doKeyPress(KeyCode.ENTER);
 784 
 785         // TODO should the following assert be enabled?
 786 //        assertEquals("Testing!", listView.getItems().get(0));
 787         assertEquals(1, rt_29650_start_count);
 788         assertEquals(1, rt_29650_commit_count);
 789         assertEquals(0, rt_29650_cancel_count);
 790 
 791         sl.dispose();
 792     }
 793 
 794     @Test public void test_rt35039() {
 795         final List<String> data = new ArrayList<>();
 796         data.add("aabbaa");
 797         data.add("bbc");
 798 
 799         final ListView<String> listView = new ListView<>();
 800         listView.setItems(FXCollections.observableArrayList(data));
 801 
 802         StageLoader sl = new StageLoader(listView);
 803 
 804         // selection starts off on row -1
 805         assertNull(listView.getSelectionModel().getSelectedItem());
 806 
 807         // select "bbc" and ensure everything is set to that
 808         listView.getSelectionModel().select(1);
 809         assertEquals("bbc", listView.getSelectionModel().getSelectedItem());
 810 
 811         // change the items list - but retain the same content. We expect
 812         // that "bbc" remains selected as it is still in the list
 813         listView.setItems(FXCollections.observableArrayList(data));
 814         assertEquals("bbc", listView.getSelectionModel().getSelectedItem());
 815 
 816         sl.dispose();
 817     }
 818 
 819     @Test public void test_rt35857() {
 820         ObservableList<String> fxList = FXCollections.observableArrayList("A", "B", "C");
 821         final ListView<String> listView = new ListView<String>(fxList);
 822 
 823         listView.getSelectionModel().select(0);
 824 
 825         ObservableList<String> selectedItems = listView.getSelectionModel().getSelectedItems();
 826         assertEquals(1, selectedItems.size());
 827         assertEquals("A", selectedItems.get(0));
 828 
 829         listView.getItems().removeAll(selectedItems);
 830         assertEquals(2, fxList.size());
 831         assertEquals("B", fxList.get(0));
 832         assertEquals("C", fxList.get(1));
 833     }
 834 
 835     private int rt_35889_cancel_count = 0;
 836     @Test public void test_rt35889() {
 837         final ListView<String> textFieldListView = new ListView<String>();
 838         textFieldListView.setItems(FXCollections.observableArrayList("A", "B", "C"));
 839         textFieldListView.setEditable(true);
 840         textFieldListView.setCellFactory(TextFieldListCell.forListView());
 841         textFieldListView.setOnEditCancel(t -> {
 842             rt_35889_cancel_count++;
 843             System.out.println("On Edit Cancel: " + t);
 844         });
 845 
 846         ListCell cell0 = (ListCell) VirtualFlowTestUtils.getCell(textFieldListView, 0);
 847         assertNull(cell0.getGraphic());
 848         assertEquals("A", cell0.getText());
 849 
 850         textFieldListView.edit(0);
 851         TextField textField = (TextField) cell0.getGraphic();
 852         assertNotNull(textField);
 853 
 854         assertEquals(0, rt_35889_cancel_count);
 855 
 856         textField.setText("Z");
 857         KeyEventFirer keyboard = new KeyEventFirer(textField);
 858         keyboard.doKeyPress(KeyCode.ENTER);
 859 
 860         assertEquals(0, rt_35889_cancel_count);
 861     }
 862 
 863     @Test public void test_rt25679() {
 864         Button focusBtn = new Button("Focus here");
 865 
 866         final ListView<String> listView = new ListView<String>();
 867         SelectionModel sm = listView.getSelectionModel();
 868         listView.setItems(FXCollections.observableArrayList("A", "B", "C"));
 869 
 870         VBox vbox = new VBox(focusBtn, listView);
 871 
 872         StageLoader sl = new StageLoader(vbox);
 873         sl.getStage().requestFocus();
 874         focusBtn.requestFocus();
 875         Toolkit.getToolkit().firePulse();
 876 
 877         // test initial state
 878         assertEquals(sl.getStage().getScene().getFocusOwner(), focusBtn);
 879         assertTrue(focusBtn.isFocused());
 880         assertEquals(-1, sm.getSelectedIndex());
 881         assertNull(sm.getSelectedItem());
 882 
 883         // move focus to the listview
 884         listView.requestFocus();
 885 
 886         // ensure that there is a selection (where previously there was not one)
 887         assertEquals(sl.getStage().getScene().getFocusOwner(), listView);
 888         assertTrue(listView.isFocused());
 889         assertEquals(-1, sm.getSelectedIndex());
 890         assertNull(sm.getSelectedItem());
 891 
 892         sl.dispose();
 893     }
 894 
 895     private int rt_37061_index_counter = 0;
 896     private int rt_37061_item_counter = 0;
 897     @Test public void test_rt_37061() {
 898         ListView<Integer> tv = new ListView<>();
 899         tv.getItems().add(1);
 900         tv.getSelectionModel().select(0);
 901 
 902         // note we add the listeners after the selection is made, so the counters
 903         // at this point are still both at zero.
 904         tv.getSelectionModel().selectedIndexProperty().addListener((observable, oldValue, newValue) -> {
 905             rt_37061_index_counter++;
 906         });
 907 
 908         tv.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
 909             rt_37061_item_counter++;
 910         });
 911 
 912         // add a new item. This does not impact the selected index or selected item
 913         // so the counters should remain at zero.
 914         tv.getItems().add(2);
 915         assertEquals(0, rt_37061_index_counter);
 916         assertEquals(0, rt_37061_item_counter);
 917     }
 918 
 919     private int rt_37538_count = 0;
 920     @Test public void test_rt_37538_noCNextCall() {
 921         test_rt_37538(false, false);
 922     }
 923 
 924     @Test public void test_rt_37538_callCNextOnce() {
 925         test_rt_37538(true, false);
 926     }
 927 
 928     @Test public void test_rt_37538_callCNextInLoop() {
 929         test_rt_37538(false, true);
 930     }
 931 
 932     private void test_rt_37538(boolean callCNextOnce, boolean callCNextInLoop) {
 933         ListView<Integer> list = new ListView<>();
 934         for ( int i = 1; i <= 50; i++ ) {
 935             list.getItems().add(i);
 936         }
 937 
 938         list.getSelectionModel().getSelectedItems().addListener((ListChangeListener.Change<? extends Integer> c) -> {
 939             if (callCNextOnce) {
 940                 c.next();
 941             } else if (callCNextInLoop) {
 942                 while (c.next()) {
 943                     // no-op
 944                 }
 945             }
 946 
 947             if (rt_37538_count >= 1) {
 948                 Thread.dumpStack();
 949                 fail("This method should only be called once");
 950             }
 951 
 952             rt_37538_count++;
 953         });
 954 
 955         StageLoader sl = new StageLoader(list);
 956         assertEquals(0, rt_37538_count);
 957         list.getSelectionModel().select(0);
 958         assertEquals(1, rt_37538_count);
 959         sl.dispose();
 960     }
 961 
 962     @Test
 963     public void test_rt_35395_fixedCellSize() {
 964         test_rt_35395(true);
 965     }
 966 
 967     @Test
 968     public void test_rt_35395_notFixedCellSize() {
 969         test_rt_35395(false);
 970     }
 971 
 972     private int rt_35395_counter;
 973 
 974     private void test_rt_35395(boolean useFixedCellSize) {
 975         rt_35395_counter = 0;
 976 
 977         ObservableList<String> items = FXCollections.observableArrayList();
 978         for (int i = 0; i < 20; ++i) {
 979             items.addAll("red", "green", "blue", "purple");
 980         }
 981 
 982         ListView<String> listView = new ListView<>(items);
 983         if (useFixedCellSize) {
 984             listView.setFixedCellSize(24);
 985         }
 986         listView.setCellFactory(lv -> new ListCell<String>() {
 987             @Override
 988             protected void updateItem(String color, boolean empty) {
 989                 rt_35395_counter += 1;
 990                 super.updateItem(color, empty);
 991                 setText(null);
 992                 if (empty) {
 993                     setGraphic(null);
 994                 } else {
 995                     Rectangle rect = new Rectangle(16, 16);
 996                     rect.setStyle("-fx-fill: " + color);
 997                     setGraphic(rect);
 998                 }
 999             }
1000         });
1001 
1002         StageLoader sl = new StageLoader(listView);
1003 
1004         Platform.runLater(() -> {
1005             rt_35395_counter = 0;
1006             items.set(10, "yellow");
1007             Platform.runLater(() -> {
1008                 Toolkit.getToolkit().firePulse();
1009                 assertEquals(1, rt_35395_counter);
1010                 rt_35395_counter = 0;
1011                 items.set(30, "yellow");
1012                 Platform.runLater(() -> {
1013                     Toolkit.getToolkit().firePulse();
1014                     assertEquals(0, rt_35395_counter);
1015                     rt_35395_counter = 0;
1016                     items.remove(12);
1017                     Platform.runLater(() -> {
1018                         Toolkit.getToolkit().firePulse();
1019                         assertEquals(useFixedCellSize ? 39 : 45, rt_35395_counter);
1020                         rt_35395_counter = 0;
1021                         items.add(12, "yellow");
1022                         Platform.runLater(() -> {
1023                             Toolkit.getToolkit().firePulse();
1024                             assertEquals(useFixedCellSize ? 39 : 45, rt_35395_counter);
1025                             rt_35395_counter = 0;
1026                             listView.scrollTo(5);
1027                             Platform.runLater(() -> {
1028                                 Toolkit.getToolkit().firePulse();
1029                                 assertEquals(5, rt_35395_counter);
1030                                 rt_35395_counter = 0;
1031                                 listView.scrollTo(55);
1032                                 Platform.runLater(() -> {
1033                                     Toolkit.getToolkit().firePulse();
1034                                     assertEquals(useFixedCellSize ? 17 : 53, rt_35395_counter);
1035                                     sl.dispose();
1036                                 });
1037                             });
1038                         });
1039                     });
1040                 });
1041             });
1042         });
1043     }
1044 
1045     @Test public void test_rt_37632() {
1046         final ObservableList<String> listOne = FXCollections.observableArrayList("A", "B", "C");
1047         final ObservableList<String> listTwo = FXCollections.observableArrayList("C");
1048 
1049         final ListView<String> listView = new ListView<>();
1050         MultipleSelectionModel<String> sm = listView.getSelectionModel();
1051         listView.setItems(listOne);
1052         listView.getSelectionModel().selectFirst();
1053 
1054         assertEquals(0, sm.getSelectedIndex());
1055         assertEquals("A", sm.getSelectedItem());
1056         assertEquals(1, sm.getSelectedIndices().size());
1057         assertEquals(0, (int) sm.getSelectedIndices().get(0));
1058         assertEquals(1, sm.getSelectedItems().size());
1059         assertEquals("A", sm.getSelectedItems().get(0));
1060 
1061         listView.setItems(listTwo);
1062 
1063         assertEquals(-1, sm.getSelectedIndex());
1064         assertNull(sm.getSelectedItem());
1065         assertEquals(0, sm.getSelectedIndices().size());
1066         assertEquals(0, sm.getSelectedItems().size());
1067     }
1068 
1069     private int rt_37853_cancelCount;
1070     private int rt_37853_commitCount;
1071     @Test public void test_rt_37853() {
1072         listView.setCellFactory(TextFieldListCell.forListView());
1073         listView.setEditable(true);
1074 
1075         for (int i = 0; i < 10; i++) {
1076             listView.getItems().add("" + i);
1077         }
1078 
1079         StageLoader sl = new StageLoader(listView);
1080 
1081         listView.setOnEditCancel(editEvent -> rt_37853_cancelCount++);
1082         listView.setOnEditCommit(editEvent -> rt_37853_commitCount++);
1083 
1084         assertEquals(0, rt_37853_cancelCount);
1085         assertEquals(0, rt_37853_commitCount);
1086 
1087         listView.edit(1);
1088         assertNotNull(listView.getEditingIndex());
1089 
1090         listView.getItems().clear();
1091         assertEquals(1, rt_37853_cancelCount);
1092         assertEquals(0, rt_37853_commitCount);
1093 
1094         sl.dispose();
1095     }
1096 
1097     @Test public void test_rt_38787_remove_b() {
1098         // selection moves to "a"
1099         test_rt_38787("a", 0, "b");
1100     }
1101 
1102     @Test public void test_rt_38787_remove_b_c() {
1103         // selection moves to "a"
1104         test_rt_38787("a", 0, "b", "c");
1105     }
1106 
1107     @Test public void test_rt_38787_remove_c_d() {
1108         // selection moves to "b"
1109         test_rt_38787("b", 1, "c", "d");
1110     }
1111 
1112     @Test public void test_rt_38787_remove_a() {
1113         // selection moves to "b", now in index 0
1114         test_rt_38787("b", 0, "a");
1115     }
1116 
1117     @Test public void test_rt_38787_remove_z() {
1118         // selection shouldn't move as 'z' doesn't exist
1119         test_rt_38787("b", 1, "z");
1120     }
1121 
1122     private void test_rt_38787(String expectedItem, int expectedIndex, String... itemsToRemove) {
1123         ListView<String> stringListView = new ListView<>();
1124         stringListView.getItems().addAll("a","b","c","d");
1125 
1126         MultipleSelectionModel<String> sm = stringListView.getSelectionModel();
1127         sm.select("b");
1128 
1129         // test pre-conditions
1130         assertEquals(1, sm.getSelectedIndex());
1131         assertEquals(1, (int)sm.getSelectedIndices().get(0));
1132         assertEquals("b", sm.getSelectedItem());
1133         assertEquals("b", sm.getSelectedItems().get(0));
1134         assertFalse(sm.isSelected(0));
1135         assertTrue(sm.isSelected(1));
1136         assertFalse(sm.isSelected(2));
1137 
1138         // removing items
1139         stringListView.getItems().removeAll(itemsToRemove);
1140 
1141         // testing against expectations
1142         assertEquals(expectedIndex, sm.getSelectedIndex());
1143         assertEquals(expectedIndex, (int)sm.getSelectedIndices().get(0));
1144         assertEquals(expectedItem, sm.getSelectedItem());
1145         assertEquals(expectedItem, sm.getSelectedItems().get(0));
1146     }
1147 
1148     private int rt_38341_indices_count = 0;
1149     private int rt_38341_items_count = 0;
1150     @Test public void test_rt_38341() {
1151         ListView<String> stringListView = new ListView<>();
1152         stringListView.getItems().addAll("a","b","c","d");
1153 
1154         MultipleSelectionModel<String> sm = stringListView.getSelectionModel();
1155         sm.getSelectedIndices().addListener((ListChangeListener<Integer>) c -> rt_38341_indices_count++);
1156         sm.getSelectedItems().addListener((ListChangeListener<String>) c -> rt_38341_items_count++);
1157 
1158         assertEquals(0, rt_38341_indices_count);
1159         assertEquals(0, rt_38341_items_count);
1160 
1161         // expand the first child of root, and select it (note: root isn't visible)
1162         sm.select(1);
1163         assertEquals(1, sm.getSelectedIndex());
1164         assertEquals(1, sm.getSelectedIndices().size());
1165         assertEquals(1, (int)sm.getSelectedIndices().get(0));
1166         assertEquals(1, sm.getSelectedItems().size());
1167         assertEquals("b", sm.getSelectedItem());
1168         assertEquals("b", sm.getSelectedItems().get(0));
1169 
1170         assertEquals(1, rt_38341_indices_count);
1171         assertEquals(1, rt_38341_items_count);
1172 
1173         // now delete it
1174         stringListView.getItems().remove(1);
1175 
1176         // selection should move to the childs parent in index 0
1177         assertEquals(0, sm.getSelectedIndex());
1178         assertEquals(1, sm.getSelectedIndices().size());
1179         assertEquals(0, (int)sm.getSelectedIndices().get(0));
1180         assertEquals(1, sm.getSelectedItems().size());
1181         assertEquals("a", sm.getSelectedItem());
1182         assertEquals("a", sm.getSelectedItems().get(0));
1183 
1184         // we also expect there to be an event in the selection model for
1185         // selected indices and selected items
1186         assertEquals(sm.getSelectedIndices() +"", 2, rt_38341_indices_count);
1187         assertEquals(2, rt_38341_items_count);
1188     }
1189 
1190     @Test public void test_rt_39132() {
1191         ObservableList items = FXCollections.observableArrayList("one", "two", "three");
1192         ListView listView = new ListView<>();
1193         listView.setItems(items);
1194 
1195         MultipleSelectionModel sm = listView.getSelectionModel();
1196         sm.select(0);
1197 
1198         assertEquals(0, sm.getSelectedIndex());
1199         assertEquals("one", sm.getSelectedItem());
1200 
1201         items.add(0, "new item");
1202         assertEquals(1, sm.getSelectedIndex());
1203         assertEquals("one", sm.getSelectedItem());
1204     }
1205 
1206     private int rt_38943_index_count = 0;
1207     private int rt_38943_item_count = 0;
1208     @Test public void test_rt_38943() {
1209         ListView<String> listView = new ListView<>(FXCollections.observableArrayList("one", "two", "three"));
1210 
1211         MultipleSelectionModel sm = listView.getSelectionModel();
1212 
1213         sm.selectedIndexProperty().addListener((observable, oldValue, newValue) -> rt_38943_index_count++);
1214         sm.selectedItemProperty().addListener((observable, oldValue, newValue) -> rt_38943_item_count++);
1215 
1216         assertEquals(-1, sm.getSelectedIndex());
1217         assertNull(sm.getSelectedItem());
1218         assertEquals(0, rt_38943_index_count);
1219         assertEquals(0, rt_38943_item_count);
1220 
1221         sm.select(0);
1222         assertEquals(0, sm.getSelectedIndex());
1223         assertEquals("one", sm.getSelectedItem());
1224         assertEquals(1, rt_38943_index_count);
1225         assertEquals(1, rt_38943_item_count);
1226 
1227         sm.clearSelection(0);
1228         assertEquals(-1, sm.getSelectedIndex());
1229         assertNull(sm.getSelectedItem());
1230         assertEquals(2, rt_38943_index_count);
1231         assertEquals(2, rt_38943_item_count);
1232     }
1233 
1234     @Test public void test_rt_38884() {
1235         ListView<String> listView = new ListView<>();
1236         ObservableList<String> items = listView.getItems();
1237 
1238         listView.getSelectionModel().getSelectedItems().addListener((ListChangeListener.Change<? extends String> c) -> {
1239             while (c.next()) {
1240                 if (c.wasRemoved()) {
1241                     assertTrue(c.getRemovedSize() > 0);
1242 
1243                     List<? extends String> removed = c.getRemoved();
1244                     String removedItem = null;
1245                     try {
1246                         removedItem = removed.get(0);
1247                     } catch (Exception e) {
1248                         fail();
1249                     }
1250 
1251                     assertEquals("foo", removedItem);
1252                 }
1253             }
1254         });
1255 
1256         items.add("foo");
1257         listView.getSelectionModel().select(0);
1258         items.clear();
1259     }
1260 
1261     private int rt_37360_add_count = 0;
1262     private int rt_37360_remove_count = 0;
1263     @Test public void test_rt_37360() {
1264         ListView<String> stringListView = new ListView<>();
1265         stringListView.getItems().addAll("a", "b");
1266 
1267         MultipleSelectionModel<String> sm = stringListView.getSelectionModel();
1268         sm.setSelectionMode(SelectionMode.MULTIPLE);
1269         sm.getSelectedItems().addListener((ListChangeListener<String>) c -> {
1270             while (c.next()) {
1271                 if (c.wasAdded()) {
1272                     rt_37360_add_count += c.getAddedSize();
1273                 }
1274                 if (c.wasRemoved()) {
1275                     rt_37360_remove_count += c.getRemovedSize();
1276                 }
1277             }
1278         });
1279 
1280         assertEquals(0, sm.getSelectedItems().size());
1281         assertEquals(0, rt_37360_add_count);
1282         assertEquals(0, rt_37360_remove_count);
1283 
1284         sm.select(0);
1285         assertEquals(1, sm.getSelectedItems().size());
1286         assertEquals(1, rt_37360_add_count);
1287         assertEquals(0, rt_37360_remove_count);
1288 
1289         sm.select(1);
1290         assertEquals(2, sm.getSelectedItems().size());
1291         assertEquals(2, rt_37360_add_count);
1292         assertEquals(0, rt_37360_remove_count);
1293 
1294         sm.clearAndSelect(1);
1295         assertEquals(1, sm.getSelectedItems().size());
1296         assertEquals(2, rt_37360_add_count);
1297         assertEquals(1, rt_37360_remove_count);
1298     }
1299 
1300     @Test public void test_rt_38491() {
1301         ListView<String> stringListView = new ListView<>();
1302         stringListView.getItems().addAll("a", "b");
1303 
1304         MultipleSelectionModel<String> sm = stringListView.getSelectionModel();
1305         sm.setSelectionMode(SelectionMode.MULTIPLE);
1306 
1307         FocusModel<String> fm = stringListView.getFocusModel();
1308 
1309         // click on row 0
1310         VirtualFlowTestUtils.clickOnRow(stringListView, 0);
1311         assertTrue(sm.isSelected(0));
1312         assertEquals("a", sm.getSelectedItem());
1313         assertTrue(fm.isFocused(0));
1314         assertEquals("a", fm.getFocusedItem());
1315         assertEquals(0, fm.getFocusedIndex());
1316 
1317         int anchor = ListCellBehavior.getAnchor(stringListView, null);
1318         assertTrue(ListCellBehavior.hasNonDefaultAnchor(stringListView));
1319         assertEquals(0, anchor);
1320 
1321         // now add a new item at row 0. This has the effect of pushing down
1322         // the selected item into row 1.
1323         stringListView.getItems().add(0, "z");
1324 
1325         // The first bug was that selection and focus were not moving down to
1326         // be on row 1, so we test that now
1327         assertFalse(sm.isSelected(0));
1328         assertFalse(fm.isFocused(0));
1329         assertTrue(sm.isSelected(1));
1330         assertEquals("a", sm.getSelectedItem());
1331         assertTrue(fm.isFocused(1));
1332         assertEquals("a", fm.getFocusedItem());
1333         assertEquals(1, fm.getFocusedIndex());
1334 
1335         // The second bug was that the anchor was not being pushed down as well
1336         // (when it should).
1337         anchor = ListCellBehavior.getAnchor(stringListView, null);
1338         assertTrue(ListCellBehavior.hasNonDefaultAnchor(stringListView));
1339         assertEquals(1, anchor);
1340     }
1341 
1342     private final ObservableList<String> rt_39256_list = FXCollections.observableArrayList();
1343     @Test public void test_rt_39256() {
1344         ListView<String> stringListView = new ListView<>();
1345         stringListView.getItems().addAll("a","b", "c", "d");
1346 
1347         MultipleSelectionModel<String> sm = stringListView.getSelectionModel();
1348         sm.setSelectionMode(SelectionMode.MULTIPLE);
1349 
1350 //        rt_39256_list.addListener((ListChangeListener<String>) change -> {
1351 //            while (change.next()) {
1352 //                System.err.println("number of selected persons (in bound list): " + change.getList().size());
1353 //            }
1354 //        });
1355 
1356         Bindings.bindContent(rt_39256_list, sm.getSelectedItems());
1357 
1358         assertEquals(0, sm.getSelectedItems().size());
1359         assertEquals(0, rt_39256_list.size());
1360 
1361         sm.selectAll();
1362         assertEquals(4, sm.getSelectedItems().size());
1363         assertEquals(4, rt_39256_list.size());
1364 
1365         sm.selectAll();
1366         assertEquals(4, sm.getSelectedItems().size());
1367         assertEquals(4, rt_39256_list.size());
1368 
1369         sm.selectAll();
1370         assertEquals(4, sm.getSelectedItems().size());
1371         assertEquals(4, rt_39256_list.size());
1372     }
1373 
1374     private final ObservableList<String> rt_39482_list = FXCollections.observableArrayList();
1375     @Test public void test_rt_39482() {
1376         ListView<String> stringListView = new ListView<>();
1377         stringListView.getItems().addAll("a", "b", "c", "d");
1378 
1379         MultipleSelectionModel<String> sm = stringListView.getSelectionModel();
1380         sm.setSelectionMode(SelectionMode.MULTIPLE);
1381 
1382         sm.getSelectedItems().addListener((ListChangeListener<String>) change -> {
1383             while (change.next()) {
1384                 System.out.println("sm.getSelectedItems(): " + change.getList());
1385             }
1386         });
1387 
1388         rt_39482_list.addListener((ListChangeListener<String>) change -> {
1389             while (change.next()) {
1390                 System.out.println("rt_39482_list: " + change.getList());
1391             }
1392         });
1393 
1394         Bindings.bindContent(rt_39482_list, sm.getSelectedItems());
1395 
1396         assertEquals(0, sm.getSelectedItems().size());
1397         assertEquals(0, rt_39482_list.size());
1398 
1399         test_rt_39482_selectRow("a", sm, 0);
1400         test_rt_39482_selectRow("b", sm, 1);
1401         test_rt_39482_selectRow("c", sm, 2);
1402         test_rt_39482_selectRow("d", sm, 3);
1403     }
1404 
1405     private void test_rt_39482_selectRow(String expectedString,
1406                                          MultipleSelectionModel<String> sm,
1407                                          int rowToSelect) {
1408         System.out.println("\nSelect row " + rowToSelect);
1409         sm.selectAll();
1410         assertEquals(4, sm.getSelectedIndices().size());
1411         assertEquals(4, sm.getSelectedItems().size());
1412         assertEquals(4, rt_39482_list.size());
1413 
1414         sm.clearAndSelect(rowToSelect);
1415         assertEquals(1, sm.getSelectedIndices().size());
1416         assertEquals(1, sm.getSelectedItems().size());
1417         assertEquals(expectedString, sm.getSelectedItem());
1418         assertEquals(expectedString, rt_39482_list.get(0));
1419         assertEquals(1, rt_39482_list.size());
1420     }
1421 
1422     @Test public void test_rt_39559_useSM_selectAll() {
1423         test_rt_39559(true);
1424     }
1425 
1426     @Test public void test_rt_39559_useKeyboard_selectAll() {
1427         test_rt_39559(false);
1428     }
1429 
1430     private void test_rt_39559(boolean useSMSelectAll) {
1431         ListView<String> stringListView = new ListView<>();
1432         stringListView.getItems().addAll("a", "b", "c", "d");
1433 
1434         MultipleSelectionModel<String> sm = stringListView.getSelectionModel();
1435         sm.setSelectionMode(SelectionMode.MULTIPLE);
1436 
1437         StageLoader sl = new StageLoader(stringListView);
1438         KeyEventFirer keyboard = new KeyEventFirer(stringListView);
1439 
1440         assertEquals(0, sm.getSelectedItems().size());
1441 
1442         sm.clearAndSelect(0);
1443 
1444         if (useSMSelectAll) {
1445             sm.selectAll();
1446         } else {
1447             keyboard.doKeyPress(KeyCode.A, KeyModifier.getShortcutKey());
1448         }
1449 
1450         assertEquals(4, sm.getSelectedItems().size());
1451         assertEquals(0, (int) ListCellBehavior.getAnchor(stringListView, -1));
1452 
1453         keyboard.doKeyPress(KeyCode.DOWN, KeyModifier.SHIFT);
1454 
1455         assertEquals(0, (int) ListCellBehavior.getAnchor(stringListView, -1));
1456         assertEquals(2, sm.getSelectedItems().size());
1457         assertEquals("a", sm.getSelectedItems().get(0));
1458         assertEquals("b", sm.getSelectedItems().get(1));
1459 
1460         sl.dispose();
1461     }
1462 
1463     @Test public void test_rt_16068_firstElement_selectAndRemoveSameRow() {
1464         // select and then remove the 'a' item, selection and focus should both
1465         // stay at the first row, now 'b'
1466         test_rt_16068(0, 0, 0);
1467     }
1468 
1469     @Test public void test_rt_16068_firstElement_selectRowAndRemoveLaterSibling() {
1470         // select row 'a', and remove row 'c', selection and focus should not change
1471         test_rt_16068(0, 2, 0);
1472     }
1473 
1474     @Test public void test_rt_16068_middleElement_selectAndRemoveSameRow() {
1475         // select and then remove the 'b' item, selection and focus should both
1476         // move up one row to the 'a' item
1477         test_rt_16068(1, 1, 0);
1478     }
1479 
1480     @Test public void test_rt_16068_middleElement_selectRowAndRemoveLaterSibling() {
1481         // select row 'b', and remove row 'c', selection and focus should not change
1482         test_rt_16068(1, 2, 1);
1483     }
1484 
1485     @Test public void test_rt_16068_middleElement_selectRowAndRemoveEarlierSibling() {
1486         // select row 'b', and remove row 'a', selection and focus should move up
1487         // one row, remaining on 'b'
1488         test_rt_16068(1, 0, 0);
1489     }
1490 
1491     @Test public void test_rt_16068_lastElement_selectAndRemoveSameRow() {
1492         // select and then remove the 'd' item, selection and focus should both
1493         // move up one row to the 'c' item
1494         test_rt_16068(3, 3, 2);
1495     }
1496 
1497     @Test public void test_rt_16068_lastElement_selectRowAndRemoveEarlierSibling() {
1498         // select row 'd', and remove row 'a', selection and focus should move up
1499         // one row, remaining on 'd'
1500         test_rt_16068(3, 0, 2);
1501     }
1502 
1503     private void test_rt_16068(int indexToSelect, int indexToRemove, int expectedIndex) {
1504         ListView<String> stringListView = new ListView<>();
1505         stringListView.getItems().addAll("a", "b", "c", "d");
1506 
1507         MultipleSelectionModel<?> sm = stringListView.getSelectionModel();
1508         FocusModel<?> fm = stringListView.getFocusModel();
1509 
1510         sm.select(indexToSelect);
1511         assertEquals(indexToSelect, sm.getSelectedIndex());
1512         assertEquals(stringListView.getItems().get(indexToSelect), sm.getSelectedItem());
1513         assertEquals(indexToSelect, fm.getFocusedIndex());
1514         assertEquals(stringListView.getItems().get(indexToSelect), fm.getFocusedItem());
1515 
1516         stringListView.getItems().remove(indexToRemove);
1517         assertEquals(expectedIndex, sm.getSelectedIndex());
1518         assertEquals(stringListView.getItems().get(expectedIndex), sm.getSelectedItem());
1519         assertEquals(expectedIndex, fm.getFocusedIndex());
1520         assertEquals(stringListView.getItems().get(expectedIndex), fm.getFocusedItem());
1521     }
1522 
1523     @Test public void test_rt_22599() {
1524         ObservableList<RT22599_DataType> initialData = FXCollections.observableArrayList(
1525                 new RT22599_DataType(1, "row1"),
1526                 new RT22599_DataType(2, "row2"),
1527                 new RT22599_DataType(3, "row3")
1528         );
1529 
1530         ListView<RT22599_DataType> listView = new ListView<>();
1531         listView.setItems(initialData);
1532 
1533         StageLoader sl = new StageLoader(listView);
1534 
1535         // testing initial state
1536         assertNotNull(listView.getSkin());
1537         assertEquals("row1", VirtualFlowTestUtils.getCell(listView, 0).getText());
1538         assertEquals("row2", VirtualFlowTestUtils.getCell(listView, 1).getText());
1539         assertEquals("row3", VirtualFlowTestUtils.getCell(listView, 2).getText());
1540 
1541         // change row 0 (where "row1" currently resides), keeping same id.
1542         // Because 'set' is called, the control should update to the new content
1543         // without any user interaction
1544         RT22599_DataType data;
1545         initialData.set(0, data = new RT22599_DataType(0, "row1a"));
1546         Toolkit.getToolkit().firePulse();
1547         assertEquals("row1a", VirtualFlowTestUtils.getCell(listView, 0).getText());
1548 
1549         // change the row 0 (where we currently have "row1a") value directly.
1550         // Because there is no associated property, this won't be observed, so
1551         // the control should still show "row1a" rather than "row1b"
1552         data.text = "row1b";
1553         Toolkit.getToolkit().firePulse();
1554         assertEquals("row1a", VirtualFlowTestUtils.getCell(listView, 0).getText());
1555 
1556         // call refresh() to force a refresh of all visible cells
1557         listView.refresh();
1558         Toolkit.getToolkit().firePulse();
1559         assertEquals("row1b", VirtualFlowTestUtils.getCell(listView, 0).getText());
1560 
1561         sl.dispose();
1562     }
1563 
1564     private static class RT22599_DataType {
1565         public int id = 0;
1566         public String text = "";
1567 
1568         public RT22599_DataType(int id, String text) {
1569             this.id = id;
1570             this.text = text;
1571         }
1572 
1573         @Override public String toString() {
1574             return text;
1575         }
1576 
1577         @Override public boolean equals(Object obj) {
1578             if (obj == null) return false;
1579             return id == ((RT22599_DataType)obj).id;
1580         }
1581     }
1582 
1583     private int rt_39966_count = 0;
1584     @Test public void test_rt_39966() {
1585         ObservableList<String> list = FXCollections.observableArrayList("Hello World");
1586         ListView<String> listView = new ListView<>(list);
1587 
1588         StageLoader sl = new StageLoader(listView);
1589 
1590         // initially there is no selection
1591         assertTrue(listView.getSelectionModel().isEmpty());
1592 
1593         listView.getSelectionModel().selectedItemProperty().addListener((value, s1, s2) -> {
1594             if (rt_39966_count == 0) {
1595                 rt_39966_count++;
1596                 assertFalse(listView.getSelectionModel().isEmpty());
1597             } else {
1598                 assertTrue(listView.getSelectionModel().isEmpty());
1599             }
1600         });
1601 
1602         // our assertion two lines down always succeeds. What fails is our
1603         // assertion above within the listener.
1604         listView.getSelectionModel().select(0);
1605         assertFalse(listView.getSelectionModel().isEmpty());
1606 
1607         list.remove(0);
1608         assertTrue(listView.getSelectionModel().isEmpty());
1609 
1610         sl.dispose();
1611     }
1612 
1613     /**
1614      * Bullet 1: selected index must be updated
1615      * Corner case: last selected. Fails for core
1616      */
1617     @Test public void test_rt_40012_selectedAtLastOnDisjointRemoveItemsAbove() {
1618         ObservableList<String> items = FXCollections.observableArrayList("0", "1", "2", "3", "4", "5");
1619         ListView<String> listView = new ListView<>(items);
1620         SelectionModel sm = listView.getSelectionModel();
1621 
1622         int last = items.size() - 1;
1623 
1624         // selecting item "5"
1625         sm.select(last);
1626 
1627         // disjoint remove of 2 elements above the last selected
1628         // Removing "1" and "3"
1629         items.removeAll(items.get(1), items.get(3));
1630 
1631         // selection should move up two places such that it remains on item "5",
1632         // but in index (last - 2).
1633         int expected = last - 2;
1634         assertEquals("5", sm.getSelectedItem());
1635         assertEquals("selected index after disjoint removes above", expected, sm.getSelectedIndex());
1636     }
1637 
1638     /**
1639      * Variant of 1: if selectedIndex is not updated,
1640      * the old index is no longer valid
1641      * for accessing the items.
1642      */
1643     @Test public void test_rt_40012_accessSelectedAtLastOnDisjointRemoveItemsAbove() {
1644         ObservableList<String> items = FXCollections.observableArrayList("0", "1", "2", "3", "4", "5");
1645         ListView<String> listView = new ListView<>(items);
1646         SelectionModel sm = listView.getSelectionModel();
1647 
1648         int last = items.size() - 1;
1649 
1650         // selecting item "5"
1651         sm.select(last);
1652 
1653         // disjoint remove of 2 elements above the last selected
1654         items.removeAll(items.get(1), items.get(3));
1655         int selected = sm.getSelectedIndex();
1656         if (selected > -1) {
1657             items.get(selected);
1658         }
1659     }
1660 
1661     /**
1662      * Bullet 2: selectedIndex notification count
1663      *
1664      * Note that we don't use the corner case of having the last index selected
1665      * (which fails already on updating the index)
1666      */
1667     private int rt_40012_count = 0;
1668     @Test public void test_rt_40012_selectedIndexNotificationOnDisjointRemovesAbove() {
1669         ObservableList<String> items = FXCollections.observableArrayList("0", "1", "2", "3", "4", "5");
1670         ListView<String> listView = new ListView<>(items);
1671         SelectionModel sm = listView.getSelectionModel();
1672 
1673         int last = items.size() - 2;
1674         sm.select(last);
1675         assertEquals(last, sm.getSelectedIndex());
1676 
1677         rt_40012_count = 0;
1678         sm.selectedIndexProperty().addListener(o -> rt_40012_count++);
1679 
1680         // disjoint remove of 2 elements above the last selected
1681         items.removeAll(items.get(1), items.get(3));
1682         assertEquals("sanity: selectedIndex must be shifted by -2", last - 2, sm.getSelectedIndex());
1683         assertEquals("must fire single event on removes above", 1, rt_40012_count);
1684     }
1685 
1686     /**
1687      * Bullet 3: unchanged selectedItem must not fire change
1688      */
1689     @Test
1690     public void test_rt_40012_selectedItemNotificationOnDisjointRemovesAbove() {
1691         ObservableList<String> items = FXCollections.observableArrayList("0", "1", "2", "3", "4", "5");
1692         ListView<String> listView = new ListView<>(items);
1693         SelectionModel sm = listView.getSelectionModel();
1694 
1695         int last = items.size() - 2;
1696         Object lastItem = items.get(last);
1697         sm.select(last);
1698         assertEquals(lastItem, sm.getSelectedItem());
1699 
1700         rt_40012_count = 0;
1701         sm.selectedItemProperty().addListener(o -> rt_40012_count++);
1702 
1703         // disjoint remove of 2 elements above the last selected
1704         items.removeAll(items.get(1), items.get(3));
1705         assertEquals("sanity: selectedItem unchanged", lastItem, sm.getSelectedItem());
1706         assertEquals("must not fire on unchanged selected item", 0, rt_40012_count);
1707     }
1708 }