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