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