1 /* 2 * Copyright (c) 2013, 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 package com.sun.javafx.scene.control.infrastructure; 26 27 import static org.junit.Assert.assertNotNull; 28 29 import javafx.geometry.Orientation; 30 import javafx.scene.Node; 31 import javafx.scene.control.*; 32 import javafx.scene.control.skin.ComboBoxListViewSkin; 33 import com.sun.javafx.scene.control.LabeledText; 34 import javafx.scene.control.skin.NestedTableColumnHeader; 35 import javafx.scene.control.skin.TableColumnHeader; 36 import javafx.scene.control.skin.TableHeaderRow; 37 import javafx.scene.control.skin.TableViewSkinBase; 38 import javafx.scene.control.skin.VirtualFlow; 39 import com.sun.javafx.scene.control.VirtualScrollBar; 40 import javafx.util.Callback; 41 import java.util.List; 42 43 import static org.junit.Assert.*; 44 45 public class VirtualFlowTestUtils { 46 47 public static void assertListContainsItemsInOrder(final List items, final Object... expected) { 48 assertEquals(expected.length, items.size()); 49 for (int i = 0; i < expected.length; i++) { 50 Object item = items.get(i); 51 assertEquals(expected[i], item); 52 } 53 } 54 55 public static void clickOnRow(final Control control, int row, KeyModifier... modifiers) { 56 clickOnRow(control, row, 1, false, modifiers); 57 } 58 59 public static void clickOnRow(final Control control, int row, int clickCount, KeyModifier... modifiers) { 60 clickOnRow(control, row, clickCount, false, modifiers); 61 } 62 63 public static void clickOnRow(final Control control, int row, boolean ignoreChildren, KeyModifier... modifiers) { 64 clickOnRow(control, row, 1, ignoreChildren, modifiers); 65 } 66 67 // ignore children allows clicking on the row, rather than a child in that row. 68 // This is good for testing, for example, TableRowBehavior 69 public static void clickOnRow(final Control control, int row, int clickCount, boolean ignoreChildren, KeyModifier... modifiers) { 70 IndexedCell cell = VirtualFlowTestUtils.getCell(control, row); 71 72 if (! ignoreChildren && ((cell instanceof TableRow) || (cell instanceof TreeTableRow))) { 73 for (Node n : cell.getChildrenUnmodifiable()) { 74 if (! (n instanceof IndexedCell)) { 75 continue; 76 } 77 IndexedCell<?> childCell = (IndexedCell<?>)n; 78 MouseEventFirer mouse = new MouseEventFirer(childCell); 79 mouse.fireMousePressAndRelease(clickCount, modifiers); 80 mouse.dispose(); 81 break; 82 } 83 } else { 84 if (ignoreChildren) { 85 // special case when we want to click on the row rather than its 86 // children (e.g. TableRow rather than its TableCell) 87 MouseEventFirer mouse = new MouseEventFirer(cell); 88 mouse.fireMousePressed(cell.getWidth(), cell.getHeight() / 2.0, modifiers); 89 mouse.fireMouseReleased(cell.getWidth(), cell.getHeight() / 2.0, modifiers); 90 mouse.dispose(); 91 } else { 92 MouseEventFirer mouse = new MouseEventFirer(cell); 93 mouse.fireMousePressAndRelease(clickCount, modifiers); 94 mouse.dispose(); 95 } 96 } 97 } 98 99 public static void assertRowsEmpty(final Control control, final int startRow, final int endRow) { 100 assertRows(control, startRow, endRow, true); 101 } 102 103 public static void assertRowsNotEmpty(final Control control, final int startRow, final int endRow) { 104 assertRows(control, startRow, endRow, false); 105 } 106 107 public static void assertCellEmpty(IndexedCell cell) { 108 if (cell instanceof TableRow || cell instanceof TreeTableRow) { 109 for (Node n : cell.getChildrenUnmodifiable()) { 110 if (! (n instanceof IndexedCell)) { 111 continue; 112 } 113 IndexedCell<?> childCell = (IndexedCell<?>)n; 114 assertCellEmpty(childCell); 115 } 116 } else { 117 final String text = cell.getText(); 118 assertTrue("Expected null, found '" + text + "'", text == null || text.isEmpty()); 119 120 final Node graphic = cell.getGraphic(); 121 assertTrue("Expected null graphic, found " + graphic, graphic == null); 122 } 123 } 124 125 public static void assertCellNotEmpty(IndexedCell cell) { 126 if (cell instanceof TableRow || cell instanceof TreeTableRow) { 127 for (Node n : cell.getChildrenUnmodifiable()) { 128 if (! (n instanceof IndexedCell)) { 129 continue; 130 } 131 IndexedCell<?> childCell = (IndexedCell<?>)n; 132 assertCellNotEmpty(childCell); 133 } 134 } else { 135 final String text = cell.getText(); 136 final Node graphic = cell.getGraphic(); 137 assertTrue("Expected a non-null text or graphic property", 138 (text != null && ! text.isEmpty()) || graphic != null); 139 } 140 } 141 142 private static void assertRows(final Control control, final int startRow, final int endRow, final boolean expectEmpty) { 143 Callback<IndexedCell<?>, Void> callback = indexedCell -> { 144 boolean hasChildrenCell = false; 145 for (Node n : indexedCell.getChildrenUnmodifiable()) { 146 if (! (n instanceof IndexedCell)) { 147 continue; 148 } 149 hasChildrenCell = true; 150 IndexedCell<?> childCell = (IndexedCell<?>)n; 151 152 if (expectEmpty) { 153 assertCellEmpty(childCell); 154 } else { 155 assertCellNotEmpty(childCell); 156 } 157 } 158 159 if (! hasChildrenCell) { 160 if (expectEmpty) { 161 assertCellEmpty(indexedCell); 162 } else { 163 assertCellNotEmpty(indexedCell); 164 } 165 } 166 return null; 167 }; 168 169 assertCallback(control, startRow, endRow, callback); 170 } 171 172 public static void assertCellTextEquals(final Control control, final int index, final String... expected) { 173 if (expected == null || expected.length == 0) return; 174 175 Callback<IndexedCell<?>, Void> callback = indexedCell -> { 176 if (indexedCell.getIndex() != index) return null; 177 178 if (expected.length == 1) { 179 assertEquals(expected[0], indexedCell.getText()); 180 } else { 181 int jump = 0; 182 for (int i = 0; i < expected.length; i++) { 183 Node childNode = indexedCell.getChildrenUnmodifiable().get(i + jump); 184 String text = null; 185 if (! (childNode instanceof IndexedCell)) { 186 jump++; 187 continue; 188 } 189 190 text = ((IndexedCell) childNode).getText(); 191 assertEquals(expected[i], text); 192 } 193 } 194 return null; 195 }; 196 197 assertCallback(control, index, index + 1, callback); 198 } 199 200 public static void assertTableCellTextEquals(final Control control, final int row, final int column, final String expected) { 201 Callback<IndexedCell<?>, Void> callback = indexedCell -> { 202 if (indexedCell.getIndex() != row) return null; 203 204 int _column = column; 205 206 // we need to account for TreeTableView having LabeledText node in the TreeTableRow 207 if (indexedCell instanceof TreeTableRow) { 208 _column++; 209 } 210 211 IndexedCell cell = (IndexedCell) indexedCell.getChildrenUnmodifiable().get(_column); 212 assertEquals(expected, cell.getText()); 213 return null; 214 }; 215 216 assertCallback(control, row, row + 1, callback); 217 } 218 219 // used by TreeView / TreeTableView to ensure the correct indentation 220 // (although note that it has only been developed so far for TreeView) 221 public static void assertLayoutX(final Control control, final int startRow, final int endRow, final double expectedLayoutX) { 222 Callback<IndexedCell<?>, Void> callback = indexedCell -> { 223 List<Node> childrenOfCell = indexedCell.getChildrenUnmodifiable(); 224 LabeledText labeledText = null; 225 for (int j = 0; j < childrenOfCell.size(); j++) { 226 Node child = childrenOfCell.get(j); 227 if (child instanceof LabeledText) { 228 labeledText = (LabeledText) child; 229 } 230 } 231 232 String error = "Element in row " + indexedCell.getIndex() + " has incorrect indentation. " 233 + "Expected " + expectedLayoutX + ", but found " + labeledText.getLayoutX(); 234 assertEquals(error, expectedLayoutX, labeledText.getLayoutX(), 0.0); 235 return null; 236 }; 237 238 assertCallback(control, startRow, endRow, callback); 239 } 240 241 public static int getCellCount(final Control control) { 242 return getVirtualFlow(control).getCellCount(); 243 } 244 245 public static IndexedCell getCell(final Control control, final int index) { 246 return getVirtualFlow(control).getCell(index); 247 } 248 249 public static IndexedCell getCell(final Control control, final int row, final int column) { 250 IndexedCell rowCell = getVirtualFlow(control).getCell(row); 251 if (column == -1) { 252 return rowCell; 253 } 254 255 int count = -1; 256 for (Node n : rowCell.getChildrenUnmodifiable()) { 257 if (! (n instanceof IndexedCell)) { 258 continue; 259 } 260 count++; 261 if (count == column) { 262 return (IndexedCell) n; 263 } 264 } 265 return null; 266 } 267 268 public static void assertCallback(final Control control, final int startRow, final int endRow, final Callback<IndexedCell<?>, Void> callback) { 269 final VirtualFlow<?> flow = getVirtualFlow(control); 270 final int sheetSize = flow.getCellCount(); 271 272 // NOTE: we used to only go to the end of the sheet, but now we go past that if 273 // endRow desires. This is to allow for us to test cells that are visually 274 // shown, but empty. 275 final int end = endRow == -1 ? sheetSize : endRow; //Math.min(endRow, sheetSize); 276 277 for (int row = startRow; row < end; row++) { 278 // old approach: 279 // callback.call((IndexedCell<?>)sheet.getChildren().get(row)); 280 281 // new approach: 282 callback.call(flow.getCell(row)); 283 } 284 } 285 286 public static void assertGraphicIsVisible(final Control control, int row) { 287 assertGraphicIsVisible(control, row, -1); 288 } 289 290 public static void assertGraphicIsVisible(final Control control, int row, int column) { 291 Cell cell = getCell(control, row, column); 292 Node graphic = cell.getGraphic(); 293 assertNotNull(graphic); 294 assertTrue(graphic.isVisible() && graphic.getOpacity() == 1.0); 295 } 296 297 public static void assertGraphicIsNotVisible(final Control control, int row) { 298 assertGraphicIsNotVisible(control, row, -1); 299 } 300 301 public static void assertGraphicIsNotVisible(final Control control, int row, int column) { 302 Cell cell = getCell(control, row, column); 303 Node graphic = cell.getGraphic(); 304 if (graphic == null) { 305 return; 306 } 307 308 assertNotNull(graphic); 309 assertTrue(!graphic.isVisible() || graphic.getOpacity() == 0.0); 310 } 311 312 public static void assertCellCount(final Control control, final int expected) { 313 assertEquals(getVirtualFlow(control).getCellCount(), expected); 314 } 315 316 public static void assertTableHeaderColumnExists(final Control control, final TableColumnBase column, boolean expected) { 317 TableHeaderRow headerRow = getTableHeaderRow(control); 318 319 NestedTableColumnHeader rootHeader = getNestedTableColumnHeader(headerRow); 320 boolean match = false; 321 for (TableColumnHeader header : rootHeader.getColumnHeaders()) { 322 match = column.equals(header.getTableColumn()); 323 if (match) break; 324 } 325 326 if (expected) { 327 assertTrue(match); 328 } else { 329 assertFalse(match); 330 } 331 } 332 333 public static boolean BLOCK_STAGE_LOADER_DISPOSE = false; 334 335 public static VirtualFlow<?> getVirtualFlow(Control control) { 336 StageLoader sl = null; 337 boolean stageLoaderCreated = false; 338 if (control.getScene() == null) { 339 sl = new StageLoader(control); 340 stageLoaderCreated = true; 341 } 342 343 VirtualFlow<?> flow; 344 if (control instanceof ComboBox) { 345 final ComboBox cb = (ComboBox) control; 346 final ComboBoxListViewSkin skin = (ComboBoxListViewSkin) cb.getSkin(); 347 control = (ListView) skin.getPopupContent(); 348 } 349 350 flow = (VirtualFlow<?>)control.lookup("#virtual-flow"); 351 352 if (stageLoaderCreated && sl != null && ! BLOCK_STAGE_LOADER_DISPOSE) { 353 sl.dispose(); 354 } 355 356 return flow; 357 } 358 359 public static VirtualScrollBar getVirtualFlowVerticalScrollbar(final Control control) { 360 return getVirtualFlowScrollbar(control, Orientation.VERTICAL); 361 } 362 363 public static VirtualScrollBar getVirtualFlowHorizontalScrollbar(final Control control) { 364 return getVirtualFlowScrollbar(control, Orientation.HORIZONTAL); 365 } 366 367 // this method must be called with the control having been shown in a 368 // stage (e.g. via StageLoader). Be sure to dispose too! 369 public static TableHeaderRow getTableHeaderRow(final Control control) { 370 if (control.getSkin() == null) { 371 throw new IllegalStateException("getTableHeaderRow requires the control to be visible in a stage"); 372 } 373 374 TableViewSkinBase<?,?,?,?,?> skin = (TableViewSkinBase) control.getSkin(); 375 TableHeaderRow headerRow = null; 376 for (Node n : skin.getChildren()) { 377 if (n instanceof TableHeaderRow) { 378 headerRow = (TableHeaderRow) n; 379 break; 380 } 381 } 382 383 return headerRow; 384 } 385 386 private static VirtualScrollBar getVirtualFlowScrollbar(final Control control, Orientation orientation) { 387 VirtualFlow<?> flow = getVirtualFlow(control); 388 VirtualScrollBar scrollBar = null; 389 for (Node child : flow.getChildrenUnmodifiable()) { 390 if (child instanceof VirtualScrollBar) { 391 if (((VirtualScrollBar)child).getOrientation() == orientation) { 392 scrollBar = (VirtualScrollBar) child; 393 } 394 } 395 } 396 397 return scrollBar; 398 } 399 400 public static TableColumnHeader getTableColumnHeader(Control table, TableColumnBase<?,?> column) { 401 TableHeaderRow headerRow = VirtualFlowTestUtils.getTableHeaderRow(table); 402 return findColumnHeader(getNestedTableColumnHeader(headerRow), column); 403 } 404 405 private static TableColumnHeader findColumnHeader(NestedTableColumnHeader nestedHeader, TableColumnBase<?,?> column) { 406 for (TableColumnHeader header : nestedHeader.getColumnHeaders()) { 407 if (header instanceof NestedTableColumnHeader) { 408 TableColumnHeader result = findColumnHeader((NestedTableColumnHeader)header, column); 409 if (result != null) { 410 return result; 411 } 412 } else { 413 if (column.equals(header.getTableColumn())) { 414 return header; 415 } 416 } 417 } 418 return null; 419 } 420 421 public static NestedTableColumnHeader getNestedTableColumnHeader(TableHeaderRow headerRow) { 422 NestedTableColumnHeader rootHeader = null; 423 424 for (Node n : headerRow.getChildren()) { 425 if (n instanceof NestedTableColumnHeader) { 426 rootHeader = (NestedTableColumnHeader) n; 427 } 428 } 429 return rootHeader; 430 } 431 }