1 /*
   2  * Copyright (c) 2010, 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.skin;
  27 
  28 import static org.junit.Assert.assertEquals;
  29 import static org.junit.Assert.assertFalse;
  30 import static org.junit.Assert.assertNotNull;
  31 import static org.junit.Assert.assertNull;
  32 import static org.junit.Assert.assertSame;
  33 import static org.junit.Assert.assertTrue;
  34 
  35 import java.util.Iterator;
  36 import java.util.LinkedList;
  37 
  38 import javafx.beans.InvalidationListener;
  39 import javafx.event.Event;
  40 import javafx.scene.control.IndexedCell;
  41 import javafx.scene.control.SkinStub;
  42 import javafx.scene.control.skin.VirtualFlow.ArrayLinkedList;
  43 import javafx.scene.input.ScrollEvent;
  44 
  45 import org.junit.Before;
  46 import org.junit.Ignore;
  47 import org.junit.Test;
  48 
  49 import java.util.List;
  50 
  51 /**
  52  * Tests for the VirtualFlow class. VirtualFlow is the guts of the ListView,
  53  * TreeView, and TableView implementations.
  54  */
  55 public class VirtualFlowTest {
  56     // The following 4 vars are used when testing the
  57     private ArrayLinkedList<CellStub> list;
  58     private CellStub a;
  59     private CellStub b;
  60     private CellStub c;
  61 
  62     // The VirtualFlow we are going to test. By default, there are 100 cells
  63     // and each cell is 100 wide and 25 tall, except for the 30th cell, which
  64     // is 200 wide and 100 tall.
  65     private VirtualFlow<IndexedCell> flow;
  66 //    private Scene scene;
  67 
  68     @Before public void setUp() {
  69         list = new ArrayLinkedList<CellStub>();
  70         a = new CellStub(flow, "A");
  71         b = new CellStub(flow, "B");
  72         c = new CellStub(flow, "C");
  73 
  74         flow = new VirtualFlow();
  75 //        flow.setManaged(false);
  76         flow.setVertical(true);
  77         flow.setCellFactory(p -> new CellStub(flow) {
  78             @Override
  79             protected double computeMinWidth(double height) {
  80                 return computePrefWidth(height);
  81             }
  82 
  83             @Override
  84             protected double computeMaxWidth(double height) {
  85                 return computePrefWidth(height);
  86             }
  87 
  88             @Override
  89             protected double computePrefWidth(double height) {
  90                 return flow.isVertical() ? (c.getIndex() == 29 ? 200 : 100) : (c.getIndex() == 29 ? 100 : 25);
  91             }
  92 
  93             @Override
  94             protected double computeMinHeight(double width) {
  95                 return computePrefHeight(width);
  96             }
  97 
  98             @Override
  99             protected double computeMaxHeight(double width) {
 100                 return computePrefHeight(width);
 101             }
 102 
 103             @Override
 104             protected double computePrefHeight(double width) {
 105                 return flow.isVertical() ? (c.getIndex() == 29 ? 100 : 25) : (c.getIndex() == 29 ? 200 : 100);
 106             }
 107         });
 108         flow.setCellCount(100);
 109         flow.resize(300, 300);
 110         pulse();
 111     }
 112 
 113     private void pulse() {
 114 //        flow.impl_processCSS(true);
 115         flow.layout();
 116     }
 117 
 118     /**
 119      * Asserts that the items in the control LinkedList and the ones in the
 120      * list are exactly the same.
 121      */
 122     private void assertMatch(List<IndexedCell> control, ArrayLinkedList<IndexedCell> list) {
 123         assertEquals("The control and list did not have the same sizes. " +
 124                      "Expected " + control.size() + " but was " + list.size(),
 125                      control.size(), list.size());
 126         int index = 0;
 127         Iterator<IndexedCell> itr = control.iterator();
 128         while (itr.hasNext()) {
 129             IndexedCell cell = itr.next();
 130             IndexedCell cell2 = list.get(index);
 131             assertSame("The control and list did not have the same item at " +
 132                        "index " + index + ". Expected " + cell + " but was " + cell2,
 133                        cell, cell2);
 134             index++;
 135         }
 136     }
 137 
 138     /**
 139      * Asserts that only the minimal number of cells are used.
 140      */
 141     public <T extends IndexedCell> void assertMinimalNumberOfCellsAreUsed(VirtualFlow<T> flow) {
 142         pulse();
 143         IndexedCell firstCell = flow.cells.getFirst();
 144         IndexedCell lastCell = flow.cells.getLast();
 145         if (flow.isVertical()) {
 146             // First make sure that enough cells were created
 147             assertTrue("There is a gap between the top of the viewport and the first cell",
 148                        firstCell.getLayoutY() <= 0);
 149             assertTrue("There is a gap between the bottom of the last cell and the bottom of the viewport",
 150                        lastCell.getLayoutY() + lastCell.getHeight() >= flow.getViewportLength());
 151 
 152             // Now make sure that no extra cells were created.
 153             if (flow.cells.size() > 3) {
 154                 IndexedCell secondLastCell = flow.cells.get(flow.cells.size() - 2);
 155                 IndexedCell secondCell = flow.cells.get(1);
 156                 assertFalse("There are more cells created before the start of " +
 157                             "the flow than necessary",
 158                             secondCell.getLayoutY() <= 0);
 159                 assertFalse("There are more cells created after the end of the " +
 160                             "flow than necessary",
 161                             secondLastCell.getLayoutY() + secondLastCell.getHeight() >= flow.getViewportLength());
 162             }
 163         } else {
 164             // First make sure that enough cells were created
 165             assertTrue("There is a gap between the left of the viewport and the first cell",
 166                        firstCell.getLayoutX() <= 0);
 167             assertTrue("There is a gap between the right of the last cell and the right of the viewport",
 168                        lastCell.getLayoutX() + lastCell.getWidth() >= flow.getViewportLength());
 169 
 170             // Now make sure that no extra cells were created.
 171             if (flow.cells.size() > 3) {
 172                 IndexedCell secondLastCell = flow.cells.get(flow.cells.size() - 2);
 173                 IndexedCell secondCell = flow.cells.get(1);
 174                 assertFalse("There are more cells created before the start of " +
 175                             "the flow than necessary",
 176                             secondCell.getLayoutX() <= 0);
 177                 assertFalse("There are more cells created after the end of the " +
 178                             "flow than necessary",
 179                             secondLastCell.getLayoutX() + secondLastCell.getWidth() >= flow.getViewportLength());
 180             }
 181         }
 182     }
 183 
 184 
 185     /***************************************************************************
 186      *                          Tests for VirtualFlow                          *
 187      *                                                                         *
 188      *  These tests are broken out into several broad categories:              *
 189      *      - general layout (position of scroll bars, viewport, etc)          *
 190      *      - cell layout                                                      *
 191      *      - cell life cycle (creation, configuration, reuse, etc)            *
 192      *      - position (stable view, adjusts when necessary, etc)              *
 193      *      - pixel scrolling (cells are reused, position updated, etc)        *
 194      *                                                                         *
 195      *  - Test that the preferred width of a vertical flow takes into account  *
 196      *    the preferred width of the cells that are visible                    *
 197      *  - Test the same for horizontal when working with a horizontal flow     *
 198      *  - Test that cells are laid out as expected in a vertical flow          *
 199      *  - Test the same for a horizontal flow                                  *
 200      *  - Test that the width of cells in a vertical flow adjusts based on the *
 201      *    width of the flow's content area                                     *
 202      *  - Test the same for the height of cells in a horizontal flow           *
 203      *  - Test that changing the number of cells (up and down) also adjusts    *
 204      *    the position such that it is "stable", unless that is not possible   *
 205      *  - Test that after changing the cell factory, things are rebuilt        *
 206      *  - Test that after changing the cell config function, things are        *
 207      *    reconfigured.                                                        *
 208      *  - Test that functions which add to the pile and so forth work as       *
 209      *    expected.                                                            *
 210      *  - Test the layout of the scroll bars in various combinations, along    *
 211      *    with the corner region and so forth.                                 *
 212      *                                                                         *
 213      **************************************************************************/
 214 
 215     ////////////////////////////////////////////////////////////////////////////
 216     //
 217     //  General Layout
 218     //
 219     ////////////////////////////////////////////////////////////////////////////
 220 
 221     /**
 222      * In this test there are no cells. The VirtualFlow should be laid out such
 223      * that the scroll bars and corner are not visible, and the clip view fills
 224      * the entire width/height of the VirtualFlow.
 225      */
 226     @Test public void testGeneralLayout_NoCells() {
 227         flow.setCellCount(0);
 228         pulse();
 229         assertFalse("The hbar should have been invisible", flow.getHbar().isVisible());
 230         assertFalse("The vbar should have been invisible", flow.getVbar().isVisible());
 231         assertFalse("The corner should have been invisible", flow.corner.isVisible());
 232         assertEquals(flow.getWidth(), flow.clipView.getWidth(), 0.0);
 233         assertEquals(flow.getHeight(), flow.clipView.getHeight(), 0.0);
 234         assertMinimalNumberOfCellsAreUsed(flow);
 235 
 236         flow.setVertical(false);
 237         pulse();
 238         assertFalse("The hbar should have been invisible", flow.getHbar().isVisible());
 239         assertFalse("The vbar should have been invisible", flow.getVbar().isVisible());
 240         assertFalse("The corner should have been invisible", flow.corner.isVisible());
 241         assertEquals(flow.getWidth(), flow.clipView.getWidth(), 0.0);
 242         assertEquals(flow.getHeight(), flow.clipView.getHeight(), 0.0);
 243         assertMinimalNumberOfCellsAreUsed(flow);
 244     }
 245 
 246     /**
 247      * When we have a few cells, not enough to fill the viewport, then we need
 248      * to make sure there is no virtual scroll bar. In this test case the cells
 249      * are not wider than the viewport so no horizontal bar either.
 250      */
 251     @Test public void testGeneralLayout_FewCells() {
 252         flow.setCellCount(3);
 253         pulse();
 254         assertFalse("The hbar should have been invisible", flow.getHbar().isVisible());
 255         assertFalse("The vbar should have been invisible", flow.getVbar().isVisible());
 256         assertFalse("The corner should have been invisible", flow.corner.isVisible());
 257         assertEquals(flow.getWidth(), flow.clipView.getWidth(), 0.0);
 258         assertEquals(flow.getHeight(), flow.clipView.getHeight(), 0.0);
 259         assertEquals(12, flow.cells.size()); // we stil have 12 cells (300px / 25px), even if only three filled cells exist
 260         assertMinimalNumberOfCellsAreUsed(flow);
 261 
 262         flow.setVertical(false);
 263         pulse();
 264         assertFalse("The hbar should have been invisible", flow.getHbar().isVisible());
 265         assertFalse("The vbar should have been invisible", flow.getVbar().isVisible());
 266         assertFalse("The corner should have been invisible", flow.corner.isVisible());
 267         assertEquals(flow.getWidth(), flow.clipView.getWidth(), 0.0);
 268         assertEquals(flow.getHeight(), flow.clipView.getHeight(), 0.0);
 269 //        assertEquals(3, flow.cells.size());
 270         assertMinimalNumberOfCellsAreUsed(flow);
 271     }
 272 
 273     /**
 274      * Tests the case of a few cells (so not requiring a vertical scroll bar),
 275      * but the cells are wider than the viewport so a horizontal scroll bar is
 276      * required.
 277      */
 278     @Test public void testGeneralLayout_FewCellsButWide() {
 279         flow.setCellCount(3);
 280         flow.resize(50, flow.getHeight());
 281         pulse();
 282         assertTrue("The hbar should have been visible", flow.getHbar().isVisible());
 283         assertFalse("The vbar should have been invisible", flow.getVbar().isVisible());
 284         assertFalse("The corner should have been invisible", flow.corner.isVisible());
 285         assertEquals(flow.getWidth(), flow.clipView.getWidth(), 0.0);
 286         assertEquals(flow.getHeight(), flow.clipView.getHeight() + flow.getHbar().getHeight(), 0.0);
 287         assertEquals(flow.getHbar().getLayoutY(), flow.getHeight() - flow.getHbar().getHeight(), 0.0);
 288         assertMinimalNumberOfCellsAreUsed(flow);
 289 
 290         flow.setVertical(false);
 291         flow.resize(300, 50);
 292         pulse();
 293         assertFalse("The hbar should have been invisible", flow.getHbar().isVisible());
 294         assertTrue("The vbar should have been visible", flow.getVbar().isVisible());
 295         assertFalse("The corner should have been invisible", flow.corner.isVisible());
 296         assertEquals(flow.getWidth(), flow.clipView.getWidth() + flow.getVbar().getWidth(), 0.0);
 297         assertEquals(flow.getHeight(), flow.clipView.getHeight(), 0.0);
 298         assertEquals(flow.getVbar().getLayoutX(), flow.getWidth() - flow.getVbar().getWidth(), 0.0);
 299         assertMinimalNumberOfCellsAreUsed(flow);
 300     }
 301 
 302     /**
 303      * Tests that having a situation where the hbar in a vertical flow is
 304      * necessary (due to wide cells) will end up hiding the hbar if the flow
 305      * becomes wide enough that the hbar is no longer necessary.
 306      */
 307     @Test public void testGeneralLayout_FewCellsButWide_ThenNarrow() {
 308         flow.setCellCount(3);
 309         flow.resize(50, flow.getHeight());
 310         pulse();
 311         flow.resize(300, flow.getHeight());
 312         pulse();
 313         assertFalse("The hbar should have been invisible", flow.getHbar().isVisible());
 314         assertFalse("The vbar should have been invisible", flow.getVbar().isVisible());
 315         assertFalse("The corner should have been invisible", flow.corner.isVisible());
 316         assertEquals(flow.getWidth(), flow.clipView.getWidth(), 0.0);
 317         assertEquals(flow.getHeight(), flow.clipView.getHeight(), 0.0);
 318         assertMinimalNumberOfCellsAreUsed(flow);
 319 
 320         flow.setVertical(false);
 321         flow.resize(300, 50);
 322         pulse();
 323         flow.resize(flow.getWidth(), 300);
 324         pulse();
 325         assertFalse("The hbar should have been invisible", flow.getHbar().isVisible());
 326         assertFalse("The vbar should have been invisible", flow.getVbar().isVisible());
 327         assertFalse("The corner should have been invisible", flow.corner.isVisible());
 328         assertEquals(flow.getWidth(), flow.clipView.getWidth(), 0.0);
 329         assertEquals(flow.getHeight(), flow.clipView.getHeight(), 0.0);
 330         assertMinimalNumberOfCellsAreUsed(flow);
 331     }
 332 
 333     /**
 334      * Tests that when there are many cells then the vbar in a vertical flow
 335      * is used.
 336      * <p>
 337      * Note, this test uncovered a bug where the bottom of the last cell was
 338      * exactly on the bottom edge of the flow and the vbar was not made visible.
 339      * Be sure to test for this explicitly some time!
 340      */
 341     @Test public void testGeneralLayout_ManyCells() {
 342         assertFalse("The hbar should have been invisible", flow.getHbar().isVisible());
 343         assertTrue("The vbar should have been visible", flow.getVbar().isVisible());
 344         assertFalse("The corner should have been invisible", flow.corner.isVisible());
 345         assertEquals(flow.getWidth(), flow.clipView.getWidth() + flow.getVbar().getWidth(), 0.0);
 346         assertEquals(flow.getHeight(), flow.clipView.getHeight(), 0.0);
 347         assertEquals(flow.getVbar().getLayoutX(), flow.getWidth() - flow.getVbar().getWidth(), 0.0);
 348         assertMinimalNumberOfCellsAreUsed(flow);
 349 
 350         flow.setVertical(false);
 351         pulse();
 352         assertTrue("The hbar should have been visible", flow.getHbar().isVisible());
 353         assertFalse("The vbar should have been invisible", flow.getVbar().isVisible());
 354         assertFalse("The corner should have been invisible", flow.corner.isVisible());
 355         assertEquals(flow.getWidth(), flow.clipView.getWidth(), 0.0);
 356         assertEquals(flow.getHeight(), flow.clipView.getHeight() + flow.getHbar().getHeight(), 0.0);
 357         assertEquals(flow.getHbar().getLayoutY(), flow.getHeight() - flow.getHbar().getHeight(), 0.0);
 358         assertMinimalNumberOfCellsAreUsed(flow);
 359     }
 360 
 361     /**
 362      * Test that after having only a few cells, if I then have many cells, that
 363      * the vbar is shown appropriately.
 364      */
 365     @Test public void testGeneralLayout_FewCells_ThenMany() {
 366         flow.setCellCount(3);
 367         pulse();
 368         flow.setCellCount(100);
 369         pulse();
 370         assertFalse("The hbar should have been invisible", flow.getHbar().isVisible());
 371         assertTrue("The vbar should have been visible", flow.getVbar().isVisible());
 372         assertFalse("The corner should have been invisible", flow.corner.isVisible());
 373         assertEquals(flow.getWidth(), flow.clipView.getWidth() + flow.getVbar().getWidth(), 0.0);
 374         assertEquals(flow.getHeight(), flow.clipView.getHeight(), 0.0);
 375         assertEquals(flow.getVbar().getLayoutX(), flow.getWidth() - flow.getVbar().getWidth(), 0.0);
 376         assertMinimalNumberOfCellsAreUsed(flow);
 377 
 378         flow.setVertical(false);
 379         flow.setCellCount(3);
 380         pulse();
 381         flow.setCellCount(100);
 382         pulse();
 383         assertTrue("The hbar should have been visible", flow.getHbar().isVisible());
 384         assertFalse("The vbar should have been invisible", flow.getVbar().isVisible());
 385         assertFalse("The corner should have been invisible", flow.corner.isVisible());
 386         assertEquals(flow.getWidth(), flow.clipView.getWidth(), 0.0);
 387         assertEquals(flow.getHeight(), flow.clipView.getHeight() + flow.getHbar().getHeight(), 0.0);
 388         assertEquals(flow.getHbar().getLayoutY(), flow.getHeight() - flow.getHbar().getHeight(), 0.0);
 389         assertMinimalNumberOfCellsAreUsed(flow);
 390     }
 391 
 392     /**
 393      * Test the case where there are many cells and they are wider than the
 394      * viewport. We should have the hbar, vbar, and corner region in this case.
 395      */
 396     @Test public void testGeneralLayout_ManyCellsAndWide() {
 397         flow.resize(50, flow.getHeight());
 398         pulse();
 399         assertTrue("The hbar should have been visible", flow.getHbar().isVisible());
 400         assertTrue("The vbar should have been visible", flow.getVbar().isVisible());
 401         assertTrue("The corner should have been visible", flow.corner.isVisible());
 402         assertEquals(flow.getWidth(), flow.clipView.getWidth() + flow.getVbar().getWidth(), 0.0);
 403         assertEquals(flow.getHeight(), flow.clipView.getHeight() + flow.getHbar().getHeight(), 0.0);
 404         assertEquals(flow.getVbar().getLayoutX(), flow.getWidth() - flow.getVbar().getWidth(), 0.0);
 405         assertEquals(flow.getVbar().getWidth(), flow.corner.getWidth(), 0.0);
 406         assertEquals(flow.getHbar().getHeight(), flow.corner.getHeight(), 0.0);
 407         assertEquals(flow.getHbar().getWidth(), flow.getWidth() - flow.corner.getWidth(), 0.0);
 408         assertEquals(flow.getVbar().getHeight(), flow.getHeight() - flow.corner.getHeight(), 0.0);
 409         assertEquals(flow.corner.getLayoutX(), flow.getWidth() - flow.corner.getWidth(), 0.0);
 410         assertEquals(flow.corner.getLayoutY(), flow.getHeight() - flow.corner.getHeight(), 0.0);
 411         assertMinimalNumberOfCellsAreUsed(flow);
 412 
 413         flow.setVertical(false);
 414         flow.resize(300, 50);
 415         pulse();
 416         assertTrue("The hbar should have been visible", flow.getHbar().isVisible());
 417         assertTrue("The vbar should have been visible", flow.getVbar().isVisible());
 418         assertTrue("The corner should have been visible", flow.corner.isVisible());
 419         assertEquals(flow.getWidth(), flow.clipView.getWidth() + flow.getVbar().getWidth(), 0.0);
 420         assertEquals(flow.getHeight(), flow.clipView.getHeight() + flow.getHbar().getHeight(), 0.0);
 421         assertEquals(flow.getVbar().getLayoutX(), flow.getWidth() - flow.getVbar().getWidth(), 0.0);
 422         assertEquals(flow.getVbar().getWidth(), flow.corner.getWidth(), 0.0);
 423         assertEquals(flow.getHbar().getHeight(), flow.corner.getHeight(), 0.0);
 424         assertEquals(flow.getHbar().getWidth(), flow.getWidth() - flow.corner.getWidth(), 0.0);
 425         assertEquals(flow.getVbar().getHeight(), flow.getHeight() - flow.corner.getHeight(), 0.0);
 426         assertEquals(flow.corner.getLayoutX(), flow.getWidth() - flow.corner.getWidth(), 0.0);
 427         assertEquals(flow.corner.getLayoutY(), flow.getHeight() - flow.corner.getHeight(), 0.0);
 428         assertMinimalNumberOfCellsAreUsed(flow);
 429     }
 430 
 431     /**
 432      * Tests that when the vertical flag changes, it results in layout
 433      */
 434     @Test public void testGeneralLayout_VerticalChangeResultsInNeedsLayout() {
 435         assertFalse(flow.isNeedsLayout());
 436         flow.setVertical(false);
 437         assertTrue(flow.isNeedsLayout());
 438     }
 439 
 440     /**
 441      * Tests that the range of the non-virtual scroll bar is valid
 442      */
 443     @Test public void testGeneralLayout_NonVirtualScrollBarRange() {
 444         flow.resize(50, flow.getHeight());
 445         pulse();
 446         assertEquals(0, flow.getHbar().getMin(), 0.0);
 447         assertEquals(flow.getMaxPrefBreadth() - flow.clipView.getWidth(), flow.getHbar().getMax(), 0.0);
 448         assertEquals((flow.clipView.getWidth()/flow.getMaxPrefBreadth()) * flow.getHbar().getMax(), flow.getHbar().getVisibleAmount(), 0.0);
 449         flow.setPosition(.28f);
 450         pulse();
 451         assertEquals(0, flow.getHbar().getMin(), 0.0);
 452         assertEquals(flow.getMaxPrefBreadth() - flow.clipView.getWidth(), flow.getHbar().getMax(), 0.0);
 453         assertEquals((flow.clipView.getWidth()/flow.getMaxPrefBreadth()) * flow.getHbar().getMax(), flow.getHbar().getVisibleAmount(), 0.0);
 454 
 455         flow.setVertical(false);
 456         flow.setPosition(0);
 457         flow.resize(300, 50);
 458         pulse();
 459         assertEquals(0, flow.getVbar().getMin(), 0.0);
 460         assertEquals(flow.getMaxPrefBreadth() - flow.clipView.getHeight(), flow.getVbar().getMax(), 0.0);
 461         assertEquals((flow.clipView.getHeight()/flow.getMaxPrefBreadth()) * flow.getVbar().getMax(), flow.getVbar().getVisibleAmount(), 0.0);
 462         flow.setPosition(.28);
 463         pulse();
 464         assertEquals(0, flow.getVbar().getMin(), 0.0);
 465         assertEquals(flow.getMaxPrefBreadth() - flow.clipView.getHeight(), flow.getVbar().getMax(), 0.0);
 466         assertEquals((flow.clipView.getHeight()/flow.getMaxPrefBreadth()) * flow.getVbar().getMax(), flow.getVbar().getVisibleAmount(), 0.0);
 467     }
 468 
 469     /**
 470      * Tests that the maxPrefBreadth is computed correctly for the first page of cells.
 471      * In our test case, the first page of cells have a uniform pref.
 472      */
 473     @Test public void testGeneralLayout_maxPrefBreadth() {
 474         assertEquals(100, flow.getMaxPrefBreadth(), 0.0);
 475     }
 476 
 477     /**
 478      * Tests that even after the first computation of max pref, that it is
 479      * updated when we encounter a new cell (while scrolling for example) that
 480      * has a larger pref.
 481      */
 482     @Ignore
 483     @Test public void testGeneralLayout_maxPrefBreadthUpdatedWhenEncounterLargerPref() {
 484         flow.setPosition(.28);
 485         pulse();
 486         assertEquals(200, flow.getMaxPrefBreadth(), 0.0);
 487     }
 488 
 489     /**
 490      * Tests that if we encounter cells or pages of cells with smaller prefs
 491      * than the max pref that we will keep the max pref the same.
 492      */
 493     @Ignore
 494     @Test public void testGeneralLayout_maxPrefBreadthRemainsSameWhenEncounterSmallerPref() {
 495         flow.setPosition(.28);
 496         pulse();
 497         flow.setPosition(.8);
 498         pulse();
 499         assertEquals(200, flow.getMaxPrefBreadth(), 0.0);
 500     }
 501 
 502     /**
 503      * Tests that changes to the vertical property will clear the maxPrefBreadth
 504      */
 505     @Test public void testGeneralLayout_VerticalChangeClearsmaxPrefBreadth() {
 506         flow.setVertical(false);
 507         assertEquals(-1, flow.getMaxPrefBreadth(), 0.0);
 508     }
 509 
 510     /**
 511      * Tests that changes to the cell count will not affect maxPrefBreadth.
 512      */
 513     @Ignore
 514     @Test public void testGeneralLayout_maxPrefBreadthUnaffectedByCellCountChanges() {
 515         flow.setCellCount(10);
 516         pulse();
 517         assertEquals(100, flow.getMaxPrefBreadth(), 0.0);
 518         flow.setCellCount(100);
 519         pulse();
 520         flow.setPosition(.28);
 521         pulse();
 522         assertEquals(200, flow.getMaxPrefBreadth(), 0.0);
 523         flow.setCellCount(10);
 524         pulse();
 525         assertEquals(200, flow.getMaxPrefBreadth(), 0.0);
 526     }
 527 
 528     /**
 529      * Tests that as we scroll, if the non-virtual scroll bar is visible, then
 530      * as we update maxPrefBreadth it will not affect the non-virtual scroll bar's
 531      * value <b>unless</b> the value is such that the scroll bar is scrolled
 532      * all the way to the end, in which case it will remain scrolled to the
 533      * end.
 534      */
 535     @Test public void testGeneralLayout_maxPrefBreadthScrollBarValueInteraction() {
 536         flow.resize(50, flow.getHeight());
 537         flow.getHbar().setValue(30);
 538         pulse();
 539         flow.setPosition(.28);
 540         pulse();
 541         assertEquals(30, flow.getHbar().getValue(), 0.0);
 542 
 543         // Reset the test and this time check what happens when we are scrolled
 544         // to the very right
 545         flow.setPosition(0);
 546         flow.setVertical(false);
 547         flow.setVertical(true);
 548         pulse();
 549         assertEquals(100, flow.getMaxPrefBreadth(), 0.0);
 550         flow.getHbar().setValue(flow.getHbar().getMax()); // scroll to the end
 551         flow.setPosition(.28);
 552         pulse();
 553         assertEquals(flow.getHbar().getMax(), flow.getHbar().getValue(), 0.0);
 554 
 555         flow.setVertical(false);
 556         flow.setPosition(0);
 557         flow.getHbar().setValue(0);
 558         flow.resize(300, 50);
 559         pulse();
 560         flow.getVbar().setValue(30);
 561         pulse();
 562         flow.setPosition(.28);
 563         pulse();
 564         assertEquals(30, flow.getVbar().getValue(), 0.0);
 565 
 566         // Reset the test and this time check what happens when we are scrolled
 567         // to the very right
 568         flow.setPosition(0);
 569         flow.setVertical(true);
 570         flow.setVertical(false);
 571         pulse();
 572         assertEquals(100, flow.getMaxPrefBreadth(), 0.0);
 573         flow.getVbar().setValue(flow.getVbar().getMax()); // scroll to the end
 574         flow.setPosition(.28);
 575         pulse();
 576         assertEquals(flow.getVbar().getMax(), flow.getVbar().getValue(), 0.0);
 577     }
 578 
 579     @Test public void testGeneralLayout_ScrollToEndOfVirtual_BarStillVisible() {
 580         assertTrue("The vbar was expected to be visible", flow.getVbar().isVisible());
 581         flow.setPosition(1);
 582         pulse();
 583         assertTrue("The vbar was expected to be visible", flow.getVbar().isVisible());
 584 
 585         flow.setPosition(0);
 586         flow.setVertical(false);
 587         pulse();
 588         assertTrue("The hbar was expected to be visible", flow.getHbar().isVisible());
 589         flow.setPosition(1);
 590         pulse();
 591         assertTrue("The hbar was expected to be visible", flow.getHbar().isVisible());
 592     }
 593 
 594     // Need to test all the resize operations and make sure the position of
 595     // nodes is as expected, that they don't get shifted etc.
 596 
 597     // Test: Scroll to the bottom, expand size out, then make smaller. The
 598     // thumb/scroll is not consistent right now.
 599 
 600     // TODO figure out and deal with what happens when orientation changes
 601     // to the hbar.value and vbar.value. Do they just switch? Probably not?
 602     // Probably reset the non-virtual direction and swap the virtual one over.
 603     // So if vbar was .5 and hbar was 30, when we set vertical = false, then
 604     // we change the hbar to .5 and the vbar to 0. However this has to be done
 605     // at the same time that the "virtual" property of the scroll bars is
 606     // changed
 607 
 608     ////////////////////////////////////////////////////////////////////////////
 609     //
 610     //  Cell Layout
 611     //
 612     ////////////////////////////////////////////////////////////////////////////
 613 
 614     /**
 615      * Test to make sure that we are virtual -- that all cells are not being
 616      * created.
 617      */
 618     @Test public void testCellLayout_NotAllCellsAreCreated() {
 619         // due to the initial size of the VirtualFlow and the number of cells
 620         // and their heights, we should have more cells than we have space to
 621         // fit them and so only enough cells should be created to meet our
 622         // needs and not any more than that
 623         assertTrue("All of the cells were created", flow.cells.size() < flow.getCellCount());
 624         assertMinimalNumberOfCellsAreUsed(flow);
 625     }
 626 
 627     /**
 628      * Tests the size and position of all the cells to make sure they were
 629      * laid out properly.
 630      */
 631     @Test public void testCellLayout_CellSizes_AfterLayout() {
 632         double offset = 0.0;
 633         for (int i = 0; i < flow.cells.size(); i++) {
 634             IndexedCell cell = flow.cells.get(i);
 635             assertEquals(25, cell.getHeight(), 0.0);
 636             assertEquals(offset, cell.getLayoutY(), 0.0);
 637             offset += cell.getHeight();
 638         }
 639 
 640         offset = 0.0;
 641         flow.setVertical(false);
 642         pulse();
 643         for (int i = 0; i < flow.cells.size(); i++) {
 644             IndexedCell cell = flow.cells.get(i);
 645             assertEquals(25, cell.getWidth(), 0.0);
 646             assertEquals(offset, cell.getLayoutX(), 0.0);
 647             offset += cell.getWidth();
 648         }
 649     }
 650 
 651     /**
 652      * Test the widths of the cells when the viewport is wider than the
 653      * max pref width/height. They should be uniform, and should be the
 654      * width of the viewport.
 655      */
 656     @Test public void testCellLayout_ViewportWiderThanmaxPrefBreadth() {
 657         // Note that the pref width of everything is 100, but the actual
 658         // available width is much larger (300 - hbar.width or
 659         // 300 - vbar.height) and so the non-virtual dimension of the cell
 660         // should be larger than the max pref
 661         double expected = flow.clipView.getWidth();
 662         for (int i = 0; i < flow.cells.size(); i++) {
 663             IndexedCell cell = flow.cells.get(i);
 664             assertEquals(expected, cell.getWidth(), 0.0);
 665         }
 666 
 667         flow.setVertical(false);
 668         pulse();
 669         expected = flow.clipView.getHeight();
 670         for (int i = 0; i < flow.cells.size(); i++) {
 671             IndexedCell cell = flow.cells.get(i);
 672             assertEquals(expected, cell.getHeight(), 0.0);
 673         }
 674     }
 675 
 676     /**
 677      * Test the widths of the cells when the viewport is shorter than the
 678      * max pref width/height. They should be uniform, and should be the max
 679      * pref.
 680      */
 681     @Test public void testCellLayout_ViewportShorterThanmaxPrefBreadth() {
 682         flow.resize(50, flow.getHeight());
 683         pulse();
 684         assertEquals(100, flow.getMaxPrefBreadth(), 0.0);
 685         for (int i = 0; i < flow.cells.size(); i++) {
 686             IndexedCell cell = flow.cells.get(i);
 687             assertEquals(flow.getMaxPrefBreadth(), cell.getWidth(), 0.0);
 688         }
 689 
 690         flow.setVertical(false);
 691         flow.resize(flow.getWidth(), 50);
 692         pulse();
 693         assertEquals(100, flow.getMaxPrefBreadth(), 0.0);
 694         for (int i = 0; i < flow.cells.size(); i++) {
 695             IndexedCell cell = flow.cells.get(i);
 696             assertEquals(flow.getMaxPrefBreadth(), cell.getHeight(), 0.0);
 697         }
 698     }
 699 
 700     /**
 701      * Test that when we scroll and encounter a cell which has a larger pref
 702      * than we have previously encountered (happens in this test when we visit
 703      * the cell for item #29), then the max pref is updated and the cells are
 704      * all resized to match.
 705      */
 706     @Ignore
 707     @Test public void testCellLayout_ScrollingFindsCellWithLargemaxPrefBreadth() {
 708         flow.resize(50, flow.getHeight());
 709         flow.setPosition(.28); // happens to position such that #29 is visible
 710         pulse();
 711         assertEquals(200, flow.getMaxPrefBreadth(), 0.0);
 712         for (int i = 0; i < flow.cells.size(); i++) {
 713             IndexedCell cell = flow.cells.get(i);
 714             assertEquals(flow.getMaxPrefBreadth(), cell.getWidth(), 0.0);
 715         }
 716 
 717         flow.setVertical(false);
 718         flow.resize(flow.getWidth(), 50);
 719         // NOTE Run this test without the pulse and it fails!
 720         pulse();
 721         flow.setPosition(.28);
 722         pulse();
 723         assertEquals(200, flow.getMaxPrefBreadth(), 0.0);
 724         for (int i = 0; i < flow.cells.size(); i++) {
 725             IndexedCell cell = flow.cells.get(i);
 726             assertEquals(flow.getMaxPrefBreadth(), cell.getHeight(), 0.0);
 727         }
 728     }
 729 
 730     /**
 731      * Checks that the initial set of cells (the first page of cells) are
 732      * indexed starting with cell #0 and working up from there.
 733      */
 734     @Test public void testCellLayout_CellIndexes_FirstPage() {
 735         for (int i = 0; i < flow.cells.size(); i++) {
 736             assertEquals(i, flow.cells.get(i).getIndex());
 737         }
 738     }
 739 
 740     /**
 741      * The bug here is that if layout() is called on the flow numerous times,
 742      * but nothing substantially has changed, we should reuse the cells in the
 743      * same order they were before. We had a bug where when you clicked on the
 744      * ListView, the click wouldn't register. This was because by clicking we
 745      * were giving focus to the ListView, which caused a layout(), and then the
 746      * cells location was shuffled. It didn't look like it to the user, but that
 747      * is what happened. As a result, when the mouse release took place, the
 748      * event was delivered to a different cell than expected and misbehavior
 749      * took place.
 750      */
 751     @Test public void testCellLayout_LayoutWithoutChangingThingsUsesCellsInSameOrderAsBefore() {
 752         List<IndexedCell> cells = new LinkedList<IndexedCell>();
 753         for (int i = 0; i < flow.cells.size(); i++) {
 754             cells.add(flow.cells.get(i));
 755         }
 756         assertMatch(cells, flow.cells); // sanity check
 757         flow.requestLayout();
 758         pulse();
 759         assertMatch(cells, flow.cells);
 760         flow.setPosition(1);
 761         pulse();
 762         cells.clear();
 763         for (int i = 0; i < flow.cells.size(); i++) {
 764             cells.add(flow.cells.get(i));
 765         }
 766         flow.requestLayout();
 767         pulse();
 768         assertMatch(cells, flow.cells);
 769     }
 770 
 771 
 772     @Test
 773     public void testCellLayout_BiasedCellAndLengthBar() {
 774         flow.setCellFactory(param -> new CellStub(flow) {
 775             @Override
 776             protected double computeMinWidth(double height) {
 777                 return 0;
 778             }
 779 
 780             @Override
 781             protected double computeMaxWidth(double height) {
 782                 return Double.MAX_VALUE;
 783             }
 784 
 785             @Override
 786             protected double computePrefWidth(double height) {
 787                 return 200;
 788             }
 789 
 790             @Override
 791             protected double computeMinHeight(double width) {
 792                 return 0;
 793             }
 794 
 795             @Override
 796             protected double computeMaxHeight(double width) {
 797                 return Double.MAX_VALUE;
 798             }
 799 
 800             @Override
 801             protected double computePrefHeight(double width) {
 802                 return getIndex() == 0 ? 100 - 5 * (Math.floorDiv((int) width - 200, 10)) : 100;
 803             }
 804         });
 805         flow.setCellCount(3);
 806         flow.recreateCells(); // This help to override layoutChildren() in flow.setCellCount()
 807         flow.getVbar().setPrefWidth(20); // Since Skins are not initialized, we set the pref width explicitly
 808         flow.requestLayout();
 809         pulse();
 810         assertEquals(300, flow.cells.get(0).getWidth(), 1e-100);
 811         assertEquals(50, flow.cells.get(0).getHeight(), 1e-100);
 812 
 813         flow.resize(200, 300);
 814 
 815         flow.requestLayout();
 816         pulse();
 817         assertEquals(200, flow.cells.get(0).getWidth(), 1e-100);
 818         assertEquals(100, flow.cells.get(0).getHeight(), 1e-100);
 819 
 820     }
 821 
 822     ////////////////////////////////////////////////////////////////////////////
 823     //
 824     //  Cell Life Cycle
 825     //
 826     ////////////////////////////////////////////////////////////////////////////
 827 
 828     @Test public void testCellLifeCycle_CellsAreCreatedOnLayout() {
 829         // when the flow was first created in setUp we do a layout()
 830         assertTrue("The cells didn't get created", flow.cells.size() > 0);
 831     }
 832 
 833 //    /**
 834 //     * During layout the order and contents of cells will change. We need
 835 //     * to make sure that CSS for cells is applied at this time. To test this,
 836 //     * I just set the position and perform a new pulse. Since layout happens
 837 //     * after the CSS updates are applied, if the test fails, then there will
 838 //     * be cells left in a state where they need their CSS applied.
 839 //     */
 840 //    @Test public void testCellLifeCycle_CSSUpdatesHappenDuringLayout() {
 841 //        flow.setPosition(.35);
 842 //        pulse();
 843 //        for (int i = 0; i < flow.cells.size(); i++) {
 844 //            IndexedCell cell = flow.cells.get(i);
 845 //            assertEquals(CssFlags.CLEAN, cell.impl_getCSSFlags());
 846 //        }
 847 //    }
 848 
 849     ////////////////////////////////////////////////////////////////////////////
 850     //
 851     //  Position
 852     //
 853     ////////////////////////////////////////////////////////////////////////////
 854 
 855     ////////////////////////////////////////////////////////////////////////////
 856     //
 857     //  Pixel Scrolling
 858     //
 859     ////////////////////////////////////////////////////////////////////////////
 860 
 861     ////////////////////////////////////////////////////////////////////////////
 862     //
 863     //  Cell Count Changes
 864     //
 865     ////////////////////////////////////////////////////////////////////////////
 866 
 867     // want to test that the view remains stable when the cell count changes
 868 
 869     // LIST VIEW: test that using a bunch of nodes as items to a ListView works
 870     // LIST VIEW: test that inserting an item moves the selected index to keep in sync
 871     // test that dynamically changing all of the contents causes them to refresh
 872 
 873     // test that when the number of cells change, that things are laid out
 874 //    @Test public void testCellCountChanges_FirstRowIsRemoved() {
 875 //
 876 //    }
 877 //
 878 //    @Test public void testCellCountChanges_MiddleRowIsRemoved() {
 879 //
 880 //    }
 881 //
 882 //    @Test public void testCellCountChanges_LastRowIsRemoved() {
 883 //
 884 //    }
 885 //
 886 ////    @Test public void testCellCountChanges_SelectedRowRemoved() {
 887 ////
 888 ////    }
 889 ////
 890 ////    @Test public void testCellCountChanges_NonSelectedRowRemoved() {
 891 ////
 892 ////    }
 893 //
 894 //    @Test public void testCellCountChanges_FirstRowIsAdded() {
 895 //
 896 //    }
 897 //
 898 //    @Test public void testCellCountChanges_MiddleRowIsAdded() {
 899 //
 900 //    }
 901 //
 902 //    @Test public void testCellCountChanges_LastRowIsAdded() {
 903 //
 904 //    }
 905 //
 906 ////    @Test public void testCellCountChanges_RowIsAddedBeforeSelectedRow() {
 907 ////
 908 ////    }
 909 ////
 910 ////    @Test public void testCellCountChanges_RowIsAddedAfterSelectedRow() {
 911 ////
 912 ////    }
 913 
 914     ////////////////////////////////////////////////////////////////////////////
 915     //
 916     //  VirtualFlow State Changes
 917     //
 918     ////////////////////////////////////////////////////////////////////////////
 919 
 920     /**
 921      * Tests that when the createCell method changes, it results in layout
 922      */
 923     @Test public void testCreateCellFunctionChangesResultInNeedsLayoutAndNoCellsAndNoAccumCell() {
 924         assertFalse(flow.isNeedsLayout());
 925         flow.getCellLength(49); // forces accum cell to be created
 926         assertNotNull("Accum cell was null", flow.accumCell);
 927         flow.setCellFactory(p -> new CellStub(flow));
 928         assertTrue(flow.isNeedsLayout());
 929         assertNull("accumCell didn't get cleared", flow.accumCell);
 930     }
 931 
 932 
 933     ////////////////////////////////////////////////////////////////////////////
 934     //
 935     //  Tests on specific functions
 936     //
 937     ////////////////////////////////////////////////////////////////////////////
 938 
 939     @Test public void test_getCellLength() {
 940         assertEquals(100, flow.getCellCount());
 941         for (int i = 0; i < 50; i++) {
 942             if (i != 29) assertEquals(25, flow.getCellLength(i), 0.0);
 943         }
 944         flow.setVertical(false);
 945         flow.requestLayout();
 946         pulse();
 947         assertEquals(100, flow.getCellCount());
 948         for (int i = 0; i < 50; i++) {
 949             if (i != 29) assertEquals("Bad index: " + i, 25, flow.getCellLength(i), 0.0);
 950         }
 951     }
 952 
 953     /*
 954     ** if we scroll the flow by a number of LINES,
 955     ** without having done anything to select a cell
 956     ** the flow should scroll.
 957     */ 
 958     @Test public void testInitialScrollEventActuallyScrolls() {
 959         /*
 960         ** re-initialize this, as it must be the first
 961         ** interaction with the flow
 962         */
 963         flow = new VirtualFlow();
 964         flow.setVertical(true);
 965         flow.setCellFactory(p -> new CellStub(flow) {
 966             @Override
 967             protected double computeMinWidth(double height) {
 968                 return computePrefWidth(height);
 969             }
 970 
 971             @Override
 972             protected double computeMaxWidth(double height) {
 973                 return computePrefWidth(height);
 974             }
 975 
 976             @Override
 977             protected double computePrefWidth(double height) {
 978                 return flow.isVertical() ? (c.getIndex() == 29 ? 200 : 100) : (c.getIndex() == 29 ? 100 : 25);
 979             }
 980 
 981             @Override
 982             protected double computeMinHeight(double width) {
 983                 return computePrefHeight(width);
 984             }
 985 
 986             @Override
 987             protected double computeMaxHeight(double width) {
 988                 return computePrefHeight(width);
 989             }
 990 
 991             @Override
 992             protected double computePrefHeight(double width) {
 993                 return flow.isVertical() ? (c.getIndex() == 29 ? 100 : 25) : (c.getIndex() == 29 ? 200 : 100);
 994             }
 995         });
 996         
 997         flow.setCellCount(100);
 998         flow.resize(300, 300);
 999         pulse();
1000        
1001         double originalValue = flow.getPosition();
1002 
1003         Event.fireEvent(flow, 
1004               new ScrollEvent(ScrollEvent.SCROLL,
1005                           0.0, -10.0, 0.0, -10.0,
1006                           false, false, false, false, true, false,
1007                           0, 0,
1008                           0, 0,
1009                           ScrollEvent.HorizontalTextScrollUnits.NONE, 0.0,
1010                           ScrollEvent.VerticalTextScrollUnits.LINES, -1.0,
1011                           0, null));
1012 
1013         assertTrue(originalValue != flow.getPosition());
1014     }
1015 
1016     @Test
1017     public void test_RT_36507() {
1018         flow = new VirtualFlow();
1019         flow.setVertical(true);
1020         // Worst case scenario is that the cells have height = 0.
1021         // The code should prevent creating more than 100 of these zero height cells
1022         // (since viewportLength is 100).
1023         // An "INFO: index exceeds maxCellCount" message should print out.
1024         flow.setCellFactory(p -> new CellStub(flow) {
1025             @Override
1026             protected double computeMaxHeight(double width) {
1027                 return 0;
1028             }
1029 
1030             @Override
1031             protected double computePrefHeight(double width) {
1032                 return 0;
1033             }
1034 
1035             @Override
1036             protected double computeMinHeight(double width) {
1037                 return 0;
1038             }
1039 
1040         });
1041         flow.setCellCount(10);
1042         flow.setViewportLength(100);
1043         flow.addLeadingCells(1, 0);
1044         flow.sheetChildren.addListener((InvalidationListener) (o) -> {
1045             int count = ((List) o).size();
1046             assertTrue(Integer.toString(count), count <= 100);
1047         });
1048         flow.addTrailingCells(true);
1049     }
1050 
1051     private int rt36556_instanceCount;
1052     @Test
1053     public void test_rt36556() {
1054         rt36556_instanceCount = 0;
1055         flow = new VirtualFlow();
1056         flow.setVertical(true);
1057         flow.setCellFactory(p -> {
1058             rt36556_instanceCount++;
1059             return new CellStub(flow);
1060         });
1061         flow.setCellCount(100);
1062         flow.resize(300, 300);
1063         pulse();
1064         final int cellCountAtStart = rt36556_instanceCount;
1065         flow.scrollPixels(10000);
1066         pulse();
1067         assertEquals(cellCountAtStart, rt36556_instanceCount);
1068         assertNull(flow.getVisibleCell(0));
1069         assertMinimalNumberOfCellsAreUsed(flow);
1070     }
1071 
1072     @Test
1073     public void test_rt36556_scrollto() {
1074         rt36556_instanceCount = 0;
1075         flow = new VirtualFlow();
1076         flow.setVertical(true);
1077         flow.setCellFactory(p -> {
1078             rt36556_instanceCount++;
1079             return new CellStub(flow);
1080         });
1081         flow.setCellCount(100);
1082         flow.resize(300, 300);
1083         pulse();
1084         final int cellCountAtStart = rt36556_instanceCount;
1085         flow.scrollTo(80);
1086         pulse();
1087         assertEquals(cellCountAtStart, rt36556_instanceCount);
1088         assertNull(flow.getVisibleCell(0));
1089         assertMinimalNumberOfCellsAreUsed(flow);
1090     }
1091     
1092     @Test
1093     public void test_RT39035() {
1094         flow.scrollPixels(250);
1095         pulse();
1096         flow.scrollPixels(500);
1097         pulse();
1098         assertTrue(flow.getPosition() < 1.0);
1099         assertMinimalNumberOfCellsAreUsed(flow);
1100     }
1101 
1102     @Test
1103     public void test_RT37421() {
1104         flow.setPosition(0.98);
1105         pulse();
1106         flow.scrollPixels(100);
1107         pulse();
1108         assertEquals(1.0, flow.getPosition(), 0.0);
1109         assertMinimalNumberOfCellsAreUsed(flow);
1110     }
1111 
1112     @Test
1113     public void test_RT39568() {
1114         flow.getHbar().setPrefHeight(16);
1115         flow.resize(50, flow.getHeight());
1116         flow.setPosition(1);
1117         pulse();
1118         assertTrue("The hbar should have been visible", flow.getHbar().isVisible());
1119         assertMinimalNumberOfCellsAreUsed(flow);
1120         assertEquals(flow.getViewportLength()-25.0, flow.cells.getLast().getLayoutY(), 0.0);
1121     }
1122 }
1123 
1124 class CellStub extends IndexedCell {
1125     String s;
1126     VirtualFlow flow;
1127 
1128     public CellStub(VirtualFlow flow) { init(flow); }
1129     public CellStub(VirtualFlow flow, String s) { init(flow); this.s = s; }
1130     
1131     private void init(VirtualFlow flow) {
1132         this.flow = flow;
1133         setSkin(new SkinStub<CellStub>(this));
1134         updateItem(this, false);
1135     }
1136 
1137     @Override
1138     public void updateIndex(int i) {
1139         super.updateIndex(i);
1140         
1141         s = "Item " + getIndex();
1142 //        updateItem(getIndex(), getIndex() >= flow.getCellCount());
1143     }
1144 }