1 /*
   2  * Copyright (c) 2011, 2015, 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 com.sun.javafx.application.PlatformImpl;
  29 import com.sun.javafx.scene.control.behavior.TreeCellBehavior;
  30 import com.sun.javafx.scene.control.infrastructure.KeyEventFirer;
  31 import com.sun.javafx.scene.control.infrastructure.KeyModifier;
  32 import com.sun.javafx.scene.control.infrastructure.StageLoader;
  33 import com.sun.javafx.scene.control.infrastructure.VirtualFlowTestUtils;
  34 import javafx.scene.control.skin.TextFieldSkin;
  35 import com.sun.javafx.scene.control.test.Employee;
  36 import com.sun.javafx.scene.control.test.Person;
  37 import com.sun.javafx.scene.control.test.RT_22463_Person;
  38 import com.sun.javafx.tk.Toolkit;
  39 
  40 import java.util.*;
  41 import java.util.stream.Collectors;
  42 
  43 import javafx.application.Platform;
  44 import javafx.beans.InvalidationListener;
  45 import javafx.beans.Observable;
  46 import static com.sun.javafx.scene.control.infrastructure.ControlTestUtils.assertStyleClassContains;
  47 import static org.junit.Assert.*;
  48 import static org.junit.Assert.assertEquals;
  49 
  50 import javafx.beans.binding.Bindings;
  51 import javafx.beans.property.ObjectProperty;
  52 import javafx.beans.property.ReadOnlyBooleanWrapper;
  53 import javafx.beans.property.SimpleObjectProperty;
  54 import javafx.collections.FXCollections;
  55 import javafx.collections.ListChangeListener;
  56 import javafx.collections.ObservableList;
  57 import javafx.event.ActionEvent;
  58 import javafx.scene.Group;
  59 import javafx.scene.Node;
  60 import javafx.scene.Scene;
  61 import javafx.scene.control.cell.CheckBoxTreeCell;
  62 import javafx.scene.control.cell.TextFieldTreeCell;
  63 import com.sun.javafx.scene.control.VirtualScrollBar;
  64 import javafx.scene.image.ImageView;
  65 import javafx.scene.input.KeyCode;
  66 import javafx.scene.layout.VBox;
  67 import javafx.scene.paint.Color;
  68 import javafx.scene.shape.Circle;
  69 import javafx.scene.shape.Rectangle;
  70 import javafx.stage.Stage;
  71 import javafx.util.Callback;
  72 
  73 import org.junit.Before;
  74 import org.junit.Ignore;
  75 import org.junit.Test;
  76 
  77 public class TreeViewTest {
  78     private TreeView<String> treeView;
  79     private MultipleSelectionModel<TreeItem<String>> sm;
  80     private FocusModel<TreeItem<String>> fm;
  81     
  82     // sample data #1
  83     private TreeItem<String> root;
  84     private TreeItem<String> child1;
  85     private TreeItem<String> child2;
  86     private TreeItem<String> child3;
  87     
  88     // sample data #1
  89     private TreeItem<String> myCompanyRootNode;
  90         private TreeItem<String> salesDepartment;
  91             private TreeItem<String> ethanWilliams;
  92             private TreeItem<String> emmaJones;
  93             private TreeItem<String> michaelBrown;
  94             private TreeItem<String> annaBlack;
  95             private TreeItem<String> rodgerYork;
  96             private TreeItem<String> susanCollins;
  97 
  98         private TreeItem<String> itSupport;
  99             private TreeItem<String> mikeGraham;
 100             private TreeItem<String> judyMayer;
 101             private TreeItem<String> gregorySmith;
 102             
 103     private String debug() {
 104         StringBuilder sb = new StringBuilder("Selected Indices: [");
 105         
 106         List<Integer> indices = sm.getSelectedIndices();
 107         for (Integer index : indices) {
 108             sb.append(index);
 109             sb.append(", ");
 110         }
 111         
 112         sb.append("] \nFocus: " + fm.getFocusedIndex());
 113 //        sb.append(" \nAnchor: " + getAnchor());
 114         return sb.toString();
 115     }
 116     
 117     @Before public void setup() {
 118         treeView = new TreeView<String>();
 119         sm = treeView.getSelectionModel();
 120         fm = treeView.getFocusModel();
 121         
 122         // build sample data #2, even though it may not be used...
 123         myCompanyRootNode = new TreeItem<String>("MyCompany Human Resources");
 124         salesDepartment = new TreeItem<String>("Sales Department");
 125             ethanWilliams = new TreeItem<String>("Ethan Williams");
 126             emmaJones = new TreeItem<String>("Emma Jones");
 127             michaelBrown = new TreeItem<String>("Michael Brown");
 128             annaBlack = new TreeItem<String>("Anna Black");
 129             rodgerYork = new TreeItem<String>("Rodger York");
 130             susanCollins = new TreeItem<String>("Susan Collins");
 131 
 132         itSupport = new TreeItem<String>("IT Support");
 133             mikeGraham = new TreeItem<String>("Mike Graham");
 134             judyMayer = new TreeItem<String>("Judy Mayer");
 135             gregorySmith = new TreeItem<String>("Gregory Smith");
 136             
 137         myCompanyRootNode.getChildren().setAll(
 138             salesDepartment,
 139             itSupport
 140         );
 141         salesDepartment.getChildren().setAll(
 142             ethanWilliams,
 143             emmaJones,
 144             michaelBrown, 
 145             annaBlack,
 146             rodgerYork,
 147             susanCollins
 148         );
 149         itSupport.getChildren().setAll(
 150             mikeGraham,
 151             judyMayer,
 152             gregorySmith
 153         );
 154     }
 155 
 156     private void installChildren() {
 157         root = new TreeItem<String>("Root");
 158         child1 = new TreeItem<String>("Child 1");
 159         child2 = new TreeItem<String>("Child 2");
 160         child3 = new TreeItem<String>("Child 3");
 161         root.setExpanded(true);
 162         root.getChildren().setAll(child1, child2, child3);
 163         treeView.setRoot(root);
 164     }
 165     
 166     @Test public void ensureCorrectInitialState() {
 167         installChildren();
 168         assertEquals(0, treeView.getRow(root));
 169         assertEquals(1, treeView.getRow(child1));
 170         assertEquals(2, treeView.getRow(child2));
 171         assertEquals(3, treeView.getRow(child3));
 172     }
 173 
 174     /*********************************************************************
 175      * Tests for the constructors                                        *
 176      ********************************************************************/
 177     
 178     @Test public void noArgConstructorSetsTheStyleClass() {
 179         assertStyleClassContains(treeView, "tree-view");
 180     }
 181 
 182     @Test public void noArgConstructorSetsNonNullSelectionModel() {
 183         assertNotNull(treeView.getSelectionModel());
 184     }
 185 
 186     @Test public void noArgConstructorSetsNullItems() {
 187         assertNull(treeView.getRoot());
 188     }
 189 
 190     @Test public void noArgConstructor_selectedItemIsNull() {
 191         assertNull(treeView.getSelectionModel().getSelectedItem());
 192     }
 193 
 194     @Test public void noArgConstructor_selectedIndexIsNegativeOne() {
 195         assertEquals(-1, treeView.getSelectionModel().getSelectedIndex());
 196     }
 197 
 198     @Test public void singleArgConstructorSetsTheStyleClass() {
 199         final TreeView<String> b2 = new TreeView<>(new TreeItem<>("Hi"));
 200         assertStyleClassContains(b2, "tree-view");
 201     }
 202 
 203     @Test public void singleArgConstructorSetsNonNullSelectionModel() {
 204         final TreeView<String> b2 = new TreeView<>(new TreeItem<>("Hi"));
 205         assertNotNull(b2.getSelectionModel());
 206     }
 207 
 208     @Test public void singleArgConstructorAllowsNullItems() {
 209         final TreeView<String> b2 = new TreeView<>(null);
 210         assertNull(b2.getRoot());
 211     }
 212 
 213     @Test public void singleArgConstructor_selectedItemIsNotNull() {
 214         TreeItem<String> hiItem = new TreeItem<>("Hi");
 215         final TreeView<String> b2 = new TreeView<>(hiItem);
 216         assertNull(b2.getSelectionModel().getSelectedItem());
 217     }
 218 
 219     @Test public void singleArgConstructor_selectedIndexIsZero() {
 220         final TreeView<String> b2 = new TreeView<>(new TreeItem<>("Hi"));
 221         assertEquals(-1, b2.getSelectionModel().getSelectedIndex());
 222     }
 223 
 224     /*********************************************************************
 225      * Tests for selection model                                         *
 226      ********************************************************************/
 227 
 228     @Test public void selectionModelCanBeNull() {
 229         treeView.setSelectionModel(null);
 230         assertNull(treeView.getSelectionModel());
 231     }
 232 
 233     @Test public void selectionModelCanBeBound() {
 234         MultipleSelectionModel<TreeItem<String>> sm = new TreeView.TreeViewBitSetSelectionModel<String>(treeView);
 235         ObjectProperty<MultipleSelectionModel<TreeItem<String>>> other = new SimpleObjectProperty<MultipleSelectionModel<TreeItem<String>>>(sm);
 236         treeView.selectionModelProperty().bind(other);
 237         assertSame(sm, treeView.getSelectionModel());
 238     }
 239 
 240     @Test public void selectionModelCanBeChanged() {
 241         MultipleSelectionModel<TreeItem<String>> sm = new TreeView.TreeViewBitSetSelectionModel<String>(treeView);
 242         treeView.setSelectionModel(sm);
 243         assertSame(sm, treeView.getSelectionModel());
 244     }
 245 
 246     @Test public void canSetSelectedItemToAnItemEvenWhenThereAreNoItems() {
 247         TreeItem<String> element = new TreeItem<String>("I AM A CRAZY RANDOM STRING");
 248         treeView.getSelectionModel().select(element);
 249         assertEquals(-1, treeView.getSelectionModel().getSelectedIndex());
 250         assertSame(element, treeView.getSelectionModel().getSelectedItem());
 251     }
 252 
 253     @Test public void canSetSelectedItemToAnItemNotInTheDataModel() {
 254         installChildren();
 255         TreeItem<String> element = new TreeItem<String>("I AM A CRAZY RANDOM STRING");
 256         treeView.getSelectionModel().select(element);
 257         assertEquals(-1, treeView.getSelectionModel().getSelectedIndex());
 258         assertSame(element, treeView.getSelectionModel().getSelectedItem());
 259     }
 260 
 261     @Test public void settingTheSelectedItemToAnItemInItemsResultsInTheCorrectSelectedIndex() {
 262         installChildren();
 263         treeView.getSelectionModel().select(child1);
 264         assertEquals(1, treeView.getSelectionModel().getSelectedIndex());
 265         assertSame(child1, treeView.getSelectionModel().getSelectedItem());
 266     }
 267 
 268     @Ignore("Not yet supported")
 269     @Test public void settingTheSelectedItemToANonexistantItemAndThenSettingItemsWhichContainsItResultsInCorrectSelectedIndex() {
 270         treeView.getSelectionModel().select(child1);
 271         installChildren();
 272         assertEquals(1, treeView.getSelectionModel().getSelectedIndex());
 273         assertSame(child1, treeView.getSelectionModel().getSelectedItem());
 274     }
 275     
 276     @Ignore("Not yet supported")
 277     @Test public void ensureSelectionClearsWhenAllItemsAreRemoved_selectIndex0() {
 278         installChildren();
 279         treeView.getSelectionModel().select(0);
 280         treeView.setRoot(null);
 281         assertEquals(-1, treeView.getSelectionModel().getSelectedIndex());
 282         assertEquals(null, treeView.getSelectionModel().getSelectedItem());
 283     }
 284     
 285     @Ignore("Not yet supported")
 286     @Test public void ensureSelectionClearsWhenAllItemsAreRemoved_selectIndex2() {
 287         installChildren();
 288         treeView.getSelectionModel().select(2);
 289         treeView.setRoot(null);
 290         assertEquals(-1, treeView.getSelectionModel().getSelectedIndex());
 291         assertEquals(null, treeView.getSelectionModel().getSelectedItem());
 292     }
 293     
 294     @Ignore("Not yet supported")
 295     @Test public void ensureSelectedItemRemainsAccurateWhenItemsAreCleared() {
 296         installChildren();
 297         treeView.getSelectionModel().select(2);
 298         treeView.setRoot(null);
 299         assertNull(treeView.getSelectionModel().getSelectedItem());
 300         assertEquals(-1, treeView.getSelectionModel().getSelectedIndex());
 301         
 302         TreeItem<String> newRoot = new TreeItem<String>("New Root");
 303         TreeItem<String> newChild1 = new TreeItem<String>("New Child 1");
 304         TreeItem<String> newChild2 = new TreeItem<String>("New Child 2");
 305         TreeItem<String> newChild3 = new TreeItem<String>("New Child 3");
 306         newRoot.setExpanded(true);
 307         newRoot.getChildren().setAll(newChild1, newChild2, newChild3);
 308         treeView.setRoot(root);
 309         
 310         treeView.getSelectionModel().select(2);
 311         assertEquals(newChild2, treeView.getSelectionModel().getSelectedItem());
 312     }
 313     
 314     @Test public void ensureSelectionIsCorrectWhenItemsChange() {
 315         installChildren();
 316         treeView.getSelectionModel().select(0);
 317         assertEquals(root, treeView.getSelectionModel().getSelectedItem());
 318 
 319         TreeItem newRoot = new TreeItem<>("New Root");
 320         treeView.setRoot(newRoot);
 321         assertEquals(-1, treeView.getSelectionModel().getSelectedIndex());
 322         assertNull(treeView.getSelectionModel().getSelectedItem());
 323     }
 324     
 325     @Test public void ensureSelectionRemainsOnBranchWhenExpanded() {
 326         installChildren();
 327         root.setExpanded(false);
 328         treeView.getSelectionModel().select(0);
 329         assertTrue(treeView.getSelectionModel().isSelected(0));
 330         root.setExpanded(true);
 331         assertTrue(treeView.getSelectionModel().isSelected(0));
 332         assertTrue(treeView.getSelectionModel().getSelectedItems().contains(root));
 333     }
 334     
 335     /*********************************************************************
 336      * Tests for misc                                                    *
 337      ********************************************************************/
 338     @Test public void ensureRootIndexIsZeroWhenRootIsShowing() {
 339         installChildren();
 340         assertEquals(0, treeView.getRow(root));
 341     }
 342     
 343     @Test public void ensureRootIndexIsNegativeOneWhenRootIsNotShowing() {
 344         installChildren();
 345         treeView.setShowRoot(false);
 346         assertEquals(-1, treeView.getRow(root));
 347     }
 348     
 349     @Test public void ensureCorrectIndexWhenRootTreeItemHasParent() {
 350         installChildren();
 351         treeView.setRoot(child1);
 352         assertEquals(-1, treeView.getRow(root));
 353         assertEquals(0, treeView.getRow(child1));
 354         assertEquals(1, treeView.getRow(child2));
 355         assertEquals(2, treeView.getRow(child3));
 356     }
 357     
 358     @Test public void ensureCorrectIndexWhenRootTreeItemHasParentAndRootIsNotShowing() {
 359         installChildren();
 360         treeView.setRoot(child1);
 361         treeView.setShowRoot(false);
 362         
 363         // despite the fact there are children in this tree, in reality none are
 364         // visible as the root node has no children (only siblings), and the
 365         // root node is not visible.
 366         assertEquals(0, treeView.getExpandedItemCount());
 367         
 368         assertEquals(-1, treeView.getRow(root));
 369         assertEquals(-1, treeView.getRow(child1));
 370         assertEquals(-1, treeView.getRow(child2));
 371         assertEquals(-1, treeView.getRow(child3));
 372     }
 373     
 374     @Test public void ensureCorrectIndexWhenRootTreeItemIsCollapsed() {
 375         installChildren();
 376         root.setExpanded(false);
 377         assertEquals(0, treeView.getRow(root));
 378         
 379         // note that the indices are negative, as these children rows are not
 380         // visible in the tree
 381         assertEquals(-1, treeView.getRow(child1));
 382         assertEquals(-1, treeView.getRow(child2));
 383         assertEquals(-1, treeView.getRow(child3));
 384     }
 385     
 386     @Test public void removingLastTest() {
 387         TreeView tree_view = new TreeView();
 388         MultipleSelectionModel sm = tree_view.getSelectionModel();
 389         TreeItem<String> tree_model = new TreeItem<String>("Root");
 390         TreeItem node = new TreeItem("Data item");
 391         tree_model.getChildren().add(node);
 392         tree_view.setRoot(tree_model);
 393         tree_model.setExpanded(true);
 394         // select the 'Data item' in the selection model
 395         sm.select(tree_model.getChildren().get(0));
 396         // remove the 'Data item' from the root node
 397         tree_model.getChildren().remove(sm.getSelectedItem());
 398 
 399         // Previously the selection was cleared, but this was changed to instead
 400         // move the selection upwards.
 401         // assert the there are no selected items any longer
 402         // assertTrue("items: " + sm.getSelectedItem(), sm.getSelectedItems().isEmpty());
 403         assertEquals(tree_model, sm.getSelectedItem());
 404     }
 405     
 406     /*********************************************************************
 407      * Tests from bug reports                                            *
 408      ********************************************************************/  
 409     @Ignore @Test public void test_rt17112() {
 410         TreeItem<String> root1 = new TreeItem<String>("Root");
 411         root1.setExpanded(true);
 412         addChildren(root1, "child");
 413         for (TreeItem child : root1.getChildren()) {
 414             addChildren(child, (String)child.getValue());
 415             child.setExpanded(true);
 416         }
 417 
 418         final TreeView treeView1 = new TreeView();
 419         final MultipleSelectionModel sm = treeView1.getSelectionModel();
 420         sm.setSelectionMode(SelectionMode.MULTIPLE);
 421         treeView1.setRoot(root1);
 422         
 423         final TreeItem<String> rt17112_child1 = root1.getChildren().get(1);
 424         final TreeItem<String> rt17112_child1_0 = rt17112_child1.getChildren().get(0);
 425         final TreeItem<String> rt17112_child2 = root1.getChildren().get(2);
 426         
 427         sm.getSelectedItems().addListener(new InvalidationListener() {
 428             int count = 0;
 429             @Override public void invalidated(Observable observable) {
 430                 if (count == 0) {
 431                     assertEquals(rt17112_child1_0, sm.getSelectedItem());
 432                     assertEquals(1, sm.getSelectedIndices().size());
 433                     assertEquals(6, sm.getSelectedIndex());
 434                     assertTrue(treeView1.getFocusModel().isFocused(6));
 435                 } else if (count == 1) {
 436                     assertEquals(rt17112_child1, sm.getSelectedItem());
 437                     assertFalse(sm.getSelectedItems().contains(rt17112_child2));
 438                     assertEquals(1, sm.getSelectedIndices().size());
 439                     assertTrue(treeView1.getFocusModel().isFocused(5));
 440                 }
 441                 count++;
 442             }
 443         });
 444         
 445         // this triggers the first callback above, so that count == 0
 446         sm.select(rt17112_child1_0);
 447 
 448         // this triggers the second callback above, so that count == 1
 449         rt17112_child1.setExpanded(false);
 450     }
 451     private void addChildren(TreeItem parent, String name) {
 452         for (int i=0; i<3; i++) {
 453             TreeItem<String> ti = new TreeItem<String>(name+"-"+i);
 454             parent.getChildren().add(ti);
 455         }
 456     }
 457     
 458     @Test public void test_rt17522_focusShouldMoveWhenItemAddedAtFocusIndex() {
 459         installChildren();
 460         FocusModel fm = treeView.getFocusModel();
 461         fm.focus(1);    // focus on child1
 462         assertTrue(fm.isFocused(1));
 463         assertEquals(child1, fm.getFocusedItem());
 464         
 465         TreeItem child0 = new TreeItem("child0");
 466         root.getChildren().add(0, child0);  // 0th index == position of child1 in root
 467         
 468         assertEquals(child1, fm.getFocusedItem());
 469         assertTrue(fm.isFocused(2));
 470     }
 471     
 472     @Test public void test_rt17522_focusShouldMoveWhenItemAddedBeforeFocusIndex() {
 473         installChildren();
 474         FocusModel fm = treeView.getFocusModel();
 475         fm.focus(1);    // focus on child1
 476         assertTrue(fm.isFocused(1));
 477         
 478         TreeItem child0 = new TreeItem("child0");
 479         root.getChildren().add(0, child0);
 480         assertTrue("Focused index: " + fm.getFocusedIndex(), fm.isFocused(2));
 481     }
 482     
 483     @Test public void test_rt17522_focusShouldNotMoveWhenItemAddedAfterFocusIndex() {
 484         installChildren();
 485         FocusModel fm = treeView.getFocusModel();
 486         fm.focus(1);    // focus on child1
 487         assertTrue(fm.isFocused(1));
 488         
 489         TreeItem child4 = new TreeItem("child4");
 490         root.getChildren().add(3, child4);
 491         assertTrue("Focused index: " + fm.getFocusedIndex(), fm.isFocused(1));
 492     }
 493     
 494     @Test public void test_rt17522_focusShouldBeMovedWhenFocusedItemIsRemoved_1() {
 495         installChildren();
 496         FocusModel fm = treeView.getFocusModel();
 497         fm.focus(1);
 498         assertTrue(fm.isFocused(1));
 499         
 500         root.getChildren().remove(child1);
 501         assertEquals(0, fm.getFocusedIndex());
 502         assertEquals(treeView.getTreeItem(0), fm.getFocusedItem());
 503     }
 504     
 505     @Test public void test_rt17522_focusShouldMoveWhenItemRemovedBeforeFocusIndex() {
 506         installChildren();
 507         FocusModel fm = treeView.getFocusModel();
 508         fm.focus(2);
 509         assertTrue(fm.isFocused(2));
 510         
 511         root.getChildren().remove(child1);
 512         assertTrue(fm.isFocused(1));
 513         assertEquals(child2, fm.getFocusedItem());
 514     }
 515 
 516 //    This test fails as, in TreeView FocusModel, we do not know the index of the
 517 //    removed tree items, which means we don't know whether they existed before
 518 //    or after the focused item.
 519 //    @Test public void test_rt17522_focusShouldNotMoveWhenItemRemovedAfterFocusIndex() {
 520 //        installChildren();
 521 //        FocusModel fm = treeView.getFocusModel();
 522 //        fm.focus(1);
 523 //        assertTrue(fm.isFocused(1));
 524 //        
 525 //        root.getChildren().remove(child3);
 526 //        assertTrue("Focused index: " + fm.getFocusedIndex(), fm.isFocused(1));
 527 //        assertEquals(child1, fm.getFocusedItem());
 528 //    }
 529     
 530     @Test public void test_rt18385() {
 531         installChildren();
 532 //        table.getItems().addAll("row1", "row2", "row3");
 533         treeView.getSelectionModel().select(1);
 534         treeView.getRoot().getChildren().add(new TreeItem("Another Row"));
 535         assertEquals(1, treeView.getSelectionModel().getSelectedIndices().size());
 536         assertEquals(1, treeView.getSelectionModel().getSelectedItems().size());
 537     }
 538     
 539     @Test public void test_rt18339_onlyEditWhenTreeViewIsEditable_editableIsFalse() {
 540         treeView.setEditable(false);
 541         treeView.edit(root);
 542         assertEquals(null, treeView.getEditingItem());
 543     }
 544     
 545     @Test public void test_rt18339_onlyEditWhenTreeViewIsEditable_editableIsTrue() {
 546         treeView.setEditable(true);
 547         treeView.edit(root);
 548         assertEquals(root, treeView.getEditingItem());
 549     }
 550     
 551     @Test public void test_rt14451() {
 552         installChildren();
 553         treeView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
 554         treeView.getSelectionModel().selectRange(0, 2); // select from 0 (inclusive) to 2 (exclusive)
 555         assertEquals(2, treeView.getSelectionModel().getSelectedIndices().size());
 556     }
 557     
 558     @Test public void test_rt21586() {
 559         installChildren();
 560         treeView.getSelectionModel().select(1);
 561         assertEquals(1, treeView.getSelectionModel().getSelectedIndex());
 562         assertEquals(child1, treeView.getSelectionModel().getSelectedItem());
 563         
 564         TreeItem root = new TreeItem<>("New Root");
 565         TreeItem child1 = new TreeItem<>("New Child 1");
 566         TreeItem child2 = new TreeItem<>("New Child 2");
 567         TreeItem child3 = new TreeItem<>("New Child 3");
 568         root.setExpanded(true);
 569         root.getChildren().setAll(child1, child2, child3);
 570         treeView.setRoot(root);
 571         assertEquals(-1, treeView.getSelectionModel().getSelectedIndex());
 572         assertNull(treeView.getSelectionModel().getSelectedItem());
 573     }
 574     
 575     @Test public void test_rt27181() {
 576         myCompanyRootNode.setExpanded(true);
 577         treeView.setRoot(myCompanyRootNode);
 578         
 579         // start test
 580         salesDepartment.setExpanded(true);
 581         treeView.getSelectionModel().select(salesDepartment);
 582         
 583         assertEquals(1, treeView.getFocusModel().getFocusedIndex());
 584         itSupport.setExpanded(true);
 585         assertEquals(1, treeView.getFocusModel().getFocusedIndex());
 586     }
 587     
 588     @Test public void test_rt27185() {
 589         myCompanyRootNode.setExpanded(true);
 590         treeView.setRoot(myCompanyRootNode);
 591         
 592         // start test
 593         itSupport.setExpanded(true);
 594         treeView.getSelectionModel().select(mikeGraham);
 595         
 596         assertEquals(mikeGraham, treeView.getFocusModel().getFocusedItem());
 597         salesDepartment.setExpanded(true);
 598         assertEquals(mikeGraham, treeView.getFocusModel().getFocusedItem());
 599     }
 600     
 601     @Ignore("Bug hasn't been fixed yet")
 602     @Test public void test_rt28114() {
 603         myCompanyRootNode.setExpanded(true);
 604         treeView.setRoot(myCompanyRootNode);
 605         
 606         // start test
 607         itSupport.setExpanded(true);
 608         treeView.getSelectionModel().select(itSupport);
 609         assertEquals(itSupport, treeView.getFocusModel().getFocusedItem());
 610         assertEquals(itSupport, treeView.getSelectionModel().getSelectedItem());
 611         assertTrue(! itSupport.isLeaf());
 612         assertTrue(itSupport.isExpanded());
 613         
 614         itSupport.getChildren().remove(mikeGraham);
 615         assertEquals(itSupport, treeView.getFocusModel().getFocusedItem());
 616         assertEquals(itSupport, treeView.getSelectionModel().getSelectedItem());
 617         assertTrue(itSupport.isLeaf());
 618         assertTrue(!itSupport.isExpanded());
 619     }
 620     
 621     @Test public void test_rt27820_1() {
 622         TreeItem root = new TreeItem("root");
 623         root.setExpanded(true);
 624         TreeItem child = new TreeItem("child");
 625         root.getChildren().add(child);
 626         treeView.setRoot(root);
 627         
 628         treeView.getSelectionModel().select(0);
 629         assertEquals(1, treeView.getSelectionModel().getSelectedItems().size());
 630         assertEquals(root, treeView.getSelectionModel().getSelectedItem());
 631         
 632         treeView.setRoot(null);
 633         assertEquals(0, treeView.getSelectionModel().getSelectedItems().size());
 634         assertNull(treeView.getSelectionModel().getSelectedItem());
 635     }
 636     
 637     @Test public void test_rt27820_2() {
 638         TreeItem root = new TreeItem("root");
 639         root.setExpanded(true);
 640         TreeItem child = new TreeItem("child");
 641         root.getChildren().add(child);
 642         treeView.setRoot(root);
 643         
 644         treeView.getSelectionModel().select(1);
 645         assertEquals(1, treeView.getSelectionModel().getSelectedItems().size());
 646         assertEquals(child, treeView.getSelectionModel().getSelectedItem());
 647         
 648         treeView.setRoot(null);
 649         assertEquals(0, treeView.getSelectionModel().getSelectedItems().size());
 650         assertNull(treeView.getSelectionModel().getSelectedItem());
 651     }
 652     
 653     @Test public void test_rt28390() {
 654         // There should be no NPE when a TreeView is shown and the disclosure
 655         // node is null in a TreeCell
 656         TreeItem root = new TreeItem("root");
 657         treeView.setRoot(root);
 658         
 659         // install a custom cell factory that forces the disclosure node to be
 660         // null (because by default a null disclosure node will be replaced by
 661         // a non-null one).
 662         treeView.setCellFactory(new Callback() {
 663             @Override public Object call(Object p) {
 664                 TreeCell treeCell = new TreeCell() {
 665                     {
 666                         disclosureNodeProperty().addListener((ov, t, t1) -> {
 667                             setDisclosureNode(null);
 668                         });
 669                     }
 670                     
 671                     @Override protected void updateItem(Object item, boolean empty) {
 672                         super.updateItem(item, empty);
 673                         setText(item == null ? "" : item.toString());
 674                     }
 675                 };
 676                 treeCell.setDisclosureNode(null);
 677                 return treeCell;
 678             }
 679         });
 680         
 681         try {
 682             Group group = new Group();
 683             group.getChildren().setAll(treeView);
 684             Scene scene = new Scene(group);
 685             Stage stage = new Stage();
 686             stage.setScene(scene);
 687             stage.show();
 688         } catch (NullPointerException e) {
 689             System.out.println("A null disclosure node is valid, so we shouldn't have an NPE here.");
 690             e.printStackTrace();
 691             assertTrue(false);
 692         }
 693     }
 694     
 695     @Test public void test_rt28534() {
 696         TreeItem root = new TreeItem("root");
 697         root.getChildren().setAll(
 698                 new TreeItem(new Person("Jacob", "Smith", "jacob.smith@example.com")),
 699                 new TreeItem(new Person("Isabella", "Johnson", "isabella.johnson@example.com")),
 700                 new TreeItem(new Person("Ethan", "Williams", "ethan.williams@example.com")),
 701                 new TreeItem(new Person("Emma", "Jones", "emma.jones@example.com")),
 702                 new TreeItem(new Person("Michael", "Brown", "michael.brown@example.com")));
 703         root.setExpanded(true);
 704         
 705         TreeView<Person> tree = new TreeView<Person>(root);
 706         
 707         VirtualFlowTestUtils.assertRowsNotEmpty(tree, 0, 6); // rows 0 - 6 should be filled
 708         VirtualFlowTestUtils.assertRowsEmpty(tree, 6, -1); // rows 6+ should be empty
 709         
 710         // now we replace the data and expect the cells that have no data
 711         // to be empty
 712         root.getChildren().setAll(
 713                 new TreeItem(new Person("*_*Emma", "Jones", "emma.jones@example.com")),
 714                 new TreeItem(new Person("_Michael", "Brown", "michael.brown@example.com")));
 715         
 716         VirtualFlowTestUtils.assertRowsNotEmpty(tree, 0, 3); // rows 0 - 3 should be filled
 717         VirtualFlowTestUtils.assertRowsEmpty(tree, 3, -1); // rows 3+ should be empty
 718     }
 719 
 720     @Test public void test_rt28556() {
 721         List<Employee> employees = Arrays.<Employee>asList(
 722             new Employee("Ethan Williams", "Sales Department"),
 723             new Employee("Emma Jones", "Sales Department"),
 724             new Employee("Michael Brown", "Sales Department"),
 725             new Employee("Anna Black", "Sales Department"),
 726             new Employee("Rodger York", "Sales Department"),
 727             new Employee("Susan Collins", "Sales Department"),
 728             new Employee("Mike Graham", "IT Support"),
 729             new Employee("Judy Mayer", "IT Support"),
 730             new Employee("Gregory Smith", "IT Support"),
 731             new Employee("Jacob Smith", "Accounts Department"),
 732             new Employee("Isabella Johnson", "Accounts Department"));
 733     
 734         TreeItem<String> rootNode = new TreeItem<String>("MyCompany Human Resources");
 735         rootNode.setExpanded(true);
 736         
 737         List<TreeItem<String>> nodeList = FXCollections.observableArrayList();
 738         for (Employee employee : employees) {
 739             nodeList.add(new TreeItem<String>(employee.getName()));
 740         }
 741         rootNode.getChildren().setAll(nodeList);
 742 
 743         TreeView<String> treeView = new TreeView<String>(rootNode);
 744         
 745         final double indent = PlatformImpl.isCaspian() ? 31 : 
 746                         PlatformImpl.isModena()  ? 35 :
 747                         0;
 748         
 749         // ensure all children of the root node have the correct indentation 
 750         // before the sort occurs
 751         VirtualFlowTestUtils.assertLayoutX(treeView, 1, 11, indent);
 752         for (TreeItem<String> children : rootNode.getChildren()) {
 753             assertEquals(rootNode, children.getParent());
 754         }
 755         
 756         // run sort
 757         Collections.sort(rootNode.getChildren(), (o1, o2) -> o1.getValue().compareTo(o2.getValue()));
 758         
 759         // ensure the same indentation exists after the sort (which is where the
 760         // bug is - it drops down to 21.0px indentation when it shouldn't).
 761         VirtualFlowTestUtils.assertLayoutX(treeView, 1, 11, indent);
 762         for (TreeItem<String> children : rootNode.getChildren()) {
 763             assertEquals(rootNode, children.getParent());
 764         }
 765     }
 766     
 767     @Test public void test_rt22463() {
 768         RT_22463_Person rootPerson = new RT_22463_Person();
 769         rootPerson.setName("Root");
 770         TreeItem<RT_22463_Person> root = new TreeItem<RT_22463_Person>(rootPerson);
 771         root.setExpanded(true);
 772         
 773         final TreeView<RT_22463_Person> tree = new TreeView<RT_22463_Person>();
 774         tree.setRoot(root);
 775         
 776         // before the change things display fine
 777         RT_22463_Person p1 = new RT_22463_Person();
 778         p1.setId(1l);
 779         p1.setName("name1");
 780         RT_22463_Person p2 = new RT_22463_Person();
 781         p2.setId(2l);
 782         p2.setName("name2");
 783         root.getChildren().addAll(
 784                 new TreeItem<RT_22463_Person>(p1), 
 785                 new TreeItem<RT_22463_Person>(p2));
 786         VirtualFlowTestUtils.assertCellTextEquals(tree, 1, "name1");
 787         VirtualFlowTestUtils.assertCellTextEquals(tree, 2, "name2");
 788         
 789         // now we change the persons but they are still equal as the ID's don't
 790         // change - but the items list is cleared so the cells should update
 791         RT_22463_Person new_p1 = new RT_22463_Person();
 792         new_p1.setId(1l);
 793         new_p1.setName("updated name1");
 794         RT_22463_Person new_p2 = new RT_22463_Person();
 795         new_p2.setId(2l);
 796         new_p2.setName("updated name2");
 797         root.getChildren().clear();
 798         root.getChildren().setAll(
 799                 new TreeItem<RT_22463_Person>(new_p1), 
 800                 new TreeItem<RT_22463_Person>(new_p2));
 801         VirtualFlowTestUtils.assertCellTextEquals(tree, 1, "updated name1");
 802         VirtualFlowTestUtils.assertCellTextEquals(tree, 2, "updated name2");
 803     }
 804     
 805     @Test public void test_rt28637() {
 806         TreeItem<String> s1, s2, s3, s4;
 807         ObservableList<TreeItem<String>> items = FXCollections.observableArrayList(
 808                 s1 = new TreeItem<String>("String1"), 
 809                 s2 = new TreeItem<String>("String2"), 
 810                 s3 = new TreeItem<String>("String3"), 
 811                 s4 = new TreeItem<String>("String4"));
 812         
 813         final TreeView<String> treeView = new TreeView<String>();
 814         
 815         TreeItem<String> root = new TreeItem<String>("Root");
 816         root.setExpanded(true);
 817         treeView.setRoot(root);
 818         treeView.setShowRoot(false);
 819         root.getChildren().addAll(items);
 820         
 821         treeView.getSelectionModel().select(0);
 822         assertEquals((Object)s1, treeView.getSelectionModel().getSelectedItem());
 823         assertEquals((Object)s1, treeView.getSelectionModel().getSelectedItems().get(0));
 824         assertEquals(0, treeView.getSelectionModel().getSelectedIndex());
 825         
 826         root.getChildren().remove(treeView.getSelectionModel().getSelectedItem());
 827         assertEquals((Object)s2, treeView.getSelectionModel().getSelectedItem());
 828         assertEquals((Object)s2, treeView.getSelectionModel().getSelectedItems().get(0));
 829         assertEquals(0, treeView.getSelectionModel().getSelectedIndex());
 830     }
 831     
 832     @Ignore("Test passes from within IDE but not when run from command line. Needs more investigation.")
 833     @Test public void test_rt28678() {
 834         TreeItem<String> s1, s2, s3, s4;
 835         ObservableList<TreeItem<String>> items = FXCollections.observableArrayList(
 836                 s1 = new TreeItem<String>("String1"), 
 837                 s2 = new TreeItem<String>("String2"), 
 838                 s3 = new TreeItem<String>("String3"), 
 839                 s4 = new TreeItem<String>("String4"));
 840         
 841         final TreeView<String> treeView = new TreeView<String>();
 842         
 843         TreeItem<String> root = new TreeItem<String>("Root");
 844         root.setExpanded(true);
 845         treeView.setRoot(root);
 846         treeView.setShowRoot(false);
 847         root.getChildren().addAll(items);
 848         
 849         Node graphic = new Circle(6, Color.RED);
 850         
 851         assertNull(s2.getGraphic());
 852         TreeCell s2Cell = (TreeCell) VirtualFlowTestUtils.getCell(treeView, 1);
 853         assertNull(s2Cell.getGraphic());
 854         
 855         s2.setGraphic(graphic);
 856         Toolkit.getToolkit().firePulse();
 857                 
 858         assertEquals(graphic, s2.getGraphic());
 859         assertEquals(graphic, s2Cell.getGraphic());
 860     }
 861     
 862     @Test public void test_rt29390() {
 863         ObservableList<TreeItem<Person>> persons = FXCollections.observableArrayList(
 864                 new TreeItem<Person>(new Person("Jacob", "Smith", "jacob.smith@example.com")),
 865                 new TreeItem<Person>(new Person("Isabella", "Johnson", "isabella.johnson@example.com")),
 866                 new TreeItem<Person>(new Person("Ethan", "Williams", "ethan.williams@example.com")),
 867                 new TreeItem<Person>(new Person("Emma", "Jones", "emma.jones@example.com")),
 868                 new TreeItem<Person>(new Person("Jacob", "Smith", "jacob.smith@example.com")),
 869                 new TreeItem<Person>(new Person("Isabella", "Johnson", "isabella.johnson@example.com")),
 870                 new TreeItem<Person>(new Person("Ethan", "Williams", "ethan.williams@example.com")),
 871                 new TreeItem<Person>(new Person("Emma", "Jones", "emma.jones@example.com")),
 872                 new TreeItem<Person>(new Person("Jacob", "Smith", "jacob.smith@example.com")),
 873                 new TreeItem<Person>(new Person("Isabella", "Johnson", "isabella.johnson@example.com")),
 874                 new TreeItem<Person>(new Person("Ethan", "Williams", "ethan.williams@example.com")),
 875                 new TreeItem<Person>(new Person("Emma", "Jones", "emma.jones@example.com")),
 876                 new TreeItem<Person>(new Person("Jacob", "Smith", "jacob.smith@example.com")),
 877                 new TreeItem<Person>(new Person("Isabella", "Johnson", "isabella.johnson@example.com")),
 878                 new TreeItem<Person>(new Person("Ethan", "Williams", "ethan.williams@example.com")),
 879                 new TreeItem<Person>(new Person("Emma", "Jones", "emma.jones@example.com")
 880         ));
 881                 
 882         TreeView<Person> treeView = new TreeView<>();
 883         treeView.setMaxHeight(50);
 884         treeView.setPrefHeight(50);
 885         
 886         TreeItem<Person> root = new TreeItem<Person>(new Person("Root", null, null));
 887         root.setExpanded(true);
 888         treeView.setRoot(root);
 889         treeView.setShowRoot(false);
 890         root.getChildren().setAll(persons);
 891         
 892         Toolkit.getToolkit().firePulse();
 893         
 894         // we want the vertical scrollbar
 895         VirtualScrollBar scrollBar = VirtualFlowTestUtils.getVirtualFlowVerticalScrollbar(treeView);
 896         
 897         assertNotNull(scrollBar);
 898         assertTrue(scrollBar.isVisible());
 899         assertTrue(scrollBar.getVisibleAmount() > 0.0);
 900         assertTrue(scrollBar.getVisibleAmount() < 1.0);
 901         
 902         // this next test is likely to be brittle, but we'll see...If it is the
 903         // cause of failure then it can be commented out
 904         assertEquals(0.125, scrollBar.getVisibleAmount(), 0.0);
 905     }
 906     
 907     @Test public void test_rt27180_collapseBranch_childSelected_singleSelection() {
 908         sm.setSelectionMode(SelectionMode.SINGLE);
 909         
 910         treeView.setRoot(myCompanyRootNode);
 911         myCompanyRootNode.setExpanded(true);
 912         salesDepartment.setExpanded(true);
 913         itSupport.setExpanded(true);
 914         sm.select(2);                   // ethanWilliams
 915         assertFalse(sm.isSelected(1));  // salesDepartment
 916         assertTrue(sm.isSelected(2));   // ethanWilliams
 917         assertTrue(treeView.getFocusModel().isFocused(2));
 918         assertEquals(1, sm.getSelectedIndices().size());
 919         
 920         // now collapse the salesDepartment, selection should
 921         // not jump down to the itSupport people
 922         salesDepartment.setExpanded(false);
 923         assertTrue(sm.isSelected(1));   // salesDepartment
 924         assertTrue(treeView.getFocusModel().isFocused(1));
 925         assertEquals(1, sm.getSelectedIndices().size());
 926     }
 927     
 928     @Test public void test_rt27180_collapseBranch_laterSiblingSelected_singleSelection() {
 929         sm.setSelectionMode(SelectionMode.SINGLE);
 930         
 931         treeView.setRoot(myCompanyRootNode);
 932         myCompanyRootNode.setExpanded(true);
 933         salesDepartment.setExpanded(true);
 934         itSupport.setExpanded(true);
 935         sm.select(8);                   // itSupport
 936         assertFalse(sm.isSelected(1));  // salesDepartment
 937         assertTrue(sm.isSelected(8));   // itSupport
 938         assertTrue(treeView.getFocusModel().isFocused(8));
 939         assertEquals(1, sm.getSelectedIndices().size());
 940         
 941         salesDepartment.setExpanded(false);
 942         assertTrue(sm.isSelected(2));   // itSupport
 943         assertTrue(treeView.getFocusModel().isFocused(2));
 944         assertEquals(1, sm.getSelectedIndices().size());
 945     }
 946     
 947     @Test public void test_rt27180_collapseBranch_laterSiblingAndChildrenSelected() {
 948         sm.setSelectionMode(SelectionMode.MULTIPLE);
 949         
 950         treeView.setRoot(myCompanyRootNode);
 951         treeView.getSelectionModel().clearSelection();
 952 
 953         myCompanyRootNode.setExpanded(true);
 954         salesDepartment.setExpanded(true);
 955         itSupport.setExpanded(true);
 956         sm.selectIndices(8, 9, 10);     // itSupport, and two people
 957         assertFalse(sm.isSelected(1));  // salesDepartment
 958         assertTrue(sm.isSelected(8));   // itSupport
 959         assertTrue(sm.isSelected(9));   // mikeGraham
 960         assertTrue(sm.isSelected(10));  // judyMayer
 961         assertTrue(treeView.getFocusModel().isFocused(10));
 962         assertEquals(3, sm.getSelectedIndices().size());
 963         
 964         salesDepartment.setExpanded(false);
 965         assertTrue(sm.isSelected(2));   // itSupport
 966         assertTrue(sm.isSelected(3));   // mikeGraham
 967         assertTrue(sm.isSelected(4));   // judyMayer
 968         assertTrue(treeView.getFocusModel().isFocused(4));
 969         assertEquals(3, sm.getSelectedIndices().size());
 970     }
 971     
 972     @Test public void test_rt27180_expandBranch_laterSiblingSelected_singleSelection() {
 973         sm.setSelectionMode(SelectionMode.SINGLE);
 974         
 975         treeView.setRoot(myCompanyRootNode);
 976         myCompanyRootNode.setExpanded(true);
 977         salesDepartment.setExpanded(false);
 978         itSupport.setExpanded(true);
 979         sm.select(2);                   // itSupport
 980         assertFalse(sm.isSelected(1));  // salesDepartment
 981         assertTrue(sm.isSelected(2));   // itSupport
 982         assertTrue(treeView.getFocusModel().isFocused(2));
 983         assertEquals(1, sm.getSelectedIndices().size());
 984         
 985         salesDepartment.setExpanded(true);
 986         assertTrue(sm.isSelected(8));   // itSupport
 987         assertTrue(treeView.getFocusModel().isFocused(8));
 988         assertEquals(1, sm.getSelectedIndices().size());
 989     }
 990     
 991     @Test public void test_rt27180_expandBranch_laterSiblingAndChildrenSelected() {
 992         sm.setSelectionMode(SelectionMode.MULTIPLE);
 993         
 994         treeView.setRoot(myCompanyRootNode);
 995         treeView.getSelectionModel().clearSelection();
 996 
 997         myCompanyRootNode.setExpanded(true);
 998         salesDepartment.setExpanded(false);
 999         itSupport.setExpanded(true);
1000         sm.selectIndices(2,3,4);     // itSupport, and two people
1001         assertFalse(sm.isSelected(1));  // salesDepartment
1002         assertTrue(sm.isSelected(2));   // itSupport
1003         assertTrue(sm.isSelected(3));   // mikeGraham
1004         assertTrue(sm.isSelected(4));  // judyMayer
1005         assertTrue(treeView.getFocusModel().isFocused(4));
1006         assertEquals(3, sm.getSelectedIndices().size());
1007         
1008         salesDepartment.setExpanded(true);
1009         assertTrue(sm.isSelected(8));   // itSupport
1010         assertTrue(sm.isSelected(9));   // mikeGraham
1011         assertTrue(sm.isSelected(10));   // judyMayer
1012         assertTrue(treeView.getFocusModel().isFocused(10));
1013         assertEquals(3, sm.getSelectedIndices().size());
1014     }
1015 
1016     @Test public void test_rt30400() {
1017         // create a treeview that'll render cells using the check box cell factory
1018         TreeItem<String> rootItem = new TreeItem<>("root");
1019         treeView.setRoot(rootItem);
1020         treeView.setMinHeight(100);
1021         treeView.setPrefHeight(100);
1022         treeView.setCellFactory(
1023                 CheckBoxTreeCell.forTreeView(
1024                         param -> new ReadOnlyBooleanWrapper(true)));
1025 
1026         // because only the first row has data, all other rows should be
1027         // empty (and not contain check boxes - we just check the first four here)
1028         VirtualFlowTestUtils.assertRowsNotEmpty(treeView, 0, 1);
1029         VirtualFlowTestUtils.assertCellNotEmpty(VirtualFlowTestUtils.getCell(treeView, 0));
1030         VirtualFlowTestUtils.assertCellEmpty(VirtualFlowTestUtils.getCell(treeView, 1));
1031         VirtualFlowTestUtils.assertCellEmpty(VirtualFlowTestUtils.getCell(treeView, 2));
1032         VirtualFlowTestUtils.assertCellEmpty(VirtualFlowTestUtils.getCell(treeView, 3));
1033     }
1034 
1035     @Test public void test_rt31165() {
1036         installChildren();
1037         treeView.setEditable(true);
1038         treeView.setCellFactory(TextFieldTreeCell.forTreeView());
1039 
1040         IndexedCell cell = VirtualFlowTestUtils.getCell(treeView, 1);
1041         assertEquals(child1.getValue(), cell.getText());
1042         assertFalse(cell.isEditing());
1043 
1044         treeView.edit(child1);
1045 
1046         assertEquals(child1, treeView.getEditingItem());
1047         assertTrue(cell.isEditing());
1048 
1049         VirtualFlowTestUtils.getVirtualFlow(treeView).requestLayout();
1050         Toolkit.getToolkit().firePulse();
1051 
1052         assertEquals(child1, treeView.getEditingItem());
1053         assertTrue(cell.isEditing());
1054     }
1055 
1056     @Test public void test_rt31404() {
1057         installChildren();
1058 
1059         IndexedCell cell = VirtualFlowTestUtils.getCell(treeView, 0);
1060         assertEquals("Root", cell.getText());
1061 
1062         treeView.setShowRoot(false);
1063         assertEquals("Child 1", cell.getText());
1064     }
1065 
1066     @Test public void test_rt31471() {
1067         installChildren();
1068 
1069         IndexedCell cell = VirtualFlowTestUtils.getCell(treeView, 0);
1070         assertEquals("Root", cell.getItem());
1071 
1072         treeView.setFixedCellSize(50);
1073 
1074         VirtualFlowTestUtils.getVirtualFlow(treeView).requestLayout();
1075         Toolkit.getToolkit().firePulse();
1076 
1077         assertEquals("Root", cell.getItem());
1078         assertEquals(50, cell.getHeight(), 0.00);
1079     }
1080 
1081     private int rt_31200_count = 0;
1082     @Test public void test_rt_31200_tableRow() {
1083         installChildren();
1084         treeView.setCellFactory(new Callback<TreeView<String>, TreeCell<String>>() {
1085             @Override
1086             public TreeCell<String> call(TreeView<String> param) {
1087                 return new TreeCell<String>() {
1088                     ImageView view = new ImageView();
1089                     { setGraphic(view); };
1090 
1091                     @Override
1092                     protected void updateItem(String item, boolean empty) {
1093                         if (getItem() == null ? item == null : getItem().equals(item)) {
1094                             rt_31200_count++;
1095                         }
1096                         super.updateItem(item, empty);
1097                         if (item == null || empty) {
1098                             view.setImage(null);
1099                             setText(null);
1100                         } else {
1101                             setText(item.toString());
1102                         }
1103                     }
1104                 };
1105             }
1106         });
1107 
1108         StageLoader sl = new StageLoader(treeView);
1109 
1110         assertEquals(24, rt_31200_count);
1111 
1112         // resize the stage
1113         sl.getStage().setHeight(250);
1114         Toolkit.getToolkit().firePulse();
1115         sl.getStage().setHeight(50);
1116         Toolkit.getToolkit().firePulse();
1117         assertEquals(24, rt_31200_count);
1118 
1119         sl.dispose();
1120     }
1121 
1122     @Test public void test_rt_30484() {
1123         installChildren();
1124         treeView.setCellFactory(new Callback<TreeView<String>, TreeCell<String>>() {
1125             @Override public TreeCell<String> call(TreeView<String> param) {
1126                 return new TreeCell<String>() {
1127                     Rectangle graphic = new Rectangle(10, 10, Color.RED);
1128                     { setGraphic(graphic); };
1129 
1130                     @Override protected void updateItem(String item, boolean empty) {
1131                         super.updateItem(item, empty);
1132                         if (item == null || empty) {
1133                             graphic.setVisible(false);
1134                             setText(null);
1135                         } else {
1136                             graphic.setVisible(true);
1137                             setText(item);
1138                         }
1139                     }
1140                 };
1141             }
1142         });
1143 
1144         // First two four have content, so the graphic should show.
1145         // All other rows have no content, so graphic should not show.
1146 
1147         VirtualFlowTestUtils.assertGraphicIsVisible(treeView, 0);
1148         VirtualFlowTestUtils.assertGraphicIsVisible(treeView, 1);
1149         VirtualFlowTestUtils.assertGraphicIsVisible(treeView, 2);
1150         VirtualFlowTestUtils.assertGraphicIsVisible(treeView, 3);
1151         VirtualFlowTestUtils.assertGraphicIsNotVisible(treeView, 4);
1152         VirtualFlowTestUtils.assertGraphicIsNotVisible(treeView, 5);
1153     }
1154 
1155     private int rt_29650_start_count = 0;
1156     private int rt_29650_commit_count = 0;
1157     private int rt_29650_cancel_count = 0;
1158     @Test public void test_rt_29650() {
1159         installChildren();
1160         treeView.setOnEditStart(t -> {
1161             rt_29650_start_count++;
1162         });
1163         treeView.setOnEditCommit(t -> {
1164             rt_29650_commit_count++;
1165         });
1166         treeView.setOnEditCancel(t -> {
1167             rt_29650_cancel_count++;
1168         });
1169 
1170         treeView.setEditable(true);
1171         treeView.setCellFactory(TextFieldTreeCell.forTreeView());
1172 
1173         StageLoader sl = new StageLoader(treeView);
1174 
1175         treeView.edit(root);
1176         TreeCell rootCell = (TreeCell) VirtualFlowTestUtils.getCell(treeView, 0);
1177         TextField textField = (TextField) rootCell.getGraphic();
1178         textField.setSkin(new TextFieldSkin(textField));
1179         textField.setText("Testing!");
1180         KeyEventFirer keyboard = new KeyEventFirer(textField);
1181         keyboard.doKeyPress(KeyCode.ENTER);
1182 
1183         assertEquals("Testing!", root.getValue());
1184         assertEquals(1, rt_29650_start_count);
1185         assertEquals(1, rt_29650_commit_count);
1186         assertEquals(0, rt_29650_cancel_count);
1187 
1188         sl.dispose();
1189     }
1190 
1191     private int rt_33559_count = 0;
1192     @Test public void test_rt_33559() {
1193         installChildren();
1194 
1195         treeView.setShowRoot(true);
1196         final MultipleSelectionModel sm = treeView.getSelectionModel();
1197         sm.setSelectionMode(SelectionMode.MULTIPLE);
1198         sm.clearAndSelect(0);
1199 
1200         treeView.getSelectionModel().getSelectedItems().addListener((ListChangeListener) c -> {
1201             while (c.next()) {
1202                 System.out.println(c);
1203                 rt_33559_count++;
1204             }
1205         });
1206 
1207         assertEquals(0, rt_33559_count);
1208         root.setExpanded(true);
1209         assertEquals(0, rt_33559_count);
1210     }
1211 
1212     @Test public void test_rt34103() {
1213         treeView.setRoot(new TreeItem("Root"));
1214         treeView.getRoot().setExpanded(true);
1215 
1216         for (int i = 0; i < 4; i++) {
1217             TreeItem parent = new TreeItem("item - " + i);
1218             treeView.getRoot().getChildren().add(parent);
1219 
1220             for (int j = 0; j < 4; j++) {
1221                 TreeItem child = new TreeItem("item - " + i + " " + j);
1222                 parent.getChildren().add(child);
1223             }
1224         }
1225 
1226         treeView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
1227 
1228         TreeItem item0 = treeView.getTreeItem(1);
1229         assertEquals("item - 0", item0.getValue());
1230         item0.setExpanded(true);
1231 
1232         treeView.getSelectionModel().clearSelection();
1233         treeView.getSelectionModel().selectIndices(1,2,3);
1234         assertEquals(3, treeView.getSelectionModel().getSelectedIndices().size());
1235 
1236         item0.setExpanded(false);
1237         Toolkit.getToolkit().firePulse();
1238         assertEquals(1, treeView.getSelectionModel().getSelectedIndices().size());
1239     }
1240 
1241     @Test public void test_rt26718() {
1242         treeView.setRoot(new TreeItem("Root"));
1243         treeView.getRoot().setExpanded(true);
1244 
1245         for (int i = 0; i < 4; i++) {
1246             TreeItem parent = new TreeItem("item - " + i);
1247             treeView.getRoot().getChildren().add(parent);
1248 
1249             for (int j = 0; j < 4; j++) {
1250                 TreeItem child = new TreeItem("item - " + i + " " + j);
1251                 parent.getChildren().add(child);
1252             }
1253         }
1254 
1255         treeView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
1256 
1257         final TreeItem item0 = treeView.getTreeItem(1);
1258         final TreeItem item1 = treeView.getTreeItem(2);
1259 
1260         assertEquals("item - 0", item0.getValue());
1261         assertEquals("item - 1", item1.getValue());
1262 
1263         item0.setExpanded(true);
1264         item1.setExpanded(true);
1265         Toolkit.getToolkit().firePulse();
1266 
1267         treeView.getSelectionModel().selectRange(0, 8);
1268         assertEquals(8, treeView.getSelectionModel().getSelectedIndices().size());
1269         assertEquals(7, treeView.getSelectionModel().getSelectedIndex());
1270         assertEquals(7, treeView.getFocusModel().getFocusedIndex());
1271 
1272         // collapse item0 - but because the selected and focused indices are
1273         // not children of item 0, they should remain where they are (but of
1274         // course be shifted up). The bug was that focus was moving up to item0,
1275         // which makes no sense
1276         item0.setExpanded(false);
1277         Toolkit.getToolkit().firePulse();
1278         assertEquals(3, treeView.getSelectionModel().getSelectedIndex());
1279         assertEquals(3, treeView.getFocusModel().getFocusedIndex());
1280     }
1281 
1282     @Test public void test_rt26721_collapseParent_firstRootChild() {
1283         treeView.setRoot(new TreeItem("Root"));
1284         treeView.getRoot().setExpanded(true);
1285 
1286         for (int i = 0; i < 4; i++) {
1287             TreeItem parent = new TreeItem("item - " + i);
1288             treeView.getRoot().getChildren().add(parent);
1289 
1290             for (int j = 0; j < 4; j++) {
1291                 TreeItem child = new TreeItem("item - " + i + " " + j);
1292                 parent.getChildren().add(child);
1293             }
1294         }
1295 
1296         treeView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
1297 
1298         final TreeItem<String> item0 = treeView.getTreeItem(1);
1299         final TreeItem<String> item0child0 = item0.getChildren().get(0);
1300         final TreeItem<String> item1 = treeView.getTreeItem(2);
1301 
1302         assertEquals("item - 0", item0.getValue());
1303         assertEquals("item - 1", item1.getValue());
1304 
1305         item0.setExpanded(true);
1306         item1.setExpanded(true);
1307         Toolkit.getToolkit().firePulse();
1308 
1309         // select the first child of item0
1310         treeView.getSelectionModel().select(item0child0);
1311 
1312         assertEquals(item0child0, treeView.getSelectionModel().getSelectedItem());
1313         assertEquals(item0child0, treeView.getFocusModel().getFocusedItem());
1314 
1315         // collapse item0 - we expect the selection / focus to move up to item0
1316         item0.setExpanded(false);
1317         Toolkit.getToolkit().firePulse();
1318         assertEquals(item0, treeView.getSelectionModel().getSelectedItem());
1319         assertEquals(item0, treeView.getFocusModel().getFocusedItem());
1320     }
1321 
1322     @Test public void test_rt26721_collapseParent_lastRootChild() {
1323         treeView.setRoot(new TreeItem("Root"));
1324         treeView.getRoot().setExpanded(true);
1325 
1326         for (int i = 0; i < 4; i++) {
1327             TreeItem parent = new TreeItem("item - " + i);
1328             treeView.getRoot().getChildren().add(parent);
1329 
1330             for (int j = 0; j < 4; j++) {
1331                 TreeItem child = new TreeItem("item - " + i + " " + j);
1332                 parent.getChildren().add(child);
1333             }
1334         }
1335 
1336         treeView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
1337 
1338         final TreeItem<String> item3 = treeView.getTreeItem(4);
1339         final TreeItem<String> item3child0 = item3.getChildren().get(0);
1340 
1341         assertEquals("item - 3", item3.getValue());
1342         assertEquals("item - 3 0", item3child0.getValue());
1343 
1344         item3.setExpanded(true);
1345         Toolkit.getToolkit().firePulse();
1346 
1347         // select the first child of item0
1348         treeView.getSelectionModel().select(item3child0);
1349 
1350         assertEquals(item3child0, treeView.getSelectionModel().getSelectedItem());
1351         assertEquals(item3child0, treeView.getFocusModel().getFocusedItem());
1352 
1353         // collapse item3 - we expect the selection / focus to move up to item3
1354         item3.setExpanded(false);
1355         Toolkit.getToolkit().firePulse();
1356         assertEquals(item3, treeView.getSelectionModel().getSelectedItem());
1357         assertEquals(item3, treeView.getFocusModel().getFocusedItem());
1358     }
1359 
1360     @Test public void test_rt26721_collapseGrandParent() {
1361         treeView.setRoot(new TreeItem("Root"));
1362         treeView.getRoot().setExpanded(true);
1363 
1364         for (int i = 0; i < 4; i++) {
1365             TreeItem parent = new TreeItem("item - " + i);
1366             treeView.getRoot().getChildren().add(parent);
1367 
1368             for (int j = 0; j < 4; j++) {
1369                 TreeItem child = new TreeItem("item - " + i + " " + j);
1370                 parent.getChildren().add(child);
1371             }
1372         }
1373 
1374         treeView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
1375 
1376         final TreeItem<String> item0 = treeView.getTreeItem(1);
1377         final TreeItem<String> item0child0 = item0.getChildren().get(0);
1378         final TreeItem<String> item1 = treeView.getTreeItem(2);
1379 
1380         assertEquals("item - 0", item0.getValue());
1381         assertEquals("item - 1", item1.getValue());
1382 
1383         item0.setExpanded(true);
1384         item1.setExpanded(true);
1385         Toolkit.getToolkit().firePulse();
1386 
1387         // select the first child of item0
1388         treeView.getSelectionModel().select(item0child0);
1389 
1390         assertEquals(item0child0, treeView.getSelectionModel().getSelectedItem());
1391         assertEquals(item0child0, treeView.getFocusModel().getFocusedItem());
1392 
1393         // collapse root - we expect the selection / focus to move up to root
1394         treeView.getRoot().setExpanded(false);
1395         Toolkit.getToolkit().firePulse();
1396         assertEquals(treeView.getRoot(), treeView.getSelectionModel().getSelectedItem());
1397         assertEquals(treeView.getRoot(), treeView.getFocusModel().getFocusedItem());
1398     }
1399 
1400     @Test public void test_rt34694() {
1401         TreeItem treeNode = new TreeItem("Controls");
1402         treeNode.getChildren().addAll(
1403             new TreeItem("Button"),
1404             new TreeItem("ButtonBar"),
1405             new TreeItem("LinkBar"),
1406             new TreeItem("LinkButton"),
1407             new TreeItem("PopUpButton"),
1408             new TreeItem("ToggleButtonBar")
1409         );
1410 
1411         final TreeView treeView = new TreeView();
1412         treeView.setRoot(treeNode);
1413         treeNode.setExpanded(true);
1414 
1415         treeView.getSelectionModel().select(0);
1416         assertTrue(treeView.getSelectionModel().isSelected(0));
1417         assertTrue(treeView.getFocusModel().isFocused(0));
1418 
1419         treeNode.getChildren().clear();
1420         treeNode.getChildren().addAll(
1421                 new TreeItem("Button1"),
1422                 new TreeItem("ButtonBar1"),
1423                 new TreeItem("LinkBar1"),
1424                 new TreeItem("LinkButton1"),
1425                 new TreeItem("PopUpButton1"),
1426                 new TreeItem("ToggleButtonBar1")
1427         );
1428         Toolkit.getToolkit().firePulse();
1429 
1430         assertTrue(treeView.getSelectionModel().isSelected(0));
1431         assertTrue(treeView.getFocusModel().isFocused(0));
1432     }
1433 
1434     private int test_rt_35213_eventCount = 0;
1435     @Test public void test_rt35213() {
1436         final TreeView<String> view = new TreeView<>();
1437 
1438         TreeItem<String> root = new TreeItem<>("Boss");
1439         view.setRoot(root);
1440 
1441         TreeItem<String> group1 = new TreeItem<>("Group 1");
1442         TreeItem<String> group2 = new TreeItem<>("Group 2");
1443         TreeItem<String> group3 = new TreeItem<>("Group 3");
1444 
1445         root.getChildren().addAll(group1, group2, group3);
1446 
1447         TreeItem<String> employee1 = new TreeItem<>("Employee 1");
1448         TreeItem<String> employee2 = new TreeItem<>("Employee 2");
1449 
1450         group2.getChildren().addAll(employee1, employee2);
1451 
1452         view.expandedItemCountProperty().addListener((observableValue, oldCount, newCount) -> {
1453 
1454             // DEBUG OUTPUT
1455 //                System.out.println("new expanded item count: " + newCount.intValue());
1456 //                for (int i = 0; i < newCount.intValue(); i++) {
1457 //                    TreeItem<String> item = view.getTreeItem(i);
1458 //                    String text = item.getValue();
1459 //                    System.out.println("person found at index " + i + " is " + text);
1460 //                }
1461 //                System.out.println("------------------------------------------");
1462 
1463             if (test_rt_35213_eventCount == 0) {
1464                 assertEquals(4, newCount);
1465                 assertEquals("Boss", view.getTreeItem(0).getValue());
1466                 assertEquals("Group 1", view.getTreeItem(1).getValue());
1467                 assertEquals("Group 2", view.getTreeItem(2).getValue());
1468                 assertEquals("Group 3", view.getTreeItem(3).getValue());
1469             } else if (test_rt_35213_eventCount == 1) {
1470                 assertEquals(6, newCount);
1471                 assertEquals("Boss", view.getTreeItem(0).getValue());
1472                 assertEquals("Group 1", view.getTreeItem(1).getValue());
1473                 assertEquals("Group 2", view.getTreeItem(2).getValue());
1474                 assertEquals("Employee 1", view.getTreeItem(3).getValue());
1475                 assertEquals("Employee 2", view.getTreeItem(4).getValue());
1476                 assertEquals("Group 3", view.getTreeItem(5).getValue());
1477             } else if (test_rt_35213_eventCount == 2) {
1478                 assertEquals(4, newCount);
1479                 assertEquals("Boss", view.getTreeItem(0).getValue());
1480                 assertEquals("Group 1", view.getTreeItem(1).getValue());
1481                 assertEquals("Group 2", view.getTreeItem(2).getValue());
1482                 assertEquals("Group 3", view.getTreeItem(3).getValue());
1483             }
1484 
1485             test_rt_35213_eventCount++;
1486         });
1487 
1488         StageLoader sl = new StageLoader(view);
1489 
1490         root.setExpanded(true);
1491         Toolkit.getToolkit().firePulse();
1492 
1493         group2.setExpanded(true);
1494         Toolkit.getToolkit().firePulse();
1495 
1496         group2.setExpanded(false);
1497         Toolkit.getToolkit().firePulse();
1498 
1499         sl.dispose();
1500     }
1501 
1502     @Test public void test_rt23245_itemIsInTree() {
1503         final TreeView<String> view = new TreeView<String>();
1504         final List<TreeItem<String>> items = new ArrayList<>();
1505         for (int i = 0; i < 10; i++) {
1506             final TreeItem<String> item = new TreeItem<String>("Item" + i);
1507             item.setExpanded(true);
1508             items.add(item);
1509         }
1510 
1511         // link the items up so that the next item is the child of the current item
1512         for (int i = 0; i < 9; i++) {
1513             items.get(i).getChildren().add(items.get(i + 1));
1514         }
1515 
1516         view.setRoot(items.get(0));
1517 
1518         for (int i = 0; i < 10; i++) {
1519             // we expect the level of the tree item at the ith position to be
1520             // 0, as every iteration we are setting the ith item as the root.
1521             assertEquals(0, view.getTreeItemLevel(items.get(i)));
1522 
1523             // whilst we are testing, we should also ensure that the ith item
1524             // is indeed the root item, and that the ith item is indeed the item
1525             // at the 0th position
1526             assertEquals(items.get(i), view.getRoot());
1527             assertEquals(items.get(i), view.getTreeItem(0));
1528 
1529             // shuffle the next item into the root position (keeping its parent
1530             // chain intact - which is what exposes this issue in the first place).
1531             if (i < 9) {
1532                 view.setRoot(items.get(i + 1));
1533             }
1534         }
1535     }
1536 
1537     @Test public void test_rt23245_itemIsNotInTree_noRootNode() {
1538         final TreeView<String> view = new TreeView<String>();
1539         final List<TreeItem<String>> items = new ArrayList<>();
1540         for (int i = 0; i < 10; i++) {
1541             final TreeItem<String> item = new TreeItem<String>("Item" + i);
1542             item.setExpanded(true);
1543             items.add(item);
1544         }
1545 
1546         // link the items up so that the next item is the child of the current item
1547         for (int i = 0; i < 9; i++) {
1548             items.get(i).getChildren().add(items.get(i + 1));
1549         }
1550 
1551         for (int i = 0; i < 10; i++) {
1552             // because we have no root (and we are not changing the root like
1553             // the previous test), we expect the tree item level of the item
1554             // in the ith position to be i.
1555             assertEquals(i, view.getTreeItemLevel(items.get(i)));
1556 
1557             // all items requested from the TreeView should be null, as the
1558             // TreeView does not have a root item
1559             assertNull(view.getTreeItem(i));
1560         }
1561     }
1562 
1563     @Test public void test_rt23245_itemIsNotInTree_withUnrelatedRootNode() {
1564         final TreeView<String> view = new TreeView<String>();
1565         final List<TreeItem<String>> items = new ArrayList<>();
1566         for (int i = 0; i < 10; i++) {
1567             final TreeItem<String> item = new TreeItem<String>("Item" + i);
1568             item.setExpanded(true);
1569             items.add(item);
1570         }
1571 
1572         // link the items up so that the next item is the child of the current item
1573         for (int i = 0; i < 9; i++) {
1574             items.get(i).getChildren().add(items.get(i + 1));
1575         }
1576 
1577         view.setRoot(new TreeItem("Unrelated root node"));
1578 
1579         for (int i = 0; i < 10; i++) {
1580             // because we have no root (and we are not changing the root like
1581             // the previous test), we expect the tree item level of the item
1582             // in the ith position to be i.
1583             assertEquals(i, view.getTreeItemLevel(items.get(i)));
1584 
1585             // all items requested from the TreeView should be null except for
1586             // the root node
1587             assertNull(view.getTreeItem(i + 1));
1588         }
1589     }
1590 
1591     @Test public void test_rt35039_setRoot() {
1592         TreeItem<String> root = new TreeItem<>("Root");
1593         root.setExpanded(true);
1594         root.getChildren().addAll(
1595                 new TreeItem("aabbaa"),
1596                 new TreeItem("bbc"));
1597 
1598         final TreeView<String> treeView = new TreeView<>();
1599         treeView.setRoot(root);
1600 
1601         StageLoader sl = new StageLoader(treeView);
1602 
1603         // We start with selection on row -1
1604         assertNull(treeView.getSelectionModel().getSelectedItem());
1605 
1606         // select "bbc" and ensure everything is set to that
1607         treeView.getSelectionModel().select(2);
1608         assertEquals("bbc", treeView.getSelectionModel().getSelectedItem().getValue());
1609 
1610         // change the items list - but retain the same content. We expect
1611         // that "bbc" remains selected as it is still in the list
1612         treeView.setRoot(root);
1613         assertEquals("bbc", treeView.getSelectionModel().getSelectedItem().getValue());
1614 
1615         sl.dispose();
1616     }
1617 
1618     @Test public void test_rt35039_resetRootChildren() {
1619         TreeItem aabbaa = new TreeItem("aabbaa");
1620         TreeItem bbc = new TreeItem("bbc");
1621 
1622         TreeItem<String> root = new TreeItem<>("Root");
1623         root.setExpanded(true);
1624         root.getChildren().setAll(aabbaa, bbc);
1625 
1626         final TreeView<String> treeView = new TreeView<>();
1627         treeView.setRoot(root);
1628 
1629         StageLoader sl = new StageLoader(treeView);
1630 
1631         // We start with selection on row -1
1632         assertNull(treeView.getSelectionModel().getSelectedItem());
1633 
1634         // select "bbc" and ensure everything is set to that
1635         treeView.getSelectionModel().select(2);
1636         assertEquals("bbc", treeView.getSelectionModel().getSelectedItem().getValue());
1637 
1638         // change the items list - but retain the same content. We expect
1639         // that "bbc" remains selected as it is still in the list
1640         root.getChildren().setAll(aabbaa, bbc);
1641         assertEquals("bbc", treeView.getSelectionModel().getSelectedItem().getValue());
1642 
1643         sl.dispose();
1644     }
1645 
1646     @Test public void test_rt35857() {
1647         TreeItem<String> root = new TreeItem<>("Root");
1648         root.setExpanded(true);
1649         TreeItem a = new TreeItem("A");
1650         TreeItem b = new TreeItem("B");
1651         TreeItem c = new TreeItem("C");
1652         root.getChildren().setAll(a, b, c);
1653 
1654         final TreeView<String> treeTableView = new TreeView<String>(root);
1655 
1656         treeTableView.getSelectionModel().select(1);
1657 
1658         ObservableList<TreeItem<String>> selectedItems = treeTableView.getSelectionModel().getSelectedItems();
1659         assertEquals(1, selectedItems.size());
1660         assertEquals("A", selectedItems.get(0).getValue());
1661 
1662         root.getChildren().removeAll(selectedItems);
1663         assertEquals(2, root.getChildren().size());
1664         assertEquals("B", root.getChildren().get(0).getValue());
1665         assertEquals("C", root.getChildren().get(1).getValue());
1666     }
1667 
1668     private int rt_35889_cancel_count = 0;
1669     @Test public void test_rt35889() {
1670         TreeItem a = new TreeItem("a");
1671         TreeItem b = new TreeItem("b");
1672         TreeItem<String> root = new TreeItem<>("Root");
1673         root.setExpanded(true);
1674         root.getChildren().setAll(a, b);
1675 
1676         final TreeView<String> textFieldTreeView = new TreeView<String>(root);
1677         textFieldTreeView.setEditable(true);
1678         textFieldTreeView.setCellFactory(TextFieldTreeCell.forTreeView());
1679         textFieldTreeView.setOnEditCancel(t -> {
1680             rt_35889_cancel_count++;
1681             System.out.println("On Edit Cancel: " + t);
1682         });
1683 
1684         TreeCell cell0 = (TreeCell) VirtualFlowTestUtils.getCell(textFieldTreeView, 0);
1685         assertNull(cell0.getGraphic());
1686         assertEquals("Root", cell0.getText());
1687 
1688         textFieldTreeView.edit(root);
1689         TextField textField = (TextField) cell0.getGraphic();
1690         assertNotNull(textField);
1691 
1692         assertEquals(0, rt_35889_cancel_count);
1693 
1694         textField.setText("Z");
1695         textField.getOnAction().handle(new ActionEvent());
1696 
1697         assertEquals(0, rt_35889_cancel_count);
1698     }
1699 
1700     @Test public void test_rt36255_selection_does_not_expand_item() {
1701         TreeItem a = new TreeItem("a");
1702         TreeItem b = new TreeItem("b");
1703         b.getChildren().add(new TreeItem("bb"));
1704 
1705         final TreeItem<String> root = new TreeItem<>();
1706         root.getChildren().addAll(a, b);
1707         root.setExpanded(true);
1708         TreeView<String> view = new TreeView<>(root);
1709         view.setCellFactory(TextFieldTreeCell.forTreeView());
1710 
1711         view.getSelectionModel().select(a);
1712 
1713         assertEquals(Arrays.asList(a), view.getSelectionModel().getSelectedItems());
1714         assertFalse(b.isExpanded());
1715 
1716         view.getSelectionModel().select(b);
1717         assertEquals(Arrays.asList(b), view.getSelectionModel().getSelectedItems());
1718         assertFalse(b.isExpanded());
1719     }
1720 
1721     @Test public void test_rt25679() {
1722         Button focusBtn = new Button("Focus here");
1723 
1724         TreeItem<String> root = new TreeItem<>("Root");
1725         root.getChildren().setAll(new TreeItem("a"), new TreeItem("b"));
1726         root.setExpanded(true);
1727 
1728         final TreeView<String> treeView = new TreeView<>(root);
1729         SelectionModel sm = treeView.getSelectionModel();
1730 
1731         VBox vbox = new VBox(focusBtn, treeView);
1732 
1733         StageLoader sl = new StageLoader(vbox);
1734         sl.getStage().requestFocus();
1735         focusBtn.requestFocus();
1736         Toolkit.getToolkit().firePulse();
1737 
1738         // test initial state
1739         assertEquals(sl.getStage().getScene().getFocusOwner(), focusBtn);
1740         assertTrue(focusBtn.isFocused());
1741         assertEquals(-1, sm.getSelectedIndex());
1742         assertNull(sm.getSelectedItem());
1743 
1744         // move focus to the treeview
1745         treeView.requestFocus();
1746 
1747         // ensure that there is a selection (where previously there was not one)
1748         assertEquals(sl.getStage().getScene().getFocusOwner(), treeView);
1749         assertTrue(treeView.isFocused());
1750         assertEquals(-1, sm.getSelectedIndex());
1751         assertNull(sm.getSelectedItem());
1752 
1753         sl.dispose();
1754     }
1755 
1756     @Test public void test_rt36885_addChildBeforeSelection() {
1757         test_rt36885(false);
1758     }
1759 
1760     @Test public void test_rt36885_addChildAfterSelection() {
1761         test_rt36885(true);
1762     }
1763 
1764     private void test_rt36885(boolean addChildToAAfterSelection) {
1765         TreeItem<String> root = new TreeItem<>("Root");     // 0
1766             TreeItem<String> a = new TreeItem<>("a");       // 1
1767                 TreeItem<String> a1 = new TreeItem<>("a1"); // a expanded = 2, a collapsed = -1
1768             TreeItem<String> b = new TreeItem<>("b");       // a expanded = 3, a collapsed = 2
1769                 TreeItem<String> b1 = new TreeItem<>("b1"); // a expanded = 4, a collapsed = 3
1770                 TreeItem<String> b2 = new TreeItem<>("b2"); // a expanded = 5, a collapsed = 4
1771 
1772         root.setExpanded(true);
1773         root.getChildren().setAll(a, b);
1774 
1775         a.setExpanded(false);
1776         if (!addChildToAAfterSelection) {
1777             a.getChildren().add(a1);
1778         }
1779 
1780         b.setExpanded(true);
1781         b.getChildren().addAll(b1, b2);
1782 
1783         final TreeView<String> treeView = new TreeView<String>(root);
1784 
1785         treeView.getFocusModel().focusedIndexProperty().addListener((observable, oldValue, newValue) -> {
1786             System.out.println("focusedIndex: " + oldValue + " to " + newValue);
1787         });
1788 
1789         MultipleSelectionModel<TreeItem<String>> sm = treeView.getSelectionModel();
1790         FocusModel<TreeItem<String>> fm = treeView.getFocusModel();
1791 
1792         sm.select(b1);
1793         assertEquals(3, sm.getSelectedIndex());
1794         assertEquals(b1, sm.getSelectedItem());
1795         assertEquals(3, fm.getFocusedIndex());
1796         assertEquals(b1, fm.getFocusedItem());
1797 
1798         if (addChildToAAfterSelection) {
1799             a.getChildren().add(a1);
1800         }
1801 
1802         a.setExpanded(true);
1803         assertEquals(4, sm.getSelectedIndex());
1804         assertEquals(b1, sm.getSelectedItem());
1805         assertEquals(4, fm.getFocusedIndex());
1806         assertEquals(b1, fm.getFocusedItem());
1807     }
1808 
1809     private int rt_37061_index_counter = 0;
1810     private int rt_37061_item_counter = 0;
1811     @Test public void test_rt_37061() {
1812         TreeItem<Integer> root = new TreeItem<>(0);
1813         root.setExpanded(true);
1814         TreeView<Integer> tv = new TreeView<>();
1815         tv.setRoot(root);
1816         tv.getSelectionModel().select(0);
1817 
1818         // note we add the listeners after the selection is made, so the counters
1819         // at this point are still both at zero.
1820         tv.getSelectionModel().selectedIndexProperty().addListener((observable, oldValue, newValue) -> {
1821             rt_37061_index_counter++;
1822         });
1823 
1824         tv.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
1825             rt_37061_item_counter++;
1826         });
1827 
1828         // add a new item. This does not impact the selected index or selected item
1829         // so the counters should remain at zero.
1830         tv.getRoot().getChildren().add(new TreeItem("1"));
1831         assertEquals(0, rt_37061_index_counter);
1832         assertEquals(0, rt_37061_item_counter);
1833     }
1834 
1835     private int rt_37395_index_addCount = 0;
1836     private int rt_37395_index_removeCount = 0;
1837     private int rt_37395_index_permutationCount = 0;
1838     private int rt_37395_item_addCount = 0;
1839     private int rt_37395_item_removeCount = 0;
1840     private int rt_37395_item_permutationCount = 0;
1841 
1842     @Test public void test_rt_37395() {
1843         // tree items - 3 items, 2nd item has 2 children
1844         TreeItem<String> root = new TreeItem<>();
1845 
1846         TreeItem<String> two = new TreeItem<>("two");
1847         two.getChildren().add(new TreeItem<>("childOne"));
1848         two.getChildren().add(new TreeItem<>("childTwo"));
1849 
1850         root.getChildren().add(new TreeItem<>("one"));
1851         root.getChildren().add(two);
1852         root.getChildren().add(new TreeItem<>("three"));
1853 
1854         // tree
1855         TreeView<String> tree = new TreeView<>();
1856         tree.setShowRoot(false);
1857         tree.setRoot(root);
1858 
1859         MultipleSelectionModel sm = tree.getSelectionModel();
1860         sm.getSelectedIndices().addListener(new ListChangeListener<Integer>() {
1861             @Override public void onChanged(Change<? extends Integer> c) {
1862                 while (c.next()) {
1863                     if (c.wasRemoved()) {
1864                         c.getRemoved().forEach(item -> {
1865                             if (item == null) {
1866                                 fail("Removed index should never be null");
1867                             } else {
1868                                 rt_37395_index_removeCount++;
1869                             }
1870                         });
1871                     }
1872                     if (c.wasAdded()) {
1873                         c.getAddedSubList().forEach(item -> {
1874                             rt_37395_index_addCount++;
1875                         });
1876                     }
1877                     if (c.wasPermutated()) {
1878                         rt_37395_index_permutationCount++;
1879                     }
1880                 }
1881             }
1882         });
1883         sm.getSelectedItems().addListener(new ListChangeListener<TreeItem<String>>() {
1884             @Override public void onChanged(Change<? extends TreeItem<String>> c) {
1885                 while (c.next()) {
1886                     if (c.wasRemoved()) {
1887                         c.getRemoved().forEach(item -> {
1888                             if (item == null) {
1889                                 fail("Removed item should never be null");
1890                             } else {
1891                                 rt_37395_item_removeCount++;
1892                             }
1893                         });
1894                     }
1895                     if (c.wasAdded()) {
1896                         c.getAddedSubList().forEach(item -> {
1897                             rt_37395_item_addCount++;
1898                         });
1899                     }
1900                     if (c.wasPermutated()) {
1901                         rt_37395_item_permutationCount++;
1902                     }
1903                 }
1904             }
1905         });
1906 
1907         assertEquals(0, rt_37395_index_removeCount);
1908         assertEquals(0, rt_37395_index_addCount);
1909         assertEquals(0, rt_37395_index_permutationCount);
1910         assertEquals(0, rt_37395_item_removeCount);
1911         assertEquals(0, rt_37395_item_addCount);
1912         assertEquals(0, rt_37395_item_permutationCount);
1913 
1914         StageLoader sl = new StageLoader(tree);
1915 
1916         // step one: select item 'three' in index 2
1917         sm.select(2);
1918         assertEquals(0, rt_37395_index_removeCount);
1919         assertEquals(1, rt_37395_index_addCount);
1920         assertEquals(0, rt_37395_index_permutationCount);
1921         assertEquals(0, rt_37395_item_removeCount);
1922         assertEquals(1, rt_37395_item_addCount);
1923         assertEquals(0, rt_37395_item_permutationCount);
1924 
1925         // step two: expand item 'two'
1926         // The first part of the bug report was that we received add/remove
1927         // change events here, when in reality we shouldn't have, so lets enforce
1928         // that. We do expect a permutation event on the index, as it has been
1929         // pushed down, but this should not result in an item permutation event,
1930         // as it remains unchanged
1931         two.setExpanded(true);
1932         assertEquals(0, rt_37395_index_removeCount);
1933         assertEquals(1, rt_37395_index_addCount);
1934         assertEquals(1, rt_37395_index_permutationCount);
1935         assertEquals(0, rt_37395_item_removeCount);
1936         assertEquals(1, rt_37395_item_addCount);
1937         assertEquals(0, rt_37395_item_permutationCount);
1938 
1939         // step three: collapse item 'two'
1940         // Same argument as in step two above: no addition or removal, just a
1941         // permutation on the index
1942         two.setExpanded(false);
1943         assertEquals(0, rt_37395_index_removeCount);
1944         assertEquals(1, rt_37395_index_addCount);
1945         assertEquals(2, rt_37395_index_permutationCount);
1946         assertEquals(0, rt_37395_item_removeCount);
1947         assertEquals(1, rt_37395_item_addCount);
1948         assertEquals(0, rt_37395_item_permutationCount);
1949 
1950         sl.dispose();
1951     }
1952 
1953     @Test public void test_rt_37502() {
1954         final TreeView<Long> tree = new TreeView<>(new NumberTreeItem(1));
1955         tree.setCellFactory(new Callback<TreeView<Long>, TreeCell<Long>>() {
1956             @Override
1957             public TreeCell<Long> call(TreeView<Long> param) {
1958                 return new TreeCell<Long>() {
1959                     @Override
1960                     protected void updateItem(Long item, boolean empty) {
1961                         super.updateItem(item, empty);
1962                         if (!empty) {
1963                             setText(item != null ? String.valueOf(item) : "");
1964                         } else{
1965                             setText(null);
1966                         }
1967                     }
1968                 };
1969             }
1970         });
1971 
1972         StageLoader sl = new StageLoader(tree);
1973 
1974         tree.getSelectionModel().select(0);
1975         tree.getRoot().setExpanded(true);
1976         Toolkit.getToolkit().firePulse();
1977 
1978         sl.dispose();
1979     }
1980 
1981     private static class NumberTreeItem extends TreeItem<Long>{
1982         private boolean loaded = false;
1983 
1984         private NumberTreeItem(long value) {
1985             super(value);
1986         }
1987 
1988         @Override public boolean isLeaf() {
1989             return false;
1990         }
1991 
1992         @Override public ObservableList<TreeItem<Long>> getChildren() {
1993             if(!loaded){
1994                 final ObservableList<TreeItem<Long>> children =  super.getChildren();
1995                 for (int i = 0; i < 10; i++) {
1996                     children.add(new NumberTreeItem(10 * getValue() + i));
1997                 }
1998                 loaded = true;
1999             }
2000             return super.getChildren();
2001         }
2002     }
2003 
2004     private int rt_37538_count = 0;
2005     @Test public void test_rt_37538_noCNextCall() {
2006         test_rt_37538(false, false);
2007     }
2008 
2009     @Test public void test_rt_37538_callCNextOnce() {
2010         test_rt_37538(true, false);
2011     }
2012 
2013     @Test public void test_rt_37538_callCNextInLoop() {
2014         test_rt_37538(false, true);
2015     }
2016 
2017     private void test_rt_37538(boolean callCNextOnce, boolean callCNextInLoop) {
2018         // create table with a bunch of rows and 1 column...
2019         TreeItem<Integer> root = new TreeItem<>(0);
2020         root.setExpanded(true);
2021         for (int i = 1; i <= 50; i++) {
2022             root.getChildren().add(new TreeItem<>(i));
2023         }
2024 
2025         final TreeView<Integer> tree = new TreeView<>(root);
2026 
2027         tree.getSelectionModel().getSelectedItems().addListener((ListChangeListener.Change<? extends TreeItem<Integer>> c) -> {
2028             if (callCNextOnce) {
2029                 c.next();
2030             } else if (callCNextInLoop) {
2031                 while (c.next()) {
2032                     // no-op
2033                 }
2034             }
2035 
2036             if (rt_37538_count >= 1) {
2037                 Thread.dumpStack();
2038                 fail("This method should only be called once");
2039             }
2040 
2041             rt_37538_count++;
2042         });
2043 
2044         StageLoader sl = new StageLoader(tree);
2045         assertEquals(0, rt_37538_count);
2046         tree.getSelectionModel().select(0);
2047         assertEquals(1, rt_37538_count);
2048         sl.dispose();
2049     }
2050 
2051     @Ignore("Fix not yet developed for TreeView")
2052     @Test public void test_rt_35395_fixedCellSize() {
2053         test_rt_35395(true);
2054     }
2055 
2056     @Ignore("Fix not yet developed for TreeView")
2057     @Test public void test_rt_35395_notFixedCellSize() {
2058         test_rt_35395(false);
2059     }
2060 
2061     private int rt_35395_counter;
2062     private void test_rt_35395(boolean useFixedCellSize) {
2063         rt_35395_counter = 0;
2064 
2065         TreeItem<String> root = new TreeItem<>("green");
2066         root.setExpanded(true);
2067         for (int i = 0; i < 20; i++) {
2068             root.getChildren().addAll(new TreeItem<>("red"), new TreeItem<>("green"), new TreeItem<>("blue"), new TreeItem<>("purple"));
2069         }
2070 
2071         TreeView<String> treeView = new TreeView<>(root);
2072         if (useFixedCellSize) {
2073             treeView.setFixedCellSize(24);
2074         }
2075         treeView.setCellFactory(tv -> new TreeCell<String>() {
2076             @Override protected void updateItem(String color, boolean empty) {
2077                 rt_35395_counter += 1;
2078                 super.updateItem(color, empty);
2079                 setText(null);
2080                 if(empty) {
2081                     setGraphic(null);
2082                 } else {
2083                     Rectangle rect = new Rectangle(16, 16);
2084                     rect.setStyle("-fx-fill: " + color);
2085                     setGraphic(rect);
2086                 }
2087             }
2088         });
2089 
2090         StageLoader sl = new StageLoader(treeView);
2091 
2092         Platform.runLater(() -> {
2093             rt_35395_counter = 0;
2094             root.getChildren().set(10, new TreeItem<>("yellow"));
2095             Platform.runLater(() -> {
2096                 Toolkit.getToolkit().firePulse();
2097                 assertEquals(1, rt_35395_counter);
2098                 rt_35395_counter = 0;
2099                 root.getChildren().set(30, new TreeItem<>("yellow"));
2100                 Platform.runLater(() -> {
2101                     Toolkit.getToolkit().firePulse();
2102                     assertEquals(0, rt_35395_counter);
2103                     rt_35395_counter = 0;
2104                     treeView.scrollTo(5);
2105                     Platform.runLater(() -> {
2106                         Toolkit.getToolkit().firePulse();
2107                         assertEquals(5, rt_35395_counter);
2108                         rt_35395_counter = 0;
2109                         treeView.scrollTo(55);
2110                         Platform.runLater(() -> {
2111                             Toolkit.getToolkit().firePulse();
2112 
2113                             int expected = useFixedCellSize ? 17 : 53;
2114                             assertEquals(expected, rt_35395_counter);
2115                             sl.dispose();
2116                         });
2117                     });
2118                 });
2119             });
2120         });
2121     }
2122 
2123     @Test public void test_rt_37632() {
2124         final TreeItem<String> rootOne = new TreeItem<>("Root 1");
2125         final TreeItem<String> rootTwo = new TreeItem<>("Root 2");
2126 
2127         final TreeView<String> treeView = new TreeView<>();
2128         MultipleSelectionModel<TreeItem<String>> sm = treeView.getSelectionModel();
2129         treeView.setRoot(rootOne);
2130         treeView.getSelectionModel().selectFirst();
2131 
2132         assertEquals(0, sm.getSelectedIndex());
2133         assertEquals(rootOne, sm.getSelectedItem());
2134         assertEquals(1, sm.getSelectedIndices().size());
2135         assertEquals(0, (int) sm.getSelectedIndices().get(0));
2136         assertEquals(1, sm.getSelectedItems().size());
2137         assertEquals(rootOne, sm.getSelectedItems().get(0));
2138 
2139         treeView.setRoot(rootTwo);
2140 
2141         assertEquals(-1, sm.getSelectedIndex());
2142         assertNull(sm.getSelectedItem());
2143         assertEquals(0, sm.getSelectedIndices().size());
2144         assertEquals(0, sm.getSelectedItems().size());
2145     }
2146 
2147     @Test public void test_rt_37853_replaceRoot() {
2148         test_rt_37853(true);
2149     }
2150 
2151     @Test public void test_rt_37853_replaceRootChildren() {
2152         test_rt_37853(false);
2153     }
2154 
2155     private int rt_37853_cancelCount;
2156     private int rt_37853_commitCount;
2157     private void test_rt_37853(boolean replaceRoot) {
2158         treeView.setCellFactory(TextFieldTreeCell.forTreeView());
2159         treeView.setEditable(true);
2160         treeView.setRoot(new TreeItem<>("Root"));
2161         treeView.getRoot().setExpanded(true);
2162 
2163         for (int i = 0; i < 10; i++) {
2164             treeView.getRoot().getChildren().add(new TreeItem<>("" + i));
2165         }
2166 
2167         StageLoader sl = new StageLoader(treeView);
2168 
2169         treeView.setOnEditCancel(editEvent -> rt_37853_cancelCount++);
2170         treeView.setOnEditCommit(editEvent -> rt_37853_commitCount++);
2171 
2172         assertEquals(0, rt_37853_cancelCount);
2173         assertEquals(0, rt_37853_commitCount);
2174 
2175         treeView.edit(treeView.getRoot().getChildren().get(0));
2176         assertNotNull(treeView.getEditingItem());
2177 
2178         if (replaceRoot) {
2179             treeView.setRoot(new TreeItem<>("New Root"));
2180         } else {
2181             treeView.getRoot().getChildren().clear();
2182             for (int i = 0; i < 10; i++) {
2183                 treeView.getRoot().getChildren().add(new TreeItem<>("new item " + i));
2184             }
2185         }
2186 
2187         assertEquals(1, rt_37853_cancelCount);
2188         assertEquals(0, rt_37853_commitCount);
2189 
2190         sl.dispose();
2191     }
2192 
2193     @Test public void test_rt_38787_remove_b() {
2194         // Remove 'b', selection moves to 'a'
2195         test_rt_38787("a", 0, 1);
2196     }
2197 
2198     @Test public void test_rt_38787_remove_b_c() {
2199         // Remove 'b' and 'c', selection moves to 'a'
2200         test_rt_38787("a", 0, 1, 2);
2201     }
2202 
2203     @Test public void test_rt_38787_remove_c_d() {
2204         // Remove 'c' and 'd', selection moves to 'b'
2205         test_rt_38787("b", 1, 2, 3);
2206     }
2207 
2208     @Test public void test_rt_38787_remove_a() {
2209         // Remove 'a', selection moves to 'b', now in index 0
2210         test_rt_38787("b", 0, 0);
2211     }
2212 
2213     private void test_rt_38787(String expectedItem, int expectedIndex, int... indicesToRemove) {
2214         TreeItem<String> a, b, c, d;
2215         TreeItem<String> root = new TreeItem<>("Root");
2216         root.setExpanded(true);
2217         root.getChildren().addAll(
2218                 a = new TreeItem<String>("a"),
2219                 b = new TreeItem<String>("b"),
2220                 c = new TreeItem<String>("c"),
2221                 d = new TreeItem<String>("d")
2222         );
2223 
2224         TreeView<String> stringTreeView = new TreeView<>(root);
2225         stringTreeView.setShowRoot(false);
2226 
2227 //        TableColumn<String,String> column = new TableColumn<>("Column");
2228 //        column.setCellValueFactory(cdf -> new ReadOnlyStringWrapper(cdf.getValue()));
2229 //        stringTableView.getColumns().add(column);
2230 
2231         MultipleSelectionModel<TreeItem<String>> sm = stringTreeView.getSelectionModel();
2232         sm.select(b);
2233 
2234         // test pre-conditions
2235         assertEquals(1, sm.getSelectedIndex());
2236         assertEquals(1, (int)sm.getSelectedIndices().get(0));
2237         assertEquals(b, sm.getSelectedItem());
2238         assertEquals(b, sm.getSelectedItems().get(0));
2239         assertFalse(sm.isSelected(0));
2240         assertTrue(sm.isSelected(1));
2241         assertFalse(sm.isSelected(2));
2242 
2243         // removing items
2244         List<TreeItem<String>> itemsToRemove = new ArrayList<>(indicesToRemove.length);
2245         for (int index : indicesToRemove) {
2246             itemsToRemove.add(root.getChildren().get(index));
2247         }
2248         root.getChildren().removeAll(itemsToRemove);
2249 
2250         // testing against expectations
2251         assertEquals(expectedIndex, sm.getSelectedIndex());
2252         assertEquals(expectedIndex, (int)sm.getSelectedIndices().get(0));
2253         assertEquals(expectedItem, sm.getSelectedItem().getValue());
2254         assertEquals(expectedItem, sm.getSelectedItems().get(0).getValue());
2255     }
2256 
2257     private int rt_38341_indices_count = 0;
2258     private int rt_38341_items_count = 0;
2259     @Test public void test_rt_38341() {
2260         Callback<Integer, TreeItem<String>> callback = number -> {
2261             final TreeItem<String> root = new TreeItem<>("Root " + number);
2262             final TreeItem<String> child = new TreeItem<>("Child " + number);
2263 
2264             root.getChildren().add(child);
2265             return root;
2266         };
2267 
2268         final TreeItem<String> root = new TreeItem<String>();
2269         root.setExpanded(true);
2270         root.getChildren().addAll(callback.call(1), callback.call(2));
2271 
2272         final TreeView<String> treeView = new TreeView<>(root);
2273         treeView.setShowRoot(false);
2274 
2275         MultipleSelectionModel<TreeItem<String>> sm = treeView.getSelectionModel();
2276         sm.getSelectedIndices().addListener((ListChangeListener<Integer>) c -> rt_38341_indices_count++);
2277         sm.getSelectedItems().addListener((ListChangeListener<TreeItem<String>>) c -> rt_38341_items_count++);
2278 
2279         assertEquals(0, rt_38341_indices_count);
2280         assertEquals(0, rt_38341_items_count);
2281 
2282         // expand the first child of root, and select it (note: root isn't visible)
2283         root.getChildren().get(0).setExpanded(true);
2284         sm.select(1);
2285         assertEquals(1, sm.getSelectedIndex());
2286         assertEquals(1, sm.getSelectedIndices().size());
2287         assertEquals(1, (int)sm.getSelectedIndices().get(0));
2288         assertEquals(1, sm.getSelectedItems().size());
2289         assertEquals("Child 1", sm.getSelectedItem().getValue());
2290         assertEquals("Child 1", sm.getSelectedItems().get(0).getValue());
2291 
2292         assertEquals(1, rt_38341_indices_count);
2293         assertEquals(1, rt_38341_items_count);
2294 
2295         // now delete it
2296         root.getChildren().get(0).getChildren().remove(0);
2297 
2298         // selection should move to the childs parent in index 0
2299         assertEquals(0, sm.getSelectedIndex());
2300         assertEquals(1, sm.getSelectedIndices().size());
2301         assertEquals(0, (int)sm.getSelectedIndices().get(0));
2302         assertEquals(1, sm.getSelectedItems().size());
2303         assertEquals("Root 1", sm.getSelectedItem().getValue());
2304         assertEquals("Root 1", sm.getSelectedItems().get(0).getValue());
2305 
2306         // we also expect there to be an event in the selection model for
2307         // selected indices and selected items
2308         assertEquals(2, rt_38341_indices_count);
2309         assertEquals(2, rt_38341_items_count);
2310     }
2311 
2312     private int rt_38943_index_count = 0;
2313     private int rt_38943_item_count = 0;
2314     @Test public void test_rt_38943() {
2315         TreeItem<String> root = new TreeItem<>("Root");
2316         root.setExpanded(true);
2317         root.getChildren().addAll(
2318             new TreeItem<>("a"),
2319             new TreeItem<>("b"),
2320             new TreeItem<>("c"),
2321             new TreeItem<>("d")
2322         );
2323 
2324         final TreeView<String> treeView = new TreeView<>(root);
2325         treeView.setShowRoot(false);
2326 
2327         MultipleSelectionModel<TreeItem<String>> sm = treeView.getSelectionModel();
2328 
2329         sm.selectedIndexProperty().addListener((observable, oldValue, newValue) -> rt_38943_index_count++);
2330         sm.selectedItemProperty().addListener((observable, oldValue, newValue) -> rt_38943_item_count++);
2331 
2332         assertEquals(-1, sm.getSelectedIndex());
2333         assertNull(sm.getSelectedItem());
2334         assertEquals(0, rt_38943_index_count);
2335         assertEquals(0, rt_38943_item_count);
2336 
2337         sm.select(0);
2338         assertEquals(0, sm.getSelectedIndex());
2339         assertEquals("a", sm.getSelectedItem().getValue());
2340         assertEquals(1, rt_38943_index_count);
2341         assertEquals(1, rt_38943_item_count);
2342 
2343         sm.clearSelection(0);
2344         assertEquals(-1, sm.getSelectedIndex());
2345         assertNull(sm.getSelectedItem());
2346         assertEquals(2, rt_38943_index_count);
2347         assertEquals(2, rt_38943_item_count);
2348     }
2349 
2350     @Test public void test_rt_38884() {
2351         final TreeItem<String> root = new TreeItem<>("Root");
2352         final TreeItem<String> foo = new TreeItem<>("foo");
2353 
2354         TreeView<String> treeView = new TreeView<>(root);
2355         treeView.setShowRoot(false);
2356         root.setExpanded(true);
2357 
2358         treeView.getSelectionModel().getSelectedItems().addListener((ListChangeListener.Change<? extends TreeItem<String>> c) -> {
2359             while (c.next()) {
2360                 if (c.wasRemoved()) {
2361                     assertTrue(c.getRemovedSize() > 0);
2362 
2363                     List<? extends TreeItem<String>> removed = c.getRemoved();
2364                     TreeItem<String> removedItem = null;
2365                     try {
2366                         removedItem = removed.get(0);
2367                     } catch (Exception e) {
2368                         fail();
2369                     }
2370 
2371                     assertEquals(foo, removedItem);
2372                 }
2373             }
2374         });
2375 
2376         root.getChildren().add(foo);
2377         treeView.getSelectionModel().select(0);
2378         root.getChildren().clear();
2379     }
2380 
2381     private int rt_37360_add_count = 0;
2382     private int rt_37360_remove_count = 0;
2383     @Test public void test_rt_37360() {
2384         TreeItem<String> root = new TreeItem<>("Root");
2385         root.setExpanded(true);
2386         root.getChildren().addAll(
2387                 new TreeItem<>("a"),
2388                 new TreeItem<>("b")
2389         );
2390 
2391         TreeView<String> stringTreeView = new TreeView<>(root);
2392         stringTreeView.setShowRoot(false);
2393 
2394         MultipleSelectionModel<TreeItem<String>> sm = stringTreeView.getSelectionModel();
2395         sm.setSelectionMode(SelectionMode.MULTIPLE);
2396         sm.getSelectedItems().addListener((ListChangeListener<TreeItem<String>>) c -> {
2397             while (c.next()) {
2398                 if (c.wasAdded()) {
2399                     rt_37360_add_count += c.getAddedSize();
2400                 }
2401                 if (c.wasRemoved()) {
2402                     rt_37360_remove_count += c.getRemovedSize();
2403                 }
2404             }
2405         });
2406 
2407         assertEquals(0, sm.getSelectedItems().size());
2408         assertEquals(0, rt_37360_add_count);
2409         assertEquals(0, rt_37360_remove_count);
2410 
2411         sm.select(0);
2412         assertEquals(1, sm.getSelectedItems().size());
2413         assertEquals(1, rt_37360_add_count);
2414         assertEquals(0, rt_37360_remove_count);
2415 
2416         sm.select(1);
2417         assertEquals(2, sm.getSelectedItems().size());
2418         assertEquals(2, rt_37360_add_count);
2419         assertEquals(0, rt_37360_remove_count);
2420 
2421         sm.clearAndSelect(1);
2422         assertEquals(1, sm.getSelectedItems().size());
2423         assertEquals(2, rt_37360_add_count);
2424         assertEquals(1, rt_37360_remove_count);
2425     }
2426 
2427     private int rt_37366_count = 0;
2428     @Test public void test_rt_37366() {
2429         final TreeItem<String> treeItem2 = new TreeItem<>("Item 2");
2430         treeItem2.getChildren().addAll(new TreeItem<>("Item 21"), new TreeItem<>("Item 22"));
2431 
2432         final TreeItem<String> root1 = new TreeItem<>("Root Node 1");
2433         root1.getChildren().addAll(new TreeItem<>("Item 1"), treeItem2, new TreeItem<>("Item 3"));
2434         root1.setExpanded(true);
2435 
2436         final TreeItem<String> root2 = new TreeItem<>("Root Node 2");
2437 
2438         final TreeItem<String> hiddenRoot = new TreeItem<>("Hidden Root Node");
2439         hiddenRoot.getChildren().add(root1);
2440         hiddenRoot.getChildren().add(root2);
2441 
2442         final TreeView<String> treeView = new TreeView<>(hiddenRoot);
2443         treeView.setShowRoot(false);
2444 
2445         MultipleSelectionModel<TreeItem<String>> sm = treeView.getSelectionModel();
2446         sm.setSelectionMode(SelectionMode.MULTIPLE);
2447         sm.getSelectedItems().addListener((ListChangeListener.Change<? extends TreeItem<String>> c) -> {
2448             rt_37366_count++;
2449         });
2450 
2451         assertEquals(0, rt_37366_count);
2452 
2453         sm.select(1);
2454         assertEquals(1, rt_37366_count);
2455         assertFalse(sm.isSelected(0));
2456         assertTrue(sm.isSelected(1));
2457         assertFalse(sm.isSelected(2));
2458 
2459         sm.select(2);
2460         assertEquals(2, rt_37366_count);
2461         assertFalse(sm.isSelected(0));
2462         assertTrue(sm.isSelected(1));
2463         assertTrue(sm.isSelected(2));
2464 
2465         root1.setExpanded(false);
2466         assertEquals(3, rt_37366_count);
2467         assertTrue(sm.isSelected(0));
2468         assertFalse(sm.isSelected(1));
2469         assertFalse(sm.isSelected(2));
2470     }
2471 
2472     @Test public void test_rt_38491() {
2473         TreeItem<String> a;
2474         TreeItem<String> root = new TreeItem<>("Root");
2475         root.setExpanded(true);
2476         root.getChildren().addAll(
2477                 a = new TreeItem<>("a"),
2478                 new TreeItem<>("b")
2479         );
2480 
2481         TreeView<String> stringTreeView = new TreeView<>(root);
2482         stringTreeView.setShowRoot(false);
2483 
2484         MultipleSelectionModel<TreeItem<String>> sm = stringTreeView.getSelectionModel();
2485         sm.setSelectionMode(SelectionMode.MULTIPLE);
2486 
2487         FocusModel<TreeItem<String>> fm = stringTreeView.getFocusModel();
2488 
2489         StageLoader sl = new StageLoader(stringTreeView);
2490 
2491         // test pre-conditions
2492         assertTrue(sm.isEmpty());
2493         assertEquals(a, fm.getFocusedItem());
2494         assertEquals(0, fm.getFocusedIndex());
2495 
2496         // click on row 0
2497         VirtualFlowTestUtils.clickOnRow(stringTreeView, 0);
2498         assertTrue(sm.isSelected(0));
2499         assertEquals(a, sm.getSelectedItem());
2500         assertTrue(fm.isFocused(0));
2501         assertEquals(a, fm.getFocusedItem());
2502         assertEquals(0, fm.getFocusedIndex());
2503 
2504         Integer anchor = TreeCellBehavior.getAnchor(stringTreeView, null);
2505         assertNotNull(anchor);
2506         assertTrue(TreeCellBehavior.hasNonDefaultAnchor(stringTreeView));
2507         assertEquals(0, (int)anchor);
2508 
2509         // now add a new item at row 0. This has the effect of pushing down
2510         // the selected item into row 1.
2511         root.getChildren().add(0, new TreeItem("z"));
2512 
2513         // The first bug was that selection and focus were not moving down to
2514         // be on row 1, so we test that now
2515         assertFalse(sm.isSelected(0));
2516         assertFalse(fm.isFocused(0));
2517         assertTrue(sm.isSelected(1));
2518         assertEquals(a, sm.getSelectedItem());
2519         assertTrue(fm.isFocused(1));
2520         assertEquals(a, fm.getFocusedItem());
2521         assertEquals(1, fm.getFocusedIndex());
2522 
2523         // The second bug was that the anchor was not being pushed down as well
2524         // (when it should).
2525         anchor = TreeCellBehavior.getAnchor(stringTreeView, null);
2526         assertNotNull(anchor);
2527         assertTrue(TreeCellBehavior.hasNonDefaultAnchor(stringTreeView));
2528         assertEquals(1, (int)anchor);
2529 
2530         sl.dispose();
2531     }
2532 
2533     private final ObservableList<TreeItem<String>> rt_39256_list = FXCollections.observableArrayList();
2534     @Test public void test_rt_39256() {
2535         TreeItem<String> root = new TreeItem<>("Root");
2536         root.setExpanded(true);
2537         root.getChildren().addAll(
2538                 new TreeItem<>("a"),
2539                 new TreeItem<>("b"),
2540                 new TreeItem<>("c"),
2541                 new TreeItem<>("d")
2542         );
2543 
2544         TreeView<String> stringTreeView = new TreeView<>(root);
2545         stringTreeView.setShowRoot(false);
2546 
2547         MultipleSelectionModel<TreeItem<String>> sm = stringTreeView.getSelectionModel();
2548         sm.setSelectionMode(SelectionMode.MULTIPLE);
2549 
2550 //        rt_39256_list.addListener((ListChangeListener<TreeItem<String>>) change -> {
2551 //            while (change.next()) {
2552 //                System.err.println("number of selected persons (in bound list): " + change.getList().size());
2553 //            }
2554 //        });
2555 
2556         Bindings.bindContent(rt_39256_list, sm.getSelectedItems());
2557 
2558         assertEquals(0, sm.getSelectedItems().size());
2559         assertEquals(0, rt_39256_list.size());
2560 
2561         sm.selectAll();
2562         assertEquals(4, sm.getSelectedItems().size());
2563         assertEquals(4, rt_39256_list.size());
2564 
2565         sm.selectAll();
2566         assertEquals(4, sm.getSelectedItems().size());
2567         assertEquals(4, rt_39256_list.size());
2568 
2569         sm.selectAll();
2570         assertEquals(4, sm.getSelectedItems().size());
2571         assertEquals(4, rt_39256_list.size());
2572     }
2573 
2574     private final ObservableList<TreeItem<String>> rt_39482_list = FXCollections.observableArrayList();
2575     @Test public void test_rt_39482() {
2576         TreeItem<String> root = new TreeItem<>("Root");
2577         root.setExpanded(true);
2578         root.getChildren().addAll(
2579                 new TreeItem<>("a"),
2580                 new TreeItem<>("b"),
2581                 new TreeItem<>("c"),
2582                 new TreeItem<>("d")
2583         );
2584 
2585         TreeView<String> stringTreeView = new TreeView<>(root);
2586         stringTreeView.setShowRoot(false);
2587 
2588         MultipleSelectionModel<TreeItem<String>> sm = stringTreeView.getSelectionModel();
2589         sm.setSelectionMode(SelectionMode.MULTIPLE);
2590 
2591 //        rt_39256_list.addListener((ListChangeListener<TreeItem<String>>) change -> {
2592 //            while (change.next()) {
2593 //                System.err.println("number of selected persons (in bound list): " + change.getList().size());
2594 //            }
2595 //        });
2596 
2597         Bindings.bindContent(rt_39482_list, sm.getSelectedItems());
2598 
2599         assertEquals(0, sm.getSelectedItems().size());
2600         assertEquals(0, rt_39482_list.size());
2601 
2602         test_rt_39482_selectRow("a", sm, 0);
2603         test_rt_39482_selectRow("b", sm, 1);
2604         test_rt_39482_selectRow("c", sm, 2);
2605         test_rt_39482_selectRow("d", sm, 3);
2606     }
2607 
2608     private void test_rt_39482_selectRow(String expectedString,
2609                                          MultipleSelectionModel<TreeItem<String>> sm,
2610                                          int rowToSelect) {
2611         System.out.println("\nSelect row " + rowToSelect);
2612         sm.selectAll();
2613         assertEquals(4, sm.getSelectedIndices().size());
2614         assertEquals(4, sm.getSelectedItems().size());
2615         assertEquals(4, rt_39482_list.size());
2616 
2617         sm.clearAndSelect(rowToSelect);
2618         assertEquals(1, sm.getSelectedIndices().size());
2619         assertEquals(1, sm.getSelectedItems().size());
2620         assertEquals(expectedString, sm.getSelectedItem().getValue());
2621         assertEquals(expectedString, rt_39482_list.get(0).getValue());
2622         assertEquals(1, rt_39482_list.size());
2623     }
2624 
2625     @Test public void test_rt_39559_useSM_selectAll() {
2626         test_rt_39559(true);
2627     }
2628 
2629     @Test public void test_rt_39559_useKeyboard_selectAll() {
2630         test_rt_39559(false);
2631     }
2632 
2633     private void test_rt_39559(boolean useSMSelectAll) {
2634         TreeItem<String> a, b;
2635         TreeItem<String> root = new TreeItem<>("Root");
2636         root.setExpanded(true);
2637         root.getChildren().addAll(
2638                 a = new TreeItem<>("a"),
2639                 b = new TreeItem<>("b"),
2640                 new TreeItem<>("c"),
2641                 new TreeItem<>("d")
2642         );
2643 
2644         TreeView<String> stringTreeView = new TreeView<>(root);
2645         stringTreeView.setShowRoot(false);
2646 
2647         MultipleSelectionModel<TreeItem<String>> sm = stringTreeView.getSelectionModel();
2648         sm.setSelectionMode(SelectionMode.MULTIPLE);
2649 
2650         StageLoader sl = new StageLoader(stringTreeView);
2651         KeyEventFirer keyboard = new KeyEventFirer(stringTreeView);
2652 
2653         assertEquals(0, sm.getSelectedItems().size());
2654 
2655         sm.clearAndSelect(0);
2656 
2657         if (useSMSelectAll) {
2658             sm.selectAll();
2659         } else {
2660             keyboard.doKeyPress(KeyCode.A, KeyModifier.getShortcutKey());
2661         }
2662 
2663         assertEquals(4, sm.getSelectedItems().size());
2664         assertEquals(0, (int) TreeCellBehavior.getAnchor(stringTreeView, -1));
2665 
2666         keyboard.doKeyPress(KeyCode.DOWN, KeyModifier.SHIFT);
2667 
2668         assertEquals(0, (int) TreeCellBehavior.getAnchor(stringTreeView, -1));
2669         assertEquals(2, sm.getSelectedItems().size());
2670         assertEquals(a, sm.getSelectedItems().get(0));
2671         assertEquals(b, sm.getSelectedItems().get(1));
2672 
2673         sl.dispose();
2674     }
2675 
2676     @Test public void test_rt_16068_firstElement_selectAndRemoveSameRow() {
2677         // select and then remove the 'a' item, selection and focus should both
2678         // stay at the first row, now 'b'
2679         test_rt_16068(0, 0, 0);
2680     }
2681 
2682     @Test public void test_rt_16068_firstElement_selectRowAndRemoveLaterSibling() {
2683         // select row 'a', and remove row 'c', selection and focus should not change
2684         test_rt_16068(0, 2, 0);
2685     }
2686 
2687     @Test public void test_rt_16068_middleElement_selectAndRemoveSameRow() {
2688         // select and then remove the 'b' item, selection and focus should both
2689         // move up one row to the 'a' item
2690         test_rt_16068(1, 1, 0);
2691     }
2692 
2693     @Test public void test_rt_16068_middleElement_selectRowAndRemoveLaterSibling() {
2694         // select row 'b', and remove row 'c', selection and focus should not change
2695         test_rt_16068(1, 2, 1);
2696     }
2697 
2698     @Test public void test_rt_16068_middleElement_selectRowAndRemoveEarlierSibling() {
2699         // select row 'b', and remove row 'a', selection and focus should move up
2700         // one row, remaining on 'b'
2701         test_rt_16068(1, 0, 0);
2702     }
2703 
2704     @Test public void test_rt_16068_lastElement_selectAndRemoveSameRow() {
2705         // select and then remove the 'd' item, selection and focus should both
2706         // move up one row to the 'c' item
2707         test_rt_16068(3, 3, 2);
2708     }
2709 
2710     @Test public void test_rt_16068_lastElement_selectRowAndRemoveEarlierSibling() {
2711         // select row 'd', and remove row 'a', selection and focus should move up
2712         // one row, remaining on 'd'
2713         test_rt_16068(3, 0, 2);
2714     }
2715 
2716     private void test_rt_16068(int indexToSelect, int indexToRemove, int expectedIndex) {
2717         TreeItem<String> root = new TreeItem<>("Root");
2718         root.setExpanded(true);
2719         root.getChildren().addAll(
2720                 new TreeItem<>("a"), // 0
2721                 new TreeItem<>("b"), // 1
2722                 new TreeItem<>("c"), // 2
2723                 new TreeItem<>("d")  // 3
2724         );
2725 
2726         TreeView<String> stringTreeView = new TreeView<>(root);
2727         stringTreeView.setShowRoot(false);
2728         MultipleSelectionModel<TreeItem<String>> sm = stringTreeView.getSelectionModel();
2729         FocusModel<TreeItem<String>> fm = stringTreeView.getFocusModel();
2730 
2731         sm.select(indexToSelect);
2732         assertEquals(indexToSelect, sm.getSelectedIndex());
2733         assertEquals(root.getChildren().get(indexToSelect).getValue(), sm.getSelectedItem().getValue());
2734         assertEquals(indexToSelect, fm.getFocusedIndex());
2735         assertEquals(root.getChildren().get(indexToSelect).getValue(), fm.getFocusedItem().getValue());
2736 
2737         root.getChildren().remove(indexToRemove);
2738         assertEquals(expectedIndex, sm.getSelectedIndex());
2739         assertEquals(root.getChildren().get(expectedIndex).getValue(), sm.getSelectedItem().getValue());
2740         assertEquals(debug(), expectedIndex, fm.getFocusedIndex());
2741         assertEquals(root.getChildren().get(expectedIndex).getValue(), fm.getFocusedItem().getValue());
2742     }
2743 
2744 
2745     private ObservableList<String> test_rt_39661_setup() {
2746         ObservableList<String>  rawItems = FXCollections.observableArrayList(
2747                 "9-item", "8-item", "7-item", "6-item",
2748                 "5-item", "4-item", "3-item", "2-item", "1-item");
2749         root = createSubTree("root", rawItems);
2750         root.setExpanded(true);
2751         treeView = new TreeView(root);
2752         return rawItems;
2753     }
2754 
2755     private TreeItem createSubTree(Object item, ObservableList<String> rawItems) {
2756         TreeItem child = new TreeItem(item);
2757         child.getChildren().setAll(rawItems.stream()
2758                 .map(rawItem -> new TreeItem(rawItem))
2759                 .collect(Collectors.toList()));
2760         return child;
2761     }
2762 
2763     @Test public void test_rt_39661_rowLessThanExpandedItemCount() {
2764         ObservableList<String> rawItems = test_rt_39661_setup();
2765         TreeItem child = createSubTree("child", rawItems);
2766         TreeItem grandChild = (TreeItem) child.getChildren().get(rawItems.size() - 1);
2767         root.getChildren().add(child);
2768         assertTrue("row of item must be less than expandedItemCount, but was: " + treeView.getRow(grandChild),
2769                 treeView.getRow(grandChild) < treeView.getExpandedItemCount());
2770     }
2771 
2772     @Test public void test_rt_39661_rowOfGrandChildParentCollapsedUpdatedOnInsertAbove() {
2773         ObservableList<String> rawItems = test_rt_39661_setup();
2774         int grandIndex = 2;
2775         int childIndex = 3;
2776 
2777         TreeItem child = createSubTree("addedChild2", rawItems);
2778         TreeItem grandChild = (TreeItem) child.getChildren().get(grandIndex);
2779         root.getChildren().add(childIndex, child);
2780 
2781         int rowOfGrand = treeView.getRow(grandChild);
2782         root.getChildren().add(childIndex - 1, createSubTree("other", rawItems));
2783 
2784         assertEquals(-1, treeView.getRow(grandChild));
2785     }
2786 
2787     @Test public void test_rt_39661_rowOfGrandChildParentCollapsedUpdatedOnInsertAboveWithoutAccess() {
2788         ObservableList<String> rawItems = test_rt_39661_setup();
2789         int grandIndex = 2;
2790         int childIndex = 3;
2791 
2792         TreeItem child = createSubTree("addedChild2", rawItems);
2793         TreeItem grandChild = (TreeItem) child.getChildren().get(grandIndex);
2794         root.getChildren().add(childIndex, child);
2795 
2796         int rowOfGrand = 7; //treeView.getRow(grandChild);
2797         root.getChildren().add(childIndex, createSubTree("other", rawItems));
2798 
2799         assertEquals(-1, treeView.getRow(grandChild));
2800     }
2801 
2802     @Test public void test_rt_39661_rowOfGrandChildParentExpandedUpdatedOnInsertAbove() {
2803         ObservableList<String> rawItems = test_rt_39661_setup();
2804         int grandIndex = 2;
2805         int childIndex = 3;
2806         TreeItem child = createSubTree("addedChild2", rawItems);
2807         TreeItem grandChild = (TreeItem) child.getChildren().get(grandIndex);
2808         child.setExpanded(true);
2809         root.getChildren().add(childIndex, child);
2810         int rowOfGrand = treeView.getRow(grandChild);
2811         root.getChildren().add(childIndex -1, createSubTree("other", rawItems));
2812         assertEquals(rowOfGrand + 1, treeView.getRow(grandChild));
2813     }
2814 
2815     /**
2816      * Testing getRow on grandChild: compare collapsed/expanded parent.
2817      */
2818     @Test public void test_rt_39661_rowOfGrandChildDependsOnParentExpansion() {
2819         ObservableList<String> rawItems = test_rt_39661_setup();
2820         int grandIndex = 2;
2821         int childIndex = 3;
2822         TreeItem collapsedChild = createSubTree("addedChild", rawItems);
2823         TreeItem collapsedGrandChild = (TreeItem) collapsedChild.getChildren().get(grandIndex);
2824         root.getChildren().add(childIndex, collapsedChild);
2825         int collapedGrandIndex = treeView.getRow(collapsedGrandChild);
2826         int collapsedRowCount = treeView.getExpandedItemCount();
2827         // start again
2828         test_rt_39661_setup();
2829         assertEquals(collapsedRowCount - 1, treeView.getExpandedItemCount());
2830         TreeItem expandedChild = createSubTree("addedChild2", rawItems);
2831         TreeItem expandedGrandChild = (TreeItem) expandedChild.getChildren().get(grandIndex);
2832         expandedChild.setExpanded(true);
2833         root.getChildren().add(childIndex, expandedChild);
2834         assertNotSame("getRow must depend on expansionState " + collapedGrandIndex,
2835                 collapedGrandIndex, treeView.getRow(expandedGrandChild));
2836     }
2837 
2838     @Test public void test_rt_39661_rowOfGrandChildInCollapsedChild() {
2839         ObservableList<String> rawItems = test_rt_39661_setup();
2840 
2841         // create a collapsed new child to insert into the root
2842         TreeItem newChild = createSubTree("added-child", rawItems);
2843         TreeItem grandChild = (TreeItem) newChild.getChildren().get(2);
2844         root.getChildren().add(6, newChild);
2845 
2846         // query the row of a grand-child
2847         int row = treeView.getRow(grandChild);
2848 
2849         // grandChild not visible, row coordinate in tree is not available
2850         assertEquals("grandChild not visible", -1, row);
2851 
2852         // the other way round: if we get a row, expect the item at the row be the grandChild
2853         if (row > -1) {
2854             assertEquals(grandChild, treeView.getTreeItem(row));
2855         }
2856     }
2857 
2858     @Test public void test_rt_39661_rowOfRootChild() {
2859         ObservableList<String> rawItems = test_rt_39661_setup();
2860         int index = 2;
2861 
2862         TreeItem child = (TreeItem) root.getChildren().get(index);
2863         assertEquals(index + 1, treeView.getRow(child));
2864     }
2865 
2866     @Test public void test_rt_39661_expandedItemCount() {
2867         ObservableList<String> rawItems = test_rt_39661_setup();
2868         int initialRowCount = treeView.getExpandedItemCount();
2869         assertEquals(root.getChildren().size() + 1, initialRowCount);
2870 
2871         TreeItem collapsedChild = createSubTree("collapsed-child", rawItems);
2872         root.getChildren().add(collapsedChild);
2873         assertEquals(initialRowCount + 1, treeView.getExpandedItemCount());
2874 
2875         TreeItem expandedChild = createSubTree("expanded-child", rawItems);
2876         expandedChild.setExpanded(true);
2877         root.getChildren().add(0, expandedChild);
2878         assertEquals(2 * initialRowCount + 1, treeView.getExpandedItemCount());
2879     }
2880 
2881     @Test public void test_rt_22599() {
2882         TreeItem<RT22599_DataType> root = new TreeItem<>();
2883         root.getChildren().setAll(
2884                 new TreeItem<>(new RT22599_DataType(1, "row1")),
2885                 new TreeItem<>(new RT22599_DataType(2, "row2")),
2886                 new TreeItem<>(new RT22599_DataType(3, "row3")));
2887         root.setExpanded(true);
2888 
2889         TreeView<RT22599_DataType> tree = new TreeView<>(root);
2890         tree.setShowRoot(false);
2891 
2892         StageLoader sl = new StageLoader(tree);
2893 
2894         // testing initial state
2895         assertNotNull(tree.getSkin());
2896         assertEquals("row1", VirtualFlowTestUtils.getCell(tree, 0).getText());
2897         assertEquals("row2", VirtualFlowTestUtils.getCell(tree, 1).getText());
2898         assertEquals("row3", VirtualFlowTestUtils.getCell(tree, 2).getText());
2899 
2900         // change row 0 (where "row1" currently resides), keeping same id.
2901         // Because 'set' is called, the control should update to the new content
2902         // without any user interaction
2903         TreeItem<RT22599_DataType> data;
2904         root.getChildren().set(0, data = new TreeItem<>(new RT22599_DataType(0, "row1a")));
2905         Toolkit.getToolkit().firePulse();
2906         assertEquals("row1a", VirtualFlowTestUtils.getCell(tree, 0).getText());
2907 
2908         // change the row 0 (where we currently have "row1a") value directly.
2909         // Because there is no associated property, this won't be observed, so
2910         // the control should still show "row1a" rather than "row1b"
2911         data.getValue().text = "row1b";
2912         Toolkit.getToolkit().firePulse();
2913         assertEquals("row1a", VirtualFlowTestUtils.getCell(tree, 0).getText());
2914 
2915         // call refresh() to force a refresh of all visible cells
2916         tree.refresh();
2917         Toolkit.getToolkit().firePulse();
2918         assertEquals("row1b", VirtualFlowTestUtils.getCell(tree, 0).getText());
2919 
2920         sl.dispose();
2921     }
2922 
2923     private static class RT22599_DataType {
2924         public int id = 0;
2925         public String text = "";
2926 
2927         public RT22599_DataType(int id, String text) {
2928             this.id = id;
2929             this.text = text;
2930         }
2931 
2932         @Override public String toString() {
2933             return text;
2934         }
2935 
2936         @Override public boolean equals(Object obj) {
2937             if (obj == null) return false;
2938             return id == ((RT22599_DataType)obj).id;
2939         }
2940     }
2941 
2942     private int rt_39966_count = 0;
2943     @Test public void test_rt_39966() {
2944         TreeItem<String> root = new TreeItem<>("Root");
2945         TreeView<String> table = new TreeView<>(root);
2946         table.setShowRoot(true);
2947 
2948         StageLoader sl = new StageLoader(table);
2949 
2950         // initially there is no selection
2951         assertTrue(table.getSelectionModel().isEmpty());
2952 
2953         table.getSelectionModel().selectedItemProperty().addListener((value, s1, s2) -> {
2954             if (rt_39966_count == 0) {
2955                 rt_39966_count++;
2956                 assertFalse(table.getSelectionModel().isEmpty());
2957             } else {
2958                 assertTrue(debug(), table.getSelectionModel().isEmpty());
2959             }
2960         });
2961 
2962         // our assertion two lines down always succeeds. What fails is our
2963         // assertion above within the listener.
2964         table.getSelectionModel().select(0);
2965         assertFalse(table.getSelectionModel().isEmpty());
2966 
2967         table.setRoot(null);
2968         assertTrue(debug(),table.getSelectionModel().isEmpty());
2969 
2970         sl.dispose();
2971     }
2972 
2973     /**
2974      * Bullet 1: selected index must be updated
2975      * Corner case: last selected. Fails for core
2976      */
2977     @Test public void test_rt_40012_selectedAtLastOnDisjointRemoveItemsAbove() {
2978         TreeItem<String> root = new TreeItem<>("Root");
2979         root.setExpanded(true);
2980         root.getChildren().addAll(
2981                 new TreeItem<>("0"),
2982                 new TreeItem<>("1"),
2983                 new TreeItem<>("2"),
2984                 new TreeItem<>("3"),
2985                 new TreeItem<>("4"),
2986                 new TreeItem<>("5")
2987         );
2988 
2989         TreeView<String> treeView = new TreeView<>(root);
2990         treeView.setShowRoot(false);
2991         sm = treeView.getSelectionModel();
2992 
2993         int last = root.getChildren().size() - 1;
2994 
2995         // selecting item "5"
2996         sm.select(last);
2997 
2998         // disjoint remove of 2 elements above the last selected
2999         // Removing "1" and "3"
3000         root.getChildren().removeAll(root.getChildren().get(1), root.getChildren().get(3));
3001 
3002         // selection should move up two places such that it remains on item "5",
3003         // but in index (last - 2).
3004         int expected = last - 2;
3005         assertEquals("5", sm.getSelectedItem().getValue());
3006         assertEquals("selected index after disjoint removes above", expected, sm.getSelectedIndex());
3007     }
3008 
3009     /**
3010      * Variant of 1: if selectedIndex is not updated,
3011      * the old index is no longer valid
3012      * for accessing the items.
3013      */
3014     @Test public void test_rt_40012_accessSelectedAtLastOnDisjointRemoveItemsAbove() {
3015         TreeItem<String> root = new TreeItem<>("Root");
3016         root.setExpanded(true);
3017         root.getChildren().addAll(
3018                 new TreeItem<>("0"),
3019                 new TreeItem<>("1"),
3020                 new TreeItem<>("2"),
3021                 new TreeItem<>("3"),
3022                 new TreeItem<>("4"),
3023                 new TreeItem<>("5")
3024         );
3025 
3026         TreeView<String> treeView = new TreeView<>(root);
3027         treeView.setShowRoot(false);
3028         sm = treeView.getSelectionModel();
3029 
3030         int last = root.getChildren().size() - 1;
3031 
3032         // selecting item "5"
3033         sm.select(last);
3034 
3035         // disjoint remove of 2 elements above the last selected
3036         root.getChildren().removeAll(root.getChildren().get(1), root.getChildren().get(3));
3037         int selected = sm.getSelectedIndex();
3038         if (selected > -1) {
3039             root.getChildren().get(selected);
3040         }
3041     }
3042 
3043     /**
3044      * Bullet 2: selectedIndex notification count
3045      *
3046      * Note that we don't use the corner case of having the last index selected
3047      * (which fails already on updating the index)
3048      */
3049     private int rt_40012_count = 0;
3050     @Test public void test_rt_40012_selectedIndexNotificationOnDisjointRemovesAbove() {
3051         TreeItem<String> root = new TreeItem<>("Root");
3052         root.setExpanded(true);
3053         root.getChildren().addAll(
3054                 new TreeItem<>("0"),
3055                 new TreeItem<>("1"),
3056                 new TreeItem<>("2"),
3057                 new TreeItem<>("3"),
3058                 new TreeItem<>("4"),
3059                 new TreeItem<>("5")
3060         );
3061 
3062         TreeView<String> treeView = new TreeView<>(root);
3063         treeView.setShowRoot(false);
3064         sm = treeView.getSelectionModel();
3065 
3066         int last = root.getChildren().size() - 2;
3067         sm.select(last);
3068         assertEquals(last, sm.getSelectedIndex());
3069 
3070         rt_40012_count = 0;
3071         sm.selectedIndexProperty().addListener(o -> rt_40012_count++);
3072 
3073         // disjoint remove of 2 elements above the last selected
3074         root.getChildren().removeAll(root.getChildren().get(1), root.getChildren().get(3));
3075         assertEquals("sanity: selectedIndex must be shifted by -2", last - 2, sm.getSelectedIndex());
3076         assertEquals("must fire single event on removes above", 1, rt_40012_count);
3077     }
3078 
3079     /**
3080      * Bullet 3: unchanged selectedItem must not fire change
3081      */
3082     @Test
3083     public void test_rt_40012_selectedItemNotificationOnDisjointRemovesAbove() {
3084         TreeItem<String> root = new TreeItem<>("Root");
3085         root.setExpanded(true);
3086         root.getChildren().addAll(
3087                 new TreeItem<>("0"),
3088                 new TreeItem<>("1"),
3089                 new TreeItem<>("2"),
3090                 new TreeItem<>("3"),
3091                 new TreeItem<>("4"),
3092                 new TreeItem<>("5")
3093         );
3094 
3095         TreeView<String> treeView = new TreeView<>(root);
3096         treeView.setShowRoot(false);
3097         sm = treeView.getSelectionModel();
3098 
3099         int last = root.getChildren().size() - 2;
3100         Object lastItem = root.getChildren().get(last);
3101         sm.select(last);
3102         assertEquals(lastItem, sm.getSelectedItem());
3103 
3104         rt_40012_count = 0;
3105         sm.selectedItemProperty().addListener(o -> rt_40012_count++);
3106 
3107         // disjoint remove of 2 elements above the last selected
3108         root.getChildren().removeAll(root.getChildren().get(1), root.getChildren().get(3));
3109         assertEquals("sanity: selectedItem unchanged", lastItem, sm.getSelectedItem());
3110         assertEquals("must not fire on unchanged selected item", 0, rt_40012_count);
3111     }
3112 
3113     private int rt_40010_count = 0;
3114     @Test public void test_rt_40010() {
3115         TreeItem<String> root = new TreeItem<>("Root");
3116         TreeItem<String> child = new TreeItem<>("child");
3117         root.setExpanded(true);
3118         root.getChildren().addAll(child);
3119 
3120         TreeView<String> treeView = new TreeView<>(root);
3121         sm = treeView.getSelectionModel();
3122 
3123         sm.getSelectedIndices().addListener((ListChangeListener<? super Integer>) l -> rt_40010_count++);
3124         sm.getSelectedItems().addListener((ListChangeListener<? super TreeItem<String>>) l -> rt_40010_count++);
3125 
3126         assertEquals(0, rt_40010_count);
3127 
3128         sm.select(1);
3129         assertEquals(1, sm.getSelectedIndex());
3130         assertEquals(child, sm.getSelectedItem());
3131         assertEquals(2, rt_40010_count);
3132 
3133         root.getChildren().remove(child);
3134         assertEquals(0, sm.getSelectedIndex());
3135         assertEquals(root, sm.getSelectedItem());
3136         assertEquals(4, rt_40010_count);
3137     }
3138 
3139     @Test public void test_rt_39674_staticChildren() {
3140         TreeItem<String> item2;
3141         TreeItem<String> root = new TreeItem<>("Root");
3142         root.setExpanded(true);
3143         root.getChildren().addAll(
3144             new TreeItem<>("0"),
3145             new TreeItem<>("1"),
3146             item2 = new TreeItem<>("2"),
3147             new TreeItem<>("3"),
3148             new TreeItem<>("4"),
3149             new TreeItem<>("5")
3150         );
3151 
3152         item2.getChildren().addAll(
3153             new TreeItem<>("0"),
3154             new TreeItem<>("1"),
3155             new TreeItem<>("2"),
3156             new TreeItem<>("3"),
3157             new TreeItem<>("4"),
3158             new TreeItem<>("5")
3159         );
3160 
3161         TreeView<String> treeView = new TreeView<>(root);
3162         sm = treeView.getSelectionModel();
3163 
3164         StageLoader sl = new StageLoader(treeView);
3165 
3166         sm.select(4); // select treeitem "3" in index 4
3167         assertEquals(4, sm.getSelectedIndex());
3168         assertEquals("3", sm.getSelectedItem().getValue());
3169 
3170         item2.setExpanded(true); // expand item 2. Selection should move to position 9
3171         assertEquals(10, sm.getSelectedIndex());
3172         assertEquals("3", sm.getSelectedItem().getValue());
3173 
3174         sl.dispose();
3175     }
3176 
3177     @Ignore("RT-39674 not yet fixed")
3178     @Test public void test_rt_39674_dynamicChildren() {
3179         TreeItem<Integer> root = createTreeItem(0);
3180         root.setExpanded(true);
3181 
3182         TreeView<Integer> treeView = new TreeView<>(root);
3183         SelectionModel<TreeItem<Integer>> sm = treeView.getSelectionModel();
3184 
3185         StageLoader sl = new StageLoader(treeView);
3186 
3187         sm.select(5);
3188         assertEquals(5, sm.getSelectedIndex());
3189         assertEquals(4, (int)sm.getSelectedItem().getValue());
3190 
3191         root.getChildren().get(2).setExpanded(true);
3192         assertEquals(12, sm.getSelectedIndex());
3193         assertEquals(4, (int)sm.getSelectedItem().getValue());
3194 
3195         sl.dispose();
3196     }
3197 
3198     private TreeItem<Integer> createTreeItem(final int index) {
3199         final TreeItem<Integer> node = new TreeItem<Integer>(index) {
3200             private boolean isLeaf;
3201             private boolean isFirstTimeChildren = true;
3202             private boolean isFirstTimeLeaf = true;
3203 
3204             @Override
3205             public ObservableList<TreeItem<Integer>> getChildren() {
3206                 if (isFirstTimeChildren) {
3207                     isFirstTimeChildren = false;
3208                     super.getChildren().setAll(buildChildren(this));
3209                 }
3210                 return super.getChildren();
3211             }
3212 
3213             @Override
3214             public boolean isLeaf() {
3215                 if (isFirstTimeLeaf) {
3216                     isFirstTimeLeaf = false;
3217                     int index = getValue();
3218                     isLeaf = index % 2 != 0;
3219                 }
3220 
3221                 return isLeaf;
3222             }
3223         };
3224         return node;
3225     }
3226 
3227     private ObservableList<TreeItem<Integer>> buildChildren(TreeItem<Integer> TreeItem) {
3228         Integer index = TreeItem.getValue();
3229         if (index % 2 == 0) {
3230             ObservableList<TreeItem<Integer>> children = FXCollections.observableArrayList();
3231             for (int i = 0; i < 5; i++) {
3232                 children.add(createTreeItem(i));
3233             }
3234 
3235             return children;
3236         }
3237 
3238         return FXCollections.emptyObservableList();
3239     }
3240 
3241     /**
3242      * ClearAndSelect fires invalid change event if selectedIndex is unchanged.
3243      */
3244     private int rt_40212_count = 0;
3245     @Test public void test_rt_40212() {
3246         TreeItem<String> root = new TreeItem<>("Root");
3247         root.setExpanded(true);
3248         root.getChildren().addAll(
3249                 new TreeItem<>("0"),
3250                 new TreeItem<>("1"),
3251                 new TreeItem<>("2"),
3252                 new TreeItem<>("3"),
3253                 new TreeItem<>("4"),
3254                 new TreeItem<>("5")
3255         );
3256 
3257         TreeView<String> stringTreeView = new TreeView<>(root);
3258         stringTreeView.setShowRoot(false);
3259 
3260         MultipleSelectionModel<TreeItem<String>> sm = stringTreeView.getSelectionModel();
3261         sm.setSelectionMode(SelectionMode.MULTIPLE);
3262 
3263         sm.selectRange(3, 5);
3264         int selected = sm.getSelectedIndex();
3265 
3266         sm.getSelectedIndices().addListener((ListChangeListener<Integer>) change -> {
3267             assertEquals("sanity: selectedIndex unchanged", selected, sm.getSelectedIndex());
3268             while(change.next()) {
3269                 assertEquals("single event on clearAndSelect already selected", 1, ++rt_40212_count);
3270 
3271                 boolean type = change.wasAdded() || change.wasRemoved() || change.wasPermutated() || change.wasUpdated();
3272                 assertTrue("at least one of the change types must be true", type);
3273             }
3274         });
3275 
3276         sm.clearAndSelect(selected);
3277     }
3278 
3279     @Test public void test_rt_40280() {
3280         final TreeView<String> view = new TreeView<>();
3281         StageLoader sl = new StageLoader(view);
3282         view.getFocusModel().getFocusedIndex();
3283         sl.dispose();
3284     }
3285 
3286     @Test public void test_rt_40278_showRoot() {
3287         TreeItem<String> root = new TreeItem<>("Root");
3288         root.setExpanded(true);
3289         root.getChildren().addAll(new TreeItem<>("0"),new TreeItem<>("1"));
3290 
3291         TreeView<String> view = new TreeView<>(root);
3292         view.setShowRoot(false);
3293         MultipleSelectionModel<TreeItem<String>> sm = view.getSelectionModel();
3294 
3295         assertFalse("sanity: test setup such that root is not showing", view.isShowRoot());
3296         sm.select(0);
3297         assertEquals(0, sm.getSelectedIndex());
3298         assertEquals(view.getTreeItem(sm.getSelectedIndex()), sm.getSelectedItem());
3299         view.setShowRoot(true);
3300         assertEquals(1, sm.getSelectedIndex());
3301         assertEquals(view.getTreeItem(sm.getSelectedIndex()), sm.getSelectedItem());
3302     }
3303 
3304     @Test public void test_rt_40278_hideRoot_selectionOnChild() {
3305         TreeItem<String> root = new TreeItem<>("Root");
3306         root.setExpanded(true);
3307         root.getChildren().addAll(new TreeItem<>("0"),new TreeItem<>("1"));
3308 
3309         TreeView<String> view = new TreeView<>(root);
3310         view.setShowRoot(true);
3311         MultipleSelectionModel<TreeItem<String>> sm = view.getSelectionModel();
3312 
3313         assertTrue("sanity: test setup such that root is showing", view.isShowRoot());
3314         sm.select(1);
3315         assertEquals(1, sm.getSelectedIndex());
3316         assertEquals(view.getTreeItem(sm.getSelectedIndex()), sm.getSelectedItem());
3317         view.setShowRoot(false);
3318         assertEquals(0, sm.getSelectedIndex());
3319         assertEquals(view.getTreeItem(sm.getSelectedIndex()), sm.getSelectedItem());
3320     }
3321 
3322     @Test public void test_rt_40278_hideRoot_selectionOnRoot() {
3323         TreeItem<String> root = new TreeItem<>("Root");
3324         root.setExpanded(true);
3325         root.getChildren().addAll(new TreeItem<>("0"),new TreeItem<>("1"));
3326 
3327         TreeView<String> view = new TreeView<>(root);
3328         view.setShowRoot(true);
3329         MultipleSelectionModel<TreeItem<String>> sm = view.getSelectionModel();
3330 
3331         assertTrue("sanity: test setup such that root is showing", view.isShowRoot());
3332         sm.select(0);
3333         assertEquals(0, sm.getSelectedIndex());
3334         assertEquals(view.getTreeItem(sm.getSelectedIndex()), sm.getSelectedItem());
3335         view.setShowRoot(false);
3336         assertEquals(0, sm.getSelectedIndex());
3337         assertEquals(view.getTreeItem(sm.getSelectedIndex()), sm.getSelectedItem());
3338     }
3339 
3340     /**
3341      * Test list change of selectedIndices on setIndices. Fails for core ..
3342      */
3343     @Test public void test_rt_40263() {
3344         TreeItem<Integer> root = new TreeItem<>(-1);
3345         root.setExpanded(true);
3346 
3347         for (int i = 0; i < 10; i++) {
3348             root.getChildren().add(new TreeItem<Integer>(i));
3349         }
3350 
3351         final TreeView<Integer> view = new TreeView<>(root);
3352         MultipleSelectionModel<TreeItem<Integer>> sm = view.getSelectionModel();
3353         sm.setSelectionMode(SelectionMode.MULTIPLE);
3354 
3355         int[] indices = new int[]{2, 5, 7};
3356         ListChangeListener<Integer> l = c -> {
3357             // firstly, we expect only one change
3358             int subChanges = 0;
3359             while(c.next()) {
3360                 subChanges++;
3361             }
3362             assertEquals(1, subChanges);
3363 
3364             // secondly, we expect the added size to be three, as that is the
3365             // number of items selected
3366             c.reset();
3367             c.next();
3368             System.out.println("Added items: " + c.getAddedSubList());
3369             assertEquals(indices.length, c.getAddedSize());
3370             assertArrayEquals(indices, c.getAddedSubList().stream().mapToInt(i -> i).toArray());
3371         };
3372         sm.getSelectedIndices().addListener(l);
3373         sm.selectIndices(indices[0], indices);
3374     }
3375 }